]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/parser.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / lib / dojo / parser.js.uncompressed.js
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 });