2010/01/18

Alpha 5 of WriteArea

These days I've kept on working on WriteArea and today I've uploaded the fifth alpha version. This version is quite better with regards to the detection of other Rich Editors and finally removes a little code that I added for their detection but I knew that shouldn't be included in a final version.

Most important: this version includes an option in the preferences to open the editor in a bottom pane of the page. I've borrowed the close button from the AdBlock Plus skin and I will try to confirm that this is OK before releasing the final version.

There's a little problem with this bottom pane: it isn't aware of tabs, so when you switch tabs it remains there and it's a little annoying, because if you close the pane you might lose all the content. I'll try to find a solution to this problem, although I'm getting already a little tired of so much work in this extension for the last few days.

2010/01/14

Alpha release of Write Area

I've uploaded a beta version of Write Area to AMO as a little preview of what's next.

New plugin

It includes the Syntax Highlighter plugin by Darren James. Remember that you must configure your page with the SyntaxHighlighter by Alex Gorbatchev

With this feature you can include code in your pages like this:

  var script =  writearea.injectScript(this.parentWindow.document, command);
  var info = script.getUserData('editorInfo');
  script.parentNode.removeChild(script);
  // undetected framework
  if (!info)
   info = {
    type:"undetected",
    data:element.ownerDocument.body.innerHTML
   };

In the editor you click the "code" button and in that dialog you paste the contents and select the programming language that must be used to highlight it. In the editor it will be shown like other <pre> code (currently in blue, but I might change that), and in the final page it will show the code as you see above if you have properly installed the js and css files as explained by Alex Gorbatchev.

Not just textareas

The second feature is the ability to use the editor even on top of other editors. It tries to detect correctly some editors, but it should be able to handle any system where a WYSIWYG editor is used. Just right click on the content and click the Write Area entry, and the rest is just the same.

I'll explain this a little better: there are lots of places where some kind of rich editor is used: CMS, Forums, WebMail... The problem is that some of those places use old versions or editors that have very few features, so trying to use them can be a little painful.

My first motivation for writing this extension was that kind of situation where I had to deal for example with the editor here at blogspot. Fortunately for me I was able to disable it and use a plain textarea and easily work with it in my extension, but not all the editors have the ability to be switched off or to show the current source in a textarea, and one day I had a simple idea to detect this situation (check if the document has designMode == 'on') and work with the innerHTML of the body.

This works as long as the editor doesn't use special elements (like the icons shown here for anchors) as that means that the innerHTML isn't really the HTML output of the editor, to get that info (or to properly change the content so it shows any special elements) the API of each editor must be used. That means that there's a set of editors that I'm detecting and in the rest of cases the body.innerHTML is used for read/write.

The current sets of editors that I've coded to detect is as follows:

  •     CKEditor
  •     FCKeditor
  •     TinyMCE 2 & 3
  •     CuteEditor
  •     YUI Editor
  •     Xinha
  •     EssentialObjects
  •     HTMLArea

I could go on adding support for any other editor out there, but I think that this is enough to test the mechanism and verify that I could increase it in the future. I'm realizing right now that the detection could be extended even with a configuration option to add the required script, this is for example the part that detects CKEditor:

 if (window.CKEDITOR)
 {
  match = nodeId.match(/cke_contents_(.*)$/);
  if (match)
  {
   return {
    type: "CKEDITOR",
    data: CKEDITOR.instances[ match[1] ].getData(),
    setDataTemplate: "CKEDITOR.instances['" + match[1] + "'].setData(##DATA##)"
   };
  }
 }

The important trick here is that the code inside the extension can't call any method or access any custom javascript object of the web page, this is done in order to protect the browser from any external attack that tries to run code with Chrome privileges.

So by default the if (window.CKEDITOR) check always fails because as I said, the code in the extension can use the window of the document, and standard properties like window.document, but won't return anything that has been set by a script.

As a test phase I used the real window as returned by window.wrappedJSObject and I was able to verify that the ideas worked, and kept on that way for a little while, but finally today I have changed all the detection code so that it's done in a single place (previously the code looked quite different) and it's merged into a big string that it's added to the web document  as a script. That script executes a function that includes all the detection parts and returns an object that it's set as a property of the script DOM node, and then in the other side of the fence, the extension gets back that object with the detected data.

Isn't a little strange that I can't get an element added normally to an object like window, but it's possible to pass an object using node.setUserData?

So back to what's important:
Now you can use this extension almost anywhere, it doesn't matter if it's your blog, your CMS or a WebMail app like Hotmail, GMail or Yahoo, if you want to overcome some limitation of the editor, or a "silly" restriction set by whoever added that editor leaving only a few features, you can right click on it and launch the WriteArea extension to work as you please. Note that you might need to disable the option in the preferences of Firefox to allow the pages to replace the default context menu, and you should be aware that dueto bug 486990 if you are using Firefox 3.5 the page might be able to ignore that restriction (but Firefox 3.6 works nicely).

I plan to do some other enhancements before releasing this as a full new version, but I think that it's worth publishing it as a beta to let people test it.

I might edit this post to explain better how to use each of those two features and why they are useful, but I would like to hear your comments about them meanwhile.

Edit 17th January: I've added an example of the output of the Syntax Highlighter plugin and an explanation of how the detection of other editors work.

2010/01/12

New releases of CKEditor and CKFinder

To start the new year, the team at CKSource has released new versions of CKEditor  and CKFinder.

CKEditor 3.1

It is an evolution of the previous version. It's focused mostly on introducing New features, several of them to bring back parts of the old FCKeditor feature set, but there are also new parts like the integration with jQuery and the new Paste system that allows to have a control about what's being pasted into the content.

As these features have been rewritten, don't be surprised if there's a little bug here and there, so if you're sure that something isn't working then head up to the dev site and search for existing bugs (if it's something obvious and easy to reproduce it's highly likely that someone has already reported it). If you can't find any ticket about the problem then file a new one with the steps to reproduce the problem (what's the browser, a sample page to load, what must be done after the page is loaded...) and then if it's confirmed it might be fixed in a future release.

If you don't report the bug with the proper steps to reproduce the problem then your problem might remain there for a long time, but even if you provide a clean set of steps to reproduce, just remember that browsers have bugs so maybe it can't be fixed because it would need some very complex workaround that can introduce other problems or it's just impossible to fix in javascript.

The best way to learn more about the new features is to read the announcement by Frederico and then download CKEditor 3.1 to test them.

Just remember that there's a lot of work going on in CKEditor as you can see in the roadmap.

CKFinder 1.4.2

The simple announcement by Wiktor explains it well, some little bugs, improved compatibility and new translations. The focus of the work is now on the new version that will try to address the missing features that are requested in the CKFinder forum and trying to look even further by making it possible to create more easily new features in the future that no one has dared to request.

There's one thing to notice about this release, as I explained in the previous post Firefox 3.6 hasn't included support of onreadystatechange and the previous versions of CKFinder fail to enable the upload button. The new release includes a little workaround so download CKFinder before your users upgrade to Firefox 3.6 and they call you to ask why does it fail.

2010/01/06

Delivering code half-done in a web browser

Today I was checking something about CKFinder and I've realized that the Upload button doesn't work in the latest beta of Firefox 3.6.

That's strange and quite important as lots of people will be using it as soon as it is released, so I dug into the code to find out the problem. After tracing the differences in the execution of 3.5 and 3.6 I've finally found that when an iframe is created it finish the initialization by checking for document.readyState to avoid problems using an unfinished document in IE, and now that code was triggered also by Firefox 3.6

The fact that Firefox includes a property that was previously supported by IE (and IIRC also Opera and Webkit) shouldn't break anything, that code path uses the document.onreadystatechange to properly listen for the change of state and finish the initialization.

The real problem is that such event hasn't been added to Firefox, so the code never executes!

Everything was OK without support for document.readyState, but now they have added it without including the document.onreadystatechange, so things might get broken due to this big oversight. I suggest all of you to review your code and check for the usage of this event (not to be confused with XHR.onreadystatechange) because Firefox 3.6 might be a real pain for you if you don't patch your code it in due time.

Mozilla guys: thank you for bringing us this extra work.

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.