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
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #5448 +/- ##
==========================================
- Coverage 83.60% 81.09% -2.52%
==========================================
Files 276 277 +1
Lines 40251 40394 +143
==========================================
- Hits 33653 32757 -896
- Misses 6598 7637 +1039
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
class LayoutSingleObject(pn.reactive.ReactiveHTML): | ||
object = param.Parameter() | ||
|
||
_ignored_refs = ("object",) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I don't add object
to _ignored_refs
its shown as a literal value. Is that a regression/ bug @philippjfr ?
doc/how_to/custom_components/reactive_html/reactive_html_layout.md
Outdated
Show resolved
Hide resolved
doc/how_to/custom_components/reactive_html/reactive_html_layout.md
Outdated
Show resolved
Hide resolved
doc/how_to/custom_components/reactive_html/reactive_html_layout.md
Outdated
Show resolved
Hide resolved
doc/how_to/custom_components/reactive_html/reactive_html_widgets.md
Outdated
Show resolved
Hide resolved
"end": "delete state.start", | ||
"save": """ | ||
data.value = canvas_el.toDataURL() | ||
data.save=false""", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need confirmation from @philippjfr that its OK and good practice to use param.Event
parameters in the way I do with save
and reset
. I.e. be setting data.save=false
in the script to enable triggering/ clicking it again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You technically shouldn't have to do that, but I guess since the value isn't synced when it is reset to false it won't trigger the next time. We should fix this and ensure it does reset automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in #6247
doc/how_to/custom_components/reactive_html/reactive_html_dataframe.md
Outdated
Show resolved
Hide resolved
Starting to think we should just turn this guide and the streamlit migration guide into tutorials. |
My plan was to create a tutorial highly inspired by AnyWidget then I would not have to invent things and it would be easy to compare to AnyWidget. |
}); | ||
self.style() | ||
const mainEle = document.querySelector("body") | ||
mainEle.addEventListener("scrollend", (event) => {state.cy.resize().fit()}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we lack a life cycle function after after_layout
@philippjfr ? Why is a hack like listening for scrollend
or adding a timeOut
sometimes needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #5195 (comment) @derrikmcs also notes that scrollend
event is not supported by all browsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There sadly is no event after after_layout
as that's the last step in the Bokeh rendering pipeline.
|
||
- `model` (default): Create child and render as Panel component. | ||
- `literal`: Create child and insert the string value as `innerHTML` | ||
- `template`: Create child and insert the string value as `innerText` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need help to define these @philippjfr . I've seen different definitions and they are not consistently aligned with what I experience when using them. See for example examples and questions below. I'm so confused.
- Can you confirm the are all rendered on
js
side. Sometimes I'm thinking eitherliteral
ortemplate
is rendered on python side using Jinja2. - I've tried to look at the .py and .ts code many times. But I cannot find the logic that distinguishes between
literal
andtemplate
. So I cannot learn from the code. Where is that code? - What does the names
literal
andtemplate
refer too? Please give me something so that I can remember the difference. - Why does the escaping not work consistently for
literal
andtemplate
values? Is it a feature or a bug? It makes it so hard to understand and trust that this is robust. See examples below. - Why can I set the
v_model
parameter value below to a markdown string that is rendered as a Markdown pane. But if I try setting it to ansvg
string then I get an error because aString
parameter cannot be assigned aSVG
pane? Is is a bug or a feature. I'm so confused here. - Why does
ReactiveHTML
even try to change parameter values to their Panel pane? It makes it so hard to useDataFrame
etc. parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the example that confuses me so much
import panel as pn
import param
from panel.reactive import ReactiveHTML
pn.extension()
class CustomComponent(ReactiveHTML):
v_model = param.String(default="I'm a **model** value. <em>emphasize</em>")
v_literal = param.String(default="I'm a **literal** value. <em>emphasize</em>")
v_template = param.String(default="I'm a **template** value. <em>emphasize</em>")
_child_config = {
"v_model": "model",
"v_literal": "literal",
"v_template": "template",
}
_template = """
<div id="el_model">${v_model}</div>
<div id="el_literal">${v_literal}</div>
<div id="el_template">${v_template}</div>
"""
component = CustomComponent(width=500, height=200).servable()
component.v_model=component.v_model.replace("**", "*")
component.v_literal=component.v_model.replace("**", "*")
component.v_template=component.v_model.replace("**", "*")
print(component.v_model)
svg = """<svg style="stroke: #e62f63;" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" slot="collapsed-icon">
<path d="M15.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V15.2222C1 16.2041 1.79594 17 2.77778 17H15.2222C16.2041 17 17 16.2041 17 15.2222V2.77778C17 1.79594 16.2041 1 15.2222 1Z" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M9 5.44446V12.5556" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M5.44446 9H12.5556" stroke-linecap="round" stroke-linejoin="round"></path></svg>"""
CustomComponent(v_literal=svg, v_template=svg, width=500, height=200).servable()
CustomComponent(v_model=svg, v_literal=svg, v_template=svg, width=500, height=200).servable()
|
||
Here is an illustrative example | ||
|
||
```{pyodide} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example below is important. I spent hours finding some combination that works.
One problem is that ReactiveHTML
has issues inserting {{ ... }}
values that contains tilde
- ReactiveHTML: Cannot insert value containing tilde using Jinja2 #5499
- Backticks break ReactiveHTML templating #5500
Another is that I could not find a working way to just loop param
. Instead I had to use param.params().values()
which is cumbersome.
Another is that I could not find an easy way to get the current value of a parameter in the loop. So I ended up having to do object.owner[object.name]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One problem is that ReactiveHTML has issues inserting {{ ... }} values that contains tilde
Will look at fixing that.
Another is that I could not find a working way to just loop param. Instead I had to use param.params().values() which is cumbersome.
.param
can be looped directly.
Another is that I could not find an easy way to get the current value of a parameter in the loop. So I ended up having to do object.owner[object.name].
This is a good point, .param.values()[name]
seems awkward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can loop .param
directly. It gives you a name of a parameter. But .param[name]
does not seem to work. Thus you cannot get the actual parameter to work with.
@@ -268,7 +272,7 @@ def resolve_stylesheet(cls, stylesheet: str, attribute: str | None = None): | |||
The stylesheet definition | |||
""" | |||
stylesheet = str(stylesheet) | |||
if not stylesheet.startswith('http') and attribute and (custom_path:= resolve_custom_path(cls, stylesheet)): | |||
if not stylesheet.startswith('http') and attribute and _is_file_path(stylesheet) and (custom_path:= resolve_custom_path(cls, stylesheet)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried running the below example. But it raised an error because it thought the _stylesheets
value was a path. This change fixes the issue.
import param
from panel.reactive import ReactiveHTML
class CounterWidget(ReactiveHTML):
value = param.Integer(default=0)
_template = """
<button id="button_el" class="styled-button" onclick="${script('click_handler')}"></button>
"""
_scripts = {
"render": "self.value()",
"click_handler": "data.value+=1",
"value": "button_el.innerHTML = `count is ${data.value}`"
}
_stylesheets = ["""\
.styled-button {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
text-align: center;
text-decoration: none;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.styled-button:hover {
background-color: #45a049;
}
"""]
CounterWidget().servable()
@@ -2,6 +2,10 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this "big" topic of custom components and ReactiveHTML
is very much hidden because its several clicks down
Also I believe that it would make navigation easier of the sections and cards where aligned as much as possible between how-to
and explanation
pages. That would make navigation easier as you don't have to remember seperately where to find ReactiveHTML components material for how-to guides and for explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend restructering the how-to
navigation by having a Components
section and moving the following cards there
- construct components
- arrange components
- style components
- build custom components
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds reasonable to me.
I've reviewed a version of the docs deployed to dev site and reviewed. Among other things I've fixed some broken navigation. I've triggered the docs dev build again for a second review. |
|
||
### React, Preact and Vue | ||
|
||
`ReactiveHTML` can be used with [React](https://react.dev/), [Preact](https://preactjs.com/) and [Vue](https://vuejs.org/). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm no longer sure ReactiveHTML
works with React (and MaterialUI). In mui/material-ui#17473 they claim React and Material does not and do not want to support ShadowRoot.
There is a minimal example here that I tried to get working but could not https://discourse.holoviz.org/t/help-with-panel-react-materialui/5204/3?u=marc
Can I do something to move this forward and be easier to review+accept @philippjfr ? I think it will be a big step forward for |
I think you've really done a great job here. It's really on me to review and see if we can fix some of the issues you raised. |
We could postpone the "hard to solve" issues if there is not time for it in the forseable future, fix the "easy ones" and release it in a first step. Then take a second step to solve the harder issues and maybe improve |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is a work of art. thanks so much @MarcSkovMadsen for putting it together, I had no idea ReactiveHTML was so POWERFUL!
One last open ended question that I'm wondering about...:
what is the difference between reactiveHTML and templates? In other words, why don't templates inherit from reactive HTML?
|
||
- `_child_config` (dict): This is a mapping that controls how children are rendered. | ||
- `_ignored_refs` (tuple[str]): This is tuple of parameter names. Use this to render Panel components as Panel components and not their value or object. | ||
- `_dom_events` (dict): This is a mapping of node IDs to DOM events to add event listeners to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's a node ID here? maybe a link would be useful? Little hard to understand this, and I'm not sure what python objects I would put into this dictionary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
having read through more of the document, I see there is a section below that goes into more detail, can we use: https://stackoverflow.com/a/16426829 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Which section are you referring to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend linking all the sections... I just reviewed this reading through github markdown, not sure if when pushed to the website this would add links automatically
You can also declare the following optional attributes: | ||
|
||
- `_child_config` (dict): This is a mapping that controls how children are rendered. | ||
- `_ignored_refs` (tuple[str]): This is tuple of parameter names. Use this to render Panel components as Panel components and not their value or object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this different from child_config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice this one doesn't have a more in-depth section below and from the explanation of _child_config is seems there is some overlap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its because I don't understand the _ignored_refs
. I could just see I had to use it to get some examples to work. Could you please elaborate @philippjfr . Thanks.
|
||
By combining Python callbacks and JavaScript callbacks, you can create dynamic and interactive components that respond to user interactions. | ||
|
||
### Parameter callbacks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section was a little confusing because it seems to be a duplicate of the above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its a subsection of the section above. The intention is to systematically go through the different kind of scripts. And there is some overlap with the above.
Any ideas to how I can make this clearer or better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can take this with a grain of salt but,
If you feel you need this section, I might change the example code so that there is no overlap, and then include a callback to the previous example:
If the key in the
_scripts
dictionary matches one of the parameters declared on the class the callback will automatically fire whenever the synced parameter value changes. This is how counts changes in the example above. Here's another example let's say we have a class...
I think the answer is history.
And thanks for the nice feedback. I'm a sucker for that :-) |
Co-authored-by: Tomas Santos <tomasscssantos@gmail.com>
pre-commit.ci autofix |
for more information, see https://pre-commit.ci
I will merge as is and if further fixes/cleanup are needed will address that as part of the ESM docs in #5593 |
The Panel 1.x refactor of our docs left the `ReactiveHTML´ documentation in a confusing state. Its not clear where to start or where to find what.
This PR tries to address that.
You can preview the documentation via https://github.com/holoviz/panel/tree/enhancement/reactive-html-docs/doc/how_to/custom_components/reactive_html/index.md and the navigation tree on the left.
Related Issues
Resources
Docs
Discourse