Location: A review of cardiac cellular electrophysiology models @ f6a8f9030738 / dojo-presentation / js / dojo / dojox / xml / widgetParser.js

Author:
David Nickerson <nickerso@users.sourceforge.net>
Date:
2009-07-16 02:00:03+12:00
Desc:
the starting point for the HH tutorial example
Permanent Source URI:
https://models.fieldml.org/workspace/a1/rawfile/f6a8f90307388eb4b040ee3566b84d88b59247f7/dojo-presentation/js/dojo/dojox/xml/widgetParser.js

/**
Take some sort of xml block
* like <dojo.button caption="blah"/> and turn
* it into a widget..
*/

dojo.provide("dojox.xml.widgetParser");
dojo.require("dojox.xml.parser");
dojo.require("dojo.parser");


	/**
	 * We want to support something like:
	 * <body>
	 * 	<script>
	 * 	<dijit.layout.SplitContainer>
	 * 		<dijit.button/>
	 * 		<div>...</div>
	 * 	</dijit.layout.SplitContainer>
	 * </body>
	 * 
	 * This is very tricky because if we parse this as XML then the <div> tag
	 * is actually an XML tag, not an XML tag, which is problematic in at least
	 * IE.
	 *
	 * So the strategy is this, silly as it may be: Convert EVERYTHING to HTML
	 * nodes, including the dijit.layout.SplitContainer by converting it to a
	 * div with the dojoType. Then run it through the standard parser.
	 * The more HTML you have relative to XML the less extra overhead this is.
	 * 
	 * For something that is all XML we could have a different approach,
	 * perhaps signified by a different type of script tag. In that case we
	 * could just instantiate all the elements without a sourceNodeRef and then
	 * add the top level components to the app.
	 * 
	 * That is very straightforward but I haven't done it.
	 * 
	 * Right now there is no mechanism to have an intermediary bridge between
	 * the XML and the widget, because we are relying on dojo.parser
	 * to do the instantiation. It isn't clear to me why we would want
	 * those bridges in this approach and not in that approach.
	 * 
	 */

dojox.xml.widgetParser = new function(){
	
	var d = dojo;
	
	this.parseNode = function(node){
		
		var toBuild = [];
		//TODO figure out the proper type
		d.query("script[type='text/xml']", node).forEach(function(script){
			toBuild.push.apply(toBuild, this._processScript(script));
		}, this).orphan();
		
		//instantiate everything at the end, doing it piecewise can give ID conflicts
		return d.parser.instantiate(toBuild);
	};

	this._processScript = function(script){
		//the text is either loaded from a separate file by the src
		//attribute or underneath the src tag
		var text = script.src ?  d._getText(script.src) : script.innerHTML || script.firstChild.nodeValue;
		var htmlNode = this.toHTML( dojox.xml.parser.parse(text).firstChild );
		
		//make the list BEFORE we copy things over to keep the query scope as
		//small as possible
		var ret = d.query('[dojoType]', htmlNode);
		//remove the script tag and replace with new HTML block
		dojo.query(">", htmlNode).place(script, "before")
		script.parentNode.removeChild(script);
		return ret;
	};
	
	/**
	 * Given an XML node converts it to HTML where the existing HTML
	 * is preserved and the dojo widget tags are converted to divs
	 * with dojoType on them.
	 */
	this.toHTML = function (/*XmlNode*/ node){
		var newNode;
		var nodeName = node.nodeName;
		var dd = dojo.doc;
		var type = node.nodeType;
		
		
		///node type 3 and 4 are text and cdata
		if(type >= 3){
			return dd.createTextNode( (type == 3 || type == 4) ? node.nodeValue : "" );
		}
		
		var localName = node.localName||nodeName.split(":").pop();
		
		//TODO:
		//		only check for namespace ONCE ever, instead of each time here,
		//		by mixing in the right check for each browser?
		var namespace = node.namespaceURI || (node.getNamespaceUri ? node.getNamespaceUri() : "");
		
		//TODO check for some real namespace
		if(namespace == "html"){
			newNode = dd.createElement(localName);
		}else{
			var dojoType = namespace + "." + localName;
			
			/**
			 * This is a horrible hack we need because creating a <div>
			 * with <option> children doesn't work well. Specifically with
			 * dojo.Declaration at some point the <option> tags get lost 
			 * entirely so we need the parent of <option> tags to be <select>
			 * tags. (Not a problem outside of dojo.Delcaration)
			 * There are a couple other ways we could do this:
			 * 1. Look at the first element child to see if it is an option and 
			 * if so create a <select> here.
			 * 2. When we add a child to parent fix up the parent then if the 
			 * child is an <option> and the parent isn't a <select>.
			 * Both of those are a bit messy and slower than this.
			 * 
			 * This is potentially a problem for other tag combinations as well,
			 * such as <tr> under a <table> or <li> under a <ul>/<ol>.
			 * (dojox.widget.SortList for example). Probably need a robust strategy for
			 * dealing with this. Worst case scenario for now is that user has to use
			 * html tag with dojoType for misbehaving widget.
			 */
			newNode = newNode || dd.createElement((dojoType == "dijit.form.ComboBox") ? "select" : "div");
			newNode.setAttribute("dojoType", dojoType);
		}
		
		//	TODO:
		//		we should probably set this up different, mixin a function
		//		depending on if it is IE rather than checking every time here
		//		the xmlns problem and the style problem are both IE specific
		d.forEach(node.attributes, function(attr){
			// NOTE: IE always iterates *all* properties!!!
			var name = attr.name || attr.nodeName;
			var value = attr.value || attr.nodeValue;
			if(name.indexOf("xmlns") != 0){
				// style=blah blah blah is a problem, in IE if you use
				// setAttribute here you get all sorts of problems. Maybe it
				// would be better to just create a giant string of HTML
				// instead of an object graph, then set innerHTML on something
				// to get the object graph? That might be cleaner...  that way
				// is uses the browser HTML parsing exactly at is and won't
				// cause any sort of issues. We could just special case style
				// as well?
				if(dojo.isIE && name == "style"){
					newNode.style.setAttribute("cssText", value);
				}else{
					newNode.setAttribute(name, value);
				}
			}
		});
		d.forEach(node.childNodes, function(cn){
			var childNode = this.toHTML(cn);
			
			// script tags in IE don't like appendChild, innerHTML or innerText
			// so if we are creating one programatically set text instead
			// could special case this for IE only
			if(localName == "script"){
				newNode.text += childNode.nodeValue;
			}else{
				newNode.appendChild(childNode);
			}
		}, this);
		return newNode;
	};
	
}();