1
0
Fork 0
mirror of synced 2024-06-20 19:30:28 +12:00
budibase/packages/standard-components/src/forms/Form.svelte

133 lines
3.4 KiB
Svelte
Raw Normal View History

<script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
import { setContext, getContext, onMount } from "svelte"
import { writable, get } from "svelte/store"
import { createValidatorFromConstraints } from "./validation"
export let datasource
export let theme
export let size
export let labelPosition = "left"
const { styleable, API } = getContext("sdk")
const component = getContext("component")
let loaded = false
let schema
let table
let fieldMap = {}
// Keep form state up to date with form fields
$: updateFormState(fieldMap)
// Form state contains observable data about the form
const formState = writable({ values: {}, errors: {}, valid: true })
// Form API contains functions to control the form
const formApi = {
registerField: field => {
if (!field) {
return
}
if (fieldMap[field] != null) {
return fieldMap[field]
}
// Create validation function based on field schema
const constraints = schema?.[field]?.constraints
const validate = createValidatorFromConstraints(constraints, field, table)
fieldMap[field] = {
fieldState: makeFieldState(field),
fieldApi: makeFieldApi(field, validate),
fieldSchema: schema?.[field] ?? {},
fieldId: `${Math.random()
.toString(32)
.substr(2)}/${field}`,
}
fieldMap = fieldMap
return fieldMap[field]
},
}
// Provide both form API and state to children
setContext("form", { formApi, formState, labelPosition })
// Creates an API for a specific field
const makeFieldApi = (field, validate) => {
return {
setValue: value => {
const { fieldState } = fieldMap[field]
fieldState.update(state => {
state.value = value
state.error = validate ? validate(value) : null
state.valid = !state.error
return state
})
fieldMap = fieldMap
},
}
}
// Creates observable state data about a specific field
const makeFieldState = field => {
return writable({
field,
value: null,
error: null,
valid: true,
})
}
// Updates the form states from the field data
const updateFormState = fieldMap => {
let values = {}
let errors = {}
Object.entries(fieldMap).forEach(([field, formField]) => {
const fieldState = get(formField.fieldState)
values[field] = fieldState.value
if (fieldState.error) {
errors[field] = fieldState.error
}
})
const valid = Object.keys(errors).length === 0
formState.set({ values, errors, valid })
}
// 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") {
console.log("No implementation for queries yet")
schema = {}
} else {
schema = table.schema || {}
}
}
}
loaded = true
}
// Load the form schema on mount
onMount(fetchSchema)
</script>
<div
lang="en"
dir="ltr"
use:styleable={$component.styles}
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
<form
class="spectrum-Form"
class:spectrum-Form--labelsAbove={labelPosition === 'above'}>
{#if loaded}
<slot />
{/if}
</form>
</div>