Control flow - Client library foundations (#72)

* removed binding references to array type

* refactored initialiseChildren into seperate file

* render function, with code blocks - tested simple cases

* few mores tests for control flow

* md components - getting TestApp to work

* new render wrapper - bug fix

* client: providing access to component root elements
This commit is contained in:
Michael Shanks 2020-01-29 23:01:14 +00:00 committed by GitHub
parent fd3fea37d6
commit be9443561d
5 changed files with 85 additions and 103 deletions

View File

@ -5,8 +5,9 @@ import { setState, setStateFromBinding } from "./state/setState";
import { trimSlash } from "./common/trimSlash"; import { trimSlash } from "./common/trimSlash";
import { isBound } from "./state/isState"; import { isBound } from "./state/isState";
import { _initialiseChildren } from "./render/initialiseChildren"; import { _initialiseChildren } from "./render/initialiseChildren";
import { createTreeNode } from "./render/renderComponent";
export const createApp = (componentLibraries, appDefinition, user, uiFunctions) => { export const createApp = (document, componentLibraries, appDefinition, user, uiFunctions) => {
const coreApi = createCoreApi(appDefinition, user); const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy); appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy);
@ -50,74 +51,32 @@ export const createApp = (componentLibraries, appDefinition, user, uiFunctions)
if(isFunction(event)) event(context); if(isFunction(event)) event(context);
} }
const initialiseChildrenParams = (parentContext, hydrate) => ({ const initialiseChildrenParams = (hydrate, treeNode) => ({
bb, coreApi, store, bb, coreApi, store, document,
componentLibraries, appDefinition, componentLibraries, appDefinition,
parentContext, hydrate, uiFunctions hydrate, uiFunctions, treeNode
}); });
const bb = (context, props) => ({ const bb = (treeNode, componentProps) => ({
hydrateChildren: _initialiseChildren(initialiseChildrenParams(context, true)), hydrateChildren: _initialiseChildren(initialiseChildrenParams(true, treeNode)),
appendChildren: _initialiseChildren(initialiseChildrenParams(context, false)), appendChildren: _initialiseChildren(initialiseChildrenParams(false, treeNode)),
insertChildren: (props, htmlElement, anchor, context) => insertChildren: (props, htmlElement, anchor) =>
_initialiseChildren(initialiseChildrenParams(context, false)) _initialiseChildren(initialiseChildrenParams(false, treeNode))
(props, htmlElement, context, anchor), (props, htmlElement, anchor),
store, context: treeNode.context,
relativeUrl, props: componentProps,
api,
call:safeCallEvent, call:safeCallEvent,
isBound,
setStateFromBinding: (binding, value) => setStateFromBinding(store, binding, value), setStateFromBinding: (binding, value) => setStateFromBinding(store, binding, value),
setState: (path, value) => setState(store, path, value), setState: (path, value) => setState(store, path, value),
getStateOrValue: (prop, currentContext) => getStateOrValue: (prop, currentContext) =>
getStateOrValue(globalState, prop, currentContext), getStateOrValue(globalState, prop, currentContext),
context, store,
props relativeUrl,
api,
isBound,
parent
}); });
return bb(); return bb(createTreeNode());
} }
const buildBindings = (boundProps, boundArrays, contextBoundProps) => {
const bindings = {};
if(boundProps && boundProps.length > 0) {
for(let p of boundProps) {
bindings[p.propName] = {
path: p.path,
fallback: p.fallback,
source: p.source
}
}
}
if(contextBoundProps && contextBoundProps.length > 0) {
for(let p of contextBoundProps) {
bindings[p.propName] = {
path: p.path,
fallback: p.fallback,
source: p.source
}
}
}
if(boundArrays && boundArrays.length > 0) {
for(let a of boundArrays) {
const arrayOfBindings = [];
for(let b of a.arrayOfBindings) {
arrayOfBindings.push(
buildBindings(
b.boundProps,
b.boundArrays,
b.contextBoundProps)
);
}
bindings[a.propName] = arrayOfBindings;
}
}
return bindings;
}

View File

@ -35,16 +35,17 @@ export const loadBudibase = async ({
props = appDefinition.props; props = appDefinition.props;
} }
const _app = createApp( const app = createApp(
window.document,
componentLibraries, componentLibraries,
appDefinition, appDefinition,
user, user,
uiFunctions || {}); uiFunctions || {});
_app.hydrateChildren( app.hydrateChildren(
[props], [props],
window.document.body); window.document.body);
return _app; return app;
}; };
if(window) { if(window) {

View File

@ -9,20 +9,26 @@ import { $ } from "../core/common";
import { renderComponent } from "./renderComponent"; import { renderComponent } from "./renderComponent";
export const _initialiseChildren = (initialiseOpts) => export const _initialiseChildren = (initialiseOpts) =>
(childrenProps, htmlElement, context, anchor=null) => { (childrenProps, htmlElement, anchor=null) => {
const { uiFunctions, bb, coreApi, const { uiFunctions, bb, coreApi,
store, componentLibraries, store, componentLibraries, treeNode,
appDefinition, parentContext, hydrate } = initialiseOpts; appDefinition, document, hydrate } = initialiseOpts;
const childComponents = []; for(let childNode of treeNode.children) {
if(childNode.unsubscribe)
childNode.unsubscribe();
if(childNode.component)
childNode.component.$destroy();
}
if(hydrate) { if(hydrate) {
while (htmlElement.firstChild) { while (htmlElement.firstChild) {
htmlElement.removeChild(htmlElement.firstChild); htmlElement.removeChild(htmlElement.firstChild);
} }
} }
const renderedComponents = [];
for(let childProps of childrenProps) { for(let childProps of childrenProps) {
const {componentName, libName} = splitName(childProps._component); const {componentName, libName} = splitName(childProps._component);
@ -30,28 +36,25 @@ export const _initialiseChildren = (initialiseOpts) =>
if(!componentName || !libName) return; if(!componentName || !libName) return;
const {initialProps, bind} = setupBinding( const {initialProps, bind} = setupBinding(
store, childProps, coreApi, store, childProps, coreApi,
context || parentContext, appDefinition.appRootPath); appDefinition.appRootPath);
/// here needs to go inside renderComponent ???
const componentProps = {
...initialProps,
_bb:bb(context || parentContext, childProps)
};
const componentConstructor = componentLibraries[libName][componentName]; const componentConstructor = componentLibraries[libName][componentName];
const {component} = renderComponent({ const renderedComponentsThisIteration = renderComponent({
props: childProps,
parentNode: treeNode,
componentConstructor,uiFunctions, componentConstructor,uiFunctions,
htmlElement, anchor, htmlElement, anchor, initialProps,
parentContext, componentProps}); bb, document});
for(let comp of renderedComponentsThisIteration) {
bind(component); comp.unsubscribe = bind(comp.component);
childComponents.push(component); renderedComponents.push(comp);
}
} }
return childComponents; return renderedComponents;
} }
const splitName = fullname => { const splitName = fullname => {
@ -64,4 +67,4 @@ const splitName = fullname => {
0, fullname.length - componentName.length - 1); 0, fullname.length - componentName.length - 1);
return {libName, componentName}; return {libName, componentName};
} }

View File

@ -1,30 +1,43 @@
export const renderComponent = ({ export const renderComponent = ({
componentConstructor, uiFunctions, componentConstructor, uiFunctions,
htmlElement, anchor, parentContext, htmlElement, anchor, props,
componentProps}) => { initialProps, bb, document,
parentNode}) => {
const func = componentProps._id const func = initialProps._id
? uiFunctions[componentProps._id] ? uiFunctions[initialProps._id]
: undefined; : undefined;
const parentContext = (parentNode && parentNode.context) || {};
let component; let renderedNodes = [];
let componentContext;
const render = (context) => { const render = (context) => {
let componentContext = parentContext;
if(context) { if(context) {
componentContext = {...componentContext}; componentContext = {...componentContext};
componentContext.$parent = parentContext; componentContext.$parent = parentContext;
} else {
componentContext = parentContext;
} }
component = new componentConstructor({ const thisNode = createTreeNode();
thisNode.context = componentContext;
thisNode.parentNode = parentNode;
parentNode.children.push(thisNode);
renderedNodes.push(thisNode);
initialProps._bb = bb(thisNode, props);
thisNode.component = new componentConstructor({
target: htmlElement, target: htmlElement,
props: componentProps, props: initialProps,
hydrate:false, hydrate:false,
anchor anchor
}); });
thisNode.rootElement = htmlElement.children[
htmlElement.children.length - 1];
} }
if(func) { if(func) {
@ -33,9 +46,15 @@ export const renderComponent = ({
render(); render();
} }
return ({ return renderedNodes;
context: componentContext,
component
});
} }
export const createTreeNode = () => ({
context: {},
rootElement: null,
parentNode: null,
children: [],
component: null,
unsubscribe: () => {}
});

View File

@ -70,7 +70,7 @@ const maketestlib = (window) => ({
node.removeChild(c); node.removeChild(c);
} }
const components = currentProps._bb.appendChildren(currentProps._children, node); const components = currentProps._bb.appendChildren(currentProps._children, node);
childNodes = components.map(c => c._element); childNodes = components.map(c => c.component._element);
} else { } else {
currentProps._bb.hydrateChildren(currentProps._children, node); currentProps._bb.hydrateChildren(currentProps._children, node);
} }