Blog


Keypress in Safari 3.1

There was an interesting post the other day which criticized Safari's choice to completely overhaul their event system and, in the process, seemingly cripple the keypress event for non-character keys.

At first glance to me, and to many others, it appeared as if a dramatic flaw had been introduced into Safari's event system - completely castrating its practical use. It was at this point that I contacted Yehuda Katz (Merb and jQuery contributor) - he knows more about the inanity of cross-browser keyboard events than anyone I know. Here's his responses to a few questions that I bounced off him:

John: At first glance through WebKit's changes it appears as if they've significantly crippled the keypress event, is this the case?

Yehuda: People should not have been using the keypress event to get the character that was pressed. That's because the keydown event only provides information about the actual key that was pressed (the A key, the right arrow, etc.), but does not tell the user what character will get added to (for instance) an input box.

Since arrow keys do not get added as text, keydown provides all the information that is needed. There were a couple of quirks with using keydown in certain cases previously which are resolved by their changes that prevent keypress from getting called if keydown's default handling is blocked.

What this means is that if people were using keypress before (and relying on Safari's native results for arrow keys, such as 63232), their code will break. However, this was a bad idea all along; people should use keydown to detect and block non-character keys. My mantra has always been: keydown/keyup if you want to know the key that was pressed; keypress if you want to know what text was detected.

John: Being able to access non-character keys must be useful - granted Safari gave strange results previously - but at least some value is better than none, right?

Yehuda: The keypress event does not provide any additional benefits over keydown for these non-character keys. Keep in mind that IE does not fire the keypress event for some non-character keys (e.g. delete, end, enter, escape, fkeys, etc.).

Because Safari appears to have fully resolved issues with suppressing default events from keydown, there should be no problem with using keydown for non-character keys.

John: So we have charCode, keyIdentifier, and keyCode along with keyup, keydown, and keypress - is there a solution that we can safely use in a cross-browser manner?

Yehuda: There's a reasonably good writeup at: http://unixpapa.com/js/key.html.
Effectively, you can use e.keyCode for all modern browsers in keydown/keyup (although you'll need to consult the chart at the bottom of that page to determine whether the keyCodes returned by the browsers are consistent).

For keypress, e.charCode || e.keyCode should work (Mozilla and Safari define e.charCode, while IE defines e.keyCode which returns the ASCII value). What that means is that you have a close-to-cross-browser keyCode for keyup/keydown and a pretty reasonable charCode for keypress.

As a result, keypress can be used for key events that produce text for which you need to know its ASCII value (A vs. a). keyup/keydown can be used for events for which you care about the physical key that was pressed. Again, there are some quirks, which can be summed up in the tables at http://unixpapa.com/js/key.html.


I'd like to thank Yehuda for taking the time to clear these points up. Effectively if you were doing key-related development, previously, in a non-cross-browser manner then you hit a wall with this recent change. If nothing else this should encourage you to keep a good separation, as Yehuda notes: "keydown/keyup if you want to know the key that was pressed; keypress if you want to know what text was detected."

Tags: safari, javascript, dom, events, webkit

getBoundingClientRect is Awesome

getBoundingClientRect is a method, first introduced by Internet Explorer 5 (and now included in Firefox 3), which provides the top, right, bottom, and left offset coordinates of an element. After PPK mentioned this method in a recent post of his, poo-poo-ing its applicability, I thought I'd take some time to show how useful it can be. What makes this method so especially handy is that the alternative means of computing an element's offset position is so horribly slow and buggy that it borders on the comical.

The general purpose of this method, or of similar implementations, is to allow the user to get the visual top/left position of an element (such that if they absolutely position an element from the document it would be on top of the desired element - making things like tooltips possible, to name a single application). This means that you have to take into account a huge number of quirks. In no particular order here are some choice bugs which make this very difficult:

  • Handling table border offsets.
  • Fixed positioned elements.
  • Scroll offsets within another element.
  • Borders of overflowed parent elements.
  • Miscalculation of absolutely positioned elements.

For example, looking at jQuery's implementation of .offset() (which provides the top/left offset of an element) we can see a number of these quirks worked around. A phenomenal amount of work has gone into the method, by the excellent Brandon Aaron, in order to "get it right" in a cross-browser manner. Check out the extensive suite of tests that are backing it up, as well.

This is where getBoundingClientRect comes into play. For a visual comparison look at the green boxes (representing the getBoundClientRect-based implementation) versus the red boxes (representing the normal cross-browser implementation).

The reason why the getBoundingClientRect implementation is more than 1 line is due to two facts:

  1. It doesn't include the document scroll offset (which is a simple addition).
  2. Internet Explorer's handling of <html/> element border widths is completely whack.

In comparison to the default technique, however, this is a veritable walk-in-the-park.

Now, not only, is this method simpler to use it's also insanely faster. Stupifyingly so. Considering that you no longer have to perform all sorts of hacks, or walk the tree to rectify miscalculations, the result is quite impressive.

View the following jQuery UI drag-and-drop sorting demo page in Firefox 2 (drag the items in the grid, at the bottom) and then view it in Firefox 3, doing this same:

http://dev.jquery.com/view/trunk/ui/tests/sortable.html

Night and day. I've also uploaded a simple movie demonstrating the difference.

I absolutely encourage everyone to check this method out. It can serve as a near-drop-in replacement for your offset needs - and will gracefully work in new browsers that support the method.

Tags: dom, javascript, browsers

JavaScript in Internet Explorer 8

Our day has finally come. CSS coders got some love with Internet Explorer 7 - us JavaScript folk got absolutely nothing. In fact, at last count, all we got were a couple new bugs to deal with (file:// requests not working via XMLHttpRequest and <object>.getElementsByTagName("*") always returning no elements).

Internet Explorer 8 is our release.

The first beta was pushed out today and it shows huge promise. It's already achieved way more than I would've expected and it's made me hungry for more. If you had bet me that Microsoft would be mentioning the phrases "ARIA", "SVG", and "MathML" in their release notes I would've lost.

Here are my thoughts on a bunch of the browser features:

W3C: querySelector

A super-fast way of finding elements based upon a CSS selector. The second browser to implement the selectors API has hit the market (behind a WebKit nightly release).

It's important to realize that any selectors here are completely reliant upon the browser's native selector implementation. IE8 is only shooting for CSS 2.1 support - which means that you really shouldn't be holding your breath for CSS 3 selectors. As I mentioned previously these blackbox APIs are going to be a source of never-ending hair-pulling and struggle going into the future.

HTML 5 Party!

I was hoping for some HTML 5-compliant features to land - well, hoping isn't the right term, perhaps 'feverishly expecting the worst' is more correct. However we can see that we've been graced with 4 full feature implementations from the spec - fantastic!

HTML 5: window.location.hash

Already supported fairly well by most browsers. Modifying window.location.hash changes the page URL and adds the page to the history (allowing for back-button simulation in Ajax applications). IE went a step further and broadcasts the hashchanged event (the first browser to do so, as far as I know).

HTML 5: DOM Storage

A feature that I discussed previously in which data can be persisted in a way that supersedes the use of primitive cookies storage. This has been in Firefox since version 2 but is absent from Opera and Safari.

HTML 5: postMessage

As I discussed previously postMessage serves as a way of communication across frames with simple text strings. IE 8, Opera 9, Firefox 3, and WebKit nightlies all support this feature - making it the only one with complete browser support.

HTML 5: Offline Events

As I've written about previously this is an easy way of detecting connectivity within the confines of JavaScript. With it we can write graceful offline Ajax applications. Firefox 3 and IE 8 appear to be the only browsers to support this feature.

XDomainRequest

This is an interesting feature for doing cross-domain requests but doesn't appear to conform to the existing Cross-site XMLHttpRequest work which is in Firefox 3. Even at that it appears to be quite crude - forfeiting any form of filtering or security controls for a simple boolean "yes/no" header.

Seeing this new API concerns me. The Cross-site XMLHttpRequest work may be in some flux, as some API points are still being hammered down, but it's doubtful that the resolution will end with something identical to what's being described by Microsoft.

DOM bug fix love

getAttribute/setAttribute have seen a major overhaul - in short: They now behave like they should and as they do in every other browser. The notorious issue of accessing relative/absolute href/src attributes is also resolved - which is just great. They also saw fit to add hasAttribute.

Also newly fixed/added:

  • .ownerElement and .ownerDocument - Finally we have a unified way of dealing with iFrames.
  • getElementById returns elements by id - ONLY. Been such a long time coming, thank goodness.
  • Doing getAttribute("checked") now returns "checked" instead of true.
  • <button/> values are actually submitted now, instead of their inner text values.
  • Dynamically created (or modified) radio buttons are now checkable.

I simultaneously feel both joy and anger in seeing these fixed. Happy that they're being released and anger for the fact that I had to struggle through them and that they now consume some portion of my brain.

W3C: Events

This is one area that is completely absent from this beta. We are still stuck with IE's attachEvent system - no addEventListener in sight. I don't know how serious they are about supporting Acid3 but it includes a test for addEventListener so they may want to consider it extra strongly.

I can partially understand their desire to keep their existing API but I don't understand why they have no interest in, also, adding addEventListener, etc. to complement what's there. I assume it's because they would now have to support concepts like event capturing.

I'm really disappointed with this. This should've been a top priority fix before implementing new psuedo-XHR systems like XDomainRequest.

JavaScript Language

The IE team has made some big improvements in improving garbage collection issues, memory management, and performance - all of which will be greatly appreciated in everyday applications.

Although, I mis-spoke slightly earlier: While IE8 is been great for JavaScript developers I was implying "JavaScript + DOM" developers. For pure JavaScript it's completely frustrating: There doesn't appear to be any new pure-JavaScript features in this release. I can only hope that we'll see more in a future beta release.

ARIA Support

This one completely blew me away. ARIA is a fantastic specification for empowering web applications with the ability to clearly communicate their intentions to a screen reader. The biggest hurdle with the effort, up until this point, has been the lack of IE support - obviously this will no longer be the case. Firefox, IE, and Opera all support ARIA now. The WebKit team doesn't appear to have interest in implementing this feature, which is a real shame.

Embedded SVG

Support for embedding namespaced elements within XHTML is now possible. This means that you can now do inline markup for SVG and MathML, amongst others, and have it "just work" (as long as you have their associated plugins installed). It's probably a bit much hoping for native SVG support, but I'll take what I can get.

Firebug for Internet Explorer

We finally have a heavily-Firebug-inspired tool inside Internet Explorer. To quote Joe Hewitt (creator of Firebug): "I couldn't be happier that Microsoft completely copied Firebug for IE8." I have to agree - a tool like this has been a long time coming and it's greatly appreciated. Only the Internet Explorer team would've ever been the ones to build this tool - there's simply too much information here that's unavailable to typical IE extensions.

Browser mode toggling

At first glance this feature makes the most sense for seeing if your IE 7 page will work ok in IE 8. In actuality, however, this will end up being very useful for developing a standards-compliant page (in IE 8, FF, Safari, Opera) and then toggling to see what the result is like in IE 7. This is so much better than the IE 6 to IE 7 jump where you have to keep your browser in a virtual machine in order for it to run side-by-side (according to Microsoft, at least - even though there were standalone solutions).

Bug Feedback

The Internet Explorer team is collecting feedback from select beta testers and publishing the bugs on a publicly-accessible site. This a huge step in the right direction (going from no communication to some is great). "Common" users are forced to vote for their "favorite" bugs to try and catch the eye of an IE developer. It's a strange situation but one that shows progress, at least.

Conclusion

In all I'm positive about this release, even with all the ups-and-downs. Seeing features like querySelector, ARIA, and postMessage helps to warm my frosty, bitter, heart. While there's still some major faux-paus in this release (no new JS langauge features?, no W3C events?, no CSS3 selectors?) I think we can, at least, be excited to see what happens in the next beta.

If nothing else the Internet Explorer team has done a great job of tackling many of the expectations of the development community - if they continue this trend I don't think anyone will be left disappointed.

Tags: ie, browsers, html5, dom, javascript

Comparing Document Position

A great blog post, for me, was one written by PPK back about two years about in which he explained how the contains() and compareDocumentPosition() methods work in their respective browsers. I've, since, done a lot of research into these methods and have used them on a number of occasions. As it turns out they're incredibly useful for a number of tasks (especially relating to the construction of pure-DOM selector engines).

DOMElement.contains(DOMNode)

Originally introduced by Internet Explorer this method determines if one DOM Node is contained within another DOM Element. This method can be especially useful when attempting to optimize CSS Selector traversals that look like "#id1 #id2". With this method you could getElementById both elements then use .contains() to determine that #id1 does, in fact, contain #id2.

There's one gotchya: .contains() will return true if the DOM Node and DOM Element are identical (even though, technically, an element cannot contain itself).

Here's a simple implementation wrapper that works in Internet Explorer, Firefox, Opera, and Safari.

function contains(a, b){
  return a.contains ?
    a != b && a.contains(b) :
    !!(a.compareDocumentPosition(arg) & 16);
}

Note that we use compareDocumentPosition, which we'll be discussing next.

DOMNode.compareDocumentPosition(DOMNode)

This method is part of the DOM Level 3 specification and allows you determine where two DOM Nodes are, in relation to each other. This method is much more powerful to .contains(). One possible use of this method is to re-order DOM nodes to be in a specific order (as was also done by PPK).

With this method you can determine a whole slew of information pertaining to the position of an element. All of this information is returned using a bitmask.

For those who aren't familiar with it, a bitmask is a way of storing multiple points of data within a single number. You end up turning on/off the individual bits of the number, giving you a final result.

Here are the results from NodeA.compareDocumentPosition(NodeB) along with all the information that you can access:

Bits Number Meaning
000000 0 Elements are identical.
000001 1 The nodes are in different documents (or one is outside of a document).
000010 2 Node B precedes Node A.
000100 4 Node A precedes Node B.
001000 8 Node B contains Node A.
010000 16 Node A contains Node B.
100000 32 For private use by the browser.

Now, this means that a possible result could be something like:

<div id="a"><div id="b"></div></div>
<script>
alert( document.getElementById("a")
  .compareDocumentPosition(document.getElementById("b")) == 20);
</script>

Since a node that contains another both "contains" it (+16) and precedes it (+4) the final result is the number 20. It might make more sense if you look at what's happening to the bits:

000100 (4) + 010000 (16) = 010100 (20)

This, undoubtedly, makes for the single most confusing method of the DOM API - however it's one whose worth will be well deserved.

Right now DOMNode.compareDocumentPosition is available in Firefox and Opera. However, there are some tricks that we can use to implement it completely in Internet Explorer, observe:

// Compare Position - MIT Licensed, John Resig
function comparePosition(a, b){
  return a.compareDocumentPosition ?
    a.compareDocumentPosition(b) :
    a.contains ?
      (a != b && a.contains(b) && 16) +
        (a != b && b.contains(a) && 8) +
        (a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
          (a.sourceIndex < b.sourceIndex && 4) +
            (a.sourceIndex > b.sourceIndex && 2) :
          1) +
      0 :
      0;
}

Internet Explorer provides us with a couple methods and properties that we can use. To start, with the .contains() method (as we discussed before) so that gives us contains (+16) and 'is contained by' (+8). Internet Explorer also has a .sourceIndex property on all DOM Elements corresponding to the position of the element absolutely within the document. For example, document.documentElement.sourceIndex == 0. Because we have this information we can complete two more pieces of the compareDocumentPosition puzzle: preceded by (+2) and followed by (+4). Additionally, if an element isn't currently located within a document it's .sourceIndex will equal -1, which gives us an answer of 1. Finally, through process of deduction, we can determine if an element is equal to itself, returning an empty bitmask of 0.

This function will work in Internet Explorer, Firefox, and Opera. We'll only have crippled functionality in Safari (since it only has .contains(), and no .sourceIndex, we'll only get 'contains' +8 and 'is contained by' +16 - all other results will return '1' representing a disconnect).

PPK provides a great example of how this new functionality can be used by creating a getElementsByTagNames method. Let's adapt it to work with our new method:

// Original by PPK quirksmode.org
function getElementsByTagNames(list, elem) {
        elem = elem || document;
       
        var tagNames = list.split(','), results = [];
       
        for ( var i = 0; i < tagNames.length; i++ ) {
                var tags = elem.getElementsByTagName( tagNames[i] );
                for ( var j = 0; j < tags.length; j++ )
                        results.push( tags[j] );
        }
       
        return results.sort(function(a, b){
                return 3 - (comparePosition(a, b) & 6);
        });
}

We could now use this to construct an, in order, table of contents for a site:

getElementsByTagNames("h1, h2, h3");

While both Firefox and Opera have taken some initiative to implement this method, I'm looking forward to seeing more browser get on board to help push this forward.



Note: In jQuery you can do $(":header") to select all header elements in order.

Tags: javascript, dom

XPath Overnight

A fascinating thing has happened in the world of JavaScript DOM traversal: Over the course of a couple months in 2007 three of the major JavaScript libraries (Prototype, Dojo, and Mootools) all switched their CSS selector engines to using the browser's native XPath functionality, rather than doing traditional DOM traversal. What's interesting about this is that the burden of functionality and performance has, literally, flipped overnight on to the browser's XPath engine, from its pure DOM implementation.

There's some really interesting things about this switch:

  • Native XPath is blazing fast. For a majority of CSS selectors it completely trumps using native DOM methods (like getElementsByTagName, for example). Sometimes it pays to special-case your code for selectors like #id, but overwhelmingly XPath is the direction in which JavaScript libraries are heading.
  • Since a large percentage of JavaScript users use JavaScript libraries (and, thus, use the behind-the-scenes XPath, as well) this means that browsers are now spending significantly more time processing XPath queries than they ever were before. This means that the performance field is now, effectively, split between two areas: Traditional DOM querying and XPath.
  • No one is analyzing the performance of browser XPath queries. Or, if they are, it's certainly not public. I'm working on some new XPath performance tests, in order to bring them some more visibility, and hope to have them released this week.
  • XPath, while incredibly useful, is a black box. The developer has no control over how fast the results come back - or if they are even correct. Contrast this with traditional DOM scripting (where you can fine-tune your queries to perfection). Browsers will always be bound to have some bugs in their implementations. For example, Safari 3 isn't capable of doing "-of-type" or ":empty" style CSS selectors, nor is any browser able to access the 'checked' property, or namespaced attributes (all noted in Prototype's implementation) which means that they have to fall back to a traditional DOM scripting model.
  • Internet Explorer is a dead-end. Since most users want a CSS selector implementation that will work against HTML documents - and IE is unable to provide one - all CSS Selector implementations must provide two (2) side-by-side selector engines in order to handle these cases (not to mention the aforementioned cases where browsers provide unexpected behavior).

A couple things to take away from all of this:

  1. XPath (and new methods like querySelector) are the way of the future for a lot of JavaScript libraries - and the next frontier for browser optimization.
  2. These implementations are black boxes that are unable to be modified by the developer (leaving them vulnerable to browser bugs).
  3. A dual DOM-only CSS selector engine must be provided well into the foreseeable future, by libraries, in order to account for browser mis-implementations.

I should, also, probably answer the inevitable question: "Why doesn't jQuery have an XPath CSS Selector implementation?" For now, my answer is: I don't want two selector implementations - it makes the code base significantly harder to maintain, increases the number of possible cross-browser bugs, and drastically increases the filesize of the resulting download. That being said, I'm strongly evaluating XPath for some troublesome selectors that could, potentially, provide some big performance wins to the end user. In the meantime, we've focused on optimizing the actual selectors that most people use (which are poorly represented in speed tests like SlickSpeed) but we hope to rectify in the future.

Tags: performance, libraries, javascript, browsers, dom

getElementsByClassName Speed Comparison

Mark Finkle suggested that I do some speed testing, now that a native implementation of getElementsByClassName has landed in the Mozilla trunk (destined for Firefox 3).

So I went around and dug up all of the different, existing, implementations that I could find. Currently, implementations fall into one of three categories (with some straddling more than one):

  • Pure DOM
    This usually involves a calls to .getElementsByClassName("*") and traversing through all matched elements, analyzing each element's className attribute along the way. Generally, the fastest method is to use a pre-compiled RegExp to test the value of the className attribute.
  • DOM Tree Walker
    Is a less-popular means of traversing DOM documents by setting some simple parameters, as specified by the DOM Level 2 Spec. For example, you could traverse through all text nodes in a document (something that you can't easily do in any other way).
  • XPath
    The most recent technique, to be popularized, was the use of XPath to find elements by classname. The implementation is generally simple: Building a single expressions and letting the XPath engine traverse through the document, finding all the relevant elements.

I've chosen some implementations that were representative of each of these techniques.

Tree Walker

An implementation using the DOM Level 2 Tree Walker methods. Builds a generic filter function and traverses through all elements.

document.getElementsByClass = function(needle) {
  function acceptNode(node) {
    if (node.hasAttribute("class")) {
      var c = " " + node.className + " ";
       if (c.indexOf(" " + needle + " ") != -1)
         return NodeFilter.FILTER_ACCEPT;
    }
    return NodeFilter.FILTER_SKIP;
  }
  var treeWalker = document.createTreeWalker(document.documentElement,
      NodeFilter.SHOW_ELEMENT, acceptNode, true);
  var outArray = new Array();
  if (treeWalker) {
    var node = treeWalker.nextNode();
    while (node) {
      outArray.push(node);
      node = treeWalker.nextNode();
    }
  }
  return outArray;
}

The Ultimate getElementsByClassName

Uses a pure DOM implementation, tries to make some optimizations for Internet Explorer.

function getElementsByClassName(oElm, strTagName, strClassName){
    var arrElements = (strTagName == "*" && oElm.all)? oElm.all :
        oElm.getElementsByTagName(strTagName);
    var arrReturnElements = new Array();
    strClassName = strClassName.replace(/\-/g, "\\-");
    var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
    var oElement;
    for(var i=0; i<arrElements.length; i++){
        oElement = arrElements[i];     
        if(oRegExp.test(oElement.className)){
            arrReturnElements.push(oElement);
        }   
    }
    return (arrReturnElements)
}

Dustin Diaz's getElementsByClass

A pure DOM implementation, caches the regexp, and is generally quite simple and easy to use.

function getElementsByClass(searchClass,node,tag) {
        var classElements = new Array();
        if ( node == null )
                node = document;
        if ( tag == null )
                tag = '*';
        var els = node.getElementsByTagName(tag);
        var elsLen = els.length;
        var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
                if ( pattern.test(els[i].className) ) {
                        classElements[j] = els[i];
                        j++;
                }
        }
        return classElements;
}

Prototype 1.5.0 (XPath)

Mixes an XPath and DOM implementation; using XPath wherever possible.

document.getElementsByClassName = function(className, parentElement) {
  if (Prototype.BrowserFeatures.XPath) {
    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
    return document._getElementsByXPath(q, parentElement);
  } else {
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    var elements = [], child;
    for (var i = 0, length = children.length; i < length; i++) {
      child = children[i];
      if (Element.hasClassName(child, className))
        elements.push(Element.extend(child));
    }
    return elements;
  }
};

Native, Firefox 3

A native implementation, written in C++; is a part of the current CVS version of Firefox, will be included in Firefox 3.

document.getElementsByClassName

The Speed Results

For the speed tests I copied the Yahoo homepage into a single HTML file and used that as the test bed. They make good use of class names (both single and multiple) and is a considerably large file with lots of elements to consider.

You can find the test files, for each of the implementations, here:
http://ejohn.org/apps/classname/

Note: "XPath" is just Prototype's implementation.

From these figures we can see that the native implementation of getElementsByClassName, in Firefox 3, is a full 8x faster than the XPath implementation. Additionally, it's a stunning 77x faster than the fastest DOM implementation.

Note: These numbers have been revised from what was originally posted as the lazy-loading nature of document.getElementsByClassName wasn't taken into account. The resulting arrays are completely looped-through now, making sure that all elements are accounted for.

Currently, Prototype has the best general-use implementation: Use XPath selectors wherever possible, fall back to fast DOM parsing.

Interestingly, only Prototype actually tries to implement the document.getElementsByClassName interface (all others do one-off names). However, Prototype doesn't check to see if the document.getElementsByClassName property already exists, and completely overwrites the, incredibly fast, native implementation that Firefox 3 provides (oops!).

In all, the results are quite astounding. The native implementation is absolutely much faster than anything I could've imagined. It completely decimates all the other pieces of code. I can't wait until this hits the general public - users will, absolutely, feel a significant increase in speed.

Tags: javascript, speed, firefox3, firefox, whatwg, xpath, dom

Offline Events

Another feature that's coming along, into Firefox 3, from the WHATWG Web Applications 1.0 spec is that of Online/Offline events. This feature serves as the second piece to the "Offline Web Application" puzzle (The first being DOM Storage), that's really coming into place in Firefox 3.

What is the purpose of offline events?

Effectively, in order to build a good offline application, you need to know when you're actually offline. Incidentally, you also need to know when your application has returned to an 'online' status again.

Generally speaking, the flow for an offline-capable web application would look something like this, written completely in pseudo-code:

function saveData( item ){
    if ( navigator.onLine ) {
        saveToServer( item );
    } else {
        toSave.push( item );
    }
}

function loadData( item ){
    if ( navigator.onLine ) {
        return loadFromServer( item );
    } else {
        var result = loadFromQueue( item );
        if ( !result ) {
            displayError();
            toLoad.push( item );
        }
        return result;
    }
}

setInterval(function(){
    if ( navigator.onLine ) {
        var item = predictNextItemToBeLoaded();
        loadData( item );
    }
}, 5000);

window.ononline = function(){
    toSave.forEach( saveData );
    toLoad.forEach( loadData );
};

window.onload = function(){
    document.getElementById("myform").onsubmit = function(){
        saveData( this );
        return false;
    };
};

As you can see from this code, there are two very important aspects at play:

  1. You need to know when the service comes back online, so that you can re-synchronize with the server.
  2. You need to know when your service is offline, so that you can be sure to queue your requests for a later time.

It is this process that offline events helps to trivialize. So let's take at the API that we're given by the specification:

navigator.onLine

This is a single property that maintains a true/false value (true for online, false for offline). Currently, this property is updated (in Firefox) whenever you switch into "Offline Mode" (more details at the end of this post).

Additionally, this property should update whenever a browser is no longer capable of connecting to the network. According to the specification:

The navigator.onLine attribute must return false if the user agent will not contact the network when the user follows links or when a script requests a remote page (or knows that such an attempt would fail)...

As of this time, Firefox only appears to update this property when switching to/from the browser's Offline mode.

One point that I found to be curious, about this property, was it's naming. It appears as if someone originally felt that "online" was spelled "on-line" (and since you can't have dashes in JavaScript property names, converted it to its camelcase equivalent, "onLine"). Unsurprisingly, this naming convention is a bit of legacy.

Interestingly, this property already exists in Firefox and Internet Explorer (the specification based itself off of these prior implementations), so you can begin using them today.

"online" and "offline" events

These two events are the truly-new aspects of offline events. They are fired against the document body of any page that switches inbetween online and offline mode. Additionally, the event bubbles up from document.body, to document, ending at window. Both of the events are non-cancellable (you can't prevent the user from coming online, or going offline).

As a side note, Mozilla has had these built in to its XPCOM layer for some time now, specifically the network:offline-status-changed event.

A Simple Test

In the ticket that describes the implementation of these events in Firefox, there's a simple test case that you can run to verify that the events are working.

Considering that you probably don't want to go through the trouble of building your own copy of "Minefield", let's take a quick look at what happens in this test's code.

<!doctype html>
<html>
<head>
  <script>
    function updateOnlineStatus(msg) {
      var status = document.getElementById("status");
      var condition = navigator.onLine ? "ONLINE" : "OFFLINE";
      status.setAttribute("class", condition);
      var state = document.getElementById("state");
      state.innerHTML = condition;
      var log = document.getElementById("log");
      log.appendChild(document.createTextNode("Event: " + msg + "; status=" + condition + "\n"));
    }
    function loaded() {
      updateOnlineStatus("load");
      document.body.addEventListener("offline", function () {
        updateOnlineStatus("offline")
      }, false);
      document.body.addEventListener("online", function () {
        updateOnlineStatus("online")
      }, false);
    }
  </script>
  <style>...</style>
</head>
<body onload="loaded()">
  <div id="status"><p id="state"></p></div>
  <div id="log"></div>
</body>
</html>

There are four important lines to take note of: 7, 16, 19, and 26. We'll be looking at them in depth here.

You'll notice, that if you tried to run the test in your browser (even if you didn't have the bleeding-edge build), that it worked! You probably saw a result just like this:

Why is this the case? As I mentioned before, Firefox already has support for the navigator.onLine event, the only thing that's been added was the addition of events to notify you when the property changes. Incidentally, you can still bind an onload="" event and check for a browser's online/offline status there.

Now, you might be asking yourself (as I did), when can a page load and NOT have a status of "online"? Upon reading through some of the information available, it appears as if this can occur when a page is loaded directly from the browser's cache, while you're in an offline mode. These are the steps that Mark Finkle took to reproduce this:

  • Load a web page from a bookmark.
  • Put the browser into offline mode.
  • Close the browser.
  • Open the browser. (Incidentally, you're back in online mode.)
  • Re-enable offline mode.
  • Click the bookmark again, and you'll be visiting the "offline" copy of that page.

Let's take a look, now, at the browser's "Work offline" mode, and what that offers to us web application developers.

Enable Offline Mode

In a nutshell: Offline mode is a means, through which, a user can tell the browser to be much greedier, and possibly more proactive, about the caching that it performs. Effectively, a browser will try to use the browser's cache as much as possible.

In order to move into offline mode, you can toggle the menu option like so:

This triggers the "offline" event - giving your web application the ability to react, and behave, accordingly.

Disable Offline Mode

Additionally, when it comes time for the user to move back into "online" mode, another event is fired (which can be handled and interpreted).

Summary and Comments

This is a really handy feature, and certainly a great tool for making offline web application simpler. I'm looking forward to its widespread adoption.

My biggest gripe with the feature, right now, is more in its implementation (rather than its concept). There's a huge amount of ambiguity concerning what "Working Offline" means. It's not really clear what is supposed to happen when that menu option is triggered - nor, what exactly, is supposed to trigger the online/offline events.

I see a huge amount of potential in Firefox 3's offline capabilities, however they really need to be clarified - and made useful to the user. Hooking "offline" mode purely to network connectivity would be a great start. (Forcing the user to toggle some sort of offline mode is cumbersome - especially considering that most of the time that I go offline, it's not of my own free will.)

In short: I think Firefox 3 is off to a great start, in supporting offline web applications, but we still have a ways to go in order to make the whole process (both for users and for developers) as smooth as possible.

Tags: browsers, javascript, firefox, mozilla, dom, offline, html5

getElementsByClassName in Firefox 3

I've been waiting for this one for a while - and it was just committed to the Mozilla trunk yesterday:
Firefox 3 is going to have support for getElementsByClassName.

Robert Sayre just merged in his changes yesterday, taking this feature live.

If you're curious as to why this feature is being included (or where the reasoning for it originated from) - it's because it's part of the Web Applications 1.0 (HTML 5) specification. The implementation that's in Firefox is slightly different from what's presented in the specification; however, you can expect that the specification will probably be updated to reflect that changes that've been made.

getElementsByClassName has long been a mainstay of web developers everywhere - and by making it official (both in specification and in implementation), web applications are going to see a huge jump in speed.

I've pulled together some simple examples of what you can do with this new element selector.

Get all elements that have a class of 'test'

document.getElementsByClassName('test')

Get all elements that have a class of 'red' and 'test'

document.getElementsByClassName('red test')

Get all elements that have a class of 'test', inside of an element that has the ID of 'main'

document.getElementById('main').getElementsByClassName('test')

And if we go ahead and add in JavaScript 1.6's Array extras, we can do some really-cool matches.

Find all div elements that have a class of 'test'

Array.filter( document.getElementsByClassName('test'), function(elem){
    return elem.nodeName == 'DIV';
});

Find all elements that have a class of 'test' (as do their parent element)

var test = document.getElementsByClassName('test');
Array.filter( test, function(elem){
    return Array.indexOf( test, elem.parentNode ) > -1;
});

Some basic code can be found in the test cases for this feature. You'll need to have a nightly version of Firefox in order to run the code contained within it. I really can't wait until this is live.

Update: This post has been submitted to Digg.

Update: This function is now documented on the MDC Wiki.

Tags: javascript, traversing, whatwg, dom, getelementsbyclassname, traversal, html5

· « Previous entries

Current Projects

jQuery JavaScript Library

jQuery

Comprehensive DOM, Event, Animation, and Ajax JavaScript Library.

Recent Projects

Pro JavaScript Techniques

JavaScript Book

The best techniques for professional JavaScript. Published by Apress.