Skip to content

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 ​

FileRole
EditorNode.vueRecursive entry point. Renders one value at one path.
useUnionField.tsRenders oneOf/anyOf unions with a branch selector.
useValueFieldRenderer.tsrenderValueField: 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.vueThe outline tree next to the form, built with the backend's createValueTreeAdapter.
pathFieldId.ts, fieldAccessibility.jsStable DOM ids per path, and label, help and error ids and ARIA attributes for controls.
langMap.tsRead 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: selectHelpField and the validation lookups used by fieldAccessibility.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:

  1. Union. renderUnionField asks 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.
  2. RichText. If the schema resolves to RichText, or the value has a markDown part, RichTextTypeField (a Toast UI Markdown editor) is used.
  3. Array values go to ArrayField.
  4. Object values go to ObjectField.
  5. 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:

  1. union (via renderUnionField);

  2. a type component from this map, keyed by refName:

    refNameComponent
    LiteralLiteralTypeField
    RichTextRichTextTypeField
    UrlUrlTypeField
    TermTermTypeField
    EnumEnumTypeField
    AgentAgentTypeField
    ReferenceReferenceTypeField
    TypedValueTypedValueTypeField
    DateDateTypeField
    DateTimeDateTimeTypeField
    QualifiedRelationQualifiedRelationTypeField
  3. TypedValue or RichText detected by shape;

  4. string, number and integer become a textarea, number input or select (for enums);

  5. arrays and objects with structured children recurse into EditorNode;

  6. 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:

json
"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:

KeyUsed for
idStable field id (ds:f00100). Help texts and DCAT mapping rules are keyed on it.
labelSingularEn, labelPluralEnField labels in the form, outline and exports.
minOccurrences, maxOccurrencesRequired structure in new documents, add and remove buttons, validation.
multilingualLanguage-map editing; validation warns when English is missing. A single-valued multilingual field may still hold several languages.
typeType hint when the schema has no $ref to the type library.
isTitleRenders the field in the larger title style.
uiHintmultiline 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.
dateModesingle, interval or both for DateTypeField.
vocabVocabulary binding for Term and Enum fields: kind, schemes, enforcement (open or closed for scheme and value) and allowCustomLabel.
allowCustomTermfalse hides free-text entry in a Term field.
allowedSchemasIdentifier schemes offered by TypedValueTypeField (for example DOI, ORCID).
captionLabel template for Agent and Reference summaries.
optionalFreeText, unionTypes, validationPatternShown 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 ​

  1. Add the definition to the type library in the schemas repository.
  2. Write types/MyTypeField.vue. Accept the props listed above and write changes with editorCtx.updateShadowAtPath. Use fieldAccessibility.js for ids and ARIA attributes.
  3. Register it in the typeComponents map in useValueFieldRenderer.ts, and in ObjectField.vue if objects of this type can appear as sub-form properties.
  4. If lists of it need their own layout, add a row renderer and a branch in ArrayField.vue.
  5. Add normalization for the new value shape in the backend if old documents need it, and an export rule in the DCAT mapping.
  6. Add a testbench descriptor and scenarios, and cover the component in the accessibility suite.