forked from tsai/budibase
28 changed files with 163 additions and 627 deletions
@ -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) |
|
||||
} |
|
||||
@ -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) |
|
||||
} |
|
||||
@ -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) |
|
||||
} |
|
||||
@ -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) |
|
||||
@ -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
|
||||
} |
// }
|
||||
|
|||||
@ -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) |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue