Extending eZ Platform Online Editor

eZ Platform uses the Alloy Editor, which is a modern WYSIWYG editor built on top of CKEditor. It means that you can use existing CKEditor plugins to extend the eZ Platform Online Editor.

Each plugin provides custom features/functionality. Good examples would be a color picker, characters/words counter, etc. Usually, they consist of one or more CKEDITOR.command and elements to provide UI to those commands like CKEDITOR.ui.button.

If you are brave enough, you can build custom plugins. And we would like to show you how to do it!

Building a custom plugin

Let's build a new eZ Platform Online Editor plugin which will allow editors to insert a random number to the content. It might not be the most demanded plugin, but it's a good demo example. And the good news is that there's already a bundle to help with this, so as a pre-requisite, please install the ExtendedAdminUIBundle source code. 

In order to provide editors with our random number feature, you need to follow these steps:

1. Work on plugin source code

You can create a javascript file for a plugin anywhere inside the Resources/public/js directory of your main bundle. But it is worth following eZ Platform conventions and use

Resources/public/js/alloyeditor/plugins/<PLUGIN_NAME>.js. So, we will end up with Resources/public/js/alloyeditor/plugins/random.js. Its content will be:

(function(global) {
    if (CKEDITOR.plugins.get('random')) {
        return;
    }

    const InsertRandomNumber = {
        exec: function(editor) {
            const number = Math.floor(Math.random() * 1000) + 100;
            editor.focus();
            editor.insertText(' ' + number);
        },
    };

    CKEDITOR.plugins.add('random', {
        init: (editor) => editor.addCommand('InsertRandomNumber', InsertRandomNumber),
    });
})(window);
random.js

Here we are:

  • Running a check to avoid duplicate plugin initialization.
  • Implementing a InsertRandomNumber command. Which generates a random number and inserts it into the editor.
  • Attaching the InsertRandomNumber command to the editor.

So, we have our Online Editor plugin, but eZ Platform is not aware of it. Let's fix that...

2. Enable the plugin in eZ Platform

This is the simplest step - you need just to modify your settings by adding the following parameters in the configuration file:

ezrichtext:
    alloy_editor:
        extra_plugins: [random]
config.yml

After doing so the Online Editor will be aware of our new plugin. And it will try to load it from the default location - which wont work, as it is inside ezplatform-admin- ui bundle and we cant add new files there! So...

3. Adding the plugin to assets

We need to load plugin sources on all pages were the Online Editor is used. And Webpack Encore will help us to achieve this. If you haven't used it before, we strongly recommend to check out the eZ Platform documentation.

Based on https://github.com/ezsyst... we found out that ezplatform-admin-ui-alloyeditor-js is the entry in which we need to add our plugin sources.

Let's create Resources/encore/ez.config.manager.js in your bundle with the following content:

const path = require('path');

module.exports = (eZConfig, eZConfigManager) => {
    eZConfigManager.add({
        eZConfig,
        entryName: 'ezplatform-admin-ui-alloyeditor-js',
        newItems: [
            path.resolve(__dirname, '../public/js/alloyeditor/plugins/random.js'),
        ]
    });
};
ez.config.manager.js

This configuration file is pretty well self-explanatory. The only thing we would highlight here is that all paths need to be relative to the directory where the ez.config.manager.js file is stored. In our case it is src/ExtendedAdminUIBundle/Resources/encore.

The remaining thing is to update your assets by running:

$ yarn encore dev
update assets

So now our plugin sources are delivered to all pages were the Online Editor is used. The next logical step is to add a button for our plugin.

4. Make a button which will provide a UI for the plugin

eZ Platform provides the EzButton base button class. And all Online Editor buttons are extending it. Our button will work in the same way. You should use a Resources/public/js/ alloyeditor/buttons/<BUTTON_NAME>.js file inside your bundle for it, which will be Resources/public/js/alloyeditor/buttons/random.js in our case:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AlloyEditor from 'alloyeditor';
import EzButton from '../../../../../../../vendor/ezsystems/ezplatform-admin-ui/src/bundle/Resources/public/js/alloyeditor/src/base/ez-button.js';

export default class BtnRandom extends EzButton {
    static get key() {
        return 'random';
    }

    insertRandomNumber(data) {
        this.execCommand(data);
    }

    render() {
        const title = 'Random';

        return (
            <button
                className="ae-button ez-btn-ae ez-btn-ae--random"
                onClick={this.insertRandomNumber.bind(this)}
                tabIndex={this.props.tabIndex}
                title={title}>
                <svg className="ez-icon ez-btn-ae__icon">
                    <use xlinkHref="/bundles/extendedadminui/img/random-icon.svg#random" />
                </svg>
            </button>
        );
    }
}

AlloyEditor.Buttons[BtnRandom.key] = AlloyEditor.BtnRandom = BtnRandom;
eZ.addConfig('ezAlloyEditor.BtnRandom', BtnRandom);

BtnRandom.propTypes = {
    command: PropTypes.string,
};

BtnRandom.defaultProps = {
    command: 'InsertRandomNumber',
};
random1.js

Please note:

  • The import path for EzButton is so ugly, because our button is defined in the src/ExtendedAdminUIBundle/Resources/public/js/alloyeditor/buttons/random.js file. And EzButton is defined at vendor/ezsystems/ezplatform-admin-ui/src/bundle/Resources/public/js/alloyeditor/src/base/ez-button.js.
  • Our button has   unique random key
  • The InsertRandomNumber command (which we defined in the plugin) will be executed on button click.
  • You need to create an icons file.

So now there is a button which executes the command in our plugin. But it is not added to our page assets and the Online Editor is not aware of it yet. Fixing that will be our last step.

5. Show the button in the Online Editor

Before enabling our new button in Online Editor UI, we need to add it to  ezplatform-admin-ui-alloyeditor-js as we did with our plugin before. In order to do so, we need to modify Resources/encore/ez.config.manager.js. so it will look like:

eZConfigManager.add({
    eZConfig,
    entryName: 'ezplatform-admin-ui-alloyeditor-js',
    newItems: [
        path.resolve(__dirname, '../public/js/alloyeditor/buttons/random.js'),
        path.resolve(__dirname, '../public/js/alloyeditor/plugins/random.js'),
    ]
});
ez.config.manager.js

And update assets by running:

$ yarn encore dev
update assets

Now our random button asset is loaded on all the pages where Online Editor is used - the Online Editor just needs to be configured to display it.

All the buttons that can control the Online Editor are shown in the editor toolbars. The toolbar is the container for the buttons. A different toolbar is displayed based on the Online Editor context. For example, EzListConfig toolbar is shown when you are editing some list. And EzTableConfig toolbar is shown when some table is being edited in Online Editor. The default toolbar is EzParagraphConfig. You can check out all available toolbars in the ezplatform-admin-ui source code.

The buttons for each toolbar are hardcoded. And right now there is no easy way to add a new buttons in some specific toolbar. But we are working on a pull request to make things more simple and will submit that soon. Our plan is to make it possible to set additional buttons in any toolbar just by using configuration changes. For now, though, we must override the toolbar.

1. Copy original base-rich-text.js

It is a simple step and you just need to the copy original file to your bundle:

$ cp \
vendor/ezsystems/ezplatform-admin-ui/src/bundle/Resources/public/js/scripts/fieldType/base/base-rich-text.js \
src/ExtendedAdminUIBundle/Resources/public/js/base-rich-text.js
copy base-rich-text.js

2. Using eZ Platform ASSET MANAGEMENT Replace original base-rich-text.js with ExtendedAdminUIBundle/Resources/public/js/base-rich-text.js

We used eZConfigManager.add in our custom ez.config.manager.js to add a new item into the existing entry. In this case we need to replace the existing item in the ezplatform-admin-ui-alloyeditor-js entry. So we will use eZConfigManager.replace. Just add the following lines to Resources/encore/ez.config.manager.js:

eZConfigManager.replace({
    eZConfig,
    entryName: 'ezplatform-admin-ui-alloyeditor-js',
    itemToReplace: path.resolve(__dirname, '../../../../vendor/ezsystems/ezplatform-admin-ui/src/bundle/Resources/public/js/scripts/fieldType/base/base-rich-text.js'),
    newItem: path.resolve(__dirname, '../public/js/base-rich-text.js'),
});
ez.config.manager.js

And update assets by running:

$ yarn encore dev
update assets

Now our custom base-rich-text.js is served instead of the original.

3. Make the required changes in ExtendedAdminUIBundle/Resources/public/js/base-rich-text.js

We are adding a new random button to EzParagraphConfig and EzCustomStyleConfig toolbars via this code:

const ezParagraphConfig = new window.eZ.ezAlloyEditor.ezParagraphConfig({ customStyles: this.customStylesConfigurations });
const ezCustomStyleConfig = new window.eZ.ezAlloyEditor.ezCustomStyleConfig({ customStyles: this.customStylesConfigurations });

const pluginButtons = ['random'];
ezCustomStyleConfig.buttons = ezCustomStyleConfig.buttons.concat(pluginButtons);
ezParagraphConfig.buttons = ezParagraphConfig.buttons.concat(pluginButtons);
base-rich-text.js

Modified toolbars are passed to AlloyEditor initialization.

At this point an assets update is required:

$ yarn encore dev
update assets

After all of these changes, if you will try to publish some content you will see a new button in the Online Editor. And if you click it random number will be added!

So that completes our journey, from developing a custom plugin to enabling it on the Online Editor by using our new eZ Platform asset management features. Some of the steps outlined here could be further optimized and will be simpler in the future eZ Platform releases. But we hope this example gave you a good perspective on the ways to extend the Online Editor.

Also, we said we would show you how to use already existing CKEditor plugin. It will be much simpler to do it if you followed all previous steps. Let's do it as the last part of this blog post!

Using existing CK Editor plugins

There are plenty of existing CK Editor plugins you might want to use in your project to extend the eZ Platform Online Editor. But please keep in mind - some of them are not compatible with the Alloy Editor, and they will not work out of the box. An example of such a plugin is Elements Path. So it is a good idea to check a plugin's description/documentation to find out if it works with Floating UI, which our Alloy Editor uses (eZ Platform Online Editor).

Also, plugins have dependencies. You can access them on the plugin download page. There will be no an error/warning message if you install a plugin and some of dependencies are not satisfied - the plugin will just not work. So its worth checking plugin dependencies before installing!

In this example, we will use the Insert Smiley plugin, which is compatible with our Online Editor and has no dependencies.

Just follow these steps to enable it in our eZ Online Editor.

1. Download plugin

ou need to download the plugin archive from its download page. And extract it to your bundle. Again, it worth to follow the eZ Platform convention and use Resources/public/js/alloyeditor/plugins/<PLUGIN_NAME>. So in our example the plugin will be extracted to Resources/public/js/alloyeditor/plugins/smiley.

2. Enable the plugin in eZ Platform

It is all about adding smiley to extra_plugins.

ezrichtext:
    alloy_editor:
        extra_plugins: [random, smiley]
config.ynl

3. Adding the plugin to assets

You can complete this step using Webpack Encore as was done for the random plugin we built in our previous example. However, there might be a lot of files in some CK Editor plugins, and it's not very easy to add all of them .Luckily there's an alternative way to load plugin sources on all pages where the eZ Online Editor is used:

If the plugin is not loaded (it is missing from assets), by default CK Editor uses ./plugins/<PLUGIN_NAME> relative path to load it. It is possible to set a custom path for the different plugin by using CKEDITOR.plugins.addExternal. You just simply provide the plugin name and its custom path. In our case it would be:

CKEDITOR.plugins.addExternal( 'smiley', '/bundles/extendedadminui/js/alloyeditor/plugins/smiley/' );
base-rich-txt.js

This code should be placed at the top of init function in modified base-rich-text.js.

An assets update is required after this change:

$ yarn encore dev
update assets

Now the new plugin sources are available, but its button is still not enabled. Let's add it too:

4. Enable button in the editor

First, you need to find the plugin button identifier; search it's source code for editor.ui.addButton. If the plugin does not provide any buttons you can always implement your own as per our first example. In this case the plugin provides a button with the Smiley identifier.

Next, we need to enable the Smiley button in the Online Editor toolbars. And you know the drill. It is all about adding Smiley to pluginButtons in our modified base-rich-text.js.

const ezParagraphConfig = new window.eZ.ezAlloyEditor.ezParagraphConfig({ customStyles: this.customStylesConfigurations });
const ezCustomStyleConfig = new window.eZ.ezAlloyEditor.ezCustomStyleConfig({ customStyles: this.customStylesConfigurations });

const pluginButtons = ['Smiley', 'random'];
ezCustomStyleConfig.buttons = ezCustomStyleConfig.buttons.concat(pluginButtons);
ezParagraphConfig.buttons = ezParagraphConfig.buttons.concat(pluginButtons);
base-rich-text.js

Afterward, another assets update is required:

$ yarn encore dev
update assets

Please keep in mind that we are going to submit a pull request which will make this step much easier. In the future, you should just need to modify configurations instead of overriding and editing base-rich-text.js.

If you reload the Online Editor, you will see a new button. It might look a bit odd, as its a default plugin button and it doesn't extend EzButton. But despite the strange look, it should work fine!

Conclusions

What a great effort! You read this blog post to the end. We hope you found a lot of new and inspiring information here. Now you should be able to extend and adapt the eZ Platform Online Editor to your needs. Please don't hesitate to get in touch if you have some questions or suggestions! Comments box below might be the simplest way to do it.

Leave us a comment
blog comments powered by Disqus