]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define( |
2 | "dojo/parser", ["require", "./_base/kernel", "./_base/lang", "./_base/array", "./_base/config", "./_base/html", "./_base/window", | |
3 | "./_base/url", "./_base/json", "./aspect", "./date/stamp", "./Deferred", "./has", "./query", "./on", "./ready"], | |
4 | function(require, dojo, dlang, darray, config, dhtml, dwindow, _Url, djson, aspect, dates, Deferred, has, query, don, ready){ | |
5 | ||
6 | // module: | |
7 | // dojo/parser | |
8 | ||
9 | new Date("X"); // workaround for #11279, new Date("") == NaN | |
10 | ||
11 | ||
12 | // Widgets like BorderContainer add properties to _Widget via dojo.extend(). | |
13 | // If BorderContainer is loaded after _Widget's parameter list has been cached, | |
14 | // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget). | |
15 | var extendCnt = 0; | |
16 | aspect.after(dlang, "extend", function(){ | |
17 | extendCnt++; | |
18 | }, true); | |
19 | ||
20 | function getNameMap(ctor){ | |
21 | // summary: | |
22 | // Returns map from lowercase name to attribute name in class, ex: {onclick: "onClick"} | |
23 | var map = ctor._nameCaseMap, proto = ctor.prototype; | |
24 | ||
25 | // Create the map if it's undefined. | |
26 | // Refresh the map if a superclass was possibly extended with new methods since the map was created. | |
27 | if(!map || map._extendCnt < extendCnt){ | |
28 | map = ctor._nameCaseMap = {}; | |
29 | for(var name in proto){ | |
30 | if(name.charAt(0) === "_"){ continue; } // skip internal properties | |
31 | map[name.toLowerCase()] = name; | |
32 | } | |
33 | map._extendCnt = extendCnt; | |
34 | } | |
35 | return map; | |
36 | } | |
37 | ||
38 | // Map from widget name or list of widget names(ex: "dijit/form/Button,acme/MyMixin") to a constructor. | |
39 | var _ctorMap = {}; | |
40 | ||
41 | function getCtor(/*String[]*/ types){ | |
42 | // summary: | |
43 | // Retrieves a constructor. If the types array contains more than one class/MID then the | |
44 | // subsequent classes will be mixed into the first class and a unique constructor will be | |
45 | // returned for that array. | |
46 | ||
47 | var ts = types.join(); | |
48 | if(!_ctorMap[ts]){ | |
49 | var mixins = []; | |
50 | for(var i = 0, l = types.length; i < l; i++){ | |
51 | var t = types[i]; | |
52 | // TODO: Consider swapping getObject and require in the future | |
53 | mixins[mixins.length] = (_ctorMap[t] = _ctorMap[t] || (dlang.getObject(t) || (~t.indexOf('/') && require(t)))); | |
54 | } | |
55 | var ctor = mixins.shift(); | |
56 | _ctorMap[ts] = mixins.length ? (ctor.createSubclass ? ctor.createSubclass(mixins) : ctor.extend.apply(ctor, mixins)) : ctor; | |
57 | } | |
58 | ||
59 | return _ctorMap[ts]; | |
60 | } | |
61 | ||
62 | var parser = { | |
63 | // summary: | |
64 | // The Dom/Widget parsing package | |
65 | ||
66 | _clearCache: function(){ | |
67 | // summary: | |
68 | // Clear cached data. Used mainly for benchmarking. | |
69 | extendCnt++; | |
70 | _ctorMap = {}; | |
71 | }, | |
72 | ||
73 | _functionFromScript: function(script, attrData){ | |
74 | // summary: | |
75 | // Convert a `<script type="dojo/method" args="a, b, c"> ... </script>` | |
76 | // into a function | |
77 | // script: DOMNode | |
78 | // The `<script>` DOMNode | |
79 | // attrData: String | |
80 | // For HTML5 compliance, searches for attrData + "args" (typically | |
81 | // "data-dojo-args") instead of "args" | |
82 | var preamble = "", | |
83 | suffix = "", | |
84 | argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args")), | |
85 | withStr = script.getAttribute("with"); | |
86 | ||
87 | // Convert any arguments supplied in script tag into an array to be passed to the | |
88 | var fnArgs = (argsStr || "").split(/\s*,\s*/); | |
89 | ||
90 | if(withStr && withStr.length){ | |
91 | darray.forEach(withStr.split(/\s*,\s*/), function(part){ | |
92 | preamble += "with("+part+"){"; | |
93 | suffix += "}"; | |
94 | }); | |
95 | } | |
96 | ||
97 | return new Function(fnArgs, preamble + script.innerHTML + suffix); | |
98 | }, | |
99 | ||
100 | instantiate: function(nodes, mixin, options){ | |
101 | // summary: | |
102 | // Takes array of nodes, and turns them into class instances and | |
103 | // potentially calls a startup method to allow them to connect with | |
104 | // any children. | |
105 | // nodes: Array | |
106 | // Array of DOM nodes | |
107 | // mixin: Object? | |
108 | // An object that will be mixed in with each node in the array. | |
109 | // Values in the mixin will override values in the node, if they | |
110 | // exist. | |
111 | // options: Object? | |
112 | // An object used to hold kwArgs for instantiation. | |
113 | // See parse.options argument for details. | |
114 | ||
115 | mixin = mixin || {}; | |
116 | options = options || {}; | |
117 | ||
118 | var dojoType = (options.scope || dojo._scopeName) + "Type", // typically "dojoType" | |
119 | attrData = "data-" + (options.scope || dojo._scopeName) + "-",// typically "data-dojo-" | |
120 | dataDojoType = attrData + "type", // typically "data-dojo-type" | |
121 | dataDojoMixins = attrData + "mixins"; // typically "data-dojo-mixins" | |
122 | ||
123 | var list = []; | |
124 | darray.forEach(nodes, function(node){ | |
125 | var type = dojoType in mixin ? mixin[dojoType] : node.getAttribute(dataDojoType) || node.getAttribute(dojoType); | |
126 | if(type){ | |
127 | var mixinsValue = node.getAttribute(dataDojoMixins), | |
128 | types = mixinsValue ? [type].concat(mixinsValue.split(/\s*,\s*/)) : [type]; | |
129 | ||
130 | list.push({ | |
131 | node: node, | |
132 | types: types | |
133 | }); | |
134 | } | |
135 | }); | |
136 | ||
137 | // Instantiate the nodes and return the objects | |
138 | return this._instantiate(list, mixin, options); | |
139 | }, | |
140 | ||
141 | _instantiate: function(nodes, mixin, options){ | |
142 | // summary: | |
143 | // Takes array of objects representing nodes, and turns them into class instances and | |
144 | // potentially calls a startup method to allow them to connect with | |
145 | // any children. | |
146 | // nodes: Array | |
147 | // Array of objects like | |
148 | // | { | |
149 | // | ctor: Function (may be null) | |
150 | // | types: ["dijit/form/Button", "acme/MyMixin"] (used if ctor not specified) | |
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 | // options: Object | |
160 | // An options object used to hold kwArgs for instantiation. | |
161 | // See parse.options argument for details. | |
162 | ||
163 | // Call widget constructors | |
164 | var thelist = darray.map(nodes, function(obj){ | |
165 | var ctor = obj.ctor || getCtor(obj.types); | |
166 | // If we still haven't resolved a ctor, it is fatal now | |
167 | if(!ctor){ | |
168 | throw new Error("Unable to resolve constructor for: '" + obj.types.join() + "'"); | |
169 | } | |
170 | return this.construct(ctor, obj.node, mixin, options, obj.scripts, obj.inherited); | |
171 | }, this); | |
172 | ||
173 | // Call startup on each top level instance if it makes sense (as for | |
174 | // widgets). Parent widgets will recursively call startup on their | |
175 | // (non-top level) children | |
176 | if(!mixin._started && !options.noStart){ | |
177 | darray.forEach(thelist, function(instance){ | |
178 | if(typeof instance.startup === "function" && !instance._started){ | |
179 | instance.startup(); | |
180 | } | |
181 | }); | |
182 | } | |
183 | ||
184 | return thelist; | |
185 | }, | |
186 | ||
187 | construct: function(ctor, node, mixin, options, scripts, inherited){ | |
188 | // summary: | |
189 | // Calls new ctor(params, node), where params is the hash of parameters specified on the node, | |
190 | // excluding data-dojo-type and data-dojo-mixins. Does not call startup(). Returns the widget. | |
191 | // ctor: Function | |
192 | // Widget constructor. | |
193 | // node: DOMNode | |
194 | // This node will be replaced/attached to by the widget. It also specifies the arguments to pass to ctor. | |
195 | // mixin: Object? | |
196 | // Attributes in this object will be passed as parameters to ctor, | |
197 | // overriding attributes specified on the node. | |
198 | // options: Object? | |
199 | // An options object used to hold kwArgs for instantiation. See parse.options argument for details. | |
200 | // scripts: DomNode[]? | |
201 | // Array of `<script type="dojo/*">` DOMNodes. If not specified, will search for `<script>` tags inside node. | |
202 | // inherited: Object? | |
203 | // Settings from dir=rtl or lang=... on a node above this node. Overrides options.inherited. | |
204 | ||
205 | var proto = ctor && ctor.prototype; | |
206 | options = options || {}; | |
207 | ||
208 | // Setup hash to hold parameter settings for this widget. Start with the parameter | |
209 | // settings inherited from ancestors ("dir" and "lang"). | |
210 | // Inherited setting may later be overridden by explicit settings on node itself. | |
211 | var params = {}; | |
212 | ||
213 | if(options.defaults){ | |
214 | // settings for the document itself (or whatever subtree is being parsed) | |
215 | dlang.mixin(params, options.defaults); | |
216 | } | |
217 | if(inherited){ | |
218 | // settings from dir=rtl or lang=... on a node above this node | |
219 | dlang.mixin(params, inherited); | |
220 | } | |
221 | ||
222 | // Get list of attributes explicitly listed in the markup | |
223 | var attributes; | |
224 | if(has("dom-attributes-explicit")){ | |
225 | // Standard path to get list of user specified attributes | |
226 | attributes = node.attributes; | |
227 | }else if(has("dom-attributes-specified-flag")){ | |
228 | // Special processing needed for IE8, to skip a few faux values in attributes[] | |
229 | attributes = darray.filter(node.attributes, function(a){ return a.specified;}); | |
230 | }else{ | |
231 | // Special path for IE6-7, avoid (sometimes >100) bogus entries in node.attributes | |
232 | var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false), | |
233 | attrs = clone.outerHTML.replace(/=[^\s"']+|="[^"]*"|='[^']*'/g, "").replace(/^\s*<[a-zA-Z0-9]*\s*/, "").replace(/\s*>.*$/, ""); | |
234 | ||
235 | attributes = darray.map(attrs.split(/\s+/), function(name){ | |
236 | var lcName = name.toLowerCase(); | |
237 | return { | |
238 | name: name, | |
239 | // getAttribute() doesn't work for button.value, returns innerHTML of button. | |
240 | // but getAttributeNode().value doesn't work for the form.encType or li.value | |
241 | value: (node.nodeName == "LI" && name == "value") || lcName == "enctype" ? | |
242 | node.getAttribute(lcName) : node.getAttributeNode(lcName).value | |
243 | }; | |
244 | }); | |
245 | } | |
246 | ||
247 | // Hash to convert scoped attribute name (ex: data-dojo17-params) to something friendly (ex: data-dojo-params) | |
248 | // TODO: remove scope for 2.0 | |
249 | var scope = options.scope || dojo._scopeName, | |
250 | attrData = "data-" + scope + "-", // typically "data-dojo-" | |
251 | hash = {}; | |
252 | if(scope !== "dojo"){ | |
253 | hash[attrData + "props"] = "data-dojo-props"; | |
254 | hash[attrData + "type"] = "data-dojo-type"; | |
255 | hash[attrData + "mixins"] = "data-dojo-mixins"; | |
256 | hash[scope + "type"] = "dojoType"; | |
257 | hash[attrData + "id"] = "data-dojo-id"; | |
258 | } | |
259 | ||
260 | // Read in attributes and process them, including data-dojo-props, data-dojo-type, | |
261 | // dojoAttachPoint, etc., as well as normal foo=bar attributes. | |
262 | var i=0, item, funcAttrs=[], jsname, extra; | |
263 | while(item = attributes[i++]){ | |
264 | var name = item.name, | |
265 | lcName = name.toLowerCase(), | |
266 | value = item.value; | |
267 | ||
268 | switch(hash[lcName] || lcName){ | |
269 | // Already processed, just ignore | |
270 | case "data-dojo-type": | |
271 | case "dojotype": | |
272 | case "data-dojo-mixins": | |
273 | break; | |
274 | ||
275 | // Data-dojo-props. Save for later to make sure it overrides direct foo=bar settings | |
276 | case "data-dojo-props": | |
277 | extra = value; | |
278 | break; | |
279 | ||
280 | // data-dojo-id or jsId. TODO: drop jsId in 2.0 | |
281 | case "data-dojo-id": | |
282 | case "jsid": | |
283 | jsname = value; | |
284 | break; | |
285 | ||
286 | // For the benefit of _Templated | |
287 | case "data-dojo-attach-point": | |
288 | case "dojoattachpoint": | |
289 | params.dojoAttachPoint = value; | |
290 | break; | |
291 | case "data-dojo-attach-event": | |
292 | case "dojoattachevent": | |
293 | params.dojoAttachEvent = value; | |
294 | break; | |
295 | ||
296 | // Special parameter handling needed for IE | |
297 | case "class": | |
298 | params["class"] = node.className; | |
299 | break; | |
300 | case "style": | |
301 | params["style"] = node.style && node.style.cssText; | |
302 | break; | |
303 | default: | |
304 | // Normal attribute, ex: value="123" | |
305 | ||
306 | // Find attribute in widget corresponding to specified name. | |
307 | // May involve case conversion, ex: onclick --> onClick | |
308 | if(!(name in proto)){ | |
309 | var map = getNameMap(ctor); | |
310 | name = map[lcName] || name; | |
311 | } | |
312 | ||
313 | // Set params[name] to value, doing type conversion | |
314 | if(name in proto){ | |
315 | switch(typeof proto[name]){ | |
316 | case "string": | |
317 | params[name] = value; | |
318 | break; | |
319 | case "number": | |
320 | params[name] = value.length ? Number(value) : NaN; | |
321 | break; | |
322 | case "boolean": | |
323 | // for checked/disabled value might be "" or "checked". interpret as true. | |
324 | params[name] = value.toLowerCase() != "false"; | |
325 | break; | |
326 | case "function": | |
327 | if(value === "" || value.search(/[^\w\.]+/i) != -1){ | |
328 | // The user has specified some text for a function like "return x+5" | |
329 | params[name] = new Function(value); | |
330 | }else{ | |
331 | // The user has specified the name of a global function like "myOnClick" | |
332 | // or a single word function "return" | |
333 | params[name] = dlang.getObject(value, false) || new Function(value); | |
334 | } | |
335 | funcAttrs.push(name); // prevent "double connect", see #15026 | |
336 | break; | |
337 | default: | |
338 | var pVal = proto[name]; | |
339 | params[name] = | |
340 | (pVal && "length" in pVal) ? (value ? value.split(/\s*,\s*/) : []) : // array | |
341 | (pVal instanceof Date) ? | |
342 | (value == "" ? new Date("") : // the NaN of dates | |
343 | value == "now" ? new Date() : // current date | |
344 | dates.fromISOString(value)) : | |
345 | (pVal instanceof _Url) ? (dojo.baseUrl + value) : | |
346 | djson.fromJson(value); | |
347 | } | |
348 | }else{ | |
349 | params[name] = value; | |
350 | } | |
351 | } | |
352 | } | |
353 | ||
354 | // Remove function attributes from DOMNode to prevent "double connect" problem, see #15026. | |
355 | // Do this as a separate loop since attributes[] is often a live collection (depends on the browser though). | |
356 | for(var j=0; j<funcAttrs.length; j++){ | |
357 | var lcfname = funcAttrs[j].toLowerCase(); | |
358 | node.removeAttribute(lcfname); | |
359 | node[lcfname] = null; | |
360 | } | |
361 | ||
362 | // Mix things found in data-dojo-props into the params, overriding any direct settings | |
363 | if(extra){ | |
364 | try{ | |
365 | extra = djson.fromJson.call(options.propsThis, "{" + extra + "}"); | |
366 | dlang.mixin(params, extra); | |
367 | }catch(e){ | |
368 | // give the user a pointer to their invalid parameters. FIXME: can we kill this in production? | |
369 | throw new Error(e.toString() + " in data-dojo-props='" + extra + "'"); | |
370 | } | |
371 | } | |
372 | ||
373 | // Any parameters specified in "mixin" override everything else. | |
374 | dlang.mixin(params, mixin); | |
375 | ||
376 | // Get <script> nodes associated with this widget, if they weren't specified explicitly | |
377 | if(!scripts){ | |
378 | scripts = (ctor && (ctor._noScript || proto._noScript) ? [] : query("> script[type^='dojo/']", node)); | |
379 | } | |
380 | ||
381 | // Process <script type="dojo/*"> script tags | |
382 | // <script type="dojo/method" event="foo"> tags are added to params, and passed to | |
383 | // the widget on instantiation. | |
384 | // <script type="dojo/method"> tags (with no event) are executed after instantiation | |
385 | // <script type="dojo/connect" data-dojo-event="foo"> tags are dojo.connected after instantiation | |
386 | // <script type="dojo/watch" data-dojo-prop="foo"> tags are dojo.watch after instantiation | |
387 | // <script type="dojo/on" data-dojo-event="foo"> tags are dojo.on after instantiation | |
388 | // note: dojo/* script tags cannot exist in self closing widgets, like <input /> | |
389 | var aspects = [], // aspects to connect after instantiation | |
390 | calls = [], // functions to call after instantiation | |
391 | watches = [], // functions to watch after instantiation | |
392 | ons = []; // functions to on after instantiation | |
393 | ||
394 | if(scripts){ | |
395 | for(i=0; i<scripts.length; i++){ | |
396 | var script = scripts[i]; | |
397 | node.removeChild(script); | |
398 | // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead | |
399 | var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")), | |
400 | prop = script.getAttribute(attrData + "prop"), | |
401 | method = script.getAttribute(attrData + "method"), | |
402 | advice = script.getAttribute(attrData + "advice"), | |
403 | scriptType = script.getAttribute("type"), | |
404 | nf = this._functionFromScript(script, attrData); | |
405 | if(event){ | |
406 | if(scriptType == "dojo/connect"){ | |
407 | aspects.push({ method: event, func: nf }); | |
408 | }else if(scriptType == "dojo/on"){ | |
409 | ons.push({ event: event, func: nf }); | |
410 | }else{ | |
411 | params[event] = nf; | |
412 | } | |
413 | }else if(scriptType == "dojo/aspect"){ | |
414 | aspects.push({ method: method, advice: advice, func: nf }); | |
415 | }else if(scriptType == "dojo/watch"){ | |
416 | watches.push({ prop: prop, func: nf }); | |
417 | }else{ | |
418 | calls.push(nf); | |
419 | } | |
420 | } | |
421 | } | |
422 | ||
423 | // create the instance | |
424 | var markupFactory = ctor.markupFactory || proto.markupFactory; | |
425 | var instance = markupFactory ? markupFactory(params, node, ctor) : new ctor(params, node); | |
426 | ||
427 | // map it to the JS namespace if that makes sense | |
428 | if(jsname){ | |
429 | dlang.setObject(jsname, instance); | |
430 | } | |
431 | ||
432 | // process connections and startup functions | |
433 | for(i=0; i<aspects.length; i++){ | |
434 | aspect[aspects[i].advice || "after"](instance, aspects[i].method, dlang.hitch(instance, aspects[i].func), true); | |
435 | } | |
436 | for(i=0; i<calls.length; i++){ | |
437 | calls[i].call(instance); | |
438 | } | |
439 | for(i=0; i<watches.length; i++){ | |
440 | instance.watch(watches[i].prop, watches[i].func); | |
441 | } | |
442 | for(i=0; i<ons.length; i++){ | |
443 | don(instance, ons[i].event, ons[i].func); | |
444 | } | |
445 | ||
446 | return instance; | |
447 | }, | |
448 | ||
449 | scan: function(root, options){ | |
450 | // summary: | |
451 | // Scan a DOM tree and return an array of objects representing the DOMNodes | |
452 | // that need to be turned into widgets. | |
453 | // description: | |
454 | // Search specified node (or document root node) recursively for class instances | |
455 | // and return an array of objects that represent potential widgets to be | |
456 | // instantiated. Searches for either data-dojo-type="MID" or dojoType="MID" where | |
457 | // "MID" is a module ID like "dijit/form/Button" or a fully qualified Class name | |
458 | // like "dijit/form/Button". If the MID is not currently available, scan will | |
459 | // attempt to require() in the module. | |
460 | // | |
461 | // See parser.parse() for details of markup. | |
462 | // root: DomNode? | |
463 | // A default starting root node from which to start the parsing. Can be | |
464 | // omitted, defaulting to the entire document. If omitted, the `options` | |
465 | // object can be passed in this place. If the `options` object has a | |
466 | // `rootNode` member, that is used. | |
467 | // options: Object | |
468 | // a kwArgs options object, see parse() for details | |
469 | // | |
470 | // returns: Promise | |
471 | // A promise that is resolved with the nodes that have been parsed. | |
472 | ||
473 | var list = [], // Output List | |
474 | mids = [], // An array of modules that are not yet loaded | |
475 | midsHash = {}; // Used to keep the mids array unique | |
476 | ||
477 | var dojoType = (options.scope || dojo._scopeName) + "Type", // typically "dojoType" | |
478 | attrData = "data-" + (options.scope || dojo._scopeName) + "-", // typically "data-dojo-" | |
479 | dataDojoType = attrData + "type", // typically "data-dojo-type" | |
480 | dataDojoTextDir = attrData + "textdir", // typically "data-dojo-textdir" | |
481 | dataDojoMixins = attrData + "mixins"; // typically "data-dojo-mixins" | |
482 | ||
483 | // Info on DOMNode currently being processed | |
484 | var node = root.firstChild; | |
485 | ||
486 | // Info on parent of DOMNode currently being processed | |
487 | // - inherited: dir, lang, and textDir setting of parent, or inherited by parent | |
488 | // - parent: pointer to identical structure for my parent (or null if no parent) | |
489 | // - scripts: if specified, collects <script type="dojo/..."> type nodes from children | |
490 | var inherited = options.inherited; | |
491 | if(!inherited){ | |
492 | function findAncestorAttr(node, attr){ | |
493 | return (node.getAttribute && node.getAttribute(attr)) || | |
494 | (node.parentNode && findAncestorAttr(node.parentNode, attr)); | |
495 | } | |
496 | inherited = { | |
497 | dir: findAncestorAttr(root, "dir"), | |
498 | lang: findAncestorAttr(root, "lang"), | |
499 | textDir: findAncestorAttr(root, dataDojoTextDir) | |
500 | }; | |
501 | for(var key in inherited){ | |
502 | if(!inherited[key]){ delete inherited[key]; } | |
503 | } | |
504 | } | |
505 | ||
506 | // Metadata about parent node | |
507 | var parent = { | |
508 | inherited: inherited | |
509 | }; | |
510 | ||
511 | // For collecting <script type="dojo/..."> type nodes (when null, we don't need to collect) | |
512 | var scripts; | |
513 | ||
514 | // when true, only look for <script type="dojo/..."> tags, and don't recurse to children | |
515 | var scriptsOnly; | |
516 | ||
517 | function getEffective(parent){ | |
518 | // summary: | |
519 | // Get effective dir, lang, textDir settings for specified obj | |
520 | // (matching "parent" object structure above), and do caching. | |
521 | // Take care not to return null entries. | |
522 | if(!parent.inherited){ | |
523 | parent.inherited = {}; | |
524 | var node = parent.node, | |
525 | grandparent = getEffective(parent.parent); | |
526 | var inherited = { | |
527 | dir: node.getAttribute("dir") || grandparent.dir, | |
528 | lang: node.getAttribute("lang") || grandparent.lang, | |
529 | textDir: node.getAttribute(dataDojoTextDir) || grandparent.textDir | |
530 | }; | |
531 | for(var key in inherited){ | |
532 | if(inherited[key]){ | |
533 | parent.inherited[key] = inherited[key]; | |
534 | } | |
535 | } | |
536 | } | |
537 | return parent.inherited; | |
538 | } | |
539 | ||
540 | // DFS on DOM tree, collecting nodes with data-dojo-type specified. | |
541 | while(true){ | |
542 | if(!node){ | |
543 | // Finished this level, continue to parent's next sibling | |
544 | if(!parent || !parent.node){ | |
545 | break; | |
546 | } | |
547 | node = parent.node.nextSibling; | |
548 | scriptsOnly = false; | |
549 | parent = parent.parent; | |
550 | scripts = parent.scripts; | |
551 | continue; | |
552 | } | |
553 | ||
554 | if(node.nodeType != 1){ | |
555 | // Text or comment node, skip to next sibling | |
556 | node = node.nextSibling; | |
557 | continue; | |
558 | } | |
559 | ||
560 | if(scripts && node.nodeName.toLowerCase() == "script"){ | |
561 | // Save <script type="dojo/..."> for parent, then continue to next sibling | |
562 | type = node.getAttribute("type"); | |
563 | if(type && /^dojo\/\w/i.test(type)){ | |
564 | scripts.push(node); | |
565 | } | |
566 | node = node.nextSibling; | |
567 | continue; | |
568 | } | |
569 | if(scriptsOnly){ | |
570 | // scriptsOnly flag is set, we have already collected scripts if the parent wants them, so now we shouldn't | |
571 | // continue further analysis of the node and will continue to the next sibling | |
572 | node = node.nextSibling; | |
573 | continue; | |
574 | } | |
575 | ||
576 | // Check for data-dojo-type attribute, fallback to backward compatible dojoType | |
577 | // TODO: Remove dojoType in 2.0 | |
578 | var type = node.getAttribute(dataDojoType) || node.getAttribute(dojoType); | |
579 | ||
580 | // Short circuit for leaf nodes containing nothing [but text] | |
581 | var firstChild = node.firstChild; | |
582 | if(!type && (!firstChild || (firstChild.nodeType == 3 && !firstChild.nextSibling))){ | |
583 | node = node.nextSibling; | |
584 | continue; | |
585 | } | |
586 | ||
587 | // Meta data about current node | |
588 | var current; | |
589 | ||
590 | var ctor = null; | |
591 | if(type){ | |
592 | // If dojoType/data-dojo-type specified, add to output array of nodes to instantiate. | |
593 | var mixinsValue = node.getAttribute(dataDojoMixins), | |
594 | types = mixinsValue ? [type].concat(mixinsValue.split(/\s*,\s*/)) : [type]; | |
595 | ||
596 | // Note: won't find classes declared via dojo/Declaration or any modules that haven't been | |
597 | // loaded yet so use try/catch to avoid throw from require() | |
598 | try{ | |
599 | ctor = getCtor(types); | |
600 | }catch(e){} | |
601 | ||
602 | // If the constructor was not found, check to see if it has modules that can be loaded | |
603 | if(!ctor){ | |
604 | darray.forEach(types, function(t){ | |
605 | if(~t.indexOf('/') && !midsHash[t]){ | |
606 | // If the type looks like a MID and it currently isn't in the array of MIDs to load, add it. | |
607 | midsHash[t] = true; | |
608 | mids[mids.length] = t; | |
609 | } | |
610 | }); | |
611 | } | |
612 | ||
613 | var childScripts = ctor && !ctor.prototype._noScript ? [] : null; // <script> nodes that are parent's children | |
614 | ||
615 | // Setup meta data about this widget node, and save it to list of nodes to instantiate | |
616 | current = { | |
617 | types: types, | |
618 | ctor: ctor, | |
619 | parent: parent, | |
620 | node: node, | |
621 | scripts: childScripts | |
622 | }; | |
623 | current.inherited = getEffective(current); // dir & lang settings for current node, explicit or inherited | |
624 | list.push(current); | |
625 | }else{ | |
626 | // Meta data about this non-widget node | |
627 | current = { | |
628 | node: node, | |
629 | scripts: scripts, | |
630 | parent: parent | |
631 | }; | |
632 | } | |
633 | ||
634 | // Recurse, collecting <script type="dojo/..."> children, and also looking for | |
635 | // descendant nodes with dojoType specified (unless the widget has the stopParser flag). | |
636 | // When finished with children, go to my next sibling. | |
637 | node = firstChild; | |
638 | scripts = childScripts; | |
639 | scriptsOnly = ctor && ctor.prototype.stopParser && !(options.template); | |
640 | parent = current; | |
641 | } | |
642 | ||
643 | var d = new Deferred(); | |
644 | ||
645 | // If there are modules to load then require them in | |
646 | if(mids.length){ | |
647 | // Warn that there are modules being auto-required | |
648 | if(has("dojo-debug-messages")){ | |
649 | console.warn("WARNING: Modules being Auto-Required: " + mids.join(", ")); | |
650 | } | |
651 | require(mids, function(){ | |
652 | // Go through list of widget nodes, filling in missing constructors, and filtering out nodes that shouldn't | |
653 | // be instantiated due to a stopParser flag on an ancestor that we belatedly learned about due to | |
654 | // auto-require of a module like ContentPane. Assumes list is in DFS order. | |
655 | d.resolve(darray.filter(list, function(widget){ | |
656 | if(!widget.ctor){ | |
657 | // Attempt to find the constructor again. Still won't find classes defined via | |
658 | // dijit/Declaration so need to try/catch. | |
659 | try{ | |
660 | widget.ctor = getCtor(widget.types); | |
661 | }catch(e){} | |
662 | } | |
663 | ||
664 | // Get the parent widget | |
665 | var parent = widget.parent; | |
666 | while(parent && !parent.types){ | |
667 | parent = parent.parent; | |
668 | } | |
669 | ||
670 | // Return false if this node should be skipped due to stopParser on an ancestor. | |
671 | // Since list[] is in DFS order, this loop will always set parent.instantiateChildren before | |
672 | // trying to compute widget.instantiate. | |
673 | var proto = widget.ctor && widget.ctor.prototype; | |
674 | widget.instantiateChildren = !(proto && proto.stopParser && !(options.template)); | |
675 | widget.instantiate = !parent || (parent.instantiate && parent.instantiateChildren); | |
676 | return widget.instantiate; | |
677 | })); | |
678 | }); | |
679 | }else{ | |
680 | // There were no modules to load, so just resolve with the parsed nodes. This separate code path is for | |
681 | // efficiency, to avoid running the require() and the callback code above. | |
682 | d.resolve(list); | |
683 | } | |
684 | ||
685 | // Return the promise | |
686 | return d.promise; | |
687 | }, | |
688 | ||
689 | _require: function(/*DOMNode*/ script){ | |
690 | // summary: | |
691 | // Helper for _scanAMD(). Takes a `<script type=dojo/require>bar: "acme/bar", ...</script>` node, | |
692 | // calls require() to load the specified modules and (asynchronously) assign them to the specified global | |
693 | // variables, and returns a Promise for when that operation completes. | |
694 | // | |
695 | // In the example above, it is effectively doing a require(["acme/bar", ...], function(a){ bar = a; }). | |
696 | ||
697 | var hash = djson.fromJson("{" + script.innerHTML + "}"), | |
698 | vars = [], | |
699 | mids = [], | |
700 | d = new Deferred(); | |
701 | ||
702 | for(var name in hash){ | |
703 | vars.push(name); | |
704 | mids.push(hash[name]); | |
705 | } | |
706 | ||
707 | require(mids, function(){ | |
708 | for(var i=0; i<vars.length; i++){ | |
709 | dlang.setObject(vars[i], arguments[i]); | |
710 | } | |
711 | d.resolve(arguments); | |
712 | }); | |
713 | ||
714 | return d.promise; | |
715 | }, | |
716 | ||
717 | _scanAmd: function(root){ | |
718 | // summary: | |
719 | // Scans the DOM for any declarative requires and returns their values. | |
720 | // description: | |
721 | // Looks for `<script type=dojo/require>bar: "acme/bar", ...</script>` node, calls require() to load the | |
722 | // specified modules and (asynchronously) assign them to the specified global variables, | |
723 | // and returns a Promise for when those operations complete. | |
724 | // root: DomNode | |
725 | // The node to base the scan from. | |
726 | ||
727 | // Promise that resolves when all the <script type=dojo/require> nodes have finished loading. | |
728 | var deferred = new Deferred(), | |
729 | promise = deferred.promise; | |
730 | deferred.resolve(true); | |
731 | ||
732 | var self = this; | |
733 | query("script[type='dojo/require']", root).forEach(function(node){ | |
734 | // Fire off require() call for specified modules. Chain this require to fire after | |
735 | // any previous requires complete, so that layers can be loaded before individual module require()'s fire. | |
736 | promise = promise.then(function(){ return self._require(node); }); | |
737 | ||
738 | // Remove from DOM so it isn't seen again | |
739 | node.parentNode.removeChild(node); | |
740 | }); | |
741 | ||
742 | return promise; | |
743 | }, | |
744 | ||
745 | parse: function(rootNode, options){ | |
746 | // summary: | |
747 | // Scan the DOM for class instances, and instantiate them. | |
748 | // description: | |
749 | // Search specified node (or root node) recursively for class instances, | |
750 | // and instantiate them. Searches for either data-dojo-type="Class" or | |
751 | // dojoType="Class" where "Class" is a a fully qualified class name, | |
752 | // like `dijit/form/Button` | |
753 | // | |
754 | // Using `data-dojo-type`: | |
755 | // Attributes using can be mixed into the parameters used to instantiate the | |
756 | // Class by using a `data-dojo-props` attribute on the node being converted. | |
757 | // `data-dojo-props` should be a string attribute to be converted from JSON. | |
758 | // | |
759 | // Using `dojoType`: | |
760 | // Attributes are read from the original domNode and converted to appropriate | |
761 | // types by looking up the Class prototype values. This is the default behavior | |
762 | // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will | |
763 | // go away in Dojo 2.0. | |
764 | // rootNode: DomNode? | |
765 | // A default starting root node from which to start the parsing. Can be | |
766 | // omitted, defaulting to the entire document. If omitted, the `options` | |
767 | // object can be passed in this place. If the `options` object has a | |
768 | // `rootNode` member, that is used. | |
769 | // options: Object? | |
770 | // A hash of options. | |
771 | // | |
772 | // - noStart: Boolean?: | |
773 | // when set will prevent the parser from calling .startup() | |
774 | // when locating the nodes. | |
775 | // - rootNode: DomNode?: | |
776 | // identical to the function's `rootNode` argument, though | |
777 | // allowed to be passed in via this `options object. | |
778 | // - template: Boolean: | |
779 | // If true, ignores ContentPane's stopParser flag and parses contents inside of | |
780 | // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes | |
781 | // nested inside the ContentPane to work. | |
782 | // - inherited: Object: | |
783 | // Hash possibly containing dir and lang settings to be applied to | |
784 | // parsed widgets, unless there's another setting on a sub-node that overrides | |
785 | // - scope: String: | |
786 | // Root for attribute names to search for. If scopeName is dojo, | |
787 | // will search for data-dojo-type (or dojoType). For backwards compatibility | |
788 | // reasons defaults to dojo._scopeName (which is "dojo" except when | |
789 | // multi-version support is used, when it will be something like dojo16, dojo20, etc.) | |
790 | // - propsThis: Object: | |
791 | // If specified, "this" referenced from data-dojo-props will refer to propsThis. | |
792 | // Intended for use from the widgets-in-template feature of `dijit._WidgetsInTemplateMixin` | |
793 | // returns: Mixed | |
794 | // Returns a blended object that is an array of the instantiated objects, but also can include | |
795 | // a promise that is resolved with the instantiated objects. This is done for backwards | |
796 | // compatibility. If the parser auto-requires modules, it will always behave in a promise | |
797 | // fashion and `parser.parse().then(function(instances){...})` should be used. | |
798 | // example: | |
799 | // Parse all widgets on a page: | |
800 | // | parser.parse(); | |
801 | // example: | |
802 | // Parse all classes within the node with id="foo" | |
803 | // | parser.parse(dojo.byId('foo')); | |
804 | // example: | |
805 | // Parse all classes in a page, but do not call .startup() on any | |
806 | // child | |
807 | // | parser.parse({ noStart: true }) | |
808 | // example: | |
809 | // Parse all classes in a node, but do not call .startup() | |
810 | // | parser.parse(someNode, { noStart:true }); | |
811 | // | // or | |
812 | // | parser.parse({ noStart:true, rootNode: someNode }); | |
813 | ||
814 | // determine the root node and options based on the passed arguments. | |
815 | var root; | |
816 | if(!options && rootNode && rootNode.rootNode){ | |
817 | options = rootNode; | |
818 | root = options.rootNode; | |
819 | }else if(rootNode && dlang.isObject(rootNode) && !("nodeType" in rootNode)){ | |
820 | options = rootNode; | |
821 | }else{ | |
822 | root = rootNode; | |
823 | } | |
824 | root = root ? dhtml.byId(root) : dwindow.body(); | |
825 | ||
826 | options = options || {}; | |
827 | ||
828 | var mixin = options.template ? { template: true } : {}, | |
829 | instances = [], | |
830 | self = this; | |
831 | ||
832 | // First scan for any <script type=dojo/require> nodes, and execute. | |
833 | // Then scan for all nodes with data-dojo-type, and load any unloaded modules. | |
834 | // Then build the object instances. Add instances to already existing (but empty) instances[] array, | |
835 | // which may already have been returned to caller. Also, use otherwise to collect and throw any errors | |
836 | // that occur during the parse(). | |
837 | var p = | |
838 | this._scanAmd(root, options).then(function(){ | |
839 | return self.scan(root, options); | |
840 | }).then(function(parsedNodes){ | |
841 | return instances = instances.concat(self._instantiate(parsedNodes, mixin, options)); | |
842 | }).otherwise(function(e){ | |
843 | // TODO Modify to follow better pattern for promise error managment when available | |
844 | console.error("dojo/parser::parse() error", e); | |
845 | throw e; | |
846 | }); | |
847 | ||
848 | // Blend the array with the promise | |
849 | dlang.mixin(instances, p); | |
850 | return instances; | |
851 | } | |
852 | }; | |
853 | ||
854 | if( 1 ){ | |
855 | dojo.parser = parser; | |
856 | } | |
857 | ||
858 | // Register the parser callback. It should be the first callback | |
859 | // after the a11y test. | |
860 | if(config.parseOnLoad){ | |
861 | ready(100, parser, "parse"); | |
862 | } | |
863 | ||
864 | return parser; | |
865 | }); |