From 9a9d534b6d7ff82e37c62ff54d4013879ef5e31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:44:26 +0000 Subject: [PATCH] Document DOMNode.watch (#3724) * Document DOMNode.watch * Create standalone example. Addresses review comment: https://github.com/Textualize/textual/pull/3724#pullrequestreview-1744357054. * Create standalone example. Addresses review comment: https://github.com/Textualize/textual/pull/3724#pullrequestreview-1744357054. --- .../guide/reactivity/dynamic_watch.py | 35 +++++++++++++++++++ docs/guide/reactivity.md | 25 +++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 docs/examples/guide/reactivity/dynamic_watch.py diff --git a/docs/examples/guide/reactivity/dynamic_watch.py b/docs/examples/guide/reactivity/dynamic_watch.py new file mode 100644 index 0000000000..bb231fa6e5 --- /dev/null +++ b/docs/examples/guide/reactivity/dynamic_watch.py @@ -0,0 +1,35 @@ +from textual.app import App, ComposeResult +from textual.reactive import reactive +from textual.widget import Widget +from textual.widgets import Button, Label, ProgressBar + + +class Counter(Widget): + DEFAULT_CSS = "Counter { height: auto; }" + counter = reactive(0) # (1)! + + def compose(self) -> ComposeResult: + yield Label() + yield Button("+10") + + def on_button_pressed(self) -> None: + self.counter += 10 + + def watch_counter(self, counter_value: int): + self.query_one(Label).update(str(counter_value)) + + +class WatchApp(App[None]): + def compose(self) -> ComposeResult: + yield Counter() + yield ProgressBar(total=100, show_eta=False) + + def on_mount(self): + def update_progress(counter_value: int): # (2)! + self.query_one(ProgressBar).update(progress=counter_value) + + self.watch(self.query_one(Counter), "counter", update_progress) # (3)! + + +if __name__ == "__main__": + WatchApp().run() diff --git a/docs/guide/reactivity.md b/docs/guide/reactivity.md index 4a7c2cd41b..aa032d3562 100644 --- a/docs/guide/reactivity.md +++ b/docs/guide/reactivity.md @@ -202,6 +202,31 @@ Textual only calls watch methods if the value of a reactive attribute _changes_. If the newly assigned value is the same as the previous value, the watch method is not called. You can override this behaviour by passing `always_update=True` to `reactive`. + +### Dynamically watching reactive attributes + +You can programmatically add watchers to reactive attributes with the method [`watch`][textual.dom.DOMNode.watch]. +This is useful when you want to react to changes to reactive attributes for which you can't edit the watch methods. + +The example below shows a widget `Counter` that defines a reactive attribute `counter`. +The app that uses `Counter` uses the method `watch` to keep its progress bar synced with the reactive attribute: + +=== "dynamic_watch.py" + + ```python hl_lines="9 28-29 31" + --8<-- "docs/examples/guide/reactivity/dynamic_watch.py" + ``` + + 1. `counter` is a reactive attribute defined inside `Counter`. + 2. `update_progress` is a custom callback that will update the progress bar when `counter` changes. + 3. We use the method `watch` to set `update_progress` as an additional watcher for the reactive attribute `counter`. + +=== "Output" + + ```{.textual path="docs/examples/guide/reactivity/dynamic_watch.py" press="enter,enter,enter"} + ``` + + ## Compute methods Compute methods are the final superpower offered by the `reactive` descriptor. Textual runs compute methods to calculate the value of a reactive attribute. Compute methods begin with `compute_` followed by the name of the reactive value.