mirror of https://github.com/Budibase/budibase.git
18 changed files with 742 additions and 154 deletions
@ -0,0 +1,17 @@ |
|||
export const insertCodeMetadata = props => { |
|||
if (props._code && props._code.length > 0) { |
|||
props._codeMeta = codeMetaData(props._code) |
|||
} |
|||
|
|||
if (!props._children || props._children.length === 0) return |
|||
|
|||
for (let child of props._children) { |
|||
insertCodeMetadata(child) |
|||
} |
|||
} |
|||
|
|||
const codeMetaData = code => { |
|||
return { |
|||
dependsOnStore: RegExp(/(store.)/g).test(code), |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
import { getStateOrValue } from "./getState" |
|||
import { setState, setStateFromBinding } from "./setState" |
|||
import { trimSlash } from "../common/trimSlash" |
|||
import { isBound } from "./isState" |
|||
import { attachChildren } from "../render/attachChildren" |
|||
|
|||
export const bbFactory = ({ |
|||
store, |
|||
getCurrentState, |
|||
frontendDefinition, |
|||
componentLibraries, |
|||
uiFunctions, |
|||
onScreenSlotRendered, |
|||
}) => { |
|||
const relativeUrl = url => |
|||
frontendDefinition.appRootPath |
|||
? frontendDefinition.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) |
|||
} |
|||
|
|||
return (treeNode, setupState) => { |
|||
const attachParams = { |
|||
componentLibraries, |
|||
uiFunctions, |
|||
treeNode, |
|||
onScreenSlotRendered, |
|||
setupState, |
|||
getCurrentState, |
|||
} |
|||
|
|||
return { |
|||
attachChildren: attachChildren(attachParams), |
|||
context: treeNode.context, |
|||
props: treeNode.props, |
|||
call: safeCallEvent, |
|||
setStateFromBinding: (binding, value) => |
|||
setStateFromBinding(store, binding, value), |
|||
setState: (path, value) => setState(store, path, value), |
|||
getStateOrValue: (prop, currentContext) => |
|||
getStateOrValue(getCurrentState(), prop, currentContext), |
|||
store: store, |
|||
relativeUrl, |
|||
api, |
|||
isBound, |
|||
parent, |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,288 @@ |
|||
import { |
|||
isEventType, |
|||
eventHandlers, |
|||
EVENT_TYPE_MEMBER_NAME, |
|||
} from "./eventHandlers" |
|||
import { bbFactory } from "./bbComponentApi" |
|||
import { getState } from "./getState" |
|||
import { attachChildren } from "../render/attachChildren" |
|||
|
|||
import { |
|||
isBound, |
|||
takeStateFromStore, |
|||
takeStateFromContext, |
|||
takeStateFromEventParameters, |
|||
BB_STATE_FALLBACK, |
|||
BB_STATE_BINDINGPATH, |
|||
BB_STATE_BINDINGSOURCE, |
|||
} from "./isState" |
|||
|
|||
const doNothing = () => {} |
|||
doNothing.isPlaceholder = true |
|||
|
|||
const isMetaProp = propName => |
|||
propName === "_component" || |
|||
propName === "_children" || |
|||
propName === "_id" || |
|||
propName === "_style" || |
|||
propName === "_code" || |
|||
propName === "_codeMeta" |
|||
|
|||
export const createStateManager = ({ |
|||
store, |
|||
coreApi, |
|||
rootPath, |
|||
frontendDefinition, |
|||
componentLibraries, |
|||
uiFunctions, |
|||
onScreenSlotRendered, |
|||
}) => { |
|||
let handlerTypes = eventHandlers(store, coreApi, rootPath) |
|||
let currentState |
|||
|
|||
// any nodes that have props that are bound to the store
|
|||
let nodesBoundByProps = [] |
|||
|
|||
// any node whose children depend on code, that uses the store
|
|||
let nodesWithCodeBoundChildren = [] |
|||
|
|||
const getCurrentState = () => currentState |
|||
const registerBindings = _registerBindings( |
|||
nodesBoundByProps, |
|||
nodesWithCodeBoundChildren |
|||
) |
|||
const bb = bbFactory({ |
|||
store, |
|||
getCurrentState, |
|||
frontendDefinition, |
|||
componentLibraries, |
|||
uiFunctions, |
|||
onScreenSlotRendered, |
|||
}) |
|||
|
|||
const setup = _setup(handlerTypes, getCurrentState, registerBindings, bb) |
|||
|
|||
const unsubscribe = store.subscribe( |
|||
onStoreStateUpdated({ |
|||
setCurrentState: s => (currentState = s), |
|||
getCurrentState, |
|||
nodesWithCodeBoundChildren, |
|||
nodesBoundByProps, |
|||
uiFunctions, |
|||
componentLibraries, |
|||
onScreenSlotRendered, |
|||
setupState: setup, |
|||
}) |
|||
) |
|||
|
|||
return { |
|||
setup, |
|||
destroy: () => unsubscribe(), |
|||
getCurrentState, |
|||
store, |
|||
} |
|||
} |
|||
|
|||
const onStoreStateUpdated = ({ |
|||
setCurrentState, |
|||
getCurrentState, |
|||
nodesWithCodeBoundChildren, |
|||
nodesBoundByProps, |
|||
uiFunctions, |
|||
componentLibraries, |
|||
onScreenSlotRendered, |
|||
setupState, |
|||
}) => s => { |
|||
setCurrentState(s) |
|||
|
|||
// the original array gets changed by components' destroy()
|
|||
// so we make a clone and check if they are still in the original
|
|||
const nodesWithBoundChildren_clone = [...nodesWithCodeBoundChildren] |
|||
for (let node of nodesWithBoundChildren_clone) { |
|||
if (!nodesWithCodeBoundChildren.includes(node)) continue |
|||
attachChildren({ |
|||
uiFunctions, |
|||
componentLibraries, |
|||
treeNode: node, |
|||
onScreenSlotRendered, |
|||
setupState, |
|||
getCurrentState, |
|||
})(node.rootElement, { hydrate: true, force: true }) |
|||
} |
|||
|
|||
for (let node of nodesBoundByProps) { |
|||
setNodeState(s, node) |
|||
} |
|||
} |
|||
|
|||
const _registerBindings = (nodesBoundByProps, nodesWithCodeBoundChildren) => ( |
|||
node, |
|||
bindings |
|||
) => { |
|||
if (bindings.length > 0) { |
|||
node.bindings = bindings |
|||
nodesBoundByProps.push(node) |
|||
const onDestroy = () => { |
|||
nodesBoundByProps = nodesBoundByProps.filter(n => n === node) |
|||
node.onDestroy = node.onDestroy.filter(d => d === onDestroy) |
|||
} |
|||
node.onDestroy.push(onDestroy) |
|||
} |
|||
if ( |
|||
node.props._children && |
|||
node.props._children.filter(c => c._codeMeta && c._codeMeta.dependsOnStore) |
|||
.length > 0 |
|||
) { |
|||
nodesWithCodeBoundChildren.push(node) |
|||
const onDestroy = () => { |
|||
nodesWithCodeBoundChildren = nodesWithCodeBoundChildren.filter( |
|||
n => n === node |
|||
) |
|||
node.onDestroy = node.onDestroy.filter(d => d === onDestroy) |
|||
} |
|||
node.onDestroy.push(onDestroy) |
|||
} |
|||
} |
|||
|
|||
const setNodeState = (storeState, node) => { |
|||
if (!node.component) return |
|||
const newProps = { ...node.bindings.initialProps } |
|||
|
|||
for (let binding of node.bindings) { |
|||
const val = getState(storeState, binding.path, binding.fallback) |
|||
|
|||
if (val === undefined && newProps[binding.propName] !== undefined) { |
|||
delete newProps[binding.propName] |
|||
} |
|||
|
|||
if (val !== undefined) { |
|||
newProps[binding.propName] = val |
|||
} |
|||
} |
|||
|
|||
node.component.$set(newProps) |
|||
} |
|||
|
|||
const _setup = ( |
|||
handlerTypes, |
|||
getCurrentState, |
|||
registerBindings, |
|||
bb |
|||
) => node => { |
|||
const props = node.props |
|||
const context = node.context || {} |
|||
const initialProps = { ...props } |
|||
const storeBoundProps = [] |
|||
const currentStoreState = getCurrentState() |
|||
|
|||
for (let propName in props) { |
|||
if (isMetaProp(propName)) continue |
|||
|
|||
const val = props[propName] |
|||
|
|||
if (isBound(val) && takeStateFromStore(val)) { |
|||
const path = BindingPath(val) |
|||
const source = BindingSource(val) |
|||
const fallback = BindingFallback(val) |
|||
|
|||
storeBoundProps.push({ |
|||
path, |
|||
fallback, |
|||
propName, |
|||
source, |
|||
}) |
|||
|
|||
initialProps[propName] = !currentStoreState |
|||
? fallback |
|||
: getState( |
|||
currentStoreState, |
|||
BindingPath(val), |
|||
BindingFallback(val), |
|||
BindingSource(val) |
|||
) |
|||
} else if (isBound(val) && takeStateFromContext(val)) { |
|||
initialProps[propName] = !context |
|||
? val |
|||
: getState( |
|||
context, |
|||
BindingPath(val), |
|||
BindingFallback(val), |
|||
BindingSource(val) |
|||
) |
|||
} else if (isEventType(val)) { |
|||
const handlersInfos = [] |
|||
for (let e of val) { |
|||
const handlerInfo = { |
|||
handlerType: e[EVENT_TYPE_MEMBER_NAME], |
|||
parameters: e.parameters, |
|||
} |
|||
const resolvedParams = {} |
|||
for (let paramName in handlerInfo.parameters) { |
|||
const paramValue = handlerInfo.parameters[paramName] |
|||
if (!isBound(paramValue)) { |
|||
resolvedParams[paramName] = () => paramValue |
|||
continue |
|||
} else if (takeStateFromContext(paramValue)) { |
|||
const val = getState( |
|||
context, |
|||
paramValue[BB_STATE_BINDINGPATH], |
|||
paramValue[BB_STATE_FALLBACK] |
|||
) |
|||
resolvedParams[paramName] = () => val |
|||
} else if (takeStateFromStore(paramValue)) { |
|||
resolvedParams[paramName] = () => |
|||
getState( |
|||
getCurrentState(), |
|||
paramValue[BB_STATE_BINDINGPATH], |
|||
paramValue[BB_STATE_FALLBACK] |
|||
) |
|||
continue |
|||
} else if (takeStateFromEventParameters(paramValue)) { |
|||
resolvedParams[paramName] = eventContext => { |
|||
getState( |
|||
eventContext, |
|||
paramValue[BB_STATE_BINDINGPATH], |
|||
paramValue[BB_STATE_FALLBACK] |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
handlerInfo.parameters = resolvedParams |
|||
handlersInfos.push(handlerInfo) |
|||
} |
|||
|
|||
if (handlersInfos.length === 0) initialProps[propName] = doNothing |
|||
else { |
|||
initialProps[propName] = async context => { |
|||
for (let handlerInfo of handlersInfos) { |
|||
const handler = makeHandler(handlerTypes, handlerInfo) |
|||
await handler(context) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
registerBindings(node, storeBoundProps) |
|||
|
|||
const setup = _setup(handlerTypes, getCurrentState, registerBindings, bb) |
|||
initialProps._bb = bb(node, setup) |
|||
|
|||
return initialProps |
|||
} |
|||
|
|||
const makeHandler = (handlerTypes, handlerInfo) => { |
|||
const handlerType = handlerTypes[handlerInfo.handlerType] |
|||
return context => { |
|||
const parameters = {} |
|||
for (let p in handlerInfo.parameters) { |
|||
parameters[p] = handlerInfo.parameters[p](context) |
|||
} |
|||
handlerType.execute(parameters) |
|||
} |
|||
} |
|||
|
|||
const BindingPath = prop => prop[BB_STATE_BINDINGPATH] |
|||
const BindingFallback = prop => prop[BB_STATE_FALLBACK] |
|||
const BindingSource = prop => prop[BB_STATE_BINDINGSOURCE] |
|||
@ -0,0 +1,9 @@ |
|||
module.exports = props => { |
|||
if (props._codeMeta) { |
|||
delete props._codeMeta |
|||
} |
|||
|
|||
for (let child of props._children || []) { |
|||
module.exports(child) |
|||
} |
|||
} |
|||
Loading…
Reference in new issue