Not sure if this is appropriately a Panel or a HoloViews question, but: obviously there is a serialization API built into panel which takes parameters declared on the Python side, turns them into JSON, and then ships them to the frontend. Is there a generic API I could subclass or override to serialize a particular param if it isn’t being handled properly? Normally I would go spelunking in code, but the async nature of Panel makes it hard to understand where exactly to begin. In particular I’m trying to understand the point at which objects are turned into Bokeh models to aid me in writing custom components. If anyone has any pointers for where to look I would appreciate it.
Panel 1.4.5
The way Panel supports custom components is described in the How to - Build Custom Components section. You would be using the ReactiveHTML
base class.
Panel 1.5.0 (not released as of 2024-08-04)
From Panel 1.5.0 the support for custom components will be significantly improved. Both technically and in the documentation. You would be using the JS Component
, ReactComponent
or AnyWidgetComponent
depending on your preferences.
If the main
branch is deployed to the dev
docs you can sneak peak there. Its not as we speak. But in 1 hour it should be.
You can then use the new features by installing the latest beta or release candidate of Panel. Currently panel==1.5.0b2
. Personally that is what I would do as the developer experience is much improved with the new components.
Hi @Marc, thanks for the references. Maybe I should explain better what I mean. I have already used ReactiveHTML
extensively and am now using JSComponent
. It’s pretty amazing and has really simplified the wrapping of external components for me. However, there are some things I’m still having trouble with. The documentation on JSComponent
says that if you want to use sub-components they should he declared as Child
parameters. Then on the JS side you call model.get_child(param)
and you get the HTML element representing the rendering of that parameter. But this causes the entire component to be fully re-rendered, which seems expensive and results in blinks and such. What I have noticed is that if I have e.g. param.String()
on a class, updating that parameter does not cause a full redraw of the component. In other words, if I have this:
class Foo(JSComponent):
content = Child()
label = param.String()
_esm = "foo.js"
and
// foo.js
export function render({model, view, el}) {
let content = model.get_child('content')
let container = document.createElement('div')
// render content into container
model.event('label', () => {
// do something with label
}
return container
}
and then on the python side I do:
foo = Foo(content=bar, label="hello")
foo.label = "world" # <-- will not trigger render, will trigger model.event('label') callback
foo.content = baz # <-- will trigger render
This is my problem in a nutshell. What I want is for an update to content
to not trigger a full re-render of the component. I had the thought that I could possibly prevent the render by doing something like this:
class Foo(JSComponent):
content = param.Parameter()
But in this case, if you try to assign to content
e.g. foo.content = pn.widgets.Button()
, you will get a serialization error. The serializer will complain that it doesn’t know how to turn a pn.widgets.Button
into a serialized form. When I dug into why this was happening, I see that when content
is a Child
, it gets turned into a Bokeh model, whereas when if it’s a param.Parameter
it does not.
So I guess what I’m actually asking is: can I somehow make it so that my updates to the actual content don’t trigger a re-render of the whole component? What I want is behavior akin to that of updating a string parameter, where I can listen for the event on the JS end and update everything myself. Does that make sense?
Panel will improve the child re-rendering as implemented in Simplify and generalize JSComponent child re-rendering by philippjfr · Pull Request #7124 · holoviz/panel (github.com). Will this solve your problem?
If you have same reproducible examples that could be used for testing, please share them in the PR.
Thanks, this might do it. I’ll have to check it out and experiment with my current use case.
Let me know how you get on with the improved child rendering @jerry.vinokurov. We’re aiming to cut an RC release quite soon.
Hi @jerry.vinokurov. Any luck in checking this out? Were you able to have a look at the PR?
Hey, I’m sorry for dropping the ball on this. I was on vacation last week but I will make sure to check this out in the next few days.
Ok, so from the way that I understand this PR and reading the test, what this does is that if I have a component that has some children, and those children are themselves components, then updating the list of children will not cause the children that are already present to get rendered again, right? I guess what I had in mind was more the following, expanding the above into an MRE:
import uuid
import panel as pn
import param
from panel.custom import JSComponent, Child, Children
class Foo(JSComponent):
content = Child()
label = param.String()
_esm = """
export function render({model, view, el}) {
let content = model.get_child('content')
let container = document.createElement('div')
let text = document.createElement('div')
text.innerHTML = model.label
container.appendChild(content)
container.appendChild(text)
console.log('rendering foo')
model.on('label', () => {
console.log('label has changed to', model.label)
text.innerHTML = model.label
})
return container
}
"""
foo = Foo(label="hello world", content=pn.widgets.TextInput(value="hello world"))
def update_content(*_):
foo.content = pn.widgets.TextInput(value=uuid.uuid4().hex)
def update_label(*_):
foo.label = uuid.uuid4().hex
content_btn = pn.widgets.Button(name="update content", on_click=update_content)
label_btn = pn.widgets.Button(name="update label", on_click=update_label)
pn.Column(foo, content_btn, label_btn).servable()
Clicking the update label
button will cause the label to change, but if you watch the JS console you will not see a message about the rendering. However if you click update content
then you will see the message that the Foo
component is rendering. I had hoped that there was some way to “just” get the child model content
on the JS side without the entirety of Foo
re-rendering. But perhaps this is just not the way things work with Bokeh? I think that with the way the new child rendering logic works now I could probably finagle something like the example I am sharing above but I would have to think more about how to structure that.