Repository: holoviz/panel

Issue:
pn.param mapping of boolean to toggle fails with param.depends

#### ALL software version info

I am working on the branch "static_serve" on the panel_dev conda environment. 
panel                     0.10.0a2.post9+g643033f           dev_0                 <develop>
param                    1.10.0a2                                      py_0                    pyviz/label/dev
parameterized       0.7.4                                            pyh9f0ad1d_0    conda-forge
(Windows, Chrome browser)

#### Description of expected behavior and the observed behavior

I expect the toggle and the checkbox to work in the same way when rendered via pn.param. However, only the checkbox seems attach the param.depends method to itself. 
No error message occurs. The method "change_toggle" is just never called. 

#### Complete, minimal, self-contained example code that reproduces the issue

```
import panel as pn
import param


class BugTester(param.Parameterized):

    bool_toggle = param.Boolean(default=False, label='toggle')
    bool_checkbox = param.Boolean(default=False, label='checkbox')

    def __init__(self, **params):
        super().__init__(**params)

    def view(self):
        widgets = {
            'bool_toggle': pn.widgets.Toggle,
            'bool_checkbox': pn.widgets.Checkbox}
        return pn.Param(self.param, widgets=widgets)

    @param.depends('bool_toggle', watch=True)
    def change_toggle(self):
        print('toggle changed')

    @param.depends('bool_checkbox', watch=True)
    def change_checkbox(self):
        print('checkbox changed')


if __name__ == "__main__":
    bug = BugTester()
    pn.serve(bug.view(), port=5050)
```






Discussion:
If you run `bug.bool_toggle = True` you should see that the callback `change_toggle` is executed. It means the parameter `bool_toggle` and the toggle widget are not synchronized.

Thanks @lvankouwen for reporting this bug!

Resolving patch:
diff --git a/panel/param.py b/panel/param.py
--- a/panel/param.py
+++ b/panel/param.py
@@ -375,86 +375,84 @@ def widget(self, p_name):
         widget._param_pane = self
 
         watchers = self._callbacks
-        if isinstance(widget, Toggle):
-            pass
+
+        def link_widget(change):
+            if p_name in self._updating:
+                return
+            try:
+                self._updating.append(p_name)
+                self.object.param.set_param(**{p_name: change.new})
+            finally:
+                self._updating.remove(p_name)
+
+        if isinstance(p_obj, param.Action):
+            def action(change):
+                value(self.object)
+            watcher = widget.param.watch(action, 'clicks')
         else:
-            def link_widget(change):
-                if p_name in self._updating:
-                    return
-                try:
-                    self._updating.append(p_name)
-                    self.object.param.set_param(**{p_name: change.new})
-                finally:
-                    self._updating.remove(p_name)
-
-            if isinstance(p_obj, param.Action):
-                def action(change):
-                    value(self.object)
-                watcher = widget.param.watch(action, 'clicks')
+            watcher = widget.param.watch(link_widget, 'value')
+        watchers.append(watcher)
+
+        def link(change, watchers=[watcher]):
+            updates = {}
+            if change.what == 'constant':
+                updates['disabled'] = change.new
+            elif change.what == 'precedence':
+                if (change.new < self.display_threshold and
+                    widget in self._widget_box.objects):
+                    self._widget_box.pop(widget)
+                elif change.new >= self.display_threshold:
+                    precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence
+                    params = self._ordered_params
+                    if self.show_name:
+                        params.insert(0, '_title')
+                    widgets = []
+                    for k in params:
+                        if precedence(k) is None or precedence(k) >= self.display_threshold:
+                            widgets.append(self._widgets[k])
+                    self._widget_box.objects = widgets
+                return
+            elif change.what == 'objects':
+                updates['options'] = p_obj.get_range()
+            elif change.what == 'bounds':
+                start, end = p_obj.get_soft_bounds()
+                updates['start'] = start
+                updates['end'] = end
+            elif change.what == 'step':
+                updates['step'] = p_obj.step
+            elif change.what == 'label':
+                updates['name'] = p_obj.label
+            elif p_name in self._updating:
+                return
+            elif isinstance(p_obj, param.Action):
+                prev_watcher = watchers[0]
+                widget.param.unwatch(prev_watcher)
+                def action(event):
+                    change.new(self.object)
+                watchers[0] = widget.param.watch(action, 'clicks')
+                idx = self._callbacks.index(prev_watcher)
+                self._callbacks[idx] = watchers[0]
+                return
             else:
-                watcher = widget.param.watch(link_widget, 'value')
-            watchers.append(watcher)
-
-            def link(change, watchers=[watcher]):
-                updates = {}
-                if change.what == 'constant':
-                    updates['disabled'] = change.new
-                elif change.what == 'precedence':
-                    if (change.new < self.display_threshold and
-                        widget in self._widget_box.objects):
-                        self._widget_box.pop(widget)
-                    elif change.new >= self.display_threshold:
-                        precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence
-                        params = self._ordered_params
-                        if self.show_name:
-                            params.insert(0, '_title')
-                        widgets = []
-                        for k in params:
-                            if precedence(k) is None or precedence(k) >= self.display_threshold:
-                                widgets.append(self._widgets[k])
-                        self._widget_box.objects = widgets
-                    return
-                elif change.what == 'objects':
-                    updates['options'] = p_obj.get_range()
-                elif change.what == 'bounds':
-                    start, end = p_obj.get_soft_bounds()
-                    updates['start'] = start
-                    updates['end'] = end
-                elif change.what == 'step':
-                    updates['step'] = p_obj.step
-                elif change.what == 'label':
-                    updates['name'] = p_obj.label
-                elif p_name in self._updating:
-                    return
-                elif isinstance(p_obj, param.Action):
-                    prev_watcher = watchers[0]
-                    widget.param.unwatch(prev_watcher)
-                    def action(event):
-                        change.new(self.object)
-                    watchers[0] = widget.param.watch(action, 'clicks')
-                    idx = self._callbacks.index(prev_watcher)
-                    self._callbacks[idx] = watchers[0]
-                    return
-                else:
-                    updates['value'] = change.new
-
-                try:
-                    self._updating.append(p_name)
-                    widget.param.set_param(**updates)
-                finally:
-                    self._updating.remove(p_name)
-
-            # Set up links to parameterized object
-            watchers.append(self.object.param.watch(link, p_name, 'constant'))
-            watchers.append(self.object.param.watch(link, p_name, 'precedence'))
-            watchers.append(self.object.param.watch(link, p_name, 'label'))
-            if hasattr(p_obj, 'get_range'):
-                watchers.append(self.object.param.watch(link, p_name, 'objects'))
-            if hasattr(p_obj, 'get_soft_bounds'):
-                watchers.append(self.object.param.watch(link, p_name, 'bounds'))
-            if 'step' in kw:
-                watchers.append(self.object.param.watch(link, p_name, 'step'))
-            watchers.append(self.object.param.watch(link, p_name))
+                updates['value'] = change.new
+
+            try:
+                self._updating.append(p_name)
+                widget.param.set_param(**updates)
+            finally:
+                self._updating.remove(p_name)
+
+        # Set up links to parameterized object
+        watchers.append(self.object.param.watch(link, p_name, 'constant'))
+        watchers.append(self.object.param.watch(link, p_name, 'precedence'))
+        watchers.append(self.object.param.watch(link, p_name, 'label'))
+        if hasattr(p_obj, 'get_range'):
+            watchers.append(self.object.param.watch(link, p_name, 'objects'))
+        if hasattr(p_obj, 'get_soft_bounds'):
+            watchers.append(self.object.param.watch(link, p_name, 'bounds'))
+        if 'step' in kw:
+            watchers.append(self.object.param.watch(link, p_name, 'step'))
+        watchers.append(self.object.param.watch(link, p_name))
 
         options = kwargs.get('options', [])
         if isinstance(options, dict):


Test patch:
diff --git a/panel/tests/test_param.py b/panel/tests/test_param.py
--- a/panel/tests/test_param.py
+++ b/panel/tests/test_param.py
@@ -363,15 +363,21 @@ class Test(param.Parameterized):
 
 def test_action_param(document, comm):
     class Test(param.Parameterized):
-        a = param.Action(lambda x: x.b.append(1))
-        b = param.List(default=[])
+        a = param.Action(lambda x: setattr(x, 'b', 2))
+        b = param.Number(default=1)
 
     test = Test()
     test_pane = Pane(test)
     model = test_pane.get_root(document, comm=comm)
 
-    slider = model.children[1]
-    assert isinstance(slider, Button)
+    button = model.children[1]
+    assert isinstance(button, Button)
+
+    # Check that the action is actually executed
+    pn_button = test_pane.layout[1]
+    pn_button.clicks = 1
+
+    assert test.b == 2
 
 
 def test_explicit_params(document, comm):