Browser Compatibility Tips (IE VS. IE)
After designing an application and building it for IE7, I was finally ready to try it out in IE6. If you haven't already, download the Virtual PC image from Microsoft for IE6 testing. I was pleasantly surprised that for the most part, everything worked perfectly! There were some issues that I knew I was going to run into, since I did take advantage of some features added to IE7 ( css pseudo classes for :hover, fixed positioning, min/max dimensions), but since I knew about them, I had already planned on what to do.
Hot Tracking - CSS or Javascript?
Rather than reverting to pure javascript to provide hot-tracking behaviors, I chose to use the CSS :hover pseudo class for IE7 and Mozilla, and only revert to javascript for IE6 and prior versions of IE. Now I know this sounds like double the work, but in reality the difference is minimal. The javascript can identify elements by examining their className property, and treat them accordingly. My :hover pseudo classes mainly toggled the display/visibility of other elements, so my javascript was as simple as finding the particular element and setting the style.display property. The big question was how to best switch between CSS friendly IE7 and the javascript requiring IE6 browser functionality. After investigating different options for 'switching' my logic, I finally decided to use the Internet Explorer proprietary conditional syntax. Here's an example
<!--[if lt IE 7]>
<script type="text/javascript" src="ie6_behavior.js"></script>
<![endif]-->
<!--[if IE 7]>
<script type="text/javascript" src="ie_behavior.js"></script>
<![endif]-->
The syntax is pretty simple, you can do comparisons based on an exact match, or < (lt) and > (gt). And since the whole thing is contained in an html comment tag, other browsers will simply ignore the entire block of text. In the example above, I'm including the ie6_behavior.js script if the version of IE is less that 7. The script file then attaches to my elements to provide the hover behaviors. You'll notice that there's a second condition that targets IE7.. I found that IE7 didn't behave as expected when creating a complex :hover action. The basic problem was that one of my elements wasn't being displayed properly after the hover class was applied. To fix the problem, I had to force IE to re-render that area. What's the easiest way to do that? toggle the style.display property. Because this isn't required for Mozilla, I decided to conditionally add it for IE7 only.
The beauty of this approach is that you can cleanly separate out your logic for legacy systems, making it easy to remove at a later point in time. And just because my example does this with javascript files, don't think that's what you're limited to - you can put these tags anywhere on your page, and put any content inside of them. I've used this same technique to import different stylesheets when dealing with minor browser differences, and it works perfectly.
The Unexpected Bugger - PNG Transparency
While I was prepared to do battle with css differences, I was pretty much blind-sided by the good old .png transparency issue with Internet Explorer. I had already been so used to IE7's support for the .png file format, that I forgot how picky IE6 was. I remembered reading a while back that there was a solution that involved using one of the proprietary image filters for IE, but I wasn't sure how complicated the solution was to implement. After searching for a bit, I found Microsoft's article. Unfortunately, the recommended solution does not provide parallel functionality for browsers which do not require this hack. Fortunately, there is another way.. You can apply the same filter to an image element... the only difficulty is transferring the image's original src attribute to the filter. Luckily more IE proprietary functionality to the rescue. By attaching a behavior to every image element, you can use javascript to transfer the src attribute. Attaching a behavior is simple - create a css selector for all images, and set the behavior to your .htc file. Here's an example
img { behavior:url(mybehavior.htc)}
Unfortunately, this is where it starts to get ugly. The IE implementation of the behavior css extension uses a different url resolution process than say, background-image. While we're used to url's being relative to the stylesheet when dealing with background-images, behaviors are relative to the page - yuck. If you truly want to create a re-usable solution, you'll need to reference your behavior from the webroot - which we all know is apt to change sooner or later, breaking your functionality. Hopefully the hack wont be needed by the time that happens. To make matters worse, we need to reference a transparent gif as well, and that too ends up resolving to be page relative. You'll need to use a root-relative path there as well.
There are still a couple more hurdles. The behavior you create, needs to be 'applied'. This is done through an event model, based on events fired by the DOM. If you think "onload" might be a good time to perform your logic, then you'll be just as surprised as I was to find that it isn't. For one reason or another, using the onload event provides you with the most random behavior you've ever seen. Images are either shrunk down to some odd dimension, or stretched beyond belief. Instead, use the "onDocumentReady" event.
Here's my final .htc
1 <public:component>
2 <public:attach event="ondocumentready" onevent="FixTransparency()" />
3 <public:attach event="onpropertychange" onevent="PropertyChanged()" />
4 <script type="text/javascript">
5 var FILTER_STRING = 'DXImageTransform.Microsoft.AlphaImageLoader';
6 var _TRANS_IMG_URL = '/2007.2/transpx.gif';
7
8 function FixTransparency()
9 {
10 var source=this.src;
11 if((source.length>3 && source.substring(source.length -3).toLowerCase()=="png") || source.indexOf("gaugeimagepipe.aspx?GaugeID")>0)
12 {
13 if (this.currentStyle.width == 'auto' || this.currentStyle.height == 'auto'){ this.style.width = this.offsetWidth + 'px'; this.style.height=this.offsetHeight + 'px';}
14 var filter=filters[FILTER_STRING];
15 this.src = _TRANS_IMG_URL;
16 if (filter)
17 {
18 filter.enabled = true;
19 filter.src = source;
20 filter.sizingMethod = 'scale';
21 }
22 else{ style.filter = 'progid:'+FILTER_STRING+'(src="'+source+"\",sizingMethod='scale')"; }
23 }
24 }
25 function PropertyChanged(){
26 if (event.propertyName == "src")
27 FixTransparency();
28 }
29 </script>
30 </public:component>
A couple of notes on the code above. I'm replacing my original img src with a transparent gif (transpx.gif). If I didn't do this, you would still see the original image over top the transparent one.. which would look as if you did nothing at all to fix the problem. I've also added a PropertyChanged listener. In case the .src property of my image get's changed on the fly, I'll want to update the filter. Oh, and see the check for "gaugeimagepipe.aspx?GaugeID" - that's because my gauges are being served up as PNGs, but the src points to the imagepipe, not a .png file. It's a bit of a hack - but it works.
When all was said and done, I really was surprised at how few problems I encountered. The trick was building my application using standards based XHTML document type, and keeping the differences in mind during development. If you follow this simple pattern, you'll have very little work to do when your complete. Throw a couple of sanity checks in for firefox as well, and you will have a cross browser compatible site created with very few headaches.