budibase/packages/client/src/createApp.js

177 lines
4.8 KiB
JavaScript

import {
split,
last
} from "lodash/fp";
import {writable} from "svelte/store";
import { $ } from "./core/common";
import {
setupBinding
} from "./state/stateBinding";
import { createCoreApi } from "./core";
import { getStateOrValue } from "./state/getState";
import { setState, setStateFromBinding } from "./state/setState";
import { trimSlash } from "./common/trimSlash";
import { isBound } from "./state/isState";
export const createApp = (componentLibraries, appDefinition, user) => {
const _initialiseChildren = (parentContext, hydrate) => (childrenProps, htmlElement, context, anchor=null) => {
const childComponents = [];
if(hydrate) {
while (htmlElement.firstChild) {
htmlElement.removeChild(htmlElement.firstChild);
}
}
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);
const componentProps = {
...initialProps,
_bb:bb(context || parentContext, childProps)
};
const component = new (componentLibraries[libName][componentName])({
target: htmlElement,
props: componentProps,
hydrate:false,
anchor
});
bind(component);
childComponents.push(component);
}
return childComponents;
}
const coreApi = createCoreApi(appDefinition, user);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy);
const store = writable({
_bbuser: user
});
let globalState = null;
store.subscribe(s => {
globalState = s;
});
const relativeUrl = (url) =>
appDefinition.appRootPath
? appDefinition.appRootPath + "/" + trimSlash(url)
: url;
const apiCall = (method) => (url, body) =>
fetch(relativeUrl(url), {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: body && JSON.stringify(body),
});
const api = {
post: apiCall("POST"),
get: apiCall("GET"),
patch: apiCall("PATCH"),
delete:apiCall("DELETE")
};
const safeCallEvent = (event, context) => {
const isFunction = (obj) =>
!!(obj && obj.constructor && obj.call && obj.apply);
if(isFunction(event)) event(context);
}
const bb = (context, props) => ({
hydrateChildren: _initialiseChildren(context, true),
appendChildren: _initialiseChildren(context, false),
insertChildren: (props, htmlElement, anchor, context) =>
_initialiseChildren(context, false)(props, htmlElement, context, anchor),
store,
relativeUrl,
api,
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
});
return bb();
}
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;
}
const splitName = fullname => {
const componentName = $(fullname, [
split("/"),
last
]);
const libName =fullname.substring(
0, fullname.length - componentName.length - 1);
return {libName, componentName};
}