Rewrite client form library to derive state where possible and handle steps
This commit is contained in:
parent
9ea255b7bc
commit
7ba8bc6a19
|
@ -1749,8 +1749,12 @@
|
||||||
"key": "valid"
|
"key": "valid"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Step",
|
"label": "Current Step",
|
||||||
"key": "step"
|
"key": "currentStep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Current Step Valid",
|
||||||
|
"key": "currentStepValid"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1776,16 +1780,7 @@
|
||||||
"defaultValue": 1,
|
"defaultValue": 1,
|
||||||
"min": 1
|
"min": 1
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"context": {
|
|
||||||
"type": "static",
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"label": "Valid",
|
|
||||||
"key": "valid"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"fieldgroup": {
|
"fieldgroup": {
|
||||||
"name": "Field Group",
|
"name": "Field Group",
|
||||||
|
|
|
@ -42,11 +42,11 @@
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
>
|
>
|
||||||
{#if $fieldState}
|
{#if fieldState}
|
||||||
<CoreDropzone
|
<CoreDropzone
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
fieldApi.setValue(e.detail)
|
fieldApi.setValue(e.detail)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -39,10 +39,10 @@
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<CoreCheckbox
|
<CoreCheckbox
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
{size}
|
{size}
|
||||||
on:change={e => fieldApi.setValue(e.detail)}
|
on:change={e => fieldApi.setValue(e.detail)}
|
||||||
{text}
|
{text}
|
||||||
|
|
|
@ -51,11 +51,11 @@
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<CoreDatePicker
|
<CoreDatePicker
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
on:change={e => fieldApi.setValue(e.detail)}
|
on:change={e => fieldApi.setValue(e.detail)}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
{enableTime}
|
{enableTime}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
// Get contexts
|
// Get contexts
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
const fieldGroupContext = getContext("fieldGroup")
|
const formStepContext = getContext("form-step")
|
||||||
|
const fieldGroupContext = getContext("field-group")
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
|
@ -26,16 +27,20 @@
|
||||||
field,
|
field,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
disabled,
|
disabled,
|
||||||
validation
|
validation,
|
||||||
|
formStepContext || 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Expose field properties to parent component
|
// Expose field properties to parent component
|
||||||
fieldState = formField?.fieldState
|
$: fieldState = $formField?.fieldState
|
||||||
fieldApi = formField?.fieldApi
|
$: fieldApi = $formField?.fieldApi
|
||||||
fieldSchema = formField?.fieldSchema
|
$: fieldSchema = $formField?.fieldSchema
|
||||||
|
|
||||||
// Keep validation rules up to date
|
// Keep validation rules up to date
|
||||||
$: fieldApi?.updateValidation(validation)
|
$: updateValidation(validation)
|
||||||
|
const updateValidation = validation => {
|
||||||
|
fieldApi?.updateValidation(validation)
|
||||||
|
}
|
||||||
|
|
||||||
// Extract label position from field group context
|
// Extract label position from field group context
|
||||||
$: labelPositionClass =
|
$: labelPositionClass =
|
||||||
|
@ -46,7 +51,7 @@
|
||||||
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
||||||
<label
|
<label
|
||||||
class:hidden={!label}
|
class:hidden={!label}
|
||||||
for={$fieldState?.fieldId}
|
for={fieldState?.fieldId}
|
||||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}
|
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}
|
||||||
>
|
>
|
||||||
{label || ""}
|
{label || ""}
|
||||||
|
@ -64,8 +69,8 @@
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<slot />
|
<slot />
|
||||||
{#if $fieldState.error}
|
{#if fieldState.error}
|
||||||
<div class="error">{$fieldState.error}</div>
|
<div class="error">{fieldState.error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
setContext("fieldGroup", { labelPosition })
|
setContext("field-group", { labelPosition })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper" use:styleable={$component.styles}>
|
<div class="wrapper" use:styleable={$component.styles}>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const fieldGroupContext = getContext("fieldGroup")
|
const fieldGroupContext = getContext("field-group")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if fieldGroupContext}
|
{#if fieldGroupContext}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import InnerForm from "./InnerForm.svelte"
|
import InnerForm from "./InnerForm.svelte"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
|
@ -9,6 +9,11 @@
|
||||||
export let actionType = "Create"
|
export let actionType = "Create"
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
const { API } = getContext("sdk")
|
||||||
|
|
||||||
|
let loaded = false
|
||||||
|
let schema
|
||||||
|
let table
|
||||||
|
|
||||||
// Returns the closes data context which isn't a built in context
|
// Returns the closes data context which isn't a built in context
|
||||||
const getInitialValues = (type, dataSource, context) => {
|
const getInitialValues = (type, dataSource, context) => {
|
||||||
|
@ -32,19 +37,48 @@
|
||||||
return closestContext
|
return closestContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetches the form schema from this form's dataSource, if one exists
|
||||||
|
const fetchSchema = async () => {
|
||||||
|
if (!dataSource?.tableId) {
|
||||||
|
schema = {}
|
||||||
|
table = null
|
||||||
|
} else {
|
||||||
|
table = await API.fetchTableDefinition(dataSource?.tableId)
|
||||||
|
if (table) {
|
||||||
|
if (dataSource?.type === "query") {
|
||||||
|
schema = {}
|
||||||
|
const params = table.parameters || []
|
||||||
|
params.forEach(param => {
|
||||||
|
schema[param.name] = { ...param, type: "string" }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
schema = table.schema || {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
|
|
||||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||||
$: resetKey = JSON.stringify(initialValues)
|
$: resetKey = JSON.stringify(initialValues)
|
||||||
|
|
||||||
|
// Load the form schema on mount
|
||||||
|
onMount(fetchSchema)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key resetKey}
|
{#if loaded}
|
||||||
<InnerForm
|
{#key resetKey}
|
||||||
{dataSource}
|
<InnerForm
|
||||||
{theme}
|
{dataSource}
|
||||||
{size}
|
{theme}
|
||||||
{disabled}
|
{size}
|
||||||
{actionType}
|
{disabled}
|
||||||
{initialValues}
|
{actionType}
|
||||||
>
|
{schema}
|
||||||
<slot />
|
{table}
|
||||||
</InnerForm>
|
{initialValues}
|
||||||
{/key}
|
>
|
||||||
|
<slot />
|
||||||
|
</InnerForm>
|
||||||
|
{/key}
|
||||||
|
{/if}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import Placeholder from "../Placeholder.svelte"
|
import Placeholder from "../Placeholder.svelte"
|
||||||
|
|
||||||
export let step
|
export let step
|
||||||
|
@ -8,18 +8,17 @@
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
|
|
||||||
|
// Set form step context so fields know what step they are within
|
||||||
|
setContext("form-step", step || 1)
|
||||||
|
|
||||||
$: formState = formContext?.formState
|
$: formState = formContext?.formState
|
||||||
|
$: currentStep = $formState?.currentStep
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !formContext}
|
{#if !formContext}
|
||||||
<Placeholder text="Form steps need to be wrapped in a form" />
|
<Placeholder text="Form steps need to be wrapped in a form" />
|
||||||
{:else if step === $formState.step}
|
{:else if step === currentStep}
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<div>
|
|
||||||
Step {step} is visible!
|
|
||||||
</div>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
|
||||||
<div>hiding step {step}!</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,41 +1,84 @@
|
||||||
<script>
|
<script>
|
||||||
import { setContext, getContext, onMount } from "svelte"
|
import { setContext, getContext } from "svelte"
|
||||||
import { writable, get } from "svelte/store"
|
import { derived, get, writable } from "svelte/store"
|
||||||
import { createValidatorFromConstraints } from "./validation"
|
import { createValidatorFromConstraints } from "./validation"
|
||||||
import { generateID } from "../helpers"
|
import { generateID } from "../helpers"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let initialValues
|
export let initialValues
|
||||||
|
export let schema
|
||||||
|
export let table
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, API, Provider, ActionTypes } = getContext("sdk")
|
const { styleable, Provider, ActionTypes } = getContext("sdk")
|
||||||
|
|
||||||
let loaded = false
|
let fields = []
|
||||||
let schema
|
const currentStep = writable(1)
|
||||||
let table
|
|
||||||
let fieldMap = {}
|
|
||||||
|
|
||||||
// Form state contains observable data about the form
|
|
||||||
const formState = writable({
|
const formState = writable({
|
||||||
values: initialValues,
|
values: {},
|
||||||
errors: {},
|
errors: {},
|
||||||
valid: true,
|
valid: true,
|
||||||
step: 1,
|
currentStep: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Form API contains functions to control the form
|
// Reactive derived stores to derive form state from field array
|
||||||
|
$: values = deriveFieldProperty(fields, f => f.fieldState.value)
|
||||||
|
$: errors = deriveFieldProperty(fields, f => f.fieldState.error)
|
||||||
|
$: valid = !Object.values($errors).some(error => error != null)
|
||||||
|
|
||||||
|
// Derive which fields belong in which steps
|
||||||
|
$: currentStepValid = derived(
|
||||||
|
[currentStep, ...fields],
|
||||||
|
([currentStepValue, ...fieldsValue]) => {
|
||||||
|
return !fieldsValue
|
||||||
|
.filter(f => f.step === currentStepValue)
|
||||||
|
.some(f => f.fieldState.error != null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update form state store from derived stores
|
||||||
|
$: {
|
||||||
|
formState.set({
|
||||||
|
values: $values,
|
||||||
|
errors: $errors,
|
||||||
|
valid,
|
||||||
|
currentStep: $currentStep,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a derived store from an array of fields, comprised of a map of
|
||||||
|
// extracted values from the field array
|
||||||
|
const deriveFieldProperty = (fieldStores, getProp) => {
|
||||||
|
return derived(fieldStores, fieldValues => {
|
||||||
|
const reducer = (map, field) => ({ ...map, [field.name]: getProp(field) })
|
||||||
|
return fieldValues.reduce(reducer, {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searches the field array for a certain field
|
||||||
|
const getField = name => {
|
||||||
|
return fields.find(field => get(field).name === name)
|
||||||
|
}
|
||||||
|
|
||||||
const formApi = {
|
const formApi = {
|
||||||
registerField: (
|
registerField: (
|
||||||
field,
|
field,
|
||||||
defaultValue = null,
|
defaultValue = null,
|
||||||
fieldDisabled = false,
|
fieldDisabled = false,
|
||||||
validationRules
|
validationRules,
|
||||||
|
step = 1
|
||||||
) => {
|
) => {
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip if we've already registered this field
|
||||||
|
const existingField = getField(field)
|
||||||
|
if (existingField) {
|
||||||
|
return existingField
|
||||||
|
}
|
||||||
|
|
||||||
// Auto columns are always disabled
|
// Auto columns are always disabled
|
||||||
const isAutoColumn = !!schema?.[field]?.autocolumn
|
const isAutoColumn = !!schema?.[field]?.autocolumn
|
||||||
|
|
||||||
|
@ -48,99 +91,79 @@
|
||||||
table
|
table
|
||||||
)
|
)
|
||||||
|
|
||||||
// Construct field object
|
// Construct field info
|
||||||
fieldMap[field] = {
|
const fieldInfo = writable({
|
||||||
fieldState: makeFieldState(
|
name: field,
|
||||||
field,
|
step: step || 1,
|
||||||
validator,
|
fieldState: {
|
||||||
|
fieldId: `id-${generateID()}`,
|
||||||
|
value: initialValues[field] ?? defaultValue,
|
||||||
|
error: null,
|
||||||
|
disabled: disabled || fieldDisabled || isAutoColumn,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
disabled || fieldDisabled || isAutoColumn
|
validator,
|
||||||
),
|
},
|
||||||
fieldApi: makeFieldApi(field, defaultValue),
|
fieldApi: makeFieldApi(field, defaultValue),
|
||||||
fieldSchema: schema?.[field] ?? {},
|
fieldSchema: schema?.[field] ?? {},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add this field
|
||||||
|
fields = [...fields, fieldInfo]
|
||||||
|
|
||||||
|
return fieldInfo
|
||||||
|
},
|
||||||
|
validate: (onlyCurrentStep = false) => {
|
||||||
|
// Validate only the current step if required
|
||||||
|
if (onlyCurrentStep) {
|
||||||
|
const stepFields = fields.filter(f => get(f).step === get(currentStep))
|
||||||
|
for (let field of stepFields) {
|
||||||
|
if (!get(field).fieldApi.validate()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value
|
// Otherwise validate all fields
|
||||||
const initialValue = get(fieldMap[field].fieldState).value
|
for (let field of fields) {
|
||||||
formState.update(state => ({
|
if (!get(field).fieldApi.validate()) {
|
||||||
...state,
|
return false
|
||||||
values: {
|
}
|
||||||
...state.values,
|
}
|
||||||
[field]: initialValue,
|
return true
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
return fieldMap[field]
|
|
||||||
},
|
|
||||||
validate: () => {
|
|
||||||
const fields = Object.keys(fieldMap)
|
|
||||||
fields.forEach(field => {
|
|
||||||
const { fieldApi } = fieldMap[field]
|
|
||||||
fieldApi.validate()
|
|
||||||
})
|
|
||||||
return get(formState).valid
|
|
||||||
},
|
},
|
||||||
clear: () => {
|
clear: () => {
|
||||||
const fields = Object.keys(fieldMap)
|
// Clear the form by clearing each individual field
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
const { fieldApi } = fieldMap[field]
|
get(field).fieldApi.clearValue()
|
||||||
fieldApi.clearValue()
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
nextStep: () => {
|
nextStep: () => {
|
||||||
formState.update(state => ({
|
currentStep.update(step => step + 1)
|
||||||
...state,
|
|
||||||
step: state.step + 1,
|
|
||||||
}))
|
|
||||||
},
|
},
|
||||||
prevStep: () => {
|
prevStep: () => {
|
||||||
formState.update(state => ({
|
currentStep.update(step => Math.max(1, step - 1))
|
||||||
...state,
|
|
||||||
step: Math.max(1, state.step - 1),
|
|
||||||
}))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide both form API and state to children
|
|
||||||
setContext("form", { formApi, formState, dataSource })
|
|
||||||
|
|
||||||
// Action context to pass to children
|
|
||||||
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
|
// Creates an API for a specific field
|
||||||
const makeFieldApi = field => {
|
const makeFieldApi = field => {
|
||||||
// Sets the value for a certain field and invokes validation
|
// Sets the value for a certain field and invokes validation
|
||||||
const setValue = (value, skipCheck = false) => {
|
const setValue = (value, skipCheck = false) => {
|
||||||
const { fieldState } = fieldMap[field]
|
const fieldInfo = getField(field)
|
||||||
const { validator } = get(fieldState)
|
const { fieldState } = get(fieldInfo)
|
||||||
|
const { validator } = fieldState
|
||||||
|
|
||||||
// Skip if the value is the same
|
// Skip if the value is the same
|
||||||
if (!skipCheck && get(fieldState).value === value) {
|
if (!skipCheck && fieldState.value === value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update field state
|
// Update field state
|
||||||
const error = validator ? validator(value) : null
|
const error = validator ? validator(value) : null
|
||||||
fieldState.update(state => {
|
fieldInfo.update(state => {
|
||||||
state.value = value
|
state.fieldState.value = value
|
||||||
state.error = error
|
state.fieldState.error = error
|
||||||
return state
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update form state
|
|
||||||
formState.update(state => {
|
|
||||||
state.values = { ...state.values, [field]: value }
|
|
||||||
if (error) {
|
|
||||||
state.errors = { ...state.errors, [field]: error }
|
|
||||||
} else {
|
|
||||||
delete state.errors[field]
|
|
||||||
}
|
|
||||||
state.valid = Object.keys(state.errors).length === 0
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -149,30 +172,23 @@
|
||||||
|
|
||||||
// Clears the value of a certain field back to the initial value
|
// Clears the value of a certain field back to the initial value
|
||||||
const clearValue = () => {
|
const clearValue = () => {
|
||||||
const { fieldState } = fieldMap[field]
|
const fieldInfo = getField(field)
|
||||||
const { defaultValue } = get(fieldState)
|
const { fieldState } = get(fieldInfo)
|
||||||
const newValue = initialValues[field] ?? defaultValue
|
const newValue = initialValues[field] ?? fieldState.defaultValue
|
||||||
|
|
||||||
// Update field state
|
// Update field state
|
||||||
fieldState.update(state => {
|
fieldInfo.update(state => {
|
||||||
state.value = newValue
|
state.fieldState.value = newValue
|
||||||
state.error = null
|
state.fieldState.error = null
|
||||||
return state
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update form state
|
|
||||||
formState.update(state => {
|
|
||||||
state.values = { ...state.values, [field]: newValue }
|
|
||||||
delete state.errors[field]
|
|
||||||
state.valid = Object.keys(state.errors).length === 0
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the validator rules for a certain field
|
// Updates the validator rules for a certain field
|
||||||
const updateValidation = validationRules => {
|
const updateValidation = validationRules => {
|
||||||
const { fieldState } = fieldMap[field]
|
const fieldInfo = getField(field)
|
||||||
const { value, error } = get(fieldState)
|
const { fieldState } = get(fieldInfo)
|
||||||
|
const { value, error } = fieldState
|
||||||
|
|
||||||
// Create new validator
|
// Create new validator
|
||||||
const schemaConstraints = schema?.[field]?.constraints
|
const schemaConstraints = schema?.[field]?.constraints
|
||||||
|
@ -184,8 +200,8 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update validator
|
// Update validator
|
||||||
fieldState.update(state => {
|
fieldInfo.update(state => {
|
||||||
state.validator = validator
|
state.fieldState.validator = validator
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -201,63 +217,46 @@
|
||||||
clearValue,
|
clearValue,
|
||||||
updateValidation,
|
updateValidation,
|
||||||
validate: () => {
|
validate: () => {
|
||||||
const { fieldState } = fieldMap[field]
|
// Validate the field by force setting the same value again
|
||||||
setValue(get(fieldState).value, true)
|
const { fieldState } = get(getField(field))
|
||||||
|
return setValue(fieldState.value, true)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates observable state data about a specific field
|
// Provide form state and api for full control by children
|
||||||
const makeFieldState = (field, validator, defaultValue, fieldDisabled) => {
|
setContext("form", {
|
||||||
return writable({
|
formState,
|
||||||
field,
|
formApi,
|
||||||
fieldId: `id-${generateID()}`,
|
|
||||||
value: initialValues[field] ?? defaultValue,
|
|
||||||
error: null,
|
|
||||||
disabled: fieldDisabled,
|
|
||||||
defaultValue,
|
|
||||||
validator,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetches the form schema from this form's dataSource, if one exists
|
// Data source is needed by attachment fields to be able to upload files
|
||||||
const fetchSchema = async () => {
|
// to the correct table ID
|
||||||
if (!dataSource?.tableId) {
|
dataSource,
|
||||||
schema = {}
|
})
|
||||||
table = null
|
|
||||||
} else {
|
|
||||||
table = await API.fetchTableDefinition(dataSource?.tableId)
|
|
||||||
if (table) {
|
|
||||||
if (dataSource?.type === "query") {
|
|
||||||
schema = {}
|
|
||||||
const params = table.parameters || []
|
|
||||||
params.forEach(param => {
|
|
||||||
schema[param.name] = { ...param, type: "string" }
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
schema = table.schema || {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the form schema on mount
|
// Provide form step context so that forms without any step components
|
||||||
onMount(fetchSchema)
|
// register their fields to step 1
|
||||||
|
setContext("form-step", 1)
|
||||||
|
|
||||||
|
// Action context to pass to children
|
||||||
|
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 },
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Provider
|
<Provider
|
||||||
{actions}
|
{actions}
|
||||||
data={{
|
data={{
|
||||||
...$formState.values,
|
...$values,
|
||||||
tableId: dataSource?.tableId,
|
valid,
|
||||||
valid: $formState.valid,
|
currentStep: $currentStep,
|
||||||
step: $formState.step,
|
currentStepValid: $currentStepValid,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
{#if loaded}
|
<slot />
|
||||||
<slot />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -25,11 +25,11 @@
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<CoreTextArea
|
<CoreTextArea
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
on:change={e => fieldApi.setValue(e.detail)}
|
on:change={e => fieldApi.setValue(e.detail)}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -77,10 +77,10 @@
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
{#if !optionsType || optionsType === "select"}
|
{#if !optionsType || optionsType === "select"}
|
||||||
<CoreSelect
|
<CoreSelect
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
{options}
|
{options}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
on:change={e => fieldApi.setValue(e.detail)}
|
on:change={e => fieldApi.setValue(e.detail)}
|
||||||
|
@ -90,10 +90,10 @@
|
||||||
/>
|
/>
|
||||||
{:else if optionsType === "radio"}
|
{:else if optionsType === "radio"}
|
||||||
<CoreRadioGroup
|
<CoreRadioGroup
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
{options}
|
{options}
|
||||||
on:change={e => fieldApi.setValue(e.detail)}
|
on:change={e => fieldApi.setValue(e.detail)}
|
||||||
getOptionLabel={flatOptions ? x => x : x => x.label}
|
getOptionLabel={flatOptions ? x => x : x => x.label}
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
$: linkedTableId = fieldSchema?.tableId
|
$: linkedTableId = fieldSchema?.tableId
|
||||||
$: fetchRows(linkedTableId)
|
$: fetchRows(linkedTableId)
|
||||||
$: fetchTable(linkedTableId)
|
$: fetchTable(linkedTableId)
|
||||||
$: singleValue = flatten($fieldState?.value)?.[0]
|
$: singleValue = flatten(fieldState?.value)?.[0]
|
||||||
$: multiValue = flatten($fieldState?.value) ?? []
|
$: multiValue = flatten(fieldState?.value) ?? []
|
||||||
$: component = multiselect ? CoreMultiselect : CoreSelect
|
$: component = multiselect ? CoreMultiselect : CoreSelect
|
||||||
|
|
||||||
const fetchTable = async id => {
|
const fetchTable = async id => {
|
||||||
|
@ -81,9 +81,9 @@
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
value={multiselect ? multiValue : singleValue}
|
value={multiselect ? multiValue : singleValue}
|
||||||
on:change={multiselect ? multiHandler : singleHandler}
|
on:change={multiselect ? multiHandler : singleHandler}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
getOptionLabel={getDisplayName}
|
getOptionLabel={getDisplayName}
|
||||||
getOptionValue={option => option._id}
|
getOptionValue={option => option._id}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
|
|
|
@ -26,12 +26,12 @@
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<CoreTextField
|
<CoreTextField
|
||||||
updateOnChange={false}
|
updateOnChange={true}
|
||||||
value={$fieldState.value}
|
value={fieldState.value}
|
||||||
on:change={e => fieldApi.setValue(e.detail)}
|
on:change={e => fieldApi.setValue(e.detail)}
|
||||||
disabled={$fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
error={$fieldState.error}
|
error={fieldState.error}
|
||||||
id={$fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{type}
|
{type}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue