root / dojo / trunk / _base / html.js

Revision 22540, 59.7 kB (checked in by doughays, 7 days ago)

Fixes #11376 !strict. Update _docScroll to also call _fixIeBiDiScrollLeft in quirks mode. Update _fixIeBiDiScrollLeft to negate scrollLeft in IE8/RTL mode. Added unit tests to ensure dojo.position(node, true) returns a value that can be used to absolute position a node in any of quirks/strict/rtl/ltr modes.

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