Operations
Interaction with documents usually occurs through executing operations, such as inserting an item or wrapping the selected text. Packages can define their own operations by combining operations, transforms and actions defined by the platform or the instance’s own packages.
New and existing operations
Operations are commonly defined in operations.json
files, which is an object mapping operation names to their definitions. Any operations.json
or operations-*.json
file in the src/
directory of your package will be automatically included.
Alternatively, operations can be registered in Javascript using the Async
Fonto Editor's SDK includes many operations out of the box, and in most cases custom operations will reuse SDK operations. See also the list of operations in our API documentation.
Operation properties
Each operation is defined as an object with mostly optional properties.
The exhausive list of operation properties is documented on our
The most commonly used properties are:
Property |
Type |
Description |
---|---|---|
|
string |
(Optional) Used when the operation is used in a UI element such as a button or menu item. |
|
string |
(Optional) Used as tooltip text by UI elements using the operation. |
|
string |
(Optional) An icon displayed by UI elements using the operation. This should be a Fontawesome (v4.7.0) icon. |
|
string[] |
(Optional) Used to define multiple, alternative, operations if the previous operation failed. |
|
object | object[] |
Steps executed when the operation is invoked. Steps form a pipeline, passing step data from each step to the next. |
Labels, footnotes and icons can be overriden for an operation by using the "label", "description" or "icon" props on buttons or menu items.
Alternative operations
An alternative form of the operation definition uses the alternatives
property instead of steps
, providing an array of operation names. Such an operation will adopt the behavior of the first of these operations which is enabled.
JSON
{
"section-button": {
"label": "Section",
"alternatives": [
"convert-selected-block-to-section",
"insert-new-section",
"horizontally-insert-new-section"
]
}
}
Operation steps
Steps or state steps can either consist out of one step or out of an array of steps. Each step has a type
and optional data
. Each step can modify step data internally, and the data template for each step can define new properties, possibly reusing existing data using the "expression.to.property
" syntax.
JSON
{
"convert-paragraph-to-codeblock": {
"label": "Code block",
"steps": [
{
"type": "transform/setContextNodeIdToSelectionAncestor",
"data": {
"selectionAncestorNodeSpec": "self::p or self::pre"
}
},
{
"type": "operation/convert-element",
"data": {
"contextNodeId": "{{contextNodeId}}",
"nodeNames": [
"codeblock"
]
}
}
]
}
}
There are different types of steps, which are prefixed in the type
property of the step. The steps can already exist in the platform or else can be created:
Step type |
Description |
See also |
---|---|---|
transform |
Is used to manipulate the step data. This should ideally not have any side-effects. | |
action |
Is used to change the state of the application other than the document content. This includes, for example, controlling UI components or communicating with the CMS. An action can also abort the running operation by returning the | |
operation |
Not all operations should be directly used in a button, these operations can be used as sub-operation. Sub-operations are passed the current step data from the operation that invokes them, which can be used to control their behavior. It is also possible to omit the operation name part of a step with the type operation. In this case, the name of the operation will be read from the | |
custom-mutation |
Is used to manipulate the XML. Only use this if none of the existing operations can be used. | |
modal |
Is used to open a modal. Modals can be submitted to provide step data for the next step in the operation, or canceled to abort the operation. |
Operation state
Each operation has an associated state, consisting of two booleans: enabled
and active
. The enabled
flag indicates whether the operation is allowed to run given the current context. The active
flag symbolizes whether the effect of the operation already applies, and is usually visualized by showing the UI component as active/pressed/selected (see also the is
prop on components such as Button).
The state of an operation is derived from the loaded documents and other application state, including the current cursor position or selection, but also the state of UI elements. Normally, the steps used to determine the state of an application are derived from the steps used to run it, although an operation can also specify the get
explicitly. Be careful that the behavior of these steps still corresponds to the actual behavior of the operation, so an operation is not enabled when it cannot be executed correctly.
When determining the state of the operation, each of the step types functions as follows:
Step type |
Behavior when getting operation state |
---|---|
transform |
It is assumed that a transform glues other operation steps together by modifying step data without side-effects, so in most cases the transform is executed as normal. If a separate get state callback was passed to add |
action |
If a get state callback was passed to add |
operation |
The state of the operation being referenced is computed and used. Built-in operations that modify the DOM use a process similar to custom mutations to determine their state. |
custom-mutation |
The custom mutation is executed on a Blueprint without affecting the actual DOM. If the custom mutation returns If the custom mutation returns Custom mutations can set the |
modal |
Modals are obviously only opened when the operation is executed. However, the data returned by a modal could be required to correctly compute the state of subsequent operation steps. Therefore, if the modal's React component specifies a default value for the This way, modals can define stub data as a stand-in during state computation for data that would otherwise be provided by the user. For more complex scenarios, we recommend adding a transform step after the modal that may perform additional processing on the step data as required. |
If any step determines the operation state to be disabled, processing stops.
Invalidating operation states
Actions and transforms are commonly used to pull some external state or data into the operations pipeline. If you have a custom action or transform that relies on external data you must also signal Fonto Editor when that data has changed, by calling Operations
For example, an action that uses the selection may invalidate corresponding operation states when the selection changes by subscribing to the selection change notifier. This is usually done in the same install.js
that registers the action:
JavaScript
addAction(
'sendCurrentParagraphToCms',
async function sendParagraph(stepData) {
await someManager.sendDataToCms(stepData.paragraph);
return;
}
);
// In this case the operation state will be invalidated every time the notifier is triggered.
selectionManager.selectionChangeNotifier.addCallback(() => {
operationsManager.invalidateOperationStatesByStepType(
'action',
'sendCurrentParagraphToCms'
);
});
Contextual operations
Contextual operations are normal operations, but intended to work based on a specific context (node).
Contextual operations are therefore always provided a context
step data by the menu, which references the context to which the menu applies.
Fonto Editor provides four common UI components that provide such a context:
-
The contextual menu, which shows up when a right-mouse-click is performed on an element
-
The element menu, which is a widget that may be configured for any element
-
The breadcrumb menu, which is an addition to the breadcrumbs shown at the bottom of the editor
-
The structure view menu, which is an element menu that occurs in the structure view add-on.
A contextual operation can be configured as a menu option in a component that is opened by the user by clicking accordingly, or as part of a group in that menu, or as part of a sub-menu. Please refer to the Configure contextual operations guide for more information.
Grouping menu options or placing them in a sub-menu is recommended to make similar operations, or operations with similar concerns, more easily findable.
Contextual menu

One portion of the contextual menu is determined by the contextual operations that have been configured for a given element. The contextual menu will show the operations for that element, and also for all ancestor elements that have contextual operations. After a certain point the operations for ancestor elements are placed inside a sub-menu for that element.
Other parts of the contextual menu are sometimes used by add-on functionality such as Fonto Content Quality or the spell-checker. These parts are not driven by contextual operation configuration.
A contextual operation can be hidden from this component by including "context-menu"
as part of the hide
property for that operation.
Element menu
The "element-menu" refers to the menu that you see when you click on the create

This widget is usually configured in the block
widget area of an element that has contextual operations configured.
A contextual operation can be hidden from this component by including "element-menu"
as part of the hide
property for that operation.
Breadcrumbs menu
The breadcrumbs menu refers to the "breadcrumb trail" of menu items in the status bar on the /editor
route, as shown here:

If the element represented by such a menu item has configured contextual operations, the menu item is turned into a split menu item. Clicking the split part of the menu opens a drop with a menu with contextual operations in it.
A contextual operation can be hidden from this component by including "breadcrumbs-menu"
as part of the property for that operation.
Structure view menu
The structure view menu refers to the button shown on a structure view item if the element for that item has configured contextual operations on it, as shown here:

A contextual operation can be hidden from this component by including "structure-view"
as part of the hide
property for that operation.
Localization
Some strings in an operation should be localized, namely everything that is shown in the interface to the user at some point. These always include the label and description, but could also include any step data property you've added on your own. They can be marked for localization by using the t__
prefix. See the documentation on Localize the interface for more information.
JSON
{
"toggle_bold": {
"label": "t__Toggle bold",
"steps": [
{
"type": "operation/toggle-inline-formatting",
"data": {
"nodeName": "b"
}
}
]
}
}
Inline XPath/ XQuery
In the step data of an operation step, the x__
prefix can be used to signal the rest of the string should be interpreted and be executed as an XQuery script. This script is supplied the following context:
Propery |
Description |
---|---|
|
A A step data containing a |
|
The context item is absent. |
The result of this XQuery script will be inserted in the step data, under the given key. Values of type xs:string
will be kept strings, xs:numeric
will be a JavaScript Number, etc, etc. However, Values of type node()
will be transformed to their Nodecontext
.
In the following example inline XPath is used to set both the contextNodeId operation data for set-attributes, as well as determine the value of @clonable
.
JSON
{
"clone-attribute-to-parent": {
"steps": [
{
"type": "transform/setContextNodeIdToSelectedElement"
},
{
"type": "operation/set-attributes",
"data": {
"contextNodeId": "x__$data('contextNode')/..",
"attributes": {
"clonableAttribute": "x__$data('contextNode')/@clonableAttribute"
}
}
}
]
}
}
Importing modules
Modules can be imported using the import module
syntax, like how every other XQuery script works. This allows for better re-use of code.
JSON
{
"clone-attribute-to-parent": {
"steps": [
{
"type": "transform/setContextNodeIdToSelectedElement"
},
{
"type": "operation/set-attributes",
"data": {
"contextNodeId": "x__$data('contextNode')/..",
"attributes": {
"clonableAttribute": "import module prefix='http://www.example.com/ns'; prefix:execute-a-function($data('contextNode'))"
}
}
}
]
}
}
Please Wait Message
The step data may contain a property called please
. This should be a string that should be displayed during the run of the operation. Use this for long running operations (like saving all documents). The modal opens when the step setting this property is ran and will close when the operation ends.
Examples
The following operations are valid operation definitions using standard Fonto Editor functionality.
Inserting an important note
JSON
{
"note-important-insert": {
"label": "t__Important note",
"steps": {
"type": "operation/vertical-insert",
"data": {
"childNodeStructure": [
"note",
{ "type": "important" },
[{ "bindTo": "selection", "empty": true }]
]
}
}
}
}
Marking a word or selection as a trademark
JSON
{
"tm-insert": {
"label": "t__Trademark",
"description": "t__Identifies a term or phrase that is trademarked.",
"icon": "trademark",
"steps": [
{
"type": "operation/toggle-inline-frame-element",
"data": {
"nodeName": "tm",
"attributes": { "tmtype": "tm" }
}
}
]
}
}
Using data from the previous operation step
JSON
{
"equation-block-insert": {
"label": "Add equation",
"description": "Add an equation to this figure.",
"icon": "calculator",
"steps": [
{
"type": "operation/open-mathml-editor",
"data": {
"mathMlPart": ["mathml"]
}
},
{
"type": "command/horizontal-insert",
"data": {
"childNodeStructure": [
"equation-block",
"{{mathMlPart}}"
]
}
}
]
}
}
Operations for different use cases
The FontoXML platform provides a wide range of operations, both complete ones only requiring some configuration and operations specifically meant to be a step of another operation. For a complete list of operations, refer to the Operations
Toggling inline elements
Inline elements can be divided in two groups. The first group contain elements which only apply some kind of formatting without suggesting any meaning. An example is the b
element found in DITA. These elements should be configured with the configure
family. For this family, the toggle-inline-formatting-element
operation should be used. The second group contains elements which describe the semantics of a text phrase. An example is the apiname
element found in DITA. These elements should be configured with the configure
family. For this family type, the toggle-inline-frame-element
operation should be used.
The following example shows the usage of the toggle-inline-formatting-element
operation for toggling a DITA b
element:
JSON
{
"b-toggle": {
"label": "Bold",
"description": "Applies bold formatting to text.",
"icon": "bold",
"keyBinding": "ctrl+b",
"osxKeyBinding": "cmd+b",
"steps": [
{
"type": "operation/toggle-inline-formatting-element",
"data": {
"nodeName": "b"
}
}
]
}
}
The following example shows the usage of the toggle-inline-frame-element
operation for toggling a DITA apiname
element:
JSON
{
"apiname-insert": {
"label": "API name",
"description": "Provides the name of an application programming interface.",
"steps": [
{
"type": "operation/toggle-inline-frame-element",
"data": {
"nodeName": "apiname"
}
}
]
}
}
As shown in the examples, both operations require the node
to be set.
Inserting structures
It is often necessary to not only insert just one element, but a whole XML structure. For this, the horizontal-insert
and vertical-insert
operations can be used. These operations differ in the way they look for the right spot for the structure to be inserted. In short, the horizontal-insert
inserts the given structure somewhere under a given node, taking into account the schema. The vertical-insert
takes a different approach, it tries to insert the given structure as close to the selection as possible. See the API pages of both operations for more information.
The following example shows how to insert a DITA consequence
element using the horizontal-insert
operation:
JSON
{
"consequence-insert": {
"label": "Add consequence",
"description": "Specifies the consequence of failing to avoid a hazard.",
"steps": [
{
"type": "operation/horizontal-insert",
"data": {
"childNodeStructure": [
"consequence",
[
{
"bindTo": "selection",
"empty": true
}
]
]
}
}
]
}
}
The example for inserting an image doubles as an example for the vertical-insert
operation.
Inserting images
Operations for inserting images generally consist of two operations. The first operation, or step, opens the browse modal in which the user can select an image to insert. The second operation inserts an element representing the image and the required surrounding elements.
Note that in this example, data is being passed from the browse modal to the vertical-insert
operation. This data is passed to the vertical-insert
operation via the model property on the operation data, the {{ and }} indicate that the value should come from the previous operation.
JSON
{
"image-figure-insert": {
"label": "Figure with image",
"description": "A figure that groups an image with its title and description.",
"steps": [
{
"type": "operation/open-image-browser-for-insert"
},
{
"type": "operation/vertical-insert",
"data": {
"model": {
"href": "{{permanentId}}"
},
"childNodeStructure": [
"fig",
[
"title",
[
{
"bindTo": "selection",
"empty": true
}
]
],
[
"desc"
],
[
"image",
{
"href": {
"bindTo": "href"
}
}
]
]
}
}
]
}
}
Removing structures
Most inline elements can be removed by using the backspace key when the cursor is positioned after these elements. Other elements can be deleted by selecting them via the breadcrumbs and pressing backspace or delete. Alternatively, a delete operation can be offered via the element's contextual menu. For this, the platform offers the delete-node
operation.
The operation in this example will be triggered via a context menu. This means that the context
is being set implicitly.
JSON
{
"syntaxdiagram-delete": {
"label": "Remove syntax diagram",
"description": "Remove this syntax diagram.",
"icon": "times",
"steps": [
{
"type": "operation/delete-node"
}
]
}
}
When the node that should be removed is not the one containing the selection directly, a transform can be added to set the context
to a different node.
JSON
{
"plentry-delete-as-row": {
"label": "Remove row",
"description": "Remove this row from the parameter table.",
"icon": "times",
"steps": [
{
"type": "transform/setContextNodeIdToSelectionAncestorMatchingDitaClass",
"data": {
"parentDitaTypes": [
"pr-d/plentry"
]
}
},
{
"type": "operation/delete-node"
}
]
}
}
Table operations
The platform offers a range of operations for working with tables. These are obviously only relevant when configuring an application which supports at least one table standard.
Inserting table
Inserting a table can be done with the table-insert
operation. This operation expects the child
to be set. It needs this structure to determine which table structure to use. In this example, the table-insert
operation is used to insert a CALS table.
The CALS standard defines the table
element as being some sort of figure-like element containing a tgroup
element, the actual table itself. If this were to be a xhtml-table-insert operation, the value of the child
would just have been "table". See the table-insert
JSON
{
"cals-table-insert": {
"label": "CALS table",
"description": "A generic CALS table",
"icon": "table",
"initialData": {
"childNodeStructure": [
"table",
[
"tgroup"
]
],
"rows": 1,
"columns": 1
},
"steps": [
{
"type": "operation/table-insert"
}
]
}
}
Adding and removing rows/columns
Adding and removing the rows or columns of tables can be done with the following operations:
These operations can be used directly and do not need any configuration. All these operations are available in their contextual variant:
These operations assume the contextNodeId to be set, which is implicitly done when used in a contextual menu.
Merging cells
Merging cells in any direction can be done with the following operations (if the used table standard supports merged cells):
These operations can be used directly and do not need any configuration.
These operations assume the context
to be set, which is implicitly done when used in a contextual menu.
Table styling
Some table specs support styling in the form of cell borders and header rows.
Contextual variants are also available:
Removing tables
Removing tables can be done by using the table-delete operation.
Custom mutations
Use a custom mutation if none of the existing operations meet the requirements. See add
Triggering operations
There are multiple ways to trigger the execution of an operation. The most obvious way is to click a button in the masthead. Another way is to click a button in the context menu. An operation can also be triggered by keyboard shortcuts when it is configured to do so.
See the "Creating a masthead" guide for more information about creating a masthead and binding operations to the buttons. See configure
for more information about adding a contextual operation to an element.
Masthead
The most obvious way of triggering an operation is by clicking a button in the masthead of the application. Most of the insert-like operations should be located in the masthead, grouped by use. These operations are executed with an empty step
.
Contextual menu
Another way of triggering an operation is by clicking a button in a context menu. These menus can be triggered by right-clicking elements which are configured to have a context menu, or by clicking the elementcontext
property on the step
. The context
property is a Node
Contextual operations may be configured using configure
Keyboard shortcut
An operation can also be triggered by keyboard shortcuts when it is configured to do so. Generally, operations used for inline formatting are triggered with keyboard shortcuts. These operations are executed with an empty step
.