source: Dev/trunk/src/client/dojo/parser.js @ 524

Last change on this file since 524 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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