Content Editor React extension for Jahia modules.
- https://github.com/Jahia/dx-commons-webpack that will provide the shared library https://github.com/Jahia/javascript-components
- https://github.com/Jahia/content-media-manager
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
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
- 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
- Download, build and install dx-commons-webpack
- Download, build and install content-media-manager
- Build and install this module
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.
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": []
}
]
}, ...
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.
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
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.
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
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)
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.
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.
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.
The removed
property is a special one, which will actually remove a property from the resulting form definition.
Channels is an Open-Source module, you can find more details about Open-Source @ Jahia in this repository.