Adding UI Actions
You can extend Jahia UI by integrating actions from your own modules. This allows you to make Jahia better fit your needs and provide features specific to your organization.
Concepts
Actions
An action describes a function that can be called by an end-user. An action is rendered by a button, icon, menu entry, or anything that a user can click, depending on the context where it is rendered. All actions are added in a central registry.
Applications display actions in predefined areas called targets. For example, an application can define a headerPrimaryActions
and a contentActions
target. The application looks in the registry for all actions to display in a specific target. The same actions can be available in different targets.
Context properties
The application can pass contextual properties to an action. For example, jContent will pass the current path of the selected node to all actions through the "path" property. If multiple nodes are selected, a "paths" property will be sent. The context will depend on the target - actions that require a path property can only be used in target that will provide this context.
Actions structure
Action properties
Each action is described by a simple JS object that is added into the common registry. As with most of the items in the registry, it must have a unique key and a list of targets. This list of targets declare where the action appears. Action descriptor can contain the following properties:
target
The list of locations where the action is added. Each value is a simple unique string provided by the application, optionally followed by a number (float) indicating the relative position of the action in the list of actions, for examplecontentActions:4
.- Starting with Jahia 8.1.6, when adding an action in the registry, you can easily identify the type and the target by looking at the html source of a similar entry: the
<li>
tag contains thedata-registry-key
(containing the type) anddata-registry-target
(containing the target and position) attributes. See example in Identifying the type and target.
- Starting with Jahia 8.1.6, when adding an action in the registry, you can easily identify the type and the target by looking at the html source of a similar entry: the
buttonLabel
The label key in the button or menu entry. The key must be prefixed by a namespace(module name):buttonLabel:
, for examplejexperience:label.title
.buttonLabelParams
Parameters passed to the label key for placeholder replacements.buttonIcon
An optional icon that you can display. It should be a React icon component. You can use the helper functiontoIconComponent()
(from@jahia/moonstone
, orwindow.jahia.moonstone
) to transform a path or an inline SVG to an icon component.-
enabled
If set to false, the action will be "disabled". -
isVisible
If set to false, the action won't be displayed.
There are 2 ways to declare actions: either simple or component actions.
Simple actions
Simple actions can be written in plain JS, with 2 simple callbacks :
onClick
The function that is called when a user triggers the action. The function receives the context as a parameter.init
An optional function that is called when the action displays or when it receives a new context. The function receives the context as a parameter and can enhance the context, for example addingbuttonLabelParams
orenabled
.
All properties of the action are merged with the context and passed to theinit()
and onClick()
functions.
When properties cannot be statically initialized, you can use the init()
function to dynamically resolve values based on the context. For example, the enabled value is not directly provided by an action, but rather set by the init()
function based on the context. The buttonLabelParams
can also be dynamic, based on the context. Even the onClick
function can be set based on the context. Only the key and target values cannot be set by init()
function.
In the example below, we add an action that will only be enabled on the mySite
website and that will simply log a message in the console when clicked.
registry.add('action', 'simple-action', {
targets: ['contentActions:10'],
buttonIcon: <DefaultEntry/>,
buttonLabel: 'Simple action',
init: context => {
context.enabled = context.path.startsWith('/sites/mySite');
console.debug('simple-action: initialized enabled=' + context.enabled)
},
onClick: context => {
console.debug('simple-action: clicked ' + context.path);
}
});
Component actions
You can create a component action by writing a React component. In this case, you just need to declare the component to use:
component
The component to use for the action.
Component actions are much more powerful than simple actions as they can rely on all React features. For example, you can make asynchronous call, check a permission, perform a graphql query, create modal when being called, and more.
The component receives the context as properties, along with the render
and loading
props. The render is actually the component to use to render the action. It can be a button or menu item, depending on the way the action was called. The component should never render a button by itself, but only contain the logic and rely on the component to do the rendering. The render component needs to receive all original props, enhanced with an onClick
callback - the function that needs to be called to trigger the action.
The loading component should be returned if the component is waiting for an asynchronous result [in future versions, the loading prop will be removed and the system will rely on React suspense].
Here is the same action as before, written as a component:
const MyComponent = ({path, render: Render, ...others}) => (
<Render
{...others}
enabled={path.startsWith('/sites/mySite')}
onClick={() => {
console.debug('clicked ' + path);
}}/>
);
registry.add('action', 'component-action', {
targets: ['contentActions:10'],
buttonIcon: <DefaultEntry/>,
buttonLabel: 'Component action',
component: MyComponent
});
Registering actions
You register actions in the registry by calling the registry.add
function (from @jahia/ui-extender
or window.jahia.uiExtender
) with "action" as the first parameter and a unique key as the second parameter, followed by the action descriptor. An action can be found by its key by using registry.get('action',key)
. Note that the action key is added in the action descriptor (under the "key" property).
If the action already exists with the same key, an error will be thrown. If you want to avoid this error and force the action to be installed you can instead use the registry.addOrReplace
function.
You can extend an existing action by adding its descriptor in the registry.add function:
registry.add('action', 'base-action', {
targets: ['contentActions:10'],
buttonIcon: <DefaultEntry/>,
buttonLabel: 'Param action',
onClick: context => {
console.debug('param', context.param);
}
});
registry.add('action', 'action-1', registry.get('action', 'base-action'), {
param: 'one'
});
registry.add('action', 'action-2', registry.get('action', 'base-action'), {
param: 'two'
});
Actually, the registry.add
can receive any number of descriptors after the key, and will compose them to create a new descriptor.
Actions cookbook
Menu actions
The @jahia/ui-extender
library provides a generic drop-down menu action. This action will open a drop-down menu when clicked, showing other actions. The menu needs to define a target where these actions will be added. A base descriptor is provided as menuAction
and can be found with registry.get('action', 'menuAction')
.
Creating a menu can be done by just declaring a few descriptors, one for the menu and one for each action in the menu:
registry.add('action', 'myMenu', registry.get('action', 'menuAction'), {
targets: ['contentActions:10'],
buttonIcon: <DefaultEntry/>,
buttonLabel: 'My menu...',
menuTarget: 'myMenuTarget'
});
registry.add('action', 'myMenu-action-1', {
targets: ['myMenuTarget:1'],
buttonLabel: 'Action 1',
onClick: () => console.debug('myMenu action 1')
});
registry.add('action', 'action-2', {
targets: ['myMenuTarget:2'],
buttonLabel: 'Action 2',
onClick: () => console.debug('myMenu action 2')
});
Documentation on the menuAction itself can also be found on GitHub.
Adding node requirements
The @jahia/data-helper
library provides useful functions to check requirements on a node. The useNodeChecks
hook will take a path as parameter, and a list of options to check on the node. These options can include:
requiredPermission
A single permission required for the action to be enabled, or a list (array) of permissionsrequiredSitePermission
A single site permission required for the action to be enabled, or a list (array) of permissionsshowOnNodeTypes
A list of node types allowed for this actionhideOnNodeTypes
A list of node types hidden for this actionrequireModuleInstalledOnSite
The name of a module that must be installed on the siteshowForPaths
A list of regexp paths on which the action is enabledhideForPaths
A list of regexp path on which the action is disabled
The hook will test these predicates on the given node and will return a simple boolean value in checksResult
. Here is a simple example of a component using the useNodeChecks hook (from @jahia/data-helper
) to test a permission:
const MyComponent = ({path, render: Render, loading: Loading, ...others}) => {
const {checksResult, loading} = useNodeChecks(
{path},
{requiredPermission: 'permission'}
);
if (Loading && loading) {
return <Loading {...others}/>;
}
console.debug('Permission check results', checksResult);
return (
<Render
{...others}
enabled={checksResult}
onClick={() => {
console.debug('clicked ' +path);
}}/>
);
};
registry.add('action', 'component-action', {
targets: ['contentActions:10'],
buttonIcon: <DefaultEntry/>,
buttonLabel: 'Permission action',
component: MyComponent
});