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 { isBound } from "./state/isState";
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);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy);
@ -50,74 +51,32 @@ export const createApp = (componentLibraries, appDefinition, user, uiFunctions)
if(isFunction(event)) event(context);
}
const initialiseChildrenParams = (parentContext, hydrate) => ({
bb, coreApi, store,
const initialiseChildrenParams = (hydrate, treeNode) => ({
bb, coreApi, store, document,
componentLibraries, appDefinition,
parentContext, hydrate, uiFunctions
hydrate, uiFunctions, treeNode
});
const bb = (context, props) => ({
hydrateChildren: _initialiseChildren(initialiseChildrenParams(context, true)),
appendChildren: _initialiseChildren(initialiseChildrenParams(context, false)),
insertChildren: (props, htmlElement, anchor, context) =>
_initialiseChildren(initialiseChildrenParams(context, false))
(props, htmlElement, context, anchor),
store,
relativeUrl,
api,
const bb = (treeNode, componentProps) => ({
hydrateChildren: _initialiseChildren(initialiseChildrenParams(true, treeNode)),
appendChildren: _initialiseChildren(initialiseChildrenParams(false, treeNode)),
insertChildren: (props, htmlElement, anchor) =>
_initialiseChildren(initialiseChildrenParams(false, treeNode))
(props, htmlElement, anchor),
context: treeNode.context,
props: componentProps,
call:safeCallEvent,
isBound,
setStateFromBinding: (binding, value) => setStateFromBinding(store, binding, value),
setState: (path, value) => setState(store, path, value),
getStateOrValue: (prop, currentContext) =>
getStateOrValue(globalState, prop, currentContext),
context,
props
store,
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;
}
const _app = createApp(
const app = createApp(
window.document,
componentLibraries,
appDefinition,
user,
uiFunctions || {});
_app.hydrateChildren(
app.hydrateChildren(
[props],
window.document.body);
return _app;
return app;
};
if(window) {

View File

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

View File

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

View File

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