From c44d0a684be1050ff2d2f0acbf80b75c8359ef9e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 18 Aug 2021 14:58:58 +0100 Subject: [PATCH] Support multiple component context exports and export additional context from forms --- .../builder/src/builderStore/dataBinding.js | 122 ++++++++++-------- .../design/AppPreview/componentStructure.json | 1 + .../ComponentSettingsSection.svelte | 2 + packages/client/src/constants.js | 2 + packages/standard-components/manifest.json | 51 +++++++- .../src/forms/FormStep.svelte | 18 +++ .../src/forms/InnerForm.svelte | 28 +++- .../standard-components/src/forms/index.js | 1 + 8 files changed, 163 insertions(+), 62 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index dc995d611c..00eaaf0249 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -120,71 +120,79 @@ const getContextBindings = (asset, componentId) => { // Create bindings for each data provider dataProviders.forEach(component => { const def = store.actions.components.getDefinition(component._component) - const contextDefinition = def.context - let schema - let readablePrefix + const contexts = Array.isArray(def.context) ? def.context : [def.context] - if (contextDefinition.type === "form") { - // Forms do not need table schemas - // Their schemas are built from their component field names - schema = buildFormSchema(component) - readablePrefix = "Fields" - } else if (contextDefinition.type === "static") { - // Static contexts are fully defined by the components - schema = {} - const values = contextDefinition.values || [] - values.forEach(value => { - schema[value.key] = { name: value.label, type: "string" } - }) - } else if (contextDefinition.type === "schema") { - // Schema contexts are generated dynamically depending on their data - const datasource = getDatasourceForProvider(asset, component) - if (!datasource) { + // Create bindings for each context block provided by this data provider + contexts.forEach(context => { + if (!context?.type) { return } - const info = getSchemaForDatasource(asset, datasource) - schema = info.schema - readablePrefix = info.table?.name - } - if (!schema) { - return - } - const keys = Object.keys(schema).sort() + let schema + let readablePrefix - // Create bindable properties for each schema field - const safeComponentId = makePropSafe(component._id) - keys.forEach(key => { - const fieldSchema = schema[key] - - // Make safe runtime binding and replace certain bindings with a - // new property to help display components - let runtimeBoundKey = key - if (fieldSchema.type === "link") { - runtimeBoundKey = `${key}_text` - } else if (fieldSchema.type === "attachment") { - runtimeBoundKey = `${key}_first` + if (context.type === "form") { + // Forms do not need table schemas + // Their schemas are built from their component field names + schema = buildFormSchema(component) + readablePrefix = "Fields" + } else if (context.type === "static") { + // Static contexts are fully defined by the components + schema = {} + const values = context.values || [] + values.forEach(value => { + schema[value.key] = { name: value.label, type: "string" } + }) + } else if (context.type === "schema") { + // Schema contexts are generated dynamically depending on their data + const datasource = getDatasourceForProvider(asset, component) + if (!datasource) { + return + } + const info = getSchemaForDatasource(asset, datasource) + schema = info.schema + readablePrefix = info.table?.name } - const runtimeBinding = `${safeComponentId}.${makePropSafe( - runtimeBoundKey - )}` - - // Optionally use a prefix with readable bindings - let readableBinding = component._instanceName - if (readablePrefix) { - readableBinding += `.${readablePrefix}` + if (!schema) { + return } - readableBinding += `.${fieldSchema.name || key}` - // Create the binding object - bindings.push({ - type: "context", - runtimeBinding, - readableBinding, - // Field schema and provider are required to construct relationship - // datasource options, based on bindable properties - fieldSchema, - providerId: component._id, + const keys = Object.keys(schema).sort() + + // Create bindable properties for each schema field + const safeComponentId = makePropSafe(component._id) + keys.forEach(key => { + const fieldSchema = schema[key] + + // Make safe runtime binding and replace certain bindings with a + // new property to help display components + let runtimeBoundKey = key + if (fieldSchema.type === "link") { + runtimeBoundKey = `${key}_text` + } else if (fieldSchema.type === "attachment") { + runtimeBoundKey = `${key}_first` + } + const runtimeBinding = `${safeComponentId}.${makePropSafe( + runtimeBoundKey + )}` + + // Optionally use a prefix with readable bindings + let readableBinding = component._instanceName + if (readablePrefix) { + readableBinding += `.${readablePrefix}` + } + readableBinding += `.${fieldSchema.name || key}` + + // Create the binding object + bindings.push({ + type: "context", + runtimeBinding, + readableBinding, + // Field schema and provider are required to construct relationship + // datasource options, based on bindable properties + fieldSchema, + providerId: component._id, + }) }) }) }) diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index c83686158f..cea20a7dcf 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -10,6 +10,7 @@ "icon": "Form", "children": [ "form", + "formstep", "fieldgroup", "stringfield", "numberfield", diff --git a/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte b/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte index 9ec1108985..f047e9316b 100644 --- a/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte @@ -85,6 +85,8 @@ props={{ options: setting.options || [], placeholder: setting.placeholder || null, + min: setting.min || null, + max: setting.max || null, }} {bindings} {componentDefinition} diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index 7204b8c951..99e95e8398 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -7,6 +7,8 @@ export const ActionTypes = { RefreshDatasource: "RefreshDatasource", SetDataProviderQuery: "SetDataProviderQuery", ClearForm: "ClearForm", + NextFormStep: "NextFormStep", + PrevFormStep: "PrevFormStep", } export const ApiVersion = "1" diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index b8c7bdc41d..d5d22aba3a 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -1705,7 +1705,9 @@ "illegalChildren": ["section"], "actions": [ "ValidateForm", - "ClearForm" + "ClearForm", + "NextFormStep", + "PrevFormStep" ], "styles": ["size"], "settings": [ @@ -1727,7 +1729,7 @@ }, { "type": "number", - "label": "Number of steps", + "label": "Steps", "key": "steps", "defaultValue": 1 }, @@ -1738,8 +1740,51 @@ "defaultValue": false } ], + "context": [ + { + "type": "static", + "values": [ + { + "label": "Valid", + "key": "valid" + }, + { + "label": "Step", + "key": "step" + } + ] + }, + { + "type": "form" + } + ] + }, + "formstep": { + "name": "Form Step", + "icon": "Form", + "hasChildren": true, + "illegalChildren": ["section"], + "actions": [ + "ValidateFormStep" + ], + "styles": ["size"], + "settings": [ + { + "type": "number", + "label": "Step", + "key": "step", + "defaultValue": 1, + "min": 1 + } + ], "context": { - "type": "form" + "type": "static", + "values": [ + { + "label": "Valid", + "key": "valid" + } + ] } }, "fieldgroup": { diff --git a/packages/standard-components/src/forms/FormStep.svelte b/packages/standard-components/src/forms/FormStep.svelte index e69de29bb2..449978ec08 100644 --- a/packages/standard-components/src/forms/FormStep.svelte +++ b/packages/standard-components/src/forms/FormStep.svelte @@ -0,0 +1,18 @@ + + +{#if !formContext} + +{:else} +
+ +
+{/if} diff --git a/packages/standard-components/src/forms/InnerForm.svelte b/packages/standard-components/src/forms/InnerForm.svelte index eea2e6ebf0..9355c67660 100644 --- a/packages/standard-components/src/forms/InnerForm.svelte +++ b/packages/standard-components/src/forms/InnerForm.svelte @@ -17,7 +17,12 @@ let fieldMap = {} // Form state contains observable data about the form - const formState = writable({ values: initialValues, errors: {}, valid: true }) + const formState = writable({ + values: initialValues, + errors: {}, + valid: true, + step: 1, + }) // Form API contains functions to control the form const formApi = { @@ -82,6 +87,18 @@ fieldApi.clearValue() }) }, + nextStep: () => { + formState.update(state => ({ + ...state, + step: state.step + 1, + })) + }, + prevStep: () => { + formState.update(state => ({ + ...state, + step: Math.max(1, state.step - 1), + })) + }, } // Provide both form API and state to children @@ -91,6 +108,8 @@ const actions = [ { type: ActionTypes.ValidateForm, callback: formApi.validate }, { type: ActionTypes.ClearForm, callback: formApi.clear }, + { type: ActionTypes.NextFormStep, callback: formApi.nextStep }, + { type: ActionTypes.PrevFormStep, callback: formApi.prevStep }, ] // Creates an API for a specific field @@ -229,7 +248,12 @@
{#if loaded} diff --git a/packages/standard-components/src/forms/index.js b/packages/standard-components/src/forms/index.js index fed371278b..4f3eaa5adb 100644 --- a/packages/standard-components/src/forms/index.js +++ b/packages/standard-components/src/forms/index.js @@ -9,3 +9,4 @@ export { default as datetimefield } from "./DateTimeField.svelte" export { default as attachmentfield } from "./AttachmentField.svelte" export { default as relationshipfield } from "./RelationshipField.svelte" export { default as passwordfield } from "./PasswordField.svelte" +export { default as formstep } from "./FormStep.svelte"