From 19bdabfaaf322621e09b9aa923203c2affb26600 Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Mon, 3 Aug 2020 15:06:51 +0100 Subject: [PATCH] binding - backend initial --- .../builderStore/fetchBindableProperties.js | 111 ++++++++++++++++ packages/builder/tests/binding.spec.js | 118 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 packages/builder/src/builderStore/fetchBindableProperties.js create mode 100644 packages/builder/tests/binding.spec.js diff --git a/packages/builder/src/builderStore/fetchBindableProperties.js b/packages/builder/src/builderStore/fetchBindableProperties.js new file mode 100644 index 0000000000..04648c4fb2 --- /dev/null +++ b/packages/builder/src/builderStore/fetchBindableProperties.js @@ -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 +} diff --git a/packages/builder/tests/binding.spec.js b/packages/builder/tests/binding.spec.js new file mode 100644 index 0000000000..0666edb39e --- /dev/null +++ b/packages/builder/tests/binding.spec.js @@ -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 } + +} \ No newline at end of file