diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js index f641c46d95..accad9f51e 100644 --- a/packages/client/src/createApp.js +++ b/packages/client/src/createApp.js @@ -6,8 +6,7 @@ import { trimSlash } from "./common/trimSlash"; import { isBound } from "./state/isState"; import { _initialiseChildren } from "./render/initialiseChildren"; -export const createApp = (componentLibraries, appDefinition, user) => { - +export const createApp = (componentLibraries, appDefinition, user, uiFunctions) => { const coreApi = createCoreApi(appDefinition, user); appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy); @@ -37,10 +36,10 @@ export const createApp = (componentLibraries, appDefinition, user) => { }); const api = { - post: apiCall("POST"), - get: apiCall("GET"), - patch: apiCall("PATCH"), - delete:apiCall("DELETE") + post: apiCall("POST"), + get: apiCall("GET"), + patch: apiCall("PATCH"), + delete: apiCall("DELETE") }; const safeCallEvent = (event, context) => { @@ -54,7 +53,7 @@ export const createApp = (componentLibraries, appDefinition, user) => { const initialiseChildrenParams = (parentContext, hydrate) => ({ bb, coreApi, store, componentLibraries, appDefinition, - parentContext, hydrate + parentContext, hydrate, uiFunctions }); const bb = (context, props) => ({ diff --git a/packages/client/src/index.js b/packages/client/src/index.js index f7003b816b..e75f9a08ab 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,7 +1,9 @@ import { createApp } from "./createApp"; import { trimSlash } from "./common/trimSlash"; -export const loadBudibase = async ({componentLibraries, props, window, localStorage}) => { +export const loadBudibase = async ({ + componentLibraries, props, + window, localStorage, uiFunctions }) => { const appDefinition = window["##BUDIBASE_APPDEFINITION##"]; @@ -33,7 +35,7 @@ export const loadBudibase = async ({componentLibraries, props, window, localStor props = appDefinition.props; } - const _app = createApp(componentLibraries, appDefinition, user); + const _app = createApp(componentLibraries, appDefinition, user, uiFunctions); _app.hydrateChildren( [props], window.document.body); diff --git a/packages/client/src/render/initialiseChildren.js b/packages/client/src/render/initialiseChildren.js index 2dd51b2768..50a0052642 100644 --- a/packages/client/src/render/initialiseChildren.js +++ b/packages/client/src/render/initialiseChildren.js @@ -6,10 +6,15 @@ import { last } from "lodash/fp"; import { $ } from "../core/common"; +import { renderComponent } from "./renderComponent"; -export const _initialiseChildren = ({ bb, coreApi, store, componentLibraries, appDefinition, parentContext, hydrate }) => +export const _initialiseChildren = (initialiseOpts) => (childrenProps, htmlElement, context, anchor=null) => { + const { uiFunctions, bb, coreApi, + store, componentLibraries, + appDefinition, parentContext, hydrate } = initialiseOpts; + const childComponents = []; if(hydrate) { @@ -18,27 +23,29 @@ export const _initialiseChildren = ({ bb, coreApi, store, componentLibraries, ap } } - for(let childProps of childrenProps) { + for(let childProps of childrenProps) { + const {componentName, libName} = splitName(childProps._component); if(!componentName || !libName) return; - + const {initialProps, bind} = setupBinding( store, childProps, coreApi, context || parentContext, appDefinition.appRootPath); - + /// here needs to go inside renderComponent ??? const componentProps = { ...initialProps, _bb:bb(context || parentContext, childProps) }; - const component = new (componentLibraries[libName][componentName])({ - target: htmlElement, - props: componentProps, - hydrate:false, - anchor - }); + const componentConstructor = componentLibraries[libName][componentName]; + + const {component} = renderComponent({ + componentConstructor,uiFunctions, + htmlElement, anchor, + parentContext, componentProps}); + bind(component); childComponents.push(component); diff --git a/packages/client/src/render/renderComponent.js b/packages/client/src/render/renderComponent.js new file mode 100644 index 0000000000..890ed4037f --- /dev/null +++ b/packages/client/src/render/renderComponent.js @@ -0,0 +1,38 @@ + +export const renderComponent = ({ + componentConstructor, uiFunctions, + htmlElement, anchor, parentContext, + componentProps}) => { + + const func = uiFunctions[componentProps._id]; + let component; + let componentContext; + const render = (context) => { + + if(context) { + componentContext = {...componentContext}; + componentContext.$parent = parentContext; + } else { + componentContext = parentContext; + } + + component = new componentConstructor({ + target: htmlElement, + props: componentProps, + hydrate:false, + anchor + }); + } + + if(func) { + func(render, parentContext); + } else { + render(); + } + + return ({ + context: componentContext, + component + }); +} + diff --git a/packages/client/tests/domControlFlow.spec.js b/packages/client/tests/domControlFlow.spec.js new file mode 100644 index 0000000000..fcdf1fa456 --- /dev/null +++ b/packages/client/tests/domControlFlow.spec.js @@ -0,0 +1,52 @@ +import { load } from "./testAppDef"; + +describe("controlFlow", () => { + + it("should display simple div, with always true render function", async () => { + + const {dom} = await load({ + _component: "testlib/div", + className: "my-test-class", + _id: "always_render" + }); + + expect(dom.window.document.body.children.length).toBe(1); + const child = dom.window.document.body.children[0]; + expect(child.className).toBe("my-test-class"); + + }) + + it("should not display div, with always false render function", async () => { + + const {dom} = await load({ + _component: "testlib/div", + className: "my-test-class", + _id: "never_render" + }); + + expect(dom.window.document.body.children.length).toBe(0); + + }) + + it("should display 3 divs in a looped render function", async () => { + + const {dom} = await load({ + _component: "testlib/div", + className: "my-test-class", + _id: "three_clones" + }); + + expect(dom.window.document.body.children.length).toBe(3); + + const child0 = dom.window.document.body.children[0]; + expect(child0.className).toBe("my-test-class"); + + const child1 = dom.window.document.body.children[1]; + expect(child1.className).toBe("my-test-class"); + + const child2 = dom.window.document.body.children[2]; + expect(child2.className).toBe("my-test-class"); + + }) + +}); diff --git a/packages/client/tests/testAppDef.js b/packages/client/tests/testAppDef.js index 0a08978784..e028730890 100644 --- a/packages/client/tests/testAppDef.js +++ b/packages/client/tests/testAppDef.js @@ -3,16 +3,32 @@ import { loadBudibase } from "../src/index"; export const load = async (props) => { const dom = new JSDOM(``); + autoAssignIds(props); setAppDef(dom.window, props); const app = await loadBudibase({ componentLibraries: allLibs(dom.window), window: dom.window, localStorage: createLocalStorage(), - props + props, + uiFunctions }); return {dom, app}; } +// this happens for real by the builder... +// ..this only assigns _ids when missing +const autoAssignIds = (props, count=0) => { + if(!props._id) { + props._id = `auto_id_${count}`; + } + if(props._children) { + for(let child of props._children) { + count += 1; + autoAssignIds(child, count); + } + } +} + const setAppDef = (window, props) => { window["##BUDIBASE_APPDEFINITION##"] = ({ componentLibraries: [], @@ -87,5 +103,18 @@ const maketestlib = (window) => ({ } }); +const uiFunctions = ({ + never_render : (render, parentContext) => {}, + + always_render : (render, parentContext) => { + render(); + }, + + three_clones : (render, parentContext) => { + for(let i = 0; i<3; i++) { + render(); + } + } +});