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