Add a way to bind ui.select.options to a list/dict instead of manually calling set_options #3691
Replies: 3 comments
-
Hi @cartographer1, Binding to collections is tricky, because it can get rather expensive to detect changes by comparing previous and current values. But for this purpose we developed "observable collections", located in the (currently undocumented) options = observables.ObservableList(['A', 'B', 'C'], on_change=lambda e: select.set_options(e.sender))
select = ui.select(options)
ui.button('Add option', on_click=lambda: options.append('D')) |
Beta Was this translation helpful? Give feedback.
-
Good stuff! Here's how I bound my select options to a dict in my ViewModel: class MyViewModel():
mydict = observables.ObservableDict()
def update_mydict(self):
new_data = some_new_dict_data_from_somewhere()
dict.clear(self.mydict) # skips _handle_change => done after update
self.mydict.update(new_data)
model = MyViewModel()
my_select = ui.select(model.mydict)
model.mydict.on_change(lambda e : my_select.set_options(e.sender)) The interesting part is that I don't just add an item to the dict, but replace the whole thing with updated values. This needs to be done in-place, using the existing With a list that should be easier, since you can do |
Beta Was this translation helpful? Give feedback.
-
I'm posting my solution for dict: class DictWrapper:
"""
A class that wraps a dictionary and allows to overwrite it with a new dictionary by writing to a property named "value".
This is used to allow binding to a dictionary, which is not implemented in NiceGUI (yet)
"""
def __init__(self, initial_dict: dict, merge: bool = False, on_write: Optional[dict[str, Callable[[Any], Any]]] = None):
self._dict = initial_dict
self._merge = merge
self._on_write = on_write or {}
def _update_value(self, value: dict) -> None:
# print(f'{self._dict} -> {value}')
if value is not None:
# remove keys from self._dict which are not in value
if not self._merge:
for key in [key for key in self._dict if key not in value]:
del self._dict[key]
self._dict.update(value)
for key, transform in self._on_write.items():
try:
# transform only those keys that are present in the new value, others are already transformed
self._dict[key] = transform(value[key])
except KeyError:
pass
# print(self._dict)
# property "value" is used to overwrite the dictionary with a new one
value = property(lambda self: self._dict, _update_value)
My specific use case was that I didn't need to bind |
Beta Was this translation helpful? Give feedback.
-
I'm surprised to see nobody has asked for this... Yes, if your UI elements is just in order, you can just assign the selection box to a variable and call set_options BUT things get very annoying if your UI elements are declared out of order.
As for possible workaround,
I supposed that I could also just bind it a dummy ui element with a bindable property and subscribe a listener on change to call set_options on my ui.select though I haven't found which one to useBeta Was this translation helpful? Give feedback.
All reactions