Joomla! Programmers Documentation

Manual Index

Editors Plugin

This part describes how to develop an Editor plugin.

The Editor plugin works like following: The plugin register an Editor provider, on onEditorSetup event, then system will use it depend on what is selected in the global configuration and how the Editor field is configured in the form.

Events in this group:

onEditorSetup

onEditorSetup

The event triggered once at runtime, when system need to render the Editor field.

Event signature:

function onEditorSetup(Joomla\CMS\Event\Editor\EditorSetupEvent $event){}

Event attributes:

/**
 * @var Joomla\CMS\Editor\EditorsRegistry $subject
 */
$subject = $event->getEditorsRegistry();

Creating an editor plugin

The plugin consist with three main things:

  • The Editor provider class, which provide a logic for rendering input, and loading Editor Buttons (XTD) plugins.
  • The Editor JavaScript code for client side integration.
  • The Plugin in Editors group, which register the provider, so system know it existence.

Following example assume you already know how to create Joomla plugin.

Let's create a simple editor

In the example we display a <textarea> as our Editor.

Backend side

For start create a plugin under plugins/editors/example/ folder, call it example, assume namespace is JoomlaExample\Plugin\Editors\Example.

Then create a provider, which will live under plugins/editors/example/src/Provider/ExampleEditorProvider.php.

namespace JoomlaExample\Plugin\Editors\Example\Provider;

use Joomla\CMS\Application\CMSApplicationInterface;
use Joomla\CMS\Editor\AbstractEditorProvider;
use Joomla\Event\DispatcherInterface;
use Joomla\Registry\Registry;

/**
 * Provider for Example editor
 */
final class ExampleEditorProvider extends AbstractEditorProvider
{
    /**
     * A Registry object holding the parameters for the plugin
     *
     * @var    Registry
     */
    protected $params;

    /**
     * The application object
     *
     * @var    CMSApplicationInterface
     */
    protected $application;

    /**
     * Class constructor
     *
     * @param   Registry                 $params
     * @param   CMSApplicationInterface  $application
     * @param   DispatcherInterface      $dispatcher
     */
    public function __construct(Registry $params, CMSApplicationInterface $application, DispatcherInterface $dispatcher)
    {
        $this->params      = $params;
        $this->application = $application;

        $this->setDispatcher($dispatcher);
    }

    /**
     * Return Editor name, CMD string.
     *
     * @return string
     */
    public function getName(): string
    {
        return 'example';
    }

    /**
     * Gets the editor HTML markup
     *
     * @param   string  $name        Input name.
     * @param   string  $content     The content of the field.
     * @param   array   $attributes  Associative array of editor attributes.
     * @param   array   $params      Associative array of editor parameters.
     *
     * @return  string  The HTML markup of the editor
     */
    public function display(string $name, string $content = '', array $attributes = [], array $params = [])
    {
        // Check editor attributes and parameters
        $col     = $attributes['col'] ?? '';
        $row     = $attributes['row'] ?? '';
        $id      = $attributes['id'] ?? '';
        $buttons = $params['buttons'] ?? true;
        $asset   = $params['asset'] ?? 0;
        $author  = $params['author'] ?? 0;

        // Render the editor markup
        return '<joomla-editor-example>'
          . '<textarea name="' . $name . '" id="' . $id . '" cols="' . $col . '" rows="' . $row . '">' . $content . '</textarea>';
          . $this->displayButtons($buttons, ['asset' => $asset, 'author' => $author, 'editorId' => $id]);
          . '</joomla-editor-example>'
    }
}

Note: We use AbstractEditorProvider as base, it already contains a logic for loading and rendering Editor Buttons (XTD) plugins.

Here display() method renders the editor markup, it is important to keep the Editor Buttons (XTD) within the same container as an editor. We use <joomla-editor-example> custom element, this will simplify client side integration, however it can be just a <div> wrapper.

Now register the provider in the system with a plugin and event:

namespace JoomlaExample\Plugin\Editors\Example\Extension;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
use JoomlaExample\Plugin\Editors\Example\Provider\ExampleEditorProvider;

final class ExampleEditor extends CMSPlugin implements SubscriberInterface
{
    /**
     * Returns an array of events this plugin will listen to.
     *
     * @return  array
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onEditorSetup' => 'onEditorSetup',
        ];
    }

    /**
     * Register Editor instance
     *
     * @param EditorSetupEvent $event
     *
     * @return void
     *
     * @since   __DEPLOY_VERSION__
     */
    public function onEditorSetup(EditorSetupEvent $event)
    {
        $event->getEditorsRegistry()->add(new ExampleEditorProvider($this->params, $this->getApplication(), $this->getDispatcher()));
    }
}

At this point, after plugin installation and enabling, the editor already should be available in Global configuration, and render our textarea. However, we are still missing a client side integration, that will be a next step.

Frontend side

This is allowing other Joomla extensions interact with editor and its content. Create a JavaScript file under media/plg_editors_example/js/editor-example.js, and modify display() method of Editor provider, to load this file.

public function display(string $name, string $content = '', array $attributes = [], array $params = [])
{
    // Load the editor asset files
    $this->application->getDocument()->getWebAssetManager()
        ->registerAndUseScript(
            'plg_editors_example.editor-example',
            'plg_editors_example/editor-example.js',
            [],
            ['type' => 'module'],
            ['editors']
        );

    // Check editor attributes and parameters
    $col     = $attributes['col'] ?? '';
    $row     = $attributes['row'] ?? '';
    $id      = $attributes['id'] ?? '';
    $buttons = $params['buttons'] ?? true;
    $asset   = $params['asset'] ?? 0;
    $author  = $params['author'] ?? 0;

    // Render the editor markup
    return '<joomla-editor-example>'
      . '<textarea name="' . $name . '" id="' . $id . '" cols="' . $col . '" rows="' . $row . '">' . $content . '</textarea>';
      . $this->displayButtons($buttons, ['asset' => $asset, 'author' => $author, 'editorId' => $id]);
      . '</joomla-editor-example>'
}

The script should register editor instance, and provide a basic methods, for set/get value and replace selection, etc.

// Import required components
import { JoomlaEditor, JoomlaEditorDecorator } from 'editor-api';

/**
 * EditorExample Decorator which implements required methods per Editor instance.
 * Joomla will use this to share it between Extension, to interact with a main Editor instance.
 */
class EditorExampleDecorator extends JoomlaEditorDecorator {

  getValue() {
    return this.instance.input.value;
  }

  setValue(value) {
    this.instance.input.value = value;
    return this;
  }

  getSelection() {
    const input = this.instance.input;

    if (input.selectionStart || input.selectionStart === 0) {
        return input.value.substring(input.selectionStart, input.selectionEnd);
    }
    return input.value;
  }

  replaceSelection(value) {
    const input = this.instance.input;
    if (input.selectionStart || input.selectionStart === 0) {
        input.value = input.value.substring(0, input.selectionStart)
            + text
            + input.value.substring(input.selectionEnd, input.value.length);
    } else {
        input.value += text;
    }
    return this;
  }

  disable(enable) {
    this.instance.input.disabled = !enable;
    this.instance.input.readOnly = !enable;
    return this;
  }
}

/**
 * The editor initialisation
 */
class JoomlaEditorExample extends HTMLElement {
    // Element attached to DOM
    connectedCallback() {
        // Pick <textarea> input which is a first children in markup
        this.input = this.firstElementChild;

        // Register the Decorator in Joomla.Editor
        const jEditor = new EditorExampleDecorator(this, 'example', this.input.id);
        JoomlaEditor.register(jEditor);

        // Find out when editor is interacted
        // The script should tell to joomla when editor or one of Editor buttons (XTD) is interacted
        if (!this.interactionCallback) {
            this.interactionCallback = () => {
                JoomlaEditor.setActive(this.input.id);
            };
        }
        this.addEventListener('click', this.interactionCallback);
    }

    // Element removed from DOM
    disconnectedCallback() {
        // Unregister editor and unbind all events
        JoomlaEditor.unregister(this.input.id);
        this.removeEventListener('click', this.interactionCallback);
    }
}

customElements.define('joomla-editor-example', JoomlaEditorExample);

And all done 🎉 Now we have a new, fully integrated editor.