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#

  1. One render primitive SchemaForm only renders a schema-driven form and reports changes via onChange and onSubmit. 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.

  2. 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).

  3. 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).

  4. 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.

  5. 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.tsx

  • Role: Renders an RJSF form from schema, formData, and optional uiSchema. Calls onChange when the user edits and onSubmit when the form is submitted (e.g. hidden submit button). Accepts formContext for custom fields and optional extraErrors, submitButtonRef, etc. Does not call syncData or close dialogs; the parent does that in the callbacks.

useSchemaFormState#

  • Location: packages/base/src/formbuilder/objectform/useSchemaFormState.ts

  • Role: Hook that owns form state and common wiring. Given sourceData, schemaProp, and model, it returns:

    • formData, setFormData (state synced from sourceData when it changes),

    • schema (deep copy of schemaProp),

    • formContextValue ({ model, formData } for SchemaForm),

    • hasSchema (whether to render or return null),

    • and, when syncData (and optionally cancel, onAfterChange) are passed: handleChangeBase and handleSubmitBase.

    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.ts

  • Role: removeFormEntry removes a property from form data, schema, and uiSchema. processBaseSchema applies shared behaviour for array options, opacity field, read-only handling, and nested object handling. Type forms call these when building schema and uiSchema in useMemo.

Type form components#

Each layer or source type has a function component (e.g. vectorlayerform.tsx, geojsonsource.tsx) that:

  1. Calls useSchemaFormState with the right props.

  2. Builds uiSchema in a useMemo using processBaseSchema, removeFormEntry, and type-specific logic (e.g. source dropdown enum, custom widgets, hidden fields).

  3. Uses handleChangeBase and handleSubmitBase from the hook, or wraps them (e.g. path-based and GeoJSON sources add path/URL validation).

  4. 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) and getSourceTypeForm(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 syncData so 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’s onSubmit runs; it may validate and then calls syncData.