diff --git a/panel/io/state.py b/panel/io/state.py index 8afc6f6d09..bcd0a45839 100644 --- a/panel/io/state.py +++ b/panel/io/state.py @@ -42,6 +42,9 @@ class _state(param.Parameterized): # An index of all currently active views _views = {} + # For templates to keep reference to their main root + _fake_roots = [] + # An index of all currently active servers _servers = {} diff --git a/panel/layout.py b/panel/layout.py index 8a3c79ebc3..6895404661 100644 --- a/panel/layout.py +++ b/panel/layout.py @@ -19,6 +19,7 @@ from bokeh.models.widgets import Tabs as BkTabs, Panel as BkPanel from .io.model import hold +from .io.state import state from .util import param_name, param_reprs from .viewable import Layoutable, Reactive @@ -190,6 +191,8 @@ def _process_param_change(self, params): return super(ListPanel, self)._process_param_change(params) def _cleanup(self, root): + if root.ref['id'] in state._fake_roots: + state._fake_roots.remove(root.ref['id']) super(ListPanel, self)._cleanup(root) for p in self.objects: p._cleanup(root) @@ -652,14 +655,17 @@ def _process_close(self, ref, attr, old, new): return old, new def _comm_change(self, doc, ref, attr, old, new): - if attr in self._changing: - self._changing.remove(attr) + if attr in self._changing.get(ref, []): + self._changing[ref].remove(attr) return if attr == 'tabs': old, new = self._process_close(ref, attr, old, new) super(Tabs, self)._comm_change(doc, ref, attr, old, new) def _server_change(self, doc, ref, attr, old, new): + if attr in self._changing.get(ref, []): + self._changing[ref].remove(attr) + return if attr == 'tabs': old, new = self._process_close(ref, attr, old, new) super(Tabs, self)._server_change(doc, ref, attr, old, new) diff --git a/panel/pane/base.py b/panel/pane/base.py index 81c966e7da..03049f670b 100644 --- a/panel/pane/base.py +++ b/panel/pane/base.py @@ -179,6 +179,8 @@ def _update_object(self, ref, doc, root, parent, comm): def _update_pane(self, *events): for ref, (_, parent) in self._models.items(): + if ref not in state._views or ref in state._fake_roots: + continue viewable, root, doc, comm = state._views[ref] if comm or state._unblocked(doc): with unlocked(): diff --git a/panel/template.py b/panel/template.py index da96047f2c..b4c76d8f4a 100644 --- a/panel/template.py +++ b/panel/template.py @@ -128,6 +128,7 @@ def _init_doc(self, doc=None, comm=None, title=None, notebook=False): model.name = name model.tags = tags add_to_doc(model, doc, hold=bool(comm)) + state._fake_roots.append(ref) state._views[ref] = (col, preprocess_root, doc, comm) col._preprocess(preprocess_root) diff --git a/panel/viewable.py b/panel/viewable.py index 8c8c238e2b..b062af5c06 100644 --- a/panel/viewable.py +++ b/panel/viewable.py @@ -469,16 +469,16 @@ def _repr_mimebundle_(self, include=None, exclude=None): return render_mimebundle(model, doc, comm, manager) def _comm_change(self, doc, ref, attr, old, new): - if attr in self._changing: - self._changing.remove(attr) + if attr in self._changing.get(ref, []): + self._changing[ref].remove(attr) return with hold(doc): self._process_events({attr: new}) def _server_change(self, doc, ref, attr, old, new): - if attr in self._changing: - self._changing.remove(attr) + if attr in self._changing.get(ref, []): + self._changing[ref].remove(attr) return state._locks.clear() @@ -735,21 +735,21 @@ def __init__(self, **params): self._callbacks = [] self._links = [] self._link_params() - self._changing = [] + self._changing = {} #---------------------------------------------------------------- # Callback API #---------------------------------------------------------------- def _update_model(self, events, msg, root, model, doc, comm): - self._changing = [ + self._changing[root.ref['id']] = [ attr for attr, value in msg.items() if not model.lookup(attr).property.matches(getattr(model, attr), value) ] try: model.update(**msg) finally: - self._changing = [] + del self._changing[root.ref['id']] def param_change(self, *events): msgs = [] @@ -764,7 +764,7 @@ def param_change(self, *events): return for ref, (model, parent) in self._models.items(): - if ref not in state._views: + if ref not in state._views or ref in state._fake_roots: continue viewable, root, doc, comm = state._views[ref] if comm or not doc.session_context or state._unblocked(doc): diff --git a/panel/widgets/base.py b/panel/widgets/base.py index f4af4af2ba..2a7ef8f7c7 100644 --- a/panel/widgets/base.py +++ b/panel/widgets/base.py @@ -68,6 +68,8 @@ def _manual_update(self, events, model, doc, root, parent, comm): def _update_widget(self, *events): for ref, (model, parent) in self._models.items(): + if ref not in state._views or ref in state._fake_roots: + continue viewable, root, doc, comm = state._views[ref] if comm or state._unblocked(doc): with unlocked():