Skip to content

Commit

Permalink
prototype that also works with CSS
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger committed Dec 21, 2024
1 parent ef242db commit 5e9ab55
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 28 deletions.
38 changes: 27 additions & 11 deletions src/js/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,31 +63,47 @@ export function DjangoForm({
return null;
}

export function OnlyOnceJS({ jsPath, autoRemove }: OnlyOnceProps): null {
export function LoadOnlyOnce({
path,
nodeName,
autoRemove,
}: OnlyOnceProps): null {
React.useEffect(() => {
// Check if the script element already exists
let el = document.head.querySelector(
"script.reactpy-staticfile[src='" + jsPath + "']",
);
// Check if the element already exists
let el: null | HTMLElement = null;
if (nodeName === "script") {
el = document.head.querySelector(
"script.reactpy-staticfile[src='" + path + "']",
);
} else if (nodeName === "link") {
el = document.head.querySelector(
"link.reactpy-staticfile[href='" + path + "']",
);
} else {
throw new Error("Invalid nodeName provided to LoadOnlyOnce");
}

// Create a new script element, if needed
// Create a new element, if needed
if (el === null) {
el = document.createElement("script");
el = document.createElement(nodeName);
el.className = "reactpy-staticfile";
if (jsPath) {
el.setAttribute("src", jsPath);
if (nodeName === "script") {
el.setAttribute("src", path);
} else if (nodeName === "link") {
el.setAttribute("href", path);
el.setAttribute("rel", "stylesheet");
}
document.head.appendChild(el);
}

// If requested, auto remove the script when it is no longer needed
// If requested, auto remove the element when it is no longer needed
if (autoRemove) {
// Keep track of the number of ReactPy components that are dependent on this script
let count = Number(el.getAttribute("data-count"));
count += 1;
el.setAttribute("data-count", count.toString());

// Remove the script element when the last dependent component is unmounted
// Remove the element when the last dependent component is unmounted
return () => {
count -= 1;
if (count === 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { OnlyOnceJS, DjangoForm, bind } from "./components";
export { LoadOnlyOnce, DjangoForm, bind } from "./components";
export { mountComponent } from "./mount";
3 changes: 2 additions & 1 deletion src/js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface DjangoFormProps {
}

export interface OnlyOnceProps {
jsPath: string;
path: string;
nodeName: "script" | "link";
autoRemove: boolean;
}
41 changes: 26 additions & 15 deletions src/reactpy_django/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@
from reactpy_django.types import AsyncFormEvent, SyncFormEvent, ViewToComponentConstructor, ViewToIframeConstructor


DjangoJS = web.export(
web.module_from_file("reactpy-django", file=Path(__file__).parent / "static" / "reactpy_django" / "client.js"),
("OnlyOnceJS"),
)


def view_to_component(
view: Callable | View | str,
transforms: Sequence[Callable[[VdomDict], Any]] = (),
Expand Down Expand Up @@ -92,7 +86,9 @@ def constructor(
return constructor


def django_css(static_path: str, key: Key | None = None) -> ComponentType:
def django_css(
static_path: str, only_once: bool = False, auto_remove: bool = False, key: Key | None = None
) -> ComponentType:
"""Fetches a CSS static file for use within ReactPy. This allows for deferred CSS loading.
Args:
Expand All @@ -102,11 +98,11 @@ def django_css(static_path: str, key: Key | None = None) -> ComponentType:
immediate siblings
"""

return _django_css(static_path=static_path, key=key)
return _django_css(static_path=static_path, only_once=only_once, auto_remove=auto_remove, key=key)


def django_js(
static_path: str, only_once: bool = False, only_once_auto_remove: bool = False, key: Key | None = None
static_path: str, only_once: bool = False, auto_remove: bool = False, key: Key | None = None
) -> ComponentType:
"""Fetches a JS static file for use within ReactPy. This allows for deferred JS loading.
Expand All @@ -117,9 +113,7 @@ def django_js(
immediate siblings
"""

return _django_js(
static_path=static_path, only_once=only_once, only_once_auto_remove=only_once_auto_remove, key=key
)
return _django_js(static_path=static_path, only_once=only_once, auto_remove=auto_remove, key=key)


def django_form(
Expand Down Expand Up @@ -285,13 +279,30 @@ def _view_to_iframe(


@component
def _django_css(static_path: str):
def _django_css(static_path: str, only_once: bool, auto_remove: bool):
if only_once:
return LoadOnlyOnce({
"path": static(static_path),
"nodeName": "link",
"autoRemove": auto_remove,
})

return html.style(cached_static_file(static_path))


@component
def _django_js(static_path: str, only_once: bool, only_once_auto_remove: bool):
def _django_js(static_path: str, only_once: bool, auto_remove: bool):
if only_once:
return DjangoJS({"jsPath": static(static_path), "autoRemove": only_once_auto_remove})
return LoadOnlyOnce({
"path": static(static_path),
"nodeName": "script",
"autoRemove": auto_remove,
})

return html.script(cached_static_file(static_path))


LoadOnlyOnce = web.export(
web.module_from_file("reactpy-django", file=Path(__file__).parent / "static" / "reactpy_django" / "client.js"),
("LoadOnlyOnce"),
)

0 comments on commit 5e9ab55

Please sign in to comment.