Focal Curve

I’m a Recovering Browser Elitist

There’s been some recent discussion about the separation of style and behavior, ever since Derek Featherstone found the head of the nail and Jeremy Keith dropped the hammer. While I’m all in favor of keeping the presentation layer segregated from the behavior layer (and both kept apart from the structure/content layer), there seem to be a few points where the two should cross. Changing the style of a link when the mouse :hovers over it merely alters the presentation of said link as a visual cue of clickability. It’s essentially presentational, so I see no reason not to use CSS to achieve the effect.

The real problem, as Derek originally pointed out, is that certain popular browsers don’t support some of the CSS selectors and properties which come in so handy for these things. For example, :hovering over elements which are not links, or changing the style of a :focused form field to indicate where you’re supposed to be typing. CSS provides these selectors for our use, but IE/Win doesn’t jive with them. Since IE/Win is, let’s face it, an unignorable majority of the web-using world, they deserve the same usability enhancements enjoyed by other browsers.

So let’s take the example of :focusing form inputs. It’s nice on long forms, especially when tabbing from field to field. Not essential to the functionality of the form, but it’s a handy attention cue that all visitors should be entitled to, regardless of their browser choice. While IE/Win ignores the :focus pseudoclass, it happily supports the onfocus and onblur event handlers in JavaScript. So we can use these to emulate the same effect by switching the class of an input:

[code lang=”css”].plain { background-color: white; }
.focus { background-color: yellow; }[/code] [code lang=”php”][/code]

Eww. Ugly, redundant, unclean. We’ve separated presentation from behavior, while introducing behavior into our structure. There is clearly a better way. Time for my first foray into real DOM scripting.


The basic goal is to have an external JavaScript that will apply onfocus and onblur class switching to every input it finds in a document, thus removing all that nasty in-line script and keeping our behavior and structure layers separate. Interestingly enough, it was Mark Wubben’s tongue-in-cheek response to the debate (via Anne van Kesteren) that finally provided me with a working script to do what I had tinkered with unsuccessfully on my own. The original script applied to anchors, but it’s easy enough to tweak it for inputs. So with props to Mark, let’s dive in:

[code lang=”javascript”]window.onload = function() {[/code] This begins our function and tells the browser to execute it when the document loads (taking the place of <body onload="function();" ... and keeping behavior out of our structure).

[code lang=”javascript”]var field = document.getElementsByTagName(“input”);[/code] This scrapes our document and returns an array of all instances of the <input> tag, assigning the “field” variable to that array for future reference.

[code lang=”javascript”]for(var i = 0; i < field.length; i++) {[/code] This begins a new statement which will apply to each instance of "field" one by one, looping through the array. [code lang="javascript"]if (field[i].type == "text" || field[i].type == "password") {[/code] This narrows the scope of "field" to only those inputs which are either type=”text” or type="password", excluding other types of inputs like checkboxes and submit buttons. This takes the place of attribute selectors, which are also unsupported by IE/Win.

[code lang=”javascript”]field[i].onfocus = function() {
this.className += ” focus”;
};
field[i].onblur = function() {
this.className = this.className.replace(/\bfocus\b/, “”);
};
[/code] This assigns the class “focus” to each field when it is focused, then strips that class when focus is lost. Note the leading space in this.className += " focus";, which allows this to work with elements which already have a class assigned. Then we close our various statements and throw in a quick field = null; to prevent the script from running over and over itself and causing a potential memory leak. Nice touch, Mark.

But what about browsers that support the CSS :focus pseudoclass and attribute selectors? Let’s open with a quick condition so this script only applies to IE, and the others can use their native CSS support:
[code lang=”javascript”]if (navigator.appVersion.indexOf(“MSIE”)!=-1) {[/code]

And the finished script goes like this:
[code lang=”javascript”]window.onload = function() {
if (navigator.appVersion.indexOf(“MSIE”)!=-1) {
var field = document.getElementsByTagName(“input”);
for(var i = 0; i < field.length; i++) { if (field[i].type == "text" || field[i].type == "password") { field[i].onfocus = function() { this.className += " focus"; }; field[i].onblur = function() { this.className = this.className.replace(/\bfocus\b/, ""); }; }; }; field = null; }; }; [/code] And the accompanying CSS: [code lang="css"]input[type="text"]:focus, input[type="password"]:focus { background-color: yellow; } input.focus { background-color: yellow; }[/code]The class rule needs to be defined separately, as IE will ignore an entire multi-selector rule when it encounters one attribute selector in the mix. And there we have it. Internet Explorer users get the same experience as Gecko/KHTML/Opera users, and it's worked well in the testing I've done so far. Because the markup is left alone, it should degrade nicely for non-IE user-agents which don't support the fancy CSS selectors (or IE with JavaScript disabled), and if IE7 supports the selectors when it comes out later this year, the script will still work (some more complex filtering may be needed to serve it only to older versions of IE). The same script can be modified to apply to other elements (like <textarea>), and different events can trigger the class swapping (e.g., onmouseover and onmouseout to emulate :hover on non-links). And best of all, this script can live in its own .js file linked to when needed, while the HTML remains structural and unsoiled. Is it a hack? Of course it is. But it’s a fairly elegant one, and I can live with that.

21 Comments on 'I’m a Recovering Browser Elitist'

  1. Bill's Cat said:

    Nice job! Interesting read.

    1
  2. Craig said:

    I suppose I should go on record to state that I’m not really encouraging the use of this technique. This is simply yet-another workaround of IE’s CSS shortcomings. :focus is still the preferred (and most appropriate, IMO) method to visually indicate focus.

    2
  3. zcorpan said:

    You need to change the CSS to:

    input[type=”text”]:focus,
    input[type=”password”]:focus {
    background-color: yellow;
    }
    input.focus {
    background-color: yellow;
    }

    Otherwise IE will ignore the entire block because it doesn’t understand the attribute selector syntax.

    3
  4. Dean Edwards said:

    You should really use:

    this.className += " focus";

    Note the extra space. This allows it to work with elements that already have a class assigned.

    4
  5. Craig said:

    Dean: Good idea, I hadn’t tested with multiple classes.

    zcorpan: You’re right. With a simple pseudoclass (input:focus), IE just ignores the selectors until it finds one it understands (input.focus). Adding the attributes makes IE ignore the entire rule. I’ll update the article with these corrections.

    5
  6. Jonathan Snook said:

    My thought is, if you’ve got script that can accomplish the task in all browsers, why not simply go with a script-only implementation? It seems like you’re complicating matters by creating duplicate implementations for the same solution.

    6
  7. Craig said:

    Jonathan: In my thinking, CSS is the preferred means to this particular end. Altering the appearance of an element to indicate a state is presentational, not behavioral (focused fields don’t need to look different to function, and I’m not using the event to trigger any other actions).

    The problem is IE’s failure to support the CSS selectors required. I can use CSS alone because it’s more correct, hence neglecting 90+% of the browsing world, but that was the “elitism” Derek originally questioned. So I’m proposing this superfluous hack to cater to that browser’s flaws, but there’s no reason not to use :focus for the browsers that support it.

    My intent is to “do it the right way first, then hack it to work in IE.”

    7
  8. Jonathan Snook said:

    Specifying the appearance of an element to indicate state is presentational. But in order to achieve a state, an event (behaviour) has to occur, in this case onfocus. In other words, a user or script has performed an action to achieve a state. That state should most definitely be described using CSS. The event handling, however, should be handled by JavaScript.

    Plus, as Jeremy Keith argued and as I stated above, why would you use a solution that works in less than 50% of all active browsers over one that works in over 90% of all active browsers?

    I’d suspect that your intent is to “do it the easy way first, then hack it to work in IE.”

    8
  9. Craig said:

    And as I stated above, the CSS solution is the better one, in my opinion, and in this particular case. Clearly the matter is open to some debate.

    So yes, if I were to implement the scripted method, the CSS-only method could be done away with. You’re absolutely correct.

    And if the crux of the debate is that event-related pseudoclass selectors should not be part of the CSS specification at all, well… I’ll stay out of that. My real goal here was delivering an equivalent experience to human visitors, regardless of their browser choice.

    9
  10. paul haine said:

    Instead of using ‘if (navigator.appVersion.indexOf(“MSIE”)!=-1) {‘, why not just use some conditional comments to feed the script to Internet Explorer only? That way modern browsers don’t even have to download it at all.

    10
  11. The Wolf said:

    Nice work! I’m glad we now have a solution for the :target and :focus pseudo-classes.

    Pual Haine, browsers still have to download them :)

    The best way of giving IE specific content is to use a server side language like PHP to check to see if the browser can accept xhtml, if it says yes you know its not IE.

    11
  12. Tino Zijdel said:

    “This begins our function and tells the browser to execute it when the document loads”

    No, this will execute after the document (including all images etcetera) has loaded; when you have many images in your document and/or they are on a slow host there will be a significant delay between the initial rendering and the availability of the behavior. You should keep that in mind.

    “for(var i = 0; i

    12
  13. Tino Zijdel said:

    continued, special HTML characters seem to break the post :/

    < field.length; i++) {”

    Comparing a property every iteration is bad for performance. Also since order doesn’t matter I would suggest to rewrite this to:

    var i = field.length;
    while (i–) { }

    Furthermore I agree with Paul Haine; browsersniffing by means of comparing the useragent string is bad practice and not fail-safe. Conditional comments should be used instead.

    13
  14. paul haine said:

    @The Wolf: if the script is being called from within a conditional comment then only IE download the script; all other browsers will simply ignore it.

    14
  15. Ross Shannon said:

    Jonathan, going with a script-only implementation means the effect will fail in browsers that have JavaScript disabled, which would be unfortunate if they were quite capable of doing it the CSS route before any hacks were added. If you add the effect with scripting, you should also keep the CSS pseudo-classes.

    15
  16. Craig said:

    Wow, the elves have been busy while I slept…

    Paul Haine and The Wolf: I considered some means of hiding the script from non-IE entirely, but the first version I wrote of it was meant to go into a general .js file along with various other functions as well. So this would be served to non-IE browsers, they just wouldn’t execute this particular function. Or rather, they would execute it and then stop as soon as they didn’t meet the first “if”. I’m sure there are better ways.

    Tino Zijdel: Yep, that’s what I said, execute when the document loads, in other words when it finishes loading. On a heavy page or a slow connection this isn’t ideal, so I’m sure something other than an onload trigger may be preferable.

    All of these issues (as well as my own laziness) are excellent reasons why the CSS-only method is still my prefered poison.

    16
  17. Jonathan Snook said:

    I suspect the number of people who have javascript turned off is a small minority.

    In any case, I’m just arguing the semantics of the issue. I’d have done the same as you. In fact, the input’s on my page use :focus and I didn’t even include an IE fallback. :)

    17
  18. Craig said:

    Semantically, I’m totally on the boat. I probably wouldn’t use :hover or :focus to display a drop-down, for example. Events affecting the structure of a page should be handled by script, not CSS. The focused field gimmick seems presentational enough that I still believe :focus is kosher.

    18
  19. Tino Zijdel said:

    There should actually be something like an ‘onrenderend’ event. The only other suitable option to append behavior to elements without the delay waiting for onload to occur is to polute your markup with inline scripts or use htc’s (which in this case wouldn’t be all that bad since it is for IE only).

    Still I strongly advice against the sort of browser sniffing displayed here. Even Opera has the ‘MSIE’ bit by default in it’s useragent string nowadays to spoof browserdetections. UA-strings cannot be trusted. I also know of some 3rd party software that actually sents an empty or bogus string, even when using IE (I believe it’s Norton Firewall – not very uncommon)…

    19
  20. Joël kuiper said:

    I actually think that replacing
    this.className = this.className.replace(/\b focus\b/, "");
    with
    this.className = this.setAttribute("class","");
    is a better idea

    20
  21. Joël kuiper said:

    ignore that please…it will remove combined classes…

    21