Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ReactiveHTML docs #5448

Merged
merged 29 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c2d2c50
start reactive-html docs refactor
Aug 26, 2023
3bbbcb1
minor fixes
Aug 26, 2023
90d694f
more reactive html docs
Aug 27, 2023
f0846cf
more ReactiveHTML docs refactor
Aug 28, 2023
3b145cd
work on reactive html docs
Aug 30, 2023
437c8e8
add reactivehtml dataframe how to
Aug 31, 2023
ca1a44b
add reactivehtml panes
Sep 1, 2023
67ad253
wip
Sep 7, 2023
bd53e0e
wip reactivehtml docs
Sep 8, 2023
f7dcf62
wip reactivehtml docs
Sep 10, 2023
4a7843d
reactivehtml - loop with .doc
Sep 10, 2023
ca5e535
add _dom_events description
Sep 10, 2023
6e05969
update reactive_html_components
MarcSkovMadsen Sep 23, 2023
0f48349
fix after read
MarcSkovMadsen Sep 23, 2023
26a0ae8
fix test issues
MarcSkovMadsen Sep 23, 2023
f5d9f1b
fix test issues
MarcSkovMadsen Sep 23, 2023
37be2ac
test examples
MarcSkovMadsen Sep 23, 2023
c2c9530
add missing links
MarcSkovMadsen Sep 23, 2023
905c8b7
Merge branch 'main' of https://github.com/holoviz/panel into enhancem…
MarcSkovMadsen Sep 23, 2023
4396451
fix resolve issue
MarcSkovMadsen Sep 23, 2023
78423a6
add anywidget comparison
MarcSkovMadsen Sep 23, 2023
a004366
remove failing description
MarcSkovMadsen Sep 23, 2023
3e938b1
make links work. improve
MarcSkovMadsen Sep 24, 2023
e78c722
comment on react etc.
MarcSkovMadsen Sep 24, 2023
3471493
fix cards layout
MarcSkovMadsen Sep 24, 2023
a99bad6
fix reactivehtml issues
MarcSkovMadsen Sep 25, 2023
5f5e330
Update doc/explanation/components/reactive_html_components.md
MarcSkovMadsen Jan 14, 2024
6976b72
apply feedback from thomascsantos
MarcSkovMadsen Jan 14, 2024
c3f4be7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/explanation/components/components_custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ will invoke the following script if it is defined on the class:

```python
_scripts = {
'some_script': 'console.log(self.state.event)'
'some_script': 'console.log(state.event)'
}
```

Expand Down
174 changes: 1 addition & 173 deletions doc/how_to/custom_components/custom_reactiveHTML.md
Original file line number Diff line number Diff line change
@@ -1,182 +1,10 @@
# Build Components from Scratch

This guide addresses how to build custom Panel components from scratch.

```{admonition} Prerequisites
1. As a how-to guide, the intent is to provide recipes for specific problems without a lot of discussion. However, this is an advanced topic so if you get stuck, please read the associated [Explanation > Building Custom Components](../../explanation/components/components_custom) for further explanation.
```

---

The `ReactiveHTML` class provides bi-directional syncing of arbitrary HTML attributes and DOM properties with parameters on the subclass. The key part of the subclass is the `_template` variable. This is the HTML template that gets rendered and declares how to link parameters on the class to HTML attributes.

## Callback Example

Let's declare a `Slideshow` component which subscribes to `click` events on an `<img>` element and advances the image `index` on each click:

```{pyodide}
import panel as pn
import param

from panel.reactive import ReactiveHTML

pn.extension()

class Slideshow(ReactiveHTML):

index = param.Integer(default=0)

_template = '<img id="slideshow" src="https://picsum.photos/800/300?image=${index}" onclick="${_img_click}"></img>'

def _img_click(self, event):
self.index += 1

print('run the code block above, then click on the image below')

Slideshow(width=500, height=200)
```

As we can see this approach lets us quickly build custom HTML components with complex interactivity. However if we do not need any complex computations in Python we can also construct a pure JS equivalent:

```{pyodide}
class JSSlideshow(ReactiveHTML):

index = param.Integer(default=0)

_template = """<img id="slideshow" src="https://picsum.photos/800/300?image=${index}" onclick="${script('click')}"></img>"""

_scripts = {'click': 'data.index += 1'}

JSSlideshow(width=800, height=300)
```

## Child Template Example

If we want to provide a template for the children of an HTML node we have to use Jinja2 syntax to loop over the parameter. The component will insert the loop variable `option` into each of the tags:

```{pyodide}
class Select(ReactiveHTML):

options = param.List(doc="Options to choose from.")

value = param.String(doc="Current selected option")

_template = """
<select id="select" value="${value}" style="width: ${model.width}px">
{% for option in options %}
<option id="option">${option}</option>
{% endfor %}
</select>
"""

_dom_events = {'select': ['change']}

select = Select(options=['A', 'B', 'C'])
select
```

The loop body can declare any number of HTML tags to add for each child object, e.g. to add labels or icons, however the child object (like the `{{option}}` or `${option}`) must always be wrapped by an HTML element (e.g. `<option>`) which must declare an `id`. Depending on your use case you can wrap each child in any HTML element you require, allowing complex nested components to be declared. Note that the example above inserted the `options` as child objects but since they are strings we could use literals instead:

```html
<select id="select" value="${value}" style="width: ${model.width}px">
{% for option in options %}
<option id="option-{{ loop.index0 }}">{{ option }}</option>
{% endfor %}
</select>
```

When using child literals we have to ensure that each `<option>` DOM node has a unique ID manually by inserting the `loop.index0` value (which would otherwise be added automatically).

## Javascript Events Example

Next we will build a more complex example using pure Javascript events to draw on a canvas with configurable line width, color and the ability to clear and save the resulting drawing.

```{pyodide}
import panel as pn

class Canvas(ReactiveHTML):

color = param.Color(default='#000000')

line_width = param.Number(default=1, bounds=(0.1, 10))

uri = param.String()

_template = """
<canvas
id="canvas"
style="border: 1px solid;"
width="${model.width}"
height="${model.height}"
onmousedown="${script('start')}"
onmousemove="${script('draw')}"
onmouseup="${script('end')}"
>
</canvas>
<button id="clear" onclick='${script("clear")}'>Clear</button>
<button id="save" onclick='${script("save")}'>Save</button>
"""

_scripts = {
'render': """
state.ctx = canvas.getContext("2d")
""",
'start': """
state.start = event
state.ctx.beginPath()
state.ctx.moveTo(state.start.offsetX, state.start.offsetY)
""",
'draw': """
if (state.start == null)
return
state.ctx.lineTo(event.offsetX, event.offsetY)
state.ctx.stroke()
""",
'end': """
delete state.start
""",
'clear': """
state.ctx.clearRect(0, 0, canvas.width, canvas.height);
""",
'save': """
data.uri = canvas.toDataURL();
""",
'line_width': """
state.ctx.lineWidth = data.line_width;
""",
'color': """
state.ctx.strokeStyle = data.color;
"""
}

canvas = Canvas(width=300, height=300)

# We create a separate HTML element which syncs with the uri parameter of the Canvas
png_view = pn.pane.HTML()
canvas.jslink(png_view, code={'uri': "target.text = `<img src='${source.uri}'></img>`"})

pn.Column(
'# Drag on canvas to draw\n To export the drawing to a png click save.',
pn.Row(
canvas.controls(['color', 'line_width']),
canvas,
png_view
)
)
```

This example leverages all three ways a script is invoked:

1. `'render'` is called on initialization
2. `'start'`, `'draw'` and `'end'` are explicitly invoked using the `${script(...)}` syntax in inline callbacks
3. `'line_width'` and `'color'` are invoked when the parameters change (i.e. when a widget is updated)

It also makes extensive use of the available objects in the namespace:

- `'render'`: Uses the `state` object to easily access the canvas rendering context in subsequent callbacks and accesses the `canvas` DOM node by name.
- `'start'`, `'draw'`: Use the `event` object provided by the `onmousedown` and `onmousemove` inline callbacks
- `'save'`, `'line_width'`, `'color'`: Use the `data` object to get and set the current state of the parameter values


## Related Resources

- Read the associated [Explanation > Building Custom Components](../../explanation/components/components_custom) for further explanation, including how to load external dependencies for your custom components.
38 changes: 3 additions & 35 deletions doc/how_to/custom_components/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ How to build custom components that are combinations of existing components.
:link: custom_reactiveHTML
:link-type: doc

How to build custom components from scratch.
How to build custom components with HTML, CSS, Javascript and `ReactiveHTML` and no Javascript tooling.
:::

::::
Expand All @@ -26,52 +26,20 @@ How to build custom components from scratch.
::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} Build a Canvas component
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/canvas_draw.png
:link: examples/canvas_draw
:link-type: doc

Build a custom component to draw on an HTML canvas based on `ReactiveHTML`.
:::

:::{grid-item-card} Wrap Leaflet.js
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/leaflet.png
:link: examples/leaflet
:link-type: doc

Build a custom component wrapping leaflet.js using `ReactiveHTML`.
:::

:::{grid-item-card} Wrap Material UI
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/material_ui.png
:link: examples/material_ui
:link-type: doc

Build custom components wrapping material UI using `ReactiveHTML`.
:::

:::{grid-item-card} Wrap a Vue.js component
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/vue.png
:link: examples/vue
:link-type: doc

Build custom component wrapping a Vue.js app using `ReactiveHTML`.
:::

:::{grid-item-card} Build a Plot Viewer
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/plot_viewer.png
:link: examples/plot_viewer
:link-type: doc

Build custom component wrapping a bokeh plot and some widgets using the `Viewer` pattern.
Build a custom component wrapping a bokeh plot and some widgets using the `Viewer` pattern.
:::

:::{grid-item-card} Build a Table Viewer
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/table_viewer.png
:link: examples/table_viewer
:link-type: doc

Build custom component wrapping a table and some widgets using the `Viewer` pattern.
Build a custom component wrapping a table and some widgets using the `Viewer` pattern.
:::

::::
Expand Down
115 changes: 115 additions & 0 deletions doc/how_to/custom_components/reactive_html/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Create Custom Components with `ReactiveHTML`

These guides addresses how to create custom components with HTML, CSS and/ or Javascript using
`ReactiveHTML` and no Javascript build tools.

A `ReactiveHTML` subclass provides bi-directional syncing of its parameters with arbitrary HTML
elements, attributes and properties. The key part of the subclass is the `_template`
variable. This is the HTML template that gets rendered and declares how to link parameters on the
subclass to HTML.

---

::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} {octicon}`columns-2;2.5em;sd-mr-1 sd-animate-grow50` Layouts
:link: reactive_html_layout
:link-type: doc

How to create layouts using `ReactiveHTML`
:::

::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} {octicon}`brush;2.5em;sd-mr-1 sd-animate-grow50` Styling
:link: reactive_html_layout
:link-type: doc

How to style `ReactiveHTML` components
:::

::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} {octicon}`bolt;2.5em;sd-mr-1 sd-animate-grow50` Callbacks
:link: reactive_html_callbacks
:link-type: doc

How to add Python and JS callbacks to `ReactiveHTML`
:::

:::{grid-item-card} {octicon}`select;2.5em;sd-mr-1 sd-animate-grow50` Widgets
:link: reactive_html_widgets
:link-type: doc

How to create input widgets using `ReactiveHTML`
:::

:::{grid-item-card} {octicon}`border-all;2.5em;sd-mr-1 sd-animate-grow50` DataFrame
:link: reactive_html_dataframe
:link-type: doc

How to create components using `ReactiveHTML` and a DataFrame parameter
:::

::::

```{toctree}
:titlesonly:
:hidden:
:maxdepth: 2

reactive_html_layout
reactive_html_styling
reactive_html_widgets
```

## Examples

::::{grid} 1 2 2 3
:gutter: 1 1 1 2

:::{grid-item-card} Build a Canvas component
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/canvas_draw.png
:link: examples/canvas_draw
:link-type: doc

Build a custom component to draw on an HTML canvas based on `ReactiveHTML`.
:::

:::{grid-item-card} Wrap Leaflet.js
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/leaflet.png
:link: examples/leaflet
:link-type: doc

Build a custom component wrapping leaflet.js using `ReactiveHTML`.
:::

:::{grid-item-card} Wrap Material UI
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/material_ui.png
:link: examples/material_ui
:link-type: doc

Build custom components wrapping material UI using `ReactiveHTML`.
:::

:::{grid-item-card} Wrap a Vue.js component
:img-top: https://assets.holoviz.org/panel/how_to/custom_components/vue.png
:link: examples/vue
:link-type: doc

Build custom component wrapping a Vue.js app using `ReactiveHTML`.
:::

::::

## Related Resources

- Read the associated [Explanation > Building Custom Components](../../explanation/components/components_custom) to learn how `ReactiveHTML` works.

## External Resources

- [Building custom Panel widgets using ReactiveHTML | Blog post](https://blog.holoviz.org/building_custom_panel_widgets_using_reactivehtml.html)
- Read the [Param Documentation](https://param.holoviz.org/): learn more about `ReactiveHTML`s powerful parameters.
Loading