musings of a Lispnik

Daniel Janus's blog

Meet my little friend createTree

| Comments

I’ve recently been developing an iPhone application in my spare time. I’m not going to tell you what it is just yet (I will post a separate entry once I manage to get it into the App Store); for now, let me just say that I’m writing it in JavaScript and HTML5, using PhoneGap and jQTouch to give it a native touch.

After having written some of code, I began testing it on a real device and encountered a nasty issue. It turned out that some of the screens of my app, containing a dynamically-generated content, sometimes would not show up. I tried to chase the problem down, but it seemed totally random. Finally, I googled up this blog post that gave me a clue.

My code was using jQuery’s .html() method (and hence innerHTML under the hood) to display the dynamic content. It turns out that, on Mobile Safari, using innerHTML is highly unreliable (at least on iOS 4.3, but this seems to be a long-standing bug). Sometimes, the change just does not happen. I changed one of my screens, to build and insert DOM objects explicitly, and sure enough, it started to work predictably well.

So I had to remove all usages of .html() from my app. The downside to it was that explicit DOM-building code is much more verbose than the version that constructs HTML and then sets it up. It’s tedious to write and contains much boilerplate.

To not be forced to change code, the above-quoted article advocates using a pure-JavaScript HTML parser outputting DOM to replace jQuery’s .html() method. I considered this for a while, but finally decided against it — I didn’t want to include another big, complex dependency that potentially could misbehave at times (writing HTML parsers is hard).

Instead, I came up with this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createTree(tree) {
    if (typeof tree === 'string' || typeof tree === 'number')
        return document.createTextNode(tree);
    var tag = tree[0], attrs = tree[1], res = document.createElement(tag);
    for (var attr in attrs) {
        val = attrs[attr];
        if (attr === 'class')
            res.className = val;
        else
            $(res).attr(attr, val);
    }
     for (var i = 2; i < tree.length; i++)
        res.appendChild(createTree(tree[i]));
    return res;
}

This is very similar in spirit to .html(), except that instead of passing HTML, you give it a data structure representing the DOM tree to construct. It can either be a string (which yields a text node), or a list consisting of the HTML tag name, an object mapping attributes to their values, and zero or more subtrees of the same form. Compare:

Using .html():

1
2
var html = '<p>This is an <span class="red">example.</span></p>';
$('#myDiv').html(html);

Using createTree:

1
2
3
4
var tree = ['p', {},
            'This is an ',
            ['span', {'class': 'red'}, 'example.']];
$('#myDiv').empty().append(createTree(tree));

A side benefit is that it is just as easy to build up a tree dynamically as it is to create HTML, and the code often gets clearer. Note how the createTree version above does not mix single and double quotes which is easy to mess up in the .html() version.

Comments