Form system#
This document describes how layer, source, processing, and story-editor forms work, including how they are built, who owns state, and how data flows.
What the form system does#
The form system powers:
Layer and source creation/editing (e.g. add layer, edit layer properties, add source, edit source properties).
Processing dialogs (e.g. Dissolve).
Story map editor (e.g. story presentation settings).
All of these use JSON Schema–driven forms (RJSF). The same form component for a given type is used both when creating a new object (e.g. in the layer creation dialog) and when editing an existing one (e.g. in the properties panel). Behaviour differs only by form context ('create' vs 'update'), which affects things like read-only fields and labels.
Design ideas#
One render primitive SchemaForm only renders a schema-driven form and reports changes via
onChangeandonSubmit. It does not own persistence, dialog behaviour, or create/update mode. The parent component owns state and decides what to do on change and submit.Shared state and handlers useSchemaFormState holds form data (synced from
sourceData), builds a copy of the schema, and provides a standard form context. It can also provide base change/submit handlers. Type-specific forms use this hook and either use those handlers directly or wrap them (e.g. to add validation or transform data before submit).Shared schema behaviour schemaUtils (
processBaseSchema,removeFormEntry) adapts the JSON schema and uiSchema before they are passed to SchemaForm (array options, opacity field, read-only handling, hiding fields). Each type form calls these and then applies its own logic (e.g. source enum, custom widgets).One component per type Each layer type, source type, and special form (Dissolve, Story editor, default processing) is a function component in its own file. It composes:
useSchemaFormState(and optionally its base handlers), schemaUtils, type-specific uiSchema/validation, and SchemaForm. There is no shared base class; behaviour is composed from the hook and utilities.Selectors and flows formselectors (
getLayerTypeForm,getSourceTypeForm) choose the right form component by type. CreationForm and EditForm use these selectors and pass a common set of props (schema, sourceData, syncData, model, formContext, etc.). Dialogs and the properties panel use CreationForm or EditForm; they do not talk to individual form components directly.
Main pieces#
SchemaForm#
Location:
packages/base/src/formbuilder/objectform/SchemaForm.tsxRole: Renders an RJSF form from
schema,formData, and optionaluiSchema. CallsonChangewhen the user edits andonSubmitwhen the form is submitted (e.g. hidden submit button). AcceptsformContextfor custom fields and optionalextraErrors,submitButtonRef, etc. Does not callsyncDataor close dialogs; the parent does that in the callbacks.
useSchemaFormState#
Location:
packages/base/src/formbuilder/objectform/useSchemaFormState.tsRole: Hook that owns form state and common wiring. Given
sourceData,schemaProp, andmodel, it returns:formData,setFormData(state synced fromsourceDatawhen it changes),schema(deep copy ofschemaProp),formContextValue({ model, formData }for SchemaForm),hasSchema(whether to render or return null),and, when
syncData(and optionallycancel,onAfterChange) are passed:handleChangeBaseandhandleSubmitBase.
Type forms use the hook and either pass these handlers straight to SchemaForm or wrap them (e.g. run validation, transform payload, update
dialogOptions).
schemaUtils#
Location:
packages/base/src/formbuilder/objectform/schemaUtils.tsRole:
removeFormEntryremoves a property from form data, schema, and uiSchema.processBaseSchemaapplies shared behaviour for array options, opacity field, read-only handling, and nested object handling. Type forms call these when buildingschemaanduiSchemainuseMemo.
Type form components#
Each layer or source type has a function component (e.g. vectorlayerform.tsx, geojsonsource.tsx) that:
Calls useSchemaFormState with the right props.
Builds uiSchema in a
useMemousingprocessBaseSchema,removeFormEntry, and type-specific logic (e.g. source dropdown enum, custom widgets, hidden fields).Uses handleChangeBase and handleSubmitBase from the hook, or wraps them (e.g. path-based and GeoJSON sources add path/URL validation).
Renders SchemaForm with the hook’s
schema,formData,formContextValue, and the chosen change/submit handlers.
The same component is used for create and edit.
Layer forms: layerform.tsx, vectorlayerform.tsx, hillshadeLayerForm.tsx, webGlLayerForm.tsx, heatmapLayerForm.tsx, storySegmentLayerForm.tsx.
Source forms: sourceform.tsx, geojsonsource.tsx, pathbasedsource.tsx, tilesourceform.tsx, geotiffsource.tsx.
Other forms: DefaultProcessingForm (processingForm.tsx), DissolveForm (process/dissolveProcessForm.tsx), StoryEditorPropertiesForm (StoryEditorForm.tsx). These use the same hook and SchemaForm. The processing forms connect the dialog OK button to a programmatic submit via submitButtonRef.
Form selectors and flows#
formselectors.ts exposes
getLayerTypeForm(layerType)andgetSourceTypeForm(sourceType)so callers get the right form component without importing each one.CreationForm (used by the layer/source creation dialog) renders the chosen source and/or layer form, stores latest form data in refs via
syncData, and registers a confirm handler. When the user clicks OK, the dialog invokes that handler and CreationForm reads the refs and calls the model’s add methods.EditForm (used by the properties panel) resolves the layer/source by id, picks the form via the selectors, and passes
syncDataso that each change updates the model (e.g.updateObjectParameters). No OK button; changes apply as you edit.
Sync policy#
Create flow (e.g. layer/source creation dialog): Form data is not written to the model while the dialog is open. On OK, the dialog’s confirm handler runs and CreationForm reads the latest data from refs and calls the model’s add methods.
Edit flow (properties panel): Form data is synced on every change. Each change calls
syncData, which updates the model so the panel always reflects the current state.Processing dialogs (e.g. Dissolve): The dialog OK button triggers a programmatic submit (via
submitButtonRef). The form’sonSubmitruns; it may validate and then callssyncData.