Adding Actions in UI - Beta

  Written by The Jahia Team
 
Developers
   Estimated reading time:

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 topBarActions and a contextualMenuActions 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 will be added into the common registry. As most of the items in the registry,  it must have a unique key and a list of targets. This list of targets will declare where the action will appear. 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 example contextualMenu:4.
  • buttonLabel 
    The label key in the button or menu entry. The key must be prefixed by a namespace (module name):buttonLabel:, for example marketing-factory-core: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. It is use possible to use the helper function toIconComponent() (from @jahia/moonstone, or window.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 actions, 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 is displayed or when it receives a new context. The function receives the context as a parameter and can enhance the context, for example to add buttonLabelParams or enabled.

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.

registry.add('action', 'simple-action', {
    init: context => {
        context.enabled = context.path.startsWith('/sites/mySite');
    },
    onClick: context => {
        window.alert('clicked ' + context.path);
    }
});
 

Component actions

It is also possible to create an component action, by writing a React component. In that case, you just need to declare the component to use :

  • component 
    the component that will be used for the action.

Component actions are much more powerful than simple actions, as they can rely on all React features. It's for example possible to make asynchronous call, to check a permission, do a graphql query, create modal when being called, ...

The component will receive the context as properties [beta : the context is currently passed as a context prop, but in the future all properties will be directly passed to the component], along with the render and loading props.

The render is actually the component that should be used to render the action - it can be a button, a 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 this component to do the rendering. The render component need to receive the full context, enhanced with an onClick callback - the function that need to be called to trigger the action. [beta : again, this context is passed in a context prop, in the next version all properties will be directly passed to the component]

The loading component should be returned if the component is waiting for an asynchronous result [in next versions, 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 = ({context, render: Render}) => (
    <Render context={{
        ...context,
        enabled: context.path.startsWith('/sites/mySite'),
        onClick: context => {
            window.alert('clicked ' + context.path);
        }
    }}/>
);

registry.add('action', '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.

It's possible to extend an existing action by adding its descriptor in the registry.add function : 

registry.add('action', 'base-action', {
    onClick: context => {
        window.alert(context.param);
    }
});

registry.add('action', 'action-1', registry.get('action', 'base-action'), {
    params: 'one'
});

registry.add('action', 'action-2', registry.get('action', 'base-action'), {
    params: 'one'
});
 

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 dropdown 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'), {
    menuTarget: 'myMenuTarget'
});

registry.add('action', 'action-1', {
    targets: ['myMenuTarget:1'],
    onClick: () => window.alert('item1')
});

registry.add('action', 'action-2', {
    targets: ['myMenuTarget:2'],
    onClick: () => window.alert('item2')
});
 

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
  • showOnNodeTypes
    A list of node types allowed for this action
  • hideOnNodeTypes 
    A list of node types hidden for this action
  • requireModuleInstalledOnSite 
    The name of a module that must be installed on the site 
  • showForPaths
    A list of regexp paths on which the action is enabled
  • hideForPaths
    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 = ({context, render: Render, loading: Loading}) => {
    const res = useNodeChecks(
        {path: context.path},
        {requiredPermission: 'permission'}
    );

    if (res.loading) {
        return (Loading && <Loading context={context}/>) || false;
    }

    return (
        <Render context={{
            ...context,
            enabled: res.checkResults,
            onClick: context => {
                window.alert('clicked ' + context.path);
            }
        }}/>
    );
};

registry.add('action', 'component-action', {
    component: MyComponent
});