MiTo Team

"Collapse" button plugin for CKEditor for Drupal

Several days ago I needed to add an ability to add "spoilers" to comments and nodes at VELOBY.NET web site (Minsk's cycling community). Spoiler is a part of text, that is hidden from viewing by default. It expands by click showing previously hidden content to users.

I've googled nice Collapse Text module for Drupal. Installed it, enabled in input format settings. Everything is fine with it.

We use CKEditor as wysiwyg-editor on VELOBY.NET. And I wanted to add a button to its toolbar, to be able to insert [collapsed]...[/collapsed] meta tags before and after text, selected in editor. Googling almost an hour gave me nothing. So I decided to write own plugin for CKEditor with this functionality.

This small article is about creating this plugin.

There is a really good tutorial Creating a Simple CKEditor Plugin. So I'm not reproducing whole plugin creation process here step-by-step. There will be only comments about final plugin code. Full source code is attached at the end of article.

First of all we think of name for new plugin and create a folder with this name. I chose mtcollapsed as plugin name.

Our plugin will add a button to editor's toolbar, so let take care about icon for it before starting coding. It should be 16x16 pixels, and I prefer PNG format. This is what I drew: . Gave it name button.png and put into our mtcollapsed directory.

Then I created an empty plugin.js file to put plugin code in. Our plugin consists of 3 parts: command, toolbar button, and dialog.

Command is really simple:

editor.addCommand('cmdMTCollapsedDialog', new CKEDITOR.dialogCommand('mtcollapsedDialog'));

It adds command cmdMTCollapsedDialog that shows mtcollapsedDialog dialog.

Adding a toolbar is simple and easy to understand too:

editor.ui.addButton('MTcollapsed', { label: 'Insert spoiler', command: 'cmdMTCollapsedDialog', icon: this.path + 'button.png' });

This adds a button with MTcollapsed name with Insert spoiler label (tooltip), with our button.png as button's icon. Clicking this button executes our cmdMTCollapsedDialog command.

And here is most complicated part - dialog:

  1. CKEDITOR.dialog.add( 'mtcollapsedDialog', function ( editor )
  2.     {
  3.       return
  4.       {
  5.         title : 'Spoiler settings',
  6.         minWidth : 300,
  7.         minHeight : 200,
  8.         contents :
  9.         [{
  10.           id : 'tab1',
  11.           label : 'Settings',
  12.           elements :
  13.           [{
  14.             type : 'text',
  15.             id : 'title',
  16.             label : 'Spoiler title',
  17.             onShow : function() { this.setValue('Hidden text'); },
  18.             validate : CKEDITOR.dialog.validate.notEmpty( "Spoiler title should be provided" )
  19.           }]
  20.         }],
  21.         onOk : function()
  22.                {
  23.                  var dialog = this;
  24.                  var title = dialog.getValueOf( 'tab1', 'title' );
  26.                  var openTag = '[collapsed title=' + title + ']';
  27.                  var closeTag = '[/collapsed]';
  28.                  var inplaceTag = ' ' + openTag + ' text ' + closeTag + ' ';
  30.                  var S = editor.getSelection();
  32.                  if( S == null)
  33.                  {
  34.                    editor.insertHtml(inplaceTag);
  35.                    return;
  36.                  }
  38.                  var R = S.getRanges();
  39.                  R = R[0];
  41.                  if( R == null)
  42.                  {
  43.                    editor.insertHtml(inplaceTag);
  44.                    return;
  45.                  }
  47.                  var startPos = Math.min(R.startOffset, R.endOffset);
  48.                  var endPos = Math.max(R.startOffset, R.endOffset);
  50.                  if( startPos == endPos )
  51.                  { editor.insertHtml(inplaceTag); return; }
  53.                  var container = new CKEDITOR.dom.element('p');
  54.                  var fragment = R.extractContents();
  56.                  container.appendText(openTag);                  
  57.                  fragment.appendTo(container);  
  58.                  container.appendText(closeTag);
  60.                  editor.insertElement(container);
  61.                }
  62.       };
  63.     });

Lines 5-7: dialog parameters setup. Dialogs params described here.

Lines 8-20: Dialog's GUI elements. We just need one textbox (type = 'text') for user to be able to provide a title for hidden text. Line 17 sets default value for this textbox. Line 18 obliges user to provide a title.

Default dialog has two buttons: OK and Cancel. Cancel button just closes dialog without any other actions. Clicking OK button raises onOk event where most work is done (lines 21-61):

Line 24: accessing value provided by user in textbox.

Lines 26-28: preparing meta tags code to insert into editor. If there is a text selected open and close tags inserted before and after this text. If there are no text selected inline open-close metatag inserted ([collapsed title='title text'][/collapsed]).

Lines 30-36: Check if there is a selection in editor. Insert inline open-close metatag at cursor position if there is no selection.

Lines 38-45: Selection consists of ranges. We need first range of selection (actually there is alway just one range in selection). Insert inline open-close metatag at cursor position if there is no first range.

Lines 47-51: Sometimes there is a selection with a range, but this range starts and ends at the same position. This means that nothing still selected. Same thing: insert inline open-close metatag at cursor position.

If there is a selection wit a range we should create a container for its contents (because there could be severals elements selected, for example two paragraphs or paragrah and image).

Lines 53-54: Create container element and prepare range contents.

Lines 56-58: Adding open tag, range contents and close tag to our container.

Line 60: Insert container to editor at cursor position (replacing current selection).

Thats all! Full plugin.js source is in archive attached at the end of this article.

Now we should add our plugin to Drupal's CKEditor. There are two ways of adding plugind to CKEditor in Drupal:

  • An easy one: just putting your plugin into "plugins" folder in CKEditor's module directory. This is really easy. But you'll have to keep this in mind everytime you updating the CKEditor module.
  • As part of own module: you should create owm Drupal module, or add some code to a new one.

I have own module on VELOBY.NET site already, so second way is right for me. But you can try to experiment with first one. it should work as well. To add CKEditor plugin with own module your module should implement hook_ckeditor_plugin hook function. This hook should return an array with plugin definitions. There is only one plugin in our case.

This is what I've added to my velobynet.module file:

  1. /**
  2. * CKEditor plugins hook
  3. */
  4. function velobynet_ckeditor_plugin() {
  5.   return array(
  6.         'mtcollapsed' => array(
  7.             'name' => 'mtcollapsed',
  8.             'desc' => 'MTCollapsed - ' . t('Inserts [collapsed][/collapsed] tags'),
  9.             'path' => drupal_get_path('module', 'velobynet') . '/ckeditor-plugins/mtcollapsed/',
  10.             'buttons' => array(
  11.                 'MTcollapsed' => array('label' => 'Insert spoiler',
  12.                                        'icon' => 'button.png' ),
  13.             )
  14.         )
  15.     );
  16. }

Line 7: Plugin name.

Line 8: Plugin description to show at CKEditor's settings page.

Line 9: Full path (with trailing slash) to plugin folder.

Lines 10-13: Plugin buttons definitions (optional). We define just one button with 'Insert spoiler' label and button.png icon.

After adding this we can enable plugin in some CKEditor's profile settings, and try how it works.

Thank you for reading this and excuse me for my English, it is far from perfect yet .

Please contact us if you have any questions.

P.S.: I've attached two zip's to this article:

  • - contains only CKEditor's plugin code
  • - Complete solution to install in Drupal as a module

Module was tested with Drupal v.6.24, CKEditor (itself) v.3.6.2 and CKEditor (Drupal module) v6.x-1.8