Add form support for fields which can only use bindings rather than explicit onchange handlers

This commit is contained in:
Andrew Kingston 2021-02-01 11:14:24 +00:00
parent 4630699f13
commit c9bf2700e3
5 changed files with 53 additions and 48 deletions

View File

@ -1,27 +1,28 @@
<script> <script>
import SpectrumField from "./SpectrumField.svelte" import SpectrumField from "./SpectrumField.svelte"
import Dropzone from "../attachments/Dropzone.svelte" import Dropzone from "../attachments/Dropzone.svelte"
import { onMount } from "svelte"
export let field export let field
export let label export let label
let previousFiles = []
let files = []
$: {
// Only actually update the value when it changes, so that we don't trigger
// validation unnecessarily
if (files !== previousFiles) {
fieldApi?.setValue(files)
previousFiles = files
}
}
let fieldState let fieldState
let fieldApi let fieldApi
// Update form value from bound value after we've mounted
let value
let mounted = false
$: mounted && fieldApi?.setValue(value)
// Get the fields initial value after initialising
onMount(() => {
value = $fieldState?.value
mounted = true
})
</script> </script>
<SpectrumField {label} {field} bind:fieldState bind:fieldApi> <SpectrumField {label} {field} bind:fieldState bind:fieldApi defaultValue={[]}>
{#if fieldState} {#if mounted}
<Dropzone bind:files /> <Dropzone bind:files={value} />
{/if} {/if}
</SpectrumField> </SpectrumField>

View File

@ -13,18 +13,19 @@
const onChange = event => { const onChange = event => {
fieldApi.setValue(event.target.checked) fieldApi.setValue(event.target.checked)
} }
// Ensure a valid boolean value is set
onMount(() => {
fieldApi.setValue($fieldState.value || false)
})
</script> </script>
<SpectrumField {label} {field} bind:fieldState bind:fieldApi> <SpectrumField
{label}
{field}
bind:fieldState
bind:fieldApi
defaultValue={false}>
{#if fieldState} {#if fieldState}
<div class="spectrum-FieldGroup spectrum-FieldGroup--horizontal"> <div class="spectrum-FieldGroup spectrum-FieldGroup--horizontal">
<label class="spectrum-Checkbox" class:is-invalid={!$fieldState.valid}> <label class="spectrum-Checkbox" class:is-invalid={!$fieldState.valid}>
<input <input
checked={$fieldState.value}
on:change={onChange} on:change={onChange}
type="checkbox" type="checkbox"
class="spectrum-Checkbox-input" class="spectrum-Checkbox-input"

View File

@ -34,7 +34,7 @@
// Form API contains functions to control the form // Form API contains functions to control the form
const formApi = { const formApi = {
registerField: field => { registerField: (field, defaultValue = null) => {
if (!field) { if (!field) {
return return
} }
@ -47,8 +47,8 @@
const validate = createValidatorFromConstraints(constraints, field, table) const validate = createValidatorFromConstraints(constraints, field, table)
fieldMap[field] = { fieldMap[field] = {
fieldState: makeFieldState(field), fieldState: makeFieldState(field, defaultValue),
fieldApi: makeFieldApi(field, validate), fieldApi: makeFieldApi(field, defaultValue, validate),
fieldSchema: schema?.[field] ?? {}, fieldSchema: schema?.[field] ?? {},
} }
fieldMap = fieldMap fieldMap = fieldMap
@ -60,13 +60,16 @@
setContext("form", { formApi, formState }) setContext("form", { formApi, formState })
// Creates an API for a specific field // Creates an API for a specific field
const makeFieldApi = (field, validate) => { const makeFieldApi = (field, defaultValue, validate) => {
return { return {
setValue: value => { setValue: value => {
const { fieldState } = fieldMap[field] const { fieldState } = fieldMap[field]
fieldState.update(state => { fieldState.update(state => {
state.value = value if (state.value === value) {
state.error = validate ? validate(value) : null return state
}
state.value = value == null ? defaultValue : value
state.error = validate ? validate(state.value) : null
state.valid = !state.error state.valid = !state.error
return state return state
}) })
@ -76,13 +79,13 @@
} }
// Creates observable state data about a specific field // Creates observable state data about a specific field
const makeFieldState = field => { const makeFieldState = (field, defaultValue) => {
return writable({ return writable({
field, field,
fieldId: `${Math.random() fieldId: `${Math.random()
.toString(32) .toString(32)
.substr(2)}/${field}`, .substr(2)}/${field}`,
value: initialValues[field] ?? null, value: initialValues[field] ?? defaultValue,
error: null, error: null,
valid: true, valid: true,
}) })

View File

@ -1,4 +1,5 @@
<script> <script>
import { onMount } from "svelte"
import { RichText } from "@budibase/bbui" import { RichText } from "@budibase/bbui"
import SpectrumField from "./SpectrumField.svelte" import SpectrumField from "./SpectrumField.svelte"
@ -8,17 +9,17 @@
let fieldState let fieldState
let fieldApi let fieldApi
let previousValue = ""
let value = ""
$: { // Update form value from bound value after we've mounted
// Only actually update the value when it changes, so that we don't trigger let value
// validation unnecessarily let mounted = false
if (value !== previousValue) { $: mounted && fieldApi?.setValue(value)
fieldApi?.setValue(value)
previousValue = value // Get the fields initial value after initialising
} onMount(() => {
} value = $fieldState?.value
mounted = true
})
// Options for rich text component // Options for rich text component
const options = { const options = {
@ -37,8 +38,8 @@
} }
</script> </script>
<SpectrumField {label} {field} bind:fieldState bind:fieldApi> <SpectrumField {label} {field} bind:fieldState bind:fieldApi defaultValue="">
{#if fieldState} {#if mounted}
<div> <div>
<RichText bind:value {options} /> <RichText bind:value {options} />
</div> </div>

View File

@ -8,6 +8,7 @@
export let fieldState export let fieldState
export let fieldApi export let fieldApi
export let fieldSchema export let fieldSchema
export let defaultValue
// Get contexts // Get contexts
const formContext = getContext("form") const formContext = getContext("form")
@ -16,16 +17,14 @@
const component = getContext("component") const component = getContext("component")
// Register field with form // Register field with form
$: formApi = formContext?.formApi const formApi = formContext?.formApi
$: labelPosition = fieldGroupContext?.labelPosition || "above" const labelPosition = fieldGroupContext?.labelPosition || "above"
$: formField = formApi?.registerField(field) const formField = formApi?.registerField(field, defaultValue)
// 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
}
// Extract label position from field group context // Extract label position from field group context
$: labelPositionClass = $: labelPositionClass =