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.