summaryrefslogtreecommitdiff
path: root/js/dojo/dojox/xml/widgetParser.js
blob: d1e81184c061644513165a9d59d43d73d91afadc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//>>built
define("dojox/xml/widgetParser", [
	"dojo/_base/lang",	// dojo.getObject
	"dojo/_base/window",	// dojo.doc
	"dojo/_base/sniff",	// dojo.isIE
	"dojo/query",
	"dojo/parser",
	"dojox/xml/parser"
], function(dojo, window, has, query, parser, dxparser){

var dXml = lang.getObject("dojox.xml", true);

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

	/**
	 * 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.
	 *
	 */

xXml.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
		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 = window.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(has("ie") && 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;
	};
	
}();

return dXml.widgetParser;

});