Building a WYSIWYG plugin in Drupal

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

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>, <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.