fetchBindableProperties - complete
This commit is contained in:
parent
549327c885
commit
e4bcd4020b
|
@ -1,3 +1,5 @@
|
|||
import { cloneDeep, difference, fill } from "lodash"
|
||||
|
||||
const stubBindings = [
|
||||
{
|
||||
// type: instance represents a bindable property of a component
|
||||
|
@ -10,7 +12,9 @@ const stubBindings = [
|
|||
},
|
||||
{
|
||||
type: "context",
|
||||
instance: { /** a component instance **/},
|
||||
instance: {
|
||||
/** a component instance **/
|
||||
},
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: "context._parent.<key of model/record>",
|
||||
// how the binding exressions looks to the user of the builder
|
||||
|
@ -19,74 +23,97 @@ const stubBindings = [
|
|||
]
|
||||
|
||||
export default function({ componentInstanceId, screen, components, models }) {
|
||||
const { target, targetAncestors, bindableInstances, bindableContexts } = walk(
|
||||
{
|
||||
instance: screen.props,
|
||||
targetId: componentInstanceId,
|
||||
components,
|
||||
models,
|
||||
}
|
||||
)
|
||||
const walkResult = walk({
|
||||
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
||||
instance: cloneDeep(screen.props),
|
||||
targetId: componentInstanceId,
|
||||
components,
|
||||
models,
|
||||
})
|
||||
|
||||
return [
|
||||
...bindableInstances
|
||||
.filter(isComponentInstanceAvailable)
|
||||
.map(componentInstanceToBindable),
|
||||
...bindableContexts.map(contextToBindables),
|
||||
...walkResult.bindableInstances
|
||||
.filter(isInstanceInSharedContext(walkResult))
|
||||
.map(componentInstanceToBindable(walkResult)),
|
||||
|
||||
...walkResult.target._contexts.map(contextToBindables(walkResult)).flat(),
|
||||
]
|
||||
}
|
||||
|
||||
const isComponentInstanceAvailable = i => true
|
||||
const isInstanceInSharedContext = walkResult => i =>
|
||||
// should cover
|
||||
// - neither are in any context
|
||||
// - both in same context
|
||||
// - instance is in ancestor context of target
|
||||
i.instance._contexts.length <= walkResult.target._contexts.length &&
|
||||
difference(i.instance._contexts, walkResult.target._contexts).length === 0
|
||||
|
||||
// turns a component instance prop into binding expressions
|
||||
// used by the UI
|
||||
const componentInstanceToBindable = i => ({
|
||||
type: "instance",
|
||||
instance: i.instance,
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: `state.${i.instance._id}.${i.prop}`,
|
||||
// how the binding exressions looks to the user of the builder
|
||||
readableBinding: `${i.instance._instanceName}`,
|
||||
})
|
||||
const componentInstanceToBindable = walkResult => i => {
|
||||
const lastContext =
|
||||
i.instance._contexts.length &&
|
||||
i.instance._contexts[i.instance._contexts.length - 1]
|
||||
const contextParentPath = lastContext
|
||||
? getParentPath(walkResult, lastContext)
|
||||
: ""
|
||||
|
||||
const contextToBindables = c => {
|
||||
const contextParentNumber = 0
|
||||
const contextParentPath = Array[contextParentNumber]
|
||||
.map(() => "_parent")
|
||||
.join(".")
|
||||
return Object.keys(c.schema).map(k => ({
|
||||
// if component is inside context, then the component lives
|
||||
// in context at runtime (otherwise, in state)
|
||||
const stateOrContext = lastContext ? "context" : "state"
|
||||
return {
|
||||
type: "instance",
|
||||
instance: i.instance,
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: `${stateOrContext}.${contextParentPath}${i.instance._id}.${i.prop}`,
|
||||
// how the binding exressions looks to the user of the builder
|
||||
readableBinding: `${i.instance._instanceName}`,
|
||||
}
|
||||
}
|
||||
|
||||
const contextToBindables = walkResult => c => {
|
||||
const contextParentPath = getParentPath(walkResult, c)
|
||||
|
||||
return Object.keys(c.model.schema).map(k => ({
|
||||
type: "context",
|
||||
instance: c.instance,
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: `context.${contextParentPath}.${k}`,
|
||||
runtimeBinding: `context.${contextParentPath}data.${k}`,
|
||||
// how the binding exressions looks to the user of the builder
|
||||
readableBinding: `${c.instance._instanceName}.${c.schema.name}.${k}`,
|
||||
readableBinding: `${c.instance._instanceName}.${c.model.name}.${k}`,
|
||||
}))
|
||||
}
|
||||
|
||||
const getParentPath = (walkResult, context) => {
|
||||
// describes the number of "_parent" in the path
|
||||
// clone array first so original array is not mtated
|
||||
const contextParentNumber = [...walkResult.target._contexts]
|
||||
.reverse()
|
||||
.indexOf(context)
|
||||
|
||||
return (
|
||||
new Array(contextParentNumber).fill("_parent").join(".") +
|
||||
// trailing . if has parents
|
||||
(contextParentNumber ? "." : "")
|
||||
)
|
||||
}
|
||||
|
||||
const walk = ({ instance, targetId, components, models, result }) => {
|
||||
if (!result) {
|
||||
result = {
|
||||
currentAncestors: [],
|
||||
currentContexts: [],
|
||||
target: null,
|
||||
targetAncestors: [],
|
||||
bindableInstances: [],
|
||||
bindableContexts: [],
|
||||
parentMap: {},
|
||||
allContexts: [],
|
||||
currentContexts: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (!instance._contexts) instance._contexts = []
|
||||
|
||||
// "component" is the component definition (object in component.json)
|
||||
const component = components[instance._component]
|
||||
const parentInstance =
|
||||
result.currentAncestors.length > 0 &&
|
||||
result.currentAncestors[result.currentAncestors.length - 1]
|
||||
|
||||
if (instance._id === targetId) {
|
||||
// set currentParents to be result parents
|
||||
result.targetAncestors = result.currentAncestors
|
||||
result.bindableContexts = result.currentContexts
|
||||
// found it
|
||||
result.target = instance
|
||||
} else {
|
||||
|
@ -102,22 +129,24 @@ const walk = ({ instance, targetId, components, models, result }) => {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// a component that provides context to it's children
|
||||
const contextualInstance = component.context && instance[component.context]
|
||||
|
||||
if (contextualInstance) {
|
||||
// add to currentContexts (ancestory of context)
|
||||
// before walking children
|
||||
const schema = models.find(m => m._id === instance[component.context])
|
||||
.schema
|
||||
result.currentContexts.push({ instance, schema })
|
||||
const model = models.find(m => m._id === instance[component.context])
|
||||
result.currentContexts.push({ instance, model })
|
||||
}
|
||||
|
||||
const currentContexts = [...result.currentContexts]
|
||||
for (let child of instance._children || []) {
|
||||
result.parentMap[child._id] = parentInstance._id
|
||||
result.currentAncestors.push(instance)
|
||||
// attaching _contexts of components, for eas comparison later
|
||||
// these have been deep cloned above, so shouln't modify the
|
||||
// original component instances
|
||||
child._contexts = currentContexts
|
||||
walk({ instance: child, targetId, components, models, result })
|
||||
result.currentAncestors.pop()
|
||||
}
|
||||
|
||||
if (contextualInstance) {
|
||||
|
|
|
@ -6,9 +6,7 @@ describe("fetch bindable properties", () => {
|
|||
componentInstanceId: "heading-id",
|
||||
...testData()
|
||||
})
|
||||
const componentBinding = result.find(r => r.instance._id === "search-input-id")
|
||||
console.debug(componentBinding)
|
||||
console.debug("result:" + JSON.stringify(result))
|
||||
const componentBinding = result.find(r => r.instance._id === "search-input-id" && r.type === "instance")
|
||||
expect(componentBinding).toBeDefined()
|
||||
expect(componentBinding.type).toBe("instance")
|
||||
expect(componentBinding.runtimeBinding).toBe("state.search-input-id.value")
|
||||
|
@ -24,19 +22,73 @@ describe("fetch bindable properties", () => {
|
|||
})
|
||||
|
||||
it("should return model schema, when inside a context", () => {
|
||||
|
||||
const result = fetchBindableProperties({
|
||||
componentInstanceId: "list-item-input-id",
|
||||
...testData()
|
||||
})
|
||||
const contextBindings = result.filter(r => r.instance._id === "list-id" && r.type==="context")
|
||||
expect(contextBindings.length).toBe(2)
|
||||
|
||||
const namebinding = contextBindings.find(b => b.runtimeBinding === "context.data.name")
|
||||
expect(namebinding).toBeDefined()
|
||||
expect(namebinding.readableBinding).toBe("list-name.Test Model.name")
|
||||
|
||||
const descriptionbinding = contextBindings.find(b => b.runtimeBinding === "context.data.description")
|
||||
expect(descriptionbinding).toBeDefined()
|
||||
expect(descriptionbinding.readableBinding).toBe("list-name.Test Model.description")
|
||||
})
|
||||
|
||||
it("should return model schema, for grantparent context", () => {
|
||||
|
||||
const result = fetchBindableProperties({
|
||||
componentInstanceId: "child-list-item-input-id",
|
||||
...testData()
|
||||
})
|
||||
const contextBindings = result.filter(r => r.type==="context")
|
||||
expect(contextBindings.length).toBe(4)
|
||||
|
||||
const namebinding_parent = contextBindings.find(b => b.runtimeBinding === "context._parent.data.name")
|
||||
expect(namebinding_parent).toBeDefined()
|
||||
expect(namebinding_parent.readableBinding).toBe("list-name.Test Model.name")
|
||||
|
||||
const descriptionbinding_parent = contextBindings.find(b => b.runtimeBinding === "context._parent.data.description")
|
||||
expect(descriptionbinding_parent).toBeDefined()
|
||||
expect(descriptionbinding_parent.readableBinding).toBe("list-name.Test Model.description")
|
||||
|
||||
const namebinding_own = contextBindings.find(b => b.runtimeBinding === "context.data.name")
|
||||
expect(namebinding_own).toBeDefined()
|
||||
expect(namebinding_own.readableBinding).toBe("child-list-name.Test Model.name")
|
||||
|
||||
const descriptionbinding_own = contextBindings.find(b => b.runtimeBinding === "context.data.description")
|
||||
expect(descriptionbinding_own).toBeDefined()
|
||||
expect(descriptionbinding_own.readableBinding).toBe("child-list-name.Test Model.description")
|
||||
})
|
||||
|
||||
it("should return bindable component props, from components in same context", () => {
|
||||
|
||||
const result = fetchBindableProperties({
|
||||
componentInstanceId: "list-item-heading-id",
|
||||
...testData()
|
||||
})
|
||||
const componentBinding = result.find(r => r.instance._id === "list-item-input-id" && r.type === "instance")
|
||||
expect(componentBinding).toBeDefined()
|
||||
expect(componentBinding.runtimeBinding).toBe("context.list-item-input-id.value")
|
||||
})
|
||||
|
||||
it("should not return model props from child context", () => {
|
||||
|
||||
it("should not return components from child context", () => {
|
||||
const result = fetchBindableProperties({
|
||||
componentInstanceId: "list-item-heading-id",
|
||||
...testData()
|
||||
})
|
||||
const componentBinding = result.find(r => r.instance._id === "child-list-item-input-id" && r.type === "instance")
|
||||
expect(componentBinding).not.toBeDefined()
|
||||
})
|
||||
|
||||
it("should return bindable component props, from components in same context (when nested context)", () => {
|
||||
const result = fetchBindableProperties({
|
||||
componentInstanceId: "child-list-item-heading-id",
|
||||
...testData()
|
||||
})
|
||||
const componentBinding = result.find(r => r.instance._id === "child-list-item-input-id" && r.type === "instance")
|
||||
expect(componentBinding).toBeDefined()
|
||||
})
|
||||
|
||||
|
||||
|
@ -83,6 +135,26 @@ const testData = () => {
|
|||
_component: "@budibase/standard-components/input",
|
||||
value: "list item"
|
||||
},
|
||||
{
|
||||
_id: "child-list-id",
|
||||
_component: "@budibase/standard-components/list",
|
||||
_instanceName: "child-list-name",
|
||||
model: "test-model-id",
|
||||
_children: [
|
||||
{
|
||||
_id: "child-list-item-heading-id",
|
||||
_instanceName: "child list item heading",
|
||||
_component: "@budibase/standard-components/heading",
|
||||
text: "hello"
|
||||
},
|
||||
{
|
||||
_id: "child-list-item-input-id",
|
||||
_instanceName: "Child List Item Input",
|
||||
_component: "@budibase/standard-components/input",
|
||||
value: "child list item"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue