Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 18 additions & 2 deletions htmltools/_jsx.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,21 +138,37 @@ def tagify_tagifiable_and_get_metadata(x: Any) -> Any:
# instead of calling the standard Tag.get_html_string() method to format the
# object, we'll recurse using _render_react_js(), which descends into the tree
# and formats objects appropriately for inside of a JSX element.
component = _render_react_js(self, 2, "\n")

# Ideally, we'd use document.currentScript.after() to insert the component
# directly after the script tag, but when dynamically rendered via jQuery (i.e.,
# @render_ui()), the script tag actually executes in <head>, which makes the
# document.currentScript reference basically useless for this purpose. So,
# instead, to make this work when dynamically rendered, this script queries for
# the first JSXTag script that hasn't yet rendered, and insert the component
# there. It seems like this should work fine as long as these script tags are
# synchronously executed in order, but if we ever need them to render
# asynchronously, it might make sense to use a unique ID for each script tag
# instead.
js = "\n".join(
[
"(function() {",
" var container = new DocumentFragment();",
" ReactDOM.render(",
_render_react_js(self, 2, "\n"),
component,
" , container);",
" document.currentScript.after(container);",
" var thisScript = document.querySelector('script[data-needs-render]');",
f" if (!thisScript) throw new Error('Failed to render JSXTag(\"{self.name}\")');",
" thisScript.after(container);",
" thisScript.removeAttribute('data-needs-render');",
"})();",
]
)

return Tag(
"script",
type="text/javascript",
data_needs_render=True,
children=[
HTML("\n" + js + "\n"),
_lib_dependency("react", script={"src": "react.production.min.js"}),
Expand Down
49 changes: 35 additions & 14 deletions tests/test_jsx_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ def test_jsx_tags():
<script src="lib/react-dom-%s/react-dom.production.min.js"></script>
</head>
<body>
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
React.createElement(Foo)
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>
</body>
Expand All @@ -44,7 +47,7 @@ def test_jsx_tags():
<script src="lib/react-dom-%s/react-dom.production.min.js"></script>
</head>
<body>
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
Expand All @@ -53,7 +56,10 @@ def test_jsx_tags():
React.createElement(Bar)
)
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>
</body>
Expand All @@ -78,7 +84,7 @@ def test_jsx_tags():
)
assert str(x) == textwrap.dedent(
"""\
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
Expand All @@ -102,7 +108,10 @@ def test_jsx_tags():
)
)
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>"""
)
Expand All @@ -116,7 +125,7 @@ def test_jsx_tags():
)
assert str(x) == textwrap.dedent(
"""\
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
Expand All @@ -129,7 +138,10 @@ def test_jsx_tags():
)
)
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>"""
)
Expand All @@ -139,15 +151,18 @@ def test_jsx_tags():
)
assert str(x) == textwrap.dedent(
"""\
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
React.createElement(
Foo, {"htmlTag": [React.createElement('div'), React.createElement(
'div', {"foo": "1"})]})
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>"""
)
Expand All @@ -159,7 +174,7 @@ def test_jsx_tags():
)
assert str(x) == textwrap.dedent(
"""\
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
Expand All @@ -169,7 +184,10 @@ def test_jsx_tags():
'div', {"style": {"color": "red"}})
)
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>"""
)
Expand Down Expand Up @@ -209,7 +227,7 @@ def tagify(self):

assert str(x) == textwrap.dedent(
"""\
<script type="text/javascript">
<script type="text/javascript" data-needs-render="">
(function() {
var container = new DocumentFragment();
ReactDOM.render(
Expand All @@ -228,7 +246,10 @@ def tagify(self):
)
)
, container);
document.currentScript.after(container);
var thisScript = document.querySelector('script[data-needs-render]');
if (!thisScript) throw new Error('Failed to render JSXTag("Foo")');
thisScript.after(container);
thisScript.removeAttribute('data-needs-render');
})();
</script>"""
)
Expand Down