From c2d2c50632eb2e2da210edf297d5be119769f098 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 26 Aug 2023 03:30:15 +0000 Subject: [PATCH 01/28] start reactive-html docs refactor --- doc/how_to/custom_components/index.md | 38 +-- .../custom_components/reactive_html/index.md | 82 +++++ .../reactive_html/reactive_html_layout.md | 292 ++++++++++++++++++ .../reactive_html/reactive_html_styling.md | 44 +++ 4 files changed, 421 insertions(+), 35 deletions(-) create mode 100644 doc/how_to/custom_components/reactive_html/index.md create mode 100644 doc/how_to/custom_components/reactive_html/reactive_html_layout.md create mode 100644 doc/how_to/custom_components/reactive_html/reactive_html_styling.md diff --git a/doc/how_to/custom_components/index.md b/doc/how_to/custom_components/index.md index 2fcb5ac752..2c85647bc0 100644 --- a/doc/how_to/custom_components/index.md +++ b/doc/how_to/custom_components/index.md @@ -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. ::: :::: @@ -26,44 +26,12 @@ 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 @@ -71,7 +39,7 @@ Build custom component wrapping a bokeh plot and some widgets using the `Viewer` :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. ::: :::: diff --git a/doc/how_to/custom_components/reactive_html/index.md b/doc/how_to/custom_components/reactive_html/index.md new file mode 100644 index 0000000000..3cca75bfcd --- /dev/null +++ b/doc/how_to/custom_components/reactive_html/index.md @@ -0,0 +1,82 @@ +# Migrate from Streamlit to Panel + +These guides addresses how to create custom components with HTML, CSS and Javascript using +`ReactiveHTML`. + +If you are not familiar with HTML, CSS or JavaScript then the [W3 HTML School](https://www.w3schools.com/html/default.asp), +[W3 CSS School](https://www.w3schools.com/css/default.asp) and [W3 JS School](https://www.w3schools.com/js/default.asp) +are good resource to learn from. You can also ask ChatGPT for help. It can often provide you with +a HTML, CSS and JavaScript starting point that you can fine tune. + +--- + +::::{grid} 1 2 2 3 +:gutter: 1 1 1 2 + +:::{grid-item-card} {octicon}`rocket;2.5em;sd-mr-1 sd-animate-grow50` Layouts +:link: get_started +:link-type: reactive_html_layout + +How to create layouts using HTML and `ReactiveHTML` +::: + +:::{grid-item-card} {octicon}`device-desktop;2.5em;sd-mr-1 sd-animate-grow50` CSS Styling +:link: panes +:link-type: reactive_html_styling + +How to style your `ReactiveHTML` components using CSS +::: + +:::: + +```{toctree} +:titlesonly: +:hidden: +:maxdepth: 2 + +reactive_html_layout +reactive_html_styling +``` + +## 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) for further explanation. diff --git a/doc/how_to/custom_components/reactive_html/reactive_html_layout.md b/doc/how_to/custom_components/reactive_html/reactive_html_layout.md new file mode 100644 index 0000000000..5b14b7d680 --- /dev/null +++ b/doc/how_to/custom_components/reactive_html/reactive_html_layout.md @@ -0,0 +1,292 @@ +# Create Layouts With ReactiveHTML + +In this guide you will learn how to build custom layouts using `ReactiveHTML` and HTML. + +If you are not familiar with HTML then the [W3 HTML School](https://www.w3schools.com/html/default.asp) +is a good resource to learn from. You can also ask ChatGPT for help. It can often provide you with +a HTML starting point that you can fine tune. + +## A layout of a single object + +You can layout a single `object` as follows. + +```{pyodide} +import panel as pn +import param + +pn.extension() + +class LayoutSingleObject(pn.reactive.ReactiveHTML): + object = param.Parameter() + + _ignored_refs = ("object",) + + _template = """ +
+

Temperature

+

A measurement from the sensor

+
${object}
+
+""" + +dial = pn.widgets.Dial( + name="°C", value=37, format="{value}", colors=[(0.40, "green"), (1, "red")], bounds=(0, 100), +) +LayoutSingleObject( + object=dial, name="Temperature", description="A measurement from the sensor", styles={"border": "2px solid lightgray"}, width=500 +).servable() +``` + +Please notice + +- We define the HTML layout in the `_template` attribute. +- In the `_template` attribute we define an outer *container* `div` with a `height` and `width` of +`100%`. This will make your `_template` responsive, i.e. (re-)size it self to the `height` and +`width` of your `ReactiveHTML` component. +- We can refer to the parameter `object` in the `_template` via the *template parameter* `${object}`. + - We must give the `div` element holding the `${object}` an `id`. If we do not do this an exception + will be raised. The `id` can be any value, for example `id="my-object"`. +- We call our *object* parameter `object` to be consistent with our built in layouts. But the +parameter can be called anything. For example `value`, `dial` or `temperature`. +- We add the `border` in the `styles` parameter so that we can better see how the `_template` layes +it self out inside the `ReactiveHTML` component. This can be useful for development. + +## A Layout of multiple objects + +```{pyodide} +import panel as pn +import param + +pn.extension() + +class LayoutMultipleValues(pn.reactive.ReactiveHTML): + object1 = param.Parameter() + object2 = param.Parameter() + + _ignored_refs = ("object1", "object2") + + _template = """ + +
+

Object 1

+
${object1}
+

Object 2

+
${object1}
+
+""" + +layout = LayoutMultipleValues( + object1="This is the **value** of `object1`", object2="This is the **value** of `object2`", + styles={"border": "2px solid lightgray"}, +) +layout +``` + +You might notice that the values of `object1` and `object2` looks like they have been +rendered as markdown! That is correct. + +Before inserting the value of a parameter in the +`_template`, Panel transforms the value using `pn.panel`. And for a string value `pn.panel` returns +a `Markdown` pane. + +Let's verify this. + +```{pyodide} +print(type(layout.object1), type(layout.object2)) +``` + +Lets for fun try another example + +```{pyodide} +LayoutMultipleValues( + object1="Do you like **beat boxing**?", + object2="https://upload.wikimedia.org/wikipedia/commons/d/d3/Beatboxset1_pepouni.ogg", + styles={"border": "2px solid lightgray"}, +) +``` + +## A layout of literal `str` values + +If you want to show the *literal* `str` value of your parameter instead of the `pn.panel` return +value you can configure that via the `_child_config` attribute. + +```{pyodide} +import panel as pn +import param + +pn.extension() + +class LayoutLiteralValues(pn.reactive.ReactiveHTML): + object1 = param.Parameter() + object2 = param.Parameter() + + _ignored_refs = ("object1", "object2") + _child_config = {"object1": "literal", "object2": "literal"} + + _template = """ + +
+

Object 1

+
${object1}
+

Object 2

+
${object2}
+
+""" + +layout = LayoutLiteralValues( + object1="This is the **value** of `object1`", object2="This is the **value** of `object2`", + styles={"border": "2px solid lightgray"}, +) +layout +``` + +Lets check the types + +```{pyodide} +print(type(layout.object1), type(layout.object2)) +``` + +## A layout of a list of objects + +```{pyodide} +If you want to want to layout a list of objects you can use a *for loop*. + +```{pyodide} +import panel as pn +import param + +pn.extension() + +class LayoutOfList(pn.reactive.ReactiveHTML): + objects = param.List() + + _template = """ + +
+ {% for object in objects %} +

Object {{ loop.index0 }}

+
${object}
+
+ {% endfor %} +
+""" + +LayoutOfList(objects=[ + "I **love** beat boxing", + "https://upload.wikimedia.org/wikipedia/commons/d/d3/Beatboxset1_pepouni.ogg", + "Yes I do!"], + styles={"border": "2px solid lightgray"}, +) +``` + +Please note that you must + +- wrap the `{% for object in objects %}` loop in a HTML element with an `id`. Here it is wrapped +with `
...
`. +- close all HTML tags! `
` is valid HTML, but not valid with `ReactiveHTML`. You must close it +as `
`. + +Please note you can optionally + +- get the index of the `{% for object in objects %}` loop via `{{ loop.index0 }}`. + +## A list like layout + +If you want to create a *list like* layout similar to `Columnn` and `Row`, you can +combine `NamedListLike` and `ReactiveHTML`. + +```{pyodide} +import panel as pn +import param + +pn.extension() + +class ListLikeLayout(pn.layout.base.NamedListLike, pn.reactive.ReactiveHTML): + objects = param.List() + + _template = """ + +
+ {% for object in objects %} +

Object {{ loop.index0 }}

+
${object}
+
+ {% endfor %} +
+""" + +layout = ListLikeLayout( + "I love beat boxing", + "https://upload.wikimedia.org/wikipedia/commons/d/d3/Beatboxset1_pepouni.ogg", + "Yes I do!", + styles={"border": "2px solid lightgray"}, +) +layout +``` + +Please note that you must + +- list `NamedListLike, ReactiveHTML` in exactly that order when you define the class! The other +way around `ReactiveHTML, NamedListLike` will not work. + +You can now use `[...]` indexing and the `.append`, `.insert`, `pop`, ... methods that you would +expect. + +## A layout of a dictionary + +If you want to layout a dictionary, you can use a for loop on the `.items()`. + +```{pyodide} +layout[2] +``` + +import panel as pn +import param + +pn.extension() + +class LayoutOfDict(pn.reactive.ReactiveHTML): + object = param.Dict() + + _template = """ + +
+ {% for key, value in object.items() %} +

Item {{ loop.index0 }}

+
{{ key }}
+
${value}
+
+ {% endfor %} +
+""" + +LayoutOfDict(object={ + "Intro": "I **love** beat boxing", + "Example": "https://upload.wikimedia.org/wikipedia/commons/d/d3/Beatboxset1_pepouni.ogg", + "*Outro*": "Yes I do!" +}, + styles={"border": "2px solid lightgray"}, +) + +Please note + +- We can insert the `key` as a literal value using `{{ key }}`. +- We must not give the HTML element containing `{{ key }}` an `id`. If we do, an exception will be +raised. + +## A Grid like layout + +If you want to create a grid like layout similar to the [`GridSpec`](https://panel.holoviz.org/reference/layouts/GridSpec.html) +you can ??? + +Todo: Figure out how to do this. Looking at the `GridSpec` it seems there is no *mixin* class I can use. diff --git a/doc/how_to/custom_components/reactive_html/reactive_html_styling.md b/doc/how_to/custom_components/reactive_html/reactive_html_styling.md new file mode 100644 index 0000000000..5cc3042db7 --- /dev/null +++ b/doc/how_to/custom_components/reactive_html/reactive_html_styling.md @@ -0,0 +1,44 @@ +# Style your ReactiveHTML template + +In this guide you will learn how to style your `ReactiveHTML` `_template` using CSS. + +If you are not familiar with CSS then the [W3 CSS School](https://www.w3schools.com/css/default.asp) +is a good resource to learn from. You can also ask ChatGPT for help. It can often provide you with +a HTML and CSS starting point that you can fine tune. + +## A Layout with CSS Styling + +```{pyodide} +import panel as pn +import param + +pn.extension() + +class SensorLayout(pn.reactive.ReactiveHTML): + object = param.Parameter() + + _ignored_refs = ("object",) + + _template = """ + +
+

Temperature

+

A measurement from the sensor

+
${object}
+
+""" + +dial = pn.widgets.Dial( + name="°C", value=37, format="{value}", colors=[(0.40, "green"), (1, "red")], bounds=(0, 100), +) +SensorLayout( + object=dial, name="Temperature", description="A measurement from the sensor", + styles={"border": "2px solid lightgray"}, +).servable() +``` + +print(type(layout.object1), type(layout.object2)) From 3bbbcb1869671a3f1698a053fee8dd0f571c29ba Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 26 Aug 2023 03:46:45 +0000 Subject: [PATCH 02/28] minor fixes --- doc/how_to/custom_components/reactive_html/index.md | 13 ++++++++----- .../reactive_html/reactive_html_layout.md | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/how_to/custom_components/reactive_html/index.md b/doc/how_to/custom_components/reactive_html/index.md index 3cca75bfcd..e341b48aa6 100644 --- a/doc/how_to/custom_components/reactive_html/index.md +++ b/doc/how_to/custom_components/reactive_html/index.md @@ -1,12 +1,15 @@ -# Migrate from Streamlit to Panel +# Create Custom Components with `ReactiveHTML` -These guides addresses how to create custom components with HTML, CSS and Javascript using -`ReactiveHTML`. +These guides addresses how to create custom components with HTML, CSS and/ or Javascript using +`ReactiveHTML` and no Javascript tooling. -If you are not familiar with HTML, CSS or JavaScript then the [W3 HTML School](https://www.w3schools.com/html/default.asp), +If you are not familiar with HTML, CSS or Javascript then the [W3 HTML School](https://www.w3schools.com/html/default.asp), [W3 CSS School](https://www.w3schools.com/css/default.asp) and [W3 JS School](https://www.w3schools.com/js/default.asp) are good resource to learn from. You can also ask ChatGPT for help. It can often provide you with -a HTML, CSS and JavaScript starting point that you can fine tune. +HTML, CSS and/ or Javascript that you can fine tune. + +`ReactiveHTML` as most of Panel builds on top of [Param](https://param.holoviz.org/). If you are +not familiar with Param you should check out the [Param Documentation]](https://param.holoviz.org/). --- diff --git a/doc/how_to/custom_components/reactive_html/reactive_html_layout.md b/doc/how_to/custom_components/reactive_html/reactive_html_layout.md index 5b14b7d680..12b026af90 100644 --- a/doc/how_to/custom_components/reactive_html/reactive_html_layout.md +++ b/doc/how_to/custom_components/reactive_html/reactive_html_layout.md @@ -1,14 +1,14 @@ # Create Layouts With ReactiveHTML -In this guide you will learn how to build custom layouts using `ReactiveHTML` and HTML. +In this guide you will learn how to build custom layouts using HTML and `ReactiveHTML`. If you are not familiar with HTML then the [W3 HTML School](https://www.w3schools.com/html/default.asp) is a good resource to learn from. You can also ask ChatGPT for help. It can often provide you with -a HTML starting point that you can fine tune. +a good HTML starting point that you can fine tune. ## A layout of a single object -You can layout a single `object` as follows. +You can layout a single object as follows. ```{pyodide} import panel as pn From 90d694f1a2c460f6bea10a83c6f9746f07e09176 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 27 Aug 2023 13:25:00 +0000 Subject: [PATCH 03/28] more reactive html docs --- .../components/components_custom.md | 2 +- .../custom_components/custom_reactiveHTML.md | 86 +------- .../custom_components/reactive_html/index.md | 47 +++-- .../reactive_html/move_elsewhere.md | 53 +++++ .../reactive_html/reactive_html_layout.md | 4 +- .../reactive_html/reactive_html_widgets.md | 183 ++++++++++++++++++ 6 files changed, 270 insertions(+), 105 deletions(-) create mode 100644 doc/how_to/custom_components/reactive_html/move_elsewhere.md create mode 100644 doc/how_to/custom_components/reactive_html/reactive_html_widgets.md diff --git a/doc/explanation/components/components_custom.md b/doc/explanation/components/components_custom.md index 4159ad0852..014b1a9f77 100644 --- a/doc/explanation/components/components_custom.md +++ b/doc/explanation/components/components_custom.md @@ -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)' } ``` diff --git a/doc/how_to/custom_components/custom_reactiveHTML.md b/doc/how_to/custom_components/custom_reactiveHTML.md index 7a12184869..2449f30d33 100644 --- a/doc/how_to/custom_components/custom_reactiveHTML.md +++ b/doc/how_to/custom_components/custom_reactiveHTML.md @@ -1,91 +1,6 @@ # 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 `` 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 = '' - - 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 = """""" - - _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 = """ - - """ - - _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. ` {% endfor %} """ - _dom_events = {'select': ['change']} + _dom_events = {'select_el': ['change']} select = Select( value="B", @@ -169,6 +169,7 @@ pn.Column(select, select.param.value) Note how we used a {% for ... %}` loop to loop over the options. In this example we inserted the options as literal `str` values via `{{option}}`. + Note that the example above inserted the `options` as child objects but since they are strings we could use literals instead: ```html @@ -180,4 +181,95 @@ Note that the example above inserted the `options` as child objects but since th ``` When using child literals we have to ensure that each `