Skip to content

Commit 693b112

Browse files
committed
New branch off of reactive-python#1289 to highlight vdom approach
1 parent 3a159af commit 693b112

File tree

6 files changed

+90
-53
lines changed

6 files changed

+90
-53
lines changed

src/js/packages/@reactpy/client/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export type ReactPyVdom = {
5353
children?: (ReactPyVdom | string)[];
5454
error?: string;
5555
eventHandlers?: { [key: string]: ReactPyVdomEventHandler };
56+
jsExecutables?: { [key: string]: string };
5657
importSource?: ReactPyVdomImportSource;
5758
};
5859

src/js/packages/@reactpy/client/src/vdom.tsx

+44-37
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ export function createAttributes(
189189
createEventHandler(client, name, handler),
190190
),
191191
),
192+
...Object.fromEntries(
193+
Object.entries(model.jsExecutables || {}).map(([name, executable]) =>
194+
createJSExecutable(name, executable),
195+
),
196+
),
192197
}),
193198
);
194199
}
@@ -198,41 +203,43 @@ function createEventHandler(
198203
name: string,
199204
{ target, preventDefault, stopPropagation }: ReactPyVdomEventHandler,
200205
): [string, () => void] {
201-
if (target.indexOf("__javascript__: ") == 0) {
202-
return [
203-
name,
204-
function (...args: any[]) {
205-
function handleEvent(...args: any[]) {
206-
const evalResult = eval(target.replace("__javascript__: ", ""));
207-
if (typeof evalResult == "function") {
208-
return evalResult(...args);
209-
}
210-
}
211-
if (args.length > 0 && args[0] instanceof Event) {
212-
return handleEvent.call(args[0].target, ...args);
213-
} else {
214-
return handleEvent(...args);
215-
}
216-
},
217-
];
218-
}
219-
return [
220-
name,
221-
function (...args: any[]) {
222-
const data = Array.from(args).map((value) => {
223-
if (!(typeof value === "object" && value.nativeEvent)) {
224-
return value;
225-
}
226-
const event = value as React.SyntheticEvent<any>;
227-
if (preventDefault) {
228-
event.preventDefault();
229-
}
230-
if (stopPropagation) {
231-
event.stopPropagation();
232-
}
233-
return serializeEvent(event.nativeEvent);
234-
});
235-
client.sendMessage({ type: "layout-event", data, target });
236-
},
237-
];
206+
const eventHandler = function (...args: any[]) {
207+
const data = Array.from(args).map((value) => {
208+
if (!(typeof value === "object" && value.nativeEvent)) {
209+
return value;
210+
}
211+
const event = value as React.SyntheticEvent<any>;
212+
if (preventDefault) {
213+
event.preventDefault();
214+
}
215+
if (stopPropagation) {
216+
event.stopPropagation();
217+
}
218+
return serializeEvent(event.nativeEvent);
219+
});
220+
client.sendMessage({ type: "layout-event", data, target });
221+
};
222+
eventHandler.isHandler = true;
223+
return [name, eventHandler];
224+
}
225+
226+
function createJSExecutable(
227+
name: string,
228+
executable: string,
229+
): [string, () => void] {
230+
const wrappedExecutable = function (...args: any[]) {
231+
function handleExecution(...args: any[]) {
232+
const evalResult = eval(executable);
233+
if (typeof evalResult == "function") {
234+
return evalResult(...args);
235+
}
236+
}
237+
if (args.length > 0 && args[0] instanceof Event) {
238+
return handleExecution.call(args[0].currentTarget, ...args);
239+
} else {
240+
return handleExecution(...args);
241+
}
242+
};
243+
wrappedExecutable.isHandler = false;
244+
return [name, wrappedExecutable];
238245
}

src/reactpy/core/layout.py

+4
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ def _render_model_attributes(
263263
attrs = raw_model["attributes"].copy()
264264
new_state.model.current["attributes"] = attrs
265265

266+
if "jsExecutables" in raw_model:
267+
executables = raw_model["jsExecutables"].copy()
268+
new_state.model.current["jsExecutables"] = executables
269+
266270
if old_state is None:
267271
self._render_model_event_handlers_without_old_state(
268272
new_state, handlers_by_event

src/reactpy/core/vdom.py

+23-14
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
EventHandlerType,
2626
ImportSourceDict,
2727
JavaScript,
28+
JSExecutableDict,
2829
VdomAttributes,
2930
VdomChildren,
3031
VdomDict,
@@ -46,6 +47,7 @@
4647
"children": {"$ref": "#/definitions/elementChildren"},
4748
"attributes": {"type": "object"},
4849
"eventHandlers": {"$ref": "#/definitions/elementEventHandlers"},
50+
"jsExecutables": {"$ref": "#/definitions/elementJSExecutables"},
4951
"importSource": {"$ref": "#/definitions/importSource"},
5052
},
5153
# The 'tagName' is required because its presence is a useful indicator of
@@ -75,6 +77,12 @@
7577
},
7678
"required": ["target"],
7779
},
80+
"elementJSExecutables": {
81+
"type": "object",
82+
"patternProperties": {
83+
".*": "str",
84+
},
85+
},
7886
"importSource": {
7987
"type": "object",
8088
"properties": {
@@ -164,7 +172,9 @@ def __call__(
164172
"""The entry point for the VDOM API, for example reactpy.html(<WE_ARE_HERE>)."""
165173
attributes, children = separate_attributes_and_children(attributes_and_children)
166174
key = attributes.get("key", None)
167-
attributes, event_handlers = separate_attributes_and_event_handlers(attributes)
175+
attributes, event_handlers, js_executables = (
176+
separate_attributes_handlers_and_executables(attributes)
177+
)
168178
if REACTPY_CHECK_JSON_ATTRS.current:
169179
json.dumps(attributes)
170180

@@ -184,6 +194,7 @@ def __call__(
184194
**({"children": children} if children else {}),
185195
**({"attributes": attributes} if attributes else {}),
186196
**({"eventHandlers": event_handlers} if event_handlers else {}),
197+
**({"jsExecutables": js_executables} if js_executables else {}),
187198
**({"importSource": self.import_source} if self.import_source else {}),
188199
}
189200

@@ -216,28 +227,26 @@ def separate_attributes_and_children(
216227
return _attributes, _children
217228

218229

219-
def separate_attributes_and_event_handlers(
230+
def separate_attributes_handlers_and_executables(
220231
attributes: Mapping[str, Any],
221-
) -> tuple[VdomAttributes, EventHandlerDict]:
232+
) -> tuple[VdomAttributes, EventHandlerDict, JSExecutableDict]:
222233
_attributes: VdomAttributes = {}
223-
_event_handlers: dict[str, EventHandlerType | JavaScript] = {}
234+
_event_handlers: dict[str, EventHandlerType] = {}
235+
_js_executables: dict[str, JavaScript] = {}
224236

225237
for k, v in attributes.items():
226-
handler: EventHandlerType | JavaScript
227-
228238
if callable(v):
229-
handler = EventHandler(to_event_handler_function(v))
239+
_event_handlers[k] = EventHandler(to_event_handler_function(v))
240+
elif isinstance(v, EventHandler):
241+
_event_handlers[k] = v
230242
elif EVENT_ATTRIBUTE_PATTERN.match(k) and isinstance(v, str):
231-
handler = JavaScript(v)
232-
elif isinstance(v, (EventHandler, JavaScript)):
233-
handler = v
243+
_js_executables[k] = JavaScript(v)
244+
elif isinstance(v, JavaScript):
245+
_js_executables[k] = v
234246
else:
235247
_attributes[k] = v
236-
continue
237-
238-
_event_handlers[k] = handler
239248

240-
return _attributes, _event_handlers
249+
return _attributes, _event_handlers, _js_executables
241250

242251

243252
def _flatten_children(children: Sequence[Any]) -> list[Any]:

src/reactpy/types.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ class DangerouslySetInnerHTML(TypedDict):
768768
"children",
769769
"attributes",
770770
"eventHandlers",
771+
"jsExecutables",
771772
"importSource",
772773
]
773774
ALLOWED_VDOM_KEYS = {
@@ -776,6 +777,7 @@ class DangerouslySetInnerHTML(TypedDict):
776777
"children",
777778
"attributes",
778779
"eventHandlers",
780+
"jsExecutables",
779781
"importSource",
780782
}
781783

@@ -788,6 +790,7 @@ class VdomTypeDict(TypedDict):
788790
children: NotRequired[Sequence[ComponentType | VdomChild]]
789791
attributes: NotRequired[VdomAttributes]
790792
eventHandlers: NotRequired[EventHandlerDict]
793+
jsExecutables: NotRequired[JavaScript]
791794
importSource: NotRequired[ImportSourceDict]
792795

793796

@@ -818,6 +821,8 @@ def __getitem__(self, key: Literal["attributes"]) -> VdomAttributes: ...
818821
@overload
819822
def __getitem__(self, key: Literal["eventHandlers"]) -> EventHandlerDict: ...
820823
@overload
824+
def __getitem__(self, key: Literal["jsExecutables"]) -> JSExecutableDict: ...
825+
@overload
821826
def __getitem__(self, key: Literal["importSource"]) -> ImportSourceDict: ...
822827
def __getitem__(self, key: VdomDictKeys) -> Any:
823828
return super().__getitem__(key)
@@ -839,6 +844,10 @@ def __setitem__(
839844
self, key: Literal["eventHandlers"], value: EventHandlerDict
840845
) -> None: ...
841846
@overload
847+
def __setitem__(
848+
self, key: Literal["jsExecutables"], value: JSExecutableDict
849+
) -> None: ...
850+
@overload
842851
def __setitem__(
843852
self, key: Literal["importSource"], value: ImportSourceDict
844853
) -> None: ...
@@ -871,6 +880,7 @@ class VdomJson(TypedDict):
871880
children: NotRequired[list[Any]]
872881
attributes: NotRequired[VdomAttributes]
873882
eventHandlers: NotRequired[dict[str, JsonEventTarget]]
883+
jsExecutables: NotRequired[dict[str, JavaScript]]
874884
importSource: NotRequired[JsonImportSource]
875885

876886

@@ -923,9 +933,15 @@ class EventHandlerType(Protocol):
923933
EventHandlerMapping = Mapping[str, EventHandlerType]
924934
"""A generic mapping between event names to their handlers"""
925935

926-
EventHandlerDict: TypeAlias = dict[str, EventHandlerType | JavaScript]
936+
EventHandlerDict: TypeAlias = dict[str, EventHandlerType]
927937
"""A dict mapping between event names to their handlers"""
928938

939+
JSExecutableMapping = Mapping[str, JavaScript]
940+
"""A generic mapping between event names to their javascript"""
941+
942+
JSExecutableDict: TypeAlias = dict[str, JavaScript]
943+
"""A dict mapping between attribute names to their javascript"""
944+
929945

930946
class VdomConstructor(Protocol):
931947
"""Standard function for constructing a :class:`VdomDict`"""

src/reactpy/web/templates/react.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function bind(node, config) {
2929
function wrapEventHandlers(props) {
3030
const newProps = Object.assign({}, props);
3131
for (const [key, value] of Object.entries(props)) {
32-
if (typeof value === "function" && value.toString().includes(".sendMessage")) {
32+
if (typeof value === "function" && value.isHandler) {
3333
newProps[key] = makeJsonSafeEventHandler(value);
3434
}
3535
}

0 commit comments

Comments
 (0)