How to customize CKEditor with your own plugins, skins, configurations
This post summarizes what I've learned about customizing the open-source WYSIWYG rich-text editor CKEditor 3.5.2 with one's own plugins, skins, and configurations. There is already a lot of good resources so wherever possible I will link to them and just summarize and/or supplement them. However I've found no overall guide for customizing CKEditor and thus intend to fill this vacancy.
To try out check the CKEditor's demo.
I say "configurations" because we had two configurations (and two skins), a normal one and then a child version of the editor (less toolbars, bigger and friendlier icons, ...).
It's important to notice that the configuration file may contain any JavaScript code (incl. function declaration and use) and access variables and functions defined in the page prior to the inclusion of CKEditor and thus you can dynamically adjust the configuration based on the calling page. Possible use of this is shown in CKEditor: Hide some toolbar buttons on a per page basis.
You also need to be aware of the order of loading of the different configurations (in-page, custom file, in plugins) and ckeditor sources and its plugins (which differ between the production and development mode). So when something doesn't work as expected, make sure that you check the order in which things get applied and overridden.
Comments:
Comments:
Comments:
Comments:
Comments:
You then just need to define the class so that it contains a background image, likely from a sprite - see kama/icons.png and kama/editor.css.
Example custom css in myckeditor/custom_skins/my_kama/my_icons/my_icons.css using the sprite my_kama/my_icons/my_icons.png and registered with the custom skin as described above:
Comments:
We have actually used our own internationalization system to provide the translation suitable for the current user. It is generated dynamically by a file included before ckeditor.js and producing a JavaScript object in the form expected by CKEditor:
The language file mylanguage.js only copies the JavaScript object with translations into the place expected by CKEditor:
The main difference I've noticed with CKEditor 3.5.2 is that in the development mode plugins are loaded after the custom configuration file and thus you won't be able to override some of their settings, such as CKEDITOR.config.smiley_descriptions. In the production mode the plugins are loaded together with the editor itself before the custom configuration and thus everything will behave as expected.
If using both modes, you will need to synchronize same files, such as editor/lang/ with editor/_source/lang/ etc.
Introduction
CKEditor is a popular rich-text editor implemented in JavaScript. Its architecture makes heavy use of plugins and events and it is very configurable and customizable. Its documentation is thousandfold better than in the 2.x version but still you can't find there everything you might want.To try out check the CKEditor's demo.
Customizing CKEditor
Layout of the customized CKEditor installation
The structure of our customized CKEditor is as follows:- <my web root>/myckeditor/
- custom_configurations/
- custom_plugins/
- custom_skins/
- editor/ - the original distribution of CKEditor, i.e. the content of ckeditor_3.5.2.zip/ckeditor/
- _samples
- _source/
- lang/
- ckeditor.js
- ckeditor_source.js
- ...
Custom configurations
You will need to change CKEditor's configuration and the best way to do so is perhaps to create your own configuration file, as described on the CKEditor's configuration page. You then tell CKEditor to load it when initializing it:
<textarea name="newedittextarea">initial content...</textarea>
<script type="text/javascript" src="/mywebapproot/myckeditor/editor/ckeditor.js"></script>
<script type="text/javascript">//<![CDATA[
CKEDITOR.replace('newedittextarea', { "customConfig": "/mywebapproot/myckeditor/custom_configs/my_config.js" , "RootPath":"/mywebapproot/"});
//]]></script>
I say "configurations" because we had two configurations (and two skins), a normal one and then a child version of the editor (less toolbars, bigger and friendlier icons, ...).
It's important to notice that the configuration file may contain any JavaScript code (incl. function declaration and use) and access variables and functions defined in the page prior to the inclusion of CKEditor and thus you can dynamically adjust the configuration based on the calling page. Possible use of this is shown in CKEditor: Hide some toolbar buttons on a per page basis.
You also need to be aware of the order of loading of the different configurations (in-page, custom file, in plugins) and ckeditor sources and its plugins (which differ between the production and development mode). So when something doesn't work as expected, make sure that you check the order in which things get applied and overridden.
Custom skins
This is how you create a custom skin based on the default kama skin:- Copy myckeditor/editor/skins/kama/ (or ../editor/_source/skins/.. if you care more for readability than performance) to myckeditor/custom_skins/my_kama/
- Use a search & replace tool to replace all occurences of "kama" with "my_kama" in the copied files (you can do it automatically, no need to manually confirm each of the few hundred replacements)
- Thus you'll have the CSS class cke_skin_my_kama instead of cke_skin_kama etc.
- This renaming of all the classes is necessary for otherwise there will be conflicts between the default kama skin and the one of yours and things might not work (this applies at least when using the compressed production version of CKEditor, ckeditor.js)
- It's important to get this step right otherwise CKEditor may fail to load
- Optional: Add a custom css to the skin:
- Create the CSS, e.g. at myckeditor/custom_skins/my_kama/my_icons/my_icons.css
- Register it with the skin: In my_kama/skin.js, add it to the appropriate component, in my case it's the editor:
CKEDITOR.skins.add('my_kama',(function(){var a='cke_ui_color';return{ editor:{css:['my_icons/my_icons.css','editor.css']},dialog:...
- Tell CKEditor to use the skin: In myckeditor/custom_configs/my_config.js, add:
var ckeditorBasePath = CKEDITOR.basePath.substr(0, CKEDITOR.basePath.indexOf("editor/")); CKEDITOR.editorConfig = function( config ) { ... config.skin = 'my_kama,' + ckeditorBasePath + 'custom_skins/my_kama/'; ... }
Custom plugins
Creating custom plugins for CKEditor is a huge topic, we will touch only some parts of it. Refer to the plugin creation section in the Resources for introduction into plugin creation.Dialog implementation options
Different types of dialog implementations
As far as I know you can create three types of dialogs with CKEditor 3.5:- Native dialogs defined with JavaScript only - this is used by all CKEditor's own plugins, check them under _source/plugins
- IFrame dialogs - the dialog system defines the common elements such as title and Ok/Cancel buttons but the content of the dialog isn't defined by JS but it's an iframe loading another page (see dialog.addIframeDialog and the content type 'iframe'); requires the plugin iframedialog (an example below)
- Custom dialogs - they do not use the dialog API at all but define a custom command whose exec function displays a new window/jQuery dialog/whatever you want and pass results back using standard CKEditor methods - see [Eliasson10]
// myckeditor/custom_plugins/my_plugin/dialogs/my_plugin.js:
CKEDITOR.dialog.add( 'my_plugin', function( editor ) {
return {
title : 'My IFrame Plugin', minWidth : 390, minHeight : 230,
contents : [ {
id : 'tab1', label : '', title : '', expand : true, padding : 0,
elements : [ {
type : 'iframe',
src : editor.config.RootPath + 'myCustomDialog.phtml',
width : 538, height : 478 - (CKEDITOR.env.ie ? 10 : 0)
} ]
} ]
, buttons : [] // don't show the default buttons
};
} );
Dialog's on OK handler options
The standard dialog API defines among others the events 'ok' and 'cancel' triggered when the corresponding dialog button is pressed (only available for native and iframe dialogs). You can (re)define their handler in two ways:- In the dialog definition function via "
, onOk: function(event) {... /* using this.getParentEditor()*/}
" - Outside of the dialog definition, for example inside a dialog iframe (option #2 above) by registering and deregistering the listener via
CKEDITOR.dialog.getCurrent().on("ok", okListener)
andCKEDITOR.dialog.getCurrent().removeListener("ok", okListener);
(okListener being a function reference). To access the editor:var editor = window.parent.CKEDITOR.dialog.getCurrent()._.editor;
How to add a custom plugin
Registering the plugin with CKEditor & enabling it
In the custom CKEditor configuration file (myckeditor/custom_configs/my_config.js):1. Register the plugin:
var ckeditorBasePath = CKEDITOR.basePath.substr(0, CKEDITOR.basePath.indexOf("editor/"));
var customPluginsRoot = ckeditorBasePath + 'custom_plugins/';
CKEDITOR.plugins.addExternal('my_plugin',customPluginsRoot+'my_plugin/', 'plugin.js');
CKEDITOR.plugins.addExternal('another_custom_plugin',customPluginsRoot+'another_custom_plugin/', 'plugin.js');
CKEDITOR.editorConfig = function( config )
{
...
Comments:
- the first argument is the plugin name, as defined in its plugin.js (normally equal to its folder name)
- the second argument is the path to the plugin folder
- the third argument is always 'plugin.js'
2. Enable the plugin:
CKEDITOR.editorConfig = function( config ) {
...
config.extraPlugins = 'my_plugin,another_custom_plugin';
...
}
Comments:
- just specify the name of the (registered) plugin in a comma-separated string
3. Add it to the toolbar:
CKEDITOR.editorConfig = function( config ) {
...
config.toolbar_Default = [
['Preview', 'Source','Link'],['MyPlugin', 'AnotherCustomPlugin']
];
...
}
Comments:
- The name, 'MyPlugin', is the CKEDITOR.ui.button name specified as the first parameter to editor.ui.addButton in plugin.js. It is not the plugin name (my_plugin) - see below.
Plugin structure and naming
Files:- custom_plugins/plugin_name/
- plugin.js
- dialogs/plugin_name.js
CKEDITOR.plugins.add( 'my_plugin',
{
requires : [ 'iframedialog' ],
init : function( editor )
{
var command = editor.addCommand( 'my_plugin', new CKEDITOR.dialogCommand( 'my_plugin' ) );
editor.ui.addButton( 'MyPlugin',
{
label : 'ids:My label...',
command : 'my_plugin'
});
CKEDITOR.dialog.add( 'my_plugin', this.path + 'dialogs/my_plugin.js' );
}
});
Comments:
- the names of the plugin, of its command, and of its dialog are all my_plugin
- the name of the plugin's toolbar button is MyPlugin - this may be added to config.toolbar_{name} in the ckeditor configuration
// myckeditor/custom_plugins/my_plugin/dialogs/my_plugin.js:
CKEDITOR.dialog.add( 'my_plugin', function( editor ) {
return {
title : 'My IFrame Plugin', minWidth : 390, minHeight : 230,
contents : [ {
id : 'tab1', label : '', title : '', expand : true, padding : 0,
elements : [ {
type : 'iframe',
src : editor.config.RootPath + 'myCustomDialog.phtml',
width : 538, height : 478 - (CKEDITOR.env.ie ? 10 : 0)
} ]
} ]
, buttons : [] // don't show the default buttons
};
} );
Comments:
- Nothing special here except of the config option editor.config.RootPath which we set ourselves when instantiating the ckeditor
Plugin toolbar icon
There are two ways to assign an icon to your plugin's button: either use the property icon when registering it, pointing to an image, or use the CSS class assigned to the "button".Using the icon property:
(Copied from c_schmitz' comment on Voofie's plugin guide):
CKEDITOR.plugins.add('myplugin',
{
init: function(editor)
{
editor.ui.addButton('myplugin',
{
label: 'myplugin',
command: myplugin,
icon: CKEDITOR.plugins.getPath('myplugin') + 'myplugin.png'
});
}
});
Using the CSS class:
CKEditor automatically assigns the CSS class "cke_button_" to toolbar elements representing plugin buttons, the command name being usually the same as the plugin name (e.g. "cke_button_my_plugin" for the plugin above) - though you can also specify your own class via the className property.You then just need to define the class so that it contains a background image, likely from a sprite - see kama/icons.png and kama/editor.css.
Example custom css in myckeditor/custom_skins/my_kama/my_icons/my_icons.css using the sprite my_kama/my_icons/my_icons.png and registered with the custom skin as described above:
/* Plugins re-using native CKEditor's icons (the default) */
.cke_button_my_plugin .cke_icon {
background-position: 0 -528px !important;
}
/* Plugins using our own icon sprite */
.cke_button_another_custom_plugin .cke_icon {
background: url("my_icons.png") no-repeat scroll 0 -1232px transparent !important;
}
Comments:
- ckeditor's own icon sprite is loaded as the background image by default, you need to use !important to override it
- the custom sprite image url is relative to the location of the css file
- to add this custom CSS to the skin you need to add it its skin.js as described above
Custom language
CKEditor supports quite a number of languages out of the box and can auto-detect which one to use based on the users' browser settings. You can add a language of your own and tell CKEditor explicitly to use it:- Create myckeditor/editor/lang/mylanguage.js based e.g. on editor/_source/lang/en.js
- It should include all the keys and certainly it must include all the sub-groups (such as contextmenu: {...}) to avoid errors because of undefined properties.
- Tell CKEditor that the language is enabled and that it should be used by default:
// myckeditor/custom_configs/my_config.js: CKEDITOR.editorConfig = function( config ) { ... CKEDITOR.lang.languages.mylanguage = 1; config.language='mylanguage'; ... }
We have actually used our own internationalization system to provide the translation suitable for the current user. It is generated dynamically by a file included before ckeditor.js and producing a JavaScript object in the form expected by CKEditor:
// myckeditor/generate_mylanguage.js.php:
// ... PHP code here ... - produces JavaScript code similar to this:
// window.GENERATED_LANG = {dir:'ltr', editorTitle:'My Editor', ...};
The language file mylanguage.js only copies the JavaScript object with translations into the place expected by CKEditor:
// myckeditor/editor/lang/mylanguage.js:
CKEDITOR.lang['mylanguage'] = GENERATED_LANG;
Note on the CKEditor's modes: production vs. development
In the production you normally use a compressed version of CKEditor, which contains both the editor and its default plugins in one compressed file, ckeditor.js. On the other hand, during development, you use for the sake of easier debugging and troubleshooting the source version ckeditor_source.js, which reads the files under the _source/ folder. It's important to know that the two versions do not behave exactly the same and thus you need to test with both.The main difference I've noticed with CKEditor 3.5.2 is that in the development mode plugins are loaded after the custom configuration file and thus you won't be able to override some of their settings, such as CKEDITOR.config.smiley_descriptions. In the production mode the plugins are loaded together with the editor itself before the custom configuration and thus everything will behave as expected.
If using both modes, you will need to synchronize same files, such as editor/lang/ with editor/_source/lang/ etc.
Resources
Warning: Some of the resources may be out-of-date. Always check their publication date and the current CKEditor APIs and docs.CKEditor in general
- CKEditor installation - minimal setup
- CKEditor FAQ
- Blog: CKEditor events - description of available global events (I failed to found them in the documentation), example of usage and link to the CKEditor events docs
Building plugins
Overall tutorials etc.
- voofie: CKEditor Plugin Development - a pretty detailed one
- Syrinx: Building Custom CK Editor Plug-ins - incl. plugins with UI in a stand-alone window
- discussion: Creating Plugins in CKeditor
- Tutorial: create external plugin for CKEDITOR - this looks pretty good and it also shows building an iframe dialog plugin (unfortunately without passing data from the dialog back to the editor)
- Dialog System (CKeditor 3.x Design and Architecture) - perhaps look over it briefly; unfortunately it misses some interesting topics such as iframe dialogs
Specialized plugin topics
- [Eliasson10] Plugin existing code into ckeditor - doesn't use the dialog API but a simple command, whose exec function opens a custom dialog and uses the CKEditor's API to pass results back
- Plugin localization in CKEditor (vs FCKeditor)
- StackOverflow: Adding a plugin from an external location to CKEditor (use CKEDITOR.plugins.addExternal)
IFrame dialogs
- stackoverflow: Example of an iframe dialog with custom onOk listener - setting a custom ok listener (to the CKEditor-provided OK button) is perhaps the best way to pass values from an iframe dialog back to the editor and it's also the standard way
- Forum: Working with iframe dialog plugins - it's little chaotic and not all information there is true or the best solution possible but still it may be useful regarding how to react to the OK/Cancel buttons and how to pass data from the dialog back to the editor; generally the "Tutorial: create external plugin for CKEDITOR" referenced above is a more reliable source
Very special general/plugin topics that may be useful at some point
- Forum: Inserting (and maintaining fake element placeholders) - two important things here: a) adding a nested iframe to a dialog (CKEDITOR.dialog.addIframe) and b) using placeholder elements for normally content normally "invisible" while editing, for instance Flash (dataProcessor and editor.createFakeParserElement)
- Custom CKEditor Menu
- Tutorial: Extend the Links dialog to link to internal CMS pages
- Forum: Integration a custom iframe with dialog's OK button
- Adding the function getSelectedHtml to CKEditor
Additional unsorted links of interest
- Recompressing ckeditor.js to fit your needs
- ASPSpellCheck plugin for CKEditor 3.x
- Blog: Avoiding extra request for the translation of a CKEditor plugin - include translations directly in plugin.js
My other related blog posts
- CKEditor: Hide some toolbar buttons on a per page basis
- CKEditor: Collapsing only 2nd+ toolbar rows – howto
- CKEditor: Scroll dialogs with the page, i.e. not fixed to the middle
- Upgrading FCKeditor 2.x to CKEditor 3.x including plugins (+ a decoupling facade)