/content-editor

Jahia Content editor React extension.

Primary LanguageJavaScriptMIT LicenseMIT

Jahia logo

content-editor

DX version

Content Editor React extension for Jahia modules.

Requirements

Language (or Domains)

As every project, Content-Editor has is own Language and specific words. Here is the list of the domains and their definition :

  • Create: Content-Editor can create new content
  • Edit: Content-Editor can edit existing content
  • EditPanel: The main panel where we can edit the values of a content
  • Section: A section is a group of FieldSet. For example, a given content can have: Metadata, content, layout, ...
  • FieldSet: A fieldset is a group of Field. Previously called mixin (still the case in OSGI)
  • Field: A field is a composition of selectorType, label and actions
  • SelectorType: A SelectorType is basically the input of the field. It answer the question: how user enter this data ?
  • Preview: The preview is the way to have a view of the current state of your EditPanel
  • Details: The details is the panel that show extra info that cannot be in the EditPanel like the publication status

File structure

The file structure reflect the domain and their relations. The Goal is to stay simple as possible.

We currently have:

  - Create
  - Edit
  - EditPanel
    - EditPanelContent
      - Preview
      - Details
      - FormBuilder
        - Section
          - FieldSet
             - Field
               - SelectorType

And we are targeting to have:

- Create
- Edit
- EditPanel
  - Section
    - FieldSet
      - Field
        - SelectorType
- Preview
- Details

Naming convention

  • Each file should be named with their functional/domain information
  • Each files should respect lowerCamelCase
  • Each files should end with their technical name. Ex: publish.action.js, EditPanel.container.jsx, ...
  • Only component file shouldn't end with their technical name.
  • Only component file should end with .jsx
  • Only component file should respect CamelCase

How to install

  1. Download, build and install dx-commons-webpack
  2. Download, build and install content-media-manager
  3. Build and install this module

Form generation

The content editor module has a GraphQL API to generate forms for content editing. This API is exposed under the "forms" field in the GraphQL Query object type.

The generation of the form is done using the following algorithm.

Please note that the argument for most queries as either a path to an existing node OR a primary node type name. We use the "primary" node type name here as it is not allowed to use "mixin" or inherited node type names in this API, only primary node type names should be used. For example, if creating a new "jnt:news" object, you can simply use that as an argument to retrieve the form, but you should not use "jmix:tags" or any other mixin type. The form generated by the API will then generated sections for each mixin and inherited JCR node types.

For a given primaryNodeType:

  • If a CND definition existing in the JCR, it is used to generate a form definition dynamically.
  • If DX modules define static forms that either override or define new forms, they will be merged in order of priority with first the dynamically generated forms from the JCR definition (if it exists) and then with the static JSON form definitions that have a higher priority.
  • Once this is done, the choicelist initializers will be called to generate the initial values for each field.

GraphQL API

Here's an example of a GraphQL query to generate a form for an existing node:

{
  forms {
    editForm(uiLocale: "en", locale: "en", nodePath: "/sites/mySite/home/area-main") {
      sections {
        name
        displayName
        fieldSets {
          name
          displayName
          fields {
            name
            selectorType
            i18n
            readOnly
            multiple
            mandatory
            valueConstraints {
              displayValue
              value {
                type
                string
              }
              properties {
                name
                value
              }
            }
            defaultValues {
              type
              string
            }
          }
        }
      }
    }
  }
}

The result will look something like this (truncated for length) :

{
  "data": {
    "forms": {
      "editForm": {
        "sections": [
          {
            "name": "content",
            "displayName": "Content",
            "fieldSets": [
              {
                "name": "mix:title",
                "displayName": "Title",
                "fields": [
                  {
                    "name": "jcr:title",
                    "selectorType": "Text",
                    "i18n": true,
                    "readOnly": false,
                    "multiple": false,
                    "mandatory": false,
                    "valueConstraints": [],
                    "defaultValues": []
                  }
                ]
              }
            ]
          },
          {
            "name": "classification",
            "displayName": "Categories",
            "fieldSets": [
              {
                "name": "jmix:categorized",
                "displayName": "categorized",
                "fields": [
                  {
                    "name": "j:defaultCategory",
                    "selectorType": "Category",
                    "i18n": false,
                    "readOnly": false,
                    "multiple": true,
                    "mandatory": false,
                    "valueConstraints": [],
                    "defaultValues": []
                  }
                ]
              }
            ]
          },
          {
            "name": "metadata",
            "displayName": "Metadata",
            "fieldSets": [
              {
                "name": "mix:created",
                "displayName": "Creation",
                "fields": [
                  {
                    "name": "jcr:created",
                    "selectorType": "DateTimePicker",
                    "i18n": false,
                    "readOnly": true,
                    "multiple": false,
                    "mandatory": false,
                    "valueConstraints": [],
                    "defaultValues": []
                  },
                  {
                    "name": "jcr:createdBy",
                    "selectorType": "Text",
                    "i18n": false,
                    "readOnly": true,
                    "multiple": false,
                    "mandatory": false,
                    "valueConstraints": [],
                    "defaultValues": []
                  }
                ]
              }, ...

Defining static forms in DX modules

A DX Module can define static forms and fieldSets by adding JSON files in the META-INF/jahia-content-editor-forms location, then in forms or fieldSets sub-directory. The files should have a meaning full name, for example for the qant:allFields node type we recommend replacing the colon (:) by an underscore so that the file name become qant_allFields.json.

Here's an example of a JSON static form definition coming from this example overrides:

We will now present the different object types that are used in static form definitions.

Form

A form is basically an object that has the following structure:

  • name ("default" is reserved as the default definition if none is found the specified primary node type name)
  • priority (used to allow overrides, the higher the priority the most important it will be)
  • a list of sections

Section

A section is basically a logical grouping of field sets. By default the content editor comes with pre-defined sections such as content, classification, metadata. A section definition is composed of :

  • a name
  • a label key for localization
  • a requiredPermission name

You can find examples of section definitions in the the default form file.

Sections themselves contain field sets.

Field Set

Basically a single field set for a node type may have :

  • a JCR definition, which will be used as the basis to generate a form dynamically
  • one or multiple static JSON definition files, that will be merged, in order of priority to produce the final resulting form.

A field set is a collection of fields. Fieldsets are associated with node types (regular or mixin). For example, a node with the following definition:

[jnt:latestBlogContent] > jnt:content, jmix:blogContent, jmix:list, mix:title, jmix:renderableList, jmix:studioOnly, jmix:bindedComponent
 - j:subNodesView (string, choicelist[templates=jnt:blogPost,resourceBundle,image]) nofulltext  itemtype = layout

Will have field sets defined for the jnt:content, jmix:blogContent, jmix:list, mix:title and all the other mixin types AND inherited types in the JCR node type definition.

When using static declaration of fieldsets, only override to existing properties are usually provided, although it is also possible to add fields that are not in a JCR definition (but this is not 100% supported yet).

A fieldset is therefore composed of:

  • a name
  • a priority
  • a collection of field definitions

Field

A field is basically associated with a node type property, although as described before it is also possible to have fields that are not related to JCR definitions but this is not yet 100% supported.

For each JCR property however, a related field is generated, and it may be overriden by using static field set definition files.

Field definitions have their own properties (not to be confused with JCR node properties), and these may be overriden using some specific rules.

There are some rules for the merging of the field properties. Basically the following cases may apply to a given property:

  • case 1 : the property can always be overriden
  • case 2 : the property can only be overridden if its value is not true
  • case 3 : the property can only be overridden if its value is not defined

Here are the association between cases and field properties:

property case 1 case 2 case 3
selectorType x
i18n x
readOnly x
multiple x
mandatory x
defaultValues x
targets x
removed x
selectorOptions x
valueConstraints x

As you can see these overrides will be done in order of priority so it is very important to remember that if you have multiple modules overriding the same node type (although this is not recommended but can be useful)

Field Priority properties

The priority field in the JSON static files is used to define in which order the definitions will be used to merge into the final form. The JCR definition will always be used first, and then the JSON files will be used in order of ascending priority. This makes it possible for multiple different modules to change a form definition and inject themselves where they need in the form generation process.

Field Selector types

The selectorType property in a form field definition is used to define the UI component that will be used to edit the field value. It is therefore very useful to set this value according to the needs of the project to build form UIs that are easy to use for end-users. In the (near) future it will also be possible to add new selector types in DX modules, making the form UI expandable.

Field Selector options

Selector options make it possible to override the default options that are specified in the JCR definition. These options are used for the moment to configure choicelist initializers. Here's an example of what selection options could look like:

For example if we have the following CND definition:

[jnt:latestBlogContent] > jnt:content, jmix:blogContent, jmix:list, mix:title, jmix:renderableList, jmix:studioOnly, jmix:bindedComponent
 - j:subNodesView (string, choicelist[templates=jnt:blogPost,resourceBundle,image]) nofulltext  itemtype = layout

The equivalent part for the j:subNodeView property would look like this:

{
    "name" :  "j:subNodeView",
    "selectorType" : "Choicelist",
    "selectorOptions" : [
        { "name" : "templates", "value" : "jnt:blogPost" },
        { "name" : "resourceBundle", "value" : null },
        { "name" : "image", "value" : null }
    ],
    "targets" : [ { "name" : "layout", "rank" : 0 } ]
}

Using static form JSON files you could override the selector options to for example change the templates allowed for this choicelist.

Important : selectorOptions can only be used with fields that have a CND definition !

If you are using purely JSON field definitions, you will instead simply have to use the valueConstraints array, which is static and not dynamic.

Removing a field

The removed property is a special one, which will actually remove a property from the resulting form definition.

Open-Source

Channels is an Open-Source module, you can find more details about Open-Source @ Jahia in this repository.