2021-01-26 09:55:44 +01:00
|
|
|
<script>
|
2021-01-27 11:59:05 +01:00
|
|
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
2021-01-26 09:55:44 +01:00
|
|
|
import { setContext, getContext, onMount } from "svelte"
|
|
|
|
import { writable, get } from "svelte/store"
|
2021-01-26 15:40:44 +01:00
|
|
|
import { createValidatorFromConstraints } from "./validation"
|
2021-02-01 15:10:38 +01:00
|
|
|
import { generateID } from "../helpers"
|
2021-01-26 09:55:44 +01:00
|
|
|
|
|
|
|
export let datasource
|
|
|
|
export let theme
|
|
|
|
export let size
|
|
|
|
|
2021-01-29 14:22:38 +01:00
|
|
|
const { styleable, API, setBindableValue, DataProvider } = getContext("sdk")
|
2021-01-26 09:55:44 +01:00
|
|
|
const component = getContext("component")
|
2021-01-29 17:40:19 +01:00
|
|
|
const dataContext = getContext("data")
|
2021-01-26 09:55:44 +01:00
|
|
|
|
|
|
|
let loaded = false
|
2021-01-26 15:40:44 +01:00
|
|
|
let schema
|
|
|
|
let table
|
2021-01-26 09:55:44 +01:00
|
|
|
let fieldMap = {}
|
|
|
|
|
2021-01-29 17:40:19 +01:00
|
|
|
// Checks if the closest data context matches the model for this forms
|
|
|
|
// datasource, and use it as the initial form values if so
|
|
|
|
const getInitialValues = context => {
|
|
|
|
return context && context.tableId === datasource?.tableId ? context : {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use the closest data context as the initial form values if it matches
|
|
|
|
const initialValues = getInitialValues(
|
|
|
|
$dataContext[$dataContext.closestComponentId]
|
|
|
|
)
|
|
|
|
|
2021-01-26 09:55:44 +01:00
|
|
|
// Form state contains observable data about the form
|
2021-01-29 17:40:19 +01:00
|
|
|
const formState = writable({ values: initialValues, errors: {}, valid: true })
|
2021-01-28 17:31:55 +01:00
|
|
|
$: updateFormState(fieldMap)
|
2021-01-26 09:55:44 +01:00
|
|
|
|
|
|
|
// Form API contains functions to control the form
|
|
|
|
const formApi = {
|
2021-02-01 12:14:24 +01:00
|
|
|
registerField: (field, defaultValue = null) => {
|
2021-01-26 09:55:44 +01:00
|
|
|
if (!field) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (fieldMap[field] != null) {
|
|
|
|
return fieldMap[field]
|
|
|
|
}
|
2021-01-26 15:40:44 +01:00
|
|
|
|
|
|
|
// Create validation function based on field schema
|
|
|
|
const constraints = schema?.[field]?.constraints
|
|
|
|
const validate = createValidatorFromConstraints(constraints, field, table)
|
|
|
|
|
2021-01-26 09:55:44 +01:00
|
|
|
fieldMap[field] = {
|
2021-02-01 12:14:24 +01:00
|
|
|
fieldState: makeFieldState(field, defaultValue),
|
|
|
|
fieldApi: makeFieldApi(field, defaultValue, validate),
|
2021-01-27 11:59:05 +01:00
|
|
|
fieldSchema: schema?.[field] ?? {},
|
2021-01-26 09:55:44 +01:00
|
|
|
}
|
|
|
|
fieldMap = fieldMap
|
|
|
|
return fieldMap[field]
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Provide both form API and state to children
|
2021-01-27 19:25:57 +01:00
|
|
|
setContext("form", { formApi, formState })
|
2021-01-26 09:55:44 +01:00
|
|
|
|
|
|
|
// Creates an API for a specific field
|
2021-02-01 12:14:24 +01:00
|
|
|
const makeFieldApi = (field, defaultValue, validate) => {
|
2021-01-26 09:55:44 +01:00
|
|
|
return {
|
|
|
|
setValue: value => {
|
|
|
|
const { fieldState } = fieldMap[field]
|
|
|
|
fieldState.update(state => {
|
2021-02-01 12:14:24 +01:00
|
|
|
if (state.value === value) {
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
state.value = value == null ? defaultValue : value
|
|
|
|
state.error = validate ? validate(state.value) : null
|
2021-01-26 09:55:44 +01:00
|
|
|
state.valid = !state.error
|
|
|
|
return state
|
|
|
|
})
|
|
|
|
fieldMap = fieldMap
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates observable state data about a specific field
|
2021-02-01 12:14:24 +01:00
|
|
|
const makeFieldState = (field, defaultValue) => {
|
2021-01-26 09:55:44 +01:00
|
|
|
return writable({
|
|
|
|
field,
|
2021-02-01 15:10:38 +01:00
|
|
|
fieldId: `id-${generateID()}-${field}`,
|
2021-02-01 12:14:24 +01:00
|
|
|
value: initialValues[field] ?? defaultValue,
|
2021-01-26 09:55:44 +01:00
|
|
|
error: null,
|
|
|
|
valid: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Updates the form states from the field data
|
|
|
|
const updateFormState = fieldMap => {
|
2021-01-29 17:40:19 +01:00
|
|
|
let values = { ...initialValues }
|
2021-01-26 09:55:44 +01:00
|
|
|
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 = {}
|
2021-01-26 15:40:44 +01:00
|
|
|
table = null
|
2021-01-26 09:55:44 +01:00
|
|
|
} else {
|
2021-01-26 15:40:44 +01:00
|
|
|
table = await API.fetchTableDefinition(datasource?.tableId)
|
2021-01-26 09:55:44 +01:00
|
|
|
if (table) {
|
|
|
|
if (datasource.type === "query") {
|
2021-01-26 15:40:44 +01:00
|
|
|
console.log("No implementation for queries yet")
|
|
|
|
schema = {}
|
2021-01-26 09:55:44 +01:00
|
|
|
} else {
|
|
|
|
schema = table.schema || {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loaded = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the form schema on mount
|
|
|
|
onMount(fetchSchema)
|
|
|
|
</script>
|
|
|
|
|
2021-01-29 14:22:38 +01:00
|
|
|
<DataProvider row={{ ...$formState.values, tableId: datasource?.tableId }}>
|
|
|
|
<div
|
|
|
|
lang="en"
|
|
|
|
dir="ltr"
|
|
|
|
use:styleable={$component.styles}
|
|
|
|
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
|
|
|
|
{#if loaded}
|
|
|
|
<slot />
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
</DataProvider>
|
2021-01-29 17:40:19 +01:00
|
|
|
|
|
|
|
<style>
|
|
|
|
div {
|
|
|
|
padding: 20px;
|
|
|
|
}
|
|
|
|
</style>
|