Showing posts with label tutorial. Show all posts
Showing posts with label tutorial. Show all posts

2020/08/30

Modifying the generated element in SimpleUploads.

I think that I've already written about this type of requests, but as I've not been able to quickly find it, it deserved a simple post.

Request: after uploading an image with SimpleUploads, wrap it in a link element for use with any type of effect like ColorBox. eg:

<a href="filepath" class="colorbox" target="_blank"><img src="filepath"/></a>


This is achieved using the 'simpleuploads.finishedUpload' event, add in your page this code (adjust it according to your needs:

CKEDITOR.on('instanceReady', function(e) {

	// the real listener
	e.editor.on( 'simpleuploads.finishedUpload' , function(ev) {
		var editor = ev.editor;
		// CKEditor.dom.element
		var element = ev.data.element;
		//console.log(element)

		var a = document.createElement('a');
		a.href = element.getAttribute('src');
		a.className = 'colorbox';
		a.target = '_blank';

		// create a CKEditor.dom.element
		var ckLink = new CKEDITOR.dom.element(a);
		// replace the image with the link
		ckLink.replace(element);
		// insert the link into the image
		element.appendTo(ckLink);
	});
});

2016/07/03

Getting a Google Maps API Key

On June 22th Google announced that from that day on, every new implementation of the Maps API requires the usage of an API key.

This is very important for anyone that wants to use my Google Maps plugin for CKEditor, because now you must get your key in order to use it.
The basic usage of the API allows 25.000 free maps loads per day, and you can have one key for each domain that you want to use. From that point you'll have to get a paid license. This is more or less the same, they have adjusted the way that somethings are counted but the important part is that previously the free usage encouraged signing the requests with an API key but now it's a forced requirement.

Getting an API key isn't too hard because the process has been streamlined and you mostly have to agree to the Terms and Conditions, you can find here their instructions, but I'm gonna provide some screenshots so you can view how easy it is.

Step by step guide

First, go to https://console.developers.google.com/flows/enableapi?apiid=maps_backend&keyType=CLIENT_SIDE&reusekey=true

You'll get a screen like this:
As this is probably a new project you just have to click Continue.
Now you'll get some notifications and progress and you'll end up with a screen similar to this one
This doesn't look right, the "You don't have permission to create an API key" message is strange, but the fact is that the "Create" button is enabled and it works, so you can define the allowed Referrer sites, or leave that blank now and adjust it later.
Click "Create" and then you'll get your API key


Click the Copy icon at its right side and you're almost done. If you have already the Google Maps plugin, then open the CKEditor configuration file, add a new entry "googleMaps_ApiKey" and then assign there the value that you got:
Now if you load CKEditor with the Google Maps plugin, the Maps dialog should work, but the static images will fail, this is because we have enabled the Google Maps API, but you need also to enable the usage of the Static Maps API with this key (and also the Street View API if you want to allow your users to use a StreetView image as the preview)

So open https://console.developers.google.com/apis/api/static_maps_backend?project=_  and this time, instead of creating a new project we will use the one that has been created previously:
Then click the Enable button
And this step is done, just repeat it for the Street View API in this link:
https://console.developers.google.com/apis/api/street_view_image_backend?project=_

Then the Geocoding so the searches also work in the dialog:
https://console.developers.google.com/apis/api/geocoding_backend?project=_

And this is over!

Summary

Please, keep in mind that these steps are the current ones as of July 2016, Google might change things or even depending on some settings on your account you might see different things, but the end goal is to get a Google Maps API key, enable also the usage of that key for the Static Maps and then put it in a googleMaps_ApiKey entry in the configuration of your CKEditor instance.

Additional notes

I think that the first time that you try to get an API key you'll get this screen:
and from them on when you try to get another key for a new domain, the dialog that it's used is the one that I've shown at first.

Also, at the top of the screen you might get a banner to sign up for Google Cloud, but if you plan to stay within the free plan limits you don't need that.


2014/02/15

How to launch the ImageMaps dialog with your own code

If you have some code outside of CKEditor that inserts an image into its content and then you want to automatically launch the ImageMaps dialog on that image, you can use simply this two lines of code:

First, select the image using the CKEditor API:

editor.getSelection().selectElement(editor.document.getById("myImageID"));

"editor" is a reference to the editor instance that contains the image, and "myImageID" is the id attribute assigned to that image.

It first gets a reference to the element with editor.document.getById and then tells the selection system to select that element.

Then launch the dialog:

editor.openDialog("ImageMaps");
It's just that simple, use the name registered for that dialog and tell editor that you want to open it :-)
 
I hope that this can be helpful for someone.

2014/02/08

How to insert a new "Browse page" button in the ImageMaps plugin of CKEditor

In this post I'll provide a simple code that inserts a new browse button in the ImageMaps dialog that allows the user to browse existing pages in your CMS besides the normal browse button used to browse for files.

The code can easily be reused for other dialogs like the link or image, you just need to check the name of the elements and then test that it works as expected.

The main idea is to use the "dialogDefinition" event to modify the definition of the dialog on the fly and then create a new button that we will insert besides the existing one. You can check other uses in the samples folder of your CKEditor, there's an "api_dialog" file that shows some of these ideas.

The code is as simple as this:

CKEDITOR.on( "dialogDefinition", function( ev ) {
    // Take the dialog name and its definition from the event data.
    var dialogName = ev.data.name;
    var dialogDefinition = ev.data.definition;

   //Customize "Image Map" dialog 
    if ( dialogName == "ImageMaps" ) {

  var infoTab = dialogDefinition.getContents('info');
  // get the existing "browse" button, adjust its position
  var browseFile = infoTab.get('browse');
  browseFile.style = 'display:inline-block;margin-top:15px; ';

  // Create a new "Browse page" button, linking to our custom page browser
  var browsePage = {
   type : 'button',
   id : 'browsePage',
   hidden : true,
   style: 'display:inline-block;margin-top:15px; ',
   filebrowser :
   {
    action : 'Browse',
    target: 'info:url',
    url: '../intLinks2.aspx'
   },
   label : 'Link to CMS Page or Form'
  };
  // Create a container for the two buttons and replace the existing browse button with this one
  var hBox = { type :'hbox', widths: ['120px', '120px'], children : [browsePage] };
  infoTab.add( hBox, 'browse' );
  infoTab.remove( 'browse' );
  infoTab.add( browseFile, 'browsePage' );

  // Force a better width for the href field
  var txtHref = infoTab.get('href');
  txtHref.style = 'width:200px';
    }
});

I hope that someone finds this useful :-)

2013/12/11

Validation of files with SimpleUploads

As a user is frustrating to upload a file and then after waiting a long time for it to finish get a message stating that your file isn't valid. That's why the SimpleUploads plugin for CKEditor allows to perform most of the validations before the upload starts and that way you'll have happier users.
In this post I will explain how to configure the most common options and you can test them in a demo page for validation of uploads.

Allowed extensions

This is the most basic check, a whitelist of extensions that the users can upload to the server
config.simpleuploads_acceptedExtensions = "jpg|png|zip";
By default the value is empty so no check is performed. You can add the the list of extensions separated by the pipe "|" and that will be used to check the filename. If the extension isn't allowed the nonAcceptedExtension message will be shown to the user (you can customize it in the lang file).
All the editors in the demo only allow to upload the files with a list matching the settings on the server, except the first inline demo. In that demo the list is set to an empty string so any file is uploaded and then you get the error message from the server (in this case an un-friendly "202 upload failed")

Denied extensions

This is the opposite of allowed, if the extension matches this list the file will be rejected. I only added this configuration option for parity, but everybody knows that a blacklist approach is not good for security. The demo page doesn't include this option.

Image extensions

This setting has two purposes.
The first one is that when you drop a file the plugin must decide if it should create an <img> or an <a> with the upload. The user can opt to press the Shift key while dropping the file to generate a link even if he adds an image file, but as a general rule you might want to allow only png and jpg files for example, or include .bmp and .tiff and then convert those files to other format at the server.
The second purpose is to restrict the accepted files when the user presses the "Add image" button as well as the uploads in the image dialog. If the selected file doesn't match this filter it will be rejected and the nonImageExtension message will be shown to the user (this feature will be included in version 4.1.1, the current one is 4.1.0).
config.simpleuploads_imageExtensions = 'jpe?g|gif|png|bmp';
The bottom editor in the middle is configured to only allow png files as images. You can check that if you drop a jpg is inserted as a link, and the toolbar buttons and image dialogs will reject anything that isn't a png.

File size

Obviously uploading a 100Mb file is usually a bad idea, but the users don't realize that and that's why you can easily set the maximum size that you allow them to use with simpleuploads_maxFileSize. The default value is empty so no check is performed, but you can set here the value that you want to limit (as an integer with the bytes, so 10Mb => 10*1024*1024
The inline editor at the top middle uses a limit of 100Kb, if the selected file is bigger, the fileTooBig message is shown to the user.
config.simpleuploads_maxFileSize = 102400;
As a side note, file size is a common problem and even if you don't bother to adjust this setting, the server might return a "413 Request Entity Too Large" status error, in that case the plugin handles that error and shows again the fileTooBig message to the user instead of just stating that the upload failed.

Custom validation

Maybe you think that you still want to do some other check like forcing the user to select a file with a custom pattern and a minimum file size, or whatever, ok, you can do that also.
The last editor at the bottom right only allows to upload files that start with "wall" by listening to the  "simpleuploads.startUpload" event.
The code in this example is quite simple
editor.on('simpleuploads.startUpload', function (ev) {
 // File name
 var name = ev.data.name;
 if (name.substr(0,4).toLowerCase() != "wall")
 {
  alert( "You must add here only wallpapers" );
  ev.cancel();
 }
});
We set the listener on the editor instance that we want and use the ev.data.name property to check the file name that the user has selected, if it doesn't pass our test we cancel the event and show a message to the user.
In fact most of the previous validations (except for identifying images) are performed using this event, the plugin adds a listener like that for each editor and uses the configuration values to verify if the file is valid or it should be rejected using the file name as well as the file object to check its size.

Server messages

Even after all these checks, there might be some other issues that can't be handled at the client side, in that case remember that in the response that you send from the server you can set an empty Url and the third parameter is a message that will be shown to the user.

2013/11/21

SimpleUploads and the new image2 widget

CKEditor 4.3 has introduced a new image plugin that is a showcase of its widget system.

The problem is that any image that has been added through direct drag&drop or paste into the editor with the SimpleUploads plugin behaves like an image (big surprise), and it's not such a widget.

That means that after inserting it, you can't edit the image until you reload the content, and it's a little bit ugly that situation because you can't be sure if everything is working correctly or if your image will be gone or whatever.

So for the moment you can use this patch:

// Let's add this to every editor on the page. You can instead add it only to a specific editor.
CKEDITOR.on('instanceReady', function(e) {
    // the real listener
    e.editor.on( 'simpleuploads.finishedUpload' , function(ev) {
        var editor = ev.editor
        // workaround for image2 support
        if (editor.widgets && editor.plugins.image2) {
            var element = ev.data.element;
            if (element.getName()=="img")
                editor.widgets.initOn(element, "image2");
        }
    });
});

It's adjusted to work only with the "image2" plugin, if you want to use other plugin to handle images then you must replace the name of it in the call to initOn.

It simply uses the event fired after an element has been inserted to check first if your editor has the widgets and image2 plugins active, then verifies that the new element is an image, and in that case it manually inits the widget on that image.

 

2013/11/18

Demo videos for the Google Maps plugin for CKEditor

I've realize that I haven't announced it here, but about a month ago I spent some time creating two videos showing most of the features available in the Google Maps plugin for CKEditor.

The idea behind the plugin is that although being able to play with the demo page and test the features by yourself, sometimes you might not be aware of all the available options, and despite my horrible pronunciation (I beg you pardon) i decided that it would be good to provide a video explaining the features available in the plugin.

Of course, creating such thorough explanation means that I found little issues, I used different browsers to test the features and record the video so in the end besides the videos I released a new version of the plugin with some improvements (and testing the dialogs was when I felt the need to improve the uploads in the CKEditor dialogs, it was a pity that some features like adding a KML file or a new icon couldn't been done by drag & drop from my computer)

The first video is available here:  Introduction to Google Maps plugin for CKEditor

It explain the main features of the two first tabs of the dialog, and how the map is rendered in the final page. Most of the options are just what you expect and you can see any change in the preview map of the dialog.

Some of the features that I added thanks to recording this video was the ability to start with the StreetView of a location or with a rotated map.

The second video is this one: Drawing shapes on Google Maps with CKEditor.

In order to record this video I finished an option that I've had in the works for some time but I never had the time to test it, drawing circles.

The video starts showing how to place markers, change the icon of the marker, how a simple CKEditor is embedded in the dialog to allow editing the popup that shows when you click on a marker, or how to insert a simple option so that the visitor can get driving directions from his location to that place.

Then it shows the other drawing options, lines, polygons and circles, all can be controlled to adjust the color, width, opacity; so you get the map that you want.

Please, keep the closed captions on and the volume low, but watch the videos and if you have some question about them or you think that I can explain something else that I've missed, then drop me a note.

2012/09/09

Adjusting a CKEditor plugin to be compatible with v3 and v4

Recently CKSource announced the first beta of the new CKEditor 4, and along the improvements, it also brings some changes of the API. You can read about those changes in the included API-CHANGES.md file (open it with any text editor, I don't know if there's an easier way to read it correctly)

I'm gonna record here the changes that I have to go through to update my plugins when I touch them (although I'm not gonna try to do full testing until it goes out of beta).

Translations

The first breaking change is that CKEDITOR.plugins.setLang has changed, so now when you pass the object with the translations it must not include the "parent" object, as the doc says:

So, in v3 we had:

    CKEDITOR.plugins.setLang( 'myplugin', 'en',
    {
        myplugin :
        {
            title : 'My Plugin'
        }
    });
In v4 it should be changed to:
    CKEDITOR.plugins.setLang( 'myplugin', 'en',
    {
        title : 'My Plugin'
    });

That's good if you want to release a plugin only for v4 or you plan to ignore it, but if you want to be ready to upgrade and try to make your code compatible with both v3 and v4 you now have a problem because the name of the function hasn't changed but the expected parameters have done so.

So my solution is to create a new helper function and call this one instead of CKEDITOR.plugins.setLang:

CKEDITOR.addPluginLang = function( plugin, lang, obj )
{
    // v3 using feature detection
    if (CKEDITOR.skins)
    {
        var newObj = {};
        newObj[ plugin ] = obj;
        obj = newObj;
    }
    CKEDITOR.plugins.setLang( plugin, lang, obj );
}

This way, we use the new format for the obj parameter, but if the function detects that it's running in v3 it adds the plugin name as the root of all the strings

CKEDITOR.addPluginLang( 'myplugin', 'en',
{
        title : 'My Plugin'
});

Spacer image

Another change is that they decided that leaving the images/spacer.gif file there was polluting the global scope, so now you just have to copy that image in every plugin to be sure that it's included in the install and then adjust the paths in your code to use this copy of the image. It's not a big deal, but certainly to me it doesn't seem a big problem to leave that simple image in a common place so it can be easily reused.

Adding CSS

The doc explain this about this change:

The "additional CSS" feature provided by `CKEDITOR.editor::addCss` is now moved
to a global `CKEDITOR.addCss`, with specified style rules applies **document wide**.

Thus the proper way for a plugin to style it's editable content is to call `CKEDITOR.addCss`
inside of the plugin's `onLoad` function, rather than it's `init` function in v3.

So in order to make your code work in both versions you have to use something like this:

...
    getPlaceholderCss : function()
    {
        return 'img.cke_video' +
                '{' +
                    'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/placeholder.png' ) + ');' +
                    'background-position: center center;' +
                    'background-repeat: no-repeat;' +
                    'background-color:gray;'+
                    'border: 1px solid #a9a9a9;' +
                    'width: 80px;' +
                    'height: 80px;' +
                '}';
    },

    onLoad : function()
    {
        // v4
        if (CKEDITOR.addCss)
            CKEDITOR.addCss( this.getPlaceholderCss() );
    },

    init : function( editor )
    {
....
        // v3
        if (editor.addCss)
            editor.addCss( this.getPlaceholderCss() );
...

 

 

So this is all for the moment. I'll keep editing this post in the future as I upgrade more code.

 

 

2012/05/01

Recipe: Live preview of CKEditor

Today we're gonna prepare a live preview of the contents edited with CKEditor.

A live preview with a WYSIWYG editor is not so important compared to other text-based editors, but anyways the behavior inside the editor might not match exactly the final output, so in those cases we can provide this option for those that want it.

Ingredients:

1 Instance of CKEditor

1 Preview container (for example a Div)

1 Copy of the onChange plugin

In case that you want to use this method with more than one instances, then you'll need as many preview containers as CKEditor instances you want to use.

Method:

1. Create the CKEditor instance in your page using any method that you like, and add the OnChange plugin to the list of extra plugins:

For example:

<textarea name='contents' id='contents'>This is an editor</textarea>
<script type='text/javascript'>
var config = { extraPlugins: 'onchange'};
CKEDITOR.replace('contents', config);
</script>

2. Now add add a little extra javascript to listen for changes in the editor instance and mirror those changes in the preview div, (based on this StackOverflow question)

CKEDITOR.on('instanceCreated', function (e) {
    e.editor.on('change', function (ev) {
        document.getElementById( ev.editor.name + '_preview').innerHTML = ev.editor.getData();
    });
});

That code will take care of any CKEditor instance and automatically copy its contents into the container with an id like the editor instance with an ending "_preview".

3. The only missing part is to initialize the preview before the user starts typing:

document.getElementById( e.editor.name + '_preview').innerHTML = e.editor.getData();

All the code together

<textarea id="contents" name="contents">This is an editor</textarea></p>
<div id="contents_preview"></div>

<script type='text/javascript'>

CKEDITOR.on('instanceCreated', function (e) {
    document.getElementById( e.editor.name + '_preview').innerHTML = e.editor.getData();
    e.editor.on('change', function (ev) {
        document.getElementById( ev.editor.name + '_preview').innerHTML = ev.editor.getData();
    });
});
var config = { extraPlugins: 'onchange'};
CKEDITOR.replace('contents', config);
</script>

This code in action:

 

 

 

2011/03/27

onChange event for CKEditor

It's a somewhat frequent request to see people asking how they can get a notification whenever the content of CKEditor changes.

Some of them are too optimistic:

I've this code <textarea onchange="myfunction()"></textarea> and when I use CKEditor "myfunction" is never called

But most of the people can understand the difference between the original textarea and a CKEditor instance and ask what's the better way to get a notification whenever the content changes, isn't there any built in event for that?

The answer is that no, there's no default event fired whenever something changes, but as I will show here it's quite easy to extend the CKEditor API and generate such event.

Generating a new 'change' event

Although there's no event for "the content has changed" there's something very similar, and that's the Undo system with its saveSnapshot event; whenever something changes it will be called, so we can listen for that event and it will help us greatly with our goal:

       editor.on( 'saveSnapshot', function(e) { somethingChanged(); });

That will take care if the change is something being changed like appying bold or any other style, a new table, pasting, ... it should handle almost everything. But there's one thing that doesn't file a 'saveSnapshot' event, and that's the undo system itself. When Undo or Redo are executed they don't fire that event, so we must listen to them:

        editor.getCommand('undo').on( 'afterUndo', function(e) { somethingChanged(); });
        editor.getCommand('redo').on( 'afterRedo', function(e) { somethingChanged(); });

Ok, Why "something changed"?
Answer: because we are not really sure that something has changed and we aren't really interested to know exactly what has changed, only that something might have changed. Any plugin will fire a "saveSnapshot" before and after any change to work properly, so in our function we will merge all those calls and fire a single event:

        var timer;
        // Avoid firing the event too often
        function somethingChanged()
        {
            if (timer)
                return;

            timer = setTimeout( function() {
                timer = 0;
                editor.fire( 'change' );
            }, editor.config.minimumChangeMilliseconds || 100);
        }

This way the editor will fire a "change" event at most every 100 milliseconds, and you can use editor.checkDirty() to verify if it has really changed (that call might be a little expensive if you are working with big documents, so it's better to avoid calling it too often by using something like the minimum 100 miliseconds that I've added)

Extra checks

To trap every change (and ASAP to avoid delays updating any UI that it's interested in this event I added also a listener for the afterCommandExec (I don't remember now which situation made me add it and I didn't put any comment explaining it :-(  )

        editor.on( 'afterCommandExec', function( event )
        {
            if ( event.data.command.canUndo !== false )
                somethingChanged();
        } );

and also a listener for the keyboard (don't know right now why I didn't listen for the editor.on('key') event; maybe I forgot about it) and new code to handle drag&drop (I've proposed to enhance the Undo system that way in ticket 7422 with some improved code)

        editor.on( 'contentDom', function()
             {
                 editor.document.on( 'keydown', function( event )
                     {
                         // Do not capture CTRL hotkeys.
                         if ( !event.data.$.ctrlKey && !event.data.$.metaKey )
                             somethingChanged();
                     });

                     // Firefox OK
                 editor.document.on( 'drop', function()
                     {
                         somethingChanged();
                     });
                     // IE OK
                 editor.document.getBody().on( 'drop', function()
                     {
                         somethingChanged();
                     });
             });

Ready to use plugin

Here you can download a zip with the full plugin and install instructions onChange event for CKEditor.
Obviously it can be improved, but the important part was to get something working good enough not to win a code contest about the best and most beautiful code.

Note: If you wanna link the plugin from any other site, please link this post so people can get the new versions when they are released instead of linking directly to the zip that will be outdated.

Edit: version 1.1 3rd September 2011

I've fixed an issue with the 'afterUndo' and 'afterRedo' events: they're fired on their respective commands, not on the editor itself; now the Undo and Redo buttons should work correctly. I can't understand how I missed that the first time.

Following the suggestion in the comments, I've added detection for changes in source mode, by keyboard, drag and drop and also on "input" if supported.

Edit: version 1.2 18th September 2011

The new CKEditor 3.6.2 fired the 'saveSnapshot' event too many times, just changing the focus might fire it and generate bogus change events when nothing has changed. Filtered those extra events.

The keyboard listener has been adjusted to ignore movement and other special keyboard events.

Edit: version 1.3 22th December 2011

Avoid firing the event after the editor has been destroyed.

Edit:

I've published a demo showing an usage example.

Edit: version 1.4 7th September 2012

Don't fire events if the editor is readonly, thanks to Ulrich Gabor.
Included code to use Mutation Observers (I'll try to explain it when I have some time).

Edit: version 1.5 20th October 2012

Detect Cut and Paste for IE in source mode thanks to Jacki.

Edit: version 1.6 18th November 2012

Detect multibyte characters thanks to Wouter.

Edit: version 1.7 6th December 2012

Compatibility with Source mode in CKEditor 4.

Note: 15th December 2012

Although the current version doesn't work correctly with CKEditor 4, I don't plan any future update.

Edit: version 1.8 8th June 2013

Use setInterval fix by Roman Minkin.


Notes to self about the Code highlighter plugin used in WriteArea:

  • It doesn't remember the last used language reverting always to Java
  • It doesn't prefill the content with the currently selected text

2011/01/16

Avoiding extra request for the translation of a CKEditor plugin

When you write a plugin for CKEditor, it's almost for sure that it will include some text, it can be the tooltip for the toolbar button, a context menu entry or some change to one dialog, and you can leave that string hardcoded in your language, but it's usually better to use the localization features of CKEditor so it's easy for users in other countries to use your plugin, as well as allowing to easily change the text to fit better in a different environment.

As you might know this is achieved by setting a "lang" property in your plugin that it's an array with the list of language codes that your plugin provides:

 lang : [ 'en', 'es' ],

That entry tells CKEditor to load the file with the translation from the lang file of your plugin (lang/en.js or lang/es.js), and it's a pity that with all the current efforts about improving performance, due to the desire to provide the translation your editor might load slightly worse as it gets that extra file.

But there's an easy workaround, you can put the localization in the plugin.js file itself, so that when it checks what does it needs to load for this plugin, it can see that the translations are ready and so it skips for the next item.

Now each translation is just a line in the plugin and it doesn't require an extra request to the server:

CKEDITOR.plugins.setLang( 'backgrounds', 'en', { backgrounds : { label : 'Background image'} } );
CKEDITOR.plugins.setLang( 'backgrounds', 'es', { backgrounds : { label : 'Imagen de fondo'} } );

 

2010/12/08

Setting 100% height in CKEditor

In CKEditor the config.height setting doesn't allow  to use percentages, only fixed sizes are supported, but that is not good when you try to provide a dialog that is just the editor without any extra stuff (like in this Write Area pane).

The available option is to use the maximize plugin and execute it using the instanceReady event; that has some little problems:

  • You have to keep the maximize plugin into the code, although you want to execute it just once.
  • If you try to remove the maximize button from the toolbar, you'll get an error.
  • There's a flicker while the editor is first created and then resized.

The way to avoid those problems is to adjust the CSS in your main page, these are the rules that I've included in Write Area to get 100% without the previous problems (keep in mind that I've tested only in Firefox, at the very least you'll have to provide the "cke_browser_gecko" rule also for other browsers (ie, webkit, opera) and test if there are other problems.

span.cke_skin_kama, span.cke_skin_office2003, span.cke_skin_v2 {
 padding:0  !important;
 border:0  !important;
 -moz-border-radius:0  !important; 
 height:100%;
}

span.cke_browser_gecko {
 height:100% !important;
 display:block;
}

.cke_skin_kama .cke_editor, .cke_skin_office2003 .cke_editor, .cke_skin_v2 .cke_editor,
.cke_skin_office2003 .cke_wrapper, .cke_skin_v2 .cke_wrapper {
 height:100%;
}

.cke_skin_kama .cke_wrapper {
 padding:0 !important;
 height:100%;
}

 

2010/01/01

Zoom Plugin for CKEditor

Recently all the browsers that are implementing the latest and greatest elements of CSS 3 have added support for CSS Transforms, and so I decided to have a little fun while creating a little example about how to create a custom dropdown element in the CKEditor toolbar.

The idea is to provide a new dropdown that allows the user to set the zoom level for the editor, this example is gonna be a little rough and it can be greatly improved, but this is mostly a proof of concept about how to do it.

The first step is to create the skeleton for the plugin:

CKEDITOR.plugins.add( 'zoom',
{
    requires : [ 'richcombo' ],

    init : function( editor )
    {

    } //Init
} );

That code should be placed in a "plugin.js" file located in the plugins/zoom folder of your CKEditor installation. If you want to create another plugin, you'll use another folder name and put that name as the first parameter in the call to CKEDITOR.plugins.add()

For this plugin we need to be sure that the code from the "richcombo" plugin is loaded, as that's the real code that will create the dropdown element, so we specify it in the requires field of the plugin.

Finally this plugin must be added to the CKEditor instance, so in your config.js (or whatever other place you want to specify it) add it to the plugins that must be loaded:

    config.extraPlugins = 'zoom';

You can place an alert() inside the init function to verify that the plugin is being loaded when you refresh the page with CKEditor. If it doesn't work, verify the error console and try to clear your cache.

Now we need to fill up the code in the init function to create the dropdown:

    init : function( editor )
    {
        var config = editor.config;

        editor.ui.addRichCombo( 'Zoom',
            {
                label : "100 %",
                title : 'Zoom',
                multiSelect : false,

                panel :
                {
                    css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss )
                },

                init : function()
                {
                    var zoomOptions = [50, 75, 100, 125, 150, 200, 400],
                        zoom;
                   
                    this.startGroup( 'Zoom level' );
                    // Loop over the Array, adding all items to the
                    // combo.
                    for ( i = 0 ; i < zoomOptions.length ; i++ )
                    {
                        zoom = zoomOptions[ i ];
                        // value, html, text
                        this.add( zoom, zoom + " %", zoom + " %" );
                    }
                    // Default value on first click
                    this.setValue(100, "100 %");
                }
            });
        // End of richCombo element

    } //Init

In this function we are creating the dropdown as a "RichCombo" element with editor.ui.addRichCombo() The function takes as parameters the name of the RichCombo and the object that contains the definition. Inside this object there's an init() function that will be called to initialize the dropdown on first click, here we specify the options available in the RichCombo and add them with this.add()

So now we can add to the toolbar of our CKEditor this 'Zoom' RichCombo element:

...
    ['Maximize', 'ShowBlocks', 'Zoom'],
...

Refreshing the page will show now the dropdown and clicking on it will show the panel, although we still haven't specified what must be done  when an option is selected. Doing it in a simple way and not trying to optimize how it works we can add this function to the RichCombo definition:

...
                    // Default value on first click
                    this.setValue(100, "100 %");
                },

                onClick : function( value )
                {
                    var body = editor.document.getBody().$;
 
                    body.style.MozTransformOrigin = "top left";
                    body.style.MozTransform = "scale(" + (value/100)  + ")";

                    body.style.WebkitTransformOrigin = "top left";
                    body.style.WebkitTransform = "scale(" + (value/100)  + ")";

                    body.style.OTransformOrigin = "top left";
                    body.style.OTransform = "scale(" + (value/100)  + ")";

                    body.style.TransformOrigin = "top left";
                    body.style.Transform = "scale(" + (value/100)  + ")";
                    // IE
                    body.style.zoom = value/100;
                }
            });
        // End of richCombo element
...

This code blindly tries to set the property for each browser, without bothering about which one is being used of if it supports CSS Transforms at all. We know that IE doesn't support them, but it has supported since long ago the style.zoom so in this case we can use just that.

A correct implementation of the plugin should verify that the browser does support CSS Transforms (or is IE and can use .zoom) before adding the RichCombo, and in the onClick event handler should use just the correct property instead of trying all of them.

This plugin also lacks internationalization, because that wasn't the point of this test, but it should be easy to add and to fix other little things.

But there are two problems that can be seen with just this code:

The first one is that the width of the combo in the toolbar is a little too big to specify some little text like "100%". This is being tracked in ticket 4559 but as you can see by reading all the comments there's no easy solution that can be applied to automatically fix it. So the solution is to add some rules in the stylesheet, although that makes deploying the plugin a little harder. Maybe the RichCombo should be allowed to specify a width in its definition?

The second problem appears only in Webkit browsers (Safari and Chrome), when you open up the panel it will take full height, and it's a little ugly. Other combos have their size specified in the skins css but that doesn't seems like a good solution if you want to publish a plugin that will be used for other people, as well as making upgrading a little harder.

Browser support: The zoom feature should work in IE6+, Firefox 3.5+, current Safari and Chrome (I don't have old versions to test) and Opera 10.5+ (still in alpha)

Happy 2010

Edit may-2013:

A new version of the plugin compatible with CKEditor 4 is available at the plugins repository. Zoom plugin.

2009/12/18

Recompressing ckeditor.js to fit your needs

Note:
Most of the content of this post as well as some corrections are available now at the official documentation for CKEditor: CKPackager - Compressing CKEditor
This post will remain here only for historical reference and to link to the new documentation (the one that you should read)

The javascript code of CKEditor is delivered as one main ckeditor.js where all the code has been compressed and merged (of course, not ALL the code, just the code that is needed for the core and the default plugins, the code of the dialogs for example is loaded on demand). And one question that you might have is: how can I compress my own version? I'll try to provide here some explanations and notes about this, but despite its length it is far from a full guide and it might contain some incorrect parts.

First you have download the ckpackager.jar (there's also an .exe for those in windows that haven't installed Java) from
http://svn.fckeditor.net/CKPackager/trunk/bin/ckpackager.jar and put it at the root of your CKEditor folder. (Note that this is a SVN repository so it should contain the latest version)

Backup the existing ckeditor.js and ckeditor_basic.js so you can check that the process is working correctly.

Now the first step is to verify that you have Java correctly installed and that everything is correct in your environment, so fire up the console/terminal/command prompt and navigate to the CKEditor folder and type there the command to start the compilation. This should generate identical files to the previous ckeditor.js and ckeditor_basic.js if you haven't changed anything in the _source folder:

java -jar ckpackager.jar ckeditor.pack

Maybe the js files aren't identical to the previous ones due to some change in the version of CKPackager used to compress the released version of CKEditor and some fix/improvement made to CKPackager since that moment, but the overall should be similar and you should be able to use the new files without differences.

Working with the source files

Now you can try to apply changes to the files under _source and test them using the ckeditor_source.js, and when you are ready to deploy you just have to compress them all again. You should be careful with your changes because compressing the source means that any slight error can be fatal.

The new packager is based on Rhino, the javascript implementation in Java from the people at Mozilla, so I don't know how it does deal with missing semicolons, and other minor issues that can be fatal with some compression systems. Of course it's better to write code without problems instead of relying on the tools to fix them.

Selecting just what you want

The ckeditor.js file includes all the plugins (but not all the code of the plugins: the dialogs are lazy loaded only when needed as I said). So if you don't want to provide several options to your users you could remove those plugins, not only from the toolbar, but also from the packaged file so you can end up with a much smaller ckeditor.js file.

In order to change what's included you need to edit ckeditor.pack and remove the entries about plugins that you don't need.

Don't alter the entries under '_source/core/*' because the editor probably will fail. Remember that if you mess with the packaging process no one else can help you, you are the one that is editing the file and not knowing what to do isn't a good idea here.

An example of a change would be to remove the line

                '_source/plugins/elementspath/plugin.js',

if you don't want to show the elements path at the bottom of the editor (there's the config.removePlugins = 'elementspath' option but that means that the code is still loaded so if you are sure that you never need this option you can save some bytes). Then you save the file and issue the recompress command, and now the ckeditor.js is 2Kb smaller.

Of course it seems that it would be a trial/error the process to find what are the files that you can remove as one plugin might depend upon another and so you need it to run correctly. In order to generate the default ckeditor.pack file, the SVN version of CKEditor includes a _dev folder with some development tools and that includes a packager folder with a HTML file created to generate the correct content according to the current configuration (this is meant to be used in the SVN version, I don't know how it will deal with the released version).

Optimizing the downloads

If you check the ckeditor.pack file you can see that the lang file isn't compressed, this is due to the fact that not everybody speaks English, so packaging the en.js file for everybody would mean a waste of bytes, but if you know that your users speak only one language, then you could specify that language in the ckeditor.pack file and get it bundled along the others.

This will increase the size of ckeditor.js but remember that those bytes were downloaded separately as another request, so now the editor will be slightly faster as we have avoided that extra step.

At the bottom of the file you can see that the kama.js file is also being bundled, so if you are using another skin you could adjust that to remove the kama and put your own one, avoiding the extra download of your selected skin.

And if you are using some custom plugin you can include it in the list so it's ready along the rest of files and the user don't suffer an extra delay for the usage of your plugins.

This is just a quick explanation, but it should be enough to get started about how to recompress the code.

Note:
Most of the content of this post as well as some corrections are available now at the official documentation for CKEditor: CKPackager - Compressing CKEditor
This post will remain here only for historical reference and to link to the new documentation (the one that you should read)

2009/12/11

Adjusting the context menu of CKEditor

The easyUpload plugin aims to replace the default image & link plugins as they can be seen as too complex, so we can expect that the config won't include their buttons in the toolbar, but the context menu entries will still be shown even if the command isn't available in the toolbar.

So you might think that it's just a matter of removing the "image" plugin, but you'll find that the context menu is still there. After a little digging you might be able that this is due because the "forms" plugin has stated that "image" is a requirement (for the input type="image). Then the next step would be to remove the forms plugin, but maybe that's a little too much.

Or what about the link plugin? It does include the unlink command as well as the anchor, so if we remove that plugin we need to copy all that code to another file.

The solution would be to remove just the context menu entries for those items that we don't want. In order to do this you need to study a little how the context menu plugin works and you'll find that it stores the available items in an object: editor._.menuItems and when the context menu is fired, every listener returns the object that should be shown at that time, but if that entry doesn't in the editor._.menuItems then it just skips that order.

So we can do this in our plugin to remove the context menu for the image plugin:

    afterInit: function(editor)
    {
        delete editor._.menuItems.image;
    },

On the other side, this plugin creates its own entries for images and links, but maybe you want to use only the new image dialog, so we need to apply some cleanup to the context menu.

Then instead of just hardcoding the elements to remove from the context menu the approach is to review the elements that are defined for the toolbar comparing them with a list of items that can be removed if they aren't used and then finally remove from the context menu all such items.

    afterInit: function(editor)
    {
        // Remove the default context menu for elements that aren't being used in the toolbar.
        // This object is composed of the button name and the name of the context menu entry
        var removableEntries = {Image:'image',Link:'link',Unlink:'unlink',EasyImage:'easyimage',EasyLink:'easylink',EasyUnlink:'easyunlink'},
            // Get the data that is being used for the toolbar, we end with an array of arrays.
            toolbar =
                    ( editor.config.toolbar instanceof Array ) ?
                        editor.config.toolbar
                    :
                        editor.config[ 'toolbar_' + editor.config.toolbar ];

        // Loop the main array (composed of groups of commands)
        for (var i=0; i<toolbar.length; i++)
        {
            var items = toolbar[i];
            if (!items)
                continue;
           
            for (var j=0; j<items.length; j++)
            {
                var buttonName = items[j];
                // If it was marked at our check object remove it because it's in use
                if (removableEntries[buttonName])
                    delete removableEntries[buttonName] ;
            }
        }
       
        // Remove all the entries that aren't used in the toolbar
        for (var command in removableEntries)
            delete editor._.menuItems[ removableEntries[command] ];
    },

With this code our plugin will take care of leaving only the correct entries in the context menu without any extra configuration

2009/12/09

Plugin localization in CKEditor (vs FCKeditor)

If you work in the web you should have already realized that there are tons of people from every where and that not all of them speak English, and even if they are able to understand it they might to work with their native tonge, so providing the ability to translate your plugins it's important if you want them to be used anywhere.

Adding the plugin

In FCKeditor the registration call of the plugin did specify the available languages:

FCKConfig.Plugins.Add( 'easyupload', 'de,en') ;

In CKEditor there's no call to add a plugin, just a list of plugins and two other lists to add extra plugins or remove existing ones (each list is just a comma separated string with the names of the plugins). So that call would be something along the lines:

config.extraPlugins = 'easyupload';

But that doesn't specify the languages available for the plugin, now instead is the plugin itself the one that specifies what are its available languages:

CKEDITOR.plugins.add( 'easyupload',
{
    // translations
    lang : ['en'],
...

The translation file

The available translations must be placed as previously under the pluginfolder/lang/languagecode.js being pluginfolder the name of your plugin and languagecode the code of the language ('en' in the above case), so we would end with a file under plugins/easyupload/lang/en.js for example.

The structure of this file is quite different from the previous one. In FCKeditor the file was a simple list of properties added to the FCKLang object:

FCKLang['EuImgDialogTitle']  = 'Insert / Edit Image' ;

Now the file is made of one (or several depending on how you have defined your data) call to the CKEDITOR.plugins.setLang method that gets as parameters the name of the plugin, the language code and an object that contains the definitions of the strings:

CKEDITOR.plugins.setLang( 'easyupload', 'en',
    {
        easyimage :
        {
            toolbar: 'Insert/Edit an image',
...
        }
    }
)

Usage

And so the way to use it now is also slightly different, the "lang" object of the current editor instance has been augmented with your definitions:

        editor.ui.addButton( 'EasyImage',
            {
                label : editor.lang.easyimage.toolbar,
                command : 'easyimage',
                icon : this.path + 'images/image.gif'
            } );

Dialogs

Now all the dialogs are created from javascript objects, there are no html parts to load so there is no need for the "fcklang" attribute and the call to FCKLanguageManager.TranslatePage(document)

2009/12/07

Using your own Uploader in CKEditor

CKEditor still doesn't include its own file manager, but the hooks to integrate your own uploader script or file manager are already in place, as you can check win CKFinder or any number of third party scripts that people have been writing.

Upgrading from FCKeditor

Maybe you want to use a previous uploader that you've used in FCKeditor and you are not sure about what are the differences, and the current documentation isn't clear enough for you. I'm pointing here the differences for the upload (or "quick upload") part, I might try to cover the slight differences in the "File browser" in other post.

  1. The file is sent as "upload" instead of "NewFile". Note that this is the value that you'll get using the default dialogs, but the name of the input is the id applied to the uiElement used to create the widget so in some situations you might get a different name.
  2. Some additional info is sent along the request as GET parameters: the name of the CKEditor instance, the current language code and a parameter to specify the callback function.
  3. The callback function is dynamic, you must read the "CKEditorFuncNum" parameter and send it back.
  4. There are no special error numbers in the callback function. Your script can return a url to use and a message to show to the user, both are optional so you can return just an URL that will be used in the dialog, or you can return a message that will be shown to the user, or a URL and a message (for example a warning that the file has been renamed and the new URL).
  5. Thanks to the message and the "langCode" parameter you can use localized messages instead of having them in English.

So I think that that is the summary of differences showing that the limitations in the previous API (hardcoded error numbers and messages, no info about the current instance...) have been fixed.

Practical example

Maybe that's not enough for you, so here's an example "upload.php" file that you can use to study and adjust your code. This code doesn't save any file, doesn't include any security check, it doesn't check for errors... It's just some basic sample explaining what you can do.

<?php

// This is just a simple example, not to be used in production!!!

// ------------------------
// Input parameters: optional means that you can ignore it, and required means that you
// must use it to provide the data back to CKEditor.
// ------------------------

// Optional: instance name (might be used to adjust the server folders for example)
$CKEditor = $_GET['CKEditor'] ;

// Required: Function number as indicated by CKEditor.
$funcNum = $_GET['CKEditorFuncNum'] ;

// Optional: To provide localized messages
$langCode = $_GET['langCode'] ;

// ------------------------
// Data processing
// ------------------------

// The returned url of the uploaded file
$url = '' ;

// Optional message to show to the user (file renamed, invalid file, not authenticated...)
$message = '';

// In FCKeditor the uploaded file was sent as 'NewFile' but in CKEditor is 'upload'
if (isset($_FILES['upload'])) {
    // ToDo: save the file :-)
    // Be careful about all the data that it's sent!!!
    // Check that the user is authenticated, that the file isn't too big,
    // that it matches the kind of allowed resources...
    $name = $_FILES['upload']['name'];

    // example: Build the url that should be used for this file   
    $url = "/images/" . $name ;
    // Usually you don't need any message when everything is OK.
//    $message = 'new file uploaded';   
}
else
{
    $message = 'No file has been sent';
}
// ------------------------
// Write output
// ------------------------
// We are in an iframe, so we must talk to the object in window.parent
echo "<script type='text/javascript'> window.parent.CKEDITOR.tools.callFunction($funcNum, '$url', '$message')</script>";

?>

If the code is deemed worth, it might be added to the wiki (for example in Custom File Browser, or creating a new article "Custom Uploader") but before doing so I would like to hear a little feedback.

 

Update 10/08/2013:

I've added the missing part to save the file, use it at your own risk. Go to the new post explaining it.

2009/11/29

CKEditor is Fast!

Yep, CKEditor is fast and this is not just a marketing slogan, but a fact. I'm stating this as the reply to this question:

I want to fire some event every time I replace <div id="editor"><div> element pressing "Create Editor" button:

  editor = CKEDITOR.appendTo('editor');
  editor.on('pluginsLoaded', function(ev) {
    …
  });


Everything is OK on first replacement of div. But when I destroy editor with "Remove Editor" button:

  editor.destroy();

and create it once again, event function is not executed.

The problem here is that the second time the CKEDITOR.appendTo is called, all the files (core, plugin, configuration, ...) are already loaded in memory, so the creation process is almost synchronous; as soon as the call is finished, the instance is almost ready and the only event you can attach the second time is the "instanceReady", the rest of events already have been executed by that time. (and that instanceReady is fired later due to the delay loading the iframe for editing and its initialization, maybe if the editor is configured to start in source mode that event is also fired synchronously. Correction, no. Starting with source mode still fires the instanceReady so it isn't related to iframes initialization, but other asynchronous task).

So we can't attach the event using editor.on() because as I said this is too late, the events have already been fired the second time. In this situation you can opt to use maybe the instance "instanceReady" because as I said this seems to be fired always. But we are playing a game with some risk: if the initalization is further optimized in the future avoiding those little milliseconds of delay that event will be fired as well before we get a change to listen to them.

Honestly I didn't speak about the events of a CKEditor instance, and this might be a little hidden topic as the official documentation doesn't explain it for the moment, but there are two ways to listen for the events of an instance.

The first method is what you can read above and everybody is used to in other environments and frameworks instance.on( eventName, callback ). But by the time we get a hold of instance the event has already been fired. Despite that we could use another workaround using the CKEDITOR.instanceCreated event. We can set a listener for that event and due to being assigned to an object that always exists, it will be always fired whenever an instance is created. So this means that any listener that we set on and editor instance inside a CKEDITOR.instanceCreated listener should then be fired even if the process of creation is synchronous. So this is a more promising solution because no matter how fast the computer, browser or CKEditor is optimized, as long as the event remains there we can use it to hook into any new instance and set our listeners.

But the second method is more appropiate for this situation, and it's based on the ability of creating events listener at creation time. It's based on this code from editor.js

                    // Register the events that may have been set at the instance
                    // configuration object.
                    if ( instanceConfig.on )
                    {
                        for ( var eventName in instanceConfig.on )
                        {
                            editor.on( eventName, instanceConfig.on[ eventName ] );
                        }
                    }

So it states that if we pass an "on" object in the configuration object at creation time, the events defined there will be assigned to our editor. And you can indeed try it:

    editor = CKEDITOR.appendTo( 'editor' , {  on:{'pluginsLoaded':function(e) { console.log('instance config::' + e.name)} } });

That event will be fired no matter if it's the first, second or Nth time that the editor is created. You have to be very careful about the syntax as it's easy to make a little mistake with some extra parenthesis or a missing bracket but the error console should warn you in that case.

2009/11/14

Controlling the cache with CKEditor

One of the biggest problems that are faced while developing some javascript code in an app like CKEditor is the browser cache as it doesn't behave like you want to in those time.
Usually the cache is great, it can be very helpful to avoid long delays loading again the shared files, but if it insists on loading the old version instead of the new one that you have just changed then it will be painful.

Depending on the browser that you are using and its configuration it can be easier or harder to force loading the new version. and the last resort is to close the browser, load about:blank and clear the cache, now it should really load the new version (unless there's some proxy between you and the server that doesn't want to cooperate).

With CKEditor 3 there's a new option to solve this problem, "CKEDITOR.timestamp"
Its main use is a way to signal to your users the version of CKEditor, because they will have even greater problems trying to clear the cache. Each new release of CKEditor will have a different timestamp, so the users will load the new version of the files.

You can also this item while you are trying to develop some plugin. You just need to make sure that every time that the page is loaded a new timestamp is generated. A typical way to do it is (before creating your instances):

CKEDITOR.timestamp = (new Date()).toString() ;

But there's one gotcha here. That string is appended to every url in order to make it unique, and at least there's one place where using that will cause a little problem:
If your plugin is providing an icon for a custom button and you use that timestamp you won't see it in the toolbar, instead you'll get the first icon of the toolbar strip. This is due to the fact that such icon is loaded as a background image for the element, and the url that it's generated isn't valid and won't be used.
The workaround is to make sure that it doesn't contains anything that can cause problems:

CKEDITOR.timestamp = (new Date()).toString().replace(/[ \(\)\+]/g, "");

Of course you can use any other way to generate the random string that doesn't have this problem from the start, but I'm too used to just put the date whenever a unique url is required.

Update:

I knew that my solution was too complex and that I did saw something easier but I couldn't remember the place and it was in front of me all the time!. Just open ckeditor_source.js and uncomment the following line

// CKEDITOR.timestamp = ( new Date() ).valueOf();

Instead of generating a string and then removing unwanted characters, using valueOf() returns only a serie of digits so there is no problem, and by having it in ckeditor_source.js means that it's ready everytime that you are working with the source files without having to add the code to the page.

2009/09/08

CKEditor events

The new articles will be focused on documenting the events fired by CKEditor as currently there's no documentation besides the source code.

Events are the way to hook different pieces, the glue for the plugin architecture of CKEditor that allows to replace / add several parts without going nuts. In this regard, this doc page is a must read for anyone that really wants to understand the basics about how it works. If you don't read it then you'll be lost and you'll ask questions that have already been answered there.

Yes, that page is quite generic, so we have to looks at the API docs for the CKEDITOR.event information, here we can find out exactly the details about how those ideas have been implemented.

As a summary: an object might implement this Api, so you can listen to their events adding your listener. The syntax is similar to the standard DOM events, but it has some slight improvements like the ability to specify an object upon which the listener must be called without resorting to extra code and specify a priority for the listener (you might need this very few times, but it's nice that it's there).

As any other event system you can remove your listener, and if you need to, you can fire an event whenever you like, on an standard object, your own object, some standard event or your own event.

Something important must be remarked at this point: If you try to register a listener to a non-existing event you won't get any error, but of course it will never be executed. So don't try to put ckeditor.on('imageDeleted') ckeditor.on('fileAdded') or whatever. Unless you write the code to fire those events you are wasting your time. Why does the event system allows to do that: because you can create any event that you want, so the system just register your listeners, expecting that you know what you are doing and the event will be fired later by any other code.

So now we now the basic, let's look at the events of one object

CKEDITOR events

CKEDITOR is the main object of the whole API, and it can fire several events that are useful to do some basic and generic tasks

"loaded"
The first event is fired only if you have used the basic integration, obviously if you use the full code from the start as soon as your code is executed the core is ready (or you wouldn't be able to call CKEDITOR.on() ). Also, there's no editor sent in the eventInfo object.

"instanceCreated"
Each time an instance is created this event is fired, before it's configuration is initialized.

"instanceReady"
It signals that the initialization of an instance has finished.

"currentInstance"
Fired when an editor gets or loses focus. (After CKEDITOR.currentInstance has been updated. This is currently an undocumented property used only for the focus management ). No editor is sent in the eventInfo object. This is fired quite a lot, for example opening a dialog means that the editor loses focus, so the event is fired, and also when it's closed.

"dialogDefinition"
Fired upon creating the definition for a dialog. The event data are the dialog name and its definition. (this is the only event in CKEDITOR that sends some data in the eventInfo.data field). This event is fired when a dialog is launched for the first time in each CKEditor instance.

So that's all for the main CKEDITOR object. It's a short list, but enough to show a bug in my previous code to provide the seamless integration of textareas in CKEDITOR: Yes the CKEDITOR object doesn't fire any event when an instance is destroyed so that part of the code must be written like this:

// Hook each textarea with its editor
CKEDITOR.on('instanceCreated', function(e) {
 if (e.editor.element.getName()=="textarea")
 {
  var node = e.editor.element.$ ;

  // If the .nativeValue hasn't been set for the textarea try to do it now
  if (typeof(node.nativeValue) == "undefined")
  {
   // IE
   if (!document.__defineGetter__) return;
   // for Opera. Safari will fail
   if (!DefineNativeValue(node)) return;
  }

  node.editor = e.editor;

  // House keeping.
  e.editor.on('destroy', function(e) {
   if (node.editor)
    delete node.editor;
  });
 }
});

This just shows again that you must not trust anyone, test whatever you find and use it as a guideline, but always be ready to think that the person that wrote some example might not have tested under your same conditions, that some code has been changed meanwhile (for example I guess that the list of events of CKEDITOR might have increased in v3.2 for example, so if you read this article 2 years from now just think that I'm talking about the 3.0 version), or the code might be wrong because whoever wrote it was too drunk that day :-)