Archive for January, 2006

Practical Applications of Browser DOM Hacking

I’ve been talking about hacking the browser’s DOM lately. Now, here are some situations where this might be useful — but I’ll only lightly cover them, and leave further exploration up to you. [Not because I'm trying to be clever and educational, but because Blogger strips out bunches of my HTML snips, even if I use escape chars. If you have any suggestions, I'm all ears.] Ok, practical applications of new knowledge, here we go!

Generate a Table of Contents

Suppose we have some ordinary HTML: some h1s, h2s, h3s, ps, and imgs. Maybe we’ve thrown in some handy CSS, so it looks nice. Now we want to generate a table of contents at the top of the page, so changes to the contents are automatically reflected above. Let’s say headers make up the links in the table of contents.

Using the getElements() function from last post, we can get an Array of all the header nodes:

var headers = getElements(
    function(node) {
        return node.nodeName.length == 2 &&
        node.nodeName.charAt(0) == "H";
    });

Now, we can create a TOC that lists each header. At the top of the page, create and insert a div to hold the table of contents — let’s call it tocDiv. Then, for each header node, do two things:

  1. Insert an anchor into tocDiv. Set its href to '#' + header.innerText. Add to it a text node (using document.createTextNode()) containing the header’s innerText.
  2. Insert a named anchor right before the header, using header.parentNode.insertBefore(). Set its name to header.innerText, so the TOC link points to it.

Now, at the top of the page, you have a list of links…each one linking to a header tag below, with the same text as the header it links to. You can do some neat formatting with CSS, too…for the tocDiv links, set their CSS class attribute to something like 'toc_' + header.nodeName. Then add CSS class definitions for toc_h1, toc_h2, etc, to make it look more like an outline (you know, make the h1’s big and bold, the h2’s smaller, that kind of thing). If you want to get really fancy, you can wrap each header node in an anchor that links up to the table of contents…even give it a title of “Back to Table of Contents” using node.setAttribute("title", "Back to Table of Contents").

Make a “List of figures”, and put captions on images

This one’s also geared towards creating an outline, and will be pretty similar to the table of contents. Suppose you have images that aren’t there for decoration or formatting, but are supposed to convey information (diagrams, charts, etc). Let’s assume that each has a meaningful alt tag. We can create a list of figures that lists the title of the image (i.e., the alt text), and links to it.

First, like we did for the table of contents, insert a node into the document to hold your list of figures. Let’s say it’s a div called figList.

Get all the images using document.getElementsByTagName("img"), and make a link pointing to each one. For each img node, again do two things:

  1. Add an anchor to figList. Get the img’s alt text via img.getAttribute("alt") — then use it for the anchor’s text, and set the anchor’s href to '#' + altText.
  2. Insert a named anchor before each img node, and set its name to altText.

Adding captions is really simple. While you’re looping through the list of img nodes, insert a span after each one, with class set to “caption”. Set the text to "Figure " + i + ": " + altText, and you’ve got automatically numbered caption images.

Format hyperlinks based on where they lead

I think I read a Jakob Nielsen article that suggested telling users before they click on a link that it’ll take them to another site. Why not put an icon next to each link whose href starts with “http://”?

var externalLinks = getElements(
    function(node) {
        // nodeType == 1 means it's an Element
        if (node.nodeType == 1 && node.nodeName == "A") {
            return node.getAttribute("href").indexOf("http://") <= 0;
        }
    });

Using AJAX, you can get really crazy — make a server-side component that returns the file size of a given URL. Now, grab all the links that point to a .pdf, .doc, .zip, or whatever, get the actual size from the server, and tell the user up-front how big the file is.

Checking our sanity

Just to reiterate, we’re talking about changing the HTML structure at runtime. Let’s step back a sec and talk about that.

  1. The server delivers a stream of text to the browser.
  2. The browser parses it and builds a “mental” model of what it says.
  3. The browser renders some text and color on the screen based on that model.

We’re talking about changing that model around at runtime, not the bytes sent to the browser. This means that if you View > Source on your page, you’ll see the original HTML, and none of the fancy nodes you added or rearranged.

To help us see the havoc we’ve wreaked on the DOM, we can hack up a simple bookmarklet to dump out the DOM as HTML in a new window. It’s as easy as this:

  • open a window
  • write out a big textarea to it (use style="width:100%;height:95%")
  • fill the textarea with this window’s document.documentElement.innerHTML

Closing thoughts

There are some general ideas that I’m using in many of these suggestions:

  • Use CSS classes, sometimes named after the node name, to keep your HTML-generating JavaScript clean.
  • Use plain text for named anchors, like header.innerText or img.getAttribute(“alt”). In programming, we usually shy away from using plain text for identifiers, because it’s easy to mis-type…but if you’re generating all your identifiers, who cares?
  • Use function pointers as general-purpose selectors, instead of trying to support several types of limited selector. For example, instead of getElementById(id), getElementNamed(tagName), and a hypothetical getElementWithAttribute(attrName, attrVal), why not just getElement(evalFunction)?
  • Two of these ideas made the assumption that your HTML is organized a certain way, which isn’t always possible. If you have a page with lots of formatting (those pesky web designers), see if you can cordon off a section of clean HTML — just a div with an id — and only scan those nodes. Another reason to use getElements() instead of the globally-scoped methods the browser provides.

Use these techniques to come up with new and exciting ways to bend the browser’s DOM to your will. Muahahahaha.

For more info:
JavaDoc-style JavaScript, pretty browser-independent
Gecko DOM reference for Firefox
AJAX, DOM Hacking’s most typical setting

Hacking the Browser’s DOM for Fun

Hacking the browser’s DOM is about traversing its structure, changing it around, and generating content. It’s the sexy half of Ajax — the part that changes the page before your very eyes! I won’t go into Ajax here, as there are other places that cover it far better than I can. Without Ajax’s other half, asynchronous HTTP, I’m not entirely sure how useful DOM hacking is, but it’s fun, and I’ve found at least a few good uses for it. YMMV — others on my team find it somewhat less attractive than I do. Along the way I’ll talk about function pointers, and throw in some recursion.

DOM Traversal

If you want to do anything interesting with the DOM, you have to see what’s in there first. The standard methods document.getElementById() and document.getElementsByTagName() are pretty useful here, but let’s create a third option, one that’s more flexible, and shows off our new function pointers knowledge:

function getElements (evalFunc, node) {
   if (!node) node = document.documentElement;

   var matches = new Array();  // storage space
   if (evalFunc(node))         // if it's a match...
       matches.push(node);     // store it

   var child = node.firstChild;
   while (child) {                // search thru each child
       matches = matches.concat(getElements(evalFunc, child));
       child = child.nextSibling;
   }

   return matches;
}
var headers = getElements(
   function(node) {
       return node.nodeName.length == 2 &&
       node.nodeName.charAt(0) == "H";
   });

Let’s start with getElements(). First, notice that it defaults node to document.documentElement, so if you call it without a node parameter, it’ll cover the whole document. Groovy, now we can forget about that. Now notice the recursion, and the structure of what it does — return an Array of nodes that meet evalFunc’s criteria. It’s classic recursive behavior: work on this, then work on this’s children (if any), and collect all the results.

It might seem like overkill, creating a whole new function — but look at how it’s used, and the power it gives you. We can get an Array of header tags by defining what a header looks like. “If the node name is 2 long, and starts with ‘H’, include it.” We could use a regular expression if we wanted to. We can pick nodes that match any criteria we can express in JavaScript.

var leafNodes = getElements (function(node) {
   return !node.hasChildNodes(); // no children = a leaf node
});

var imagesWithAltText = getElements (
   function(node) {
       if (node.nodeName == "IMG") {
           return node.getAttribute("alt") != "";
       }
       return false;
   });

See how handy function pointers are? They’re like little nuggets of code you can throw around. This “evaluator function” approach reminds me both of Ruby’s Enumerable.find_all method, and Java’s FileFilter and FilenameFilter interfaces. Or really, any case where you want lots of control over how you select items from a collection. Note that because Java doesn’t have function pointers like Ruby and JavaScript, it makes you wrap your function in a class (technically called a functor).

Creating Nodes

So now we can pick out nodes, let’s start creating new ones. This is pretty easy, except for the usual browser differences. I run Firefox 1.5 and IE 6, and my code for here will work in IE 6. I’ll note some basic Firefox issues as I go, but nothing too extensive.

In IE 6, you create a new node like this:

var myDivNode = document.createElement("<div>");

You can stuff any valid HTML in there, as far as I know, except it has to be an empty node. If you want to create, say, a hyperlink, you create both the anchor node and the text node, and append the text node into the anchor node:

var linkNode = document.createElement("<a href="http://www.google.com"></a>");
var linkLabel = document.createTextNode("Google");
linkNode.appendChild(linkLabel);

In Firefox 1.5, you only pass the tag name to document.createElement(), and you call elem.setAttribute("attrName", "attrValue") for all your attributes. Definitely tedious.

Note that linkNode.appendChild(linkLabel) there. It does just what it says: makes linkLabel a child of linkNode. Along with insertBefore, removeChild, and replaceChild, you can quickly get used to chaging the DOM at runtime. [See this JavaDoc-style JavaScript site for the details.]

I’ll stop here, and save the practical applications for another post. Some ideas, though:

  • creating a table of contents from all header tags
  • putting captions on all images that have alt text
  • formatting external links differently from internal ones

JavaScript OO and Function Pointers

A few things converged nicely for me this week: function pointers in JavaScript, and hacking the browser’s DOM. I think I’ll talk about function pointers and JavaScript’s OO, and leave browser DOM hacking for the next post.

I picked up function pointers from Ruby. They’re there in lots of languages, but Ruby’s where I discovered them. Even though I’ve been using JavaScript for years, I never saw them staring me in the face. Now that I get them, I finally get JavaScript’s OO paradigm. When you say:

function PublicSpeaker() {
    this.greet = _greet;
}
function _greet() {
    alert("Hello, everyone.");
}
new PublicSpeaker().greet();

…the deal is that this.greet is an instance variable of PublicSpeaker whose value is a function pointer, and JavaScript interprets new PublicSpeaker().greet() as “since greet is a function pointer, call the function it points to” (naturally). And when you say:

function BadPublicSpeaker() {
    this.inheritFrom = PublicSpeaker;
    this.inheritFrom();
}

…you’re setting the object’s instance variable inheritFrom to the
PublicSpeaker function (bummer that you have to explicitly call the super constructer, though).

[I know I can use the prototype method of establishing inheritance, but I'm not entirely comfortable with it yet...I don't grok it, and I do grok this. And just in case, here's what grok means.]

It seems most of JavaScript’s object-orientedness is based on function pointers. This probably explains why I hated JavaScript’s OO: I didn’t grok function pointers, so I was missing most of the picture. I could never remember that wacky syntax. I used to call it “object-scented” (oh, the hilarity).

All this improved understanding of JavaScript makes it much easier to start hacking the browser’s DOM (among other things), which I’ll get to later. In the meantime, you can dig on Object-Oriented Programming with JavaScript.


Say Hello

danbernier [at] gmail [dot] com
Twitter @danbernier
Hartford.rb
LinkedIn

Tweeting:

I’m a BackPack fan

Backpack

Categories