holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
410 stars 69 forks source link

Triggering a function in one instance based on parameter changes in another #930

Closed droumis closed 3 months ago

droumis commented 3 months ago

ALL software version info

param 2.0.2 panel 1.3.8

Description of expected behavior and the observed behavior

Triggering a function in one instance based on parameter changes in another. The controlling widget is in the parent (or other instance).

In the following MRE, how to get 'triggered in Child B' to print when triggered from the sliders made in Parent or child B.

Complete, minimal, self-contained example code that reproduces the issue

import param
from panel.viewable import Viewer
import panel as pn; pn.extension()

class Parent(Viewer):
    slider_vals = param.Number(default=2.0, bounds=(0.1, 5.0))

    trig = param.Event()

    def __init__(self, **params):
        super().__init__(**params)

    @param.depends('slider_vals', watch=True)
    def trigger(self):
        # self.trig = True
        self.param.trigger('trig')

    @param.depends('trig', watch=True)
    def print_trig_P(self):
        print('triggered in Parent')

    def __controls__(self):
        return pn.Row(pn.widgets.FloatSlider.from_param(self.param.slider_vals))

class ChildA(Parent):

    def __init__(self, **params):
        super().__init__(**params)

    def __controls__(self):
        return pn.Row(pn.widgets.FloatSlider.from_param(self.param.slider_vals))

class ChildB(Parent):

    def __init__(self, **params):
        super().__init__(**params)

    @param.depends('trig', watch=True)
    def print_trig_B(self):
        print('triggered in Child B')

    def __controls__(self):
        return pn.Row(pn.widgets.FloatSlider.from_param(self.param.slider_vals))

    @param.depends('trig', watch=True)
    def __panel__(self):
        return pn.pane.Markdown('Child B triggered!')

child_a = ChildA()
parent = Parent()
child_b = ChildB()

pn.Column(child_a.__controls__(), parent.__controls__(), child_b.__controls__()).servable()
droumis commented 3 months ago

Thinking about it more.. I guess this is probably beyond what param/panel could reasonably provide and would probably require an ad hoc global event dispatcher of some sort.

I think I just need to adjust my mental model of how to architect an advanced panel app... just don't exactly understand how yet

droumis commented 3 months ago

Here is a a solution.. creating another class as the global state and then passing an instance of that state object to each of the Parents, Child instances. Now, triggering any of the slider widgets results in printing 'triggered in Parent' 3 times and 'triggered in Child B' once... which I guess is the unavoidable compromise for this solution. muy complicado

import param
from panel.viewable import Viewer
import panel as pn; pn.extension()

class SharedState(param.Parameterized):
    slider_vals = param.Number(default=2.0, bounds=(0.1, 5.0))
    trig = param.Event()

class Parent(Viewer):

    state = param.ClassSelector(class_=SharedState)

    def __init__(self, **params):
        super().__init__(**params)

    @param.depends('state.slider_vals', watch=True)
    def trigger(self):
        self.state.trig = True
        # self.param.trigger('state.trig')

    @param.depends('state.trig', watch=True)
    def print_trig_P(self):
        print('triggered in Parent')

    def __controls__(self):
        return pn.Row(pn.widgets.FloatSlider.from_param(self.state.param.slider_vals))

class ChildA(Parent):

    def __init__(self, **params):
        super().__init__(**params)

    def __controls__(self):
        return pn.Row(pn.widgets.FloatSlider.from_param(self.state.param.slider_vals))

class ChildB(Parent):

    def __init__(self, **params):
        super().__init__(**params)

    @param.depends('state.trig', watch=True)
    def print_trig_B(self):
        print('triggered in Child B')

    def __controls__(self):
        return pn.Row(pn.widgets.FloatSlider.from_param(self.state.param.slider_vals))

    @param.depends('state.trig', watch=True)
    def __panel__(self):
        return pn.pane.Markdown('Child B triggered!')

state = SharedState()
parent = Parent(state=state)
child_a = ChildA(state=state)
child_b = ChildB(state=state)

pn.Column(child_a.__controls__(), parent.__controls__(), child_b.__controls__()).servable()