diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js index 477e9bf236..d2c4dc2c17 100644 --- a/packages/client/src/createApp.js +++ b/packages/client/src/createApp.js @@ -14,22 +14,23 @@ import { trimSlash } from "./common/trimSlash"; export const createApp = (componentLibraries, appDefinition, user) => { - const initialiseComponent = (props, htmlElement) => { + const initialiseComponent = (parentContext) => (props, htmlElement, context) => { const {componentName, libName} = splitName(props._component); if(!componentName || !libName) return; - const {initialProps, bind} = setupBinding(store, props, coreApi); + const {initialProps, bind} = setupBinding(store, props, coreApi, context); const component = new (componentLibraries[libName][componentName])({ target: htmlElement, - props: {...initialProps, _bb}, + props: {...initialProps, _bb:bbInContext(context || parentContext)}, hydrate:true }); bind(component); + return component; } const coreApi = createCoreApi(appDefinition, user); @@ -65,16 +66,26 @@ export const createApp = (componentLibraries, appDefinition, user) => { delete:apiCall("DELETE") }; - const _bb = { - initialiseComponent, + const bb = () => ({ + initialiseComponent: initialiseComponent(), store, relativeUrl, api, getStateOrValue: (prop, currentContext) => getStateOrValue(globalState, prop, currentContext) - }; + }); - return _bb; + const bbRoot = bb(); + + const bbInContext = (context) => { + if(!context) return bbRoot; + const bbCxt = bb(); + bbCxt.context = context; + bbCxt.initialiseComponent=initialiseComponent(context); + return bbCxt; + } + + return bbRoot; } diff --git a/packages/client/src/state/isState.js b/packages/client/src/state/isState.js index e57abe9f68..32ca6ec63a 100644 --- a/packages/client/src/state/isState.js +++ b/packages/client/src/state/isState.js @@ -5,4 +5,10 @@ export const BB_STATE_FALLBACK = "##bbstatefallback"; export const isBound = (prop) => prop[BB_STATE_BINDINGPATH] !== undefined; export const takeStateFromStore = (prop) => prop[BB_STATE_BINDINGSOURCE] === undefined - || prop[BB_STATE_BINDINGSOURCE] === "store"; \ No newline at end of file + || prop[BB_STATE_BINDINGSOURCE] === "store"; + +export const takeStateFromContext = (prop) => + prop[BB_STATE_BINDINGSOURCE] === "context"; + +export const takeStateFromEventParameters = (prop) => + prop[BB_STATE_BINDINGSOURCE] === "event"; \ No newline at end of file diff --git a/packages/client/src/state/stateBinding.js b/packages/client/src/state/stateBinding.js index ae6bffe647..945b96faa2 100644 --- a/packages/client/src/state/stateBinding.js +++ b/packages/client/src/state/stateBinding.js @@ -8,11 +8,12 @@ import { import { isBound, takeStateFromStore, + takeStateFromContext, takeStateFromEventParameters, BB_STATE_FALLBACK, BB_STATE_BINDINGPATH } from "./isState"; const doNothing = () => {}; -export const setupBinding = (store, rootProps, coreApi) => { +export const setupBinding = (store, rootProps, coreApi, context) => { const rootInitialProps = {...rootProps}; @@ -39,6 +40,17 @@ export const setupBinding = (store, rootProps, coreApi) => { }); initialProps[propName] = fallback; + } else if(isBound(val) && takeStateFromContext(val)) { + + const binding = stateBinding(val); + const fallback = stateFallback(val); + + initialProps[propName] = getState( + context || {}, + binding, + fallback + ); + } else if(isEventType(val)) { const handlers = { propName, handlers:[] }; @@ -107,7 +119,7 @@ export const setupBinding = (store, rootProps, coreApi) => { const closuredHandlers = []; for(let h of boundHandler.handlers) { const handlerType = handlerTypes[h.handlerType]; - closuredHandlers.push((context) => { + closuredHandlers.push((eventContext) => { const parameters = {}; for(let pname in h.parameters) { @@ -118,8 +130,13 @@ export const setupBinding = (store, rootProps, coreApi) => { : takeStateFromStore(p) ? getState( s, p[BB_STATE_BINDINGPATH], p[BB_STATE_FALLBACK]) - : getState( + : takeStateFromEventParameters(p) + ? getState( + eventContext, p[BB_STATE_BINDINGPATH], p[BB_STATE_FALLBACK]) + : takeStateFromContext(p) + ? getState( context, p[BB_STATE_BINDINGPATH], p[BB_STATE_FALLBACK]) + : p[BB_STATE_FALLBACK]; } handlerType.execute(parameters) diff --git a/packages/client/tests/stateBinding.spec.js b/packages/client/tests/stateBinding.spec.js index 874cb394a4..f34be14419 100644 --- a/packages/client/tests/stateBinding.spec.js +++ b/packages/client/tests/stateBinding.spec.js @@ -148,7 +148,7 @@ describe("setupBinding", () => { }); - it("event handlers should recognise context state", () => { + it("event handlers should recognise event parameter", () => { const {component, store, props} = testSetup(); @@ -156,7 +156,7 @@ describe("setupBinding", () => { bind(component); expect(component.props.boundToEventOutput).toBe("initial address"); - component.props.eventBoundUsingContext({addressOverride: "Overridden Address"}); + component.props.eventBoundUsingEventParam({addressOverride: "Overridden Address"}); expect(component.props.boundToEventOutput).toBe("Overridden Address"); store.update(s => { @@ -167,14 +167,27 @@ describe("setupBinding", () => { component.props.eventBound(); expect(component.props.boundToEventOutput).toBe("123 Main Street"); - component.props.eventBoundUsingContext({addressOverride: "Overridden Address"}); + component.props.eventBoundUsingEventParam({addressOverride: "Overridden Address"}); expect(component.props.boundToEventOutput).toBe("Overridden Address"); }); + it("should bind initial props to supplied context", () => { + + const {component, store, props} = testSetup(); + + const {bind} = testSetupBinding(store, props, component, { + ContextValue : "Real Context Value" + }); + bind(component); + + expect(component.props.boundToContext).toBe("Real Context Value"); + + }); + }); -const testSetupBinding = (store, props, component) => { - const setup = setupBinding(store, props); +const testSetupBinding = (store, props, component, context) => { + const setup = setupBinding(store, props, undefined, context); component.props = setup.initialProps; // svelte does this for us in real life return setup; } @@ -210,16 +223,17 @@ const testSetup = () => { unbound: "hello", multiPartBound: binding("Customer.Name", "ACME"), boundToEventOutput: binding("Customer.Address", "initial address"), + boundToContext: binding("ContextValue", "context fallback", "context"), eventBound: [ event("Set State", { path: "Customer.Address", value: binding("addressToSet", "event fallback address") }) ], - eventBoundUsingContext: [ + eventBoundUsingEventParam: [ event("Set State", { path: "Customer.Address", - value: binding("addressOverride", "", "context") + value: binding("addressOverride", "", "event") }) ], arrayWithInnerBinding: [ diff --git a/packages/standard-components/components.json b/packages/standard-components/components.json index 3bf37dcabe..5a1c3115c7 100644 --- a/packages/standard-components/components.json +++ b/packages/standard-components/components.json @@ -74,7 +74,9 @@ "width": {"type":"string","default":"auto"}, "height": {"type":"string","default":"auto"}, "containerClass":"string", - "itemContainerClass":"string" + "itemContainerClass":"string", + "data": "state", + "dataItemComponent": "component" }, "tags": ["div", "container", "layout", "panel"] }, diff --git a/packages/standard-components/public/bundle.css b/packages/standard-components/public/bundle.css index 07b4d80529..55c074cc1b 100644 --- a/packages/standard-components/public/bundle.css +++ b/packages/standard-components/public/bundle.css @@ -1,11 +1,12 @@ #current_component.svelte-1xqz9vm{height:100%;width:100%} -.root.svelte-1oto99m{height:100%;display:grid;grid-template-columns:[left] 1fr [middle] auto [right] 1fr;grid-template-rows:[top] 1fr [center] auto [bottom] 1fr}.content.svelte-1oto99m{grid-column-start:middle;grid-row-start:center;width:400px}.logo-container.svelte-1oto99m{margin-bottom:20px -}.logo-container.svelte-1oto99m>img.svelte-1oto99m{max-width:100%}.login-button-container.svelte-1oto99m{text-align:right;margin-top:20px}.incorrect-details-panel.svelte-1oto99m{margin-top:30px;padding:10px;border-style:solid;border-width:1px;border-color:maroon;border-radius:1px;text-align:center;color:maroon;background-color:mistyrose}.form-root.svelte-1oto99m{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-1oto99m{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-1oto99m{grid-column-start:control;padding:5px 10px} +.root.svelte-crnq0a{height:100%;display:grid;grid-template-columns:[left] 1fr [middle] auto [right] 1fr;grid-template-rows:[top] 1fr [center] auto [bottom] 1fr}.content.svelte-crnq0a{grid-column-start:middle;grid-row-start:center;width:400px}.logo-container.svelte-crnq0a{margin-bottom:20px +}.logo-container.svelte-crnq0a>img.svelte-crnq0a{max-width:100%}.login-button-container.svelte-crnq0a{text-align:right;margin-top:20px}.incorrect-details-panel.svelte-crnq0a{margin-top:30px;padding:10px;border-style:solid;border-width:1px;border-color:maroon;border-radius:1px;text-align:center;color:maroon;background-color:mistyrose}.form-root.svelte-crnq0a{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-crnq0a{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-crnq0a{grid-column-start:control;padding:5px 10px}.default-input.svelte-crnq0a{font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;width:100%}.default-button.svelte-crnq0a{font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;color:#333;background-color:#f4f4f4;outline:none}.default-button.svelte-crnq0a:active{background-color:#ddd}.default-button.svelte-crnq0a:focus{border-color:#666} +.form-root.svelte-m9d6ue{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-m9d6ue{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-m9d6ue{grid-column-start:control;padding:5px 10px}.overflow.svelte-m9d6ue{grid-column-start:overflow}.full-width.svelte-m9d6ue{width:100%} +.default.svelte-1ec4wqj{width:100%;font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;width:100%}.default.svelte-1ec4wqj:disabled{color:#ccc} .root.svelte-10kw8to{display:grid} .root.svelte-aihwli{height:100%;width:100%;grid-template-columns:[navbar] auto [content] 1fr;display:grid}.navbar.svelte-aihwli{grid-column:navbar;background:var(--navBarBackground);border:var(--navBarBorder);color:var(--navBarColor)}.navitem.svelte-aihwli{padding:10px 17px;cursor:pointer}.navitem.svelte-aihwli:hover{background:var(--itemHoverBackground);color:var(--itemHoverColor)}.navitem.selected.svelte-aihwli{background:var(--selectedItemBackground);border:var(--selectedItemBorder);color:var(--selectedItemColor)}.content.svelte-aihwli{grid-column:content} -.default.svelte-1ec4wqj{width:100%;font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;width:100%}.default.svelte-1ec4wqj:disabled{color:#ccc} -.form-root.svelte-m9d6ue{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-m9d6ue{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-m9d6ue{grid-column-start:control;padding:5px 10px}.overflow.svelte-m9d6ue{grid-column-start:overflow}.full-width.svelte-m9d6ue{width:100%} .table-default.svelte-h8rqk6{width:100%;margin-bottom:1rem;color:#212529;border-collapse:collapse}.table-default.svelte-h8rqk6 .thead-default .th-default.svelte-h8rqk6{vertical-align:bottom;border-bottom:2px solid #dee2e6;font-weight:bold}.table-default.svelte-h8rqk6 .th-default.svelte-h8rqk6{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6;font-weight:normal}.th-default.svelte-h8rqk6{text-align:inherit}.table-default.svelte-h8rqk6 .tbody-default .tr-default.svelte-h8rqk6:hover{color:#212529;background-color:rgba(0,0,0,.075);cursor:pointer} +.horizontal.svelte-osi0db{display:inline-block}.vertical.svelte-osi0db{display:block} .default.svelte-1q8lga0{font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;color:#333;background-color:#f4f4f4;outline:none}.default.svelte-1q8lga0:active{background-color:#ddd}.default.svelte-1q8lga0:focus{border-color:#666} /*# sourceMappingURL=bundle.css.map */ \ No newline at end of file diff --git a/packages/standard-components/public/bundle.css.map b/packages/standard-components/public/bundle.css.map index 7a057a727e..95736da2e9 100644 --- a/packages/standard-components/public/bundle.css.map +++ b/packages/standard-components/public/bundle.css.map @@ -4,23 +4,25 @@ "sources": [ "..\\src\\Test\\TestApp.svelte", "..\\src\\Login.svelte", + "..\\src\\Form.svelte", + "..\\src\\Textbox.svelte", "..\\src\\Grid.svelte", "..\\src\\Nav.svelte", - "..\\src\\Textbox.svelte", - "..\\src\\Form.svelte", "..\\src\\Table.svelte", + "..\\src\\StackPanel.svelte", "..\\src\\Button.svelte" ], "sourcesContent": [ - "\n\n{#await _appPromise}\nloading\n{:then _bb}\n\n
{col.title} | \r\n {/each}\r\n
---|
{_bb.getStateOrValue(col.value, row)} | \r\n {/each}\r\n