Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / XHTML

JsonML Simplified - Array.prototype.toDomNodes

4.50/5 (3 votes)
26 May 2011CPOL3 min read 44.9K   130  
A new JavaScript method to make JsonML a viable option for your projects

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:

JavaScript
/* Convert JsonML Array form into nested dom nodes */
Array.prototype.toDomNodes = function(p /* parent */, 
f /* function to call on creation of each node */)
{
	if (typeof this[0] === 'string')
	{
		// create element
		var d = document, el = d.createElement(this[0]);

		// resolve parent
		p = (typeof p === 'string') ? d.getElementById(p) : p;

		// a list of attributes to map or 'set directly'
		var am = {id:"id","class":"className",rowspan:"rowSpan",
		colspan:"colSpan",cellpadding:"cellPadding",cellspacing:
		"cellSpacing",maxlength:"maxLength",readonly:"readOnly",
		contenteditable:"contentEditable"};

		// add attributes
		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]; } // not a string, set directly (expando)
					else { el.setAttribute(a, n[a]); alert(a); }
				}
			}
		}

		// insert element (must be inserted before function call, 
		// otherwise .parentNode does not exist)
		if (p) { p.appendChild(el); }
		
		// pass each node to a function (attributes will exist, 
		// but not innerText||children. can be used for adding events, etc.)
		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) // add text node
			{
				el.appendChild(d.createTextNode(n));
			}
			else if (c === Array) // add children
			{
				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:

JavaScript
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:

JavaScript
["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:

JavaScript
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:

JavaScript
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.

JavaScript
jsonMl.toDomNodes(document.body, function(){

	// this holds the new element
	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.

JavaScript
var myNewNode = jsonMl.toDomNodes();

When you are ready to insert the new nodes into the DOM, you can call the traditional appendChild method.

JavaScript
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)