Support multiple component context exports and export additional context from forms
This commit is contained in:
parent
fa275b2c4b
commit
32fe0ea072
|
@ -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,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"icon": "Form",
|
"icon": "Form",
|
||||||
"children": [
|
"children": [
|
||||||
"form",
|
"form",
|
||||||
|
"formstep",
|
||||||
"fieldgroup",
|
"fieldgroup",
|
||||||
"stringfield",
|
"stringfield",
|
||||||
"numberfield",
|
"numberfield",
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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}
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue