diff --git a/panel/io/datamodel.py b/panel/io/datamodel.py index 445408c961..7a6bd521ea 100644 --- a/panel/io/datamodel.py +++ b/panel/io/datamodel.py @@ -199,7 +199,10 @@ def create_linked_datamodel(obj, root=None): else: _DATA_MODELS[cls] = model = construct_data_model(obj) properties = model.properties() - model = model(**{k: v for k, v in obj.param.values().items() if k in properties}) + props = {k: v for k, v in obj.param.values().items() if k in properties} + if root: + props['name'] = f"{root.ref['id']}-{id(obj)}" + model = model(**props) _changing = [] def cb_bokeh(attr, old, new): diff --git a/panel/io/notifications.py b/panel/io/notifications.py index abbd0cfb62..b9c7e46770 100644 --- a/panel/io/notifications.py +++ b/panel/io/notifications.py @@ -34,6 +34,8 @@ class Notification(param.Parameterized): notification_type = param.String(default=None, constant=True, label='type') + _rendered = param.Boolean(default=False) + _destroyed = param.Boolean(default=False) def destroy(self) -> None: @@ -194,46 +196,48 @@ def __css__(cls): }) """, "notifications": """ - var notification = state.current || data.notifications[data.notifications.length-1] - if (notification._destroyed) { - return - } - var config = { - duration: notification.duration, - type: notification.notification_type, - message: notification.message - } - if (notification.background != null) { - config.background = notification.background; - } - if (notification.icon != null) { - config.icon = notification.icon; - } - var toast = state.toaster.open(config); - function destroy() { - if (state.current !== notification) { - notification._destroyed = true; + let to_render = [] + for (notification of data.notifications) { + if (notification._destroyed || notification._rendered) { + return } + var config = { + duration: notification.duration, + type: notification.notification_type, + message: notification.message + } + if (notification.background != null) { + config.background = notification.background; + } + if (notification.icon != null) { + config.icon = notification.icon; + } + let toast = state.toaster.open(config); + function destroy() { + if (state.current !== notification) { + notification._destroyed = true; + } + } + notification._rendered = true + toast.on('dismiss', destroy) + if (notification.duration) { + setTimeout(destroy, notification.duration) + } + if (notification.properties === undefined) + return + view.connect(notification.properties._destroyed.change, function () { + state.toaster.dismiss(toast) + }) } - toast.on('dismiss', destroy) - if (notification.duration) { - setTimeout(destroy, notification.duration) - } - if (notification.properties === undefined) - return - view.connect(notification.properties._destroyed.change, function () { - state.toaster.dismiss(toast) - }) """, "_clear": "state.toaster.dismissAll()", "position": """ script('_clear'); script('render'); for (notification of data.notifications) { - state.current = notification; - script('notifications'); + notification._rendered = false; } - state.current = undefined + script('notifications'); """ } diff --git a/panel/reactive.py b/panel/reactive.py index 340a59c916..7fc101cec2 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -2170,6 +2170,27 @@ def _update_model( ) ): continue + elif isinstance(v, list) and all(isinstance(vs, param.Parameterized) for vs in v): + from .io.datamodel import create_linked_datamodel + old = getattr(model.data, prop) + if isinstance(old, list): + mapping = {o.name: o for o in old} + vals = [] + for vs in v: + if (vname:=f"{root.ref['id']}-{id(vs)}") in mapping: + vals.append(mapping[vname]) + else: + vals.append(create_linked_datamodel(vs, root)) + v = vals + data_msg[prop] = v + elif isinstance(v, param.Parameterized): + from .io.datamodel import create_linked_datamodel + old = getattr(model.data, prop) + if old.name == f"{root.ref['id']}-{id(v)}": + v = old + else: + v = create_linked_datamodel(vs, root) + data_msg[prop] = v elif isinstance(v, str): data_msg[prop] = HTML_SANITIZER.clean(v) else: