Serialization API?

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.

1 Like

Hi @jerry.vinokurov

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?

1 Like

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.

3 Likes

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.

1 Like