Allow fields to dynamically update validation rules so that other fields can be referenced

This commit is contained in:
Andrew Kingston 2021-08-11 15:07:06 +01:00
parent cebfcb280b
commit 99cb7c8789
2 changed files with 47 additions and 6 deletions

View File

@ -1,7 +1,7 @@
<script>
import Placeholder from "../Placeholder.svelte"
import FieldGroupFallback from "./FieldGroupFallback.svelte"
import { getContext } from "svelte"
import { getContext, onMount } from "svelte"
export let label
export let field
@ -34,6 +34,9 @@
fieldApi = formField?.fieldApi
fieldSchema = formField?.fieldSchema
// Keep validation rules up to date
$: fieldApi?.updateValidation(validation)
// Extract label position from field group context
$: labelPositionClass =
labelPosition === "above" ? "" : `spectrum-FieldLabel--${labelPosition}`

View File

@ -36,7 +36,7 @@
// Create validation function based on field schema
const schemaConstraints = schema?.[field]?.constraints
const validate = createValidatorFromConstraints(
const validator = createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
@ -47,10 +47,11 @@
fieldMap[field] = {
fieldState: makeFieldState(
field,
validator,
defaultValue,
disabled || fieldDisabled || isAutoColumn
),
fieldApi: makeFieldApi(field, defaultValue, validate),
fieldApi: makeFieldApi(field, defaultValue),
fieldSchema: schema?.[field] ?? {},
}
@ -93,9 +94,11 @@
]
// Creates an API for a specific field
const makeFieldApi = (field, defaultValue, validate) => {
const makeFieldApi = field => {
// Sets the value for a certain field and invokes validation
const setValue = (value, skipCheck = false) => {
const { fieldState } = fieldMap[field]
const { defaultValue, validator } = get(fieldState)
// Skip if the value is the same
if (!skipCheck && get(fieldState).value === value) {
@ -103,7 +106,7 @@
}
const newValue = value == null ? defaultValue : value
const newError = validate ? validate(newValue) : null
const newError = validator ? validator(newValue) : null
// Update field state
fieldState.update(state => {
@ -127,15 +130,20 @@
return !newError
}
// Clears the value of a certain field back to the initial value
const clearValue = () => {
const { fieldState } = fieldMap[field]
const { defaultValue } = get(fieldState)
const newValue = initialValues[field] ?? defaultValue
// Update field state
fieldState.update(state => {
state.value = newValue
state.error = null
return state
})
// Update form state
formState.update(state => {
state.values = { ...state.values, [field]: newValue }
delete state.errors[field]
@ -144,9 +152,37 @@
})
}
// Updates the validator rules for a certain field
const updateValidation = validationRules => {
const { fieldState } = fieldMap[field]
const { value, error } = get(fieldState)
// Create new validator
const schemaConstraints = schema?.[field]?.constraints
const validator = createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
// Update validator
fieldState.update(state => {
state.validator = validator
return state
})
// If there is currently an error, run the validator again in case
// the error should be cleared by the new validation rules
if (error) {
setValue(value, true)
}
}
return {
setValue,
clearValue,
updateValidation,
validate: () => {
const { fieldState } = fieldMap[field]
setValue(get(fieldState).value, true)
@ -155,13 +191,15 @@
}
// Creates observable state data about a specific field
const makeFieldState = (field, defaultValue, fieldDisabled) => {
const makeFieldState = (field, validator, defaultValue, fieldDisabled) => {
return writable({
field,
fieldId: `id-${generateID()}`,
value: initialValues[field] ?? defaultValue,
error: null,
disabled: fieldDisabled,
defaultValue,
validator,
})
}