]>
Commit | Line | Data |
---|---|---|
2f01fe57 AD |
1 | /* |
2 | Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. | |
3 | Available via Academic Free License >= 2.1 OR the modified BSD license. | |
4 | see: http://dojotoolkit.org/license for details | |
5 | */ | |
6 | ||
7 | ||
a089699c AD |
8 | if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
9 | dojo._hasResource["dojo.parser"] = true; | |
2f01fe57 AD |
10 | dojo.provide("dojo.parser"); |
11 | dojo.require("dojo.date.stamp"); | |
a089699c AD |
12 | |
13 | new Date("X"); // workaround for #11279, new Date("") == NaN | |
14 | ||
15 | dojo.parser = new function(){ | |
16 | // summary: The Dom/Widget parsing package | |
17 | ||
18 | var d = dojo; | |
19 | this._attrName = d._scopeName + "Type"; | |
20 | this._query = "[" + this._attrName + "]"; | |
21 | ||
22 | function val2type(/*Object*/ value){ | |
23 | // summary: | |
24 | // Returns name of type of given value. | |
25 | ||
26 | if(d.isString(value)){ return "string"; } | |
27 | if(typeof value == "number"){ return "number"; } | |
28 | if(typeof value == "boolean"){ return "boolean"; } | |
29 | if(d.isFunction(value)){ return "function"; } | |
30 | if(d.isArray(value)){ return "array"; } // typeof [] == "object" | |
31 | if(value instanceof Date) { return "date"; } // assume timestamp | |
32 | if(value instanceof d._Url){ return "url"; } | |
33 | return "object"; | |
34 | } | |
35 | ||
36 | function str2obj(/*String*/ value, /*String*/ type){ | |
37 | // summary: | |
38 | // Convert given string value to given type | |
39 | switch(type){ | |
40 | case "string": | |
41 | return value; | |
42 | case "number": | |
43 | return value.length ? Number(value) : NaN; | |
44 | case "boolean": | |
45 | // for checked/disabled value might be "" or "checked". interpret as true. | |
46 | return typeof value == "boolean" ? value : !(value.toLowerCase()=="false"); | |
47 | case "function": | |
48 | if(d.isFunction(value)){ | |
49 | // IE gives us a function, even when we say something like onClick="foo" | |
50 | // (in which case it gives us an invalid function "function(){ foo }"). | |
51 | // Therefore, convert to string | |
52 | value=value.toString(); | |
53 | value=d.trim(value.substring(value.indexOf('{')+1, value.length-1)); | |
54 | } | |
55 | try{ | |
56 | if(value === "" || value.search(/[^\w\.]+/i) != -1){ | |
57 | // The user has specified some text for a function like "return x+5" | |
58 | return new Function(value); | |
59 | }else{ | |
60 | // The user has specified the name of a function like "myOnClick" | |
61 | // or a single word function "return" | |
62 | return d.getObject(value, false) || new Function(value); | |
63 | } | |
64 | }catch(e){ return new Function(); } | |
65 | case "array": | |
66 | return value ? value.split(/\s*,\s*/) : []; | |
67 | case "date": | |
68 | switch(value){ | |
69 | case "": return new Date(""); // the NaN of dates | |
70 | case "now": return new Date(); // current date | |
71 | default: return d.date.stamp.fromISOString(value); | |
72 | } | |
73 | case "url": | |
74 | return d.baseUrl + value; | |
75 | default: | |
76 | return d.fromJson(value); | |
77 | } | |
78 | } | |
79 | ||
80 | var instanceClasses = { | |
81 | // map from fully qualified name (like "dijit.Button") to structure like | |
82 | // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} } | |
83 | }; | |
84 | ||
85 | // Widgets like BorderContainer add properties to _Widget via dojo.extend(). | |
86 | // If BorderContainer is loaded after _Widget's parameter list has been cached, | |
87 | // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget). | |
88 | dojo.connect(dojo, "extend", function(){ | |
89 | instanceClasses = {}; | |
90 | }); | |
91 | ||
92 | function getClassInfo(/*String*/ className){ | |
93 | // className: | |
94 | // fully qualified name (like "dijit.form.Button") | |
95 | // returns: | |
96 | // structure like | |
97 | // { | |
98 | // cls: dijit.Button, | |
99 | // params: { label: "string", disabled: "boolean"} | |
100 | // } | |
101 | ||
102 | if(!instanceClasses[className]){ | |
103 | // get pointer to widget class | |
104 | var cls = d.getObject(className); | |
105 | if(!cls){ return null; } // class not defined [yet] | |
106 | ||
107 | var proto = cls.prototype; | |
108 | ||
109 | // get table of parameter names & types | |
110 | var params = {}, dummyClass = {}; | |
111 | for(var name in proto){ | |
112 | if(name.charAt(0)=="_"){ continue; } // skip internal properties | |
113 | if(name in dummyClass){ continue; } // skip "constructor" and "toString" | |
114 | var defVal = proto[name]; | |
115 | params[name]=val2type(defVal); | |
116 | } | |
117 | ||
118 | instanceClasses[className] = { cls: cls, params: params }; | |
119 | } | |
120 | return instanceClasses[className]; | |
121 | } | |
122 | ||
123 | this._functionFromScript = function(script){ | |
124 | var preamble = ""; | |
125 | var suffix = ""; | |
126 | var argsStr = script.getAttribute("args"); | |
127 | if(argsStr){ | |
128 | d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){ | |
129 | preamble += "var "+part+" = arguments["+idx+"]; "; | |
130 | }); | |
131 | } | |
132 | var withStr = script.getAttribute("with"); | |
133 | if(withStr && withStr.length){ | |
134 | d.forEach(withStr.split(/\s*,\s*/), function(part){ | |
135 | preamble += "with("+part+"){"; | |
136 | suffix += "}"; | |
137 | }); | |
138 | } | |
139 | return new Function(preamble+script.innerHTML+suffix); | |
140 | } | |
141 | ||
142 | this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){ | |
143 | // summary: | |
144 | // Takes array of nodes, and turns them into class instances and | |
145 | // potentially calls a startup method to allow them to connect with | |
146 | // any children. | |
147 | // nodes: Array | |
148 | // Array of nodes or objects like | |
149 | // | { | |
150 | // | type: "dijit.form.Button", | |
151 | // | node: DOMNode, | |
152 | // | scripts: [ ... ], // array of <script type="dojo/..."> children of node | |
153 | // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc. | |
154 | // | } | |
155 | // mixin: Object? | |
156 | // An object that will be mixed in with each node in the array. | |
157 | // Values in the mixin will override values in the node, if they | |
158 | // exist. | |
159 | // args: Object? | |
160 | // An object used to hold kwArgs for instantiation. | |
161 | // Supports 'noStart' and inherited. | |
162 | var thelist = [], dp = dojo.parser; | |
163 | mixin = mixin||{}; | |
164 | args = args||{}; | |
165 | ||
166 | d.forEach(nodes, function(obj){ | |
167 | if(!obj){ return; } | |
168 | ||
169 | // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.s | |
170 | var node, type, clsInfo, clazz, scripts; | |
171 | if(obj.node){ | |
172 | // new format of nodes[] array, object w/lots of properties pre-computed for me | |
173 | node = obj.node; | |
174 | type = obj.type; | |
175 | clsInfo = obj.clsInfo || (type && getClassInfo(type)); | |
176 | clazz = clsInfo && clsInfo.cls; | |
177 | scripts = obj.scripts; | |
178 | }else{ | |
179 | // old (backwards compatible) format of nodes[] array, simple array of DOMNodes | |
180 | node = obj; | |
181 | type = dp._attrName in mixin ? mixin[dp._attrName] : node.getAttribute(dp._attrName); | |
182 | clsInfo = type && getClassInfo(type); | |
183 | clazz = clsInfo && clsInfo.cls; | |
184 | scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] : | |
185 | d.query("> script[type^='dojo/']", node)); | |
186 | } | |
187 | if(!clsInfo){ | |
188 | throw new Error("Could not load class '" + type); | |
189 | } | |
190 | ||
191 | // Setup hash to hold parameter settings for this widget. Start with the parameter | |
192 | // settings inherited from ancestors ("dir" and "lang"). | |
193 | // Inherited setting may later be overridden by explicit settings on node itself. | |
194 | var params = {}, | |
195 | attributes = node.attributes; | |
196 | if(args.defaults){ | |
197 | // settings for the document itself (or whatever subtree is being parsed) | |
198 | dojo.mixin(params, args.defaults); | |
199 | } | |
200 | if(obj.inherited){ | |
201 | // settings from dir=rtl or lang=... on a node above this node | |
202 | dojo.mixin(params, obj.inherited); | |
203 | } | |
204 | ||
205 | // read parameters (ie, attributes) specified on DOMNode | |
206 | // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"} | |
207 | for(var name in clsInfo.params){ | |
208 | var item = name in mixin?{value:mixin[name],specified:true}:attributes.getNamedItem(name); | |
209 | if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; } | |
210 | var value = item.value; | |
211 | // Deal with IE quirks for 'class' and 'style' | |
212 | switch(name){ | |
213 | case "class": | |
214 | value = "className" in mixin?mixin.className:node.className; | |
215 | break; | |
216 | case "style": | |
217 | value = "style" in mixin?mixin.style:(node.style && node.style.cssText); // FIXME: Opera? | |
218 | } | |
219 | var _type = clsInfo.params[name]; | |
220 | if(typeof value == "string"){ | |
221 | params[name] = str2obj(value, _type); | |
222 | }else{ | |
223 | params[name] = value; | |
224 | } | |
225 | } | |
226 | ||
227 | // Process <script type="dojo/*"> script tags | |
228 | // <script type="dojo/method" event="foo"> tags are added to params, and passed to | |
229 | // the widget on instantiation. | |
230 | // <script type="dojo/method"> tags (with no event) are executed after instantiation | |
231 | // <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation | |
232 | // note: dojo/* script tags cannot exist in self closing widgets, like <input /> | |
233 | var connects = [], // functions to connect after instantiation | |
234 | calls = []; // functions to call after instantiation | |
235 | ||
236 | d.forEach(scripts, function(script){ | |
237 | node.removeChild(script); | |
238 | var event = script.getAttribute("event"), | |
239 | type = script.getAttribute("type"), | |
240 | nf = d.parser._functionFromScript(script); | |
241 | if(event){ | |
242 | if(type == "dojo/connect"){ | |
243 | connects.push({event: event, func: nf}); | |
244 | }else{ | |
245 | params[event] = nf; | |
246 | } | |
247 | }else{ | |
248 | calls.push(nf); | |
249 | } | |
250 | }); | |
251 | ||
252 | var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory; | |
253 | // create the instance | |
254 | var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node); | |
255 | thelist.push(instance); | |
256 | ||
257 | // map it to the JS namespace if that makes sense | |
258 | var jsname = node.getAttribute("jsId"); | |
259 | if(jsname){ | |
260 | d.setObject(jsname, instance); | |
261 | } | |
262 | ||
263 | // process connections and startup functions | |
264 | d.forEach(connects, function(connect){ | |
265 | d.connect(instance, connect.event, null, connect.func); | |
266 | }); | |
267 | d.forEach(calls, function(func){ | |
268 | func.call(instance); | |
269 | }); | |
270 | }); | |
271 | ||
272 | // Call startup on each top level instance if it makes sense (as for | |
273 | // widgets). Parent widgets will recursively call startup on their | |
274 | // (non-top level) children | |
275 | if(!mixin._started){ | |
276 | // TODO: for 2.0, when old instantiate() API is desupported, store parent-child | |
277 | // relationships in the nodes[] array so that no getParent() call is needed. | |
278 | // Note that will require a parse() call from ContentPane setting a param that the | |
279 | // ContentPane is the parent widget (so that the parse doesn't call startup() on the | |
280 | // ContentPane's children) | |
281 | d.forEach(thelist, function(instance){ | |
282 | if( !args.noStart && instance && | |
283 | instance.startup && | |
284 | !instance._started && | |
285 | (!instance.getParent || !instance.getParent()) | |
286 | ){ | |
287 | instance.startup(); | |
288 | } | |
289 | }); | |
290 | } | |
291 | return thelist; | |
292 | }; | |
293 | ||
294 | this.parse = function(/*DomNode?*/ rootNode, /* Object? */ args){ | |
295 | // summary: | |
296 | // Scan the DOM for class instances, and instantiate them. | |
297 | // | |
298 | // description: | |
299 | // Search specified node (or root node) recursively for class instances, | |
300 | // and instantiate them Searches for | |
301 | // dojoType="qualified.class.name" | |
302 | // | |
303 | // rootNode: DomNode? | |
304 | // A default starting root node from which to start the parsing. Can be | |
305 | // omitted, defaulting to the entire document. If omitted, the `args` | |
306 | // object can be passed in this place. If the `args` object has a | |
307 | // `rootNode` member, that is used. | |
308 | // | |
309 | // args: | |
310 | // a kwArgs object passed along to instantiate() | |
311 | // | |
312 | // * noStart: Boolean? | |
313 | // when set will prevent the parser from calling .startup() | |
314 | // when locating the nodes. | |
315 | // * rootNode: DomNode? | |
316 | // identical to the function's `rootNode` argument, though | |
317 | // allowed to be passed in via this `args object. | |
318 | // * inherited: Object | |
319 | // Hash possibly containing dir and lang settings to be applied to | |
320 | // parsed widgets, unless there's another setting on a sub-node that overrides | |
321 | // | |
322 | // | |
323 | // example: | |
324 | // Parse all widgets on a page: | |
325 | // | dojo.parser.parse(); | |
326 | // | |
327 | // example: | |
328 | // Parse all classes within the node with id="foo" | |
329 | // | dojo.parser.parse(dojo.byId(foo)); | |
330 | // | |
331 | // example: | |
332 | // Parse all classes in a page, but do not call .startup() on any | |
333 | // child | |
334 | // | dojo.parser.parse({ noStart: true }) | |
335 | // | |
336 | // example: | |
337 | // Parse all classes in a node, but do not call .startup() | |
338 | // | dojo.parser.parse(someNode, { noStart:true }); | |
339 | // | // or | |
340 | // | dojo.parser.parse({ noStart:true, rootNode: someNode }); | |
341 | ||
342 | // determine the root node based on the passed arguments. | |
343 | var root; | |
344 | if(!args && rootNode && rootNode.rootNode){ | |
345 | args = rootNode; | |
346 | root = args.rootNode; | |
347 | }else{ | |
348 | root = rootNode; | |
349 | } | |
350 | ||
351 | var attrName = this._attrName; | |
352 | function scan(parent, list){ | |
353 | // summary: | |
354 | // Parent is an Object representing a DOMNode, with or without a dojoType specified. | |
355 | // Scan parent's children looking for nodes with dojoType specified, storing in list[]. | |
356 | // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[]. | |
357 | // parent: Object | |
358 | // Object representing the parent node, like | |
359 | // | { | |
360 | // | node: DomNode, // scan children of this node | |
361 | // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node | |
362 | // | | |
363 | // | // attributes only set if node has dojoType specified | |
364 | // | scripts: [], // empty array, put <script type=dojo/*> in here | |
365 | // | clsInfo: { cls: dijit.form.Button, ...} | |
366 | // | } | |
367 | // list: DomNode[] | |
368 | // Output array of objects (same format as parent) representing nodes to be turned into widgets | |
369 | ||
370 | // Effective dir and lang settings on parent node, either set directly or inherited from grandparent | |
371 | var inherited = dojo.clone(parent.inherited); | |
372 | dojo.forEach(["dir", "lang"], function(name){ | |
373 | var val = parent.node.getAttribute(name); | |
374 | if(val){ | |
375 | inherited[name] = val; | |
376 | } | |
377 | }); | |
378 | ||
379 | // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[]. | |
380 | var scripts = parent.scripts; | |
381 | ||
382 | // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively | |
383 | var recurse = !parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser; | |
384 | ||
385 | // scan parent's children looking for dojoType and <script type=dojo/*> | |
386 | for(var child = parent.node.firstChild; child; child = child.nextSibling){ | |
387 | if(child.nodeType == 1){ | |
388 | var type = recurse && child.getAttribute(attrName); | |
389 | if(type){ | |
390 | // if dojoType specified, add to output array of nodes to instantiate | |
391 | var params = { | |
392 | "type": type, | |
393 | clsInfo: getClassInfo(type), // note: won't find classes declared via dojo.Declaration | |
394 | node: child, | |
395 | scripts: [], // <script> nodes that are parent's children | |
396 | inherited: inherited // dir & lang attributes inherited from parent | |
397 | }; | |
398 | list.push(params); | |
399 | ||
400 | // Recurse, collecting <script type="dojo/..."> children, and also looking for | |
401 | // descendant nodes with dojoType specified (unless the widget has the stopParser flag), | |
402 | scan(params, list); | |
403 | }else if(scripts && child.nodeName.toLowerCase() == "script"){ | |
404 | // if <script type="dojo/...">, save in scripts[] | |
405 | type = child.getAttribute("type"); | |
406 | if (type && /^dojo\//i.test(type)) { | |
407 | scripts.push(child); | |
408 | } | |
409 | }else if(recurse){ | |
410 | // Recurse, looking for grandchild nodes with dojoType specified | |
411 | scan({ | |
412 | node: child, | |
413 | inherited: inherited | |
414 | }, list); | |
415 | } | |
416 | } | |
417 | } | |
418 | } | |
419 | ||
420 | // Make list of all nodes on page w/dojoType specified | |
421 | var list = []; | |
422 | scan({ | |
423 | node: root ? dojo.byId(root) : dojo.body(), | |
424 | inherited: (args && args.inherited) || { | |
425 | dir: dojo._isBodyLtr() ? "ltr" : "rtl" | |
426 | } | |
427 | }, list); | |
428 | ||
429 | // go build the object instances | |
430 | return this.instantiate(list, null, args); // Array | |
431 | }; | |
2f01fe57 | 432 | }(); |
a089699c AD |
433 | |
434 | //Register the parser callback. It should be the first callback | |
435 | //after the a11y test. | |
436 | ||
2f01fe57 | 437 | (function(){ |
a089699c AD |
438 | var parseRunner = function(){ |
439 | if(dojo.config.parseOnLoad){ | |
440 | dojo.parser.parse(); | |
441 | } | |
442 | }; | |
443 | ||
444 | // FIXME: need to clobber cross-dependency!! | |
445 | if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){ | |
446 | dojo._loaders.splice(1, 0, parseRunner); | |
447 | }else{ | |
448 | dojo._loaders.unshift(parseRunner); | |
449 | } | |
2f01fe57 | 450 | })(); |
a089699c | 451 | |
2f01fe57 | 452 | } |