diff --git a/panel/models/reactive_html.py b/panel/models/reactive_html.py index c7f03f6a9a..416943db21 100644 --- a/panel/models/reactive_html.py +++ b/panel/models/reactive_html.py @@ -159,7 +159,7 @@ def find_attrs(html): } -def construct_data_model(parameterized, name=None, ignore=[]): +def construct_data_model(parameterized, name=None, ignore=[], types={}): properties = {} for pname in parameterized.param: if pname in ignore: @@ -167,7 +167,8 @@ def construct_data_model(parameterized, name=None, ignore=[]): p = parameterized.param[pname] if p.precedence and p.precedence < 0: continue - prop = PARAM_MAPPING.get(type(p)) + ptype = types.get(pname, type(p)) + prop = PARAM_MAPPING.get(ptype) pname = parameterized._rename.get(pname, pname) if pname == 'name' or pname is None: continue @@ -199,7 +200,7 @@ class ReactiveHTML(HTMLBox): callbacks = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.String))) - children = bp.Dict(bp.String, bp.List(bp.Either(bp.Instance(LayoutDOM), bp.String))) + children = bp.Dict(bp.String, bp.Either(bp.List(bp.Either(bp.Instance(LayoutDOM), bp.String)), bp.String)) data = bp.Instance(DataModel) diff --git a/panel/models/reactive_html.ts b/panel/models/reactive_html.ts index 001de05ad9..a8f08cefcd 100644 --- a/panel/models/reactive_html.ts +++ b/panel/models/reactive_html.ts @@ -103,14 +103,21 @@ export class ReactiveHTMLView extends PanelHTMLBoxView { this.connect(this.model.properties.children.change, async () => { this.html = htmlDecode(this.model.html) || this.model.html await this.rebuild() - if (this._parent != null) - this._parent.invalidate_layout() + this.invalidate_layout() }) for (const prop in this.model.data.properties) { this.connect(this.model.data.properties[prop].change, () => { - if (!this._changing) { - this._update(prop) - this.invalidate_layout() + for (const node in this.model.children) { + if (this.model.children[node] == prop) { + let children = this.model.data[prop] + if (!isArray(children)) + children = [children] + this._render_node(node, children) + this.invalidate_layout() + } else if (!this._changing) { + this._update(prop) + this.invalidate_layout() + } } }) } @@ -195,35 +202,42 @@ export class ReactiveHTMLView extends PanelHTMLBoxView { private _render_child(model: any, el: Element): void { const view: any = this._child_views.get(model) - view._parent = this if (view == null) el.innerHTML = model - else + else { + view._parent = view view.renderTo(el) + } } - private _render_children(): void { + _render_node(node: any, children: any[]): void { const id = this.model.data.id - for (const node in this.model.children) { - const children = this.model.children[node] - if (this.model.looped.indexOf(node) > -1) { - for (let i = 0; i < children.length; i++) { - let el: any = document.getElementById(`${node}-${i}-${id}`) - if (el == null) { - console.warn(`DOM node '${node}-${i}-${id}' could not be found. Cannot render children.`) - continue - } - this._render_child(children[i], el) - } - } else { - let el: any = document.getElementById(`${node}-${id}`) + if (this.model.looped.indexOf(node) > -1) { + for (let i = 0; i < children.length; i++) { + let el: any = document.getElementById(`${node}-${i}-${id}`) if (el == null) { - console.warn(`DOM node '${node}-${id}' could not be found. Cannot render children.`) + console.warn(`DOM node '${node}-${i}-${id}' could not be found. Cannot render children.`) continue } - for (const child of children) - this._render_child(child, el) + this._render_child(children[i], el) } + } else { + let el: any = document.getElementById(`${node}-${id}`) + if (el == null) { + console.warn(`DOM node '${node}-${id}' could not be found. Cannot render children.`) + return + } + for (const child of children) + this._render_child(child, el) + } + } + + private _render_children(): void { + for (const node in this.model.children) { + let children = this.model.data[prop] + if (!isArray(children)) + children = [children] + this._render_node(node, children) } } diff --git a/panel/reactive.py b/panel/reactive.py index 0630ff6864..4bf51c6828 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -996,13 +996,21 @@ def __init__(mcs, name, bases, dict_): if node not in mcs._attrs: mcs._attrs[node] = [] mcs._attrs[node].append((attr, param_attrs, template)) - ignored = list(Reactive.param)+list(mcs._parser.children.values()) + ignored = list(Reactive.param) + types = {} + for child in mcs._parser.children.values(): + if mcs._child_config.get(child) == 'literal': + types[child] = param.String + else: + ignored.append(child) ignored.remove('name') # Create model with unique name ReactiveHTMLMetaclass._name_counter[name] += 1 model_name = f'{name}{ReactiveHTMLMetaclass._name_counter[name]}' - mcs._data_model = construct_data_model(mcs, name=model_name, ignore=ignored) + mcs._data_model = construct_data_model( + mcs, name=model_name, ignore=ignored, types=types + ) @@ -1174,7 +1182,10 @@ def _process_children(self, doc, root, model, comm, children): return children def _init_params(self): - ignored = list(Reactive.param)+list(self._parser.children.values()) + ignored = list(Reactive.param) + for child in self._parser.children.values(): + if self._child_config.get(child) != 'literal': + ignored.append(child) params = { p : getattr(self, p) for p in list(Layoutable.param) if getattr(self, p) is not None and p != 'name' @@ -1223,6 +1234,7 @@ def _get_children(self, doc, root, model, comm, old_children=None): for parent, children_param in self._parser.children.items(): mode = self._child_config.get(children_param, 'model') if mode == 'literal': + new_panes[parent] = None continue panes = getattr(self, children_param) if isinstance(panes, dict): @@ -1256,7 +1268,7 @@ def _get_children(self, doc, root, model, comm, old_children=None): child_panes = child_panes.values() mode = self._child_config.get(children_param, 'model') if mode == 'literal': - new_models[parent] = child_panes + new_models[parent] = children_param elif children_param in old_children: # Find existing models old_panes = old_children[children_param] @@ -1397,6 +1409,8 @@ def _update_model(self, events, msg, root, model, doc, comm): for prop, v in list(msg.items()): if prop in child_params: new_children[prop] = prop + if self._child_config.get(prop) == 'literal': + data_msg[prop] = bleach.clean(v) elif prop in list(Reactive.param)+['events']: model_msg[prop] = v elif prop in self.param and (self.param[prop].precedence or 0) < 0: