From b607f83675a66540cce4a14fc84903d7d273e14a Mon Sep 17 00:00:00 2001 From: rmorshea Date: Fri, 27 Aug 2021 12:59:31 -0700 Subject: [PATCH 1/2] pass importSource to bind() --- docs/source/javascript-components.rst | 7 ++- .../packages/idom-client-react/package.json | 2 +- .../idom-client-react/src/component.js | 60 ++++++++++--------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/docs/source/javascript-components.rst b/docs/source/javascript-components.rst index 8a6539691..29ca951eb 100644 --- a/docs/source/javascript-components.rst +++ b/docs/source/javascript-components.rst @@ -73,7 +73,12 @@ adheres to the following interface: loadImportSource(source: string, sourceType: "NAME" | "URL") => Module; } - type bind = (node: HTMLElement, context: LayoutContext) => ({ + type ImportSource = { + source: string; + sourceType: string; + } + + type bind = (node: HTMLElement, context: LayoutContext, source: ImportSource) => ({ render(component: any, props: Object, childModels: Array): void; unmount(): void; }); diff --git a/src/client/packages/idom-client-react/package.json b/src/client/packages/idom-client-react/package.json index ed86c10b1..45848f6ee 100644 --- a/src/client/packages/idom-client-react/package.json +++ b/src/client/packages/idom-client-react/package.json @@ -1,7 +1,7 @@ { "name": "idom-client-react", "description": "A client for IDOM implemented in React", - "version": "0.9.2", + "version": "0.10.0", "author": "Ryan Morshead", "license": "MIT", "type": "module", diff --git a/src/client/packages/idom-client-react/src/component.js b/src/client/packages/idom-client-react/src/component.js index ffc0712bb..594afa199 100644 --- a/src/client/packages/idom-client-react/src/component.js +++ b/src/client/packages/idom-client-react/src/component.js @@ -36,6 +36,33 @@ export function Element({ model }) { } } +export function elementChildren(modelChildren) { + if (!modelChildren) { + return []; + } else { + return modelChildren.map((child) => { + switch (typeof child) { + case "object": + return html`<${Element} key=${child.key} model=${child} />`; + case "string": + return child; + } + }); + } +} + +export function elementAttributes(model, sendEvent) { + const attributes = Object.assign({}, model.attributes); + + if (model.eventHandlers) { + for (const [eventName, eventSpec] of Object.entries(model.eventHandlers)) { + attributes[eventName] = eventHandler(sendEvent, eventSpec); + } + } + + return attributes; +} + function StandardElement({ model }) { const config = React.useContext(LayoutConfigContext); const children = elementChildren(model.children); @@ -95,33 +122,6 @@ function RenderImportedElement({ model, importSource }) { return html`
`; } -export function elementChildren(modelChildren) { - if (!modelChildren) { - return []; - } else { - return modelChildren.map((child) => { - switch (typeof child) { - case "object": - return html`<${Element} key=${child.key} model=${child} />`; - case "string": - return child; - } - }); - } -} - -export function elementAttributes(model, sendEvent) { - const attributes = Object.assign({}, model.attributes); - - if (model.eventHandlers) { - for (const [eventName, eventSpec] of Object.entries(model.eventHandlers)) { - attributes[eventName] = eventHandler(sendEvent, eventSpec); - } - } - - return attributes; -} - function eventHandler(sendEvent, eventSpec) { return function () { const data = Array.from(arguments).map((value) => { @@ -152,7 +152,11 @@ function loadImportSource(config, importSource) { return { data: importSource, bind: (node) => { - const binding = module.bind(node, config); + const shortImportSource = { + source: importSource.source, + sourceType: importSource.sourceType, + }; + const binding = module.bind(node, config, shortImportSource); if ( typeof binding.render == "function" && typeof binding.unmount == "function" From 0fa107f8990bdf4576be501f1e7571437b1ad98a Mon Sep 17 00:00:00 2001 From: rmorshea Date: Fri, 27 Aug 2021 13:07:52 -0700 Subject: [PATCH 2/2] check for same sourceInfo in react template This allows the template to determine whether the imported component comes from the same import source. Passing children from different import sources is difficult to handle unless treated with manual care. --- docs/source/javascript-components.rst | 4 +-- src/idom/web/templates/react.js | 40 +++++++++++++++++---------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/docs/source/javascript-components.rst b/docs/source/javascript-components.rst index 29ca951eb..a8e4cfd0b 100644 --- a/docs/source/javascript-components.rst +++ b/docs/source/javascript-components.rst @@ -73,12 +73,12 @@ adheres to the following interface: loadImportSource(source: string, sourceType: "NAME" | "URL") => Module; } - type ImportSource = { + type SourceInfo = { source: string; sourceType: string; } - type bind = (node: HTMLElement, context: LayoutContext, source: ImportSource) => ({ + type bind = (node: HTMLElement, context: LayoutContext, source: SourceInfo) => ({ render(component: any, props: Object, childModels: Array): void; unmount(): void; }); diff --git a/src/idom/web/templates/react.js b/src/idom/web/templates/react.js index 6bbc66f73..0ea54fc31 100644 --- a/src/idom/web/templates/react.js +++ b/src/idom/web/templates/react.js @@ -10,23 +10,30 @@ import { elementChildren, } from "$CDN/idom-client-react"; -export function bind(node, config) { +export function bind(node, config, sourceInfo) { return { render: (component, props, children) => - ReactDOM.render(createElement(config, component, props, children), node), + ReactDOM.render( + createElement(config, sourceInfo, component, props, children), + node + ), unmount: () => ReactDOM.unmountComponentAtNode(node), }; } -function createElement(config, component, props, children) { +function createElement(config, sourceInfo, component, props, children) { return React.createElement( LayoutConfigContext.Provider, { value: config }, - React.createElement(component, props, ...createChildren(children, config)) + React.createElement( + component, + props, + ...createChildren(config, sourceInfo, children) + ) ); } -function createChildren(children, config) { +function createChildren(config, sourceInfo, children) { if (!children) { return []; } @@ -34,35 +41,40 @@ function createChildren(children, config) { if (typeof child == "string") { return child; } else if (child.importSource) { - return createElementFromThisImportSource(child, config); + return createElementFromThisImportSource(config, sourceInfo, child); } else { return React.createElement(Element, { model: child }); } }); } -function createElementFromThisImportSource(model, config) { - const Component = ThisImportSource[model.tagName]; - if (!Component) { +function createElementFromThisImportSource(config, sourceInfo, model) { + if ( + model.importSource.source != sourceInfo.source || + model.importSource.sourceType != sourceInfo.sourceType + ) { console.error( `Cannot create ${model.tagName} from different import source ` + `${model.importSource.source} (type: ${model.importSource.sourceType})` ); + return React.createElement("pre", {}, "error"); } return React.createElement( - Component, + ThisImportSource[model.tagName], elementAttributes(model, (event) => { - event.data = event.data.filter(value => { + event.data = event.data.filter((value) => { try { JSON.stringify(value); } catch (err) { - console.error(`Failed to serialize some event data for ${model.tagName}`) + console.error( + `Failed to serialize some event data for ${model.tagName}` + ); return false; } return true; - }) + }); config.sendEvent(event); }), - ...createChildren(model.children, config) + ...createChildren(config, sourceInfo, model.children) ); }