2009/12/19

GeckoSpellChecker plugin for CKEditor

A little while ago I created the GeckoSpellChecker plugin for FCKeditor that allows to use the internal Firefox spellchecker from the editing area without any external server.

Now the plugin has a version ready for CKEditor. Be sure to download the 1.0 version for CKEditor, the 0.7 version will remain there for the people using FCKeditor.

I hope that you can find this useful.

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/16

Adjusting the preview text in easyUpload

Over the years people have reported that sometimes the users don't understand what's the Lorem Ipsum text that appears in the preview of the image and flash dialogs, claiming that it's not English and it's not what they have written.

The solution is to point them to the source of those dialogs and then remove the offending text (of course they will lose then the ability to understand how the various align values will affect their layout, but it's their choice).

With the improved capabilities of CKEditor this can be done much easier. For example I've added just an id to the uiElement that it's used to create the preview:

        {
            type : 'html',
            id : 'htmlPreview',
            html : '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.image.preview ) +'<br>'+
...

The first motivation to add this id was to provide a way to remove the preview for those that didn't want that in a dialog with simple options. But with just that addition it's possible to write a little code using the 'dialogDefinition' event. First we check that it's loading our dialog and then we can change both the label over the preview as well as the context of the preview:

    var preview = infoTab.get('htmlPreview');
    preview.html = '<div>My image preview<br>' +
        '<div id="ImagePreviewLoader" style="display:none"><div class="loading">&nbsp;</div></div>'+
        '<div id="ImagePreviewBox">'+
        '<a href="javascript:void(0)" target="_blank" onclick="return false;" id="previewLink">'+
        '<img id="previewImage" alt="" /></a>This is some example text around the image' +
        '</div>'+'</div>';

We could change the html to whatever we would like, but in order to mantain compability and keep the preview working we should preserve the "placeholder" elements (div, a, img) and change only the texts (in this case "My image preview" and "This is some example text around the image")

This is the complete function:

// When opening a dialog, its "definition" is created for it, for
// each editor instance. The "dialogDefinition" event is then
// fired. We should use this event to make customizations to the
// definition of existing dialogs.
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;

        // Check if the definition is from the dialog we're
        // interested on (the "easyImage" dialog).
        if ( dialogName == 'easyImage' )
        {
            // Get a reference to the "Info" tab.
            var infoTab = dialogDefinition.getContents( 'info' );

            var preview = infoTab.get('htmlPreview');
            preview.html = '<div>Image preview<br>' +
                '<div id="ImagePreviewLoader" style="display:none"><div class="loading">&nbsp;</div></div>'+
                                    '<div id="ImagePreviewBox">'+
                                    '<a href="javascript:void(0)" target="_blank" onclick="return false;" id="previewLink">'+
                                    '<img id="previewImage" alt="" /></a>This is some example text around the image' +
                                    '</div>'+'</div>';
        }
    });

Another option to customize the lorem ipsum text would be to mark is as a configuration option or language item (like the title above it: editor.lang.image.preview), but this is a worse move in my point of view. I haven't studied if I'm wrong and it can't be done other ways, but it seems that then it will specify the whole "Lorem ipsum... " text at the plugin.js level or in each language file. Either of those options means that all that text will be loaded whenever the editor is loaded even if you are not going to use the image dialog, and the reality is that very few people don't understand that this is just some placeholder and the important thing is only the image, not the text.

Going even further with the customization, the first option (despite its simplicity) is more powerful because it could be used to inject other elements in the html so you could turn it into a real preview by changing the contents of the box with parts of the current content of the editor each time the dialog is loaded. I won't recomend that as it might mean some extra work to make it work as desired, but it's certainly doable.

So I would say that just adding the id is enough and it can allow the developer to chose betweeen removing the preview (this requires some other fixes in that dialog) or change its contents.

2009/12/12

Uploading .app files

Developing an extension for an application like CKEditor means that you have to be aware of the environment. CKEditor tries to cover the differences between the browsers and even adds workarounds for the bugs that are known in each one (as long as it's possible of course)

So when you are writting your code and something doesn't work as expected you have to start searching for bugs in your own code (the usual culprit), then you have to review how you are using the CKEditor API, verify that you have understood the explanation and that it matches the actual code that you have written. Then you escalate up the problem to the CKEditor source code, maybe there's some bug there that it's causing your problems. And sometimes you have to get even higher in the chain...

One of these situations is an issue that I've talked about in the post about uploads in CKEditor, I did try to apply an onChange listener for an uiElement with type="file", but it didn't work. After debugging the issue I found that despite the huge differences in DOM (this element was created as an iframe with a form and the <input type="file">), the event listeners aren't aware of this difference, so they apply the listeners to the container <div>.

This week I've been writing the code to fix this problem (that's the beauty of open source, when a problem is spotted it's possible to really check what's going on under the hood and create the patch without waiting for a company to include it in their plans), and after some tests it seemed to work: as soon as the user picks an image from their computer it is uploaded to the server and they just have to review the Alt attribute, the Class or the Title.

I had to review that the events are being reapplied correctly when the iframe recreates, so the tests (somewhat random) also included uploading non image files to verify that they are handled properly and everything still works.

Then I started to find that something was wrong, I selected an application, the file picker showed the name but there was no message. I debugged it further and the onChange event was being correctly fired (well, at first it was fired too many times due to a bug in the logic to append the listeners, but this is what these tests were about).

Maybe not CKEditor?

I had this form inside an iframe in a lot of nested code with lots of javascript, custom events and God knows what else. The next step was to create a simple testcase to focus on the problem:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    <head>
    <title>Upload test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <script type="text/javascript">
            function fileChanged(e)
            {
                var input = document.getElementById("upload");
                console.log("Selected file: " + input.value);
                //input.form.submit();
            }
        </script>
    </head>
<body>
    <form method="POST" enctype="multipart/form-data">
        <input type="file" name="upload" id="upload" onchange="fileChanged(event)">
        <input type="submit">
    </form>
</body>
</html>

This showed that I could select any file and it showed the name correctly and the form was posted by pressing the submit button or with the js call. But if I selected an application (I'm using currently a MacBook) then it showed the name and the form wasn't submitted. No error message, no warning, nothing.

The simple test removed CKEditor out of the equation and avoided long hours trying to find a non-existing problem in that code, and instead it pointed to some other basic issue with the browser and the upload of Applications (.app)

The current browser was Firefox 3.5, so I searched bugzilla but I wasn't able to find any report about this, the only bug that reported some strange problem was one about sending sometimes files with 0 size to the server.

So I added

                console.log("File size: " + input.files[0].fileSize);

and it showed the .app to be files of 0 size.

Something is quite wrong here!. I tested Safari and it seemed to allow the upload the .app but it showed the size as 102 bytes instead of several Mb.

Then I finally realized that an Application in Mac OS X is really a folder with subfolders and files, so this is the reason why it fails. Firefox accepts the .app in the filepicker but at the upload time it somehow sees that it is a folder (or that it isn't a real file) and rejects to send the form, but as I said, there's no warning about the problem. Testing with Opera did provide a message stating that the path pointed to some file that it couldn't find.

The most interesting situation was Safari, I changed the form to point to a real script that would handle the file and the result was that the application has been compressed on the fly and sent to the server as a .zip

How does each browser behave?

This is a live test with some code similar to the above:

So on one hand we have Firefox, it doesn't allow to upload the .app and doesn't provide any hint that there's a problem with the selected file. Also shows the size as 0.

Opera does its job much better and shows a tooltip on the <input type="file"> explaining that there's a problem there. Better but still not good enough as people might get confused about the reason of the problem. It hasn't implemented the API to get the size of a selected file.

Safari is quite different, if you select an .app then it will compress it and send it as a .zip so even if the user might find it a little strange that it has bothered to compress the data he finds that he was able to upload the file that he wanted. The only issue is that it shows the size as 102 instead of showing the same data that it's in the Finder.

The final test was the new beta of Google Chrome. Like Safari it showed the size of an .app as 102 bytes, but when I tried to upload it to the server (localhost) it just hanged. It showed a warning about a non responding page and no matter if I selected to close the page or ignore the warning. Chrome was dead and the only solution was to force quit it, even if the tab can be closed, something remains working there that didn't allow a clean exit.

So this can lead to one bug report for each browser (well Opera is almost OK, so it should be just a feature request), so much fun and job for a little test trying to find a bug in my code or CKEditor.

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/08

Class selector in easyUpload

Following with this serie of posts about the new easyUpload plugin for CKEditor, today I'm gonna talk about the class selector for images.

What's the class selector

The selector is used due to two reasons:

  1. Avoid showing the user all the options about Border, HSpace, VSpace. This avoids clutter in the dialog, avoids the use of presentational attributes (changing the underlaying code to use sytles instead of attributes is not enough, they are placed there on each image and so they can't be used in a somewhat unified way) and puts the presentation in the stylesheet instead of the html document.
  2. Asking users to use a text box and learn the available class names that they can use it's not an option. They won't. It's not their job to learn the names that the designer has selected so they are gonna use any kind of cheatsheet, so it's better to offer that cheatsheet inline so they pick the one that they want and see how it does affect the image.

You can try to tell them to just upload the picture, don't change anything in the dialog about the presentation and use the Styles combo in the main menu, but this is too complex: If they shouldn't use something in the dialog, then they shouldn't see that option at all, and it seems logical to then put the options of the Styles combo related to the image in that dialog.

There's something about this option that you might argue that it shouldn't be done this way and it's the fact that only class names are used from the Styles data. It won't use the css style="", or any other attribute that you can specify in the definition of that Style, but I've already said that from my point of view this is better to avoid leaving scattered attributes and styles all around the HTML instead of putting the presentation in the stylesheet.

This is an example of a valid definition that would be shown in the class combo of the easyUpload dialog as "Nice picture" and if selected the image would end up with a class="highlighted"

    {
        name : 'Nice picture',
        element : 'img',
        attributes : { 'class' : 'highlighted'}
    },

CKEditor vs CKEditor

The UI of the dialog is quite similar to the previous one for FCKeditor (some small layout differences aside), but the underlying code for this feature is quite different.

In order to make this feature work, previously I just re-scanned the data in the FCK.Styles.GetStyles() array looking for element=img and the use of any class. But the new implementation of the styles combo makes this approach impossible. It's using private data locked inside the object so it can't be used outside of it, and thus the plugin now needs to copy the loading data code (fortunatelly the "resource manager" in CKEditor means that the file is loaded only once and then it's shared for every later request). Trivia: This investigation of the source code led to a bug found while trying to understand the behavior; The styles combo is "dead" until you first click on it, its initialization is delayed until it's shown so if you don't click on it, the data that it's shown is always the same because it has no data to match the current style of the selection.

The bad part about having to request the load of the stylescombo data is that if in the future the format of the data is augmented to allow XML files like it was possible in FCKeditor, or even if a plugin is added that does allow to parse the stylesheet and create the proper entries automatically, then that code won't work for this plugin due to the fact that it's hardcoded to load just the js definitions file, the easyUpload plugin would need to copy again the part about how to load the styles data.

The idea about creating a plugin that does scan the stylesheet and automatically populates the Styles data instead of having to define in an external file the available Styles is a clear advantage. I've been using one such plugin for several years in FCKeditor and I've never had to care about adjusting the (at that time) XML file with the definition of the style for each site. Just defining properly the css selectors in the stylesheet is enough to make the plugin parse the data and fill the combo. Unfortunately the code was developed for a company so it can't be shared, but I can assure you that it's quite simple; Due to the new structure (with private data) of CKEditor I'm not so confident about the ease of coding it to use this kind of feature in the new version. But if you look at the posts of beginners (and maybe not-so begginers) about the use of the Styles combo a question repeated over and over again is why the styles combo isn't filled with the data from their stylesheet.

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/12/06

Simple things are hard to achieve

A very important part of easyUpload is that it must be easy, it should protect the user against unexpected behavior, it should be straight forward and simple, let's see two details:

Tabs

There are no tabs in the dialog. If no image is selected in the editor then it starts with a screen asking the user to select the image that he wants to use, and the options are quite clear: upload from the computer, browse the server, use an url.

In order to hide the tab bar (because in reality the code is splitted in two tabs, but only one of them is visible each time) I had to find a workaround because as I said only one tab is visible, but the code checks for the number of tabs and then it decides to make the tab bar visible.

So this is one possible solution (in the onShow method of the dialog)

this.parts.tabs.hide();

This does indeed hide the tab bar, but the spacing remains there, a better solution is to mark the dialog as "single paged":

this.parts.dialog.addClass("cke_single_page");

This little trick adds the class used by CKEditor to mark a dialog as single paged, so the tabs bar is gone as well as the spacing.

Ok button

If the user browses the server, as soon as he has picked the image the first screen dissapears and then the image properties are shown. The other two options have a button each one to confirm the selection, but there's a problem: the big "OK" button at the end. Users might click on it instead of using the one besides its option, so we need to listen to this button.

That's easy, you just need to add an event listener on the dialog for the 'ok' event.
But wait, it doesn't work!
The dialog keeps showing the dreaded "Some of the options have been changed", not our event handler!

Fortunately the Event system in CKEditor has a nice feature: The ability to specify a priority for our listener so we just need use a priority lower than the default (10) to get the event first. But if you look at the code you'll find that the dialog constructor adds its listener for this event (the one that later will show the alert) with a priority of 0. Oh, no.

Why does it needs to specify 0 as its priority? If it's the first listener that it's being added (and we are talking about the constructor) it should get fired first by default without the need to specify anything and saving those little bytes in the code.

What can we do now?
If we add the listener also with 0 it will be called later anyway :-(
But...
We are talking about javascript, in javascript there are no unsigned integers, just numbers, so this little trick is enough

    this.on( 'ok', function( evt ) {
...
    }, this, null, -1 );

Exactly, by setting our listener with priority -1 we get over the default one and no unexpected prompts will be shown :-)
 

Making options simple for the plain users usually requires a significant ammount of time to find the right parts to tweak, so it isn't strange that as the software becomes bigger there are some rough edges and only by focusing on some narrow part it's possible to improve it at the cost of not working on other improvements.

2009/12/05

File upload in CKEditor

CKEditor redefines the way that dialogs are created by defining them as a hierarchy of "uiElements", each element is defined as a JS object and its type define its behavior.

You can see this if you open the source of any of the dialogs and search for "contents :", there you'll find the structure of the tabs and you can see how the "type" property is used with values such "hbox"/"vbox" (for layout of several elements: horizontal/vertical box), other values that matches typical HTML form elements: "text", "button", "password", "textarea", "checkbox", "radio", "select", "html" (this one allows any kind of html to be used directly), and some special ones: "file", "fileButton". These elements and behaviors are defined in the dialogui plugin.

File upload

When you want to provide the "quickupload" feature you must use the "file" uiElement, this one takes care of creating an iframe whose contents are a form with an <input type="file">. But of course, you must specify the url to POST the file, so this is handled by the fileBrowser plugin. This plugin scans every dialog based on the dialogDefinition event and then adjust the properties so the form is created correctly as well as adjusting the buttons used to launch the file browser. This is done based on the existence of a "filebrowser" property in the element definition.

The current definition of the "file" uiElement has some bugs/problems, and so far I haven't fixed all of them as it took me long enough to understand the behavior and I needed to move forward, but I'll try to look at them in order to finish the easyUpload plugin.

One of the problems is that if a user selects a local file but presses the dialog "OK" button instead of the "upload file" [hey, we're talking about users ;-) ] then the dialog might close without any warning and no file is uploaded. This is due to the fact that the .getValue() function is hardcoded to return an empty string, fortunately it's easy to fix that problem at the initialization of our plugin:

        // Fix file input definition:
        CKEDITOR.ui.dialog.file.prototype.getValue = function()
        {
            return this.getInputElement().$.value;
        };

This only causes a little issue: now if you try to close the dialog with the Cancel button it will always state "Some of the options have been changed..." (quite unfriendly message by the way, it would be much better to state "The file upload has been changed..." using the proper label for each element). So we just need to force the initialization of the "initial value" for the element to match the empty string (as I'll explain next, the initialization code is using the container instead of the real <input> so it's doing wrong things at that time)

        CKEDITOR.ui.dialog.file.prototype.setInitValue = function()
        {
            this._.initValue = '';
        };

Of course these two changes are easy and should be available also in the core code, but I needed them right now, so it's easier to provide the fix in the plugin instead of waiting for the patch to get approved.

Pending issues

In the original easyUpload I added the code (that it was then merged into the core) that instead of just letting the user wait for the upload to finish without any indication that there's something going on, he would see the loading throbber. Of course, it's not really "useful" as it doesn't correctly state how much of the file has been uploaded or how much time remains, to see that kind of info you should use Firefox 3.5 with CKFinder 1.4.1.
And it might be argued that sometimes people report problems because they try to upload a file with a wrongly configured connector and they just see the infinite throbber, but the ones that see that problem are the developers setting up CKEditor and I would argue that they should learn a little about all the tools that are available for developers to find javascript errors, server responses, etc...
But let's get back to the topic: the "please wait" message is still missing, and the aim of the plugin is to be user-friendly so it should get one.

Another friendly feature was the automatic upload of the file as soon as it was selected from their computer instead of having to click on the "upload" button.
First let's understand the 'file' widget; it's quite different from the rest of elements as it does create it's own upload iframe, and that iframe is recreated each time the .reset() method is called on it. That happens when you load the dialog, or when a file is uploaded or when the dialog is closed. So every now and the the iframe with the inner form and the <input type="file"> will be recreated, but currently it doesn't fires any event to the parent window, so it can't be tracked to reassign the needed events on it. Besides that, the creation logic of the uiElements is based on assigning the event listeners required on them after they have been created, but due to the iframe element and the oddities of this element, at the time that those assignments happen the inner frame still isn't ready and instead it tries to work with the container of the iframe (quite useless). So the fix for this means that the 'file' uiElement must keep the list of events that it must assign to the inner <input> and that the iframe must notify to the parent container that it has finished loading so it gets reapplied the desired set of events.
Quite a lot of work for a little detail, but the end user just care to use the program without having to take a semminar about uploading files.

The last difference with regards to FCKeditor is that previously if the user picked a ".doc" file in the image dialog he would get immediately a warning stating that this isn't a recognized image format so that won't be uploaded. Now this check at the client side is missing, so the user will upload the file and it's the server the one that must reject it.
This means two things: poorly written server connectors will allow to upload any kind of files at the image dialog and then users will ask why they have uploaded the .doc and it doesn't work (or even worse: iirc correctly Safari in Mac does allows to use a .pdf as source for an img, so they upload the pdf, put it as source for an image and then only Safari users will see the contents). The second problem is that the user has wasted his time uploading the file to the server instead of getting an immediate feedback stating that the file isn't valid for this dialog.

As you can see, there are some rough edges about this functionality that we should expect to improve in CKEditor as it evolves, and meanwhile I'll try to fix them in the easyUpload plugin.

2009/12/04

Porting easyUpload to CKEditor

A little while back I was tasked to upgrade the easyUpload plugin to CKEditor 3. Due to a broken leg this job has taken too long, but it's finally starting to see the end, and I think that writting some little posts about the problems/workarounds that I've used might be useful for other people trying to write some plugins/customize CKEditor.

Given the new events in CKEditor at first it looked like it would be possible to directly use the existing dialogs and then apply our changes over them (you can look at the api_dialog.html file to see how it's done). That was my idea after writting the original plugin for FCKeditor, I saw that I used a bunch of existing code so having a way to modify the dialog before it's used would be great. But the reality is that in their current state those dialogs can't be customized without writting lots of code (and ugly code) to change the inner logic. I'm gonna talk about the image dialog to explain some of its problems, of course I expect this to change in due course and a future version of the plugin might be as clean as originally envisioned.

In the image dialog you just can't remove the preview or the scripts will fail. The possibility of removing that part hasn't been considered/tested and so the code uses it without first testing for its existence. This is easy to workaround (just some "if"s that you can check when the plugin is releaded) and I think that it might be fixed in the official version. Anyway, this isn't a high priority item because the preview is quite nice in order to verify how the image will look like, but if someone cares to provide a patch to do it the chances will be higher :-)

Lock size & Reset size buttons: They are a single entity without any identifier. It's possible to hide them only after the dialog has been created (vs using the dialogDefinition event to remove them like it's possible for other elements). I think that the solution that I've included (just splitting each button as a single element and reviewing a little some scripts that expected those buttons to always exist) is also easy to add to the general code. For me the important part here is the removal of the "Lock size" button, for plain users that option must be checked always and they shouldn't have to option to turn it off, removing it from the screen just removes a question from their minds.

"Some of the options have been changed...", yep, if you load the image dialog with an existing image loaded and then you try to dismiss the dialog, this prompt will be shown leaving you wondering what has been changed and doubting about closing the dialog or not. This is due to a delayed initialization to avoid an IE7 rendering bug (it seems that IE has several problems with the sizes of the dialogs under certain conditions), and so that initialization needs to be patched with the fixed function.

Considering the number of elements to remove, the scripts that needs to be dynamically patched (as explained above) and then the new elements to add with their logic I decided that it was easier for me to start with a copy of the image dialog and then apply all the changes there instead of having to find ways to override everything that I needed while trying to retain the original functions to perform their work.

These are some of the problems that I've faced, but not the only ones, although they have different origin so they must be handled in a different post.