Extending Jahia UI
To declare a new module, you have two options:
- A simple module with very little to no JavaScript (for example, a legacy site settings)
- More complex modules with React and using Jahia moonstone design system
Common step
Declare your module
Create a jahia.json
file under src/main/resources/javascript/apps/jahia.json
with the following content:
{ "jahia": { "apps": { "jahia": "javascript/apps/register.js" } } }
Let's decrypt the most important part "jahia": "javascript/apps/register.js"
. The key here is the app you want to extend and the value is the path to the file that will load/register your app. This file will be the entry point that will contain the initialization part of your module.
Load/Register your app
You will need to create the JS file you declared in the jahia.json
file, in this example register.js
, and add the logic you need inside. The module can register different extensions in the application. In a simple JS module, this file can be written manually. In complex modules, it will be generated by a JS bundler like webpack. This file should remain small, as the application won't start until it's fully loaded and executed.
Registrations
The registrations need to be done in the register.js file (the one specified in the jahia.json file)
All extensions are added into the registry by calling the registry.add
function. The registry
object can be found by importing registry
from @jahia/ui-extender
. The add
function task has at least 3 parameters: the type of extension to register, its unique id, and an object describing the extension.
To ease this process when doing simple JS, we expose a few things in the window.jahia
object. As of today in the jahia object you can find:
- uiExtender, which contains the registry
- moonstone, which contains React components and utility functions from the moonstone design system
- i18n, which contains all the necessary function to handle translations
Routes
React routes can be added in the application as extensions. For example, the following can be used to declare a new route/linkchecker (path) that will display a linkchecker (render) in the main area (target) :
window.jahia.uiExtender.registry.add('route', 'route-jcontent', { targets: ['main:2'], path: `/linkchecker`, // Catch /linkchecker urls render: function () { // The render function for my route in this example we want to display an iframe which contains our legacy site settings, to do that we pass the URL to the `getIframeRenderer` function return window.jahia.uiExtender.getIframeRenderer(window.contextJsParameters.contextPath + '/cms/editframe/default/sites/$site-key.linkChecker.html'); } });
Primary nav items
Items in the primary nav bar can be added as primary-nav-item
. Here, add a link to /linkchecker
(path) into the main primary nav (target) with a label and an icon.
// Call the add method from the registry which is in uIExtender to add a menu entry to point to my module // `primary-nav-item` is the type I want to register and `linkcheckerRoute` is the key (must be unique), the last parameter is an object with the necessary options window.jahia.uiExtender.registry.add('primary-nav-item', 'linkcheckerRoute', { targets: ['nav-root-top:21'], // Which menu I want to extend, it can take multiple values, each value can be ordered `target:position` path: '/linkchecker', // Path to call when clicking on my link label: 'linkchecker:label.title', // RB to use to display the name of my link `namespace:key` icon: window.jahia.moonstone.toIconComponent('Feather') // Icon to use with my link, we must use the `toIconComponent` function to make sure we return an Icon Component });
Admin panels
Administration panels can be registered with a single entry, that will add an item in the tree and the appropriate route at the same time.
window.jahia.uiExtender.registry.add('adminRoute', 'linkchecker', { targets: ['jcontent:10'], label: 'linkchecker:label.title', isSelectable: true, requiredPermission: 'siteAdminLinkChecker', requireModuleInstalledOnSite: 'linkchecker', iframeUrl: window.contextJsParameters.contextPath + '/cms/editframe/default/$lang/sites/$site-key.linkChecker.html' });
See more in settings documentation.
Actions
You can add actions in the application anywhere that buttons or menu entries display.
window.jahia.uiExtender.registry.add('action', 'downloadFile', { buttonIcon: window.jahia.moonstone.toIconComponent('CloudDownload'), buttonLabel: 'jcontent:label.contentManager.contentPreview.download', targets: ['contentActions:10'], onClick: context => { window.open(context.path); } });
Translation namespaces
If you want to use translated label in your module, you need to declare your module namespace, which must be your module name :
// Declare the namespace to find the translations for my module, put the name of your module and reuse it in your labels window.jahia.i18n.loadNamespaces('linkchecker');
This will download the translations from JSON files under the following path src/main/resources/javascript/locales/<lang>.json
:
{ "label" : { "title": "Link Checker" } }
Such label can be used in your code with: {moduleId}:{label.path}
For instance, to retrieve the title label for the linkchecker module, you will need to use:
linkchecker:label.title
More complex modules
Maven
Add a maven dependency on app-shell
to your project with the classifier manifest:
<dependency> <groupId>org.jahia.modules</groupId> <artifactId>app-shell</artifactId> <version>2.0.0</version> <type>json</type> <classifier>manifest</classifier> </dependency>
And use copy plugin:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>initialize</phase> <goals> <goal>copy</goal> </goals> </execution> </executions> <configuration> <artifactItems> <artifactItem> <groupId>org.jahia.modules</groupId> <artifactId>app-shell</artifactId> <type>json</type> <classifier>manifest</classifier> </artifactItem> </artifactItems> </configuration> </plugin>
This provides the manifest that contains all shared packages while building the application or extension.
Set webpack configuration to use DLL and CopyWebpackPlugin to provide package.json
The output exposed of the extension must be a single file named *.bundle.js
{ output: { path: path.resolve(__dirname, 'src/main/resources/javascript/apps/'), filename: 'content-editor-ext.bundle.js', chunkFilename: '[name].content-editor.[chunkhash:6].js' } }
And the plugin configuration:
{ plugins: [ new webpack.DllReferencePlugin({ manifest: require('./target/dependency/app-shell-1.0.0-SNAPSHOT-manifest') }), new CopyWebpackPlugin([{ from: './package.json', to: '' }]) ] }