]> git.wh0rd.org Git - tt-rss.git/blob - lib/dojo/parser.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dojo / parser.js.uncompressed.js
1 define(
2         "dojo/parser", ["./_base/kernel", "./_base/lang", "./_base/array", "./_base/html", "./_base/window", "./_base/url",
3                 "./_base/json", "./aspect", "./date/stamp", "./query", "./on", "./ready"],
4         function(dojo, dlang, darray, dhtml, dwindow, _Url, djson, aspect, dates, query, don){
5
6 // module:
7 //              dojo/parser
8 // summary:
9 //              The Dom/Widget parsing package
10
11 new Date("X"); // workaround for #11279, new Date("") == NaN
12
13 var features = {
14         // Feature detection for when node.attributes only lists the attributes specified in the markup
15         // rather than old IE/quirks behavior where it lists every default value too
16         "dom-attributes-explicit": document.createElement("div").attributes.length < 40
17 };
18 function has(feature){
19         return features[feature];
20 }
21
22
23 dojo.parser = new function(){
24         // summary:
25         //              The Dom/Widget parsing package
26
27         var _nameMap = {
28                 // Map from widget name (ex: "dijit.form.Button") to structure mapping
29                 // lowercase version of attribute names to the version in the widget ex:
30                 //      {
31                 //              label: "label",
32                 //              onclick: "onClick"
33                 //      }
34         };
35         function getNameMap(proto){
36                 // summary:
37                 //              Returns map from lowercase name to attribute name in class, ex: {onclick: "onClick"}
38                 var map = {};
39                 for(var name in proto){
40                         if(name.charAt(0)=="_"){ continue; }    // skip internal properties
41                         map[name.toLowerCase()] = name;
42                 }
43                 return map;
44         }
45         // Widgets like BorderContainer add properties to _Widget via dojo.extend().
46         // If BorderContainer is loaded after _Widget's parameter list has been cached,
47         // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
48         aspect.after(dlang, "extend", function(){
49                 _nameMap = {};
50         }, true);
51
52         // Map from widget name (ex: "dijit.form.Button") to constructor
53         var _ctorMap = {};
54
55         this._functionFromScript = function(script, attrData){
56                 // summary:
57                 //              Convert a <script type="dojo/method" args="a, b, c"> ... </script>
58                 //              into a function
59                 // script: DOMNode
60                 //              The <script> DOMNode
61                 // attrData: String
62                 //              For HTML5 compliance, searches for attrData + "args" (typically
63                 //              "data-dojo-args") instead of "args"
64                 var preamble = "";
65                 var suffix = "";
66                 var argsStr = (script.getAttribute(attrData + "args") || script.getAttribute("args"));
67                 if(argsStr){
68                         darray.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
69                                 preamble += "var "+part+" = arguments["+idx+"]; ";
70                         });
71                 }
72                 var withStr = script.getAttribute("with");
73                 if(withStr && withStr.length){
74                         darray.forEach(withStr.split(/\s*,\s*/), function(part){
75                                 preamble += "with("+part+"){";
76                                 suffix += "}";
77                         });
78                 }
79                 return new Function(preamble+script.innerHTML+suffix);
80         };
81
82         this.instantiate = /*====== dojo.parser.instantiate= ======*/function(nodes, mixin, args){
83                 // summary:
84                 //              Takes array of nodes, and turns them into class instances and
85                 //              potentially calls a startup method to allow them to connect with
86                 //              any children.
87                 // nodes: Array
88                 //              Array of nodes or objects like
89                 //      |               {
90                 //      |                       type: "dijit.form.Button",
91                 //      |                       node: DOMNode,
92                 //      |                       scripts: [ ... ],       // array of <script type="dojo/..."> children of node
93                 //      |                       inherited: { ... }      // settings inherited from ancestors like dir, theme, etc.
94                 //      |               }
95                 // mixin: Object?
96                 //              An object that will be mixed in with each node in the array.
97                 //              Values in the mixin will override values in the node, if they
98                 //              exist.
99                 // args: Object?
100                 //              An object used to hold kwArgs for instantiation.
101                 //              See parse.args argument for details.
102
103                 var thelist = [],
104                 mixin = mixin||{};
105                 args = args||{};
106
107                 // Precompute names of special attributes we are looking for
108                 // TODO: for 2.0 default to data-dojo- regardless of scopeName (or maybe scopeName won't exist in 2.0)
109                 var dojoType = (args.scope || dojo._scopeName) + "Type",                // typically "dojoType"
110                         attrData = "data-" + (args.scope || dojo._scopeName) + "-",// typically "data-dojo-"
111                         dataDojoType = attrData + "type",                                               // typically "data-dojo-type"
112                         dataDojoProps = attrData + "props",                                             // typically "data-dojo-props"
113                         dataDojoAttachPoint = attrData + "attach-point",
114                         dataDojoAttachEvent = attrData + "attach-event",
115                         dataDojoId = attrData + "id";
116
117                 // And make hash to quickly check if a given attribute is special, and to map the name to something friendly
118                 var specialAttrs = {};
119                 darray.forEach([dataDojoProps, dataDojoType, dojoType, dataDojoId, "jsId", dataDojoAttachPoint,
120                                 dataDojoAttachEvent, "dojoAttachPoint", "dojoAttachEvent", "class", "style"], function(name){
121                         specialAttrs[name.toLowerCase()] = name.replace(args.scope, "dojo");
122                 });
123
124                 darray.forEach(nodes, function(obj){
125                         if(!obj){ return; }
126
127                         var node = obj.node || obj,
128                                 type = dojoType in mixin ? mixin[dojoType] : obj.node ? obj.type : (node.getAttribute(dataDojoType) || node.getAttribute(dojoType)),
129                                 ctor = _ctorMap[type] || (_ctorMap[type] = dlang.getObject(type)),
130                                 proto = ctor && ctor.prototype;
131                         if(!ctor){
132                                 throw new Error("Could not load class '" + type);
133                         }
134
135                         // Setup hash to hold parameter settings for this widget.       Start with the parameter
136                         // settings inherited from ancestors ("dir" and "lang").
137                         // Inherited setting may later be overridden by explicit settings on node itself.
138                         var params = {};
139
140                         if(args.defaults){
141                                 // settings for the document itself (or whatever subtree is being parsed)
142                                 dlang.mixin(params, args.defaults);
143                         }
144                         if(obj.inherited){
145                                 // settings from dir=rtl or lang=... on a node above this node
146                                 dlang.mixin(params, obj.inherited);
147                         }
148
149                         // Get list of attributes explicitly listed in the markup
150                         var attributes;
151                         if(has("dom-attributes-explicit")){
152                                 // Standard path to get list of user specified attributes
153                                 attributes = node.attributes;
154                         }else{
155                                 // Special path for IE, avoid (sometimes >100) bogus entries in node.attributes
156                                 var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false),
157                                         attrs = clone.outerHTML.replace(/=[^\s"']+|="[^"]*"|='[^']*'/g, "").replace(/^\s*<[a-zA-Z0-9]*/, "").replace(/>.*$/, "");
158
159                                 attributes = darray.map(attrs.split(/\s+/), function(name){
160                                         var lcName = name.toLowerCase();
161                                         return {
162                                                 name: name,
163                                                 // getAttribute() doesn't work for button.value, returns innerHTML of button.
164                                                 // but getAttributeNode().value doesn't work for the form.encType or li.value
165                                                 value: (node.nodeName == "LI" && name == "value") || lcName == "enctype" ?
166                                                                 node.getAttribute(lcName) : node.getAttributeNode(lcName).value,
167                                                 specified: true
168                                         };
169                                 });
170                         }
171
172                         // Read in attributes and process them, including data-dojo-props, data-dojo-type,
173                         // dojoAttachPoint, etc., as well as normal foo=bar attributes.
174                         var i=0, item;
175                         while(item = attributes[i++]){
176                                 if(!item || !item.specified){
177                                         continue;
178                                 }
179
180                                 var name = item.name,
181                                         lcName = name.toLowerCase(),
182                                         value = item.value;
183
184                                 if(lcName in specialAttrs){
185                                         switch(specialAttrs[lcName]){
186
187                                         // Data-dojo-props.   Save for later to make sure it overrides direct foo=bar settings
188                                         case "data-dojo-props":
189                                                 var extra = value;
190                                                 break;
191
192                                         // data-dojo-id or jsId. TODO: drop jsId in 2.0
193                                         case "data-dojo-id":
194                                         case "jsId":
195                                                 var jsname = value;
196                                                 break;
197
198                                         // For the benefit of _Templated
199                                         case "data-dojo-attach-point":
200                                         case "dojoAttachPoint":
201                                                 params.dojoAttachPoint = value;
202                                                 break;
203                                         case "data-dojo-attach-event":
204                                         case "dojoAttachEvent":
205                                                 params.dojoAttachEvent = value;
206                                                 break;
207
208                                         // Special parameter handling needed for IE
209                                         case "class":
210                                                 params["class"] = node.className;
211                                                 break;
212                                         case "style":
213                                                 params["style"] = node.style && node.style.cssText;
214                                                 break;
215                                         }
216                                 }else{
217                                         // Normal attribute, ex: value="123"
218
219                                         // Find attribute in widget corresponding to specified name.
220                                         // May involve case conversion, ex: onclick --> onClick
221                                         if(!(name in proto)){
222                                                 var map = (_nameMap[type] || (_nameMap[type] = getNameMap(proto)));
223                                                 name = map[lcName] || name;
224                                         }
225
226                                         // Set params[name] to value, doing type conversion
227                                         if(name in proto){
228                                                 switch(typeof proto[name]){
229                                                 case "string":
230                                                         params[name] = value;
231                                                         break;
232                                                 case "number":
233                                                         params[name] = value.length ? Number(value) : NaN;
234                                                         break;
235                                                 case "boolean":
236                                                         // for checked/disabled value might be "" or "checked".  interpret as true.
237                                                         params[name] = value.toLowerCase() != "false";
238                                                         break;
239                                                 case "function":
240                                                         if(value === "" || value.search(/[^\w\.]+/i) != -1){
241                                                                 // The user has specified some text for a function like "return x+5"
242                                                                 params[name] = new Function(value);
243                                                         }else{
244                                                                 // The user has specified the name of a function like "myOnClick"
245                                                                 // or a single word function "return"
246                                                                 params[name] = dlang.getObject(value, false) || new Function(value);
247                                                         }
248                                                         break;
249                                                 default:
250                                                         var pVal = proto[name];
251                                                         params[name] =
252                                                                 (pVal && "length" in pVal) ? (value ? value.split(/\s*,\s*/) : []) :    // array
253                                                                         (pVal instanceof Date) ?
254                                                                                 (value == "" ? new Date("") :   // the NaN of dates
255                                                                                 value == "now" ? new Date() :   // current date
256                                                                                 dates.fromISOString(value)) :
257                                                                 (pVal instanceof dojo._Url) ? (dojo.baseUrl + value) :
258                                                                 djson.fromJson(value);
259                                                 }
260                                         }else{
261                                                 params[name] = value;
262                                         }
263                                 }
264                         }
265
266                         // Mix things found in data-dojo-props into the params, overriding any direct settings
267                         if(extra){
268                                 try{
269                                         extra = djson.fromJson.call(args.propsThis, "{" + extra + "}");
270                                         dlang.mixin(params, extra);
271                                 }catch(e){
272                                         // give the user a pointer to their invalid parameters. FIXME: can we kill this in production?
273                                         throw new Error(e.toString() + " in data-dojo-props='" + extra + "'");
274                                 }
275                         }
276
277                         // Any parameters specified in "mixin" override everything else.
278                         dlang.mixin(params, mixin);
279
280                         var scripts = obj.node ? obj.scripts : (ctor && (ctor._noScript || proto._noScript) ? [] :
281                                                 query("> script[type^='dojo/']", node));
282
283                         // Process <script type="dojo/*"> script tags
284                         // <script type="dojo/method" event="foo"> tags are added to params, and passed to
285                         // the widget on instantiation.
286                         // <script type="dojo/method"> tags (with no event) are executed after instantiation
287                         // <script type="dojo/connect" data-dojo-event="foo"> tags are dojo.connected after instantiation
288                         // <script type="dojo/watch" data-dojo-prop="foo"> tags are dojo.watch after instantiation
289                         // <script type="dojo/on" data-dojo-event="foo"> tags are dojo.on after instantiation
290                         // note: dojo/* script tags cannot exist in self closing widgets, like <input />
291                         var connects = [],      // functions to connect after instantiation
292                                 calls = [],             // functions to call after instantiation
293                                 watch = [],  //functions to watch after instantiation
294                                 on = []; //functions to on after instantiation
295
296                         if(scripts){
297                                 for(i=0; i<scripts.length; i++){
298                                         var script = scripts[i];
299                                         node.removeChild(script);
300                                         // FIXME: drop event="" support in 2.0. use data-dojo-event="" instead
301                                         var event = (script.getAttribute(attrData + "event") || script.getAttribute("event")),
302                                                 prop = script.getAttribute(attrData + "prop"),
303                                                 type = script.getAttribute("type"),
304                                                 nf = this._functionFromScript(script, attrData);
305                                         if(event){
306                                                 if(type == "dojo/connect"){
307                                                         connects.push({event: event, func: nf});
308                                                 }else if(type == "dojo/on"){
309                                                         on.push({event: event, func: nf});
310                                                 }else{
311                                                         params[event] = nf;
312                                                 }
313                                         }else if(type == "dojo/watch"){
314                                                 watch.push({prop: prop, func: nf});
315                                         }else{
316                                                 calls.push(nf);
317                                         }
318                                 }
319                         }
320
321                         // create the instance
322                         var markupFactory = ctor.markupFactory || proto.markupFactory;
323                         var instance = markupFactory ? markupFactory(params, node, ctor) : new ctor(params, node);
324                         thelist.push(instance);
325
326                         // map it to the JS namespace if that makes sense
327                         if(jsname){
328                                 dlang.setObject(jsname, instance);
329                         }
330
331                         // process connections and startup functions
332                         for(i=0; i<connects.length; i++){
333                                 aspect.after(instance, connects[i].event, dojo.hitch(instance, connects[i].func), true);
334                         }
335                         for(i=0; i<calls.length; i++){
336                                 calls[i].call(instance);
337                         }
338                         for(i=0; i<watch.length; i++){
339                                 instance.watch(watch[i].prop, watch[i].func);
340                         }
341                         for(i=0; i<on.length; i++){
342                                 don(instance, on[i].event, on[i].func);
343                         }
344                 }, this);
345
346                 // Call startup on each top level instance if it makes sense (as for
347                 // widgets).  Parent widgets will recursively call startup on their
348                 // (non-top level) children
349                 if(!mixin._started){
350                         darray.forEach(thelist, function(instance){
351                                 if( !args.noStart && instance  &&
352                                         dlang.isFunction(instance.startup) &&
353                                         !instance._started
354                                 ){
355                                         instance.startup();
356                                 }
357                         });
358                 }
359                 return thelist;
360         };
361
362         this.parse = /*====== dojo.parser.parse= ======*/ function(rootNode, args){
363                 // summary:
364                 //              Scan the DOM for class instances, and instantiate them.
365                 //
366                 // description:
367                 //              Search specified node (or root node) recursively for class instances,
368                 //              and instantiate them. Searches for either data-dojo-type="Class" or
369                 //              dojoType="Class" where "Class" is a a fully qualified class name,
370                 //              like `dijit.form.Button`
371                 //
372                 //              Using `data-dojo-type`:
373                 //              Attributes using can be mixed into the parameters used to instantiate the
374                 //              Class by using a `data-dojo-props` attribute on the node being converted.
375                 //              `data-dojo-props` should be a string attribute to be converted from JSON.
376                 //
377                 //              Using `dojoType`:
378                 //              Attributes are read from the original domNode and converted to appropriate
379                 //              types by looking up the Class prototype values. This is the default behavior
380                 //              from Dojo 1.0 to Dojo 1.5. `dojoType` support is deprecated, and will
381                 //              go away in Dojo 2.0.
382                 //
383                 // rootNode: DomNode?
384                 //              A default starting root node from which to start the parsing. Can be
385                 //              omitted, defaulting to the entire document. If omitted, the `args`
386                 //              object can be passed in this place. If the `args` object has a
387                 //              `rootNode` member, that is used.
388                 //
389                 // args: Object
390                 //              a kwArgs object passed along to instantiate()
391                 //
392                 //                      * noStart: Boolean?
393                 //                              when set will prevent the parser from calling .startup()
394                 //                              when locating the nodes.
395                 //                      * rootNode: DomNode?
396                 //                              identical to the function's `rootNode` argument, though
397                 //                              allowed to be passed in via this `args object.
398                 //                      * template: Boolean
399                 //                              If true, ignores ContentPane's stopParser flag and parses contents inside of
400                 //                              a ContentPane inside of a template.   This allows dojoAttachPoint on widgets/nodes
401                 //                              nested inside the ContentPane to work.
402                 //                      * inherited: Object
403                 //                              Hash possibly containing dir and lang settings to be applied to
404                 //                              parsed widgets, unless there's another setting on a sub-node that overrides
405                 //                      * scope: String
406                 //                              Root for attribute names to search for.   If scopeName is dojo,
407                 //                              will search for data-dojo-type (or dojoType).   For backwards compatibility
408                 //                              reasons defaults to dojo._scopeName (which is "dojo" except when
409                 //                              multi-version support is used, when it will be something like dojo16, dojo20, etc.)
410                 //                      * propsThis: Object
411                 //                              If specified, "this" referenced from data-dojo-props will refer to propsThis.
412                 //                              Intended for use from the widgets-in-template feature of `dijit._WidgetsInTemplateMixin`
413                 //
414                 // example:
415                 //              Parse all widgets on a page:
416                 //      |               dojo.parser.parse();
417                 //
418                 // example:
419                 //              Parse all classes within the node with id="foo"
420                 //      |               dojo.parser.parse(dojo.byId('foo'));
421                 //
422                 // example:
423                 //              Parse all classes in a page, but do not call .startup() on any
424                 //              child
425                 //      |               dojo.parser.parse({ noStart: true })
426                 //
427                 // example:
428                 //              Parse all classes in a node, but do not call .startup()
429                 //      |               dojo.parser.parse(someNode, { noStart:true });
430                 //      |               // or
431                 //      |               dojo.parser.parse({ noStart:true, rootNode: someNode });
432
433                 // determine the root node based on the passed arguments.
434                 var root;
435                 if(!args && rootNode && rootNode.rootNode){
436                         args = rootNode;
437                         root = args.rootNode;
438                 }else{
439                         root = rootNode;
440                 }
441                 root = root ? dhtml.byId(root) : dwindow.body();
442                 args = args || {};
443
444                 var dojoType = (args.scope || dojo._scopeName) + "Type",                // typically "dojoType"
445                         attrData = "data-" + (args.scope || dojo._scopeName) + "-",     // typically "data-dojo-"
446                         dataDojoType = attrData + "type",                                               // typically "data-dojo-type"
447                         dataDojoTextDir = attrData + "textdir";                                 // typically "data-dojo-textdir"
448
449                 // List of all nodes on page w/dojoType specified
450                 var list = [];
451
452                 // Info on DOMNode currently being processed
453                 var node = root.firstChild;
454
455                 // Info on parent of DOMNode currently being processed
456                 //      - inherited: dir, lang, and textDir setting of parent, or inherited by parent
457                 //      - parent: pointer to identical structure for my parent (or null if no parent)
458                 //      - scripts: if specified, collects <script type="dojo/..."> type nodes from children
459                 var inherited = args && args.inherited;
460                 if(!inherited){
461                         function findAncestorAttr(node, attr){
462                                 return (node.getAttribute && node.getAttribute(attr)) ||
463                                         (node !== dwindow.doc && node !== dwindow.doc.documentElement && node.parentNode ? findAncestorAttr(node.parentNode, attr) : null);
464                         }
465                         inherited = {
466                                 dir: findAncestorAttr(root, "dir"),
467                                 lang: findAncestorAttr(root, "lang"),
468                                 textDir: findAncestorAttr(root, dataDojoTextDir)
469                         };
470                         for(var key in inherited){
471                                 if(!inherited[key]){ delete inherited[key]; }
472                         }
473                 }
474                 var parent = {
475                         inherited: inherited
476                 };
477
478                 // For collecting <script type="dojo/..."> type nodes (when null, we don't need to collect)
479                 var scripts;
480
481                 // when true, only look for <script type="dojo/..."> tags, and don't recurse to children
482                 var scriptsOnly;
483
484                 function getEffective(parent){
485                         // summary:
486                         //              Get effective dir, lang, textDir settings for specified obj
487                         //              (matching "parent" object structure above), and do caching.
488                         //              Take care not to return null entries.
489                         if(!parent.inherited){
490                                 parent.inherited = {};
491                                 var node = parent.node,
492                                         grandparent = getEffective(parent.parent);
493                                 var inherited  = {
494                                         dir: node.getAttribute("dir") || grandparent.dir,
495                                         lang: node.getAttribute("lang") || grandparent.lang,
496                                         textDir: node.getAttribute(dataDojoTextDir) || grandparent.textDir
497                                 };
498                                 for(var key in inherited){
499                                         if(inherited[key]){
500                                                 parent.inherited[key] = inherited[key];
501                                         }
502                                 }
503                         }
504                         return parent.inherited;
505                 }
506
507                 // DFS on DOM tree, collecting nodes with data-dojo-type specified.
508                 while(true){
509                         if(!node){
510                                 // Finished this level, continue to parent's next sibling
511                                 if(!parent || !parent.node){
512                                         break;
513                                 }
514                                 node = parent.node.nextSibling;
515                                 scripts = parent.scripts;
516                                 scriptsOnly = false;
517                                 parent = parent.parent;
518                                 continue;
519                         }
520
521                         if(node.nodeType != 1){
522                                 // Text or comment node, skip to next sibling
523                                 node = node.nextSibling;
524                                 continue;
525                         }
526
527                         if(scripts && node.nodeName.toLowerCase() == "script"){
528                                 // Save <script type="dojo/..."> for parent, then continue to next sibling
529                                 type = node.getAttribute("type");
530                                 if(type && /^dojo\/\w/i.test(type)){
531                                         scripts.push(node);
532                                 }
533                                 node = node.nextSibling;
534                                 continue;
535                         }
536                         if(scriptsOnly){
537                                 node = node.nextSibling;
538                                 continue;
539                         }
540
541                         // Check for data-dojo-type attribute, fallback to backward compatible dojoType
542                         var type = node.getAttribute(dataDojoType) || node.getAttribute(dojoType);
543
544                         // Short circuit for leaf nodes containing nothing [but text]
545                         var firstChild = node.firstChild;
546                         if(!type && (!firstChild || (firstChild.nodeType == 3 && !firstChild.nextSibling))){
547                                 node = node.nextSibling;
548                                 continue;
549                         }
550
551                         // Setup data structure to save info on current node for when we return from processing descendant nodes
552                         var current = {
553                                 node: node,
554                                 scripts: scripts,
555                                 parent: parent
556                         };
557
558                         // If dojoType/data-dojo-type specified, add to output array of nodes to instantiate
559                         var ctor = type && (_ctorMap[type] || (_ctorMap[type] = dlang.getObject(type))), // note: won't find classes declared via dojo.Declaration
560                                 childScripts = ctor && !ctor.prototype._noScript ? [] : null; // <script> nodes that are parent's children
561                         if(type){
562                                 list.push({
563                                         "type": type,
564                                         node: node,
565                                         scripts: childScripts,
566                                         inherited: getEffective(current) // dir & lang settings for current node, explicit or inherited
567                                 });
568                         }
569
570                         // Recurse, collecting <script type="dojo/..."> children, and also looking for
571                         // descendant nodes with dojoType specified (unless the widget has the stopParser flag).
572                         // When finished with children, go to my next sibling.
573                         node = firstChild;
574                         scripts = childScripts;
575                         scriptsOnly = ctor && ctor.prototype.stopParser && !(args && args.template);
576                         parent = current;
577
578                 }
579
580                 // go build the object instances
581                 var mixin = args && args.template ? {template: true} : null;
582                 return this.instantiate(list, mixin, args); // Array
583         };
584 }();
585
586
587 //Register the parser callback. It should be the first callback
588 //after the a11y test.
589 if(dojo.config.parseOnLoad){
590         dojo.ready(100, dojo.parser, "parse");
591 }
592
593 return dojo.parser;
594 });