YUI Dialogs, IE z-index and Tragedy

June 16, 2009. Filed under fail 7 yui 2

Although I happen to be doing a large amount of frontend engineering at work, I've never really viewed myself as a frontend engineer. This is partially a bit of self-conceit, but also I am simply still in the process of paying the necessary dues to rightfully claim that title. This is a story about paying the dues.

At work I am working on the cross-browser stage of a large application and had just finished ensuring Firefox and Safari played nicely, so next up was IE7. I fired up Virtualbox, selected a Windows XP image with IE7 installed, and ran straight into the brick wall that is the IE z-index bug.

A picture of layered squares.

Looking at the above squares, you might assume (well, if they were rendered with CSS instead of being a PNG...) they are layered using their z-index values. Perhaps the code looks like:

.red, .grey {  z-index: 0; }
.why_did_i_pick_such_an_ugly_color { z-index: 1; }
.blue { z-index: 2; }

But unfortunately Internet Explorer doesn't respect the z-indexvalue, so you run into awkward scenarios. My awkward scenario was when I realized my modal YUI Dialogs were no longer rendering as I expected.

The correctly positioned dialogs.

Instead, the dialogs were rendering beneath their underlay, making them difficult to read and impossible interact with.

The incorrectly rendered dialogs in IE.

The devastation was even more complete on IE6, where transparency workarounds cause the underlay to be pure black, and thus trying to use a dialog turned the screen into blackness (unless there was an <input> tag somewhere, in which case it was blackness punctuated by yet another z-index bug).

Working around this issue is actually quite simple, and is covered briefly on the YUI Container page. Simply rearrange the popups to be direct descendants of the <body> tag, and you'll be in business.

Reorganizing the YUI nodes for proper rendering in IE.

In your code, the change could be as simple as:

var yus = YAHOO.util.Selector;
// some code...

// the old way
// d.render("some-element-id");

// the new way
var body = yus.query("body")[0];

Then you're fixed. (Well, you may have to do some new positioning logic...)

Two Hours Later, Everything Went to Hell

After checking everything (hah...) worked, I went off to work on something else. Maybe I was triaging the cancer-like growth of bug tickets, or maybe I was eating dinner, let's not get caught up on the details.

Then I came back to run the application through a quick test-run, and calamity struck with quiet certainty. Let me give you the facts, and then you can spend a few moments trying to figure it out.

First let me describe the system that the bug was occuring in:

  1. On one screen there is a form with two inputs: one serving as a user-facing primary key, and another storing a JSON blob.
    • The blob is populated by the backend when the page loads.
    • The UI's state is re-serialized into the JSON blob when the page's Save button is selected.
  2. Within that page, there are a list of rules.
    • There can be zero or more rules.
    • Each rule has a type, a key, a value, and zero or more conditions.
    • You can create new rules in a popup dialog.
    • You can create new conditions when you create a new rule.
    • Upon clicking Add Rule, the rule is displayed in the primary screen.
    • Rules in the primary screen may be deleted using Delete buttons.
    • Rules in the primary screen may be edited using Edit buttons.
  3. There is a popup container which is displayed for editing rules as well as for creating new rules. It is not opened for deleting rules.
  4. Given the complexity of these various operations, state is maintained in two locations:
    • A JavaScript list containing a dictionary for each rule.
    • The edit screen maintains its own dictionary describing the current changes to the rule being edited, allowing the user to cancel the changes to an edited rule, or to save the changes and to override the corresponding rule in the primary rule list.
  5. Upon clicking Save, the primary rule list is serialized to JSON, injected into a hidden form input, and submitted via POST using the form's submit method in JavaScript. The user-facing primary key is also contained within that form.
  6. If you visit the page without the user-facing primary key in either GET or POST, then you are redirected to the application's front page. (The page is meaningless without an associated primary key.)

Now to describe the bug.

  • If you enter the page, add a new rule and then click Save, you are incorrectly redirected to the front page.
  • If you enter the page and then click Save without making changes, you correctly return to the same page.
  • If you enter the page, delete a rule and then click Save, you are correctly returned to the same page, with the item deleted.

I was stuck at this point for a long time. A long time. Here are some more hints if you haven't gotten it yet.

  • If you enter, edit a rule, then Save, you are incorrectly redirected to the front page.
  • This problem only surfaced after making the adjustment for IE's z-index bug.

Still confused? Hey, I was too.

  • Many--perhaps all, but I'll need a fact-finder--YUI dialogs contain a <form> tag.

And the sad conclusion.

var submit = function() {
    // do some stuff.

There went four hours of my life.