]> git.wh0rd.org - tt-rss.git/blobdiff - lib/dojo/parser.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / parser.js
index 7ae0356917bf9b2c413e79b8d1088831dfda7594..bd52030e005c4e84e0aa7734165d4e26733c9e48 100644 (file)
@@ -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);