Wednesday, August 18, 2010

Fixing a long-standing issue with TinyMCE

I do a lot of development work with the TinyMCE text editor, since it's the one that powers all the rich text editing capabilities at Lottery Post.  TinyMCE is pretty much one of the most popular JavaScript-based text editors in use today.

I wanted to pass along a solution to a problem I finally solved today, for other developers who also use TinyMCE in their development projects.

The problem is that in Internet Explorer, when you hover your mouse over the blank text editor (no content currently in the editor), instead of the nice I-beam mouse cursor, you still see the arrow pointer.

In all other browsers you see the proper I-beam.

Why is this?  First of all, the text editor is basically an HTML document loaded into an iframe.  It contains all the normal HTML document structure as a Web page, only you're able to edit this particular Web page.  In IE, the body tag does not automatically extend to 100% width and height, whereas in other browsers it tends to inherit the full width and height of the viewport.  Further complicating things is that the HTML tag is an actual DOM element in IE, and the body tag seems to adapt to the dimensions of that HTML element.

As a result, even if you set cursor: text on the body tag within the editor, you won't see the I-beam in IE, because the body tag's width and height are zero. (Or maybe just the height of one line of content.) Because all other browsers automatically extend the body tag dimensions to 100%, you'll see the I-beam in those other browsers.

So, I knew from previous experience that the trick is to set 100% height on BOTH the html tag and body tag, like this:

html { height: 100%; }

body {
   height: 100%;
   cursor: text;

(Note, I also included the cursor: text definition in the body tag that forces the I-beam to appear.)

But there's a problem with doing that in TinyMCE. Because the body tag contains top and bottom padding, the height of the body tag ends up becoming 100% plus 6 pixels (given a top and bottom padding of 3px). As a result, the scroll bar shows up on the right, ruining the editor appearance.

In non-IE browsers, the answer to this would be pretty simple: Set the box sizing model for the body tag to border-width, and the body tag height would become 100% exactly, because the height calculation would include the padding. But, of course, IE versions 7 and lower do not have support for the box sizing model, so that won't work.

This got me to thinking about any possible IE-only browser capabilities that could help to get that body tag to exactly 100%, including the padding. Dusting off my ancient CSS knowledge, I remembered the IE-only expression() capability, which lets you use JavaScript code within CSS.

So I changed my CSS to the following:

html { height: 100%; }

body {
   height: expression(
       (document.documentElement.clientHeight - 6)
       + "px");
   cursor: text;

This would seemingly solve my problem perfectly, because the 100% height set on the html tag would not be a problem for any other browser, since no other browser recognizes it as a real DOM element, and the expression() set on the body tag's height would also be ignored by all other browsers.

So I fired up my test editor, and voila — it worked! I could hover my mouse over the blank text editor and see the I-beam all everywhere the mouse moved. I tested it in other browsers — fine there too.

Then I used the resize handle on the text editor to change the editor's size, and oops — the body tag height was frozen at whatever the initial height was. The editor size was changing just fine, but the body tag stayed a fixed height.

This didn't make sense — I thought expression() was supposed to be a dynamic property that would constantly refresh itself. After all, so many performance books and blogs complain about how expression() should be avoided because it slows everything down from constant refreshing.

So I did some more research and discovered that when IE first interprets the expression, it makes a determination of whether the expression will always return a fixed value (a static value) or if the value will change over time (a dynamic value). I guess this is a performance tweak to make expressions faster.

The way I had coded my expression apparently made IE think that the value was static, so it was only evaluated upon initial page load, and never refreshed after that.

I could not locate a set of hard-and-fast rules for forcing an expression to become dynamic, so I tinkered with it a bit and found that including some kind of test condition would do the trick.

There are indeed many things you can put in the expression that will slow performance to a crawl. For example, you can assign variables, do conditionals (e.g., test? one : two), etc., but they all kill performance because they get executed so many times.

But I did find a dynamic expression that seems to evaluate very fast, and does not slow performance significantly. (IE is slow anyway, but I digress...)

Simply testing for the existance of the window object — something that will always be there — was enough to force a dynamic value, while remaining very fast.

So my final CSS that dyanimcally updates the body tag height as the user changes the editor size is as follows.  (I included some other code in the body tag style definition to ensure all other browser render perfectly.)

html { height: 100%; }

body {
   box-sizing: border-box;
   -moz-box-sizing: border-box;
   -ms-box-sizing: border-box;
   -webkit-box-sizing: border-box;
   height: 100%;
   height: expression((window) &&
       ((document.documentElement.clientHeight - 6)
       + "px"));
   cursor: text;

Simply insert this CSS code at the top of the content.css file in the skin folder you're using, and IE will give you the proper behavior for mouse-over.  I tested it in IE 6, 7, and 8, and all work fine.  I also tested it in the IE 9 preview, and it didn't work (because it no longer supports expressions), but there's still plenty of time to deal with that.

I hope this this tip will help alleviate some long-standing headaches for TinyMCE devs!


At 8:37 PM, truecritic said...

"...but there's still plenty of time to deal with that."

You'll have a beta IE9 in September.

Post a Comment

<< Home