]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dojo/_base/html.js
Comment class stub
[eow] / static / dojo-release-1.1.1 / dojo / _base / html.js
1 if(!dojo._hasResource["dojo._base.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo._base.html"] = true;
3 dojo.require("dojo._base.lang");
4 dojo.provide("dojo._base.html");
5
6 // FIXME: need to add unit tests for all the semi-public methods
7
8 try{
9         document.execCommand("BackgroundImageCache", false, true);
10 }catch(e){
11         // sane browsers don't have cache "issues"
12 }
13
14 // =============================
15 // DOM Functions
16 // =============================
17
18 /*=====
19 dojo.byId = function(id, doc){
20         //      summary:
21         //              Returns DOM node with matching `id` attribute or `null` 
22         //              if not found, similar to "$" function in another library.
23         //              If `id` is a DomNode, this function is a no-op.
24         //
25         //      id: String|DOMNode
26         //              A string to match an HTML id attribute or a reference to a DOM Node
27         //
28         //      doc: Document?
29         //              Document to work in. Defaults to the current value of
30         //              dojo.doc.  Can be used to retrieve
31         //              node references from other documents.
32 =====*/
33 if(dojo.isIE || dojo.isOpera){
34         dojo.byId = function(id, doc){
35                 if(dojo.isString(id)){
36                         var _d = doc || dojo.doc;
37                         var te = _d.getElementById(id);
38                         // attributes.id.value is better than just id in case the 
39                         // user has a name=id inside a form
40                         if(te && te.attributes.id.value == id){
41                                 return te;
42                         }else{
43                                 var eles = _d.all[id];
44                                 if(!eles || !eles.length){ return eles; }
45                                 // if more than 1, choose first with the correct id
46                                 var i=0;
47                                 while((te=eles[i++])){
48                                         if(te.attributes.id.value == id){ return te; }
49                                 }
50                         }
51                 }else{
52                         return id; // DomNode
53                 }
54         }
55 }else{
56         dojo.byId = function(id, doc){
57                 return dojo.isString(id) ? (doc || dojo.doc).getElementById(id) : id; // DomNode
58         }
59 }
60 /*=====
61 }
62 =====*/
63
64 (function(){
65         /*
66         dojo.createElement = function(obj, parent, position){
67                 // TODO: need to finish this!
68         }
69         */
70
71         var d = dojo;
72
73         var _destroyContainer = null;
74         dojo.addOnUnload(function(){
75                 _destroyContainer=null; //prevent IE leak
76         });
77         dojo._destroyElement = function(/*String||DomNode*/node){
78                 // summary:
79                 //              removes node from its parent, clobbers it and all of its
80                 //              children.
81                 //      node:
82                 //              the element to be destroyed, either as an ID or a reference
83
84                 node = d.byId(node);
85                 try{
86                         if(!_destroyContainer){
87                                 _destroyContainer = document.createElement("div");
88                         }
89                         _destroyContainer.appendChild(node.parentNode ? node.parentNode.removeChild(node) : node);
90                         // NOTE: see http://trac.dojotoolkit.org/ticket/2931. This may be a bug and not a feature
91                         _destroyContainer.innerHTML = ""; 
92                 }catch(e){
93                         /* squelch */
94                 }
95         };
96
97         dojo.isDescendant = function(/*DomNode|String*/node, /*DomNode|String*/ancestor){
98                 //      summary:
99                 //              Returns true if node is a descendant of ancestor
100                 //      node: id or node reference to test
101                 //      ancestor: id or node reference of potential parent to test against
102                 try{
103                         node = d.byId(node);
104                         ancestor = d.byId(ancestor);
105                         while(node){
106                                 if(node === ancestor){
107                                         return true; // Boolean
108                                 }
109                                 node = node.parentNode;
110                         }
111                 }catch(e){ /* squelch, return false */ }
112                 return false; // Boolean
113         };
114
115         dojo.setSelectable = function(/*DomNode|String*/node, /*Boolean*/selectable){
116                 //      summary: enable or disable selection on a node
117                 //      node:
118                 //              id or reference to node
119                 //      selectable:
120                 node = d.byId(node);
121                 if(d.isMozilla){
122                         node.style.MozUserSelect = selectable ? "" : "none";
123                 }else if(d.isKhtml){
124                         node.style.KhtmlUserSelect = selectable ? "auto" : "none";
125                 }else if(d.isIE){
126                         node.unselectable = selectable ? "" : "on";
127                         d.query("*", node).forEach(function(descendant){
128                                 descendant.unselectable = selectable ? "" : "on";
129                         });
130                 }
131                 //FIXME: else?  Opera?
132         };
133
134         var _insertBefore = function(/*Node*/node, /*Node*/ref){
135                 ref.parentNode.insertBefore(node, ref);
136                 return true;    //      boolean
137         }
138
139         var _insertAfter = function(/*Node*/node, /*Node*/ref){
140                 //      summary:
141                 //              Try to insert node after ref
142                 var pn = ref.parentNode;
143                 if(ref == pn.lastChild){
144                         pn.appendChild(node);
145                 }else{
146                         return _insertBefore(node, ref.nextSibling);    //      boolean
147                 }
148                 return true;    //      boolean
149         }
150
151         dojo.place = function(/*String|DomNode*/node, /*String|DomNode*/refNode, /*String|Number*/position){
152                 //      summary:
153                 //              Attempt to insert node into the DOM, choosing from various positioning options.
154                 //              Returns true if successful, false otherwise.
155                 //      node: 
156                 //              id or node reference to place relative to refNode
157                 //      refNode: 
158                 //              id or node reference to use as basis for placement
159                 //      position:
160                 //              string noting the position of node relative to refNode or a
161                 //              number indicating the location in the childNodes collection of
162                 //              refNode. Accepted string values are:
163                 //
164                 //              * before
165                 //              * after
166                 //              * first
167                 //              * last
168                 //
169                 //              "first" and "last" indicate positions as children of refNode.
170
171                 // FIXME: need to write tests for this!!!!
172                 if(!node || !refNode || position === undefined){ 
173                         return false;   //      boolean 
174                 }
175                 node = d.byId(node);
176                 refNode = d.byId(refNode);
177                 if(typeof position == "number"){
178                         var cn = refNode.childNodes;
179                         if((position == 0 && cn.length == 0) ||
180                                 cn.length == position){
181                                 refNode.appendChild(node); return true;
182                         }
183                         if(position == 0){
184                                 return _insertBefore(node, refNode.firstChild);
185                         }
186                         return _insertAfter(node, cn[position-1]);
187                 }
188                 switch(position.toLowerCase()){
189                         case "before":
190                                 return _insertBefore(node, refNode);    //      boolean
191                         case "after":
192                                 return _insertAfter(node, refNode);             //      boolean
193                         case "first":
194                                 if(refNode.firstChild){
195                                         return _insertBefore(node, refNode.firstChild); //      boolean
196                                 }
197                                 // else fallthrough...
198                         default: // aka: last
199                                 refNode.appendChild(node);
200                                 return true;    //      boolean
201                 }
202         }
203
204         // Box functions will assume this model.
205         // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode.
206         // Can be set to change behavior of box setters.
207         
208         // can be either:
209         //      "border-box"
210         //      "content-box" (default)
211         dojo.boxModel = "content-box";
212         
213         // We punt per-node box mode testing completely.
214         // If anybody cares, we can provide an additional (optional) unit 
215         // that overrides existing code to include per-node box sensitivity.
216
217         // Opera documentation claims that Opera 9 uses border-box in BackCompat mode.
218         // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box.
219         // IIRC, earlier versions of Opera did in fact use border-box.
220         // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault.
221
222         if(d.isIE /*|| dojo.isOpera*/){
223                 var _dcm = document.compatMode;
224                 // client code may have to adjust if compatMode varies across iframes
225                 d.boxModel = _dcm == "BackCompat" || _dcm == "QuirksMode" || d.isIE<6 ? "border-box" : "content-box"; // FIXME: remove IE < 6 support?
226         }
227
228         // =============================
229         // Style Functions
230         // =============================
231         
232         // getComputedStyle drives most of the style code.
233         // Wherever possible, reuse the returned object.
234         //
235         // API functions below that need to access computed styles accept an 
236         // optional computedStyle parameter.
237         // If this parameter is omitted, the functions will call getComputedStyle themselves.
238         // This way, calling code can access computedStyle once, and then pass the reference to 
239         // multiple API functions. 
240
241 /*=====
242         dojo.getComputedStyle = function(node){
243                 //      summary:
244                 //              Returns a "computed style" object.
245                 //
246                 //      description:
247                 //              Gets a "computed style" object which can be used to gather
248                 //              information about the current state of the rendered node. 
249                 //
250                 //              Note that this may behave differently on different browsers.
251                 //              Values may have different formats and value encodings across
252                 //              browsers.
253                 //
254                 //              Note also that this method is expensive.  Wherever possible,
255                 //              reuse the returned object.
256                 //
257                 //              Use the dojo.style() method for more consistent (pixelized)
258                 //              return values.
259                 //
260                 //      node: DOMNode
261                 //              A reference to a DOM node. Does NOT support taking an
262                 //              ID string for speed reasons.
263                 //      example:
264                 //      |       dojo.getComputedStyle(dojo.byId('foo')).borderWidth;
265                 return; // CSS2Properties
266         }
267 =====*/
268
269         var gcs, dv = document.defaultView;
270         if(d.isSafari){
271                 gcs = function(/*DomNode*/node){
272                         var s = dv.getComputedStyle(node, null);
273                         if(!s && node.style){ 
274                                 node.style.display = ""; 
275                                 s = dv.getComputedStyle(node, null);
276                         }
277                         return s || {};
278                 }; 
279         }else if(d.isIE){
280                 gcs = function(node){
281                         return node.currentStyle;
282                 };
283         }else{
284                 gcs = function(node){
285                         return dv.getComputedStyle(node, null);
286                 };
287         }
288         dojo.getComputedStyle = gcs;
289
290         if(!d.isIE){
291                 dojo._toPixelValue = function(element, value){
292                         // style values can be floats, client code may want
293                         // to round for integer pixels.
294                         return parseFloat(value) || 0; 
295                 }
296         }else{
297                 dojo._toPixelValue = function(element, avalue){
298                         if(!avalue){ return 0; }
299                         // on IE7, medium is usually 4 pixels
300                         if(avalue=="medium"){ return 4; }
301                         // style values can be floats, client code may
302                         // want to round this value for integer pixels.
303                         if(avalue.slice && (avalue.slice(-2)=='px')){ return parseFloat(avalue); }
304                         with(element){
305                                 var sLeft = style.left;
306                                 var rsLeft = runtimeStyle.left;
307                                 runtimeStyle.left = currentStyle.left;
308                                 try{
309                                         // 'avalue' may be incompatible with style.left, which can cause IE to throw
310                                         // this has been observed for border widths using "thin", "medium", "thick" constants
311                                         // those particular constants could be trapped by a lookup
312                                         // but perhaps there are more
313                                         style.left = avalue;
314                                         avalue = style.pixelLeft;
315                                 }catch(e){
316                                         avalue = 0;
317                                 }
318                                 style.left = sLeft;
319                                 runtimeStyle.left = rsLeft;
320                         }
321                         return avalue;
322                 }
323         }
324         var px = d._toPixelValue;
325
326         // FIXME: there opacity quirks on FF that we haven't ported over. Hrm.
327         /*=====
328         dojo._getOpacity = function(node){
329                         //      summary:
330                         //              Returns the current opacity of the passed node as a
331                         //              floating-point value between 0 and 1.
332                         //      node: DomNode
333                         //              a reference to a DOM node. Does NOT support taking an
334                         //              ID string for speed reasons.
335                         //      return: Number between 0 and 1
336         }
337         =====*/
338
339         dojo._getOpacity = d.isIE ? function(node){
340                 try{
341                         return node.filters.alpha.opacity / 100; // Number
342                 }catch(e){
343                         return 1; // Number
344                 }
345         } : function(node){
346                 return gcs(node).opacity;
347         };
348
349         /*=====
350         dojo._setOpacity = function(node, opacity){
351                         //      summary:
352                         //              set the opacity of the passed node portably. Returns the
353                         //              new opacity of the node.
354                         //      node: DOMNode
355                         //              a reference to a DOM node. Does NOT support taking an
356                         //              ID string for performance reasons.
357                         //      opacity: Number
358                         //              A Number between 0 and 1. 0 specifies transparent.
359                         //      return: Number between 0 and 1
360         }
361         =====*/
362
363         dojo._setOpacity = d.isIE ? function(/*DomNode*/node, /*Number*/opacity){
364                 if(opacity == 1){
365                         // on IE7 Alpha(Filter opacity=100) makes text look fuzzy so remove it altogether (bug #2661)
366                         var filterRE = /FILTER:[^;]*;?/i;
367                         node.style.cssText = node.style.cssText.replace(filterRE, "");
368                         if(node.nodeName.toLowerCase() == "tr"){
369                                 d.query("> td", node).forEach(function(i){
370                                         i.style.cssText = i.style.cssText.replace(filterRE, "");
371                                 });
372                         }
373                 }else{
374                         var o = "Alpha(Opacity="+ opacity * 100 +")";
375                         node.style.filter = o;
376                 }
377                 if(node.nodeName.toLowerCase() == "tr"){
378                         d.query("> td", node).forEach(function(i){
379                                 i.style.filter = o;
380                         });
381                 }
382                 return opacity;
383         } : function(node, opacity){
384                 return node.style.opacity = opacity;
385         };
386
387         var _pixelNamesCache = {
388                 left: true, top: true
389         };
390         var _pixelRegExp = /margin|padding|width|height|max|min|offset/;  // |border
391         var _toStyleValue = function(node, type, value){
392                 type = type.toLowerCase();
393                 if(d.isIE && value == "auto"){
394                         if(type == "height"){ return node.offsetHeight; }
395                         if(type == "width"){ return node.offsetWidth; }
396                 }
397                 if(!(type in _pixelNamesCache)){
398                         //      if(dojo.isOpera && type == "cssText"){
399                         //              FIXME: add workaround for #2855 here
400                         //      }
401                         _pixelNamesCache[type] = _pixelRegExp.test(type);
402                 }
403                 return _pixelNamesCache[type] ? px(node, value) : value;
404         }
405
406         var _floatStyle = d.isIE ? "styleFloat" : "cssFloat";
407         var _floatAliases = { "cssFloat": _floatStyle, "styleFloat": _floatStyle, "float": _floatStyle };
408         
409         // public API
410         
411         dojo.style = function(  /*DomNode|String*/ node, 
412                                                         /*String?|Object?*/ style, 
413                                                         /*String?*/ value){
414                 //      summary:
415                 //              Accesses styles on a node. If 2 arguments are
416                 //              passed, acts as a getter. If 3 arguments are passed, acts
417                 //              as a setter.
418                 //      node:
419                 //              id or reference to node to get/set style for
420                 //      style:
421                 //              the style property to set in DOM-accessor format
422                 //              ("borderWidth", not "border-width") or an object with key/value
423                 //              pairs suitable for setting each property.
424                 //      value:
425                 //              If passed, sets value on the node for style, handling
426                 //              cross-browser concerns.
427                 //      example:
428                 //              Passing only an ID or node returns the computed style object of
429                 //              the node:
430                 //      |       dojo.style("thinger");
431                 //      example:
432                 //              Passing a node and a style property returns the current
433                 //              normalized, computed value for that property:
434                 //      |       dojo.style("thinger", "opacity"); // 1 by default
435                 //
436                 //      example:
437                 //              Passing a node, a style property, and a value changes the
438                 //              current display of the node and returns the new computed value
439                 //      |       dojo.style("thinger", "opacity", 0.5); // == 0.5
440                 //
441                 //      example:
442                 //              Passing a node, an object-style style property sets each of the values in turn and returns the computed style object of the node:
443                 //      |       dojo.style("thinger", {
444                 //      |               "opacity": 0.5,
445                 //      |               "border": "3px solid black",
446                 //      |               "height": 300
447                 //      |       });
448                 //
449                 //      example:
450                 //              When the CSS style property is hyphenated, the JavaScript property is camelCased.
451                 //              font-size becomes fontSize, and so on.
452                 //      |       dojo.style("thinger",{
453                 //      |               fontSize:"14pt",
454                 //      |               letterSpacing:"1.2em"
455                 //      |       });
456                 //
457                 //      example:
458                 //              dojo.NodeList implements .style() using the same syntax, omitting the "node" parameter, calling
459                 //              dojo.style() on every element of the list. See: dojo.query and dojo.NodeList
460                 //      |       dojo.query(".someClassName").style("visibility","hidden");
461                 //      |       // or
462                 //      |       dojo.query("#baz > div").style({
463                 //      |               opacity:0.75,
464                 //      |               fontSize:"13pt"
465                 //      |       });
466
467                 var n = d.byId(node), args = arguments.length, op = (style=="opacity");
468                 style = _floatAliases[style] || style;
469                 if(args == 3){
470                         return op ? d._setOpacity(n, value) : n.style[style] = value; /*Number*/
471                 }
472                 if(args == 2 && op){
473                         return d._getOpacity(n);
474                 }
475                 var s = gcs(n);
476                 if(args == 2 && !d.isString(style)){
477                         for(var x in style){
478                                 d.style(node, x, style[x]);
479                         }
480                         return s;
481                 }
482                 return (args == 1) ? s : _toStyleValue(n, style, s[style]); /* CSS2Properties||String||Number */
483         }
484
485         // =============================
486         // Box Functions
487         // =============================
488
489         dojo._getPadExtents = function(/*DomNode*/n, /*Object*/computedStyle){
490                 //      summary:
491                 //              Returns object with special values specifically useful for node
492                 //              fitting.
493                 //
494                 //              * l/t = left/top padding (respectively)
495                 //              * w = the total of the left and right padding 
496                 //              * h = the total of the top and bottom padding
497                 //
498                 //              If 'node' has position, l/t forms the origin for child nodes. 
499                 //              The w/h are used for calculating boxes.
500                 //              Normally application code will not need to invoke this
501                 //              directly, and will use the ...box... functions instead.
502                 var 
503                         s = computedStyle||gcs(n), 
504                         l = px(n, s.paddingLeft), 
505                         t = px(n, s.paddingTop);
506                 return { 
507                         l: l,
508                         t: t,
509                         w: l+px(n, s.paddingRight),
510                         h: t+px(n, s.paddingBottom)
511                 };
512         }
513
514         dojo._getBorderExtents = function(/*DomNode*/n, /*Object*/computedStyle){
515                 //      summary:
516                 //              returns an object with properties useful for noting the border
517                 //              dimensions.
518                 //
519                 //              * l/t = the sum of left/top border (respectively)
520                 //              * w = the sum of the left and right border
521                 //              * h = the sum of the top and bottom border
522                 //
523                 //              The w/h are used for calculating boxes.
524                 //              Normally application code will not need to invoke this
525                 //              directly, and will use the ...box... functions instead.
526                 var 
527                         ne = "none",
528                         s = computedStyle||gcs(n), 
529                         bl = (s.borderLeftStyle != ne ? px(n, s.borderLeftWidth) : 0),
530                         bt = (s.borderTopStyle != ne ? px(n, s.borderTopWidth) : 0);
531                 return { 
532                         l: bl,
533                         t: bt,
534                         w: bl + (s.borderRightStyle!=ne ? px(n, s.borderRightWidth) : 0),
535                         h: bt + (s.borderBottomStyle!=ne ? px(n, s.borderBottomWidth) : 0)
536                 };
537         }
538
539         dojo._getPadBorderExtents = function(/*DomNode*/n, /*Object*/computedStyle){
540                 //      summary:
541                 //              returns object with properties useful for box fitting with
542                 //              regards to padding.
543                 //
544                 //              * l/t = the sum of left/top padding and left/top border (respectively)
545                 //              * w = the sum of the left and right padding and border
546                 //              * h = the sum of the top and bottom padding and border
547                 //
548                 //              The w/h are used for calculating boxes.
549                 //              Normally application code will not need to invoke this
550                 //              directly, and will use the ...box... functions instead.
551                 var 
552                         s = computedStyle||gcs(n), 
553                         p = d._getPadExtents(n, s),
554                         b = d._getBorderExtents(n, s);
555                 return { 
556                         l: p.l + b.l,
557                         t: p.t + b.t,
558                         w: p.w + b.w,
559                         h: p.h + b.h
560                 };
561         }
562
563         dojo._getMarginExtents = function(n, computedStyle){
564                 //      summary:
565                 //              returns object with properties useful for box fitting with
566                 //              regards to box margins (i.e., the outer-box).
567                 //
568                 //              * l/t = marginLeft, marginTop, respectively
569                 //              * w = total width, margin inclusive
570                 //              * h = total height, margin inclusive
571                 //
572                 //              The w/h are used for calculating boxes.
573                 //              Normally application code will not need to invoke this
574                 //              directly, and will use the ...box... functions instead.
575                 var 
576                         s = computedStyle||gcs(n), 
577                         l = px(n, s.marginLeft),
578                         t = px(n, s.marginTop),
579                         r = px(n, s.marginRight),
580                         b = px(n, s.marginBottom);
581                 if(d.isSafari && (s.position != "absolute")){
582                         // FIXME: Safari's version of the computed right margin
583                         // is the space between our right edge and the right edge 
584                         // of our offsetParent. 
585                         // What we are looking for is the actual margin value as 
586                         // determined by CSS.
587                         // Hack solution is to assume left/right margins are the same.
588                         r = l;
589                 }
590                 return { 
591                         l: l,
592                         t: t,
593                         w: l+r,
594                         h: t+b
595                 };
596         }
597
598         // Box getters work in any box context because offsetWidth/clientWidth
599         // are invariant wrt box context
600         //
601         // They do *not* work for display: inline objects that have padding styles
602         // because the user agent ignores padding (it's bogus styling in any case)
603         //
604         // Be careful with IMGs because they are inline or block depending on 
605         // browser and browser mode.
606
607         // Although it would be easier to read, there are not separate versions of 
608         // _getMarginBox for each browser because:
609         // 1. the branching is not expensive
610         // 2. factoring the shared code wastes cycles (function call overhead)
611         // 3. duplicating the shared code wastes bytes
612         
613         dojo._getMarginBox = function(/*DomNode*/node, /*Object*/computedStyle){
614                 // summary:
615                 //              returns an object that encodes the width, height, left and top
616                 //              positions of the node's margin box.
617                 var s = computedStyle||gcs(node), me = d._getMarginExtents(node, s);
618                 var     l = node.offsetLeft - me.l,     t = node.offsetTop - me.t;
619                 if(d.isMoz){
620                         // Mozilla:
621                         // If offsetParent has a computed overflow != visible, the offsetLeft is decreased
622                         // by the parent's border.
623                         // We don't want to compute the parent's style, so instead we examine node's
624                         // computed left/top which is more stable.
625                         var sl = parseFloat(s.left), st = parseFloat(s.top);
626                         if(!isNaN(sl) && !isNaN(st)){
627                                 l = sl, t = st;
628                         }else{
629                                 // If child's computed left/top are not parseable as a number (e.g. "auto"), we
630                                 // have no choice but to examine the parent's computed style.
631                                 var p = node.parentNode;
632                                 if(p && p.style){
633                                         var pcs = gcs(p);
634                                         if(pcs.overflow != "visible"){
635                                                 var be = d._getBorderExtents(p, pcs);
636                                                 l += be.l, t += be.t;
637                                         }
638                                 }
639                         }
640                 }else if(d.isOpera){
641                         // On Opera, offsetLeft includes the parent's border
642                         var p = node.parentNode;
643                         if(p){
644                                 var be = d._getBorderExtents(p);
645                                 l -= be.l, t -= be.t;
646                         }
647                 }
648                 return { 
649                         l: l, 
650                         t: t, 
651                         w: node.offsetWidth + me.w, 
652                         h: node.offsetHeight + me.h 
653                 };
654         }
655         
656         dojo._getContentBox = function(node, computedStyle){
657                 // summary:
658                 //              Returns an object that encodes the width, height, left and top
659                 //              positions of the node's content box, irrespective of the
660                 //              current box model.
661
662                 // clientWidth/Height are important since the automatically account for scrollbars
663                 // fallback to offsetWidth/Height for special cases (see #3378)
664                 var s=computedStyle||gcs(node), pe=d._getPadExtents(node, s), be=d._getBorderExtents(node, s), w=node.clientWidth, h;
665                 if(!w){
666                         w=node.offsetWidth, h=node.offsetHeight;
667                 }else{
668                         h=node.clientHeight, be.w = be.h = 0; 
669                 }
670                 // On Opera, offsetLeft includes the parent's border
671                 if(d.isOpera){ pe.l += be.l; pe.t += be.t; };
672                 return { 
673                         l: pe.l, 
674                         t: pe.t, 
675                         w: w - pe.w - be.w, 
676                         h: h - pe.h - be.h
677                 };
678         }
679
680         dojo._getBorderBox = function(node, computedStyle){
681                 var s=computedStyle||gcs(node), pe=d._getPadExtents(node, s), cb=d._getContentBox(node, s);
682                 return { 
683                         l: cb.l - pe.l, 
684                         t: cb.t - pe.t, 
685                         w: cb.w + pe.w, 
686                         h: cb.h + pe.h
687                 };
688         }
689
690         // Box setters depend on box context because interpretation of width/height styles
691         // vary wrt box context.
692         //
693         // The value of dojo.boxModel is used to determine box context.
694         // dojo.boxModel can be set directly to change behavior.
695         //
696         // Beware of display: inline objects that have padding styles
697         // because the user agent ignores padding (it's a bogus setup anyway)
698         //
699         // Be careful with IMGs because they are inline or block depending on 
700         // browser and browser mode.
701         // 
702         // Elements other than DIV may have special quirks, like built-in
703         // margins or padding, or values not detectable via computedStyle.
704         // In particular, margins on TABLE do not seems to appear 
705         // at all in computedStyle on Mozilla.
706         
707         dojo._setBox = function(/*DomNode*/node, /*Number?*/l, /*Number?*/t, /*Number?*/w, /*Number?*/h, /*String?*/u){
708                 //      summary:
709                 //              sets width/height/left/top in the current (native) box-model
710                 //              dimentions. Uses the unit passed in u.
711                 //      node: DOM Node reference. Id string not supported for performance reasons.
712                 //      l: optional. left offset from parent.
713                 //      t: optional. top offset from parent.
714                 //      w: optional. width in current box model.
715                 //      h: optional. width in current box model.
716                 //      u: optional. unit measure to use for other measures. Defaults to "px".
717                 u = u || "px";
718                 var s = node.style;
719                 if(!isNaN(l)){ s.left = l+u; }
720                 if(!isNaN(t)){ s.top = t+u; }
721                 if(w>=0){ s.width = w+u; }
722                 if(h>=0){ s.height = h+u; }
723         }
724
725         dojo._usesBorderBox = function(/*DomNode*/node){
726                 //      summary: 
727                 //              True if the node uses border-box layout.
728
729                 // We could test the computed style of node to see if a particular box
730                 // has been specified, but there are details and we choose not to bother.
731                 var n = node.tagName;
732                 // For whatever reason, TABLE and BUTTON are always border-box by default.
733                 // If you have assigned a different box to either one via CSS then
734                 // box functions will break.
735                 return d.boxModel=="border-box" || n=="TABLE" || n=="BUTTON"; // boolean
736         }
737
738         dojo._setContentSize = function(/*DomNode*/node, /*Number*/widthPx, /*Number*/heightPx, /*Object*/computedStyle){
739                 //      summary:
740                 //              Sets the size of the node's contents, irrespective of margins,
741                 //              padding, or borders.
742                 if(d._usesBorderBox(node)){
743                         var pb = d._getPadBorderExtents(node, computedStyle);
744                         if(widthPx >= 0){ widthPx += pb.w; }
745                         if(heightPx >= 0){ heightPx += pb.h; }
746                 }
747                 d._setBox(node, NaN, NaN, widthPx, heightPx);
748         }
749
750         dojo._setMarginBox = function(/*DomNode*/node,  /*Number?*/leftPx, /*Number?*/topPx, 
751                                                                                                         /*Number?*/widthPx, /*Number?*/heightPx, 
752                                                                                                         /*Object*/computedStyle){
753                 //      summary:
754                 //              sets the size of the node's margin box and placement
755                 //              (left/top), irrespective of box model. Think of it as a
756                 //              passthrough to dojo._setBox that handles box-model vagaries for
757                 //              you.
758
759                 var s = computedStyle||gcs(node);
760                 // Some elements have special padding, margin, and box-model settings. 
761                 // To use box functions you may need to set padding, margin explicitly.
762                 // Controlling box-model is harder, in a pinch you might set dojo.boxModel.
763                 var bb=d._usesBorderBox(node),
764                                 pb=bb ? _nilExtents : d._getPadBorderExtents(node, s),
765                                 mb=d._getMarginExtents(node, s);
766                 if(widthPx>=0){ widthPx = Math.max(widthPx - pb.w - mb.w, 0); }
767                 if(heightPx>=0){ heightPx = Math.max(heightPx - pb.h - mb.h, 0); }
768                 d._setBox(node, leftPx, topPx, widthPx, heightPx);
769         }
770         
771         var _nilExtents = { l:0, t:0, w:0, h:0 };
772
773         // public API
774         
775         dojo.marginBox = function(/*DomNode|String*/node, /*Object?*/box){
776                 //      summary:
777                 //              Getter/setter for the margin-box of node.
778                 //      description: 
779                 //              Returns an object in the expected format of box (regardless
780                 //              if box is passed). The object might look like:
781                 //                      `{ l: 50, t: 200, w: 300: h: 150 }`
782                 //              for a node offset from its parent 50px to the left, 200px from
783                 //              the top with a margin width of 300px and a margin-height of
784                 //              150px.
785                 //      node:
786                 //              id or reference to DOM Node to get/set box for
787                 //      box:
788                 //              If passed, denotes that dojo.marginBox() should
789                 //              update/set the margin box for node. Box is an object in the
790                 //              above format. All properties are optional if passed.
791                 var n=d.byId(node), s=gcs(n), b=box;
792                 return !b ? d._getMarginBox(n, s) : d._setMarginBox(n, b.l, b.t, b.w, b.h, s); // Object
793         }
794
795         dojo.contentBox = function(/*DomNode|String*/node, /*Object?*/box){
796                 //      summary:
797                 //              Getter/setter for the content-box of node.
798                 //      description:
799                 //              Returns an object in the expected format of box (regardless if box is passed).
800                 //              The object might look like:
801                 //                      `{ l: 50, t: 200, w: 300: h: 150 }`
802                 //              for a node offset from its parent 50px to the left, 200px from
803                 //              the top with a content width of 300px and a content-height of
804                 //              150px. Note that the content box may have a much larger border
805                 //              or margin box, depending on the box model currently in use and
806                 //              CSS values set/inherited for node.
807                 //      node:
808                 //              id or reference to DOM Node to get/set box for
809                 //      box:
810                 //              If passed, denotes that dojo.contentBox() should
811                 //              update/set the content box for node. Box is an object in the
812                 //              above format. All properties are optional if passed.
813                 var n=dojo.byId(node), s=gcs(n), b=box;
814                 return !b ? d._getContentBox(n, s) : d._setContentSize(n, b.w, b.h, s); // Object
815         }
816         
817         // =============================
818         // Positioning 
819         // =============================
820         
821         var _sumAncestorProperties = function(node, prop){
822                 if(!(node = (node||0).parentNode)){return 0};
823                 var val, retVal = 0, _b = d.body();
824                 while(node && node.style){
825                         if(gcs(node).position == "fixed"){
826                                 return 0;
827                         }
828                         val = node[prop];
829                         if(val){
830                                 retVal += val - 0;
831                                 // opera and khtml #body & #html has the same values, we only
832                                 // need one value
833                                 if(node == _b){ break; }
834                         }
835                         node = node.parentNode;
836                 }
837                 return retVal;  //      integer
838         }
839
840         dojo._docScroll = function(){
841                 var 
842                         _b = d.body(),
843                         _w = d.global,
844                         de = d.doc.documentElement;
845                 return {
846                         y: (_w.pageYOffset || de.scrollTop || _b.scrollTop || 0),
847                         x: (_w.pageXOffset || d._fixIeBiDiScrollLeft(de.scrollLeft) || _b.scrollLeft || 0)
848                 };
849         };
850         
851         dojo._isBodyLtr = function(){
852                 //FIXME: could check html and body tags directly instead of computed style?  need to ignore case, accept empty values
853                 return !("_bodyLtr" in d) ? 
854                         d._bodyLtr = gcs(d.body()).direction == "ltr" :
855                         d._bodyLtr; // Boolean 
856         }
857         
858         dojo._getIeDocumentElementOffset = function(){
859                 // summary
860                 // The following values in IE contain an offset:
861                 //     event.clientX 
862                 //     event.clientY 
863                 //     node.getBoundingClientRect().left
864                 //     node.getBoundingClientRect().top
865                 // But other position related values do not contain this offset, such as
866                 // node.offsetLeft, node.offsetTop, node.style.left and node.style.top.
867                 // The offset is always (2, 2) in LTR direction. When the body is in RTL
868                 // direction, the offset counts the width of left scroll bar's width.
869                 // This function computes the actual offset.
870
871                 //NOTE: assumes we're being called in an IE browser
872
873                 var de = d.doc.documentElement;
874                 //FIXME: use this instead?                      var de = d.compatMode == "BackCompat" ? d.body : d.documentElement;
875
876                 return (d.isIE >= 7) ?
877                         {x: de.getBoundingClientRect().left, y: de.getBoundingClientRect().top}
878                 :
879                         // IE 6.0
880                         {x: d._isBodyLtr() || window.parent == window ?
881                                 de.clientLeft : de.offsetWidth - de.clientWidth - de.clientLeft, 
882                                 y: de.clientTop}; // Object
883         };
884         
885         dojo._fixIeBiDiScrollLeft = function(/*Integer*/ scrollLeft){
886                 // In RTL direction, scrollLeft should be a negative value, but IE 
887                 // returns a positive one. All codes using documentElement.scrollLeft
888                 // must call this function to fix this error, otherwise the position
889                 // will offset to right when there is a horizontal scrollbar.
890                 var dd = d.doc;
891                 if(d.isIE && !dojo._isBodyLtr()){
892                         var de = dd.compatMode == "BackCompat" ? dd.body : dd.documentElement;
893                         return scrollLeft + de.clientWidth - de.scrollWidth; // Integer
894                 }
895                 return scrollLeft; // Integer
896         }
897
898         dojo._abs = function(/*DomNode*/node, /*Boolean?*/includeScroll){
899                 //      summary:
900                 //              Gets the position of the passed element relative to
901                 //              the viewport (if includeScroll==false), or relative to the
902                 //              document root (if includeScroll==true).
903                 //
904                 //              Returns an object of the form:
905                 //                      { x: 100, y: 300 }
906                 //              if includeScroll is passed, the x and y values will include any
907                 //              document offsets that may affect the position relative to the
908                 //              viewport.
909
910                 // FIXME: need to decide in the brave-new-world if we're going to be
911                 // margin-box or border-box.
912                 var ownerDocument = node.ownerDocument;
913                 var ret = {
914                         x: 0,
915                         y: 0
916                 };
917
918                 // targetBoxType == "border-box"
919                 var db = d.body();
920                 if(d.isIE || (d.isFF >= 3)){
921                         var client = node.getBoundingClientRect();
922                         var offset = (d.isIE) ? d._getIeDocumentElementOffset() : { x: 0, y: 0};
923                         ret.x = client.left - offset.x;
924                         ret.y = client.top - offset.y;
925                 }else if(ownerDocument["getBoxObjectFor"]){
926                         // mozilla
927                         var bo = ownerDocument.getBoxObjectFor(node),
928                                 b = d._getBorderExtents(node);
929                         ret.x = bo.x - b.l - _sumAncestorProperties(node, "scrollLeft");
930                         ret.y = bo.y - b.t - _sumAncestorProperties(node, "scrollTop");
931                 }else{
932                         if(node["offsetParent"]){
933                                 var endNode;
934                                 // in Safari, if the node is an absolutely positioned child of
935                                 // the body and the body has a margin the offset of the child
936                                 // and the body contain the body's margins, so we need to end
937                                 // at the body
938                                 // FIXME: getting contrary results to the above in latest WebKit.
939                                 if(d.isSafari &&
940                                         //(node.style.getPropertyValue("position") == "absolute") &&
941                                         (gcs(node).position == "absolute") &&
942                                         (node.parentNode == db)){
943                                         endNode = db;
944                                 }else{
945                                         endNode = db.parentNode;
946                                 }
947                                 if(node.parentNode != db){
948                                         var nd = node;
949                                         if(d.isOpera){ nd = db; }
950                                         ret.x -= _sumAncestorProperties(nd, "scrollLeft");
951                                         ret.y -= _sumAncestorProperties(nd, "scrollTop");
952                                 }
953                                 var curnode = node;
954                                 do{
955                                         var n = curnode.offsetLeft;
956                                         //FIXME: ugly hack to workaround the submenu in 
957                                         //popupmenu2 does not shown up correctly in opera. 
958                                         //Someone have a better workaround?
959                                         if(!d.isOpera || n > 0){
960                                                 ret.x += isNaN(n) ? 0 : n;
961                                         }
962                                         var t = curnode.offsetTop;
963                                         ret.y += isNaN(t) ? 0 : t;
964                                         if(d.isSafari && curnode != node){
965                                                 var cs = gcs(curnode);
966                                                 ret.x += px(curnode, cs.borderLeftWidth);
967                                                 ret.y += px(curnode, cs.borderTopWidth);
968                                         }
969                                         curnode = curnode.offsetParent;
970                                 }while((curnode != endNode) && curnode);
971                         }else if(node.x && node.y){
972                                 ret.x += isNaN(node.x) ? 0 : node.x;
973                                 ret.y += isNaN(node.y) ? 0 : node.y;
974                         }
975                 }
976                 // account for document scrolling
977                 // if offsetParent is used, ret value already includes scroll position
978                 // so we may have to actually remove that value if !includeScroll
979                 if(includeScroll){
980                         var scroll = d._docScroll();
981                         ret.y += scroll.y;
982                         ret.x += scroll.x;
983                 }
984
985                 return ret; // object
986         }
987
988         // FIXME: need a setter for coords or a moveTo!!
989         dojo.coords = function(/*DomNode|String*/node, /*Boolean?*/includeScroll){
990                 //      summary:
991                 //              Returns an object that measures margin box width/height and
992                 //              absolute positioning data from dojo._abs().
993                 //
994                 //      description:
995                 //              Returns an object that measures margin box width/height and
996                 //              absolute positioning data from dojo._abs().
997                 //              Return value will be in the form:
998                 //                      `{ l: 50, t: 200, w: 300: h: 150, x: 100, y: 300 }`
999                 //              Does not act as a setter. If includeScroll is passed, the x and
1000                 //              y params are affected as one would expect in dojo._abs().
1001                 var n=d.byId(node), s=gcs(n), mb=d._getMarginBox(n, s);
1002                 var abs = d._abs(n, includeScroll);
1003                 mb.x = abs.x;
1004                 mb.y = abs.y;
1005                 return mb;
1006         }
1007
1008         // =============================
1009         // Element attribute Functions
1010         // =============================
1011
1012         var _fixAttrName = function(/*String*/name){
1013                 switch(name.toLowerCase()){
1014                         case "tabindex":
1015                                 // Internet Explorer will only set or remove tabindex
1016                                 // if it is spelled "tabIndex"
1017                                 // console.debug((dojo.isIE && dojo.isIE < 8)? "tabIndex" : "tabindex");
1018                                 return (d.isIE && d.isIE < 8) ? "tabIndex" : "tabindex";
1019                         default:
1020                                 return name;
1021                 }
1022         }
1023
1024         // non-deprecated HTML4 attributes with default values
1025         // http://www.w3.org/TR/html401/index/attributes.html
1026         // FF and Safari will return the default values if you
1027         // access the attributes via a property but not
1028         // via getAttribute()
1029         var _attrProps = {
1030                 colspan: "colSpan",
1031                 enctype: "enctype",
1032                 frameborder: "frameborder",
1033                 method: "method",
1034                 rowspan: "rowSpan",
1035                 scrolling: "scrolling",
1036                 shape: "shape",
1037                 span: "span",
1038                 type: "type",
1039                 valuetype: "valueType"
1040         }
1041
1042         dojo.hasAttr = function(/*DomNode|String*/node, /*String*/name){
1043                 //      summary:
1044                 //              Returns true if the requested attribute is specified on the
1045                 //              given element, and false otherwise.
1046                 //      node:
1047                 //              id or reference to the element to check
1048                 //      name:
1049                 //              the name of the attribute
1050                 //      returns:
1051                 //              true if the requested attribute is specified on the
1052                 //              given element, and false otherwise
1053                 var attr = d.byId(node).getAttributeNode(_fixAttrName(name));
1054                 return attr ? attr.specified : false; // Boolean
1055         }
1056
1057         var _evtHdlrMap = {
1058                 
1059         }
1060
1061         var _ctr = 0;
1062         var _attrId = dojo._scopeName + "attrid";
1063
1064         dojo.attr = function(/*DomNode|String*/node, /*String|Object*/name, /*String?*/value){
1065                 //      summary:
1066                 //              Gets or sets an attribute on an HTML element.
1067                 //      description:
1068                 //              Handles normalized getting and setting of attributes on DOM
1069                 //              Nodes. If 2 arguments are passed, and a the second argumnt is a
1070                 //              string, acts as a getter.
1071                 //      
1072                 //              If a third argument is passed, or if the second argumnt is a
1073                 //              map of attributes, acts as a setter.
1074                 //
1075                 //              When passing functions as values, note that they will not be
1076                 //              directly assigned to slots on the node, but rather the default
1077                 //              behavior will be removed and the new behavior will be added
1078                 //              using `dojo.connect()`, meaning that event handler properties
1079                 //              will be normalized and that some caveats with regards to
1080                 //              non-standard behaviors for onsubmit apply. Namely that you
1081                 //              should cancel form submission using `dojo.stopEvent()` on the
1082                 //              passed event object instead of returning a boolean value from
1083                 //              the handler itself.
1084                 //      node:
1085                 //              id or reference to the element to get or set the attribute on
1086                 //      name:
1087                 //              the name of the attribute to get or set.
1088                 //      value:
1089                 //              The value to set for the attribute
1090                 //      returns:
1091                 //              when used as a getter, the value of the requested attribute
1092                 //              or null if that attribute does not have a specified or
1093                 //              default value;
1094                 //
1095                 //              when user as a setter, undefined
1096                 //      example:
1097                 //      |       // get the current value of the "foo" attribute on a node
1098                 //      |       dojo.attr(dojo.byId("nodeId"), "foo");
1099                 //      |       
1100                 //      |       // we can just pass the id:
1101                 //      |       dojo.attr("nodeId", "foo");
1102                 //      |
1103                 //      |       // use attr() to set the tab index
1104                 //      |       dojo.attr("nodeId", "tabindex", 3);
1105                 //      |
1106                 //      |       // set multiple values at once, including event handlers:
1107                 //      |       dojo.attr("formId", {
1108                 //      |               "foo": "bar",
1109                 //      |               "tabindex": -1,
1110                 //      |               "method": "POST",
1111                 //      |               "onsubmit": function(e){
1112                 //      |                       // stop submitting the form. Note that the IE behavior
1113                 //      |                       // of returning true or false will have no effect here
1114                 //      |                       // since our handler is connect()ed to the built-in
1115                 //      |                       // onsubmit behavior and so we need to use
1116                 //      |                       // dojo.stopEvent() to ensure that the submission
1117                 //      |                       // doesn't proceed.
1118                 //      |                       dojo.stopEvent(e);
1119                 //      |
1120                 //      |                       // submit the form with Ajax
1121                 //      |                       dojo.xhrPost({ form: "formId" });
1122                 //      |               }
1123                 //      |       });
1124
1125                 var args = arguments.length;
1126                 if(args == 2 && !d.isString(name)){
1127                         for(var x in name){ d.attr(node, x, name[x]); }
1128                         return;
1129                 }
1130                 node = d.byId(node);
1131                 name = _fixAttrName(name);
1132                 if(args == 3){
1133                         if(d.isFunction(value)){
1134                                 // clobber if we can
1135                                 var attrId = d.attr(node, _attrId);
1136                                 if(!attrId){
1137                                         attrId = _ctr++;
1138                                         d.attr(node, _attrId, attrId);
1139                                 }
1140                                 if(!_evtHdlrMap[attrId]){
1141                                         _evtHdlrMap[attrId] = {};
1142                                 }
1143                                 var h = _evtHdlrMap[attrId][name];
1144                                 if(h){
1145                                         d.disconnect(h);
1146                                 }else{
1147                                         try{
1148                                                 delete node[name];
1149                                         }catch(e){}
1150                                 }
1151
1152                                 // ensure that event objects are normalized, etc.
1153                                 _evtHdlrMap[attrId][name] = d.connect(node, name, value);
1154
1155                         }else if(typeof value == "boolean"){ // e.g. onsubmit, disabled
1156                                 // if a function, we should normalize the event object here!!!
1157                                 node[name] = value;
1158                         }else{
1159                                 node.setAttribute(name, value);
1160                         }
1161                         return;
1162                 }else{
1163                         // should we access this attribute via a property or
1164                         // via getAttribute()?
1165                         var prop = _attrProps[name.toLowerCase()];
1166                         if(prop){
1167                                 return node[prop];
1168                         }else{
1169                                 var value = node[name];
1170                                 return (typeof value == 'boolean' || typeof value == 'function') ? value : (d.hasAttr(node, name) ? node.getAttribute(name) : null);
1171                         }
1172                 }
1173         }
1174
1175         dojo.removeAttr = function(/*DomNode|String*/node, /*String*/name){
1176                 //      summary:
1177                 //              Removes an attribute from an HTML element.
1178                 //      node:
1179                 //              id or reference to the element to remove the attribute from
1180                 //      name:
1181                 //              the name of the attribute to remove
1182                 d.byId(node).removeAttribute(_fixAttrName(name));
1183         }
1184 })();
1185
1186 // =============================
1187 // (CSS) Class Functions
1188 // =============================
1189
1190 dojo.hasClass = function(/*DomNode|String*/node, /*String*/classStr){
1191         //      summary:
1192         //              Returns whether or not the specified classes are a portion of the
1193         //              class list currently applied to the node. 
1194         return ((" "+dojo.byId(node).className+" ").indexOf(" "+classStr+" ") >= 0);  // Boolean
1195 };
1196
1197 dojo.addClass = function(/*DomNode|String*/node, /*String*/classStr){
1198         //      summary:
1199         //              Adds the specified classes to the end of the class list on the
1200         //              passed node.
1201         node = dojo.byId(node);
1202         var cls = node.className;
1203         if((" "+cls+" ").indexOf(" "+classStr+" ") < 0){
1204                 node.className = cls + (cls ? ' ' : '') + classStr;
1205         }
1206 };
1207
1208 dojo.removeClass = function(/*DomNode|String*/node, /*String*/classStr){
1209         // summary: Removes the specified classes from node.
1210         node = dojo.byId(node);
1211         var t = dojo.trim((" " + node.className + " ").replace(" " + classStr + " ", " "));
1212         if(node.className != t){ node.className = t; }
1213 };
1214
1215 dojo.toggleClass = function(/*DomNode|String*/node, /*String*/classStr, /*Boolean?*/condition){
1216         //      summary:        
1217         //              Adds a class to node if not present, or removes if present.
1218         //              Pass a boolean condition if you want to explicitly add or remove.
1219         //      condition:
1220         //              If passed, true means to add the class, false means to remove.
1221         if(condition === undefined){
1222                 condition = !dojo.hasClass(node, classStr);
1223         }
1224         dojo[condition ? "addClass" : "removeClass"](node, classStr);
1225 };
1226
1227 }