Saturday, April 25, 2009

Website Internals: The JavaScript Tag Module

I need something to get myself blogging again, so I figured that I should write about how my website is written. I'm going to start with one of the parts written in JavaScript: the Tag module. It's comprised of a simple JavaScript object with static methods and properties, no instances. Its main purpose is to create (X)HTML nodes for use in what used to be known as "dynamic" HTML. Incidentally, it's also part of my "old projects" series: I originally created it for an Intel-sponsored research project that I was working on during my time in university. (For more details on the project, please see my curriculum vitae.)

In practice, calls using Tag.create() don't look too horrible. Consider the example of building a minimal HTML5 document:

var html = Tag.create('html', {children: [
    Tag.create('head', {children: Tag.create('title')),
    Tag.create('body')
]});

Or, a data table:

var table = Tag.create('table', {
    attributes: {summary: 'MLS Statistics'},
    classes: ['sortable', 'centered'], // uses sorttable 
    styles: {border: '1px red inset'}, // use CSS style names, not JS ones
    children: [
        Tag.create('thead', {children: Tag.create('tr', {
            // text is automatically converted
            Tag.create('th', {children: 'Team'}),
            Tag.create('th', {children: 'Goals For'}),
            Tag.create('th', {children: 'Goals Against'}) 
        })}),
        Tag.create('tbody', {children: [
            Tag.create('tr', {
        Tag.create('tbody', {children: [
            Tag.create('tr', {
                classes: ['west-coast', 'usa'],
                children: [
                    Tag.create('td', {children: 'Seattle Sounders FC'}),
                    Tag.create('td', {children: '9'}),
                    Tag.create('td', {children: '3'})
                ]
            }),
            // add more teams here...
        ])
    ]
});

I recently modified the function so that it deserializes an object (originally a JSON string) into a DOM node tree. This is particularly useful when you're sending partial HTML documents as JSON strings (which I prefer to sending HTML strings and dealing with that mess). So, the first example would look like this:

var html = Tag.create({
    "name": "html",
    "children": [
        {
            "name": "head",
            "children": {"name": "title"}
        },
        {"name": "body"}
    ]
});

Interestingly enough, I created this without the knowledge of the existence of JSONML or any of its bretheren, although I suspected that JSON-HTML converters already existed.

Three of the functions in the module are basically wrapper functions. Tag.createWithText() creates an HTML element with a text child node, and Tag.createHeader() creates an HTML header element (e.g., <h1>...</h1>) with a text child node. Tag.text() is shorthand for the DOM's document.createTextNode() method.

In the current iteration, nearly all of the event-related code is commented out, as there are far more competent JavaScript libraries out there which deal with cross-browser events. The only event-related function left is Tag.dispatchEvent(), which sends a "synthetic" event for a given HTML element. If I remember correctly, I coded for both the W3C and Microsoft models, but I don't remember testing it on browsers other than IE6/7 and Firefox 2. At some point, I'll probably reintegrate event support to Tag.create() at minimum, using Base2.

The remaining function is a utility function. Tag.inXHTML() determines whether the document in question is in XHTML mode, using a variety of heuristics. I'm sure there's a better way of doing it, but I couldn't find one when I was researching it.

This module has been tested in IE6/7, Firefox 1.5/2/3, Safari 2/3, and Opera 9.5 - although not all in the same time periods. It's licensed under the Apache Licence (version 2.0) and is currently somewhere in my compressed JavaScript file. If there's any interest, I'll put up the non-compressed version somewhere and update this post.