Editor rendering
The form is not written per profile. It is rendered from the profile's JSON Schema and the document's current value, so a new field or profile needs a schema change and no Vue change. This page follows a schema node from the profile to the component on screen. The code is in app/sidedoc-frontend/src/components/editor/.
The pieces
| File | Role |
|---|---|
EditorNode.vue | Recursive entry point. Renders one value at one path. |
useUnionField.ts | Renders oneOf/anyOf unions with a branch selector. |
useValueFieldRenderer.ts | renderValueField: one field (schema node plus value) to a component. |
renderers/ | Structural renderers: ObjectField, ArrayField, PrimitiveField and the row lists for arrays of each type (TermArrayRows, AgentArrayRows, UrlArrayRows, ...). |
types/ | One component per type-library type: LiteralTypeField, RichTextTypeField, UrlTypeField, TermTypeField, EnumTypeField, AgentTypeField, ReferenceTypeField, TypedValueTypeField, DateTypeField, DateTimeTypeField, QualifiedRelationTypeField. |
ValueTreePanel.vue, ValueTreeNode.vue | The outline tree next to the form, built with the backend's createValueTreeAdapter. |
pathFieldId.ts, fieldAccessibility.js | Stable DOM ids per path, and label, help and error ids and ARIA attributes for controls. |
langMap.ts | Read and write one language of a language-map value. |
The editor context
Every renderer reads its services from an injected editorCtx and throws if it is missing. WorkspaceEditorView.vue provides it (BackendDemo.vue and the testbench harness provide their own). Most entries forward to the ShadowApplicationService of the current document's profile:
- schema lookup:
resolveSchemaNode,getSchemaForPath,getMinOccurrences,getMaxOccurrences,getUnionList,getUnionLabel,matchUnionIndex,getAllowedSchemas,getArrayPatterns,isUrlLikeItem; - values:
createEmptyFromSchema,hasMeaningfulContent,getByPath,getPersistedByPath,isFieldPersistedPresent,cloneValue; - edits:
updateShadowAtPath,deleteShadowAtPath,addArrayItemAtPath,removeArrayItemAtPath; - language:
selectedLanguage,availableLanguages,pickTranslation; - help and validation:
selectHelpFieldand the validation lookups used byfieldAccessibility.js.
Edits go through the editor session (createEditorSessionAdapter in the backend): the shadow is patched with applyPatch, then the change is stored with documentService.patchDocumentByPath, and IndexedDB is updated through the workspace.
resolveSchemaNode(node) returns { node, refName }: the schema node with $refs resolved against the type library, and the name of the type-library definition it points to (for example Literal for .../type-library.json#/$defs/Literal). Most dispatch decisions use refName.
From schema node to component
EditorNode receives value, path, label and schemaNode and picks a renderer in this order:
- Union.
renderUnionFieldasks the backend's union adapter whether the node is a union. If it is, it renders a selector for the branches and the editor for the selected branch. Typed drafts are kept per branch, so switching back restores what the user typed. - RichText. If the schema resolves to
RichText, or the value has amarkDownpart,RichTextTypeField(a Toast UI Markdown editor) is used. - Array values go to
ArrayField. - Object values go to
ObjectField. - Anything else goes to
PrimitiveField.
ObjectField renders a known type component when the object is a type-library type (by refName, by x-sidedoc.type, or by shape, for example an object with href is a Url). Otherwise it renders a sub-form: one row per property, labelled from x-sidedoc.labelSingularEn, with required fields shown and optional ones behind "add" buttons (small optional sections are expanded straight away). Each property is rendered with renderValueField.
ArrayField looks at the item schema and picks a row renderer: MultilangStringArrayRows for flat Literal lists, DateArrayRows, RichTextArrayRows, UrlArrayRows, AgentArrayRows, ReferenceArrayRows, TermArrayRows, QualifiedRelationArrayRows, TypedArrayField (a list of one type component, used for TypedValue, multiline Literal and other type-library types), StringArrayRows, or ComplexObjectArrayRows and ObjectTable for objects. Add and remove buttons respect minOccurrences and maxOccurrences.
renderValueField(path, schema, value, label, ...) in useValueFieldRenderer.ts is the per-field switch:
union (via
renderUnionField);a type component from this map, keyed by
refName:refNameComponent LiteralLiteralTypeFieldRichTextRichTextTypeFieldUrlUrlTypeFieldTermTermTypeFieldEnumEnumTypeFieldAgentAgentTypeFieldReferenceReferenceTypeFieldTypedValueTypedValueTypeFieldDateDateTypeFieldDateTimeDateTimeTypeFieldQualifiedRelationQualifiedRelationTypeFieldTypedValue or RichText detected by shape;
string,numberandintegerbecome atextarea, numberinputorselect(for enums);arrays and objects with structured children recurse into
EditorNode;remaining flat objects render one input per property.
Type components receive value, path, label, readOnly, schemaNode and renderValueField (for nested values) and write back through editorCtx.updateShadowAtPath.
x-sidedoc keywords
Every field in a generated profile has an x-sidedoc block. Example, the datasheet title:
"title": {
"$ref": "https://sidedoc.app/schemas/0.2.2/type-library.json#/$defs/Literal",
"x-sidedoc": {
"id": "ds:f00100",
"labelSingularEn": "Title",
"optionalFreeText": false,
"multilingual": true,
"minOccurrences": 1,
"maxOccurrences": 1,
"type": "Literal",
"isTitle": true
}
}What the code reads:
| Key | Used for |
|---|---|
id | Stable field id (ds:f00100). Help texts and DCAT mapping rules are keyed on it. |
labelSingularEn, labelPluralEn | Field labels in the form, outline and exports. |
minOccurrences, maxOccurrences | Required structure in new documents, add and remove buttons, validation. |
multilingual | Language-map editing; validation warns when English is missing. A single-valued multilingual field may still hold several languages. |
type | Type hint when the schema has no $ref to the type library. |
isTitle | Renders the field in the larger title style. |
uiHint | multiline gives a Literal a multi-line text area (and a list of them in TypedArrayField). doi, identifier and identifier_agent switch TypedValueTypeField modes. A hint containing mail makes UrlTypeField an e-mail field. Other values in the schemas (term_lookup, enum_select, url, date_picker, checksum, enum_lookup) are not read by name; those fields render from their type. |
dateMode | single, interval or both for DateTypeField. |
vocab | Vocabulary binding for Term and Enum fields: kind, schemes, enforcement (open or closed for scheme and value) and allowCustomLabel. |
allowCustomTerm | false hides free-text entry in a Term field. |
allowedSchemas | Identifier schemes offered by TypedValueTypeField (for example DOI, ORCID). |
caption | Label template for Agent and Reference summaries. |
optionalFreeText, unionTypes, validationPattern | Shown in the field help (details level). The actual pattern check is the JSON Schema pattern. |
On the profile root, x-sidedoc also carries the profile id (ds:v00003), the version and, optionally, docsSchemaUrl naming the help file.
Adding a field type
- Add the definition to the type library in the schemas repository.
- Write
types/MyTypeField.vue. Accept the props listed above and write changes witheditorCtx.updateShadowAtPath. UsefieldAccessibility.jsfor ids and ARIA attributes. - Register it in the
typeComponentsmap inuseValueFieldRenderer.ts, and inObjectField.vueif objects of this type can appear as sub-form properties. - If lists of it need their own layout, add a row renderer and a branch in
ArrayField.vue. - Add normalization for the new value shape in the backend if old documents need it, and an export rule in the DCAT mapping.
- Add a testbench descriptor and scenarios, and cover the component in the accessibility suite.