Linked param ListSelector objects not updatting

This question is very similar to the GoogleMapViewer example and the related question on stackoverflow. I have been studying that example and would expect the code below to work, but it is not updating as I would expect.

There are a few differences between the example and my code:

  • I am using two ListSelector parameters rather than the ObjectSelector to obtain lists of items from the selection widgets rather than a single value.
  • I’m using a list parameter, selector_objects, that is updated by the method watching the join_mselect ListSelector parameter to update the selector ListSelector parameter rather than a static dictionary attribute.

The first version of the join_mselect_callback that is commented out and returns the selector_objects attribute behaves as expected by returning an updated list every time I make a selection of more than two items from the widget display of the join_mselect attribute.

The issue that I am having trouble resolving is that the second version of join_mselect_callback (with watch=True) does not cause the widget display of the selector to update automatically. I’ve noticed the following as I tried to determine why selector was not updating:

  • The selector_objects list does update as expected in the background.
  • If I set the objects of of selector, then the widget does update and it updates again if I make a selection from join_mselect. But, when I make a second selection from join_mselect it does not update. I’ve added a sequence of screenshots of this below the code section.
import panel as pn
import param
pn.extension()

lst = ['a', 'b', 'c']

class DynamicListSelector(param.Parameterized):
    selector_objects = param.List()
    join_mselect = param.ListSelector(default=[], objects=lst)
    selector = param.ListSelector(default=[], objects=lst)
    
    def __init__(self, **param):
        super().__init__(**param)
        self.selector_objects = lst
    
    # returns a modified list that updates as expected when selecting from join_mselect
#     @param.depends('join_mselect')
#     def join_mselect_callback(self):
#         if len(self.join_mselect) > 1:
#             self.selector_objects.append(self.join_mselect)
#             return self.selector_objects

    # selector_objects updates, but the selector widget does not update
    @param.depends('join_mselect', watch=True)
    def join_mselect_callback(self):
        if len(self.join_mselect) > 1:
            self.selector_objects.append(self.join_mselect)
            self.param['selector'].objects = self.selector_objects

example = DynamicListSelector()
# used with first version of join_mselect_callback where watch=False
# pn.Row(example.join_mselect_callback, example.param.join_mselect)

# use with second version of join_mselsect_callback where watch=True
pn.Row(example.param.selector, example.param.join_mselect)

Sequential screenshots of the unexpected behavior:
Selection of ‘a’ and ‘b’ caused the list attribute to update, but the widget does note update.

Setting the objects of the selector causes the widget to update.

Now, selecting from join mselect does cause the selector to update:

Selecting from join mselect a second time does not cause the selector to update, but like the initial step the list attribute does update.

After enough fiddling with this, I was able to create a version that behaves as I wanted.

I changed the callback function that updates the options of the selector parameter to use a variable within the function that is a copy of the selector_objects list rather than setting the objects of the selector parameter to the selector_objects parameter.

I think this resolved the issue because panel was not registering a change when the selector_objects list was appended to, but does register a change when the objects of selector (sorry that naming is confusing) is set to an actual new python list object in memory.

import panel as pn
import param

pn.extension()

lst = ['a', 'b', 'c']

class DynamicListSelector(param.Parameterized):
    selector_objects = param.List()
    join_mselect = param.ListSelector(default=[], objects=lst)
    selector = param.ListSelector(default=[], objects=lst)
    
    def __init__(self, **param):
        super().__init__(**param)
        self.selector_objects = lst

    @param.depends('join_mselect', watch=True)
    def join_mselect_callback(self):
        if len(self.join_mselect) > 1:
            temp_list = self.selector_objects.copy()
            temp_list.append(self.join_mselect)
            self.param['selector'].objects = temp_list
            self.selector_objects = temp_list

example = DynamicListSelector()
pn.Row(example.param.selector, example.param.join_mselect)
1 Like