Introduction
The code presented in this article allows DOM elements to be created from a JSON object formatted as markup (JsonML) via one function call. This means you can have your Ajax calls return a JSON object and insert that object into the DOM without manual parsing or using .innerHTML
.
If used & reused correctly, this code could dramatically reduce the footprint of your web application.
Background
Over the years, I have often wanted to introduce JsonML into one of my projects, not the templating or any of that stuff, just the concept of returning a JSON object from an Ajax call, doing whatever with it and then inserting it into the document when ready. However, all the JsonML scripts I’ve come across have always felt a little bloated and not something I really wanted to introduce into my applications.
I recently found myself with a little spare time; so I decided to have a crack at writing a more compact self contained JsonML parser. It was important that it meet the following requirements:
- Use as little code as possible
- Be totally self contained
- Use only w3c compliant DOM methods
- Must not use any browser sniffing
- Must insert nodes exactly as defined in the JsonML object
- Must allow me to create objects at runtime using less code
- Must allow me to specify a parent element of which to insert the new nodes
- Must allow me to specify a function to be called as each node is created
As JsonML is defined using an array, I built my method as a prototype of the native Array
object, meaning all array elements inherit the new method.
I also decided to name my method toDomNodes
rather than use any JsonML reference. This is because unlike traditional JsonML parsers, my version does not attempt to overcome browser inconsistencies by inserting missing elements such as Tbody and Thead (required by Internet Explorer). This was an important decision as I wanted the elements inserted into DOM to be an exact copy of my JSON object.
Using the Code
Here is the finished, self contained method:
Array.prototype.toDomNodes = function(p ,
f )
{
if (typeof this[0] === 'string')
{
var d = document, el = d.createElement(this[0]);
p = (typeof p === 'string') ? d.getElementById(p) : p;
var am = {id:"id","class":"className",rowspan:"rowSpan",
colspan:"colSpan",cellpadding:"cellPadding",cellspacing:
"cellSpacing",maxlength:"maxLength",readonly:"readOnly",
contenteditable:"contentEditable"};
if (this[1] && this[1].constructor == Object)
{
var n = this[1];
for (var a in this[1])
{
if (typeof this[a] != 'function')
{
if (a === "style" && typeof el.style.cssText
!= "undefined") { el.style.cssText = n[a]; }
else if (a === "style") { el.style = n[a]; }
else if (am[a]) { el[am[a]] = n[a]; }
else if (typeof this[a] != "string") { el[a] =
n[a]; }
else { el.setAttribute(a, n[a]); alert(a); }
}
}
}
if (p) { p.appendChild(el); }
if (typeof f === 'function') { f.apply(el); }
for (var i=1,l=this.length;i<l;i++)
{
var n = this[i], c = n.constructor;
if (c === String)
{
el.appendChild(d.createTextNode(n));
}
else if (c === Array)
{
n.toDomNodes(el, f);
}
}
return el;
}
return null;
};
This version includes a few comments to help you understand how it works and requires 1.73k of bandwidth to use. The minified version (no comments or formatting) is only 936 bytes. I don’t know about you but I’m much happier introducing 1k of script into my projects, especially when it allows me to reduce code in other areas of the application such as replacing:
var myDiv = document.createElement("div")
myDiv.id = "myDiv"
myDiv.style.border = "1px solid red";
myDiv.style.backgroudColor = "#cccccc";
myDiv.innerHTML = "Hello World!";
document.body.appendChild(myDiv);
with code like this:
["div",{"style":"background-color:#ccc; border: 1px solid red;"},
"Hello World!"].toDomNodes(document.body);
It also means that I can now return JsonML from my Ajax calls. As an example, imagine jsonMl contains my Ajax response:
var jsonMl = ["table",{"class":"MyTable","style":"background-color:yellow"},
["tbody",
["tr",
["td",{"class":"MyTD","style":"border:1px solid black"},
"#550758"],
["td",{"class":"MyTD","style":"background-color:red"},
"Example text here"]
],
["tr",
["td",{"class":"MyTD","style":"border:1px solid black"},
"#993101"],
["td",{"class":"MyTD","style":"background-color:green"},
"127624015"]
],
["tr",
["td",{"class":"MyTD","style":"border:1px solid black"},
"#E33D87"],
["td",{"class":"MyTD","style":"background-color:blue"},
"\u00A0",
["span",{"id":"mySpan","style":
"background-color:maroon;color:#fff
!important"},"\u00A9"],
"\u00A0"
]
]
]
];
I can insert this object straight into the document by calling:
jsonMl.toDomNodes(document.body);
Or, I can ask the parser to tell me each time a new element is created. This allows me to apply some client side logic or assign events based on attributes, etc.
jsonMl.toDomNodes(document.body, function(){
alert(this.tagName);
});
The this
object within your function will contain the newly created object. When your function is called, each new element will contain all its attributes and styles but not its children. That’s because this is a recursive function and child nodes have not been created at the point where it calls your function.
If you pass a parent element as the first parameter, the nodes are automatically inserted into that element. If you do not want to insert the elements into the DOM, simply pass null
or no parameter. In either case, this method will return a reference to the newly created DOM nodes.
var myNewNode = jsonMl.toDomNodes();
When you are ready to insert the new nodes into the DOM, you can call the traditional appendChild
method.
document.body.appendChild(myNewNode);
Points of Interest
This is the first incarnation. I have tested it and it works in IE5.5/6/7/8/9, Chrome, Firefox, Opera and Safari on the PC, iPad and iPhone. I have included a small zip file with an example of its usage along with the minified version. If there are any issues, please leave a comment and I’ll do my best to help out.
History
- 26 May 2011 - Initial release