binding - backend initial

This commit is contained in:
Michael Shanks 2020-08-03 15:06:51 +01:00
parent 3a277ad0dc
commit 19bdabfaaf
2 changed files with 229 additions and 0 deletions

View File

@ -0,0 +1,111 @@
export default function({ componentInstanceId, screen, components, models }) {
const { target, targetAncestors, bindableInstances, bindableContexts } = walk(
{
instance: screen.props,
targetId: componentInstanceId,
components,
models,
}
)
return [
...bindableInstances
.filter(isComponentInstanceAvailable)
.map(componentInstanceToBindable),
...bindableContexts.map(contextToBindables),
]
}
const isComponentInstanceAvailable = i => true
// 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 contextToBindables = c => {
const contextParentNumber = 0
const contextParentPath = Array[contextParentNumber]
.map(() => "_parent")
.join(".")
return Object.keys(c.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}`,
// how the binding exressions looks to the user of the builder
readableBinding: `${c.instance._instanceName}.${c.schema.name}.${k}`,
}))
}
const walk = ({ instance, targetId, components, models, result }) => {
if (!result) {
result = {
currentAncestors: [],
currentContexts: [],
target: null,
targetAncestors: [],
bindableInstances: [],
bindableContexts: [],
parentMap: {},
}
}
// "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 {
if (instance.bindable) {
// pushing all components in here initially
// but this will not be correct, as some of
// these components will be in another context
// but we dont know this until the end of the walk
// so we will filter in another metod
result.bindableInstances.push({
instance,
prop: instance.bindable,
})
}
}
console.log(instance._component)
console.debug(components)
// 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 })
}
for (let child of instance._children || []) {
result.parentMap[child._id] = parentInstance._id
result.currentAncestors.push(instance)
walk({ instance, targetId, components, models, result })
result.currentAncestors.pop()
}
if (contextualInstance) {
// child walk done, remove from currentContexts
result.currentContexts.pop()
}
return result
}

View File

@ -0,0 +1,118 @@
import fetchbindableProperties from "../src/builderStore/fetchBindableProperties"
describe("fetch bindable properties", () => {
it("should return bindable properties from screen components", () => {
const result = fetchbindableProperties({
componentInstanceId: "heading-id",
...testData()
})
const componentBinding = result.find(r => r.instance._id === "search-input-id")
expect(componentBinding).toBeDefined()
expect(componentBinding.type).toBe("instance")
expect(componentBinding.runtimeBinding).toBe("state.search-input-id.value")
})
it("should not return bindable components when not in their context", () => {
})
it("should return model schema, when inside a context", () => {
})
it("should return model schema, for grantparent context", () => {
})
it("should return bindable component props, from components in same context", () => {
})
it("should not return model props from child context", () => {
})
})
const testData = () => {
const screen = {
instanceName: "test screen",
name: "screen-id",
route: "/",
props: {
_id:"screent-root-id",
_component: "@budibase/standard-components/container",
_children: [
{
_id: "heading-id",
_instanceName: "list item heading",
_component: "@budibase/standard-components/heading",
text: "Screen Title"
},
{
_id: "search-input-id",
_instanceName: "Search Input",
_component: "@budibase/standard-components/input",
value: "search phrase"
},
{
_id: "list-id",
_component: "@budibase/standard-components/list",
_instanceName: "list-name",
model: "test-model-id",
_children: [
{
_id: "list-item-heading-id",
_instanceName: "list item heading",
_component: "@budibase/standard-components/heading",
text: "hello"
}
]
},
]
}
}
const models = [{
id: "test-model-id",
name: "Test Model",
schema: {
name: {
type: "string"
},
description: {
type: "string"
}
}
}]
const components = {
"@budibase/standard-components/container" : {
props: {},
},
"@budibase/standard-components/list" : {
context: "model",
props: {
model: "string"
},
},
"@budibase/standard-components/input" : {
bindable: "value",
props: {
value: "string"
},
},
"@budibase/standard-components/heading" : {
props: {
text: "string"
},
},
}
return { screen, models, components }
}