I recently developed a simple module for ContentBox called jsFiddle. Basically, it allows you to insert “fiddles” from the excellent http://jsfiddle.net into your posts and pages. In developing this module (and CKEditor plugin), I wanted to be able to insert complex data into the content editor, but then be able to re-edit that content after insertion. I’m pretty happy with how it turned out.

In the following paragraphs, I’m going to walk through the process of developing a ContentBox module that includes a CKEditor plugin which is capable of content re-editing. Hopefully this will inspire some others to develop some killer new ContentBox modules with helpful CKEditor plugins.

NOTE: In this walkthrough, i will include snippets of code, but most of them will be severely trimmed for the purposes of this post. If you really want to follow along with the full code, please grab it from the GitHub repo. Furthermore, I’m not advocating that this is the “best” or “only” way to accomplish this. My only intention is to share some ideas about things that have worked for me. So if you have corrections or suggestions for how things can be done better, I’m always ready and willing for civil, constructive feedback 🙂

Some Context

Before we dive in and start developing a module, let’s talk in a bit more depth about what we want to accomplish. First, we need a true module that will let us save “settings” for our module. For this example, we want to save some default values as our settings. (I won’t cover this here, but the process is pretty straightforward and is covered really nicely by Andrew Scott here).

Next, we need to develop a CKEditor plugin, since we want to allow people to be able to insert “fiddles” into their content. This should, at the very least, allow for the following:

  • Shiny icon in the toolbar that the content editor can click to bring up a modal dialog and select their fiddle
  • Allow for right-click context menu addition from within the content editing area itself
  • A nice visual representation of our inserted, complex data
  • The ability to edit our complex data, even after we’ve inserted it (without having to hack away in source mode)

Finally, we need a way to transform our complex data into the actual content that we want to display on our page (in this case, an iFrame that shows our “fiddle”).

Sound good? Ok, let’s start!

Create Our Module

First things first, let’s create out module. In ContentBoxRoot/modules/contentbox/modules, create a new folder and call it whatever you want to name your module. In this example, I’m naming mine “jsFiddle“.

Next, create the following folders:

  • handlers
  • includes
  • interceptors
  • model
  • views

Creating a CKEditor Plugin

Since this article is primarily geared at creating a plugin for CKEditor, let’s start there.

In your includes folder, create another folder named whatever you’d like your CKEditor plugin to be called. I named mine “cbjsFiddle”. In this folder, create the following:

  • editor.css – this is stylesheet of custom CSS that will be applied to the CKEditor and allow us to “style” the content we insert into the editor
  • jsfiddle.png – An icon that will be used in the toolbar and context menu within CKEditor. Use whatever icon you want, and name it however you like
  • plugin.js – This is the brain of your plugin and will contain all the logic to make your plugin work within CKEditor
NOTE: When your module is finally complete, they files will originate from this location, and be copied into the contentboxroot/modules/contentbox-admin/includes/ckeditor/plugins folder. For the purposes of development, go ahead and make that copy now and put it in the target path. Once you’re done with development, you can copy the finished set of files back into your module. For now, though, we’ll do development in the destination path.
One more thing. Be sure to check out the CKEditor 4 API documentation. It’s built on the excellent JSDuck documentation generator (the brain-child of the awesome guys at Sencha), so is super-easy and fun to use.
Ok, one more thing ;). A LOT of the following is not my own, but is either a straight copy, or some modification of examples of plugins and modules that already come with ContentBox. The rest is digging into the CKEditor docs and hacking until something works!

Plugin.js

While we will be filling it up with a bunch of code, and CKEditor plugin is really nothing more than a simple self-executing anonymous function. So let’s start with that:

(function(){ 
    // bunch of awesome plugin code here 
})();

Register the Plugin

The next step is to register the plugin with CKEditor. This is done very easily via the CKEditor.plugins class’s add() method:
CKEditor.plugins.add( 'cbjsFiddle', {
    // a CKEditor.pluginDefinition here...
});
As the add() method expects a plugin definition, we will provide that. There are a few options you can configure, including:
  • lang – array of language files that are available for this plugin
  • requires – other plugins upon which this plugin is dependent
  • init() – initialization function called when the instance of the plugin is created
CKEditor.plugins.add( 'cbjsFiddle', {
    init: function( editor ) {
        // all of our plugin's guts
    }
});

For our purposes, we’ll focus on the init() method. Since we have our init() now defined, the remainder of the code I show will assume that we are within the init() method.

Adding Our Plugin to the Toolbar

First things first: let’s get an icon into the CKEditor toolbar! To do this, we simply leverage the CKEditor.ui API to add a button. We can call the addButton() method, which merely requires:

  • The name of our plugin
  • The button definition
    • label – the help text that will appear when you hover over the icon
    • icon – the path to the icon you want to use for the button
    • command – the name of the command you want to execute when this button is clicked

Here’s the final version:

// add button to toolbar 
editor.ui.addButton('cbjsFiddle',{ label:'Embed a fiddle from jsFiddle',   icon: this.path + 'jsfiddle.png', command:'cbjsFiddle' });

For right now, don’t worry about the “command” part…we’ll come back to it. Let’s finish the UI first.

Adding Plugin Actions to Context Menu

As we noted in the introduction, we want our plugin to have two roles in the context menu:

  • Option to insert new “fiddle”
  • Option to edit selected “fiddle”

To do this, we need to add two new menu items to the context menu, one for inserting and one for editing. Adding a menu item is simple. Using the instance of the CKEditor.editor class which is passed to our init() method, we simply do the following:

// 'insert fiddle' menu item
editor.addMenuItem('cbjsFiddle', {
    label: 'Embed jsFiddle',     
    command: 'cbjsFiddle',     
    icon: this.path + 'jsfiddle.png',     
    group: 'contentbox' 
});
// 'edit fiddle menu item
editor.addMenuItem( 'jsFiddleItem',{
    label : 'Edit Fiddle',
    icon: this.path + 'jsfiddle.png',
    command : 'fiddleDialog',
    group : 'clipboard'
});

Pretty straightforward. For each item, we use addMenuItem(), which simply expects an arbitrary name, and a menu item config.

Excellent. So at this point, our plugin should technically *work*. However, we still have a bit of work to do to get the plumbing connected.

Add Some Commands

When we added our toolbar icon, as well as our context menu icons, you’ll remember that each button had a “command” config. As mentioned before, this simply instructs CKEditor which command (user-defined, in our case) it should execute when that button is clicked. Of course, at this point, we haven’t defined what those commands are, so our plugin does not yet do anything. Let’s change that.

To register a command with CKEditor, you simply call the addCommand() method on the instance of the CKEDITOR.editor class which was passed in the init method of our plugin. The addCommand() method requires two arguments:

  • commandName – The identifier name of the command (e.g., the command being called from our buttons)
  • commandDefinition – The command definition that will be applied to the execution of the command

Let’s first add a command for the “cbjsFiddle” command name that we added to the toolbar button and the ‘insert fiddle’ button:

editor.addCommand( 'cbjsFiddle', {     
   exec: function( editor ) {         
      openRemoteModal( getModuleURL( 'jsFiddle', 'Home.entry' ), {             
         editorName: editor.name         
      }     
   } 
});

Let’s unpack this a bit. First, in the commandDefinition, we define what we want to happen when the CKEDITOR.commandDefinition.exec() method is fired, which is automatically called when the button is clicked. This method passes an instance of the CKEDITOR.editor class, which we can use to do all manner of things. In this case, we’re calling the openRemoteModal() method, which is defined as part of the core ContentBox JavaScript. This method takes 4 arguments:

  • url – the URL that will be loaded
  • params – any params that will be passed to the URL, eventually being added to the RequestCollection
  • width – the width of the modal window
  • height – the height of the modal window

In short, this method will make an AJAX request to the URL of our choice and pass along any parameters that we want. When the response is received, it will then render the data in the response into a modal window for us. In this particular plugin, we are sending a request to the “Home” handler and executing the “entry” method. This method (as we’ll see later) will render a view that contains some form fields and other content that will let us interact with our module however we want. More on that later.

Add Some Listeners

So we’ve successfully added some commands for what happens when we click the buttons in our plugin. However, the buttons in the context menu aren’t going to show up automatically…we need to tell the Context Menu plugin about them. Fortunately, this is incredibly simple to do:

editor.contextMenu.addListener(function(element, selection) {
    return { cbjsFiddle: CKEDITOR.TRISTATE_ON }; });
editor.contextMenu.addListener( function( element ) {     
    // do some extra stuff here...later...just be patient :)     
    return { jsFiddleItem : CKEDITOR.TRISTATE_ON }; 
});

The listeners are very simple. We simply add an event listener to the Context Menu plugin that already exists on the editor (e.g., “contextMenu”). When the context menu event occurs (e.g., when you right-click in the editor), it will match up the passed keys (cbjsFiddle, jsFiddleItem) with the names of the menu items that we created earlier. And because we specify that each of these are “TRISTATE_ON” (CKEditor-speak for “on” or “active”), they will be displayed as configured in the context menu. Easy, right? We have a bit more to add to this later, but now that at least the shell of our plugin is complete, we need to move on to some ContentBox module goodness.

Adding the Plugin Programmatically to CKEditor

While we’ve created the physical plugin, CKEditor still doesn’t know about it at all. Yep, even though our plugin fully lives within CKEditor’s plugin location, the instance of CKEditor that loads when editing a page or post still needs to be told that the plugin exists AND that we want it to load it as part of CKEditor’s initial configuration. Fortunately, this is incredibly easy to do by creating an interceptor within our ContentBox module.

To do this, create a new file within your module’s “interceptors” folder. You can call it anything you like; I named mine “jsFiddle.cfc”. Here’s what it looks like:

component extends="coldbox.system.Interceptor"{
  /**     
   * CKEditor Integrations     
   */   
  function cbadmin_ckeditorExtraPlugins( required any event, required struct interceptData ){       
    arrayAppend( arguments.interceptData.extraPlugins, "cbjsFiddle" );   
  }   
  /**     
   * CKEditor Integrations     
   */   
  function cbadmin_ckeditorToolbar( required any event, required struct interceptData ){   
    var itemLen = arrayLen( arguments.interceptData.toolbar );     
    for( var x =1; x lte itemLen; x++ ){     
      if( isStruct( arguments.interceptData.toolbar[x] )       
          AND arguments.interceptData.toolbar[x].name eq "contentbox" ){         
        arrayAppend( arguments.interceptData.toolbar[x].items, "cbjsFiddle" );         
        break;       
      }     
    }   
  } 
}

Pretty straightforward. Out of the box, ContentBox’s page and post editors announce, among others, two specific events relating to the CKEditor:

  • cbadmin_ckeditorExtraPlugins
  • cbadmin_ckeditorToolbar

They are both fairly straightforward to use. In the ckeditorExtraPlugins, we simply append our custom plugin’s folder name to an already-existing array of plugins. And in ckeditorToolbar, we roll over all the toolbar “groups” until we come to the “contentbox” group, where we then append our custom plugin’s name.

With these in place, CKEditor will know:

  • That there is a plugin, located in the cbjsFiddle folder that it should load as part of its plugin loading regimen
  • That there is a toolbar item named cbjsFiddle that it should add to the “contentbox” toolbar group. Notice that the name we specify here is precisely the same as that which we used when we did addButton(…) earlier.

Last thing: we need to let our module know about our interceptor. In our module’s ModuleConfig.cfc, add the following in configure():

// Custom Declared Interceptors
interceptors = [     
    { class="#moduleMapping#.interceptors.jsFiddle", name="jsFiddle" }  
];

Alright. At this point, we should be able to reload the application, go to the content editor, and notice three things:

  1. A new shiny icon exists in the toolbar
  2. When we right-click in the content editor area, we should see an option for inserting a “fiddle”
  3. When we right-click in the content editor area, we should see an option for editing a “fiddle”

We still have a bit to do to bring this altogether, but our plugin is technically working. Woot!

Creating Views for Our Modal Window

Remember when we added the “commands” to the buttons in our plugin, and how those commands eventually called the openRemoteModal() method, which I said would make an AJAX request to retrieve a view that would display data? Well, let’s go ahead and make those views.

First, in our module’s views folder, add the following files:

  • entry.cfm
  • entryHelper.cfm

Entry.cfm

This view will be the main display workhorse for our module. While I won’t post the entire view here, the gist of what we want to create is a view that will allow the user to be able to specify various details about the fiddle that they want to insert into the CKEditor. This view can literally be whatever you want it to be, can contain any content you’d like, and interact with the rest of your module in whatever way you want. Really, the most important thing is that we are able to easily extract data from it in JavaScript so that we can later let CKEditor know about the choices that have been made. For this reason, I wrap all the user-selection options in a <form>…this makes it super simple to get the inputs in JavaScript later on.

So here’s a very skeletal version of entry.cfm:

<h2>Insert jsFiddle</h2>
#html.startForm(name="jsFiddleForm")#  /* all sorts of input fields here... */  <button class="button2" onclick="insertFiddleByURL( this );return false;"> Insert Fiddle </button> #html.endForm()#

To be honest, the most important thing about this view is the button, wherein I’ve specified that the insertFiddleByURL() method should be fired when the button is clicked. That’s it. Let’s create that function now.

EntryHelper.cfm

By convention, when ColdBox loads a view, it will automatically load any other view that has the following naming convention: {viewName}Helper.cfm. These “helper” views are really handy for including extra, non-HTML type code and logic for your view, like CSS, JavaScript, etc. While not necessary whatsoever, it’s just another way to clean up your code, keeping presentation separate from functionality.

But I digress. In entryHelper.cfm, let’s add the insertFiddleByURL() method:

/*
 * Insert a fiddle manually by providing the URL
 */
function insertFiddleByURL( btn ) {
  var inputs = $( btn ).parent().find( 'input' );
    prepareFiddle( inputs );
} 

/*
 * Common method to prepare fiddles for insertion...and insert them
 * @inputs - collection of inputs
 */
function prepareFiddle( inputs ) {
  var vals = [],
      iframe = '';
  inputs.each(function() {
    if( this.type=='checkbox' ) {
      vals.push( this.checked ? true : false );
    }
    else {
      // add values to array
      vals.push( this.value );  
    }
  })
  if( vals[0]=='' ) {
    alert( 'Please enter a URL!' );
    return false;
  }
  // create double-mustache syntax
  element += 'jsFiddle - {1}'.format( vals[0], vals[1], vals[2], vals[3], vals[4], vals[5], vals[6], vals[7], vals[8] );
  // insert into editor
  sendEditorText ( element );
}
function sendEditorText(text){ $("###rc.editorName#").ckeditorGet().insertElement(  CKEDITOR.dom.element.createFromHtml( text ) );  closeRemoteModal(); }

As you can see, insertFiddleByURL() inevitably calls the prepareFiddle() method. Here, we take the values of all the user inputs, and create a faux-HTML element called “fiddle”, and add an attribute for each of the data values that we want to send through to CKEditor. Finally, we hand all this off to the sendEditorText() method.

This method is the lynchpin between our module’s view and the CKEditor plugin that we created. If you remember, when we originally call openRemoteModal() from our plugin’s commands, we pass the “editorName” value as a parameter to our view. Now that we’re in the view, we can reference this same property, which allows us to get a reference, in JavaScript, to the instance of the CKEditor on our page. With this reference, we can now call the CKEDITOR.dom.element.createFromHTML() method, and insert our custom “fiddle” element into the content editor as fully editable content. Awesome.

If we now go back to the content editor, we should see a new “fiddle” element in the content editor. Moreover, if we switch to source mode, we should see something like this:

<fiddle css="false" 
        height="400px" 
        html="true" 
        js="false" 
        resources="false" 
        result="true" 
        src="http://jsfiddle.net/existdissolve/XKvc4/" width="80%">
    jsFiddle : Add Items
</fiddle>

Warning: As it turns out, this doesn’t work in IE8. Not only does the DOM implementation of IE8 break when trying to insert a “fiddle” element, but it also has no idea how to style it (see below). In my next post, I’ll show how to get around this.

How Do You Style a “Fiddle”?

If you’ve successfully inserted a “fiddle”, you’ll quickly notice something: All you see is the “title”, or the inner contents of the fiddle. This is reasonable: after all, “fiddle” is not a standard HTML element, so the browser doesn’t really know what to do with it in terms of layout, styling etc. That’s okay, though. Since our new HTML element is still technically a valid element, CSS can still interact with it. Because of this, we can easily style our fiddle however we want by simply adding a little bit of CSS.

So let’s go back to our custom plugin folder. Remember how we created that editor.css file early on? Go ahead an open it up, and paste the following:

fiddle {
    border: dotted 2px #333;
    color: #fafafa;
    border-radius: 4px;
    padding:10px;
    background-color:#3D6E99;
    display:block;
    font-weight:bold;
    width: 80%;
}

With that done, we simply need to let CKEditor know about our custom stylesheet. Back in our plugin.js, let’s add the following to the init() method:

// add a custom stylesheet to the editor so we can style the  tag
if( typeof CKEDITOR.config.contentsCss=='object' ) {
     CKEDITOR.config.contentsCss.push( this.path + 'editor.css' );
}  
else {
     CKEDITOR.config.contentsCss = [CKEDITOR.config.contentsCss, this.path + 'editor.css'];
}

Pretty straightforward. CKEDITOR.config has a property where you can define either a single stylesheet, or array of stylesheets that will be used by CKEditor. This simply adds our custom stylesheet to this. Whoop-dee-doo. 🙂 

Now that we’ve added our custom stylesheet, we should be able to reload the page, insert another fiddle, and now see a pretty, blue, block-level element that represents our “fiddle”. While not necessary by any means, it is a nice bit of visual feedback to the user to let them know what they heck they are looking at.

Rendering Our Fiddle

At this point, if we were to save our post or page and load the published version of our content, the “fiddle” would render just fine. However, all we would see is the inner content of our fiddle, which is not exactly what we’re after. What we really want is to transform our fiddle from a CKEditor-editable element into an iFrame that renders the actual content of the remote fiddle we are trying to present.

The answer, of course, is found back in our interceptor. In our interceptor, we can now add a listener for the announcement of cb_onContentRendering, another built-in ContentBox interception point. By intercepting at this point, we’ll be able to interact with the pre-rendered content, modify it as we need to, and send it on its way to be rendered the way we want it. The benefit of this approach, of course, is that we can keep the content (the “fiddle” syntax) in the database as is, but arbitrarily render it in whatever way we want.

Our cb_onContentRendering() looks like this:

function cb_onContentRendering( required any event, required struct interceptData ) {
  // regex for fiddle tag syntax
  var regex 	= "<fiddle\b[^>]*>(.*?)</fiddle>";
  // get string builder
  var builder = arguments.interceptData.builder;
  // find regex matches 
  var targets = reMatch( regex, builder.toString() );
  var replacer = "";
  // loop over all matches
  for( var match in targets ) {
    // get attributes
    var attributes = reMatch( '[a-z]+=\"[a-zA-Z0-9\.\?\&/:%]+\"', match );
    var urlStatic = "result,js,css,html,resources";
    var urlArgs = "";
    replacer = "";
    replacer &= "<iframe "; 			
    // loop over attributes and deal with them as needed 			
    for( var attribute in attributes ) { 				
      switch( listGetAt( attribute, 1, "=" ) ) { 					
        case "result": 					
        case "js": 					
        case "css": 					
        case "html": 					
        case "resources": 					 						          
          if( listGetAt( attribute, 2, "=" )=='"true"' ) { 							
            urlArgs = listAppend( urlArgs, listGetAt( attribute, 1, "=" ) );
          } 						
          break; 					
        case "height": 					
        case "width": 						
          replacer &= " #listGetAt( attribute, 1, '=' )#=#listGetAt( attribute, 2, '=' )#";	 						        
          break; 					
        case "src": 						
          var theurl = replace( listGetAt( attribute, 2, "=" ), '"', '', 'all' ); 							 
            theurl &= right( theurl, 1 )=="/" ? "embedded/" : "/embedded/"; 							
            theurl &= ListInCommon( urlArgs, urlStatic ); 						
            replacer &= " #listGetAt( attribute, 1, '=' )#=""#theurl#"""; 						
            break; 				
      } 			
    } 			
    replacer &= ">";
    // find the match syntax position
    var pos = builder.indexOf( match );
    // get the length
    var len = len( match );
    while( pos gt -1 ){
      // Replace it
      builder.replace( javaCast( "int", pos ), JavaCast( "int", pos+len ), replacer );
      // look again
      pos = builder.indexOf( match, javaCast( "int", pos ) );
    }			
  }
}

A lot of code, but very basic in what it’s doing. First, I define a regex to find all the “fiddle” elements. Next, I loop over the attributes of the fiddle elements, and turn them into the query string for the iFrame URL. Once my full replacement string is ready, I simply replace the old “fiddle” element with my new, desired iFrame. That’s it!

So we now have a fully functioning module. Not only have we created a custom CKEditor plugin that works, but we have also created a fancy interceptor that will take our fake “fiddle” element and convert it into the actual content that we want to render on our pages and posts.

We could stop here, but we haven’t yet fulfilled all of our goals. Yes, we can insert a fiddle, and yes we can render it. But what if we want to edit an existing fiddle? Right now, the only way to do that is via the Source Mode in CKEditor. Not awesome. Let’s fix it.

Creating an Editable Fiddle

Up to this point, you may have been wondering why we inserted a “fiddle” element at all, when we could have just as easily inserted an iFrame with all the properties it needs to have. The answer is simple: usability.

When you insert an iFrame, you get an ugly red box that says “iFrame”. Yes, you can edit this iFrame, but it’s about as useful as editing in Source Mode. So for me, I decided to go with the “fiddle” approach so that 1.) we could insert a visually-meaningful element into the content editor and 2.) so that we can easily edit the properties of this element via CKEditor’s element API.

So let’s dive in. Going back to our CKEditor plugin, go ahead and re-open plugin.js. We’ll now fill in some of the gaps that I skipped over in the original pass.

Context-Aware Context Option

Earlier, we added two new context-menu items, one for inserting and one for editing. While the “insert” button should probably always show up, we don’t necessarily want the “edit” option to show up all the time. For example, if you’re editing a paragraph element, it doesn’t make sense that you should see an option to edit a fiddle. So what we really want is to restrict the display of the “edit” option in the context menu to only show up when we are currently in the context of a “fiddle” element.

With this in mind, let’s re-work our listener a bit:

editor.contextMenu.addListener( function( element ) {
    if ( element.getAscendant( 'fiddle', true ) ) {
        return { abbrItem: CKEDITOR.TRISTATE_OFF };
    }
});

This is more or less the same as before. The only addition is that we are now checking to make sure that the current contextual element is a “fiddle” element. We do this via the getAscendant() method. With this modification, we should now only see the “edit” option in the context menu IF we are currently within the context of a fiddle element. Neat.

Another Call to openRemoteModal()…but with Data!

The next thing we need to do is to modify the “command” that gets fired when our now-contextually aware “edit” option is fired. When inserting, we simply call the openRemoteModal() method, which in turn renders (via AJAX) the contents of our entry.cfm view. When editing a fiddle, however, we need our view to be aware of the data that already exists for the selected fiddle. To do this, we will still call openRemoteModal(). But this time, we’ll first “collect” the data from our fiddle’s attributes, and send them along to our view. Here’s what our modified “command” looks like:

editor.addCommand( 'fiddleDialog', {
    exec:function( editor ){
        var element = editor.getSelection().getStartElement();
	// Open the selector widget dialog.
	openRemoteModal( getModuleURL('jsFiddle', 'Home.edit'), {
            url: element.getAttribute( 'src' ),
            title: element.getText(),
            height: element.getAttribute( 'height' ),
            width: element.getAttribute( 'width' ),
            js: element.getAttribute( 'js' ),
            html: element.getAttribute( 'html' ),
            css: element.getAttribute( 'css' ),
            resources: element.getAttribute( 'resources' ),
            result: element.getAttribute( 'result' ),
            editorName: editor.name
        });
    }
});

This is exactly like what we did for the “insert” command. However, we’ve souped this one up a bit. First, we get the current “selection” or “element” from the CKEditor. Since we already ensured that we were within a “fiddle” element in order to even execute this command, all that’s left is to retrieve the actual element reference from CKEditor. Once we get the element reference, we can use the getAttribute() and getText() methods to build up a params object to send along with our AJAX request. Now, when the AJAX request is made, the data from our element will be sent as well, and we can access all the data in a view via the request collection.

Once we’ve passed the data successfully to our “edit” view (in this case, I just made a separate edit.cfm and editHelper.cfm in views), we can add one final method to help us collect any data re-modified in our view and update the “fiddle” element in the CKEditor. The following is a slightly different version of the sendEditorText() method in views/editHelper.cfm:

function sendEditorText( vals ){
    var editor = $("###rc.editorName#").ckeditorGet(),
    element = editor.getSelection().getStartElement();
    // update element attributes and text
    element.setAttribute( 'src', vals[ 0 ] );
    element.setAttribute( 'height', vals[ 2 ] );
    element.setAttribute( 'width', vals[ 3 ] );
    element.setAttribute( 'result', vals[ 4 ] );
    element.setAttribute( 'js', vals[ 5 ] );
    element.setAttribute( 'resources', vals[ 6 ] );
    element.setAttribute( 'css', vals[ 7 ] );
    element.setAttribute( 'html', vals[ 8 ] );
    element.setText( vals[ 1 ] );
    closeRemoteModal();
}

If you remember, in our “insert” method, we prepared an entire “fiddle” element and inserted it into CKEditor. Since we already have an element and only want to edit its attributes, in this version of sendEditorText(), we simply retrieve the “context” element that we wish to edit, and set its attributes to the values collected from our view’s form. This will automatically update the “fiddle” element in CKEditor, without the need of removing it and re-inserting it.

Writing Plugin to the Plugins Folder

At this point, we’re completely done with our plugin. The last thing we need to do is to modify the internals of our module so that it will automatically copy the contents of our module’s plugin folder and add it to the correct location within CKEditor.

To do this, we’ll simply add a few lines to the onActivate() method in our ModuleConfig.cfc:

function onActivate(){
    ...
    // Install the ckeditor plugin
    var ckeditorPluginsPath = controller.getSetting("modules")["contentbox-admin"].path & "/includes/ckeditor/plugins/cbjsFiddle";
    var fileUtils = controller.getWireBox().getInstance("fileUtils@jsFiddle");
    var pluginPath = controller.getSetting("modules")["jsFiddle"].path & "/includes/cbjsFiddle";
    fileUtils.directoryCopy( source=pluginPath, destination=ckeditorPluginsPath );
}

And lastly, we should add something in onDeactivate() to delete the plugin, just to be courteous and keep our user’s plugins directory clear of stuff they’re not using:

function onDeactivate(){
    ...
    // Uninstall the ckeditor plugin
    var ckeditorPluginsPath = controller.getSetting("modules")["contentbox-admin"].path & "/includes/ckeditor/plugins/cbjsFiddle";
    var fileUtils = controller.getWireBox().getInstance("fileUtils@jsFiddle");
    var pluginPath = controller.getSetting("modules")["jsFiddle"].path & "/includes/cbjsFiddle";
    fileUtils.directoryRemove(path=ckeditorPluginsPath, recurse=true);
}

Done!

Wrapping Up

And with that, we have a complete CKEditor plugin that is capable of not only inserting complex data types via ColdBox views and handlers, but we are also able to edit it via the same process. And with our interceptor, we are able to transform the content into something that will work when rendered. While there is a bit of setup and work involved for the first time doing this, I think this represents a fairly reproducible model for creating modules with CKEditor plugins within ContentBox.

I hope this was helpful to someone. I’d love to hear any constructive feedback that anyone has about this approach–thanks!