refactor client library

This commit is contained in:
Martin McKeaveney 2020-05-29 14:06:10 +01:00
parent 7a3b368399
commit e648dc80e8
28 changed files with 161 additions and 625 deletions

View File

@ -155,7 +155,6 @@ const createScreen = store => (screenName, route, layoutComponentName) => {
description: "",
url: "",
_css: "",
uiFunctions: "",
props: createProps(rootComponent).props,
}
@ -281,7 +280,6 @@ const _savePage = async s => {
const page = s.pages[s.currentPageName]
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions,
screens: page._screens,
})
}

View File

@ -116,8 +116,7 @@
stylesheetLinks,
selectedComponentType,
selectedComponentId,
frontendDefinition: JSON.stringify(frontendDefinition),
currentPageFunctions: $store.currentPageFunctions,
frontendDefinition: JSON.stringify(frontendDefinition)
})} />
{/if}
</div>

View File

@ -36,7 +36,6 @@ export default ({
</style>
<script>
window["##BUDIBASE_FRONTEND_DEFINITION##"] = ${frontendDefinition};
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = ${currentPageFunctions};
import('/_builder/budibase-client.esm.mjs')
.then(module => {

View File

@ -32,6 +32,11 @@
type="number"
class="budibase__input"
bind:value={workflowBlock.args[parameter]} />
{:else if type === 'longText'}
<textarea
type="text"
class="budibase__input"
bind:value={workflowBlock.args[parameter]} />
{:else if type === 'model'}
<select
class="budibase__input"

View File

@ -66,8 +66,8 @@ const ACTION = {
params: {
to: "string",
from: "string",
subject: "string",
text: "string",
subject: "longText",
text: "longText",
},
},
}

View File

@ -16,6 +16,5 @@
},
"_code": ""
},
"_css": "",
"uiFunctions": ""
"_css": ""
}

View File

@ -16,6 +16,5 @@
},
"_code": ""
},
"_css": "",
"uiFunctions": ""
"_css": ""
}

View File

@ -16,6 +16,5 @@
},
"_code": ""
},
"_css": "",
"uiFunctions": ""
"_css": ""
}

View File

@ -39,6 +39,7 @@
"deep-equal": "^2.0.1",
"lodash": "^4.17.15",
"lunr": "^2.3.5",
"mustache": "^4.0.1",
"regexparam": "^1.3.0",
"shortid": "^2.2.8",
"svelte": "^3.9.2"

View File

@ -1,7 +1,4 @@
import { loadRecord } from "./loadRecord"
import { listRecords } from "./listRecords"
import { authenticate } from "./authenticate"
import { saveRecord } from "./saveRecord"
import { triggerWorkflow } from "./workflow"
export const createApi = ({ rootPath = "", setState, getState }) => {
@ -60,10 +57,7 @@ export const createApi = ({ rootPath = "", setState, getState }) => {
}
return {
loadRecord: loadRecord(apiOpts),
listRecords: listRecords(apiOpts),
authenticate: authenticate(apiOpts),
saveRecord: saveRecord(apiOpts),
triggerWorkflow: triggerWorkflow(apiOpts),
}
}

View File

@ -1,19 +0,0 @@
import { trimSlash } from "../common/trimSlash"
export const listRecords = api => async ({ indexKey, statePath }) => {
if (!indexKey) {
api.error("Load Record: record key not set")
return
}
if (!statePath) {
api.error("Load Record: state path not set")
return
}
const records = await api.get({
url: `/api/listRecords/${trimSlash(indexKey)}`,
})
if (api.isSuccess(records)) api.setState(statePath, records)
}

View File

@ -1,19 +0,0 @@
import { trimSlash } from "../common/trimSlash"
export const loadRecord = api => async ({ recordKey, statePath }) => {
if (!recordKey) {
api.error("Load Record: record key not set")
return
}
if (!statePath) {
api.error("Load Record: state path not set")
return
}
const record = await api.get({
url: `/api/record/${trimSlash(recordKey)}`,
})
if (api.isSuccess(record)) api.setState(statePath, record)
}

View File

@ -1,29 +0,0 @@
import { trimSlash } from "../common/trimSlash"
export const saveRecord = api => async ({ statePath }) => {
if (!statePath) {
api.error("Load Record: state path not set")
return
}
const recordtoSave = api.getState(statePath)
if (!recordtoSave) {
api.error(`there is no record in state: ${statePath}`)
return
}
if (!recordtoSave.key) {
api.error(
`item in state does not appear to be a record - it has no key (${statePath})`
)
return
}
const savedRecord = await api.post({
url: `/api/record/${trimSlash(recordtoSave.key)}`,
body: recordtoSave,
})
if (api.isSuccess(savedRecord)) api.setState(statePath, savedRecord)
}

View File

@ -1,4 +1,5 @@
import get from "lodash/fp/get"
import mustache from "mustache";
/**
* The workflow orchestrator is a class responsible for executing workflows.
@ -41,23 +42,28 @@ export const clientStrategy = {
for (let arg in args) {
const argValue = args[arg]
// Means that it's bound to state or workflow context
if (argValue.startsWith("$")) {
// if value is bound to workflow context.
if (argValue.startsWith("$context")) {
const path = argValue.replace("$context.", "")
// pass in the value from context
mappedArgs[arg] = get(path, this.context)
}
// if the value is bound to state
if (argValue.startsWith("$state")) {
const path = argValue.replace("$state.", "")
// pass in the value from state
// TODO: not working
mappedArgs[arg] = api.getState(path)
}
}
mappedArgs[arg] = mustache.render(argValue, {
context: this.context,
state: api.getState()
});
}
// if (argValue.startsWith("$")) {
// // if value is bound to workflow context.
// if (argValue.startsWith("$context")) {
// const path = argValue.replace("$context.", "")
// // pass in the value from context
// mappedArgs[arg] = get(path, this.context)
// }
// // if the value is bound to state
// if (argValue.startsWith("$state")) {
// const path = argValue.replace("$state.", "")
// // pass in the value from state
// // TODO: not working
// mappedArgs[arg] = api.getState(path)
// }
// }
// }
console.log(mappedArgs)

View File

@ -4,13 +4,12 @@ import { createTreeNode } from "./render/prepareRenderComponent"
import { screenRouter } from "./render/screenRouter"
import { createStateManager } from "./state/stateManager"
export const createApp = (
export const createApp = ({
componentLibraries,
frontendDefinition,
user,
uiFunctions,
window
) => {
}) => {
let routeTo
let currentUrl
let screenStateManager
@ -21,7 +20,6 @@ export const createApp = (
store,
frontendDefinition,
componentLibraries,
uiFunctions,
onScreenSlotRendered: () => {},
routeTo,
appRootPath: frontendDefinition.appRootPath,
@ -53,7 +51,6 @@ export const createApp = (
const attachChildrenParams = stateManager => {
const getInitialiseParams = treeNode => ({
componentLibraries,
uiFunctions,
treeNode,
onScreenSlotRendered,
setupState: stateManager.setup,
@ -68,7 +65,6 @@ export const createApp = (
store: writable({ _bbuser: user }),
frontendDefinition,
componentLibraries,
uiFunctions,
onScreenSlotRendered,
appRootPath: frontendDefinition.appRootPath,
// seems weird, but the routeTo variable may not be available at this point

View File

@ -10,7 +10,6 @@ export const loadBudibase = async opts => {
// const _localStorage = (opts && opts.localStorage) || localStorage
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
const uiFunctions = _window["##BUDIBASE_FRONTEND_FUNCTIONS##"]
// TODO: update
const user = {}
@ -36,14 +35,12 @@ export const loadBudibase = async opts => {
pageStore,
routeTo,
rootNode,
} = createApp(
componentLibraryModules,
} = createApp({
componentLibraries: componentLibraryModules,
frontendDefinition,
user,
uiFunctions || {},
_window,
rootNode
)
window
})
const route = _window.location
? _window.location.pathname.replace(frontendDefinition.appRootPath, "")

View File

@ -5,7 +5,6 @@ import deepEqual from "deep-equal"
export const attachChildren = initialiseOpts => (htmlElement, options) => {
const {
uiFunctions,
componentLibraries,
treeNode,
onScreenSlotRendered,
@ -31,8 +30,6 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
}
}
// htmlElement.classList.add(`lay-${treeNode.props._id}`)
const childNodes = []
for (let childProps of treeNode.props._children) {
const { componentName, libName } = splitName(childProps._component)
@ -45,7 +42,6 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
props: childProps,
parentNode: treeNode,
ComponentConstructor,
uiFunctions,
htmlElement,
anchor,
getCurrentState,

View File

@ -1,14 +1,11 @@
export const prepareRenderComponent = ({
ComponentConstructor,
uiFunctions,
htmlElement,
anchor,
props,
parentNode,
getCurrentState,
}) => {
const func = props._id ? uiFunctions[props._id] : undefined
const parentContext = (parentNode && parentNode.context) || {}
let nodesToRender = []
@ -42,13 +39,7 @@ export const prepareRenderComponent = ({
}
}
if (func) {
const state = getCurrentState()
const routeParams = state["##routeParams"]
func(createNodeAndRender, parentContext, getCurrentState(), routeParams)
} else {
createNodeAndRender()
}
createNodeAndRender()
return nodesToRender
}

View File

@ -1,4 +1,4 @@
import { getStateOrValue } from "./getState"
// import { getStateOrValue } from "./getState"
import { setState, setStateFromBinding } from "./setState"
import { trimSlash } from "../common/trimSlash"
import { isBound } from "./parseBinding"
@ -10,7 +10,6 @@ export const bbFactory = ({
getCurrentState,
frontendDefinition,
componentLibraries,
uiFunctions,
onScreenSlotRendered,
}) => {
const relativeUrl = url => {
@ -41,17 +40,9 @@ export const bbFactory = ({
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,
@ -62,12 +53,12 @@ export const bbFactory = ({
attachChildren: attachChildren(attachParams),
context: treeNode.context,
props: treeNode.props,
call: safeCallEvent,
call: (event, context) => event(context),
setStateFromBinding: (binding, value) =>
setStateFromBinding(store, binding, value),
setState: (path, value) => setState(store, path, value),
getStateOrValue: (prop, currentContext) =>
getStateOrValue(getCurrentState(), prop, currentContext),
// getStateOrValue: (prop, currentContext) =>
// getStateOrValue(getCurrentState(), prop, currentContext),
getContext: getContext(treeNode),
setContext: setContext(treeNode),
store: store,

View File

@ -1,71 +0,0 @@
import { ERROR } from "./standardState"
export const getNewChildRecordToState = (coreApi, setState) => ({
recordKey,
collectionName,
childRecordType,
statePath,
}) => {
const error = errorHandler(setState)
try {
if (!recordKey) {
error("getNewChild > recordKey not set")
return
}
if (!collectionName) {
error("getNewChild > collectionName not set")
return
}
if (!childRecordType) {
error("getNewChild > childRecordType not set")
return
}
if (!statePath) {
error("getNewChild > statePath not set")
return
}
const rec = coreApi.recordApi.getNewChild(
recordKey,
collectionName,
childRecordType
)
setState(statePath, rec)
} catch (e) {
error(e.message)
}
}
export const getNewRecordToState = (coreApi, setState) => ({
collectionKey,
childRecordType,
statePath,
}) => {
const error = errorHandler(setState)
try {
if (!collectionKey) {
error("getNewChild > collectionKey not set")
return
}
if (!childRecordType) {
error("getNewChild > childRecordType not set")
return
}
if (!statePath) {
error("getNewChild > statePath not set")
return
}
const rec = coreApi.recordApi.getNew(collectionKey, childRecordType)
setState(statePath, rec)
} catch (e) {
error(e.message)
}
}
const errorHandler = setState => message => setState("##error_message", message)

View File

@ -22,7 +22,7 @@ export const eventHandlers = (store, rootPath, routeTo) => {
const api = createApi({
rootPath,
setState: setStateWithStore,
getState: (path, fallback) => getState(currentState, path, fallback),
getState: (path, fallback) => getState(currentState, path, fallback)
})
const setStateHandler = ({ path, value }) => setState(store, path, value)

View File

@ -1,46 +1,49 @@
import { isUndefined, isObject } from "lodash/fp"
import { parseBinding, isStoreBinding } from "./parseBinding"
// import { isUndefined, isObject } from "lodash/fp"
import getOr from "lodash/fp/getOr";
// import { parseBinding, isStoreBinding } from "./parseBinding"
export const getState = (s, path, fallback) => {
if (!s) return fallback
export const getState = (state, path, fallback) => {
if (!state) return fallback
if (!path || path.length === 0) return fallback
if (path === "$") return s
return getOr(fallback, path, state);
const pathParts = path.split(".")
const safeGetPath = (obj, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex]
// if (path === "$") return state
if (pathParts.length - 1 == currentPartIndex) {
const value = obj[currentKey]
if (isUndefined(value)) return fallback
else return value
}
// const pathParts = path.split(".")
// const safeGetPath = (obj, currentPartIndex = 0) => {
// const currentKey = pathParts[currentPartIndex]
if (
obj[currentKey] === null ||
obj[currentKey] === undefined ||
!isObject(obj[currentKey])
) {
return fallback
}
// if (pathParts.length - 1 == currentPartIndex) {
// const value = obj[currentKey]
// if (isUndefined(value)) return fallback
// else return value
// }
return safeGetPath(obj[currentKey], currentPartIndex + 1)
}
// if (
// obj[currentKey] === null ||
// obj[currentKey] === undefined ||
// !isObject(obj[currentKey])
// ) {
// return fallback
// }
return safeGetPath(s)
// return safeGetPath(obj[currentKey], currentPartIndex + 1)
// }
// return safeGetPath(state)
}
export const getStateOrValue = (globalState, prop, currentContext) => {
if (!prop) return prop
// export const getStateOrValue = (globalState, prop, currentContext) => {
// if (!prop) return prop
const binding = parseBinding(prop)
// const binding = parseBinding(prop)
if (binding) {
const stateToUse = isStoreBinding(binding) ? globalState : currentContext
// if (binding) {
// const stateToUse = isStoreBinding(binding) ? globalState : currentContext
return getState(stateToUse, binding.path, binding.fallback)
}
// return getState(stateToUse, binding.path, binding.fallback)
// }
return prop
}
// return prop
// }

View File

@ -36,7 +36,7 @@ export const parseBinding = prop => {
export const isStoreBinding = binding => binding && binding.source === "store"
export const isContextBinding = binding =>
binding && binding.source === "context"
export const isEventBinding = binding => binding && binding.source === "event"
// export const isEventBinding = binding => binding && binding.source === "event"
const hasBindingObject = prop =>
typeof prop === "object" && prop[BB_STATE_BINDINGPATH] !== undefined

View File

@ -1,32 +1,34 @@
import { isObject } from "lodash/fp"
// import isObject from "lodash/fp/isObject"
import set from "lodash/fp/set";
import { parseBinding } from "./parseBinding"
export const setState = (store, path, value) => {
if (!path || path.length === 0) return
const pathParts = path.split(".")
// const pathParts = path.split(".")
const safeSetPath = (state, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex]
// const safeSetPath = (state, currentPartIndex = 0) => {
// const currentKey = pathParts[currentPartIndex]
if (pathParts.length - 1 == currentPartIndex) {
state[currentKey] = value
return
}
// if (pathParts.length - 1 == currentPartIndex) {
// state[currentKey] = value
// return
// }
if (
state[currentKey] === null ||
state[currentKey] === undefined ||
!isObject(state[currentKey])
) {
state[currentKey] = {}
}
// if (
// state[currentKey] === null ||
// state[currentKey] === undefined ||
// !isObject(state[currentKey])
// ) {
// state[currentKey] = {}
// }
safeSetPath(state[currentKey], currentPartIndex + 1)
}
// safeSetPath(state[currentKey], currentPartIndex + 1)
// }
store.update(state => {
safeSetPath(state)
// safeSetPath(state)
state = set(path, value, state);
return state
})
}

View File

@ -6,6 +6,7 @@ import {
import { bbFactory } from "./bbComponentApi"
import { getState } from "./getState"
import { attachChildren } from "../render/attachChildren"
import mustache from "mustache"
import { parseBinding } from "./parseBinding"
@ -18,14 +19,14 @@ const isMetaProp = propName =>
propName === "_id" ||
propName === "_style" ||
propName === "_code" ||
propName === "_codeMeta"
propName === "_codeMeta" ||
propName === "_styles"
export const createStateManager = ({
store,
appRootPath,
frontendDefinition,
componentLibraries,
uiFunctions,
onScreenSlotRendered,
routeTo,
}) => {
@ -48,7 +49,6 @@ export const createStateManager = ({
getCurrentState,
frontendDefinition,
componentLibraries,
uiFunctions,
onScreenSlotRendered,
})
@ -60,7 +60,6 @@ export const createStateManager = ({
getCurrentState,
nodesWithCodeBoundChildren,
nodesBoundByProps,
uiFunctions,
componentLibraries,
onScreenSlotRendered,
setupState: setup,
@ -79,13 +78,12 @@ const onStoreStateUpdated = ({
setCurrentState,
getCurrentState,
nodesWithCodeBoundChildren,
nodesBoundByProps,
uiFunctions,
// nodesBoundByProps,
componentLibraries,
onScreenSlotRendered,
setupState,
}) => s => {
setCurrentState(s)
}) => state => {
setCurrentState(state)
// the original array gets changed by components' destroy()
// so we make a clone and check if they are still in the original
@ -93,7 +91,6 @@ const onStoreStateUpdated = ({
for (let node of nodesWithBoundChildren_clone) {
if (!nodesWithCodeBoundChildren.includes(node)) continue
attachChildren({
uiFunctions,
componentLibraries,
treeNode: node,
onScreenSlotRendered,
@ -102,9 +99,9 @@ const onStoreStateUpdated = ({
})(node.rootElement, { hydrate: true, force: true })
}
for (let node of nodesBoundByProps) {
setNodeState(s, node)
}
// for (let node of nodesBoundByProps) {
// setNodeState(state, node)
// }
}
const _registerBindings = (nodesBoundByProps, nodesWithCodeBoundChildren) => (
@ -172,29 +169,36 @@ const _setup = (
const propValue = props[propName]
const binding = parseBinding(propValue)
const isBound = !!binding
// const binding = parseBinding(propValue)
// const isBound = !!binding
if (isBound) binding.propName = propName
if (isBound && binding.source === "state") {
storeBoundProps.push(binding)
initialProps[propName] = !currentStoreState
? binding.fallback
: getState(
currentStoreState,
binding.path,
binding.fallback,
binding.source
)
if (typeof propValue === "string") {
initialProps[propName] = mustache.render(propValue, {
state: currentStoreState,
context
})
}
if (isBound && binding.source === "context") {
initialProps[propName] = !context
? propValue
: getState(context, binding.path, binding.fallback, binding.source)
}
// if (isBound) binding.propName = propName
// if (isBound && binding.source === "state") {
// storeBoundProps.push(binding)
// initialProps[propName] = !currentStoreState
// ? binding.fallback
// : getState(
// currentStoreState,
// binding.path,
// binding.fallback,
// binding.source
// )
// }
// if (isBound && binding.source === "context") {
// initialProps[propName] = !context
// ? propValue
// : getState(context, binding.path, binding.fallback, binding.source)
// }
if (isEventType(propValue)) {
const handlersInfos = []
@ -203,33 +207,38 @@ const _setup = (
handlerType: event[EVENT_TYPE_MEMBER_NAME],
parameters: event.parameters,
}
const resolvedParams = {}
for (let paramName in handlerInfo.parameters) {
const paramValue = handlerInfo.parameters[paramName]
const paramBinding = parseBinding(paramValue)
if (!paramBinding) {
resolvedParams[paramName] = () => paramValue
continue
}
resolvedParams[paramName] = () => mustache.render(paramValue, {
state: getCurrentState(),
context,
})
// const paramBinding = parseBinding(paramValue)
// if (!paramBinding) {
// resolvedParams[paramName] = () => paramValue
// continue
// }
let paramValueSource
// let paramValueSource
if (paramBinding.source === "context") paramValueSource = context
if (paramBinding.source === "state")
paramValueSource = getCurrentState()
if (paramBinding.source === "context") paramValueSource = context
// if (paramBinding.source === "context") paramValueSource = context
// if (paramBinding.source === "state")
// paramValueSource = getCurrentState()
// The new dynamic event parameter bound to the relevant source
resolvedParams[paramName] = () =>
getState(paramValueSource, paramBinding.path, paramBinding.fallback)
// // The new dynamic event parameter bound to the relevant source
// resolvedParams[paramName] = () =>
// getState(paramValueSource, paramBinding.path, paramBinding.fallback)
}
handlerInfo.parameters = resolvedParams
handlersInfos.push(handlerInfo)
}
if (handlersInfos.length === 0) initialProps[propName] = doNothing
else {
if (handlersInfos.length === 0) {
initialProps[propName] = doNothing
} else {
initialProps[propName] = async context => {
for (let handlerInfo of handlersInfos) {
const handler = makeHandler(handlerTypes, handlerInfo)

View File

@ -1,283 +0,0 @@
import {
isEventType,
eventHandlers,
EVENT_TYPE_MEMBER_NAME,
} from "./eventHandlers"
import { bbFactory } from "./bbComponentApi"
import { getState } from "./getState"
import { attachChildren } from "../render/attachChildren"
import { parseBinding } from "./parseBinding"
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,
routeTo,
}) => {
let handlerTypes = eventHandlers(store, coreApi, rootPath, routeTo)
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)
}
/**
* Bind a components event handler parameters to state, context or the event itself.
* @param {Array} eventHandlerProp - event handler array from component definition
*/
function bindComponentEventHandlers(
eventHandlerProp,
context,
getCurrentState
) {
const boundEventHandlers = []
for (let event of eventHandlerProp) {
const boundEventHandler = {
handlerType: event[EVENT_TYPE_MEMBER_NAME],
parameters: event.parameters,
}
const boundParameters = {}
for (let paramName in boundEventHandler.parameters) {
const paramValue = boundEventHandler.parameters[paramName]
const paramBinding = parseBinding(paramValue)
if (!paramBinding) {
boundParameters[paramName] = () => paramValue
continue
}
let paramValueSource
if (paramBinding.source === "context") paramValueSource = context
if (paramBinding.source === "state") paramValueSource = getCurrentState()
// The new dynamic event parameter bound to the relevant source
boundParameters[paramName] = eventContext =>
getState(
paramBinding.source === "event" ? eventContext : paramValueSource,
paramBinding.path,
paramBinding.fallback
)
}
boundEventHandler.parameters = boundParameters
boundEventHandlers.push(boundEventHandlers)
return boundEventHandlers
}
}
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 propValue = props[propName]
const binding = parseBinding(propValue)
const isBound = !!binding
if (isBound) binding.propName = propName
if (isBound && binding.source === "state") {
storeBoundProps.push(binding)
initialProps[propName] = !currentStoreState
? binding.fallback
: getState(
currentStoreState,
binding.path,
binding.fallback,
binding.source
)
}
if (isBound && binding.source === "context") {
initialProps[propName] = !context
? propValue
: getState(context, binding.path, binding.fallback, binding.source)
}
if (isEventType(propValue)) {
const boundEventHandlers = bindComponentEventHandlers(
propValue,
context,
getCurrentState
)
if (boundEventHandlers.length === 0) {
initialProps[propName] = doNothing
} else {
initialProps[propName] = async context => {
for (let handlerInfo of boundEventHandlers) {
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 paramName in handlerInfo.parameters) {
parameters[paramName] = handlerInfo.parameters[paramName](context)
}
handlerType.execute(parameters)
}
}

View File

@ -13,7 +13,7 @@ export const load = async (page, screens, url, appRootPath) => {
autoAssignIds(s.props)
}
setAppDef(dom.window, page, screens)
addWindowGlobals(dom.window, page, screens, appRootPath, uiFunctions, {
addWindowGlobals(dom.window, page, screens, appRootPath, {
hierarchy: {},
actions: [],
triggers: [],
@ -27,13 +27,12 @@ export const load = async (page, screens, url, appRootPath) => {
return { dom, app }
}
const addWindowGlobals = (window, page, screens, appRootPath, uiFunctions) => {
const addWindowGlobals = (window, page, screens, appRootPath) => {
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
page,
screens,
appRootPath,
}
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = uiFunctions
}
export const makePage = props => ({ props })
@ -183,29 +182,4 @@ const maketestlib = window => ({
set(opts.props)
opts.target.appendChild(node)
},
})
const uiFunctions = {
never_render: () => {},
always_render: render => {
render()
},
three_clones: render => {
for (let i = 0; i < 3; i++) {
render()
}
},
with_context: render => {
render({ testKey: "test value" })
},
n_clones_based_on_store: (render, _, state) => {
const n = state.componentCount || 0
for (let i = 0; i < n; i++) {
render({ index: `index_${i}` })
}
},
}
})

View File

@ -103,7 +103,6 @@ const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
filename,
`
window['##BUDIBASE_FRONTEND_DEFINITION##'] = ${clientUiDefinition};
window['##BUDIBASE_FRONTEND_FUNCTIONS##'] = ${pkg.uiFunctions};
`
)
}