IE7 z-index Bug and Workaround

For the tl;dr Crowd

find all the parent elements in your page that are

  • position : relative
  • position : absolute
  • position : fixed

Set descending z-index values for each. I.e., the first element that matches the above should start with say z-index 1000, the second should get 999, and so on until all positioned elements are given descending order z-index values.

This fix will only work top-left to bottom-right.  Meaning, if you have normal fly-out menus, the above should get your visually at par with Chrome or Firefox.  This may not work for you if your the absolutely positioned visual element is lower down in the DOM hierarchy.

The (really) Long Version

IE7 is an order of magnitude easier to build web applications and web pages versus the Sisyphean effort required to get IE6 compliant.  But it is not without it’s own set of unique and equally difficult bugs.

One bug that has several posts scattered around the ‘net is the infamous IE7 z-index stacking order bug.  A one-sentence precis of this bug is z-indexed elements do not respect their assigned stacking order when used in conjunction with absolute and relative positioning.

Some required reading:

  • Quirksmode – from 2006, and maybe a bit dated
  • Brenelz – decent fix, but incorrect assessments in main post
  • StackOverflow – accepted solution has the correct analysis, and a fix for the asker
  • Webdemar – offers a simple solution – it may be all you need to fix your issue

A brief sample

To illustrate, examine this example: Simple Example

View the source of the sample page to get an idea of the markup that will cause this bug.  The example shows exactly what is needed to fix this particular instance, but to fix the doubtlessly more complicated examples found in most web applications/pages requires a bit more digging, and a lot more understanding.

If the above links didn’t solve your issue, you are in the boat I was (still am?) in; sometimes floundering to apply what should be turn-key solutions/workarounds into magical fixes.

The problem

The gist of the IE7 z-index bug is that a positioned element (i.e., one that is positioned absolutely, relatively, or fixedly) will reset the z-index stacking order to 0.  In a vacuum, this would go unnoticed, but when applied in conjunction with other elements that are absolutely positioned, the bug symtoms show themselves.

The solution at a glance

The workaround is to cascade a reverse z-index through your DOM; in effect, you assign the highest z-index to the positioned elements as you work your way down through the node tree.  Assume the following example has position:relative for all the elements:


body
    div - 10000
        div - 9900
            div - 9890
            div - 9880
                div - absolutely positioned menu (or something)
                    - this will display above 9800 (and its widget), 9700, etc.
        div - 9800
            div - widget, child nodes are absolutely positioned
                (before the z-index fix, this would obstruct the menu)
        div - 9700
    div - 9000
    div - 8000

A simple bit of Javascript could massage the DOM for you automatically.  Say the problematic widget is an absolutely positioned flyout menu that is built and applied as the child of DIV 9890.  Upon instantiation, have a utility method crawl up the DOM tree and sniff for postioning, and if found, apply a descending z-index value.  Something like the following pseudo-code should get you well on your way:

var IE7Kludge = {
    zIndex : 10000,
    applyIe7Workaround : function(el) {
        var p = el.parentNode;
        while(p != document.body) {
            if(p.style.position == 'absolute' || p.style.position == 'fixed' || p.style.position == 'relative') {
                p.style.zIndex = this.zIndex--;
            }
        }
    }
}
IE7Kludge.applyIeWorkaround(document.getElementById("myWidgetRootId");

 

Another problem

The crux of the issue is finding all those elements in your DOM hierarchy that do have positioning.  In the apps I work on, there are literally dozens of widgets running side-by-side that instantiate, append, and apply behavior after the DOM has loaded fully.  The above solution caught roughly 75% of the issues, but there were lots of stragglers that I had to manually find and apply my fixes.  For this, I used a slightly modified version of the above, and ran it before my widget-triggered version (pardon the jQuery, but one could easily modify this to fit POJS or any number of other libraries that offer CSS selectors):

var IE7Kludge = {
    zIndex : 10000,
    fixStragglers : function(j) {
        var idx = this.zIndex;
        j.each(function(i, el) {
            idx = idx-10;
            $(el).css("zIndex", idx);
        });
        this.zIndex = idx;
    }
}
IE7Kludge.fixStragglers($('.myCrazy, .selector, .that-finds-all-relative-positioned-elements'));

Yet another problem

With the above two utility methods, I was able to get to about 90% compliance.

The last 10% is due to the fact that this workaround works only top-left to bottom-right. If I only supported widgets that flew out to the left, or top to bottom, there would be no more work to be done, but if you have widgets that fly out bottom-up (say, a viewport aware info bubble that pops above an anchor element if near the bottom of the viewport), its hierarchical z-index stacking is below elements above it in the DOM.  So yet another set of headaches to deal with.

The fix for this last issue is similar to the first kludge, walking the DOM.  There are some caveats, and this may or may not apply to your situation:

  1. While running the 1st fix, I made sure to keep references to the modified elements in order of manipulation, working my way up the DOM tree.  It was easy enough to do this by .push()’ing the element references into a handy array that I could access in a callback when the fly-out closed itself.
  2. While iterating through that array, we must treat it FILO (first in, last out) stack, as IE7 apparently calculates the z-index stacking order from the root node onwards, not as a gestalt sum after all behaviors are applied.  The handy array.pop() method makes this a simple exercise.

The pseudo code for this (again in jQuery-speak):


var IE7Kludge = {
    zIndex3 : 10000,
    seed : new Date().getTime(),

    generateGUID : function() {
        return "custom_id_" + this.seed++;
    }

    handleInstanceManipulation : function(el) {
        var pos, j, z, p = el.parentNode;
        var backup = [];
        var z3 = this.zIndex3;

        while(p != document.body) {
            j = $(p);
            pos = j.css("position");
            z = j.css("zIndex");

            if(pos == 'relative' || pos == 'absolute' || pos == 'fixed') {
                if(p.id == "") {
                    p.id = this.generateGUID();
                }

                backup.push({
                    selector : p.id,
                    zIndex : z
                });
                j.css("zIndex", z3--);
            }

            j = null;
            p = p.parentNode;
        }

        return backup;
    },

    resetInstanceManipulation : function(backup) {
        var i;
    	while(backup.length > 0) {
    	    i = backup.pop();
    	    $(i.selector).css("zIndex", i.zIndex);
    	}
    }
}

Home stretch

With the combination of the above 3 workarounds, I was able to get my web app fully compliant in IE7. For convenience’s sake, here’s a skeleton of my static methods. If you’re in a jQuery shop, it’s basically a drop-in, otherwise you’ll have to account for how the DOM reports CSS values (instance, javascript assignment, and external css files). Hope this helps someone.

var IE7Kludge = {
    zIndex3 : 10000,
    zIndex : 10000,
    seed : new Date().getTime(),

    generateGUID : function() {
        return "custom_id_" + this.seed++;
    },

    applyIe7Workaround : function(el) {
        var p = el.parentNode;
        while(p != document.body) {
            if(p.style.position == 'absolute' || p.style.position == 'fixed' || p.style.position == 'relative') {
                p.style.zIndex = this.zIndex--;
            }
        }
    },

    fixStragglers : function(j) {
        var idx = this.zIndex; // this value must stay at the preset value every time, so we copy it
        j.each(function(i, el) {
            idx = idx-10;
            $(el).css("zIndex", idx);
        });
        this.zIndex = idx;
    },

    handleInstanceManipulation : function(el) {
        var pos, j, z, p = el.parentNode;
        var backup = [];
        var z3 = this.zIndex3; // this value must stay at the preset value every time, so we copy it

        while(p != document.body) {
            j = $(p);
            pos = j.css("position");
            z = j.css("zIndex");

            if(pos == 'relative' || pos == 'absolute' || pos == 'fixed') {
                if(p.id == "") {
                    p.id = this.generateGUID();
                }

                backup.push({
                    selector : p.id,
                    zIndex : z
                });
                j.css("zIndex", z3--);
            }

            j = null;
            p = p.parentNode;
        }

        return backup;
    },

    resetInstanceManipulation : function(backup) {
        var i;
    	while(backup.length > 0) {
    	    i = backup.pop();
    	    $(i.selector).css("zIndex", i.zIndex);
    	}
    }
}

 

Border Around IE6, 7, 8 Form Submit Button

IEs have an interesting behavior around forms that may drive pixel-perfect designers and implementers nuts.  In certain conditions, IE6, 7 and 8 will “enhance” the form functionality by showing you which button is the submit button by drawing a black, 1 px border around it.

If you’re like most web-devs, you style inputs, textareas, and especially buttons to match the look-and-feel of the overall site.  Usually that entails setting properties like background-image and border.

The issue can be reproduced when the following criteria is fulfilled:

  1. use a form in your site
  2. use the standard <input type=”submit” /> button to trigger form submission
    • OR use the <button type=”submit”> to trigger form submission
  3. style the button visually using CSS

My test page can be downloaded here: test page

In this image, we see a standard form in IE7 with one text input field and one submit button.  Both the inputs are styled with a 1px solid #ccc border and #eee background-color.  Pardon the horrible render; MS Paint doesn’t have a lot of options for export.

formOff

In this image we see the same form in IE7 with focus (by clicking into the text input field).

formOn

It’s a subtle change, and most won’t notice it.  It only becomes an issue if your UI/UE team gets really antsy about visual changes that they didn’t want.

Two simple fixes come immediately to mind.

  1. Change the <input type=”submit” /> or <button> to a simple button and trigger the submit with javascript.
  2. Implement a CSS fix that tricks IE into not displaying the border.

For solution 2, the fix is a pretty straightforward one with some caveats.  Add this bit of CSS to whatever style cascades down to the submit button:

.fixIE {
filter : chroma(color=#000000);

}

“Filter” is IE-specific.  “Chroma” will set to transparent whichever color is cited, so in the above case, if an element has or inherits the “fixIE” class, anything with the color #000000 (or black) will be set to transparent.

The caveats should be obvious.  Not only will the extra border be transparent, but if the default text color or background-color is #000, it will be rendered transparent.

There’s a slightly less-obvious caveat as well to this.  Even as the color itself is rendered transparent, IE will still draw the border around the button.  The net effect is that as the form receives focus, the submit button will act as if  a 1px margin was applied around the element, and it will visually contract.  It will expand back out to the previous size if focus moves to some other element in the page not contained by the form.

Pretty ugly hack, and I’m inclined to tell you to just use fix 1.

PHP Dynamic Modules Mac OS X

My development machine is currently a MacBook Pro Core2Duo.  It also happens to use a 64bit architecture.  My previous machine was a MacBook Pro CoreDuo which was 32bit.

Compiling a simple dynamic extension for PHP became something of an issue when moving on to the newer MBP.

I was trying to install the RAR extension for PHP on my dev laptop to test a few unrelated bits and I got mired in the process of getting this damned extension to load for Apache.  My error logs, enabled just to debug this process, were filling up with:

PHP Warning:  PHP Startup: Unable to load dynamic library ‘/usr/lib/php/extensions/no-debug-non-zts-20060613/rar.so’ – (null) in Unknown on line 0

Every page that describes the process had the same steps.  Get archive.  Untar,  ./configure, make, make install, make test, add extension=rar.so.  Blah blah blah.  One typical test was to run the CLI version of PHP and confirm that the dynamic extension was loading and was not causing startup errors:

php -i | grep rar

or

php -m | grep rar

Yep.  No problems.  I could even run tests like:

php -r “echo rar_open(‘myRar.rar’);”

The above would give me the internal ID for the rar object handle.  Great!  I was in business.  However, after bouncing Apache 2, my phpinfo() page failed to show RAR as a loaded extension.  The above impromtu tests confirmed that the proper modifications to php.ini and paths to my extensions_dir were correctly set.  WTF?

Doing some serious googling, I stumbled on a thread off of Marc Liyanage’s invaluable Mac OS X PHP site entropy.ch: http://www.entropy.ch/phpbb2/viewtopic.php?t=2877

MACOSX_DEPLOYMENT_TARGET=10.5 CFLAGS=”-arch x86_64 -g -Os -pipe -no-cpp-precomp” CCFLAGS=”-arch x86_64 -g -Os -pipe” CXXFLAGS=”-arch x86_64 -g -Os -pipe” LDFLAGS=”-arch x86_64 -bind_at_load” ./configure

To sum it up in one line, compiling a dynamic extension for PHP may default to a 32bit architecture.  This will fail when PHP as a DSO for Apache 2 is compiled against 64bit libs.

The punchline?  RAR now loads for Apache as intended and works a charm.  The CLI version complains every time I invoke it :).

Javascript toSource() in IE6/7

Mozilla based browsers have a pretty nifty instance method “toSource()”. It simply outputs a string literal representation of any Javascript object, which is at heart, *anything* in the Javascript interpreter.

The main reason I use this method is to do a quick & dirty deep copy of Javascript objects and arrays. When paired with the “eval()” function, you can be 100% sure that the new array or object you just created is its own instance and doesn’t have reference pointers to existent objects or arrays.

My main use-case is using an object as hashmap or associative array, but needing to copy it regardless of whether one of its member attributes is another object or array (which would preserve the pointer). Performing a toSource() and eval() allow you to do just this.

var burdensomeBabyNames = [ ... ]; // imagine a deep array of strings, other arrays, objects, etc.
// now we have a deep copy, quick and easy|dirty
var burdensomeBabyNamesCopy = eval(burdensomeBabyNames.toSource());

For example, say you have an array of names in memory: “Oedipus”, “Ophelia”, “Freya”, “Loki”, “Lachesis”, “Atropos”, and “Morta”.

If that array was instantiated in memory as “burdensomeBabyNames”, the string literal representation would look something like:

var burdensomeBabyNames = [
  'Oedipus', 'Ophelia', 'Freya', 'Loki',
  'Lachesis', 'Atropos', 'Morta'
];

In a Mozilla based browser, one could call “toSource()” return an useful textual representation of the string literal. I.e., calling:

var xNames = burdensomeBabyNames.toSource();

The var xNames would now contain the string:

['Oedipus','Ophelia','Freya','Loki','Lachesis','Atropos','Morta']

Great, huh? That’s really useful if you never have to consider the Internet Explorer crowd, but since IE still owns a ridiculously high percentage of the browser usage pie, folks who use this functionality need a work-around.

Anyone who’s worked with asynchronous calls vis-a-vis JSON (Javascript Object Notation, see http://www.json.org) will see that the toSource() call is just a rough draft of the much deeper JSON.encode().

The same example as above could be rewritten to use JSON encode and decode:

var burdensomeBabyNames = [ ... ]; // imagine a deep array of strings, other arrays, objects, etc.

// serialize or "toSource()" the array
var burdensomeBabyNamesJSON = JSON.encode(burdensomeBabyNames);

// now we have a deep copy, quick and easy|dirty
var burdensomeBabyNamesCopy = JSON.decode(burdensomeBabyNamesJSON);

One could just include the minified version of JSON.js from the above link and call it good. If you happen to be working within a framework like YUI (Yahoo UI, see http://developer.yahoo.com/yui) or ExtJs (see http://www.extjs.com), the encode/decode functionality is already folded into the core library.

Portable Apps – A (newish) Friend for Web App Devs

PortableApps.com wraps popular open source projects in self-contained, portable containers. Their products will happily live on a USB-key and (so far) work as well as the standard install-spam-registry-and-or-user-data-folder versions.

In particular, the Firefox Portable versions are of some note for Web (App) Developers who are required to test and support their output on older browsers.  While Firefox versions are generally happy to share cache, bookmarks, etc., extensions are often version specific; some critical extensions such as <a href=”http://www.getfirebug.com/”>Firebug</a&gt; are major-version sensitive and make juggling several versions a time-consuming headache.  The portable versions remove the forementioned shared resources to their own directory structure wrappers and allow full-speed, simultaneous usage of several versions of Firefox (as of this writing, 2.014 and 3.0).

Much kudos to the Portable Apps team!

Make a donation if they make your work-life better ;).