Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
holoviz
GitHub Repository: holoviz/panel
Path: blob/main/doc/how_to/custom_components/esm/custom_widgets.md
2012 views

Create Custom Widgets using ESM Components

In this guide we will show you how to efficiently implement custom widgets using JSComponent, ReactComponent and AnyWidgetComponent to get input from the user.

Image Button

This example we will show you to create an ImageButton.

::::{tab-set}

:::{tab-item} JSComponent

import panel as pn import param from panel.custom import JSComponent from panel.widgets import WidgetBase pn.extension() class ImageButton(JSComponent, WidgetBase): clicks = param.Integer(default=0) image = param.String() value = param.Event() _esm = """ export function render({ model }) { const button = document.createElement('button'); button.id = 'button'; button.className = 'pn-container center-content'; const img = document.createElement('img'); img.id = 'image'; img.className = 'image-size'; img.src = model.image; button.appendChild(img); button.addEventListener('click', () => { model.clicks += 1; }); return button } """ _stylesheets = [""" .pn-container { height: 100%; width: 100%; } .center-content { display: flex; align-items: center; justify-content: center; padding: 1em; } .image-size { width: 100%; max-height: 100%; object-fit: contain; } """] @param.depends('clicks') def _trigger_value(self): self.param.trigger('value') button = ImageButton( image="https://panel.holoviz.org/_static/logo_stacked.png", styles={"border": "2px solid lightgray"}, width=400, height=200 ) pn.Column(button, button.param.clicks,).servable()

:::

:::{tab-item} ReactComponent

import panel as pn import param from panel.custom import ReactComponent from panel.widgets import WidgetBase pn.extension() class ImageButton(ReactComponent, WidgetBase): clicks = param.Integer(default=0) image = param.String() value = param.Event() _esm = """ export function render({ model }) { const [clicks, setClicks] = model.useState("clicks"); const [image] = model.useState("image"); return ( <button onClick={e => setClicks(clicks+1)} className="pn-container center-content"> <img src={image} className="image-size" src={ image }/> </button> ) } """ _stylesheets = [""" .pn-container { height: 100%; width: 100%; } .center-content { display: flex; align-items: center; justify-content: center; padding: 1em; } .image-size { width: 100%; max-height: 100%; object-fit: contain; } """] @param.depends('clicks') def _trigger_value(self): self.param.trigger('value') button = ImageButton( image="https://panel.holoviz.org/_static/logo_stacked.png", styles={"border": "2px solid lightgray"}, width=400 ) pn.Column(button, button.param.clicks).servable()

:::

:::{tab-item} AnyWidgetComponent

import panel as pn import param from panel.custom import AnyWidgetComponent from panel.widgets import WidgetBase pn.extension() class ImageButton(AnyWidgetComponent, WidgetBase): clicks = param.Integer(default=0) image = param.String() value = param.Event() _esm = """ function render({ model, el }) { const button = document.createElement('button'); button.id = 'button'; button.className = 'pn-container center-content'; const img = document.createElement('img'); img.id = 'image'; img.className = 'image-size'; img.src = model.get("image"); button.appendChild(img); button.addEventListener('click', () => { model.set("clicks", model.get("clicks")+1); model.save_changes(); }); el.appendChild(button); } export default { render } """ _stylesheets = [""" .pn-container { height: 100%; width: 100%; } .center-content { display: flex; align-items: center; justify-content: center; padding: 1em; } .image-size { width: 100%; max-height: 100%; object-fit: contain; } """] @param.depends('clicks') def _trigger_value(self): self.param.trigger('value') button = ImageButton( image="https://panel.holoviz.org/_static/logo_stacked.png", styles={"border": "2px solid lightgray"}, width=400, height=200 ) pn.Column(button, button.param.clicks).servable()

:::

::::

If you don't want the button styling, you can change the <button> tag to a <div> tag.

The ImageButton now works as any other widget. Lets try the .from_param method to create an ImageButton from a `Parameter:

class MyClass(param.Parameterized): clicks = param.Integer(default=0) value = param.Event() @param.depends("value", watch=True) def _handle_value(self): if self.value: self.clicks += 1 my_instance = MyClass() button2 = ImageButton.from_param(my_instance.param.value, image="https://panel.holoviz.org/_static/logo_stacked.png",) pn.Column(button2, my_instance.param.clicks).servable()

When you click the image button you should see the number of clicks increase.