kusoglogo.png

Building Custom CK Editor Plug-ins


Target Audience

This document assumes you under JavaScript from an object oriented perspective. That means you know how to define a new JavaScript object. The CKEditor takes advantage of advanced JavaScript techniques like anonymous functions, objects, and heavily utilizes scope and visibility techniques available in JavaScript that I consider advanced. If you dont have a good handle on JavaScript, you may find this document challenging.

Introduction

CKEditor is one of the best JavaScript based html editors on the market today. It is maintained well, and is offered as an open source free project that you can also buy if you need a commercial license for it. One aspect of the editor that makes it so good is its openness for new features via plug-ins. In fact, version 3 uses its own plug-in infrastructure for all the main features of the product. This guide is written specifically for version 3 of CKEditor.

Understanding the big picture

if you look at the directory structure of the editor you will notice a plugins directory with about 66 subdirectories within it. Each of the subdirectories, such as "about", "basicstyles" and "blockquote", is a plugin. In a clean install, these are the main features of the product being exposed as plug-ins. The standard for the editor is one plug-in per subdirectory in the plug-ins directory. At the very minimum, each plug-in directory will have a plugin.js file contained within it. This is what the main editor JavaScript will load when it is told to use the plug-in.

Note: If you try to edit the standard plug-ins that come with the core product in the plugins directory, your changes will not get used by the editor. In an attempt to speed up the load time of the editor for version 3, the base distribution combines all the plug-ins scripts into the main ckeditor.js file. So even though there is a plugin.js in the plugins/about folder, it is not actually used at runtime because the script in that plugin.js is packaged into the ckeditor.js. This greatly reduces the time it takes to load the editor because there is less back and forth between the browser and the webserver to get the editor up and running. You can repackage the editor into a new ckeditor.js so that it includes your custom plug-ins or modifications to the standard plug-ins. This is discussed later in the documentation.

More complex plug-ins will also have a dialogs directory within its main plug-in directory. A good example of this are the standard plug-ins "table" and "about". The dialogs directory is the place where the plug-ins popup dialogs are defined (along with resources like images if needed), if the plug-in needs a dialog. When you are building a plug-in that will need to interact with the user after they trigger it via a toolbar button, one way to get information from the user is via the editors popup dialogs.

The editors popup dialog is a dynamic html based popup that appears within the main browser. While this gives it flexibility in look & feel, it does constrain it to the boundaries of the browser window. Another alternative is for the plug-in to show another browser window with an html page in it. I'll outline how to use this technique later in the document. Just keep in mind that there are at least 4 levels of plug-ins you can build:

  1. No user interface. The user clicks the button and some action is taken. The bold, italic, and underline toolbar buttons are a good example of this. They dont need to show an interface, they just change the content that is highlighted.
  2. Button drop down interface. The user clicks the button and a small drop down appears below the button. This is used by plug-ins such as "colorbutton", but the actual support for this ability comes from the "panelbutton" plug-in.
  3. CKEditor Dialog interface. The user clicks the button and a popup dialog appears. This is not a real dialog window, but a dynamic html window that is drawn onto the page using absolute positioning. The good thing about using this option for a complex interface for the user is that it is standard and your plug-in will look like the other standard dialogs from the editor. The bad thing is that it is constrained within the browser window. This is used by plug-ins such as "table", but the actual support for this ability comes from the "dialog" plug-in.
  4. Browser Window interface. The user clicks the button and the plug-in shows another browser window. The separate browser window will show the user interface to configure options similarly to the way the CKEditor dialog interface would, but can now be shown outside of the main browser window. With more and more users having two or more displays, this could be important for some plug-ins that want the user to be able to see both the plug-in and the entire document they are editing at the same time. If the dialog needs to be able to keep the user from interacting with the editor while they are using the plug-in option, you can show the separate browser window with the window.showModalDialog method which will accomplish this as well.

Building your first Plug-In

Regardless of the type of UI your plug-in will use, the following steps are needed to get all plug-ins started.

  1. The first step to creating a new plug-in will be to create a uniquely named directory in the main plugins directory within the ckeditor directory. Remember, the common practice is to have each plug-in be in its own directory.
  2. Create a new JavaScript file called plugin.js within your new plug-in directory. You must name the file plugin.js. No other file name will be recognized by the main editor scripts.
  3. Get the main structure of your plugin.js file in place. I've noticed several techniques being used in the standard plug-ins, but the most common seems to be a somewhat odd script format, but works well enough. The following shows the basic format of the script:
(function() {
 //...registration code here...
 })();
 //...Custom plug-in configuration options here...

What the above JavaScript does is define an "anonymous function" and then calls it all in one chunk of script.
I've broken the plugin.js work down into two main sections. The main registration code, and the custom plug-in configuration options.

4. Within the main registration code area of the anonymous function described in the previous step, create script that will register a new JavaScript object that gets used when the plug-in is activated. The main registration code calls the CKEDITOR.plugins.add method, which takes a unique name for the plug-in and a JavaScript object that has an init method in it. Because the plug-in name is used over and over in the script, it is typically assigned to a variable. Here is a simple example:

(function(){
 b='syrinxImage';
 CKEDITOR.plugins.add(b,{
 init:function(c){
 c.addCommand(b,a);
 c.ui.addButton('SyrinxImage',{
 label:'Insert Image',
 icon: this.path + 'toolBarButton.png',
 command:b
 });
 }
 });
 })(); 

In the above code example, the CKEDITOR.plugins.add method is being called with the plug-in name "syrinxImage" and a new object that does the work of setting up the options for the plug-in. The init method of the JavaScript object is doing two separate things:

a. Adding a new command. A command is the thing that actually does the work of the plug in, such as bolding text in the editor or showing a dialog to setup the number of rows and columns in a table and then inserting it into the editor.

b. Adding a new toolbar button. The toolbar button is taking two parameters, the first being the unique id to use for the toolbar button. The second parameter is an object that defines the label, icon and command to trigger for the button. The unique id used for the button is the name that is used when you are setting up a custom toolbar for the editor.

Adding a CKEditor Toolbar Button

Creating a plug-in and registering a button as shown in the above examples is not enough to have the button appear in the toolbar; you must also define a custom toolbar with the button, which uses the unique id you registered the button with. One way to setup a custom toolbar is to set the toolbar attribute in the object passed to CKEditor.replace to initialize the editor. Here is an example toobar with the new toolbar button being defined in this example:

[
 ['Source','-','Templates'],
 ['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'],
 ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
 ['Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'],
 '/',
 ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'],
 ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote'],
 ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],
 ['Link','Unlink','Anchor'],
 ['SyrinxImage','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'],
 '/',
 ['Styles','Format','Font','FontSize'],
 ['TextColor','BGColor'],
 ['Maximize', 'ShowBlocks','-','About']
 ]

Notice the "SyrinxImage" unique id being used in the toolbar definition above. The image used for the button is defined with the icon attribute of the object being passed to the addButton method. Notice how in the code example it is using this.path, which will be set to the directory of the plug in. So in this example, the "toolBarButton.png" is located directly within the plug-in directory "syrinxImage".

Note that in the above "code snippet", the actual action to call when the command is trigger is passed as the variable "a" which is not defined in the code. It has been removed to stay focused on the CKEDITOR.plugins.add method being called.

5. Define the action object for the command. In the previous step, the init method was calling the addCommand and passing in an undefined variable "a". The "a" variable is assigned to a JavaScript object as shown in the following example:

var a= {
 exec:function(editor){
 var media = window.showModalDialog(editor.config.syrinx_siteBase + "/popups/InsertImagePopup.aspx?T=Insert Image",null,"dialogWidth:750px;dialogHeight:500px;center:yes; resizable: yes; help: no");
 if(media != false && media != null)
 {
 if(media.mediaUrl.substr(media.mediaUrl.length-4) == ".wmv")
 {
 var x = '<div title="click to play video" onclick="showMediaClip(this,'' + media.mediaUrl + '',' + media.width + ',' + media.height +
 ')" style="cursor: pointer"><img alt="" src="' + media.mediaImage + '" /></div>';
 editor.insertHtml(x);
 }
 else
 editor.insertHtml("<img src='" + media.mediaUrl + "' />");
 }
 }
 }

In this example, we are using a second browser window via the window.showModalDialog rather than the CKEditor dialog. When going with this approach the command action displays the modal dialog browser window which returns a JavaScript object that indicate the image that was selected by the user from the popup window.

We use the JavaScript object to insert the needed html into the editor via the insertHtml method.
Notice the call to window.showModalDialog is using "editor.config.syrinx_siteBase" as part of the first parameter being passed. That is a custom plug-in config option.

6. In the custom plug-in configuration options section of the plugin.js file, you would define as many plug-in options you need to control the plug-in who's values can be specified by the editor in the same way as other editor options are provided. In this example, we just define it as an empty string, as such:

CKEDITOR.config.syrinx_siteBase = "";

The above code is actually adding a new attribute to the CKEDITOR.config JavaScript object. This is essentially the default value for the property. When you initalize the editor with the CKEDITOR.replace call, you would use the syrinx_siteBase as an attribute of the object passed as the 2nd parameter to replace, where the value would override the default value defined in the plug-in.

The entire plugin.js file being referenced above looks like the following:

(function(){
  var a= {
    exec:function(editor){
      var media = window.showModalDialog(editor.config.syrinx_siteBase + 
                  "/popups/InsertImagePopup.aspx?T=Insert Image",null,
                  "dialogWidth:750px;dialogHeight:500px;center:yes; resizable: yes; help: no");

      if(media != false && media != null)
      {
        if(media.mediaUrl.substr(media.mediaUrl.length-4) == ".wmv")
        {
          var x = '<div title="click to play video" onclick="showMediaClip(this,'' + media.mediaUrl + '',' + media.width + ',' + media.height +
          ')" style="cursor: pointer"><img alt="" src="' + media.mediaImage + '" /></div>';
          editor.insertHtml(x);
        }
        else
          editor.insertHtml("<img src='" + media.mediaUrl + "' />");
      }
    }
  },

  b='syrinxImage';
  CKEDITOR.plugins.add(b,{
    init:function(editor){
      editor.addCommand(b,a);
      editor.ui.addButton('SyrinxImage',{
        label:'Insert Image',
        icon: this.path + 'toolBarButton.png',
        command:b
      });
    }
  });

})();

CKEDITOR.config.syrinx_siteBase = "";

The key to remember in the above plugin.js example is that the main work that happens when the plug-in toolbar button gets selected is happening in the exec method of the object passed into the command object. If you would rather use the CKEditor Dialog UI rather than the standard browser dialog, you would modify the exec method to show the CKEditor Dialog.

Using the CKEditor Dialog

If you want your plug-in to have a more integrated feel to it, and dont mind the limitation of being constrained to being over the main content as a absolute positioned element, then your init method of the object passed to the CKEDITOR.plugins.add method can use the CKEditor dialogs. All of the standard plug-ins use this for a complex plug-in UI. The init method would need to define the new dialog with the CKEDITOR.dialog.add method as well as changing the way the command is added. In the example above that uses the standard browser window for the UI, we registered a basic command object. With a CKEditor dialog, we would register a CKEDITOR.dialogCommand. The following example demonstrates this:

CKEDITOR.plugins.add('syrinxCode', {
  init: function(a) {
    var b = 'syrinxCode';
    CKEDITOR.dialog.add(b, this.path + 'dialogs/code.js');
    a.addCommand(b, new CKEDITOR.dialogCommand(b));
    a.ui.addButton('SyrinxCode', { label: 'Insert Code Block', command: b });
    //...remaining left out for brevity.
  }
});

Notice the call to CKEDITOR.dialog.add in the example above is using this.path again. This is the same as the example used in the browser window UI example previously. This time we adding "dialogs/code.js" to the plug-in path. It is standard for plug-ins to put their dialog JS files in the dialogs folder.

Another point to notice in the above code is the command being registered. In this example, we are not passing in a custom command object that has an exec function. Instead, we are creating a new dialog command and passing in the dialog id we used when we defined the dialog with the add method.

Defining Dialogs with JavaScript

As part of the effort to make the editor load faster and perform better, the standard dialogs are all built with JavaScript only. There are no separate html files (with the exception of the wsc plugin for the spellchecker which also has some server interaction to do the spell checking). This means that the UI of the dialog is built up by adding UI elements to the dialog using JavaScript. The processing is somewhat cumbersome and can add up to a decent amount of JavaScript, but when the editor is packing all of its JavaScript into one large JS, it is still just one file to download rather than dozens.

Your dialog JavaScript file should have a similar style structure as the main plug-in JavaScript. A self calling anonymous JavaScript function. The follow code shows the basic structure, minus the bulky dialog UI building code:

(function() {
  CKEDITOR.dialog.add('syrinxCode', function(e) { return buildDialog(e, 'syrinxCode'); });
})();

For reasons unknown to me at this point, the function calls the CKEDITOR.dialog.add again, just like the main plug-in code does. However, this time it passes in a function that should return an object that represents the dialog. In the code above, we are calling another function, buildDialog, which will define the dialog object and return it.

Using the CKEditor Drop Down Panel

this section still needs to be written. Coming soon!

Defining a CKEditor Dialog

This section still needs to be written. Coming soon!