1
0
Fork 0
mirror of synced 2024-07-04 14:01:27 +12:00

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,4 @@
import { loadRecord } from "./loadRecord"
import { listRecords } from "./listRecords"
import { authenticate } from "./authenticate" import { authenticate } from "./authenticate"
import { saveRecord } from "./saveRecord"
import { triggerWorkflow } from "./workflow" import { triggerWorkflow } from "./workflow"
export const createApi = ({ rootPath = "", setState, getState }) => { export const createApi = ({ rootPath = "", setState, getState }) => {
@ -60,10 +57,7 @@ export const createApi = ({ rootPath = "", setState, getState }) => {
} }
return { return {
loadRecord: loadRecord(apiOpts),
listRecords: listRecords(apiOpts),
authenticate: authenticate(apiOpts), authenticate: authenticate(apiOpts),
saveRecord: saveRecord(apiOpts),
triggerWorkflow: triggerWorkflow(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 get from "lodash/fp/get"
import mustache from "mustache";
/** /**
* The workflow orchestrator is a class responsible for executing workflows. * The workflow orchestrator is a class responsible for executing workflows.
@ -41,23 +42,28 @@ export const clientStrategy = {
for (let arg in args) { for (let arg in args) {
const argValue = args[arg] const argValue = args[arg]
// Means that it's bound to state or workflow context // Means that it's bound to state or workflow context
if (argValue.startsWith("$")) { mappedArgs[arg] = mustache.render(argValue, {
// if value is bound to workflow context. context: this.context,
if (argValue.startsWith("$context")) { state: api.getState()
const path = argValue.replace("$context.", "") });
// pass in the value from context
mappedArgs[arg] = get(path, this.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 the value is bound to state
if (argValue.startsWith("$state")) { // if (argValue.startsWith("$state")) {
const path = argValue.replace("$state.", "") // const path = argValue.replace("$state.", "")
// pass in the value from state // // pass in the value from state
// TODO: not working // // TODO: not working
mappedArgs[arg] = api.getState(path) // mappedArgs[arg] = api.getState(path)
} // }
} // }
} // }
console.log(mappedArgs) console.log(mappedArgs)

View file

@ -4,13 +4,12 @@ import { createTreeNode } from "./render/prepareRenderComponent"
import { screenRouter } from "./render/screenRouter" import { screenRouter } from "./render/screenRouter"
import { createStateManager } from "./state/stateManager" import { createStateManager } from "./state/stateManager"
export const createApp = ( export const createApp = ({
componentLibraries, componentLibraries,
frontendDefinition, frontendDefinition,
user, user,
uiFunctions,
window window
) => { }) => {
let routeTo let routeTo
let currentUrl let currentUrl
let screenStateManager let screenStateManager
@ -21,7 +20,6 @@ export const createApp = (
store, store,
frontendDefinition, frontendDefinition,
componentLibraries, componentLibraries,
uiFunctions,
onScreenSlotRendered: () => {}, onScreenSlotRendered: () => {},
routeTo, routeTo,
appRootPath: frontendDefinition.appRootPath, appRootPath: frontendDefinition.appRootPath,
@ -53,7 +51,6 @@ export const createApp = (
const attachChildrenParams = stateManager => { const attachChildrenParams = stateManager => {
const getInitialiseParams = treeNode => ({ const getInitialiseParams = treeNode => ({
componentLibraries, componentLibraries,
uiFunctions,
treeNode, treeNode,
onScreenSlotRendered, onScreenSlotRendered,
setupState: stateManager.setup, setupState: stateManager.setup,
@ -68,7 +65,6 @@ export const createApp = (
store: writable({ _bbuser: user }), store: writable({ _bbuser: user }),
frontendDefinition, frontendDefinition,
componentLibraries, componentLibraries,
uiFunctions,
onScreenSlotRendered, onScreenSlotRendered,
appRootPath: frontendDefinition.appRootPath, appRootPath: frontendDefinition.appRootPath,
// seems weird, but the routeTo variable may not be available at this point // 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 _localStorage = (opts && opts.localStorage) || localStorage
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"] const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
const uiFunctions = _window["##BUDIBASE_FRONTEND_FUNCTIONS##"]
// TODO: update // TODO: update
const user = {} const user = {}
@ -36,14 +35,12 @@ export const loadBudibase = async opts => {
pageStore, pageStore,
routeTo, routeTo,
rootNode, rootNode,
} = createApp( } = createApp({
componentLibraryModules, componentLibraries: componentLibraryModules,
frontendDefinition, frontendDefinition,
user, user,
uiFunctions || {}, window
_window, })
rootNode
)
const route = _window.location const route = _window.location
? _window.location.pathname.replace(frontendDefinition.appRootPath, "") ? _window.location.pathname.replace(frontendDefinition.appRootPath, "")

View file

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

View file

@ -1,14 +1,11 @@
export const prepareRenderComponent = ({ export const prepareRenderComponent = ({
ComponentConstructor, ComponentConstructor,
uiFunctions,
htmlElement, htmlElement,
anchor, anchor,
props, props,
parentNode, parentNode,
getCurrentState, getCurrentState,
}) => { }) => {
const func = props._id ? uiFunctions[props._id] : undefined
const parentContext = (parentNode && parentNode.context) || {} const parentContext = (parentNode && parentNode.context) || {}
let nodesToRender = [] 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 return nodesToRender
} }

View file

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

View file

@ -1,46 +1,49 @@
import { isUndefined, isObject } from "lodash/fp" // import { isUndefined, isObject } from "lodash/fp"
import { parseBinding, isStoreBinding } from "./parseBinding" import getOr from "lodash/fp/getOr";
// import { parseBinding, isStoreBinding } from "./parseBinding"
export const getState = (s, path, fallback) => { export const getState = (state, path, fallback) => {
if (!s) return fallback if (!state) return fallback
if (!path || path.length === 0) return fallback if (!path || path.length === 0) return fallback
if (path === "$") return s return getOr(fallback, path, state);
const pathParts = path.split(".") // if (path === "$") return state
const safeGetPath = (obj, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex]
if (pathParts.length - 1 == currentPartIndex) { // const pathParts = path.split(".")
const value = obj[currentKey] // const safeGetPath = (obj, currentPartIndex = 0) => {
if (isUndefined(value)) return fallback // const currentKey = pathParts[currentPartIndex]
else return value
}
if ( // if (pathParts.length - 1 == currentPartIndex) {
obj[currentKey] === null || // const value = obj[currentKey]
obj[currentKey] === undefined || // if (isUndefined(value)) return fallback
!isObject(obj[currentKey]) // else return value
) { // }
return fallback
}
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) => { // export const getStateOrValue = (globalState, prop, currentContext) => {
if (!prop) return prop // if (!prop) return prop
const binding = parseBinding(prop) // const binding = parseBinding(prop)
if (binding) { // if (binding) {
const stateToUse = isStoreBinding(binding) ? globalState : currentContext // 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 isStoreBinding = binding => binding && binding.source === "store"
export const isContextBinding = binding => export const isContextBinding = binding =>
binding && binding.source === "context" binding && binding.source === "context"
export const isEventBinding = binding => binding && binding.source === "event" // export const isEventBinding = binding => binding && binding.source === "event"
const hasBindingObject = prop => const hasBindingObject = prop =>
typeof prop === "object" && prop[BB_STATE_BINDINGPATH] !== undefined 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" import { parseBinding } from "./parseBinding"
export const setState = (store, path, value) => { export const setState = (store, path, value) => {
if (!path || path.length === 0) return if (!path || path.length === 0) return
const pathParts = path.split(".") // const pathParts = path.split(".")
const safeSetPath = (state, currentPartIndex = 0) => { // const safeSetPath = (state, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex] // const currentKey = pathParts[currentPartIndex]
if (pathParts.length - 1 == currentPartIndex) { // if (pathParts.length - 1 == currentPartIndex) {
state[currentKey] = value // state[currentKey] = value
return // return
} // }
if ( // if (
state[currentKey] === null || // state[currentKey] === null ||
state[currentKey] === undefined || // state[currentKey] === undefined ||
!isObject(state[currentKey]) // !isObject(state[currentKey])
) { // ) {
state[currentKey] = {} // state[currentKey] = {}
} // }
safeSetPath(state[currentKey], currentPartIndex + 1) // safeSetPath(state[currentKey], currentPartIndex + 1)
} // }
store.update(state => { store.update(state => {
safeSetPath(state) // safeSetPath(state)
state = set(path, value, state);
return state return state
}) })
} }

View file

@ -6,6 +6,7 @@ import {
import { bbFactory } from "./bbComponentApi" import { bbFactory } from "./bbComponentApi"
import { getState } from "./getState" import { getState } from "./getState"
import { attachChildren } from "../render/attachChildren" import { attachChildren } from "../render/attachChildren"
import mustache from "mustache"
import { parseBinding } from "./parseBinding" import { parseBinding } from "./parseBinding"
@ -18,14 +19,14 @@ const isMetaProp = propName =>
propName === "_id" || propName === "_id" ||
propName === "_style" || propName === "_style" ||
propName === "_code" || propName === "_code" ||
propName === "_codeMeta" propName === "_codeMeta" ||
propName === "_styles"
export const createStateManager = ({ export const createStateManager = ({
store, store,
appRootPath, appRootPath,
frontendDefinition, frontendDefinition,
componentLibraries, componentLibraries,
uiFunctions,
onScreenSlotRendered, onScreenSlotRendered,
routeTo, routeTo,
}) => { }) => {
@ -48,7 +49,6 @@ export const createStateManager = ({
getCurrentState, getCurrentState,
frontendDefinition, frontendDefinition,
componentLibraries, componentLibraries,
uiFunctions,
onScreenSlotRendered, onScreenSlotRendered,
}) })
@ -60,7 +60,6 @@ export const createStateManager = ({
getCurrentState, getCurrentState,
nodesWithCodeBoundChildren, nodesWithCodeBoundChildren,
nodesBoundByProps, nodesBoundByProps,
uiFunctions,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,
setupState: setup, setupState: setup,
@ -79,13 +78,12 @@ const onStoreStateUpdated = ({
setCurrentState, setCurrentState,
getCurrentState, getCurrentState,
nodesWithCodeBoundChildren, nodesWithCodeBoundChildren,
nodesBoundByProps, // nodesBoundByProps,
uiFunctions,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,
setupState, setupState,
}) => s => { }) => state => {
setCurrentState(s) setCurrentState(state)
// the original array gets changed by components' destroy() // the original array gets changed by components' destroy()
// so we make a clone and check if they are still in the original // 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) { for (let node of nodesWithBoundChildren_clone) {
if (!nodesWithCodeBoundChildren.includes(node)) continue if (!nodesWithCodeBoundChildren.includes(node)) continue
attachChildren({ attachChildren({
uiFunctions,
componentLibraries, componentLibraries,
treeNode: node, treeNode: node,
onScreenSlotRendered, onScreenSlotRendered,
@ -102,9 +99,9 @@ const onStoreStateUpdated = ({
})(node.rootElement, { hydrate: true, force: true }) })(node.rootElement, { hydrate: true, force: true })
} }
for (let node of nodesBoundByProps) { // for (let node of nodesBoundByProps) {
setNodeState(s, node) // setNodeState(state, node)
} // }
} }
const _registerBindings = (nodesBoundByProps, nodesWithCodeBoundChildren) => ( const _registerBindings = (nodesBoundByProps, nodesWithCodeBoundChildren) => (
@ -172,29 +169,36 @@ const _setup = (
const propValue = props[propName] const propValue = props[propName]
const binding = parseBinding(propValue) // const binding = parseBinding(propValue)
const isBound = !!binding // const isBound = !!binding
if (isBound) binding.propName = propName if (typeof propValue === "string") {
initialProps[propName] = mustache.render(propValue, {
if (isBound && binding.source === "state") { state: currentStoreState,
storeBoundProps.push(binding) context
})
initialProps[propName] = !currentStoreState
? binding.fallback
: getState(
currentStoreState,
binding.path,
binding.fallback,
binding.source
)
} }
if (isBound && binding.source === "context") { // if (isBound) binding.propName = propName
initialProps[propName] = !context
? propValue // if (isBound && binding.source === "state") {
: getState(context, binding.path, binding.fallback, binding.source) // 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)) { if (isEventType(propValue)) {
const handlersInfos = [] const handlersInfos = []
@ -203,33 +207,38 @@ const _setup = (
handlerType: event[EVENT_TYPE_MEMBER_NAME], handlerType: event[EVENT_TYPE_MEMBER_NAME],
parameters: event.parameters, parameters: event.parameters,
} }
const resolvedParams = {} const resolvedParams = {}
for (let paramName in handlerInfo.parameters) { for (let paramName in handlerInfo.parameters) {
const paramValue = handlerInfo.parameters[paramName] const paramValue = handlerInfo.parameters[paramName]
const paramBinding = parseBinding(paramValue) resolvedParams[paramName] = () => mustache.render(paramValue, {
if (!paramBinding) { state: getCurrentState(),
resolvedParams[paramName] = () => paramValue context,
continue })
} // const paramBinding = parseBinding(paramValue)
// if (!paramBinding) {
// resolvedParams[paramName] = () => paramValue
// continue
// }
let paramValueSource // let paramValueSource
if (paramBinding.source === "context") paramValueSource = context // if (paramBinding.source === "context") paramValueSource = context
if (paramBinding.source === "state") // if (paramBinding.source === "state")
paramValueSource = getCurrentState() // paramValueSource = getCurrentState()
if (paramBinding.source === "context") paramValueSource = context
// The new dynamic event parameter bound to the relevant source // // The new dynamic event parameter bound to the relevant source
resolvedParams[paramName] = () => // resolvedParams[paramName] = () =>
getState(paramValueSource, paramBinding.path, paramBinding.fallback) // getState(paramValueSource, paramBinding.path, paramBinding.fallback)
} }
handlerInfo.parameters = resolvedParams handlerInfo.parameters = resolvedParams
handlersInfos.push(handlerInfo) handlersInfos.push(handlerInfo)
} }
if (handlersInfos.length === 0) initialProps[propName] = doNothing if (handlersInfos.length === 0) {
else { initialProps[propName] = doNothing
} else {
initialProps[propName] = async context => { initialProps[propName] = async context => {
for (let handlerInfo of handlersInfos) { for (let handlerInfo of handlersInfos) {
const handler = makeHandler(handlerTypes, handlerInfo) 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) autoAssignIds(s.props)
} }
setAppDef(dom.window, page, screens) setAppDef(dom.window, page, screens)
addWindowGlobals(dom.window, page, screens, appRootPath, uiFunctions, { addWindowGlobals(dom.window, page, screens, appRootPath, {
hierarchy: {}, hierarchy: {},
actions: [], actions: [],
triggers: [], triggers: [],
@ -27,13 +27,12 @@ export const load = async (page, screens, url, appRootPath) => {
return { dom, app } return { dom, app }
} }
const addWindowGlobals = (window, page, screens, appRootPath, uiFunctions) => { const addWindowGlobals = (window, page, screens, appRootPath) => {
window["##BUDIBASE_FRONTEND_DEFINITION##"] = { window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
page, page,
screens, screens,
appRootPath, appRootPath,
} }
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = uiFunctions
} }
export const makePage = props => ({ props }) export const makePage = props => ({ props })
@ -184,28 +183,3 @@ const maketestlib = window => ({
opts.target.appendChild(node) 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, filename,
` `
window['##BUDIBASE_FRONTEND_DEFINITION##'] = ${clientUiDefinition}; window['##BUDIBASE_FRONTEND_DEFINITION##'] = ${clientUiDefinition};
window['##BUDIBASE_FRONTEND_FUNCTIONS##'] = ${pkg.uiFunctions};
` `
) )
} }