Nunney church, Somerset, UK
The Echoplex

DOM branching

As an interface, the DOM attracts a fair amount of critisism for its verbosity and some of the unintuitive mechanisms it provides.

It’s also often derided for its omissions. I mean, why is there no insertBefore method, when an insertAfter method exists in the spec? Thankfully some changes are coming through; features like getElementsByClassName and querySelectorAll have quickly become standardised.

While much of the rather clunky DOM interface persists, library authors have been quite successful in creating abstractions for dealing with the DOM in a more intuitive way. Although even with library abstractions, one area that still causes me pain is the creation of nested DOM fragments.

In some situations you can use innerHTML. However, it’s usually a hack for when you can’t be bothered to do things the DOM way. Which, besides being a pain, is actually a lot more versatile and elegant from a code point of view.

So, in efforts to find a better solution, I’ve created a few functions as part of my library that make creating elements and fragments using the DOM interface a lot more convenient. One utility for creating elements, named createElement (logically enough), and a related utility, named createBranch, for merging nodes into nested DOM fragments.

For a simple example, say you want to create some simple wrapper HTML like below, and retain a reference to the inner element:

<div id="widget-foo">
  <div class="content">
  </div>
</div>

Using the default DOM interface you might do this:

var wrapper = document.createElement( 'div' );
wrapper.setAttribute( 'id', 'widget-foo' );

var inner = document.createElement( 'div' );
inner.className = 'content';

wrapper.appendChild( inner );

Fairly long-winded compared to an innerHTML approach. But using the createElement utility (which I should note does not conflict with the document namesake) you can cut out some of the fat, while retaining the advantage of keeping the DOM references accessible.

var wrapper = createElement( '#widget-foo' );
var inner = createElement( '.content' );

wrapper.appendChild( inner );

You’ll notice the argument passed in is a simple CSS selector. The default element type if not specified is a div otherwise you could write ‘div.content’ explicitly, or ‘p.content’ for a different element.

Taking things a step further, the createBranch utility merges elements defined with same syntax that are passed in as arguments.

var branch = createBranch( 
    '#widget-foo',  
        '.content @:content' 
   );

This creates the HTML structure, but instead of returning an element reference you get an object of references:

{
  root   : #widget-foo,
  div    : [ #widget-foo, .content ],
  content: [ .content ]
}

The function collects references to all the created elements in a way similar to getElementsByTagName. The '@:content’ declaration simply creates a direct named reference so we don’t have to go digging when we need it later. Using the created branch is now as simple as this:

branch.content.innerHTML = 'Hello world'
document.body.appendChild( branch.root )

More complicated structures can be achieved by passing in arrays as sibling groups:

var branch = createBranch( 
    '#widget-foo',  
        '.content', [ 
            'h1 setText: "Introduction"', 
            'p setText: "DOM stands for Document Object Model."'    
        ]);

Which would create the following DOM fragment:

<div id="widget-foo">
  <div class="content">
    <h1>Introduction</h1>
    <p>DOM stands for Document Object Model.</p>
  </div>
</div>

Branches can also be nested, so creating something like an HTML table is manageable:

var thead = createBranch( 'thead', 'tr', 
   [ 
     'th setText: "Pros"', 
     'th setText: "Cons"' 
   ]);
   
var tbody = createBranch( 'tbody @:tbody', 
   [
   createBranch( 'tr',
      [ 
        'td setText: "Fast"', 
        'td setText: "Expensive"' 
      ]),
   createBranch( 'tr',
      [ 
        'td setText: "Looks good"', 
        'td setText: "High Maintenence"' 
      ])
   ]);

var table = createBranch( 'table.results', [ thead, tbody ] );

I have been using these utilities for a while now, and though they may not be perfect, they are definitely more practical to use than bare-bones DOM construction. I also find myself more inclined to use real DOM fragments rather than hacking away with innerHTML, which can only be a good thing.

The source for these utilities is freely available, under the conditions of an MIT license, over on the Jelly JavaScript project site.

Tagged with:

This is…

the online home of Pete Boere, web developer in Bristol, UK.

follow moi

code.google.com/p/jelly-javascript

Flickr photostream