Support multiple component context exports and export additional context from forms

This commit is contained in:
Andrew Kingston 2021-08-18 14:58:58 +01:00
parent fa275b2c4b
commit 32fe0ea072
8 changed files with 163 additions and 62 deletions

View File

@ -120,71 +120,79 @@ const getContextBindings = (asset, componentId) => {
// Create bindings for each data provider // Create bindings for each data provider
dataProviders.forEach(component => { dataProviders.forEach(component => {
const def = store.actions.components.getDefinition(component._component) const def = store.actions.components.getDefinition(component._component)
const contextDefinition = def.context const contexts = Array.isArray(def.context) ? def.context : [def.context]
let schema
let readablePrefix
if (contextDefinition.type === "form") { // Create bindings for each context block provided by this data provider
// Forms do not need table schemas contexts.forEach(context => {
// Their schemas are built from their component field names if (!context?.type) {
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) {
return 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 if (context.type === "form") {
const safeComponentId = makePropSafe(component._id) // Forms do not need table schemas
keys.forEach(key => { // Their schemas are built from their component field names
const fieldSchema = schema[key] schema = buildFormSchema(component)
readablePrefix = "Fields"
// Make safe runtime binding and replace certain bindings with a } else if (context.type === "static") {
// new property to help display components // Static contexts are fully defined by the components
let runtimeBoundKey = key schema = {}
if (fieldSchema.type === "link") { const values = context.values || []
runtimeBoundKey = `${key}_text` values.forEach(value => {
} else if (fieldSchema.type === "attachment") { schema[value.key] = { name: value.label, type: "string" }
runtimeBoundKey = `${key}_first` })
} 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( if (!schema) {
runtimeBoundKey return
)}`
// Optionally use a prefix with readable bindings
let readableBinding = component._instanceName
if (readablePrefix) {
readableBinding += `.${readablePrefix}`
} }
readableBinding += `.${fieldSchema.name || key}`
// Create the binding object const keys = Object.keys(schema).sort()
bindings.push({
type: "context", // Create bindable properties for each schema field
runtimeBinding, const safeComponentId = makePropSafe(component._id)
readableBinding, keys.forEach(key => {
// Field schema and provider are required to construct relationship const fieldSchema = schema[key]
// datasource options, based on bindable properties
fieldSchema, // Make safe runtime binding and replace certain bindings with a
providerId: component._id, // 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,
})
}) })
}) })
}) })

View File

@ -10,6 +10,7 @@
"icon": "Form", "icon": "Form",
"children": [ "children": [
"form", "form",
"formstep",
"fieldgroup", "fieldgroup",
"stringfield", "stringfield",
"numberfield", "numberfield",

View File

@ -85,6 +85,8 @@
props={{ props={{
options: setting.options || [], options: setting.options || [],
placeholder: setting.placeholder || null, placeholder: setting.placeholder || null,
min: setting.min || null,
max: setting.max || null,
}} }}
{bindings} {bindings}
{componentDefinition} {componentDefinition}

View File

@ -7,6 +7,8 @@ export const ActionTypes = {
RefreshDatasource: "RefreshDatasource", RefreshDatasource: "RefreshDatasource",
SetDataProviderQuery: "SetDataProviderQuery", SetDataProviderQuery: "SetDataProviderQuery",
ClearForm: "ClearForm", ClearForm: "ClearForm",
NextFormStep: "NextFormStep",
PrevFormStep: "PrevFormStep",
} }
export const ApiVersion = "1" export const ApiVersion = "1"

View File

@ -1705,7 +1705,9 @@
"illegalChildren": ["section"], "illegalChildren": ["section"],
"actions": [ "actions": [
"ValidateForm", "ValidateForm",
"ClearForm" "ClearForm",
"NextFormStep",
"PrevFormStep"
], ],
"styles": ["size"], "styles": ["size"],
"settings": [ "settings": [
@ -1727,7 +1729,7 @@
}, },
{ {
"type": "number", "type": "number",
"label": "Number of steps", "label": "Steps",
"key": "steps", "key": "steps",
"defaultValue": 1 "defaultValue": 1
}, },
@ -1738,8 +1740,51 @@
"defaultValue": false "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": { "context": {
"type": "form" "type": "static",
"values": [
{
"label": "Valid",
"key": "valid"
}
]
} }
}, },
"fieldgroup": { "fieldgroup": {

View File

@ -0,0 +1,18 @@
<script>
import { getContext } from "svelte"
import Placeholder from "../Placeholder.svelte"
export let step
const { styleable } = getContext("sdk")
const component = getContext("component")
const formContext = getContext("form")
</script>
{#if !formContext}
<Placeholder text="Form steps need to be wrapped in a form" />
{:else}
<div use:styleable={$component.styles}>
<slot />
</div>
{/if}

View File

@ -17,7 +17,12 @@
let fieldMap = {} let fieldMap = {}
// Form state contains observable data about the form // 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 // Form API contains functions to control the form
const formApi = { const formApi = {
@ -82,6 +87,18 @@
fieldApi.clearValue() 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 // Provide both form API and state to children
@ -91,6 +108,8 @@
const actions = [ const actions = [
{ type: ActionTypes.ValidateForm, callback: formApi.validate }, { type: ActionTypes.ValidateForm, callback: formApi.validate },
{ type: ActionTypes.ClearForm, callback: formApi.clear }, { 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 // Creates an API for a specific field
@ -229,7 +248,12 @@
<Provider <Provider
{actions} {actions}
data={{ ...$formState.values, tableId: dataSource?.tableId }} data={{
...$formState.values,
tableId: dataSource?.tableId,
valid: $formState.valid,
step: $formState.step,
}}
> >
<div use:styleable={$component.styles}> <div use:styleable={$component.styles}>
{#if loaded} {#if loaded}

View File

@ -9,3 +9,4 @@ export { default as datetimefield } from "./DateTimeField.svelte"
export { default as attachmentfield } from "./AttachmentField.svelte" export { default as attachmentfield } from "./AttachmentField.svelte"
export { default as relationshipfield } from "./RelationshipField.svelte" export { default as relationshipfield } from "./RelationshipField.svelte"
export { default as passwordfield } from "./PasswordField.svelte" export { default as passwordfield } from "./PasswordField.svelte"
export { default as formstep } from "./FormStep.svelte"