Wednesday, November 5, 2014

Solving Glyphicon Support In IE7

Yes, I am talking about IE7 support. If you don't care about half of the how and why, you can just scroll down to my solution. If you care a little bit more about why I came up with this solution, read on.

The Backstory

Bootstrap has become a staple to websites like salt and pepper have to a dining room table and while most of the world developers have moved on to Bootstrap 3, my current project is reveling in Bootstrap 2.3. To some this may seem like a travesty, but I have good reasons for it, the most prominent being required IE7 support, and that is where the reasoning ends in this post.

Actually, if I really think about it, this blog deals less with me being on Bootstrap 2.3 for this application, and more with the Glyphicons used in Bootstrap 3. Why? Well, I had a need for Glyphicons from BS 3 in our BS 2 application so, like any good developer would do, I ported them over. Done. Well, kinda...

Bootstrap halflings sprite
See, Bootstrap 3 doesn't support IE7, and for good reasons, but our application has to. And we needed the Glyphicons in sizes that range from "you can read that?" to "in your face!" which is why we couldn't use the Bootstrap 2 icons; they're a sprite comprised of fixed sized icons.

So, again, I ported over the BS 3 Glyphicons. But here lies a significant road block: Glyphicons use the "content" CSS property with pseudo-selector ":before" and IE7 doesn't support that. This was a headache for me at first, but then I found this nice little LESS project: Glyphicons for IE7.

This solution uses Dynamic Properties (CSS expressions) to populate the "innerHTML" of the Glyphicon element with the entity code for the proper icon. It's a clever solution and since I have been in modern browsers for so long (thankfully), I had forgotten about this hidden gem/curse. It's a gem because it gives you flexibility to compensate for IE shortcomings. It's a curse because expressions are JavaScript functions that are executed with the application of the CSS and that is not very performance friendly. In this case the expression is executed using a "zoom" property which tells IE7 the element "hasLayout", triggering the expression. The performance of doing this isn't too bad, but it leads to another issue.

Since expressions are executed when the browser loads the CSS, any dynamic changes will not work. The expressions are only executed once; never again. To provide an example of where this fails, I am using a "glyphicon-chevron-down" to indicate a collapsible section on my page. When I click the chevron, the section below expands and the icon changes to a "glyphicon-chevron-up"... except in IE7. Changing the class doesn't force the expression to execute again, so the "innerHTML" set with the expression will never change. Yuck, but okay. There is another property that can be used to trigger dynamic expressions: "top". And, it works any time you change the class on the page. But about that... I forgot something important about that approach: performance. A word to the wise: Don't use "top: expression(bla bla)" when you have a bunch of elements it will execute on.

Here you can see the original expression, the bad expression, and my final bit

Yeah, so... after I opened Task Manager and clicked "End Process" for IE, and prayed for forgiveness, I was able to get back to work coming up with a better solution.

I liked the CSS file approach since it had all the entity codes in it (and I'm lazy), but I needed something more flexible so I could change the icon dynamically like in my example above. I decided to try something a little off-beat, and it worked well enough. Here is my approach:

The Solution

Since I was using the LESS file for the IE7 Glyphicons I decided to work with that. As mentioned the LESS file used a "zoom" property to execute the expression. I needed to get away from the expressions, but I wanted to use the list of entity codes in the LESS file. Using jQuery you can read CSS properties for an element so I decided to find another property to store the entity code in. There wasn't anything really obvious so I settled for the "background-image" property and modified the LESS mixin (see my image above).

A quick check with a little jQuery showed the "background-image" property gave me the entity code... the end of the URL.

Duh. It made total sense, since the background-image is a URL. But I could work with that easily. With it accessible in the script I just needed to strip away the URL parts I didn't need. The "location" object in JavaScript has everything you need to identify the URL, and putting a slash in front of the entity code in the CSS kept it relative to the root, reducing the amount of clutter I had to remove. To clean up the entity code for the Glyphicon I decided to use RegEx with a capture parenthesis along with the string I made by concatenating the "location" parts.

After that, the only thing left that had to be done was set the html of the element. The expression in the CSS was setting the "innerHTML" and that same approach was done with jQuery's "html()" call.

Well, I lied. After that there were still two things to do: execute this when the page loaded, and execute it when the icons need to change. To handle the first one, I put all that code into a function called "setIEGlyph" which accepts a jQuery object of the Glyphicon element, and I called that function in a "ready" function using a ".each" call.

Unfortunately, there is no event to bind a handler to for when a property changes on an element, so I just called "setIEGlyph" in my other functions when the class changes, passing the Glyphicon element along. There are other approaches where you could write your own override for addClass and removeClass to call a custom trigger, but I didn't need that level of complexity.

I should mention that this only fires off for IE7 or lower. I have a conditional comment for my HTML tag at the top of the document that adds the typical "ltie8" as a class on the HTML element and I just check for that class using jQuery to see if I need to call this. This is a nice approach to checking browser compatibility without a ton of extra hooks, hacks, or plug-ins.

For a more complete, though condensed, look at my code:

Voila. Problem solved and my IE7 process didn't have to be killed again.

No comments:

Post a Comment