Add a way to change the value of ValueElement without triggering handlers #3999
Replies: 3 comments 2 replies
-
Hi @flonou, You could add event handlers once the data is initialized. Does that help in your case? age = ui.number(label='Age')
def initialize_data():
age.value = 10
age.on_value_change(lambda: ui.notify(f'Age changed to {age.value}'))
ui.button('Initialize Data', on_click=initialize_data) Adding a Maybe disabling change handlers could be done like this: with age.change_handlers_disabled:
age.value = 10 But the consequence is a bit unclear. What if you call |
Beta Was this translation helpful? Give feedback.
-
Hi, Disabling the handlers could be a nice solution with clear intent from the developer to not trigger anything One could argue that it could roughly be the same as def set_value_without_notify (self, new_value):
with self.change_handlers_disabled:
self.value = new_value I also guess there is some confusion between |
Beta Was this translation helpful? Give feedback.
-
I was about to make the same suggestion as @flonou. It took me a while to find this conversation. I hope I'm not too late to add some input. Some months ago I wrote a custom annotation tool for my employer using NiceGUI. I know it was probably not really idiomatic, but I quickly realize that defining my UI components as classes was the most ergonomic for me. Instead of relying on binding (which introduce a lot of hardcoded string) or using refreshable (which I didn't know at the time), I explicitly wrote update statements using set_value (and similar methods). To prevent circular triggering, I used a boolean flag to disable my handlers when necessary. Eventually, the flag bookkeeping was extracted into three helpers functions where my past self left a TODO, asking me to revisit the question. Here is the bookkeeping code with a simple example of "class component". from __future__ import annotations
import dataclasses
from contextlib import contextmanager
from typing import Any, TypeVar
from nicegui import ui
####################################################################################################
# Callback disabling (bookkeeping previously mentioned)
# When using nicegui, it's easy to create callback loops looking like this
#
# value change in client -> callback -> elem.set_value(...) -> callback -> ...
#
# When writing ui components as classes, an easy workaround is to return early in callback methods
# based on a boolean attribute. Hence, the following decorators and context manager.
# TODO: Continue investigations on whether nicegui already provides something similar.
_MethodType = TypeVar("_MethodType")
_hidden_attribute = "__callbacks_disabled"
def callback(method: _MethodType) -> _MethodType:
def wrapper(self, *args, **kwargs):
if getattr(self, _hidden_attribute, False):
return
return method(self, *args, **kwargs)
return wrapper
@contextmanager
def disable_callbacks(instance: Any) -> None:
if getattr(instance, _hidden_attribute, False):
yield
else:
setattr(instance, _hidden_attribute, True)
yield
setattr(instance, _hidden_attribute, False)
def disable_callbacks_for_method(method: _MethodType) -> _MethodType:
def wrapper(self, *args, **kwargs):
with disable_callbacks(self):
result = method(self, *args, **kwargs)
return result
return wrapper
####################################################################################################
# Example of UI defined as a class
@dataclasses.dataclass
class State:
a: int
b: bool
name_to_state = {
"bob": State(a=55, b=True),
"alice": State(a=48, b=False),
}
class Example:
def __init__(self) -> None:
self.name_picker = ui.select(options=list(name_to_state.keys()), on_change=self.select)
self.a = ui.number(on_change=self.update)
self.b = ui.switch(on_change=self.update)
self.name_picker.set_value("bob") # will trigger select
@disable_callbacks_for_method
def select(self) -> None:
print("select")
state = name_to_state[self.name_picker.value]
self.a.set_value(state.a)
self.b.set_value(state.b)
@callback
def update(self) -> None:
print("update")
state = name_to_state[self.name_picker.value]
state.a = self.a.value
state.b = self.b.value
Example()
ui.run(reload=False) @falkoschindler does this "class style" appears like a bad idea to you? Personally, I like having all the layout stuff in the |
Beta Was this translation helpful? Give feedback.
-
In my application, I sometimes need to create the UI and set the data afterwards.
Doing so will trigger value changed handlers.
Unless I missed something, there is no way to silently change a value.
Something like :
ui_element.set_value_without_notify(value)
My current workaround is to remove all change handlers temporarily but I feel a simpler way should exist (or maybe it does and I missed it ? )
Beta Was this translation helpful? Give feedback.
All reactions