I’ve created a library to aid in the creation of DOM trees from within Javascript using a json-like syntax. If you’ve ever createElemented or appendChilded your way to a DOM tree, you know why this might be handy.
I like having a standardized DOM, but the language is so incredibly verbose that to build even the most rudimentary DOM tree in javascript takes long line after long line of code. Take the following HTML excerpt for example:
<div class="player">
<img class="icon" src="img/p1.gif"/>
<p class="playername">
<span class="name">Warren</span> (next to move)
</p>
<p class="actions">
[ <a href="lastmove.php">last move</a> ]
</p>
<br class="clear"/>
</div>
Now if we want to do this in raw standard DOM javascript code, it will look like this:
var div_player = document.createElement('div');
div_player.className = 'player';
var img_icon = div_player.appendChild(document.createElement('img'));
img_icon.className = 'icon';
img_icon.setAttribute('src', 'img/p1.gif');
var p_playername = div_player.appendChild(document.createElement('p'));
p_playername.className = 'playername';
var span_name = p_playername.appendChild(document.createElement('span'));
span_name.className = 'name';
span_name.appendChild(document.createTextNode('Warren'));
p_playername.appendChild(document.createTextNode(' (next to move)'));
var p_actions = div_player.appendChild(document.createElement('p'));
p_actions.className = 'actions';
p_actions.appendChild(document.createTextNode('[ '));
var a_lastmove = p_actions.appendChild(document.createElement('a'));
a_lastmove.setAttribute('href', 'lastmove.php');
a_lastmove.appendChild(document.createTextNode('last move'));
p_actions.appendChild(document.createTextNode(' ]'));
br_clear = div_player.appendChild(document.createElement('br'));
br_clear.className = 'clear';
And to be fair, I even took advantage of the .className property available for HTML instead of the DOM’s more verbose setAttribute method. The resulting code is much harder to read or debug and is horribly bulky in an environment where code is actually transmitted to the client over a network.
To solve the problem, I created a function called jcreate(). The function takes a single parameter and returns a single node. But the range of parameters it can take is its power. If you pass a string, it will return a DOMTextNode containing that string. If you pass in an array, the first element of the array becomes the “tag descriptor,” and the following elements become the child nodes (becoming parameters, themselves to recursive calls to jcreate()). The descriptor format is simply “tagname.classname#idname” and so it works like this:
jcreate('Warren'); // creates a text node
jcreate(['div']); // creates a div element
jcreate(['div.test#name']); // creates a div element with attributes class="test" and id="name"
jcreate(['div.test#name', 'Warren']); // identical div, but with a child text node
jcreate(['div.test#name, ['p.text', 'Warren'], ['p.subtext', 'super genius']]); // here the div contains two paragraphs
// now let's format that with indents
jcreate(
['div.test#name,
['p.text', 'Warren'],
['p.subtext', 'super genius'],
]);
In addition to the CSS-like descriptor above, you can also have an attribute descriptor of the format “@attrname.” An attribute is coded then as ['@href', '#help'] and so a link is coded as ['a', ['@href', '#help'], 'click for help'].
You can also use DOM nodes in the json-syntax instead of the json-like alternative. Why would you? Because it can give you a reference variable to that node that you can use later in your code to change the contents. It looks like this:
jcreate(
['div.test#name,
x = jcreate(['p.text', 'Warren']),
['p.subtext', 'super genius'],
]);
.
.
.
x.className = 'text highlight';
The HTML code at the beginning of this article can be then built dynamically using the following code:
x = jcreate(
['div.player',
['img.icon', ['@src', 'img/p1.gif']],
['p.playername', ['span.name', 'Warren'], ' (next to move)'],
['p.actions',
'[ ',
['a', ['@href', 'lastmove.php'], 'last move'],
' ]',
]
['br.clear']]);
The overhead for all this convenience is a few functions:
function create(n,c,i) { var e = document.createElement(n); if(c) e.className = c; if(i) e.id = i; return e; }
function attr(n,v) { var a = document.createAttribute(n); a.value = v; return a; }
function nodecreate(n) { return create(n.tagName, n.className, n.id); }
function text(s) { return document.createTextNode(s); }
function jid(s)
{
a = s.split('#');
b = a[0].split('.');
return {tagName: b[0], className: b[1], id: a[1]};
}
function jcreate(j)
{
if (j.nodeType) return j;
if (typeof(j) == 'string') return text(j);
if (j[0])
{
var id = j[0];
if (id.substring(0,1) == '@') return attr(id.substring(1), j[1]);
var e = nodecreate(jid(id));
for (var i = 1; i < j.length; i++)
{
c = jcreate(j[i]);
c && ((c.nodeType == 2) ? e.setAttributeNode(c) : e.appendChild(c));
}
return e;
}
return null;
}
In the future, I might extend this to allow javascript objects more complex than arrays (more like json syntax). But so far, I haven't really found a good reason to.
I've tested this, so far, in IE, Mozilla, Opera, and Konqueror. I am confident it works in Safari also but if someone could verify that and comment here, I'd be grateful.