diff options
Diffstat (limited to 'lib/dojo/parser.js')
-rw-r--r-- | lib/dojo/parser.js | 302 |
1 files changed, 206 insertions, 96 deletions
diff --git a/lib/dojo/parser.js b/lib/dojo/parser.js index 7ae035691..bd52030e0 100644 --- a/lib/dojo/parser.js +++ b/lib/dojo/parser.js @@ -1,5 +1,5 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ @@ -10,14 +10,14 @@ dojo._hasResource["dojo.parser"] = true; dojo.provide("dojo.parser"); dojo.require("dojo.date.stamp"); + new Date("X"); // workaround for #11279, new Date("") == NaN dojo.parser = new function(){ - // summary: The Dom/Widget parsing package + // summary: + // The Dom/Widget parsing package var d = dojo; - this._attrName = d._scopeName + "Type"; - this._query = "[" + this._attrName + "]"; function val2type(/*Object*/ value){ // summary: @@ -42,13 +42,13 @@ dojo.parser = new function(){ case "number": return value.length ? Number(value) : NaN; case "boolean": - // for checked/disabled value might be "" or "checked". interpret as true. + // for checked/disabled value might be "" or "checked". interpret as true. return typeof value == "boolean" ? value : !(value.toLowerCase()=="false"); case "function": if(d.isFunction(value)){ // IE gives us a function, even when we say something like onClick="foo" - // (in which case it gives us an invalid function "function(){ foo }"). - // Therefore, convert to string + // (in which case it gives us an invalid function "function(){ foo }"). + // Therefore, convert to string value=value.toString(); value=d.trim(value.substring(value.indexOf('{')+1, value.length-1)); } @@ -77,7 +77,7 @@ dojo.parser = new function(){ } } - var instanceClasses = { + var dummyClass = {}, instanceClasses = { // map from fully qualified name (like "dijit.Button") to structure like // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} } }; @@ -85,45 +85,70 @@ dojo.parser = new function(){ // 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). - dojo.connect(dojo, "extend", function(){ + // TODO: remove this in 2.0, when we stop caching parameters. + d.connect(d, "extend", function(){ instanceClasses = {}; }); - function getClassInfo(/*String*/ className){ + function getProtoInfo(cls, params){ + // cls: A prototype + // The prototype of the class to check props on + // params: Object + // The parameters object to mix found parameters onto. + for(var name in cls){ + if(name.charAt(0)=="_"){ continue; } // skip internal properties + if(name in dummyClass){ continue; } // skip "constructor" and "toString" + params[name] = val2type(cls[name]); + } + return params; + } + + function getClassInfo(/*String*/ className, /*Boolean*/ skipParamsLookup){ + // summary: + // Maps a widget name string like "dijit.form.Button" to the widget constructor itself, + // and a list of that widget's parameters and their types // className: // fully qualified name (like "dijit.form.Button") // returns: // structure like - // { - // cls: dijit.Button, + // { + // cls: dijit.Button, // params: { label: "string", disabled: "boolean"} // } - if(!instanceClasses[className]){ + var c = instanceClasses[className]; + if(!c){ // get pointer to widget class - var cls = d.getObject(className); + var cls = d.getObject(className), params = null; if(!cls){ return null; } // class not defined [yet] - - var proto = cls.prototype; - - // get table of parameter names & types - var params = {}, dummyClass = {}; - for(var name in proto){ - if(name.charAt(0)=="_"){ continue; } // skip internal properties - if(name in dummyClass){ continue; } // skip "constructor" and "toString" - var defVal = proto[name]; - params[name]=val2type(defVal); + if(!skipParamsLookup){ // from fastpath, we don't need to lookup the attrs on the proto because they are explicit + params = getProtoInfo(cls.prototype, {}) } - - instanceClasses[className] = { cls: cls, params: params }; + c = { cls: cls, params: params }; + + }else if(!skipParamsLookup && !c.params){ + // if we're calling getClassInfo and have a cls proto, but no params info, scan that cls for params now + // and update the pointer in instanceClasses[className]. This happens when a widget appears in another + // widget's template which still uses dojoType, but an instance of the widget appears prior with a data-dojo-type, + // skipping this lookup the first time. + c.params = getProtoInfo(c.cls.prototype, {}); } - return instanceClasses[className]; + + return c; } - this._functionFromScript = function(script){ + this._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 = ""; var suffix = ""; - var argsStr = script.getAttribute("args"); + var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args")); if(argsStr){ d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){ preamble += "var "+part+" = arguments["+idx+"]; "; @@ -137,7 +162,7 @@ dojo.parser = new function(){ }); } return new Function(preamble+script.innerHTML+suffix); - } + }; this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){ // summary: @@ -158,69 +183,106 @@ dojo.parser = new function(){ // exist. // args: Object? // An object used to hold kwArgs for instantiation. - // Supports 'noStart' and inherited. - var thelist = [], dp = dojo.parser; + // See parse.args argument for details. + + var thelist = [], mixin = mixin||{}; args = args||{}; - + + // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0) + var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType" + attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-" + d.forEach(nodes, function(obj){ if(!obj){ return; } - // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.s - var node, type, clsInfo, clazz, scripts; + // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc. + var node, type, clsInfo, clazz, scripts, fastpath; if(obj.node){ // new format of nodes[] array, object w/lots of properties pre-computed for me node = obj.node; type = obj.type; - clsInfo = obj.clsInfo || (type && getClassInfo(type)); + fastpath = obj.fastpath; + clsInfo = obj.clsInfo || (type && getClassInfo(type, fastpath)); clazz = clsInfo && clsInfo.cls; scripts = obj.scripts; }else{ - // old (backwards compatible) format of nodes[] array, simple array of DOMNodes + // old (backwards compatible) format of nodes[] array, simple array of DOMNodes. no fastpath/data-dojo-type support here. node = obj; - type = dp._attrName in mixin ? mixin[dp._attrName] : node.getAttribute(dp._attrName); + type = attrName in mixin ? mixin[attrName] : node.getAttribute(attrName); clsInfo = type && getClassInfo(type); clazz = clsInfo && clsInfo.cls; - scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] : + scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] : d.query("> script[type^='dojo/']", node)); } if(!clsInfo){ throw new Error("Could not load class '" + type); } - // Setup hash to hold parameter settings for this widget. Start with the parameter + // 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 = {}, - attributes = node.attributes; + var params = {}; + if(args.defaults){ // settings for the document itself (or whatever subtree is being parsed) - dojo.mixin(params, args.defaults); + d._mixin(params, args.defaults); } if(obj.inherited){ // settings from dir=rtl or lang=... on a node above this node - dojo.mixin(params, obj.inherited); + d._mixin(params, obj.inherited); } + + // mix things found in data-dojo-props into the params + if(fastpath){ + var extra = node.getAttribute(attrData + "props"); + if(extra && extra.length){ + try{ + extra = d.fromJson.call(args.propsThis, "{" + extra + "}"); + d._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 + "'"); + } + } - // read parameters (ie, attributes) specified on DOMNode - // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"} - for(var name in clsInfo.params){ - var item = name in mixin?{value:mixin[name],specified:true}:attributes.getNamedItem(name); - if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; } - var value = item.value; - // Deal with IE quirks for 'class' and 'style' - switch(name){ - case "class": - value = "className" in mixin?mixin.className:node.className; - break; - case "style": - value = "style" in mixin?mixin.style:(node.style && node.style.cssText); // FIXME: Opera? + // For the benefit of _Templated, check if node has data-dojo-attach-point/data-dojo-attach-event + // and mix those in as though they were parameters + var attachPoint = node.getAttribute(attrData + "attach-point"); + if(attachPoint){ + params.dojoAttachPoint = attachPoint; } - var _type = clsInfo.params[name]; - if(typeof value == "string"){ - params[name] = str2obj(value, _type); - }else{ - params[name] = value; + var attachEvent = node.getAttribute(attrData + "attach-event"); + if(attachEvent){ + params.dojoAttachEvent = attachEvent; + } + dojo.mixin(params, mixin); + }else{ + // FIXME: we need something like "deprecateOnce()" to throw dojo.deprecation for something. + // remove this logic in 2.0 + // read parameters (ie, attributes) specified on DOMNode + + var attributes = node.attributes; + + // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"} + for(var name in clsInfo.params){ + var item = name in mixin ? { value:mixin[name], specified:true } : attributes.getNamedItem(name); + if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; } + var value = item.value; + // Deal with IE quirks for 'class' and 'style' + switch(name){ + case "class": + value = "className" in mixin ? mixin.className : node.className; + break; + case "style": + value = "style" in mixin ? mixin.style : (node.style && node.style.cssText); // FIXME: Opera? + } + var _type = clsInfo.params[name]; + if(typeof value == "string"){ + params[name] = str2obj(value, _type); + }else{ + params[name] = value; + } } } @@ -235,9 +297,10 @@ dojo.parser = new function(){ d.forEach(scripts, function(script){ node.removeChild(script); - var event = script.getAttribute("event"), + // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead + var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")), type = script.getAttribute("type"), - nf = d.parser._functionFromScript(script); + nf = d.parser._functionFromScript(script, attrData); if(event){ if(type == "dojo/connect"){ connects.push({event: event, func: nf}); @@ -255,7 +318,8 @@ dojo.parser = new function(){ thelist.push(instance); // map it to the JS namespace if that makes sense - var jsname = node.getAttribute("jsId"); + // FIXME: in 2.0, drop jsId support. use data-dojo-id instead + var jsname = (node.getAttribute(attrData + "id") || node.getAttribute("jsId")); if(jsname){ d.setObject(jsname, instance); } @@ -279,9 +343,9 @@ dojo.parser = new function(){ // ContentPane is the parent widget (so that the parse doesn't call startup() on the // ContentPane's children) d.forEach(thelist, function(instance){ - if( !args.noStart && instance && - instance.startup && - !instance._started && + if( !args.noStart && instance && + dojo.isFunction(instance.startup) && + !instance._started && (!instance.getParent || !instance.getParent()) ){ instance.startup(); @@ -291,34 +355,57 @@ dojo.parser = new function(){ return thelist; }; - this.parse = function(/*DomNode?*/ rootNode, /* Object? */ args){ + this.parse = function(rootNode, args){ // 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 - // dojoType="qualified.class.name" + // 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 instantitate 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 `args` - // object can be passed in this place. If the `args` object has a + // object can be passed in this place. If the `args` object has a // `rootNode` member, that is used. // - // args: + // args: Object // a kwArgs object passed along to instantiate() - // + // // * noStart: Boolean? // when set will prevent the parser from calling .startup() - // when locating the nodes. + // when locating the nodes. // * rootNode: DomNode? // identical to the function's `rootNode` argument, though - // allowed to be passed in via this `args object. + // allowed to be passed in via this `args 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._Templated` // // example: // Parse all widgets on a page: @@ -326,10 +413,10 @@ dojo.parser = new function(){ // // example: // Parse all classes within the node with id="foo" - // | dojo.parser.parse(dojo.byId(foo)); + // | dojo.parser.parse(dojo.byId('foo')); // // example: - // Parse all classes in a page, but do not call .startup() on any + // Parse all classes in a page, but do not call .startup() on any // child // | dojo.parser.parse({ noStart: true }) // @@ -337,7 +424,7 @@ dojo.parser = new function(){ // Parse all classes in a node, but do not call .startup() // | dojo.parser.parse(someNode, { noStart:true }); // | // or - // | dojo.parser.parse({ noStart:true, rootNode: someNode }); + // | dojo.parser.parse({ noStart:true, rootNode: someNode }); // determine the root node based on the passed arguments. var root; @@ -347,8 +434,12 @@ dojo.parser = new function(){ }else{ root = rootNode; } + root = root ? dojo.byId(root) : dojo.body(); + args = args || {}; + + var attrName = (args.scope || d._scopeName) + "Type", // typically "dojoType" + attrData = "data-" + (args.scope || d._scopeName) + "-"; // typically "data-dojo-" - var attrName = this._attrName; function scan(parent, list){ // summary: // Parent is an Object representing a DOMNode, with or without a dojoType specified. @@ -357,7 +448,7 @@ dojo.parser = new function(){ // parent: Object // Object representing the parent node, like // | { - // | node: DomNode, // scan children of this node + // | node: DomNode, // scan children of this node // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node // | // | // attributes only set if node has dojoType specified @@ -370,6 +461,7 @@ dojo.parser = new function(){ // Effective dir and lang settings on parent node, either set directly or inherited from grandparent var inherited = dojo.clone(parent.inherited); dojo.forEach(["dir", "lang"], function(name){ + // TODO: what if this is a widget and dir/lang are declared in data-dojo-props? var val = parent.node.getAttribute(name); if(val){ inherited[name] = val; @@ -377,20 +469,31 @@ dojo.parser = new function(){ }); // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[]. - var scripts = parent.scripts; + var scripts = parent.clsInfo && !parent.clsInfo.cls.prototype._noScript ? parent.scripts : null; // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively - var recurse = !parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser; + var recurse = (!parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser) || (args && args.template); // scan parent's children looking for dojoType and <script type=dojo/*> for(var child = parent.node.firstChild; child; child = child.nextSibling){ if(child.nodeType == 1){ - var type = recurse && child.getAttribute(attrName); + // FIXME: desupport dojoType in 2.0. use data-dojo-type instead + var type, html5 = recurse && child.getAttribute(attrData + "type"); + if(html5){ + type = html5; + }else{ + // fallback to backward compatible mode, using dojoType. remove in 2.0 + type = recurse && child.getAttribute(attrName); + } + + var fastpath = html5 == type; + if(type){ - // if dojoType specified, add to output array of nodes to instantiate + // if dojoType/data-dojo-type specified, add to output array of nodes to instantiate var params = { "type": type, - clsInfo: getClassInfo(type), // note: won't find classes declared via dojo.Declaration + fastpath: fastpath, + clsInfo: getClassInfo(type, fastpath), // note: won't find classes declared via dojo.Declaration node: child, scripts: [], // <script> nodes that are parent's children inherited: inherited // dir & lang attributes inherited from parent @@ -403,7 +506,7 @@ dojo.parser = new function(){ }else if(scripts && child.nodeName.toLowerCase() == "script"){ // if <script type="dojo/...">, save in scripts[] type = child.getAttribute("type"); - if (type && /^dojo\//i.test(type)) { + if (type && /^dojo\/\w/i.test(type)) { scripts.push(child); } }else if(recurse){ @@ -417,17 +520,24 @@ dojo.parser = new function(){ } } + // Ignore bogus entries in inherited hash like {dir: ""} + var inherited = {}; + if(args && args.inherited){ + for(var key in args.inherited){ + if(args.inherited[key]){ inherited[key] = args.inherited[key]; } + } + } + // Make list of all nodes on page w/dojoType specified var list = []; scan({ - node: root ? dojo.byId(root) : dojo.body(), - inherited: (args && args.inherited) || { - dir: dojo._isBodyLtr() ? "ltr" : "rtl" - } + node: root, + inherited: inherited }, list); // go build the object instances - return this.instantiate(list, null, args); // Array + var mixin = args && args.template ? {template: true} : null; + return this.instantiate(list, mixin, args); // Array }; }(); @@ -435,14 +545,14 @@ dojo.parser = new function(){ //after the a11y test. (function(){ - var parseRunner = function(){ + var parseRunner = function(){ if(dojo.config.parseOnLoad){ - dojo.parser.parse(); + dojo.parser.parse(); } }; // FIXME: need to clobber cross-dependency!! - if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){ + if(dojo.getObject("dijit.wai.onload") === dojo._loaders[0]){ dojo._loaders.splice(1, 0, parseRunner); }else{ dojo._loaders.unshift(parseRunner); |