Building a WYSIWYG plugin in Drupal

This blog post is more than 6 years old, so the content may be out of date.

As developers, we usually praise and curse WYSIWYG editors equally. They all suffer from issues, but they're usually better than the alternative - trying to convince clients and content editors to use raw HTML? Not likely!

These usually work with input filters: say for example, you have a YouTube plugin which lets you enter a video ID and embeds the video in the post.

The input filter would probably take something like [youtube:mELysb2vcrc], and translate the code in square brackets into the HTML code required to embed the YouTube player.

Wouldn't it be great if - instead of having to remember silly square-bracket syntax - you could simply click the YouTube icon in the WYSIWYG toolbar, type the video ID into a popup box, and click OK? This is where wysiwyg integration comes in.

The module code

The module code's simple: you just implement a hook:

/**
 * Implementation of hook_wysiwyg_include_directory().
 *
 * This tells the Wysiwyg module to search within the 'plugins' directory for
 * Wysiwyg plugins.
 *
 * @param String $type
 * The type of plugin being checked. One of:
 * - editor: for WYSIWYG editors such as TinyMCE, CKEditor, Nice Edit, etc.
 * - plugin: for toolbar buttons such as bold, add-image, strike-through, etc.
 *
 * @return String
 * The path to the plugin directory (relative to this module). This is usually
 * simply the plugin-type: e.g. "plugin" or "editor".
 */
function wysiwyg_plugin_example_wysiwyg_include_directory($type) {
  return $type;
}

You then create a directory/file structure:

- plugins
  - $pluginName
    - $pluginName.css
    - $pluginName.js
    - images (if needed)
    - langs
      - en.js
  - $pluginName.inc

Within $pluginName.inc, you add a specially named hook implementation:

/**
 * Specially named implementation of hook_wysiwyg_plugin().
 *
 * Should be named {$module}_{$plugin}_plugin().
 */
function wysiwyg_plugin_example_foo_plugin() {
  $plugins['foo'] = array(
    'title' => t('Foo: an eample wysiwyg plugin'),
    // The 'icon file' is the icon that appears in the Wysiwyg toolbar.
    'icon file' => 'foo.toolbar_icon.gif',
    'icon title' => t('An example wysiwyg plugin'),
    'settings' => array(),
  );
  return $plugins;
}

The $pluginName.css file is required to avoid 404s, but can be empty if your plugin doesn't require custom css (there may well be a way to avoid this - comments/feedback welcome!)

The $pluginName.js file looks after what happens when you click the toolbar icon, or enable/disable the WYSIWYG editor. In this example, we simply add a HTML comment (which is represented in the WYSIWYG by an icon).

// $Id$
 
(function ($) {
 
Drupal.wysiwyg.plugins['foo'] = {
 
  /**
   * Return whether the passed node belongs to this plugin (note that "node" in this context is a JQuery node, not a Drupal node).
   *
   * We identify code managed by this FOO plugin by giving it the HTML class
   * 'wysiwyg_plugin_example-foo'.
   */
  isNode: function(node) {
    res = $(node).is('img.wysiwyg_plugin_example-foo');
    return ($(node).is('img.wysiwyg_plugin_example-foo'));
  },
 
  /**
   * Invoke is called when the toolbar button is clicked.
   */
  invoke: function(data, settings, instanceId) {
     // Typically, an icon might be added to the WYSIWYG, which HTML gets added
     // to the plain-text version.
     if (data.format == 'html') {
       var content = this._getPlaceholder(settings);
     }
     else {
       var content = '<!--wysiwyg_example_plugin-->';
     }
     if (typeof content != 'undefined') {
       Drupal.wysiwyg.instances[instanceId].insert(content);
     }
   },
 
  /**
   * Replace all <!--wysiwyg_example_plugin--> tags with the icon.
   */
  attach: function(content, settings, instanceId) {
    content = content.replace(/<!--wysiwyg_example_plugin-->/g, this._getPlaceholder(settings));
    return content;
  },
 
  /**
   * Replace the icons with <!--wysiwyg_example_plugin--> tags in content upon detaching editor.
   */
  detach: function(content, settings, instanceId) {
    var $content = $('<div>' + content + '</div>');
    $.each($('img.wysiwyg_plugin_example-foo', $content), function (i, elem) {
      elem.parentNode.insertBefore(document.createComment('wysiwyg_example_plugin'), elem);
      elem.parentNode.removeChild(elem);
    });
    return $content.html();
  },
 
  /**
   * Helper function to return a HTML placeholder.
   *
   * Here we provide an image to visually represent the hidden HTML in the Wysiwyg editor.
   */
  _getPlaceholder: function (settings) {
    return '<img src="' + settings.path + '/images/foo.content_icon.gif" alt="&lt;--wysiwyg_example_plugin-&gt;" title="&lt;--wysiwyg_example_plugin--&gt;" class="wysiwyg_plugin_example-foo drupal-content" />';
  }
};
 
})(jQuery);

I'm currently looking for a good place to contribute this knowledge to Drupal.org - possibly to the wysiwyg module or a new wysiwyg-examples module, and a handbook entry.

Comments

This is great info, thanks a lot!

thanks Marcus - exactly what I needed - will thank you in beer form some time soon.

Nice job! But I have a question: what if I want to create a dialog? How can I implement a dialog and show it?

Thanks dude,
very short and informative. Your function signature explanation {$module}_{$plugin}_plugin() saved my day ;)
@skiller: look at the wysiwyg_imageupload module to see how a dialog is built.

thanks
Paul

Lifesaver. This is literally the only place on the net I could find this info - and it still works in Drupal 7.14. (At first I wasn't sure where to create the plugins directory/file structure - but of course it just goes inside my own module directory.) Many, many thanks.

nice and very helpful guidelines.

Thanks helped me a lot

Great info here. Managed to work this out before finding your post but stuck on the next bit of my plugin. I am trying to use the tinyMCE windowManager to create a popup box to allow the user to make a selection. Please could you let me know how to do this using this method.

Native plugins like emoticons use this method:

ed.addCommand('mceExample', function() {
  ed.windowManager.open({
    file : url + '/example.php',
    width : 600 + parseInt(ed.getLang('example.delta_width', 0)),
    height : 600 + parseInt(ed.getLang('example.delta_height', 0)),
    inline : 1
  }, {
    plugin_url : url
  });
});

This was very helpful! Thank you for posting it!

My friend, where can i find more documentation on this subjext, particularly the javascript api. I need to make the buton show a popup window, recieve some input and then insert something in the wysiwyg, but i cant find mor information on the api, this is almost the only source i can find in the web.
Thank you!

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <apache>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo]. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.