diff options
Diffstat (limited to 'lib/dojo/parser.js')
-rw-r--r-- | lib/dojo/parser.js | 698 |
1 files changed, 439 insertions, 259 deletions
diff --git a/lib/dojo/parser.js b/lib/dojo/parser.js index 245528328..7ae035691 100644 --- a/lib/dojo/parser.js +++ b/lib/dojo/parser.js @@ -5,268 +5,448 @@ */ -if(!dojo._hasResource["dojo.parser"]){ -dojo._hasResource["dojo.parser"]=true; +if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.parser"] = true; dojo.provide("dojo.parser"); dojo.require("dojo.date.stamp"); -new Date("X"); -dojo.parser=new function(){ -var d=dojo; -this._attrName=d._scopeName+"Type"; -this._query="["+this._attrName+"]"; -function _1(_2){ -if(d.isString(_2)){ -return "string"; -} -if(typeof _2=="number"){ -return "number"; -} -if(typeof _2=="boolean"){ -return "boolean"; -} -if(d.isFunction(_2)){ -return "function"; -} -if(d.isArray(_2)){ -return "array"; -} -if(_2 instanceof Date){ -return "date"; -} -if(_2 instanceof d._Url){ -return "url"; -} -return "object"; -}; -function _3(_4,_5){ -switch(_5){ -case "string": -return _4; -case "number": -return _4.length?Number(_4):NaN; -case "boolean": -return typeof _4=="boolean"?_4:!(_4.toLowerCase()=="false"); -case "function": -if(d.isFunction(_4)){ -_4=_4.toString(); -_4=d.trim(_4.substring(_4.indexOf("{")+1,_4.length-1)); -} -try{ -if(_4===""||_4.search(/[^\w\.]+/i)!=-1){ -return new Function(_4); -}else{ -return d.getObject(_4,false)||new Function(_4); -} -} -catch(e){ -return new Function(); -} -case "array": -return _4?_4.split(/\s*,\s*/):[]; -case "date": -switch(_4){ -case "": -return new Date(""); -case "now": -return new Date(); -default: -return d.date.stamp.fromISOString(_4); -} -case "url": -return d.baseUrl+_4; -default: -return d.fromJson(_4); -} -}; -var _6={}; -dojo.connect(dojo,"extend",function(){ -_6={}; -}); -function _7(_8){ -if(!_6[_8]){ -var _9=d.getObject(_8); -if(!_9){ -return null; -} -var _a=_9.prototype; -var _b={},_c={}; -for(var _d in _a){ -if(_d.charAt(0)=="_"){ -continue; -} -if(_d in _c){ -continue; -} -var _e=_a[_d]; -_b[_d]=_1(_e); -} -_6[_8]={cls:_9,params:_b}; -} -return _6[_8]; -}; -this._functionFromScript=function(_f){ -var _10=""; -var _11=""; -var _12=_f.getAttribute("args"); -if(_12){ -d.forEach(_12.split(/\s*,\s*/),function(_13,idx){ -_10+="var "+_13+" = arguments["+idx+"]; "; -}); -} -var _14=_f.getAttribute("with"); -if(_14&&_14.length){ -d.forEach(_14.split(/\s*,\s*/),function(_15){ -_10+="with("+_15+"){"; -_11+="}"; -}); -} -return new Function(_10+_f.innerHTML+_11); -}; -this.instantiate=function(_16,_17,_18){ -var _19=[],dp=dojo.parser; -_17=_17||{}; -_18=_18||{}; -d.forEach(_16,function(obj){ -if(!obj){ -return; -} -var _1a,_1b,_1c,_1d,_1e; -if(obj.node){ -_1a=obj.node; -_1b=obj.type; -_1c=obj.clsInfo||(_1b&&_7(_1b)); -_1d=_1c&&_1c.cls; -_1e=obj.scripts; -}else{ -_1a=obj; -_1b=dp._attrName in _17?_17[dp._attrName]:_1a.getAttribute(dp._attrName); -_1c=_1b&&_7(_1b); -_1d=_1c&&_1c.cls; -_1e=(_1d&&(_1d._noScript||_1d.prototype._noScript)?[]:d.query("> script[type^='dojo/']",_1a)); -} -if(!_1c){ -throw new Error("Could not load class '"+_1b); -} -var _1f={},_20=_1a.attributes; -if(_18.defaults){ -dojo.mixin(_1f,_18.defaults); -} -if(obj.inherited){ -dojo.mixin(_1f,obj.inherited); -} -for(var _21 in _1c.params){ -var _22=_21 in _17?{value:_17[_21],specified:true}:_20.getNamedItem(_21); -if(!_22||(!_22.specified&&(!dojo.isIE||_21.toLowerCase()!="value"))){ -continue; -} -var _23=_22.value; -switch(_21){ -case "class": -_23="className" in _17?_17.className:_1a.className; -break; -case "style": -_23="style" in _17?_17.style:(_1a.style&&_1a.style.cssText); -} -var _24=_1c.params[_21]; -if(typeof _23=="string"){ -_1f[_21]=_3(_23,_24); -}else{ -_1f[_21]=_23; -} -} -var _25=[],_26=[]; -d.forEach(_1e,function(_27){ -_1a.removeChild(_27); -var _28=_27.getAttribute("event"),_1b=_27.getAttribute("type"),nf=d.parser._functionFromScript(_27); -if(_28){ -if(_1b=="dojo/connect"){ -_25.push({event:_28,func:nf}); -}else{ -_1f[_28]=nf; -} -}else{ -_26.push(nf); -} -}); -var _29=_1d.markupFactory||_1d.prototype&&_1d.prototype.markupFactory; -var _2a=_29?_29(_1f,_1a,_1d):new _1d(_1f,_1a); -_19.push(_2a); -var _2b=_1a.getAttribute("jsId"); -if(_2b){ -d.setObject(_2b,_2a); -} -d.forEach(_25,function(_2c){ -d.connect(_2a,_2c.event,null,_2c.func); -}); -d.forEach(_26,function(_2d){ -_2d.call(_2a); -}); -}); -if(!_17._started){ -d.forEach(_19,function(_2e){ -if(!_18.noStart&&_2e&&_2e.startup&&!_2e._started&&(!_2e.getParent||!_2e.getParent())){ -_2e.startup(); -} -}); -} -return _19; -}; -this.parse=function(_2f,_30){ -var _31; -if(!_30&&_2f&&_2f.rootNode){ -_30=_2f; -_31=_30.rootNode; -}else{ -_31=_2f; -} -var _32=this._attrName; -function _33(_34,_35){ -var _36=dojo.clone(_34.inherited); -dojo.forEach(["dir","lang"],function(_37){ -var val=_34.node.getAttribute(_37); -if(val){ -_36[_37]=val; -} -}); -var _38=_34.scripts; -var _39=!_34.clsInfo||!_34.clsInfo.cls.prototype.stopParser; -for(var _3a=_34.node.firstChild;_3a;_3a=_3a.nextSibling){ -if(_3a.nodeType==1){ -var _3b=_39&&_3a.getAttribute(_32); -if(_3b){ -var _3c={"type":_3b,clsInfo:_7(_3b),node:_3a,scripts:[],inherited:_36}; -_35.push(_3c); -_33(_3c,_35); -}else{ -if(_38&&_3a.nodeName.toLowerCase()=="script"){ -_3b=_3a.getAttribute("type"); -if(_3b&&/^dojo\//i.test(_3b)){ -_38.push(_3a); -} -}else{ -if(_39){ -_33({node:_3a,inherited:_36},_35); -} -} -} -} -} -}; -var _3d=[]; -_33({node:_31?dojo.byId(_31):dojo.body(),inherited:(_30&&_30.inherited)||{dir:dojo._isBodyLtr()?"ltr":"rtl"}},_3d); -return this.instantiate(_3d,null,_30); -}; + +new Date("X"); // workaround for #11279, new Date("") == NaN + +dojo.parser = new function(){ + // summary: The Dom/Widget parsing package + + var d = dojo; + this._attrName = d._scopeName + "Type"; + this._query = "[" + this._attrName + "]"; + + function val2type(/*Object*/ value){ + // summary: + // Returns name of type of given value. + + if(d.isString(value)){ return "string"; } + if(typeof value == "number"){ return "number"; } + if(typeof value == "boolean"){ return "boolean"; } + if(d.isFunction(value)){ return "function"; } + if(d.isArray(value)){ return "array"; } // typeof [] == "object" + if(value instanceof Date) { return "date"; } // assume timestamp + if(value instanceof d._Url){ return "url"; } + return "object"; + } + + function str2obj(/*String*/ value, /*String*/ type){ + // summary: + // Convert given string value to given type + switch(type){ + case "string": + return value; + case "number": + return value.length ? Number(value) : NaN; + case "boolean": + // 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 + value=value.toString(); + value=d.trim(value.substring(value.indexOf('{')+1, value.length-1)); + } + try{ + if(value === "" || value.search(/[^\w\.]+/i) != -1){ + // The user has specified some text for a function like "return x+5" + return new Function(value); + }else{ + // The user has specified the name of a function like "myOnClick" + // or a single word function "return" + return d.getObject(value, false) || new Function(value); + } + }catch(e){ return new Function(); } + case "array": + return value ? value.split(/\s*,\s*/) : []; + case "date": + switch(value){ + case "": return new Date(""); // the NaN of dates + case "now": return new Date(); // current date + default: return d.date.stamp.fromISOString(value); + } + case "url": + return d.baseUrl + value; + default: + return d.fromJson(value); + } + } + + var instanceClasses = { + // map from fully qualified name (like "dijit.Button") to structure like + // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} } + }; + + // 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(){ + instanceClasses = {}; + }); + + function getClassInfo(/*String*/ className){ + // className: + // fully qualified name (like "dijit.form.Button") + // returns: + // structure like + // { + // cls: dijit.Button, + // params: { label: "string", disabled: "boolean"} + // } + + if(!instanceClasses[className]){ + // get pointer to widget class + var cls = d.getObject(className); + 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); + } + + instanceClasses[className] = { cls: cls, params: params }; + } + return instanceClasses[className]; + } + + this._functionFromScript = function(script){ + var preamble = ""; + var suffix = ""; + var argsStr = script.getAttribute("args"); + if(argsStr){ + d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){ + preamble += "var "+part+" = arguments["+idx+"]; "; + }); + } + var withStr = script.getAttribute("with"); + if(withStr && withStr.length){ + d.forEach(withStr.split(/\s*,\s*/), function(part){ + preamble += "with("+part+"){"; + suffix += "}"; + }); + } + return new Function(preamble+script.innerHTML+suffix); + } + + this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){ + // 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 nodes or objects like + // | { + // | type: "dijit.form.Button", + // | 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. + // args: Object? + // An object used to hold kwArgs for instantiation. + // Supports 'noStart' and inherited. + var thelist = [], dp = dojo.parser; + mixin = mixin||{}; + args = args||{}; + + 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; + 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)); + clazz = clsInfo && clsInfo.cls; + scripts = obj.scripts; + }else{ + // old (backwards compatible) format of nodes[] array, simple array of DOMNodes + node = obj; + type = dp._attrName in mixin ? mixin[dp._attrName] : node.getAttribute(dp._attrName); + clsInfo = type && getClassInfo(type); + clazz = clsInfo && clsInfo.cls; + 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 + // settings inherited from ancestors ("dir" and "lang"). + // Inherited setting may later be overridden by explicit settings on node itself. + var params = {}, + attributes = node.attributes; + if(args.defaults){ + // settings for the document itself (or whatever subtree is being parsed) + dojo.mixin(params, args.defaults); + } + if(obj.inherited){ + // settings from dir=rtl or lang=... on a node above this node + dojo.mixin(params, obj.inherited); + } + + // 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? + } + var _type = clsInfo.params[name]; + if(typeof value == "string"){ + params[name] = str2obj(value, _type); + }else{ + params[name] = value; + } + } + + // Process <script type="dojo/*"> script tags + // <script type="dojo/method" 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" event="foo"> tags are dojo.connected after instantiation + // note: dojo/* script tags cannot exist in self closing widgets, like <input /> + var connects = [], // functions to connect after instantiation + calls = []; // functions to call after instantiation + + d.forEach(scripts, function(script){ + node.removeChild(script); + var event = script.getAttribute("event"), + type = script.getAttribute("type"), + nf = d.parser._functionFromScript(script); + if(event){ + if(type == "dojo/connect"){ + connects.push({event: event, func: nf}); + }else{ + params[event] = nf; + } + }else{ + calls.push(nf); + } + }); + + var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory; + // create the instance + var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node); + thelist.push(instance); + + // map it to the JS namespace if that makes sense + var jsname = node.getAttribute("jsId"); + if(jsname){ + d.setObject(jsname, instance); + } + + // process connections and startup functions + d.forEach(connects, function(connect){ + d.connect(instance, connect.event, null, connect.func); + }); + d.forEach(calls, function(func){ + func.call(instance); + }); + }); + + // 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 + if(!mixin._started){ + // TODO: for 2.0, when old instantiate() API is desupported, store parent-child + // relationships in the nodes[] array so that no getParent() call is needed. + // Note that will require a parse() call from ContentPane setting a param that the + // 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 && + (!instance.getParent || !instance.getParent()) + ){ + instance.startup(); + } + }); + } + return thelist; + }; + + this.parse = function(/*DomNode?*/ rootNode, /* Object? */ 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" + // + // 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 + // `rootNode` member, that is used. + // + // args: + // a kwArgs object passed along to instantiate() + // + // * 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 `args object. + // * 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 + // + // + // example: + // Parse all widgets on a page: + // | dojo.parser.parse(); + // + // example: + // Parse all classes within the node with id="foo" + // | dojo.parser.parse(dojo.byId(foo)); + // + // example: + // Parse all classes in a page, but do not call .startup() on any + // child + // | dojo.parser.parse({ noStart: true }) + // + // example: + // 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 }); + + // determine the root node based on the passed arguments. + var root; + if(!args && rootNode && rootNode.rootNode){ + args = rootNode; + root = args.rootNode; + }else{ + root = rootNode; + } + + var attrName = this._attrName; + function scan(parent, list){ + // summary: + // Parent is an Object representing a DOMNode, with or without a dojoType specified. + // Scan parent's children looking for nodes with dojoType specified, storing in list[]. + // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[]. + // parent: Object + // Object representing the parent node, like + // | { + // | 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 + // | scripts: [], // empty array, put <script type=dojo/*> in here + // | clsInfo: { cls: dijit.form.Button, ...} + // | } + // list: DomNode[] + // Output array of objects (same format as parent) representing nodes to be turned into widgets + + // 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){ + var val = parent.node.getAttribute(name); + if(val){ + inherited[name] = val; + } + }); + + // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[]. + var scripts = parent.scripts; + + // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively + var recurse = !parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser; + + // 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); + if(type){ + // if dojoType 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 + node: child, + scripts: [], // <script> nodes that are parent's children + inherited: inherited // dir & lang attributes inherited from parent + }; + list.push(params); + + // Recurse, collecting <script type="dojo/..."> children, and also looking for + // descendant nodes with dojoType specified (unless the widget has the stopParser flag), + scan(params, list); + }else if(scripts && child.nodeName.toLowerCase() == "script"){ + // if <script type="dojo/...">, save in scripts[] + type = child.getAttribute("type"); + if (type && /^dojo\//i.test(type)) { + scripts.push(child); + } + }else if(recurse){ + // Recurse, looking for grandchild nodes with dojoType specified + scan({ + node: child, + inherited: inherited + }, list); + } + } + } + } + + // 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" + } + }, list); + + // go build the object instances + return this.instantiate(list, null, args); // Array + }; }(); + +//Register the parser callback. It should be the first callback +//after the a11y test. + (function(){ -var _3e=function(){ -if(dojo.config.parseOnLoad){ -dojo.parser.parse(); -} -}; -if(dojo.exists("dijit.wai.onload")&&(dijit.wai.onload===dojo._loaders[0])){ -dojo._loaders.splice(1,0,_3e); -}else{ -dojo._loaders.unshift(_3e); -} + var parseRunner = function(){ + if(dojo.config.parseOnLoad){ + dojo.parser.parse(); + } + }; + + // FIXME: need to clobber cross-dependency!! + if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){ + dojo._loaders.splice(1, 0, parseRunner); + }else{ + dojo._loaders.unshift(parseRunner); + } })(); + } |