]> git.wh0rd.org Git - tt-rss.git/blob - lib/dojo/_base/html.js
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
[tt-rss.git] / lib / dojo / _base / html.js
1 /*
2         Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
3         Available via Academic Free License >= 2.1 OR the modified BSD license.
4         see: http://dojotoolkit.org/license for details
5 */
6
7
8 if(!dojo._hasResource["dojo._base.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9 dojo._hasResource["dojo._base.html"] = true;
10 dojo.require("dojo._base.lang");
11 dojo.provide("dojo._base.html");
12
13 // FIXME: need to add unit tests for all the semi-public methods
14
15 try{
16         document.execCommand("BackgroundImageCache", false, true);
17 }catch(e){
18         // sane browsers don't have cache "issues"
19 }
20
21 // =============================
22 // DOM Functions
23 // =============================
24
25 /*=====
26 dojo.byId = function(id, doc){
27         //      summary:
28         //              Returns DOM node with matching `id` attribute or `null`
29         //              if not found. If `id` is a DomNode, this function is a no-op.
30         //
31         //      id: String|DOMNode
32         //              A string to match an HTML id attribute or a reference to a DOM Node
33         //
34         //      doc: Document?
35         //              Document to work in. Defaults to the current value of
36         //              dojo.doc.  Can be used to retrieve
37         //              node references from other documents.
38         //
39         //      example:
40         //      Look up a node by ID:
41         //      |       var n = dojo.byId("foo");
42         //
43         //      example:
44         //      Check if a node exists, and use it.
45         //      |       var n = dojo.byId("bar");
46         //      |       if(n){ doStuff() ... }
47         //
48         //      example:
49         //      Allow string or DomNode references to be passed to a custom function:
50         //      |       var foo = function(nodeOrId){
51         //      |               nodeOrId = dojo.byId(nodeOrId);
52         //      |               // ... more stuff
53         //      |       }
54 =====*/
55
56 if(dojo.isIE || dojo.isOpera){
57         dojo.byId = function(id, doc){
58                 if(typeof id != "string"){
59                         return id;
60                 }
61                 var _d = doc || dojo.doc, te = _d.getElementById(id);
62                 // attributes.id.value is better than just id in case the 
63                 // user has a name=id inside a form
64                 if(te && (te.attributes.id.value == id || te.id == id)){
65                         return te;
66                 }else{
67                         var eles = _d.all[id];
68                         if(!eles || eles.nodeName){
69                                 eles = [eles];
70                         }
71                         // if more than 1, choose first with the correct id
72                         var i=0;
73                         while((te=eles[i++])){
74                                 if((te.attributes && te.attributes.id && te.attributes.id.value == id)
75                                         || te.id == id){
76                                         return te;
77                                 }
78                         }
79                 }
80         };
81 }else{
82         dojo.byId = function(id, doc){
83                 // inline'd type check
84                 return (typeof id == "string") ? (doc || dojo.doc).getElementById(id) : id; // DomNode
85         };
86 }
87 /*=====
88 };
89 =====*/
90
91 (function(){
92         var d = dojo;
93         var byId = d.byId;
94
95         var _destroyContainer = null,
96                 _destroyDoc;
97                 d.addOnWindowUnload(function(){
98                 _destroyContainer = null; //prevent IE leak
99         });
100         
101 /*=====
102         dojo._destroyElement = function(node){
103                 // summary:
104                 //              Existing alias for `dojo.destroy`. Deprecated, will be removed
105                 //              in 2.0
106         }
107 =====*/
108         dojo._destroyElement = dojo.destroy = function(/*String|DomNode*/node){
109                 //      summary:
110                 //              Removes a node from its parent, clobbering it and all of its
111                 //              children.
112                 //
113                 //      description:
114                 //              Removes a node from its parent, clobbering it and all of its
115                 //              children. Function only works with DomNodes, and returns nothing.
116                 //
117                 //      node:
118                 //              A String ID or DomNode reference of the element to be destroyed
119                 //
120                 //      example:
121                 //      Destroy a node byId:
122                 //      |       dojo.destroy("someId");
123                 //
124                 //      example:
125                 //      Destroy all nodes in a list by reference:
126                 //      |       dojo.query(".someNode").forEach(dojo.destroy);
127
128                 node = byId(node);
129                 try{
130                         var doc = node.ownerDocument;
131                         // cannot use _destroyContainer.ownerDocument since this can throw an exception on IE
132                         if(!_destroyContainer || _destroyDoc != doc){
133                                 _destroyContainer = doc.createElement("div");
134                                 _destroyDoc = doc;
135                         }
136                         _destroyContainer.appendChild(node.parentNode ? node.parentNode.removeChild(node) : node);
137                         // NOTE: see http://trac.dojotoolkit.org/ticket/2931. This may be a bug and not a feature
138                         _destroyContainer.innerHTML = "";
139                 }catch(e){
140                         /* squelch */
141                 }
142         };
143
144         dojo.isDescendant = function(/*DomNode|String*/node, /*DomNode|String*/ancestor){
145                 //      summary:
146                 //              Returns true if node is a descendant of ancestor
147                 //      node: string id or node reference to test
148                 //      ancestor: string id or node reference of potential parent to test against
149                 //
150                 // example:
151                 //      Test is node id="bar" is a descendant of node id="foo"
152                 //      |       if(dojo.isDescendant("bar", "foo")){ ... }
153                 try{
154                         node = byId(node);
155                         ancestor = byId(ancestor);
156                         while(node){
157                                 if(node == ancestor){
158                                         return true; // Boolean
159                                 }
160                                 node = node.parentNode;
161                         }
162                 }catch(e){ /* squelch, return false */ }
163                 return false; // Boolean
164         };
165
166         dojo.setSelectable = function(/*DomNode|String*/node, /*Boolean*/selectable){
167                 //      summary: 
168                 //              Enable or disable selection on a node
169                 //      node:
170                 //              id or reference to node
171                 //      selectable:
172                 //              state to put the node in. false indicates unselectable, true 
173                 //              allows selection.
174                 //      example:
175                 //      Make the node id="bar" unselectable
176                 //      |       dojo.setSelectable("bar"); 
177                 //      example:
178                 //      Make the node id="bar" selectable
179                 //      |       dojo.setSelectable("bar", true);
180                 node = byId(node);
181                                 if(d.isMozilla){
182                         node.style.MozUserSelect = selectable ? "" : "none";
183                 }else if(d.isKhtml || d.isWebKit){
184                                         node.style.KhtmlUserSelect = selectable ? "auto" : "none";
185                                 }else if(d.isIE){
186                         var v = (node.unselectable = selectable ? "" : "on");
187                         d.query("*", node).forEach("item.unselectable = '"+v+"'");
188                 }
189                                 //FIXME: else?  Opera?
190         };
191
192         var _insertBefore = function(/*DomNode*/node, /*DomNode*/ref){
193                 var parent = ref.parentNode;
194                 if(parent){
195                         parent.insertBefore(node, ref);
196                 }
197         };
198
199         var _insertAfter = function(/*DomNode*/node, /*DomNode*/ref){
200                 //      summary:
201                 //              Try to insert node after ref
202                 var parent = ref.parentNode;
203                 if(parent){
204                         if(parent.lastChild == ref){
205                                 parent.appendChild(node);
206                         }else{
207                                 parent.insertBefore(node, ref.nextSibling);
208                         }
209                 }
210         };
211
212         dojo.place = function(node, refNode, position){
213                 //      summary:
214                 //              Attempt to insert node into the DOM, choosing from various positioning options.
215                 //              Returns the first argument resolved to a DOM node.
216                 //
217                 //      node: String|DomNode
218                 //              id or node reference, or HTML fragment starting with "<" to place relative to refNode
219                 //
220                 //      refNode: String|DomNode
221                 //              id or node reference to use as basis for placement
222                 //
223                 //      position: String|Number?
224                 //              string noting the position of node relative to refNode or a
225                 //              number indicating the location in the childNodes collection of refNode.
226                 //              Accepted string values are:
227                 //      |       * before
228                 //      |       * after
229                 //      |       * replace
230                 //      |       * only
231                 //      |       * first
232                 //      |       * last
233                 //              "first" and "last" indicate positions as children of refNode, "replace" replaces refNode,
234                 //              "only" replaces all children.  position defaults to "last" if not specified
235                 //
236                 //      returns: DomNode
237                 //              Returned values is the first argument resolved to a DOM node.
238                 //
239                 //              .place() is also a method of `dojo.NodeList`, allowing `dojo.query` node lookups.
240                 //
241                 // example:
242                 //              Place a node by string id as the last child of another node by string id:
243                 //      |       dojo.place("someNode", "anotherNode");
244                 //
245                 // example:
246                 //              Place a node by string id before another node by string id
247                 //      |       dojo.place("someNode", "anotherNode", "before");
248                 //
249                 // example:
250                 //              Create a Node, and place it in the body element (last child):
251                 //      |       dojo.place("<div></div>", dojo.body());
252                 //
253                 // example:
254                 //              Put a new LI as the first child of a list by id:
255                 //      |       dojo.place("<li></li>", "someUl", "first");
256
257                 refNode = byId(refNode);
258                 if(typeof node == "string"){ // inline'd type check
259                         node = node.charAt(0) == "<" ? d._toDom(node, refNode.ownerDocument) : byId(node);
260                 }
261                 if(typeof position == "number"){ // inline'd type check
262                         var cn = refNode.childNodes;
263                         if(!cn.length || cn.length <= position){
264                                 refNode.appendChild(node);
265                         }else{
266                                 _insertBefore(node, cn[position < 0 ? 0 : position]);
267                         }
268                 }else{
269                         switch(position){
270                                 case "before":
271                                         _insertBefore(node, refNode);
272                                         break;
273                                 case "after":
274                                         _insertAfter(node, refNode);
275                                         break;
276                                 case "replace":
277                                         refNode.parentNode.replaceChild(node, refNode);
278                                         break;
279                                 case "only":
280                                         d.empty(refNode);
281                                         refNode.appendChild(node);
282                                         break;
283                                 case "first":
284                                         if(refNode.firstChild){
285                                                 _insertBefore(node, refNode.firstChild);
286                                                 break;
287                                         }
288                                         // else fallthrough...
289                                 default: // aka: last
290                                         refNode.appendChild(node);
291                         }
292                 }
293                 return node; // DomNode
294         }
295
296         // Box functions will assume this model.
297         // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode.
298         // Can be set to change behavior of box setters.
299
300         // can be either:
301         //      "border-box"
302         //      "content-box" (default)
303         dojo.boxModel = "content-box";
304
305         // We punt per-node box mode testing completely.
306         // If anybody cares, we can provide an additional (optional) unit 
307         // that overrides existing code to include per-node box sensitivity.
308
309         // Opera documentation claims that Opera 9 uses border-box in BackCompat mode.
310         // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box.
311         // IIRC, earlier versions of Opera did in fact use border-box.
312         // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault.
313
314                 if(d.isIE /*|| dojo.isOpera*/){
315                 // client code may have to adjust if compatMode varies across iframes
316                 d.boxModel = document.compatMode == "BackCompat" ? "border-box" : "content-box";
317         }
318         
319         // =============================
320         // Style Functions
321         // =============================
322
323         // getComputedStyle drives most of the style code.
324         // Wherever possible, reuse the returned object.
325         //
326         // API functions below that need to access computed styles accept an 
327         // optional computedStyle parameter.
328         // If this parameter is omitted, the functions will call getComputedStyle themselves.
329         // This way, calling code can access computedStyle once, and then pass the reference to 
330         // multiple API functions.
331
332 /*=====
333         dojo.getComputedStyle = function(node){
334                 //      summary:
335                 //              Returns a "computed style" object.
336                 //
337                 //      description:
338                 //              Gets a "computed style" object which can be used to gather
339                 //              information about the current state of the rendered node.
340                 //
341                 //              Note that this may behave differently on different browsers.
342                 //              Values may have different formats and value encodings across
343                 //              browsers.
344                 //
345                 //              Note also that this method is expensive.  Wherever possible,
346                 //              reuse the returned object.
347                 //
348                 //              Use the dojo.style() method for more consistent (pixelized)
349                 //              return values.
350                 //
351                 //      node: DOMNode
352                 //              A reference to a DOM node. Does NOT support taking an
353                 //              ID string for speed reasons.
354                 //      example:
355                 //      |       dojo.getComputedStyle(dojo.byId('foo')).borderWidth;
356                 //
357                 //      example:
358                 //      Reusing the returned object, avoiding multiple lookups:
359                 //      |       var cs = dojo.getComputedStyle(dojo.byId("someNode"));
360                 //      |       var w = cs.width, h = cs.height;
361                 return; // CSS2Properties
362         }
363 =====*/
364
365         // Although we normally eschew argument validation at this
366         // level, here we test argument 'node' for (duck)type,
367         // by testing nodeType, ecause 'document' is the 'parentNode' of 'body'
368         // it is frequently sent to this function even 
369         // though it is not Element.
370         var gcs;
371                 if(d.isWebKit){
372                         gcs = function(/*DomNode*/node){
373                         var s;
374                         if(node.nodeType == 1){
375                                 var dv = node.ownerDocument.defaultView;
376                                 s = dv.getComputedStyle(node, null);
377                                 if(!s && node.style){
378                                         node.style.display = "";
379                                         s = dv.getComputedStyle(node, null);
380                                 }
381                         }
382                         return s || {};
383                 };
384                 }else if(d.isIE){
385                 gcs = function(node){
386                         // IE (as of 7) doesn't expose Element like sane browsers
387                         return node.nodeType == 1 /* ELEMENT_NODE*/ ? node.currentStyle : {};
388                 };
389         }else{
390                 gcs = function(node){
391                         return node.nodeType == 1 ?
392                                 node.ownerDocument.defaultView.getComputedStyle(node, null) : {};
393                 };
394         }
395                 dojo.getComputedStyle = gcs;
396
397                 if(!d.isIE){
398                         d._toPixelValue = function(element, value){
399                         // style values can be floats, client code may want
400                         // to round for integer pixels.
401                         return parseFloat(value) || 0;
402                 };
403                 }else{
404                 d._toPixelValue = function(element, avalue){
405                         if(!avalue){ return 0; }
406                         // on IE7, medium is usually 4 pixels
407                         if(avalue == "medium"){ return 4; }
408                         // style values can be floats, client code may
409                         // want to round this value for integer pixels.
410                         if(avalue.slice && avalue.slice(-2) == 'px'){ return parseFloat(avalue); }
411                         with(element){
412                                 var sLeft = style.left;
413                                 var rsLeft = runtimeStyle.left;
414                                 runtimeStyle.left = currentStyle.left;
415                                 try{
416                                         // 'avalue' may be incompatible with style.left, which can cause IE to throw
417                                         // this has been observed for border widths using "thin", "medium", "thick" constants
418                                         // those particular constants could be trapped by a lookup
419                                         // but perhaps there are more
420                                         style.left = avalue;
421                                         avalue = style.pixelLeft;
422                                 }catch(e){
423                                         avalue = 0;
424                                 }
425                                 style.left = sLeft;
426                                 runtimeStyle.left = rsLeft;
427                         }
428                         return avalue;
429                 }
430         }
431                 var px = d._toPixelValue;
432
433         // FIXME: there opacity quirks on FF that we haven't ported over. Hrm.
434         /*=====
435         dojo._getOpacity = function(node){
436                         //      summary:
437                         //              Returns the current opacity of the passed node as a
438                         //              floating-point value between 0 and 1.
439                         //      node: DomNode
440                         //              a reference to a DOM node. Does NOT support taking an
441                         //              ID string for speed reasons.
442                         //      returns: Number between 0 and 1
443                         return; // Number
444         }
445         =====*/
446
447                 var astr = "DXImageTransform.Microsoft.Alpha";
448         var af = function(n, f){
449                 try{
450                         return n.filters.item(astr);
451                 }catch(e){
452                         return f ? {} : null;
453                 }
454         };
455
456                 dojo._getOpacity =
457                         d.isIE ? function(node){
458                         try{
459                                 return af(node).Opacity / 100; // Number
460                         }catch(e){
461                                 return 1; // Number
462                         }
463                 } :
464                         function(node){
465                         return gcs(node).opacity;
466                 };
467
468         /*=====
469         dojo._setOpacity = function(node, opacity){
470                         //      summary:
471                         //              set the opacity of the passed node portably. Returns the
472                         //              new opacity of the node.
473                         //      node: DOMNode
474                         //              a reference to a DOM node. Does NOT support taking an
475                         //              ID string for performance reasons.
476                         //      opacity: Number
477                         //              A Number between 0 and 1. 0 specifies transparent.
478                         //      returns: Number between 0 and 1
479                         return; // Number
480         }
481         =====*/
482
483         dojo._setOpacity =
484                                 d.isIE ? function(/*DomNode*/node, /*Number*/opacity){
485                         var ov = opacity * 100, opaque = opacity == 1;
486                         node.style.zoom = opaque ? "" : 1;
487
488                         if(!af(node)){
489                                 if(opaque){
490                                         return opacity;
491                                 }
492                                 node.style.filter += " progid:" + astr + "(Opacity=" + ov + ")";
493                         }else{
494                                 af(node, 1).Opacity = ov;
495                         }
496
497                         // on IE7 Alpha(Filter opacity=100) makes text look fuzzy so disable it altogether (bug #2661),
498                         //but still update the opacity value so we can get a correct reading if it is read later.
499                         af(node, 1).Enabled = !opaque;
500
501                         if(node.nodeName.toLowerCase() == "tr"){
502                                 d.query("> td", node).forEach(function(i){
503                                         d._setOpacity(i, opacity);
504                                 });
505                         }
506                         return opacity;
507                 } :
508                                 function(node, opacity){
509                         return node.style.opacity = opacity;
510                 };
511
512         var _pixelNamesCache = {
513                 left: true, top: true
514         };
515         var _pixelRegExp = /margin|padding|width|height|max|min|offset/;  // |border
516         var _toStyleValue = function(node, type, value){
517                 type = type.toLowerCase(); // FIXME: should we really be doing string case conversion here? Should we cache it? Need to profile!
518                                 if(d.isIE){
519                         if(value == "auto"){
520                                 if(type == "height"){ return node.offsetHeight; }
521                                 if(type == "width"){ return node.offsetWidth; }
522                         }
523                         if(type == "fontweight"){
524                                 switch(value){
525                                         case 700: return "bold";
526                                         case 400:
527                                         default: return "normal";
528                                 }
529                         }
530                 }
531                                 if(!(type in _pixelNamesCache)){
532                         _pixelNamesCache[type] = _pixelRegExp.test(type);
533                 }
534                 return _pixelNamesCache[type] ? px(node, value) : value;
535         };
536
537         var _floatStyle = d.isIE ? "styleFloat" : "cssFloat",
538                 _floatAliases = { "cssFloat": _floatStyle, "styleFloat": _floatStyle, "float": _floatStyle }
539         ;
540
541         // public API
542
543         dojo.style = function(  /*DomNode|String*/ node,
544                                                         /*String?|Object?*/ style,
545                                                         /*String?*/ value){
546                 //      summary:
547                 //              Accesses styles on a node. If 2 arguments are
548                 //              passed, acts as a getter. If 3 arguments are passed, acts
549                 //              as a setter.
550                 //      description:
551                 //              Getting the style value uses the computed style for the node, so the value
552                 //              will be a calculated value, not just the immediate node.style value.
553                 //              Also when getting values, use specific style names,
554                 //              like "borderBottomWidth" instead of "border" since compound values like
555                 //              "border" are not necessarily reflected as expected.
556                 //              If you want to get node dimensions, use `dojo.marginBox()`, 
557                 //              `dojo.contentBox()` or `dojo.position()`.
558                 //      node:
559                 //              id or reference to node to get/set style for
560                 //      style:
561                 //              the style property to set in DOM-accessor format
562                 //              ("borderWidth", not "border-width") or an object with key/value
563                 //              pairs suitable for setting each property.
564                 //      value:
565                 //              If passed, sets value on the node for style, handling
566                 //              cross-browser concerns.  When setting a pixel value,
567                 //              be sure to include "px" in the value. For instance, top: "200px".
568                 //              Otherwise, in some cases, some browsers will not apply the style.
569                 //      example:
570                 //              Passing only an ID or node returns the computed style object of
571                 //              the node:
572                 //      |       dojo.style("thinger");
573                 //      example:
574                 //              Passing a node and a style property returns the current
575                 //              normalized, computed value for that property:
576                 //      |       dojo.style("thinger", "opacity"); // 1 by default
577                 //
578                 //      example:
579                 //              Passing a node, a style property, and a value changes the
580                 //              current display of the node and returns the new computed value
581                 //      |       dojo.style("thinger", "opacity", 0.5); // == 0.5
582                 //
583                 //      example:
584                 //              Passing a node, an object-style style property sets each of the values in turn and returns the computed style object of the node:
585                 //      |       dojo.style("thinger", {
586                 //      |               "opacity": 0.5,
587                 //      |               "border": "3px solid black",
588                 //      |               "height": "300px"
589                 //      |       });
590                 //
591                 //      example:
592                 //              When the CSS style property is hyphenated, the JavaScript property is camelCased.
593                 //              font-size becomes fontSize, and so on.
594                 //      |       dojo.style("thinger",{
595                 //      |               fontSize:"14pt",
596                 //      |               letterSpacing:"1.2em"
597                 //      |       });
598                 //
599                 //      example:
600                 //              dojo.NodeList implements .style() using the same syntax, omitting the "node" parameter, calling
601                 //              dojo.style() on every element of the list. See: `dojo.query()` and `dojo.NodeList()`
602                 //      |       dojo.query(".someClassName").style("visibility","hidden");
603                 //      |       // or
604                 //      |       dojo.query("#baz > div").style({
605                 //      |               opacity:0.75,
606                 //      |               fontSize:"13pt"
607                 //      |       });
608
609                 var n = byId(node), args = arguments.length, op = (style == "opacity");
610                 style = _floatAliases[style] || style;
611                 if(args == 3){
612                         return op ? d._setOpacity(n, value) : n.style[style] = value; /*Number*/
613                 }
614                 if(args == 2 && op){
615                         return d._getOpacity(n);
616                 }
617                 var s = gcs(n);
618                 if(args == 2 && typeof style != "string"){ // inline'd type check
619                         for(var x in style){
620                                 d.style(node, x, style[x]);
621                         }
622                         return s;
623                 }
624                 return (args == 1) ? s : _toStyleValue(n, style, s[style] || n.style[style]); /* CSS2Properties||String||Number */
625         }
626
627         // =============================
628         // Box Functions
629         // =============================
630
631         dojo._getPadExtents = function(/*DomNode*/n, /*Object*/computedStyle){
632                 //      summary:
633                 //              Returns object with special values specifically useful for node
634                 //              fitting.
635                 //      description:
636                 //              Returns an object with `w`, `h`, `l`, `t` properties:
637                 //      |               l/t = left/top padding (respectively)
638                 //      |               w = the total of the left and right padding 
639                 //      |               h = the total of the top and bottom padding
640                 //              If 'node' has position, l/t forms the origin for child nodes.
641                 //              The w/h are used for calculating boxes.
642                 //              Normally application code will not need to invoke this
643                 //              directly, and will use the ...box... functions instead.
644                 var 
645                         s = computedStyle||gcs(n),
646                         l = px(n, s.paddingLeft),
647                         t = px(n, s.paddingTop);
648                 return {
649                         l: l,
650                         t: t,
651                         w: l+px(n, s.paddingRight),
652                         h: t+px(n, s.paddingBottom)
653                 };
654         }
655
656         dojo._getBorderExtents = function(/*DomNode*/n, /*Object*/computedStyle){
657                 //      summary:
658                 //              returns an object with properties useful for noting the border
659                 //              dimensions.
660                 //      description:
661                 //              * l/t = the sum of left/top border (respectively)
662                 //              * w = the sum of the left and right border
663                 //              * h = the sum of the top and bottom border
664                 //
665                 //              The w/h are used for calculating boxes.
666                 //              Normally application code will not need to invoke this
667                 //              directly, and will use the ...box... functions instead.
668                 var 
669                         ne = "none",
670                         s = computedStyle||gcs(n),
671                         bl = (s.borderLeftStyle != ne ? px(n, s.borderLeftWidth) : 0),
672                         bt = (s.borderTopStyle != ne ? px(n, s.borderTopWidth) : 0);
673                 return {
674                         l: bl,
675                         t: bt,
676                         w: bl + (s.borderRightStyle!=ne ? px(n, s.borderRightWidth) : 0),
677                         h: bt + (s.borderBottomStyle!=ne ? px(n, s.borderBottomWidth) : 0)
678                 };
679         }
680
681         dojo._getPadBorderExtents = function(/*DomNode*/n, /*Object*/computedStyle){
682                 //      summary:
683                 //              Returns object with properties useful for box fitting with
684                 //              regards to padding.
685                 // description:
686                 //              * l/t = the sum of left/top padding and left/top border (respectively)
687                 //              * w = the sum of the left and right padding and border
688                 //              * h = the sum of the top and bottom padding and border
689                 //
690                 //              The w/h are used for calculating boxes.
691                 //              Normally application code will not need to invoke this
692                 //              directly, and will use the ...box... functions instead.
693                 var 
694                         s = computedStyle||gcs(n),
695                         p = d._getPadExtents(n, s),
696                         b = d._getBorderExtents(n, s);
697                 return {
698                         l: p.l + b.l,
699                         t: p.t + b.t,
700                         w: p.w + b.w,
701                         h: p.h + b.h
702                 };
703         }
704
705         dojo._getMarginExtents = function(n, computedStyle){
706                 //      summary:
707                 //              returns object with properties useful for box fitting with
708                 //              regards to box margins (i.e., the outer-box).
709                 //
710                 //              * l/t = marginLeft, marginTop, respectively
711                 //              * w = total width, margin inclusive
712                 //              * h = total height, margin inclusive
713                 //
714                 //              The w/h are used for calculating boxes.
715                 //              Normally application code will not need to invoke this
716                 //              directly, and will use the ...box... functions instead.
717                 var 
718                         s = computedStyle||gcs(n),
719                         l = px(n, s.marginLeft),
720                         t = px(n, s.marginTop),
721                         r = px(n, s.marginRight),
722                         b = px(n, s.marginBottom);
723                 if(d.isWebKit && (s.position != "absolute")){
724                         // FIXME: Safari's version of the computed right margin
725                         // is the space between our right edge and the right edge 
726                         // of our offsetParent.
727                         // What we are looking for is the actual margin value as 
728                         // determined by CSS.
729                         // Hack solution is to assume left/right margins are the same.
730                         r = l;
731                 }
732                 return {
733                         l: l,
734                         t: t,
735                         w: l+r,
736                         h: t+b
737                 };
738         }
739
740         // Box getters work in any box context because offsetWidth/clientWidth
741         // are invariant wrt box context
742         //
743         // They do *not* work for display: inline objects that have padding styles
744         // because the user agent ignores padding (it's bogus styling in any case)
745         //
746         // Be careful with IMGs because they are inline or block depending on 
747         // browser and browser mode.
748
749         // Although it would be easier to read, there are not separate versions of 
750         // _getMarginBox for each browser because:
751         // 1. the branching is not expensive
752         // 2. factoring the shared code wastes cycles (function call overhead)
753         // 3. duplicating the shared code wastes bytes
754
755         dojo._getMarginBox = function(/*DomNode*/node, /*Object*/computedStyle){
756                 // summary:
757                 //              returns an object that encodes the width, height, left and top
758                 //              positions of the node's margin box.
759                 var s = computedStyle || gcs(node), me = d._getMarginExtents(node, s);
760                 var l = node.offsetLeft - me.l, t = node.offsetTop - me.t, p = node.parentNode;
761                                 if(d.isMoz){
762                         // Mozilla:
763                         // If offsetParent has a computed overflow != visible, the offsetLeft is decreased
764                         // by the parent's border.
765                         // We don't want to compute the parent's style, so instead we examine node's
766                         // computed left/top which is more stable.
767                         var sl = parseFloat(s.left), st = parseFloat(s.top);
768                         if(!isNaN(sl) && !isNaN(st)){
769                                 l = sl, t = st;
770                         }else{
771                                 // If child's computed left/top are not parseable as a number (e.g. "auto"), we
772                                 // have no choice but to examine the parent's computed style.
773                                 if(p && p.style){
774                                         var pcs = gcs(p);
775                                         if(pcs.overflow != "visible"){
776                                                 var be = d._getBorderExtents(p, pcs);
777                                                 l += be.l, t += be.t;
778                                         }
779                                 }
780                         }
781                 }else if(d.isOpera || (d.isIE > 7 && !d.isQuirks)){
782                         // On Opera and IE 8, offsetLeft/Top includes the parent's border
783                         if(p){
784                                 be = d._getBorderExtents(p);
785                                 l -= be.l;
786                                 t -= be.t;
787                         }
788                 }
789                                 return {
790                         l: l,
791                         t: t,
792                         w: node.offsetWidth + me.w,
793                         h: node.offsetHeight + me.h 
794                 };
795         }
796
797         dojo._getContentBox = function(node, computedStyle){
798                 // summary:
799                 //              Returns an object that encodes the width, height, left and top
800                 //              positions of the node's content box, irrespective of the
801                 //              current box model.
802
803                 // clientWidth/Height are important since the automatically account for scrollbars
804                 // fallback to offsetWidth/Height for special cases (see #3378)
805                 var s = computedStyle || gcs(node),
806                         pe = d._getPadExtents(node, s),
807                         be = d._getBorderExtents(node, s),
808                         w = node.clientWidth,
809                         h
810                 ;
811                 if(!w){
812                         w = node.offsetWidth, h = node.offsetHeight;
813                 }else{
814                         h = node.clientHeight, be.w = be.h = 0;
815                 }
816                 // On Opera, offsetLeft includes the parent's border
817                                 if(d.isOpera){ pe.l += be.l; pe.t += be.t; };
818                                 return {
819                         l: pe.l,
820                         t: pe.t,
821                         w: w - pe.w - be.w,
822                         h: h - pe.h - be.h
823                 };
824         }
825
826         dojo._getBorderBox = function(node, computedStyle){
827                 var s = computedStyle || gcs(node),
828                         pe = d._getPadExtents(node, s),
829                         cb = d._getContentBox(node, s)
830                 ;
831                 return {
832                         l: cb.l - pe.l,
833                         t: cb.t - pe.t,
834                         w: cb.w + pe.w,
835                         h: cb.h + pe.h
836                 };
837         }
838
839         // Box setters depend on box context because interpretation of width/height styles
840         // vary wrt box context.
841         //
842         // The value of dojo.boxModel is used to determine box context.
843         // dojo.boxModel can be set directly to change behavior.
844         //
845         // Beware of display: inline objects that have padding styles
846         // because the user agent ignores padding (it's a bogus setup anyway)
847         //
848         // Be careful with IMGs because they are inline or block depending on 
849         // browser and browser mode.
850         //
851         // Elements other than DIV may have special quirks, like built-in
852         // margins or padding, or values not detectable via computedStyle.
853         // In particular, margins on TABLE do not seems to appear 
854         // at all in computedStyle on Mozilla.
855
856         dojo._setBox = function(/*DomNode*/node, /*Number?*/l, /*Number?*/t, /*Number?*/w, /*Number?*/h, /*String?*/u){
857                 //      summary:
858                 //              sets width/height/left/top in the current (native) box-model
859                 //              dimentions. Uses the unit passed in u.
860                 //      node:
861                 //              DOM Node reference. Id string not supported for performance
862                 //              reasons.
863                 //      l:
864                 //              left offset from parent.
865                 //      t:
866                 //              top offset from parent.
867                 //      w:
868                 //              width in current box model.
869                 //      h:
870                 //              width in current box model.
871                 //      u:
872                 //              unit measure to use for other measures. Defaults to "px".
873                 u = u || "px";
874                 var s = node.style;
875                 if(!isNaN(l)){ s.left = l + u; }
876                 if(!isNaN(t)){ s.top = t + u; }
877                 if(w >= 0){ s.width = w + u; }
878                 if(h >= 0){ s.height = h + u; }
879         }
880
881         dojo._isButtonTag = function(/*DomNode*/node) {
882                 // summary:
883                 //              True if the node is BUTTON or INPUT.type="button".
884                 return node.tagName == "BUTTON"
885                         || node.tagName=="INPUT" && (node.getAttribute("type")||'').toUpperCase() == "BUTTON"; // boolean
886         }
887
888         dojo._usesBorderBox = function(/*DomNode*/node){
889                 //      summary:
890                 //              True if the node uses border-box layout.
891
892                 // We could test the computed style of node to see if a particular box
893                 // has been specified, but there are details and we choose not to bother.
894
895                 // TABLE and BUTTON (and INPUT type=button) are always border-box by default.
896                 // If you have assigned a different box to either one via CSS then
897                 // box functions will break.
898
899                 var n = node.tagName;
900                 return d.boxModel=="border-box" || n=="TABLE" || d._isButtonTag(node); // boolean
901         }
902
903         dojo._setContentSize = function(/*DomNode*/node, /*Number*/widthPx, /*Number*/heightPx, /*Object*/computedStyle){
904                 //      summary:
905                 //              Sets the size of the node's contents, irrespective of margins,
906                 //              padding, or borders.
907                 if(d._usesBorderBox(node)){
908                         var pb = d._getPadBorderExtents(node, computedStyle);
909                         if(widthPx >= 0){ widthPx += pb.w; }
910                         if(heightPx >= 0){ heightPx += pb.h; }
911                 }
912                 d._setBox(node, NaN, NaN, widthPx, heightPx);
913         }
914
915         dojo._setMarginBox = function(/*DomNode*/node,  /*Number?*/leftPx, /*Number?*/topPx,
916                                                                                                         /*Number?*/widthPx, /*Number?*/heightPx,
917                                                                                                         /*Object*/computedStyle){
918                 //      summary:
919                 //              sets the size of the node's margin box and placement
920                 //              (left/top), irrespective of box model. Think of it as a
921                 //              passthrough to dojo._setBox that handles box-model vagaries for
922                 //              you.
923
924                 var s = computedStyle || gcs(node),
925                 // Some elements have special padding, margin, and box-model settings.
926                 // To use box functions you may need to set padding, margin explicitly.
927                 // Controlling box-model is harder, in a pinch you might set dojo.boxModel.
928                         bb = d._usesBorderBox(node),
929                         pb = bb ? _nilExtents : d._getPadBorderExtents(node, s)
930                 ;
931                 if(d.isWebKit){
932                         // on Safari (3.1.2), button nodes with no explicit size have a default margin
933                         // setting an explicit size eliminates the margin.
934                         // We have to swizzle the width to get correct margin reading.
935                         if(d._isButtonTag(node)){
936                                 var ns = node.style;
937                                 if(widthPx >= 0 && !ns.width) { ns.width = "4px"; }
938                                 if(heightPx >= 0 && !ns.height) { ns.height = "4px"; }
939                         }
940                 }
941                 var mb = d._getMarginExtents(node, s);
942                 if(widthPx >= 0){ widthPx = Math.max(widthPx - pb.w - mb.w, 0); }
943                 if(heightPx >= 0){ heightPx = Math.max(heightPx - pb.h - mb.h, 0); }
944                 d._setBox(node, leftPx, topPx, widthPx, heightPx);
945         }
946
947         var _nilExtents = { l:0, t:0, w:0, h:0 };
948
949         // public API
950
951         dojo.marginBox = function(/*DomNode|String*/node, /*Object?*/box){
952                 //      summary:
953                 //              Getter/setter for the margin-box of node.
954                 //      description:
955                 //              Getter/setter for the margin-box of node.
956                 //              Returns an object in the expected format of box (regardless
957                 //              if box is passed). The object might look like:
958                 //                      `{ l: 50, t: 200, w: 300: h: 150 }`
959                 //              for a node offset from its parent 50px to the left, 200px from
960                 //              the top with a margin width of 300px and a margin-height of
961                 //              150px.
962                 //      node:
963                 //              id or reference to DOM Node to get/set box for
964                 //      box:
965                 //              If passed, denotes that dojo.marginBox() should
966                 //              update/set the margin box for node. Box is an object in the
967                 //              above format. All properties are optional if passed.
968                 //      example:
969                 //      Retrieve the marginbox of a passed node
970                 //      |       var box = dojo.marginBox("someNodeId");
971                 //      |       console.dir(box);
972                 //
973                 //      example:
974                 //      Set a node's marginbox to the size of another node
975                 //      |       var box = dojo.marginBox("someNodeId");
976                 //      |       dojo.marginBox("someOtherNode", box);
977                 
978                 var n = byId(node), s = gcs(n), b = box;
979                 return !b ? d._getMarginBox(n, s) : d._setMarginBox(n, b.l, b.t, b.w, b.h, s); // Object
980         }
981
982         dojo.contentBox = function(/*DomNode|String*/node, /*Object?*/box){
983                 //      summary:
984                 //              Getter/setter for the content-box of node.
985                 //      description:
986                 //              Returns an object in the expected format of box (regardless if box is passed).
987                 //              The object might look like:
988                 //                      `{ l: 50, t: 200, w: 300: h: 150 }`
989                 //              for a node offset from its parent 50px to the left, 200px from
990                 //              the top with a content width of 300px and a content-height of
991                 //              150px. Note that the content box may have a much larger border
992                 //              or margin box, depending on the box model currently in use and
993                 //              CSS values set/inherited for node.
994                 //              While the getter will return top and left values, the
995                 //              setter only accepts setting the width and height.
996                 //      node:
997                 //              id or reference to DOM Node to get/set box for
998                 //      box:
999                 //              If passed, denotes that dojo.contentBox() should
1000                 //              update/set the content box for node. Box is an object in the
1001                 //              above format, but only w (width) and h (height) are supported.
1002                 //              All properties are optional if passed.
1003                 var n = byId(node), s = gcs(n), b = box;
1004                 return !b ? d._getContentBox(n, s) : d._setContentSize(n, b.w, b.h, s); // Object
1005         }
1006
1007         // =============================
1008         // Positioning 
1009         // =============================
1010
1011         var _sumAncestorProperties = function(node, prop){
1012                 if(!(node = (node||0).parentNode)){return 0}
1013                 var val, retVal = 0, _b = d.body();
1014                 while(node && node.style){
1015                         if(gcs(node).position == "fixed"){
1016                                 return 0;
1017                         }
1018                         val = node[prop];
1019                         if(val){
1020                                 retVal += val - 0;
1021                                 // opera and khtml #body & #html has the same values, we only
1022                                 // need one value
1023                                 if(node == _b){ break; }
1024                         }
1025                         node = node.parentNode;
1026                 }
1027                 return retVal;  //      integer
1028         }
1029
1030         dojo._docScroll = function(){
1031                 var n = d.global;
1032                 return "pageXOffset" in n? { x:n.pageXOffset, y:n.pageYOffset } :
1033                         (n=d.doc.documentElement, n.clientHeight? { x:d._fixIeBiDiScrollLeft(n.scrollLeft), y:n.scrollTop } :
1034                         (n=d.body(), { x:n.scrollLeft||0, y:n.scrollTop||0 }));
1035         };
1036
1037         dojo._isBodyLtr = function(){
1038                 return "_bodyLtr" in d? d._bodyLtr :
1039                         d._bodyLtr = (d.body().dir || d.doc.documentElement.dir || "ltr").toLowerCase() == "ltr"; // Boolean 
1040         }
1041
1042                 dojo._getIeDocumentElementOffset = function(){
1043                 //      summary:
1044                 //              returns the offset in x and y from the document body to the
1045                 //              visual edge of the page
1046                 //      description:
1047                 // The following values in IE contain an offset:
1048                 //      |               event.clientX
1049                 //      |               event.clientY
1050                 //      |               node.getBoundingClientRect().left
1051                 //      |               node.getBoundingClientRect().top
1052                 //              But other position related values do not contain this offset,
1053                 //              such as node.offsetLeft, node.offsetTop, node.style.left and
1054                 //              node.style.top. The offset is always (2, 2) in LTR direction.
1055                 //              When the body is in RTL direction, the offset counts the width
1056                 //              of left scroll bar's width.  This function computes the actual
1057                 //              offset.
1058
1059                 //NOTE: assumes we're being called in an IE browser
1060
1061                 var de = d.doc.documentElement; // only deal with HTML element here, _abs handles body/quirks 
1062
1063                 if(d.isIE < 8){
1064                         var r = de.getBoundingClientRect(); // works well for IE6+
1065                         //console.debug('rect left,top = ' + r.left+','+r.top + ', html client left/top = ' + de.clientLeft+','+de.clientTop + ', rtl = ' + (!d._isBodyLtr()) + ', quirks = ' + d.isQuirks);
1066                         var l = r.left,
1067                             t = r.top;
1068                         if(d.isIE < 7){
1069                                 l += de.clientLeft;     // scrollbar size in strict/RTL, or,
1070                                 t += de.clientTop;      // HTML border size in strict
1071                         }
1072                         return {
1073                                 x: l < 0? 0 : l, // FRAME element border size can lead to inaccurate negative values
1074                                 y: t < 0? 0 : t
1075                         };
1076                 }else{
1077                         return {
1078                                 x: 0,
1079                                 y: 0
1080                         };
1081                 }
1082
1083         };
1084         
1085         dojo._fixIeBiDiScrollLeft = function(/*Integer*/ scrollLeft){
1086                 // In RTL direction, scrollLeft should be a negative value, but IE < 8
1087                 // returns a positive one. All codes using documentElement.scrollLeft
1088                 // must call this function to fix this error, otherwise the position
1089                 // will offset to right when there is a horizontal scrollbar.
1090
1091                                 var dd = d.doc;
1092                 if(d.isIE < 8 && !d._isBodyLtr()){
1093                         var de = d.isQuirks ? dd.body : dd.documentElement;
1094                         return scrollLeft + de.clientWidth - de.scrollWidth; // Integer
1095                 }
1096                                 return scrollLeft; // Integer
1097         }
1098
1099         // FIXME: need a setter for coords or a moveTo!!
1100         dojo._abs = dojo.position = function(/*DomNode*/node, /*Boolean?*/includeScroll){
1101                 //      summary:
1102                 //              Gets the position and size of the passed element relative to
1103                 //              the viewport (if includeScroll==false), or relative to the
1104                 //              document root (if includeScroll==true).
1105                 //
1106                 //      description:
1107                 //              Returns an object of the form:
1108                 //                      { x: 100, y: 300, w: 20, h: 15 }
1109                 //              If includeScroll==true, the x and y values will include any
1110                 //              document offsets that may affect the position relative to the
1111                 //              viewport.
1112                 //              Uses the border-box model (inclusive of border and padding but
1113                 //              not margin).  Does not act as a setter.
1114
1115                 var db = d.body(), dh = db.parentNode, ret;
1116                 node = byId(node);
1117                 if(node["getBoundingClientRect"]){
1118                         // IE6+, FF3+, super-modern WebKit, and Opera 9.6+ all take this branch
1119                         ret = node.getBoundingClientRect();
1120                         ret = { x: ret.left, y: ret.top, w: ret.right - ret.left, h: ret.bottom - ret.top };
1121                                         if(d.isIE){
1122                                 // On IE there's a 2px offset that we need to adjust for, see _getIeDocumentElementOffset()
1123                                 var offset = d._getIeDocumentElementOffset();
1124
1125                                 // fixes the position in IE, quirks mode
1126                                 ret.x -= offset.x + (d.isQuirks ? db.clientLeft+db.offsetLeft : 0);
1127                                 ret.y -= offset.y + (d.isQuirks ? db.clientTop+db.offsetTop : 0);
1128                         }else if(d.isFF == 3){
1129                                 // In FF3 you have to subtract the document element margins.
1130                                 // Fixed in FF3.5 though.
1131                                 var cs = gcs(dh);
1132                                 ret.x -= px(dh, cs.marginLeft) + px(dh, cs.borderLeftWidth);
1133                                 ret.y -= px(dh, cs.marginTop) + px(dh, cs.borderTopWidth);
1134                         }
1135                                 }else{
1136                         // FF2 and older WebKit
1137                         ret = {
1138                                 x: 0,
1139                                 y: 0,
1140                                 w: node.offsetWidth,
1141                                 h: node.offsetHeight
1142                         };
1143                         if(node["offsetParent"]){
1144                                 ret.x -= _sumAncestorProperties(node, "scrollLeft");
1145                                 ret.y -= _sumAncestorProperties(node, "scrollTop");
1146
1147                                 var curnode = node;
1148                                 do{
1149                                         var n = curnode.offsetLeft,
1150                                                 t = curnode.offsetTop;
1151                                         ret.x += isNaN(n) ? 0 : n;
1152                                         ret.y += isNaN(t) ? 0 : t;
1153
1154                                         cs = gcs(curnode);
1155                                         if(curnode != node){
1156                                                                 if(d.isMoz){
1157                                                         // tried left+right with differently sized left/right borders
1158                                                         // it really is 2xleft border in FF, not left+right, even in RTL!
1159                                                         ret.x += 2 * px(curnode,cs.borderLeftWidth);
1160                                                         ret.y += 2 * px(curnode,cs.borderTopWidth);
1161                                                 }else{
1162                                                                         ret.x += px(curnode, cs.borderLeftWidth);
1163                                                         ret.y += px(curnode, cs.borderTopWidth);
1164                                                                 }
1165                                                         }
1166                                         // static children in a static div in FF2 are affected by the div's border as well
1167                                         // but offsetParent will skip this div!
1168                                                         if(d.isMoz && cs.position=="static"){
1169                                                 var parent=curnode.parentNode;
1170                                                 while(parent!=curnode.offsetParent){
1171                                                         var pcs=gcs(parent);
1172                                                         if(pcs.position=="static"){
1173                                                                 ret.x += px(curnode,pcs.borderLeftWidth);
1174                                                                 ret.y += px(curnode,pcs.borderTopWidth);
1175                                                         }
1176                                                         parent=parent.parentNode;
1177                                                 }
1178                                         }
1179                                                         curnode = curnode.offsetParent;
1180                                 }while((curnode != dh) && curnode);
1181                         }else if(node.x && node.y){
1182                                 ret.x += isNaN(node.x) ? 0 : node.x;
1183                                 ret.y += isNaN(node.y) ? 0 : node.y;
1184                         }
1185                 }
1186                 // account for document scrolling
1187                 // if offsetParent is used, ret value already includes scroll position
1188                 // so we may have to actually remove that value if !includeScroll
1189                 if(includeScroll){
1190                         var scroll = d._docScroll();
1191                         ret.x += scroll.x;
1192                         ret.y += scroll.y;
1193                 }
1194
1195                 return ret; // Object
1196         }
1197
1198         dojo.coords = function(/*DomNode|String*/node, /*Boolean?*/includeScroll){
1199                 //      summary:
1200                 //              Deprecated: Use position() for border-box x/y/w/h
1201                 //              or marginBox() for margin-box w/h/l/t.
1202                 //              Returns an object representing a node's size and position.
1203                 //
1204                 //      description:
1205                 //              Returns an object that measures margin-box (w)idth/(h)eight
1206                 //              and absolute position x/y of the border-box. Also returned
1207                 //              is computed (l)eft and (t)op values in pixels from the
1208                 //              node's offsetParent as returned from marginBox().
1209                 //              Return value will be in the form:
1210                 //|                     { l: 50, t: 200, w: 300: h: 150, x: 100, y: 300 }
1211                 //              Does not act as a setter. If includeScroll is passed, the x and
1212                 //              y params are affected as one would expect in dojo.position().
1213                 var n = byId(node), s = gcs(n), mb = d._getMarginBox(n, s);
1214                 var abs = d.position(n, includeScroll);
1215                 mb.x = abs.x;
1216                 mb.y = abs.y;
1217                 return mb;
1218         }
1219
1220         // =============================
1221         // Element attribute Functions
1222         // =============================
1223
1224         // dojo.attr() should conform to http://www.w3.org/TR/DOM-Level-2-Core/
1225
1226         var _propNames = {
1227                         // properties renamed to avoid clashes with reserved words
1228                         "class":   "className",
1229                         "for":     "htmlFor",
1230                         // properties written as camelCase
1231                         tabindex:  "tabIndex",
1232                         readonly:  "readOnly",
1233                         colspan:   "colSpan",
1234                         frameborder: "frameBorder",
1235                         rowspan:   "rowSpan",
1236                         valuetype: "valueType"
1237                 },
1238                 _attrNames = {
1239                         // original attribute names
1240                         classname: "class",
1241                         htmlfor:   "for",
1242                         // for IE
1243                         tabindex:  "tabIndex",
1244                         readonly:  "readOnly"
1245                 },
1246                 _forcePropNames = {
1247                         innerHTML: 1,
1248                         className: 1,
1249                         htmlFor:   d.isIE,
1250                         value:     1
1251                 };
1252
1253         var _fixAttrName = function(/*String*/ name){
1254                 return _attrNames[name.toLowerCase()] || name;
1255         };
1256
1257         var _hasAttr = function(node, name){
1258                 var attr = node.getAttributeNode && node.getAttributeNode(name);
1259                 return attr && attr.specified; // Boolean
1260         };
1261
1262         // There is a difference in the presence of certain properties and their default values
1263         // between browsers. For example, on IE "disabled" is present on all elements,
1264         // but it is value is "false"; "tabIndex" of <div> returns 0 by default on IE, yet other browsers
1265         // can return -1.
1266
1267         dojo.hasAttr = function(/*DomNode|String*/node, /*String*/name){
1268                 //      summary:
1269                 //              Returns true if the requested attribute is specified on the
1270                 //              given element, and false otherwise.
1271                 //      node:
1272                 //              id or reference to the element to check
1273                 //      name:
1274                 //              the name of the attribute
1275                 //      returns:
1276                 //              true if the requested attribute is specified on the
1277                 //              given element, and false otherwise
1278                 var lc = name.toLowerCase();
1279                 return _forcePropNames[_propNames[lc] || name] || _hasAttr(byId(node), _attrNames[lc] || name); // Boolean
1280         }
1281
1282         var _evtHdlrMap = {}, _ctr = 0,
1283                 _attrId = dojo._scopeName + "attrid",
1284                 // the next dictionary lists elements with read-only innerHTML on IE
1285                 _roInnerHtml = {col: 1, colgroup: 1,
1286                         // frameset: 1, head: 1, html: 1, style: 1,
1287                         table: 1, tbody: 1, tfoot: 1, thead: 1, tr: 1, title: 1};
1288
1289         dojo.attr = function(/*DomNode|String*/node, /*String|Object*/name, /*String?*/value){
1290                 //      summary:
1291                 //              Gets or sets an attribute on an HTML element.
1292                 //      description:
1293                 //              Handles normalized getting and setting of attributes on DOM
1294                 //              Nodes. If 2 arguments are passed, and a the second argumnt is a
1295                 //              string, acts as a getter.
1296                 //
1297                 //              If a third argument is passed, or if the second argument is a
1298                 //              map of attributes, acts as a setter.
1299                 //
1300                 //              When passing functions as values, note that they will not be
1301                 //              directly assigned to slots on the node, but rather the default
1302                 //              behavior will be removed and the new behavior will be added
1303                 //              using `dojo.connect()`, meaning that event handler properties
1304                 //              will be normalized and that some caveats with regards to
1305                 //              non-standard behaviors for onsubmit apply. Namely that you
1306                 //              should cancel form submission using `dojo.stopEvent()` on the
1307                 //              passed event object instead of returning a boolean value from
1308                 //              the handler itself.
1309                 //      node:
1310                 //              id or reference to the element to get or set the attribute on
1311                 //      name:
1312                 //              the name of the attribute to get or set.
1313                 //      value:
1314                 //              The value to set for the attribute
1315                 //      returns:
1316                 //              when used as a getter, the value of the requested attribute
1317                 //              or null if that attribute does not have a specified or
1318                 //              default value;
1319                 //
1320                 //              when used as a setter, the DOM node
1321                 //
1322                 //      example:
1323                 //      |       // get the current value of the "foo" attribute on a node
1324                 //      |       dojo.attr(dojo.byId("nodeId"), "foo");
1325                 //      |       // or we can just pass the id:
1326                 //      |       dojo.attr("nodeId", "foo");
1327                 //
1328                 //      example:
1329                 //      |       // use attr() to set the tab index
1330                 //      |       dojo.attr("nodeId", "tabIndex", 3);
1331                 //      |
1332                 //
1333                 //      example:
1334                 //      Set multiple values at once, including event handlers:
1335                 //      |       dojo.attr("formId", {
1336                 //      |               "foo": "bar",
1337                 //      |               "tabIndex": -1,
1338                 //      |               "method": "POST",
1339                 //      |               "onsubmit": function(e){
1340                 //      |                       // stop submitting the form. Note that the IE behavior
1341                 //      |                       // of returning true or false will have no effect here
1342                 //      |                       // since our handler is connect()ed to the built-in
1343                 //      |                       // onsubmit behavior and so we need to use
1344                 //      |                       // dojo.stopEvent() to ensure that the submission
1345                 //      |                       // doesn't proceed.
1346                 //      |                       dojo.stopEvent(e);
1347                 //      |
1348                 //      |                       // submit the form with Ajax
1349                 //      |                       dojo.xhrPost({ form: "formId" });
1350                 //      |               }
1351                 //      |       });
1352                 //
1353                 //      example:
1354                 //      Style is s special case: Only set with an object hash of styles
1355                 //      |       dojo.attr("someNode",{
1356                 //      |               id:"bar",
1357                 //      |               style:{
1358                 //      |                       width:"200px", height:"100px", color:"#000"
1359                 //      |               }
1360                 //      |       });
1361                 //
1362                 //      example:
1363                 //      Again, only set style as an object hash of styles:
1364                 //      |       var obj = { color:"#fff", backgroundColor:"#000" };
1365                 //      |       dojo.attr("someNode", "style", obj);
1366                 //      |
1367                 //      |       // though shorter to use `dojo.style()` in this case:
1368                 //      |       dojo.style("someNode", obj);
1369
1370                 node = byId(node);
1371                 var args = arguments.length, prop;
1372                 if(args == 2 && typeof name != "string"){ // inline'd type check
1373                         // the object form of setter: the 2nd argument is a dictionary
1374                         for(var x in name){
1375                                 d.attr(node, x, name[x]);
1376                         }
1377                         return node; // DomNode
1378                 }
1379                 var lc = name.toLowerCase(),
1380                         propName = _propNames[lc] || name,
1381                         forceProp = _forcePropNames[propName],
1382                         attrName = _attrNames[lc] || name;
1383                 if(args == 3){
1384                         // setter
1385                         do{
1386                                 if(propName == "style" && typeof value != "string"){ // inline'd type check
1387                                         // special case: setting a style
1388                                         d.style(node, value);
1389                                         break;
1390                                 }
1391                                 if(propName == "innerHTML"){
1392                                         // special case: assigning HTML
1393                                                                                 if(d.isIE && node.tagName.toLowerCase() in _roInnerHtml){
1394                                                 d.empty(node);
1395                                                 node.appendChild(d._toDom(value, node.ownerDocument));
1396                                         }else{
1397                                                                                         node[propName] = value;
1398                                                                                 }
1399                                                                                 break;
1400                                 }
1401                                 if(d.isFunction(value)){
1402                                         // special case: assigning an event handler
1403                                         // clobber if we can
1404                                         var attrId = d.attr(node, _attrId);
1405                                         if(!attrId){
1406                                                 attrId = _ctr++;
1407                                                 d.attr(node, _attrId, attrId);
1408                                         }
1409                                         if(!_evtHdlrMap[attrId]){
1410                                                 _evtHdlrMap[attrId] = {};
1411                                         }
1412                                         var h = _evtHdlrMap[attrId][propName];
1413                                         if(h){
1414                                                 d.disconnect(h);
1415                                         }else{
1416                                                 try{
1417                                                         delete node[propName];
1418                                                 }catch(e){}
1419                                         }
1420                                         // ensure that event objects are normalized, etc.
1421                                         _evtHdlrMap[attrId][propName] = d.connect(node, propName, value);
1422                                         break;
1423                                 }
1424                                 if(forceProp || typeof value == "boolean"){
1425                                         // special case: forcing assignment to the property
1426                                         // special case: setting boolean to a property instead of attribute
1427                                         node[propName] = value;
1428                                         break;
1429                                 }
1430                                 // node's attribute
1431                                 node.setAttribute(attrName, value);
1432                         }while(false);
1433                         return node; // DomNode
1434                 }
1435                 // getter
1436                 // should we access this attribute via a property or
1437                 // via getAttribute()?
1438                 value = node[propName];
1439                 if(forceProp && typeof value != "undefined"){
1440                         // node's property
1441                         return value;   // Anything
1442                 }
1443                 if(propName != "href" && (typeof value == "boolean" || d.isFunction(value))){
1444                         // node's property
1445                         return value;   // Anything
1446                 }
1447                 // node's attribute
1448                 // we need _hasAttr() here to guard against IE returning a default value
1449                 return _hasAttr(node, attrName) ? node.getAttribute(attrName) : null; // Anything
1450         }
1451
1452         dojo.removeAttr = function(/*DomNode|String*/ node, /*String*/ name){
1453                 //      summary:
1454                 //              Removes an attribute from an HTML element.
1455                 //      node:
1456                 //              id or reference to the element to remove the attribute from
1457                 //      name:
1458                 //              the name of the attribute to remove
1459                 byId(node).removeAttribute(_fixAttrName(name));
1460         }
1461
1462         dojo.getNodeProp = function(/*DomNode|String*/ node, /*String*/ name){
1463                 //      summary:
1464                 //              Returns an effective value of a property or an attribute.
1465                 //      node:
1466                 //              id or reference to the element to remove the attribute from
1467                 //      name:
1468                 //              the name of the attribute
1469                 node = byId(node);
1470                 var lc = name.toLowerCase(),
1471                         propName = _propNames[lc] || name;
1472                 if((propName in node) && propName != "href"){
1473                         // node's property
1474                         return node[propName];  // Anything
1475                 }
1476                 // node's attribute
1477                 var attrName = _attrNames[lc] || name;
1478                 return _hasAttr(node, attrName) ? node.getAttribute(attrName) : null; // Anything
1479         }
1480
1481         dojo.create = function(tag, attrs, refNode, pos){
1482                 //      summary:
1483                 //              Create an element, allowing for optional attribute decoration
1484                 //              and placement.
1485                 //
1486                 // description:
1487                 //              A DOM Element creation function. A shorthand method for creating a node or
1488                 //              a fragment, and allowing for a convenient optional attribute setting step,
1489                 //              as well as an optional DOM placement reference.
1490                 //|
1491                 //              Attributes are set by passing the optional object through `dojo.attr`.
1492                 //              See `dojo.attr` for noted caveats and nuances, and API if applicable.
1493                 //|
1494                 //              Placement is done via `dojo.place`, assuming the new node to be the action 
1495                 //              node, passing along the optional reference node and position.
1496                 //
1497                 // tag: String|DomNode
1498                 //              A string of the element to create (eg: "div", "a", "p", "li", "script", "br"),
1499                 //              or an existing DOM node to process.
1500                 //
1501                 // attrs: Object
1502                 //              An object-hash of attributes to set on the newly created node.
1503                 //              Can be null, if you don't want to set any attributes/styles.
1504                 //              See: `dojo.attr` for a description of available attributes.
1505                 //
1506                 // refNode: String?|DomNode?
1507                 //              Optional reference node. Used by `dojo.place` to place the newly created
1508                 //              node somewhere in the dom relative to refNode. Can be a DomNode reference
1509                 //              or String ID of a node.
1510                 //
1511                 // pos: String?
1512                 //              Optional positional reference. Defaults to "last" by way of `dojo.place`,
1513                 //              though can be set to "first","after","before","last", "replace" or "only"
1514                 //              to further control the placement of the new node relative to the refNode.
1515                 //              'refNode' is required if a 'pos' is specified.
1516                 //
1517                 // returns: DomNode
1518                 //
1519                 // example:
1520                 //      Create a DIV:
1521                 //      |       var n = dojo.create("div");
1522                 //
1523                 // example:
1524                 //      Create a DIV with content:
1525                 //      |       var n = dojo.create("div", { innerHTML:"<p>hi</p>" });
1526                 //
1527                 // example:
1528                 //      Place a new DIV in the BODY, with no attributes set
1529                 //      |       var n = dojo.create("div", null, dojo.body());
1530                 //
1531                 // example:
1532                 //      Create an UL, and populate it with LI's. Place the list as the first-child of a 
1533                 //      node with id="someId":
1534                 //      |       var ul = dojo.create("ul", null, "someId", "first");
1535                 //      |       var items = ["one", "two", "three", "four"];
1536                 //      |       dojo.forEach(items, function(data){
1537                 //      |               dojo.create("li", { innerHTML: data }, ul);
1538                 //      |       });
1539                 //
1540                 // example:
1541                 //      Create an anchor, with an href. Place in BODY:
1542                 //      |       dojo.create("a", { href:"foo.html", title:"Goto FOO!" }, dojo.body());
1543                 //
1544                 // example:
1545                 //      Create a `dojo.NodeList()` from a new element (for syntatic sugar):
1546                 //      |       dojo.query(dojo.create('div'))
1547                 //      |               .addClass("newDiv")
1548                 //      |               .onclick(function(e){ console.log('clicked', e.target) })
1549                 //      |               .place("#someNode"); // redundant, but cleaner.
1550
1551                 var doc = d.doc;
1552                 if(refNode){
1553                         refNode = byId(refNode);
1554                         doc = refNode.ownerDocument;
1555                 }
1556                 if(typeof tag == "string"){ // inline'd type check
1557                         tag = doc.createElement(tag);
1558                 }
1559                 if(attrs){ d.attr(tag, attrs); }
1560                 if(refNode){ d.place(tag, refNode, pos); }
1561                 return tag; // DomNode
1562         }
1563
1564         /*=====
1565         dojo.empty = function(node){
1566                         //      summary:
1567                         //              safely removes all children of the node.
1568                         //      node: DOMNode|String
1569                         //              a reference to a DOM node or an id.
1570                         //      example:
1571                         //      Destroy node's children byId:
1572                         //      |       dojo.empty("someId");
1573                         //
1574                         //      example:
1575                         //      Destroy all nodes' children in a list by reference:
1576                         //      |       dojo.query(".someNode").forEach(dojo.empty);
1577         }
1578         =====*/
1579
1580         d.empty =
1581                                 d.isIE ?  function(node){
1582                         node = byId(node);
1583                         for(var c; c = node.lastChild;){ // intentional assignment
1584                                 d.destroy(c);
1585                         }
1586                 } :
1587                                 function(node){
1588                         byId(node).innerHTML = "";
1589                 };
1590
1591         /*=====
1592         dojo._toDom = function(frag, doc){
1593                         //      summary:
1594                         //              instantiates an HTML fragment returning the corresponding DOM.
1595                         //      frag: String
1596                         //              the HTML fragment
1597                         //      doc: DocumentNode?
1598                         //              optional document to use when creating DOM nodes, defaults to
1599                         //              dojo.doc if not specified.
1600                         //      returns: DocumentFragment
1601                         //
1602                         //      example:
1603                         //      Create a table row:
1604                         //      |       var tr = dojo._toDom("<tr><td>First!</td></tr>");
1605         }
1606         =====*/
1607
1608         // support stuff for dojo._toDom
1609         var tagWrap = {
1610                         option: ["select"],
1611                         tbody: ["table"],
1612                         thead: ["table"],
1613                         tfoot: ["table"],
1614                         tr: ["table", "tbody"],
1615                         td: ["table", "tbody", "tr"],
1616                         th: ["table", "thead", "tr"],
1617                         legend: ["fieldset"],
1618                         caption: ["table"],
1619                         colgroup: ["table"],
1620                         col: ["table", "colgroup"],
1621                         li: ["ul"]
1622                 },
1623                 reTag = /<\s*([\w\:]+)/,
1624                 masterNode = {}, masterNum = 0,
1625                 masterName = "__" + d._scopeName + "ToDomId";
1626
1627         // generate start/end tag strings to use
1628         // for the injection for each special tag wrap case.
1629         for(var param in tagWrap){
1630                 var tw = tagWrap[param];
1631                 tw.pre  = param == "option" ? '<select multiple="multiple">' : "<" + tw.join("><") + ">";
1632                 tw.post = "</" + tw.reverse().join("></") + ">";
1633                 // the last line is destructive: it reverses the array,
1634                 // but we don't care at this point
1635         }
1636
1637         d._toDom = function(frag, doc){
1638                 //      summary:
1639                 //              converts HTML string into DOM nodes.
1640
1641                 doc = doc || d.doc;
1642                 var masterId = doc[masterName];
1643                 if(!masterId){
1644                         doc[masterName] = masterId = ++masterNum + "";
1645                         masterNode[masterId] = doc.createElement("div");
1646                 }
1647
1648                 // make sure the frag is a string.
1649                 frag += "";
1650
1651                 // find the starting tag, and get node wrapper
1652                 var match = frag.match(reTag),
1653                         tag = match ? match[1].toLowerCase() : "",
1654                         master = masterNode[masterId],
1655                         wrap, i, fc, df;
1656                 if(match && tagWrap[tag]){
1657                         wrap = tagWrap[tag];
1658                         master.innerHTML = wrap.pre + frag + wrap.post;
1659                         for(i = wrap.length; i; --i){
1660                                 master = master.firstChild;
1661                         }
1662                 }else{
1663                         master.innerHTML = frag;
1664                 }
1665
1666                 // one node shortcut => return the node itself
1667                 if(master.childNodes.length == 1){
1668                         return master.removeChild(master.firstChild); // DOMNode
1669                 }
1670
1671                 // return multiple nodes as a document fragment
1672                 df = doc.createDocumentFragment();
1673                 while(fc = master.firstChild){ // intentional assignment
1674                         df.appendChild(fc);
1675                 }
1676                 return df; // DOMNode
1677         }
1678
1679         // =============================
1680         // (CSS) Class Functions
1681         // =============================
1682         var _className = "className";
1683
1684         dojo.hasClass = function(/*DomNode|String*/node, /*String*/classStr){
1685                 //      summary:
1686                 //              Returns whether or not the specified classes are a portion of the
1687                 //              class list currently applied to the node.
1688                 //
1689                 //      node:
1690                 //              String ID or DomNode reference to check the class for.
1691                 //
1692                 //      classStr:
1693                 //              A string class name to look for.
1694                 //
1695                 //      example:
1696                 //      Do something if a node with id="someNode" has class="aSillyClassName" present
1697                 //      |       if(dojo.hasClass("someNode","aSillyClassName")){ ... }
1698
1699                 return ((" "+ byId(node)[_className] +" ").indexOf(" " + classStr + " ") >= 0);  // Boolean
1700         };
1701
1702         var spaces = /\s+/, a1 = [""],
1703                 str2array = function(s){
1704                         if(typeof s == "string" || s instanceof String){
1705                                 if(s.indexOf(" ") < 0){
1706                                         a1[0] = s;
1707                                         return a1;
1708                                 }else{
1709                                         return s.split(spaces);
1710                                 }
1711                         }
1712                         // assumed to be an array
1713                         return s || "";
1714                 };
1715
1716         dojo.addClass = function(/*DomNode|String*/node, /*String|Array*/classStr){
1717                 //      summary:
1718                 //              Adds the specified classes to the end of the class list on the
1719                 //              passed node. Will not re-apply duplicate classes.
1720                 //
1721                 //      node:
1722                 //              String ID or DomNode reference to add a class string too
1723                 //
1724                 //      classStr:
1725                 //              A String class name to add, or several space-separated class names,
1726                 //              or an array of class names.
1727                 //
1728                 // example:
1729                 //      Add a class to some node:
1730                 //      |       dojo.addClass("someNode", "anewClass");
1731                 //
1732                 // example:
1733                 //      Add two classes at once:
1734                 //      |       dojo.addClass("someNode", "firstClass secondClass");
1735                 //
1736                 // example:
1737                 //      Add two classes at once (using array):
1738                 //      |       dojo.addClass("someNode", ["firstClass", "secondClass"]);
1739                 //
1740                 // example:
1741                 //      Available in `dojo.NodeList` for multiple additions
1742                 //      |       dojo.query("ul > li").addClass("firstLevel");
1743
1744                 node = byId(node);
1745                 classStr = str2array(classStr);
1746                 var cls = node[_className], oldLen;
1747                 cls = cls ? " " + cls + " " : " ";
1748                 oldLen = cls.length;
1749                 for(var i = 0, len = classStr.length, c; i < len; ++i){
1750                         c = classStr[i];
1751                         if(c && cls.indexOf(" " + c + " ") < 0){
1752                                 cls += c + " ";
1753                         }
1754                 }
1755                 if(oldLen < cls.length){
1756                         node[_className] = cls.substr(1, cls.length - 2);
1757                 }
1758         };
1759
1760         dojo.removeClass = function(/*DomNode|String*/node, /*String|Array?*/classStr){
1761                 // summary:
1762                 //              Removes the specified classes from node. No `dojo.hasClass`
1763                 //              check is required.
1764                 //
1765                 // node:
1766                 //              String ID or DomNode reference to remove the class from.
1767                 //
1768                 // classStr:
1769                 //              An optional String class name to remove, or several space-separated
1770                 //              class names, or an array of class names. If omitted, all class names
1771                 //              will be deleted.
1772                 //
1773                 // example:
1774                 //      Remove a class from some node:
1775                 //      |       dojo.removeClass("someNode", "firstClass");
1776                 //
1777                 // example:
1778                 //      Remove two classes from some node:
1779                 //      |       dojo.removeClass("someNode", "firstClass secondClass");
1780                 //
1781                 // example:
1782                 //      Remove two classes from some node (using array):
1783                 //      |       dojo.removeClass("someNode", ["firstClass", "secondClass"]);
1784                 //
1785                 // example:
1786                 //      Remove all classes from some node:
1787                 //      |       dojo.removeClass("someNode");
1788                 //
1789                 // example:
1790                 //      Available in `dojo.NodeList()` for multiple removal
1791                 //      |       dojo.query(".foo").removeClass("foo");
1792
1793                 node = byId(node);
1794                 var cls;
1795                 if(classStr !== undefined){
1796                         classStr = str2array(classStr);
1797                         cls = " " + node[_className] + " ";
1798                         for(var i = 0, len = classStr.length; i < len; ++i){
1799                                 cls = cls.replace(" " + classStr[i] + " ", " ");
1800                         }
1801                         cls = d.trim(cls);
1802                 }else{
1803                         cls = "";
1804                 }
1805                 if(node[_className] != cls){ node[_className] = cls; }
1806         };
1807
1808         dojo.toggleClass = function(/*DomNode|String*/node, /*String|Array*/classStr, /*Boolean?*/condition){
1809                 //      summary:
1810                 //              Adds a class to node if not present, or removes if present.
1811                 //              Pass a boolean condition if you want to explicitly add or remove.
1812                 //      condition:
1813                 //              If passed, true means to add the class, false means to remove.
1814                 //
1815                 // example:
1816                 //      |       dojo.toggleClass("someNode", "hovered");
1817                 //
1818                 // example:
1819                 //      Forcefully add a class
1820                 //      |       dojo.toggleClass("someNode", "hovered", true);
1821                 //
1822                 // example:
1823                 //      Available in `dojo.NodeList()` for multiple toggles
1824                 //      |       dojo.query(".toggleMe").toggleClass("toggleMe");
1825
1826                 if(condition === undefined){
1827                         condition = !d.hasClass(node, classStr);
1828                 }
1829                 d[condition ? "addClass" : "removeClass"](node, classStr);
1830         };
1831
1832 })();
1833
1834 }