forked from tsai/budibase
2 changed files with 229 additions and 0 deletions
@ -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 |
||||
|
} |
||||
@ -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 } |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue