]> git.wh0rd.org - 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 });