mirror of https://github.com/Budibase/budibase.git
12 changed files with 451 additions and 116 deletions
@ -0,0 +1,63 @@ |
|||
<script> |
|||
export let onSelect = () => {} |
|||
|
|||
let options = [ |
|||
{ |
|||
name: "state", |
|||
description: "Front-end client state.", |
|||
}, |
|||
{ |
|||
name: "context", |
|||
description: "The component context object.", |
|||
}, |
|||
{ |
|||
name: "event", |
|||
description: "DOM event handler arguments.", |
|||
}, |
|||
] |
|||
</script> |
|||
|
|||
<ul class="options"> |
|||
{#each options as option} |
|||
<li on:click={() => onSelect(`${option.name}.`)}> |
|||
<span class="name">{option.name}</span> |
|||
<span class="description">{option.description}</span> |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
|
|||
<style> |
|||
.options { |
|||
width: 172px; |
|||
margin: 0; |
|||
position: absolute; |
|||
top: 35px; |
|||
padding: 10px; |
|||
z-index: 1; |
|||
background: rgba(249, 249, 249, 1); |
|||
min-height: 50px; |
|||
border-radius: 2px; |
|||
} |
|||
|
|||
.description { |
|||
font-size: 0.8em; |
|||
} |
|||
|
|||
.name { |
|||
color: rgba(22, 48, 87, 0.6); |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
text-transform: uppercase; |
|||
margin-top: 5px; |
|||
display: block; |
|||
} |
|||
|
|||
.name:hover { |
|||
cursor: pointer; |
|||
font-weight: 800; |
|||
} |
|||
|
|||
li { |
|||
list-style-type: none; |
|||
} |
|||
</style> |
|||
@ -0,0 +1 @@ |
|||
export { default } from "./PropertyCascader.svelte" |
|||
@ -0,0 +1,274 @@ |
|||
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) { |
|||
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); |
|||
|
|||
if (boundEventHandlers.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 paramName in handlerInfo.parameters) { |
|||
parameters[paramName] = handlerInfo.parameters[paramName](context) |
|||
} |
|||
handlerType.execute(parameters) |
|||
} |
|||
} |
|||
Loading…
Reference in new issue