Plato on Github
Report Home
dojo/parser.js
Maintainability
56.80
Lines of code
926
Difficulty
89.29
Estimated Errors
7.22
Function weight
By Complexity
By SLOC
define([ "require", "./_base/kernel", "./_base/lang", "./_base/array", "./_base/config", "./dom", "./_base/window", "./_base/url", "./aspect", "./promise/all", "./date/stamp", "./Deferred", "./has", "./query", "./on", "./ready" ], function(require, dojo, dlang, darray, config, dom, dwindow, _Url, aspect, all, dates, Deferred, has, query, don, ready){ // module: // dojo/parser new Date("X"); // workaround for #11279, new Date("") == NaN // data-dojo-props etc. is not restricted to JSON, it can be any javascript function myEval(text){ return eval("(" + text + ")"); } // Widgets like BorderContainer add properties to _Widget via dojo.extend(). // If BorderContainer is loaded after _Widget's parameter list has been cached, // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget). var extendCnt = 0; aspect.after(dlang, "extend", function(){ extendCnt++; }, true); function getNameMap(ctor){ // summary: // Returns map from lowercase name to attribute name in class, ex: {onclick: "onClick"} var map = ctor._nameCaseMap, proto = ctor.prototype; // Create the map if it's undefined. // Refresh the map if a superclass was possibly extended with new methods since the map was created. if(!map || map._extendCnt < extendCnt){ map = ctor._nameCaseMap = {}; for(var name in proto){ if(name.charAt(0) === "_"){ continue; } // skip internal properties map[name.toLowerCase()] = name; } map._extendCnt = extendCnt; } return map; } function getCtor(/*String[]*/ types, /*Function?*/ contextRequire){ // summary: // Retrieves a constructor. If the types array contains more than one class/MID then the // subsequent classes will be mixed into the first class and a unique constructor will be // returned for that array. if(!contextRequire){ contextRequire = require; } // Map from widget name or list of widget names(ex: "dijit/form/Button,acme/MyMixin") to a constructor. // Keep separate map for each requireContext to avoid false matches (ex: "./Foo" can mean different things // depending on context.) var ctorMap = contextRequire._dojoParserCtorMap || (contextRequire._dojoParserCtorMap = {}); var ts = types.join(); if(!ctorMap[ts]){ var mixins = []; for(var i = 0, l = types.length; i < l; i++){ var t = types[i]; // TODO: Consider swapping getObject and require in the future mixins[mixins.length] = (ctorMap[t] = ctorMap[t] || (dlang.getObject(t) || (~t.indexOf('/') && contextRequire(t)))); } var ctor = mixins.shift(); ctorMap[ts] = mixins.length ? (ctor.createSubclass ? ctor.createSubclass(mixins) : ctor.extend.apply(ctor, mixins)) : ctor; } return ctorMap[ts]; } var parser = { // summary: // The Dom/Widget parsing package _clearCache: function(){ // summary: // Clear cached data. Used mainly for benchmarking. extendCnt++; _ctorMap = {}; }, _functionFromScript: function(script, attrData){ // summary: // Convert a `<script type="dojo/method" args="a, b, c"> ... </script>` // into a function // script: DOMNode // The `<script>` DOMNode // attrData: String // For HTML5 compliance, searches for attrData + "args" (typically // "data-dojo-args") instead of "args" var preamble = "", suffix = "", argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args")), withStr = script.getAttribute("with"); // Convert any arguments supplied in script tag into an array to be passed to the var fnArgs = (argsStr || "").split(/\s*,\s*/); if(withStr && withStr.length){ darray.forEach(withStr.split(/\s*,\s*/), function(part){ preamble += "with(" + part + "){"; suffix += "}"; }); } return new Function(fnArgs, preamble + script.innerHTML + suffix); }, instantiate: function(nodes, mixin, options){ // summary: // Takes array of nodes, and turns them into class instances and // potentially calls a startup method to allow them to connect with // any children. // nodes: Array // Array of DOM nodes // mixin: Object? // An object that will be mixed in with each node in the array. // Values in the mixin will override values in the node, if they // exist. // options: Object? // An object used to hold kwArgs for instantiation. // See parse.options argument for details. // returns: // Array of instances. mixin = mixin || {}; options = options || {}; var dojoType = (options.scope || dojo._scopeName) + "Type", // typically "dojoType" attrData = "data-" + (options.scope || dojo._scopeName) + "-", // typically "data-dojo-" dataDojoType = attrData + "type", // typically "data-dojo-type" dataDojoMixins = attrData + "mixins"; // typically "data-dojo-mixins" var list = []; darray.forEach(nodes, function(node){ var type = dojoType in mixin ? mixin[dojoType] : node.getAttribute(dataDojoType) || node.getAttribute(dojoType); if(type){ var mixinsValue = node.getAttribute(dataDojoMixins), types = mixinsValue ? [type].concat(mixinsValue.split(/\s*,\s*/)) : [type]; list.push({ node: node, types: types }); } }); // Instantiate the nodes and return the list of instances. return this._instantiate(list, mixin, options); }, _instantiate: function(nodes, mixin, options, returnPromise){ // summary: // Takes array of objects representing nodes, and turns them into class instances and // potentially calls a startup method to allow them to connect with // any children. // nodes: Array // Array of objects like // | { // | ctor: Function (may be null) // | types: ["dijit/form/Button", "acme/MyMixin"] (used if ctor not specified) // | node: DOMNode, // | scripts: [ ... ], // array of <script type="dojo/..."> children of node // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc. // | } // mixin: Object // An object that will be mixed in with each node in the array. // Values in the mixin will override values in the node, if they // exist. // options: Object // An options object used to hold kwArgs for instantiation. // See parse.options argument for details. // returnPromise: Boolean // Return a Promise rather than the instance; supports asynchronous widget creation. // returns: // Array of instances, or if returnPromise is true, a promise for array of instances // that resolves when instances have finished initializing. // Call widget constructors. Some may be asynchronous and return promises. var thelist = darray.map(nodes, function(obj){ var ctor = obj.ctor || getCtor(obj.types, options.contextRequire); // If we still haven't resolved a ctor, it is fatal now if(!ctor){ throw new Error("Unable to resolve constructor for: '" + obj.types.join() + "'"); } return this.construct(ctor, obj.node, mixin, options, obj.scripts, obj.inherited); }, this); // After all widget construction finishes, call startup on each top level instance if it makes sense (as for // widgets). Parent widgets will recursively call startup on their (non-top level) children function onConstruct(thelist){ if(!mixin._started && !options.noStart){ darray.forEach(thelist, function(instance){ if(typeof instance.startup === "function" && !instance._started){ instance.startup(); } }); } return thelist; } if(returnPromise){ return all(thelist).then(onConstruct); }else{ // Back-compat path, remove for 2.0 return onConstruct(thelist); } }, construct: function(ctor, node, mixin, options, scripts, inherited){ // summary: // Calls new ctor(params, node), where params is the hash of parameters specified on the node, // excluding data-dojo-type and data-dojo-mixins. Does not call startup(). // ctor: Function // Widget constructor. // node: DOMNode // This node will be replaced/attached to by the widget. It also specifies the arguments to pass to ctor. // mixin: Object? // Attributes in this object will be passed as parameters to ctor, // overriding attributes specified on the node. // options: Object? // An options object used to hold kwArgs for instantiation. See parse.options argument for details. // scripts: DomNode[]? // Array of `<script type="dojo/*">` DOMNodes. If not specified, will search for `<script>` tags inside node. // inherited: Object? // Settings from dir=rtl or lang=... on a node above this node. Overrides options.inherited. // returns: // Instance or Promise for the instance, if markupFactory() itself returned a promise var proto = ctor && ctor.prototype; options = options || {}; // Setup hash to hold parameter settings for this widget. Start with the parameter // settings inherited from ancestors ("dir" and "lang"). // Inherited setting may later be overridden by explicit settings on node itself. var params = {}; if(options.defaults){ // settings for the document itself (or whatever subtree is being parsed) dlang.mixin(params, options.defaults); } if(inherited){ // settings from dir=rtl or lang=... on a node above this node dlang.mixin(params, inherited); } // Get list of attributes explicitly listed in the markup var attributes; if(has("dom-attributes-explicit")){ // Standard path to get list of user specified attributes attributes = node.attributes; }else if(has("dom-attributes-specified-flag")){ // Special processing needed for IE8, to skip a few faux values in attributes[] attributes = darray.filter(node.attributes, function(a){ return a.specified; }); }else{ // Special path for IE6-7, avoid (sometimes >100) bogus entries in node.attributes var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false), attrs = clone.outerHTML.replace(/=[^\s"']+|="[^"]*"|='[^']*'/g, "").replace(/^\s*<[a-zA-Z0-9]*\s*/, "").replace(/\s*>.*$/, ""); attributes = darray.map(attrs.split(/\s+/), function(name){ var lcName = name.toLowerCase(); return { name: name, // getAttribute() doesn't work for button.value, returns innerHTML of button. // but getAttributeNode().value doesn't work for the form.encType or li.value value: (node.nodeName == "LI" && name == "value") || lcName == "enctype" ? node.getAttribute(lcName) : node.getAttributeNode(lcName).value }; }); } // Hash to convert scoped attribute name (ex: data-dojo17-params) to something friendly (ex: data-dojo-params) // TODO: remove scope for 2.0 var scope = options.scope || dojo._scopeName, attrData = "data-" + scope + "-", // typically "data-dojo-" hash = {}; if(scope !== "dojo"){ hash[attrData + "props"] = "data-dojo-props"; hash[attrData + "type"] = "data-dojo-type"; hash[attrData + "mixins"] = "data-dojo-mixins"; hash[scope + "type"] = "dojotype"; hash[attrData + "id"] = "data-dojo-id"; } // Read in attributes and process them, including data-dojo-props, data-dojo-type, // dojoAttachPoint, etc., as well as normal foo=bar attributes. var i = 0, item, funcAttrs = [], jsname, extra; while(item = attributes[i++]){ var name = item.name, lcName = name.toLowerCase(), value = item.value; switch(hash[lcName] || lcName){ // Already processed, just ignore case "data-dojo-type": case "dojotype": case "data-dojo-mixins": break; // Data-dojo-props. Save for later to make sure it overrides direct foo=bar settings case "data-dojo-props": extra = value; break; // data-dojo-id or jsId. TODO: drop jsId in 2.0 case "data-dojo-id": case "jsid": jsname = value; break; // For the benefit of _Templated case "data-dojo-attach-point": case "dojoattachpoint": params.dojoAttachPoint = value; break; case "data-dojo-attach-event": case "dojoattachevent": params.dojoAttachEvent = value; break; // Special parameter handling needed for IE case "class": params["class"] = node.className; break; case "style": params["style"] = node.style && node.style.cssText; break; default: // Normal attribute, ex: value="123" // Find attribute in widget corresponding to specified name. // May involve case conversion, ex: onclick --> onClick if(!(name in proto)){ var map = getNameMap(ctor); name = map[lcName] || name; } // Set params[name] to value, doing type conversion if(name in proto){ switch(typeof proto[name]){ case "string": params[name] = value; break; case "number": params[name] = value.length ? Number(value) : NaN; break; case "boolean": // for checked/disabled value might be "" or "checked". interpret as true. params[name] = value.toLowerCase() != "false"; break; case "function": if(value === "" || value.search(/[^\w\.]+/i) != -1){ // The user has specified some text for a function like "return x+5" params[name] = new Function(value); }else{ // The user has specified the name of a global function like "myOnClick" // or a single word function "return" params[name] = dlang.getObject(value, false) || new Function(value); } funcAttrs.push(name); // prevent "double connect", see #15026 break; default: var pVal = proto[name]; params[name] = (pVal && "length" in pVal) ? (value ? value.split(/\s*,\s*/) : []) : // array (pVal instanceof Date) ? (value == "" ? new Date("") : // the NaN of dates value == "now" ? new Date() : // current date dates.fromISOString(value)) : (pVal instanceof _Url) ? (dojo.baseUrl + value) : myEval(value); } }else{ params[name] = value; } } } // Remove function attributes from DOMNode to prevent "double connect" problem, see #15026. // Do this as a separate loop since attributes[] is often a live collection (depends on the browser though). for(var j = 0; j < funcAttrs.length; j++){ var lcfname = funcAttrs[j].toLowerCase(); node.removeAttribute(lcfname); node[lcfname] = null; } // Mix things found in data-dojo-props into the params, overriding any direct settings if(extra){ try{ extra = myEval.call(options.propsThis, "{" + extra + "}"); dlang.mixin(params, extra); }catch(e){ // give the user a pointer to their invalid parameters. FIXME: can we kill this in production? throw new Error(e.toString() + " in data-dojo-props='" + extra + "'"); } } // Any parameters specified in "mixin" override everything else. dlang.mixin(params, mixin); // Get <script> nodes associated with this widget, if they weren't specified explicitly if(!scripts){ scripts = (ctor && (ctor._noScript || proto._noScript) ? [] : query("> script[type^='dojo/']", node)); } // Process <script type="dojo/*"> script tags // <script type="dojo/method" data-dojo-event="foo"> tags are added to params, and passed to // the widget on instantiation. // <script type="dojo/method"> tags (with no event) are executed after instantiation // <script type="dojo/connect" data-dojo-event="foo"> tags are dojo.connected after instantiation, // and likewise with <script type="dojo/aspect" data-dojo-method="foo"> // <script type="dojo/watch" data-dojo-prop="foo"> tags are dojo.watch after instantiation // <script type="dojo/on" data-dojo-event="foo"> tags are dojo.on after instantiation // note: dojo/* script tags cannot exist in self closing widgets, like <input /> var aspects = [], // aspects to connect after instantiation calls = [], // functions to call after instantiation watches = [], // functions to watch after instantiation ons = []; // functions to on after instantiation if(scripts){ for(i = 0; i < scripts.length; i++){ var script = scripts[i]; node.removeChild(script); // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")), prop = script.getAttribute(attrData + "prop"), method = script.getAttribute(attrData + "method"), advice = script.getAttribute(attrData + "advice"), scriptType = script.getAttribute("type"), nf = this._functionFromScript(script, attrData); if(event){ if(scriptType == "dojo/connect"){ aspects.push({ method: event, func: nf }); }else if(scriptType == "dojo/on"){ ons.push({ event: event, func: nf }); }else{ // <script type="dojo/method" data-dojo-event="foo"> // TODO for 2.0: use data-dojo-method="foo" instead (also affects dijit/Declaration) params[event] = nf; } }else if(scriptType == "dojo/aspect"){ aspects.push({ method: method, advice: advice, func: nf }); }else if(scriptType == "dojo/watch"){ watches.push({ prop: prop, func: nf }); }else{ calls.push(nf); } } } // create the instance var markupFactory = ctor.markupFactory || proto.markupFactory; var instance = markupFactory ? markupFactory(params, node, ctor) : new ctor(params, node); function onInstantiate(instance){ // map it to the JS namespace if that makes sense if(jsname){ dlang.setObject(jsname, instance); } // process connections and startup functions for(i = 0; i < aspects.length; i++){ aspect[aspects[i].advice || "after"](instance, aspects[i].method, dlang.hitch(instance, aspects[i].func), true); } for(i = 0; i < calls.length; i++){ calls[i].call(instance); } for(i = 0; i < watches.length; i++){ instance.watch(watches[i].prop, watches[i].func); } for(i = 0; i < ons.length; i++){ don(instance, ons[i].event, ons[i].func); } return instance; } if(instance.then){ return instance.then(onInstantiate); }else{ return onInstantiate(instance); } }, scan: function(root, options){ // summary: // Scan a DOM tree and return an array of objects representing the DOMNodes // that need to be turned into widgets. // description: // Search specified node (or document root node) recursively for class instances // and return an array of objects that represent potential widgets to be // instantiated. Searches for either data-dojo-type="MID" or dojoType="MID" where // "MID" is a module ID like "dijit/form/Button" or a fully qualified Class name // like "dijit/form/Button". If the MID is not currently available, scan will // attempt to require() in the module. // // See parser.parse() for details of markup. // root: DomNode? // A default starting root node from which to start the parsing. Can be // omitted, defaulting to the entire document. If omitted, the `options` // object can be passed in this place. If the `options` object has a // `rootNode` member, that is used. // options: Object // a kwArgs options object, see parse() for details // // returns: Promise // A promise that is resolved with the nodes that have been parsed. var list = [], // Output List mids = [], // An array of modules that are not yet loaded midsHash = {}; // Used to keep the mids array unique var dojoType = (options.scope || dojo._scopeName) + "Type", // typically "dojoType" attrData = "data-" + (options.scope || dojo._scopeName) + "-", // typically "data-dojo-" dataDojoType = attrData + "type", // typically "data-dojo-type" dataDojoTextDir = attrData + "textdir", // typically "data-dojo-textdir" dataDojoMixins = attrData + "mixins"; // typically "data-dojo-mixins" // Info on DOMNode currently being processed var node = root.firstChild; // Info on parent of DOMNode currently being processed // - inherited: dir, lang, and textDir setting of parent, or inherited by parent // - parent: pointer to identical structure for my parent (or null if no parent) // - scripts: if specified, collects <script type="dojo/..."> type nodes from children var inherited = options.inherited; if(!inherited){ function findAncestorAttr(node, attr){ return (node.getAttribute && node.getAttribute(attr)) || (node.parentNode && findAncestorAttr(node.parentNode, attr)); } inherited = { dir: findAncestorAttr(root, "dir"), lang: findAncestorAttr(root, "lang"), textDir: findAncestorAttr(root, dataDojoTextDir) }; for(var key in inherited){ if(!inherited[key]){ delete inherited[key]; } } } // Metadata about parent node var parent = { inherited: inherited }; // For collecting <script type="dojo/..."> type nodes (when null, we don't need to collect) var scripts; // when true, only look for <script type="dojo/..."> tags, and don't recurse to children var scriptsOnly; function getEffective(parent){ // summary: // Get effective dir, lang, textDir settings for specified obj // (matching "parent" object structure above), and do caching. // Take care not to return null entries. if(!parent.inherited){ parent.inherited = {}; var node = parent.node, grandparent = getEffective(parent.parent); var inherited = { dir: node.getAttribute("dir") || grandparent.dir, lang: node.getAttribute("lang") || grandparent.lang, textDir: node.getAttribute(dataDojoTextDir) || grandparent.textDir }; for(var key in inherited){ if(inherited[key]){ parent.inherited[key] = inherited[key]; } } } return parent.inherited; } // DFS on DOM tree, collecting nodes with data-dojo-type specified. while(true){ if(!node){ // Finished this level, continue to parent's next sibling if(!parent || !parent.node){ break; } node = parent.node.nextSibling; scriptsOnly = false; parent = parent.parent; scripts = parent.scripts; continue; } if(node.nodeType != 1){ // Text or comment node, skip to next sibling node = node.nextSibling; continue; } if(scripts && node.nodeName.toLowerCase() == "script"){ // Save <script type="dojo/..."> for parent, then continue to next sibling type = node.getAttribute("type"); if(type && /^dojo\/\w/i.test(type)){ scripts.push(node); } node = node.nextSibling; continue; } if(scriptsOnly){ // scriptsOnly flag is set, we have already collected scripts if the parent wants them, so now we shouldn't // continue further analysis of the node and will continue to the next sibling node = node.nextSibling; continue; } // Check for data-dojo-type attribute, fallback to backward compatible dojoType // TODO: Remove dojoType in 2.0 var type = node.getAttribute(dataDojoType) || node.getAttribute(dojoType); // Short circuit for leaf nodes containing nothing [but text] var firstChild = node.firstChild; if(!type && (!firstChild || (firstChild.nodeType == 3 && !firstChild.nextSibling))){ node = node.nextSibling; continue; } // Meta data about current node var current; var ctor = null; if(type){ // If dojoType/data-dojo-type specified, add to output array of nodes to instantiate. var mixinsValue = node.getAttribute(dataDojoMixins), types = mixinsValue ? [type].concat(mixinsValue.split(/\s*,\s*/)) : [type]; // Note: won't find classes declared via dojo/Declaration or any modules that haven't been // loaded yet so use try/catch to avoid throw from require() try{ ctor = getCtor(types, options.contextRequire); }catch(e){} // If the constructor was not found, check to see if it has modules that can be loaded if(!ctor){ darray.forEach(types, function(t){ if(~t.indexOf('/') && !midsHash[t]){ // If the type looks like a MID and it currently isn't in the array of MIDs to load, add it. midsHash[t] = true; mids[mids.length] = t; } }); } var childScripts = ctor && !ctor.prototype._noScript ? [] : null; // <script> nodes that are parent's children // Setup meta data about this widget node, and save it to list of nodes to instantiate current = { types: types, ctor: ctor, parent: parent, node: node, scripts: childScripts }; current.inherited = getEffective(current); // dir & lang settings for current node, explicit or inherited list.push(current); }else{ // Meta data about this non-widget node current = { node: node, scripts: scripts, parent: parent }; } // Recurse, collecting <script type="dojo/..."> children, and also looking for // descendant nodes with dojoType specified (unless the widget has the stopParser flag). // When finished with children, go to my next sibling. scripts = childScripts; scriptsOnly = node.stopParser || (ctor && ctor.prototype.stopParser && !(options.template)); parent = current; node = firstChild; } var d = new Deferred(); // If there are modules to load then require them in if(mids.length){ // Warn that there are modules being auto-required if(has("dojo-debug-messages")){ console.warn("WARNING: Modules being Auto-Required: " + mids.join(", ")); } var r = options.contextRequire || require; r(mids, function(){ // Go through list of widget nodes, filling in missing constructors, and filtering out nodes that shouldn't // be instantiated due to a stopParser flag on an ancestor that we belatedly learned about due to // auto-require of a module like ContentPane. Assumes list is in DFS order. d.resolve(darray.filter(list, function(widget){ if(!widget.ctor){ // Attempt to find the constructor again. Still won't find classes defined via // dijit/Declaration so need to try/catch. try{ widget.ctor = getCtor(widget.types, options.contextRequire); }catch(e){} } // Get the parent widget var parent = widget.parent; while(parent && !parent.types){ parent = parent.parent; } // Return false if this node should be skipped due to stopParser on an ancestor. // Since list[] is in DFS order, this loop will always set parent.instantiateChildren before // trying to compute widget.instantiate. var proto = widget.ctor && widget.ctor.prototype; widget.instantiateChildren = !(proto && proto.stopParser && !(options.template)); widget.instantiate = !parent || (parent.instantiate && parent.instantiateChildren); return widget.instantiate; })); }); }else{ // There were no modules to load, so just resolve with the parsed nodes. This separate code path is for // efficiency, to avoid running the require() and the callback code above. d.resolve(list); } // Return the promise return d.promise; }, _require: function(/*DOMNode*/ script, /*Object?*/ options){ // summary: // Helper for _scanAMD(). Takes a `<script type=dojo/require>bar: "acme/bar", ...</script>` node, // calls require() to load the specified modules and (asynchronously) assign them to the specified global // variables, and returns a Promise for when that operation completes. // // In the example above, it is effectively doing a require(["acme/bar", ...], function(a){ bar = a; }). var hash = myEval("{" + script.innerHTML + "}"), // can't use dojo/json::parse() because maybe no quotes vars = [], mids = [], d = new Deferred(); var contextRequire = (options && options.contextRequire) || require; for(var name in hash){ vars.push(name); mids.push(hash[name]); } contextRequire(mids, function(){ for(var i = 0; i < vars.length; i++){ dlang.setObject(vars[i], arguments[i]); } d.resolve(arguments); }); return d.promise; }, _scanAmd: function(root, options){ // summary: // Scans the DOM for any declarative requires and returns their values. // description: // Looks for `<script type=dojo/require>bar: "acme/bar", ...</script>` node, calls require() to load the // specified modules and (asynchronously) assign them to the specified global variables, // and returns a Promise for when those operations complete. // root: DomNode // The node to base the scan from. // options: Object? // a kwArgs options object, see parse() for details // Promise that resolves when all the <script type=dojo/require> nodes have finished loading. var deferred = new Deferred(), promise = deferred.promise; deferred.resolve(true); var self = this; query("script[type='dojo/require']", root).forEach(function(node){ // Fire off require() call for specified modules. Chain this require to fire after // any previous requires complete, so that layers can be loaded before individual module require()'s fire. promise = promise.then(function(){ return self._require(node, options); }); // Remove from DOM so it isn't seen again node.parentNode.removeChild(node); }); return promise; }, parse: function(rootNode, options){ // summary: // Scan the DOM for class instances, and instantiate them. // description: // Search specified node (or root node) recursively for class instances, // and instantiate them. Searches for either data-dojo-type="Class" or // dojoType="Class" where "Class" is a a fully qualified class name, // like `dijit/form/Button` // // Using `data-dojo-type`: // Attributes using can be mixed into the parameters used to instantiate the // Class by using a `data-dojo-props` attribute on the node being converted. // `data-dojo-props` should be a string attribute to be converted from JSON. // // Using `dojoType`: // Attributes are read from the original domNode and converted to appropriate // types by looking up the Class prototype values. This is the default behavior // from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will // go away in Dojo 2.0. // rootNode: DomNode? // A default starting root node from which to start the parsing. Can be // omitted, defaulting to the entire document. If omitted, the `options` // object can be passed in this place. If the `options` object has a // `rootNode` member, that is used. // options: Object? // A hash of options. // // - noStart: Boolean?: // when set will prevent the parser from calling .startup() // when locating the nodes. // - rootNode: DomNode?: // identical to the function's `rootNode` argument, though // allowed to be passed in via this `options object. // - template: Boolean: // If true, ignores ContentPane's stopParser flag and parses contents inside of // a ContentPane inside of a template. This allows dojoAttachPoint on widgets/nodes // nested inside the ContentPane to work. // - inherited: Object: // Hash possibly containing dir and lang settings to be applied to // parsed widgets, unless there's another setting on a sub-node that overrides // - scope: String: // Root for attribute names to search for. If scopeName is dojo, // will search for data-dojo-type (or dojoType). For backwards compatibility // reasons defaults to dojo._scopeName (which is "dojo" except when // multi-version support is used, when it will be something like dojo16, dojo20, etc.) // - propsThis: Object: // If specified, "this" referenced from data-dojo-props will refer to propsThis. // Intended for use from the widgets-in-template feature of `dijit._WidgetsInTemplateMixin` // - contextRequire: Function: // If specified, this require is utilised for looking resolving modules instead of the // `dojo/parser` context `require()`. Intended for use from the widgets-in-template feature of // `dijit._WidgetsInTemplateMixin`. // returns: Mixed // Returns a blended object that is an array of the instantiated objects, but also can include // a promise that is resolved with the instantiated objects. This is done for backwards // compatibility. If the parser auto-requires modules, it will always behave in a promise // fashion and `parser.parse().then(function(instances){...})` should be used. // example: // Parse all widgets on a page: // | parser.parse(); // example: // Parse all classes within the node with id="foo" // | parser.parse(dojo.byId('foo')); // example: // Parse all classes in a page, but do not call .startup() on any // child // | parser.parse({ noStart: true }) // example: // Parse all classes in a node, but do not call .startup() // | parser.parse(someNode, { noStart:true }); // | // or // | parser.parse({ noStart:true, rootNode: someNode }); // determine the root node and options based on the passed arguments. var root; if(!options && rootNode && rootNode.rootNode){ options = rootNode; root = options.rootNode; }else if(rootNode && dlang.isObject(rootNode) && !("nodeType" in rootNode)){ options = rootNode; }else{ root = rootNode; } root = root ? dom.byId(root) : dwindow.body(); options = options || {}; var mixin = options.template ? { template: true } : {}, instances = [], self = this; // First scan for any <script type=dojo/require> nodes, and execute. // Then scan for all nodes with data-dojo-type, and load any unloaded modules. // Then build the object instances. Add instances to already existing (but empty) instances[] array, // which may already have been returned to caller. Also, use otherwise to collect and throw any errors // that occur during the parse(). var p = this._scanAmd(root, options).then(function(){ return self.scan(root, options); }).then(function(parsedNodes){ return self._instantiate(parsedNodes, mixin, options, true); }).then(function(_instances){ // Copy the instances into the instances[] array we declared above, and are accessing as // our return value. return instances = instances.concat(_instances); }).otherwise(function(e){ // TODO Modify to follow better pattern for promise error management when available console.error("dojo/parser::parse() error", e); throw e; }); // Blend the array with the promise dlang.mixin(instances, p); return instances; } }; if(has("extend-dojo")){ dojo.parser = parser; } // Register the parser callback. It should be the first callback // after the a11y test. if(config.parseOnLoad){ ready(100, parser, "parse"); } return parser; });