Wednesday, January 13, 2010

DOM Manipulation in JavaScript, a Utility

In my latest addition to the q12 JavaScript library, I have added a simple function to construct a nested document structure. I grew tired of writing repetitive and difficult to follow code like this
var div = document.createElement('div');
div['id'] = 'outer-div';
var link = document.createElement('a');
link.href = 'http://blog.jeffscudder.com';
link.appendChild(document.createTextNode('my blog'));
div.appendChild(link);
Using the new tree function this would become:
var div = tree([
'div', {
id: 'outer-div'}, [
'a', {
href: 'http://blog.jeffscudder.com'}, [
'my blog']]]);
In the process of writing this utility function, I came across another small oddity in editing HTML using JavaScript related to the style attribute. Most HTML attributes are simple text properties, however when editing the CSS style of a node, you cannot set the full content of the style attribute.
// You cannot do this:
div.style = "color:red; font-weight:bold";
Instead, you set each sub item in the style attribute:
// This works:
div.style.color = 'red';
div.style['font-weight'] = 'bold';
The tree function that I've written handles this nested style structure as well. Here's an example:
['div', {
'id': 'foo',
'style': {'color': 'red', 'font-weight': 'bold'}},
'This works too!']
Now that you have an idea of how to use this utility, here's the code. Minus the comments it's only about twenty-one lines.
/**
* Creates a DOM tree from a simple list.
* The structure of the tree passed in is as follows:
* ['elementTag',
* {attribute1: value,
* attribute2: value,
* style: {property1: value,
* property2: value}},
* 'child text node',
* ['elementTag',
* {property: value},
* 'grandchild text node'],
* 'third node']
* The above will result in a DOM node which has three child nodes, the
* first and third will be text nodes because the values were strings.
* The second child node will be a DOM node as well.
*
* @param {Array} t The tree's structure as a collection of strings, lists,
* and simple objects. The structure is as follows
* ['elementTag', {attributes}, child, child, child, ...]
* @return {Element} Returns a new DOM element.
*/
function tree(t) {
// Create the node using the tag which is first in the list.
var domNode = document.createElement(t[0]);
// Add all HTML attributes to the node.
for (var key in t[1]) {
// The style attributes get special treatment.
if (key == 'style') {
for (var styleAttribute in t[1].style) {
domNode.style[styleAttribute] = t[1].style[styleAttribute];
}
} else {
domNode[key] = t[1][key];
}
}
// Iterate over all child nodes, converting them to either text or HTML
// nodes.
for (var index = 2, child; child = t[index]; index++) {
if (typeof(child) == 'string') {
domNode.appendChild(document.createTextNode(child));
} else {
// Buid recursively.
domNode.appendChild(tree(child));
}
}
return domNode;
}
What do you think, is there anything you would add?

On a realted note, if you're interested in a more powerful templating system, you might want to try Closure Templates which were recently open sourced.