1 define("dojo/dom-geometry", ["./sniff", "./_base/window","./dom", "./dom-style"],
2 function(has
, win
, dom
, style
){
9 // This module defines the core dojo DOM geometry API.
12 // Box functions will assume this model.
13 // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode.
14 // Can be set to change behavior of box setters.
18 // "content-box" (default)
19 geom
.boxModel
= "content-box";
21 // We punt per-node box mode testing completely.
22 // If anybody cares, we can provide an additional (optional) unit
23 // that overrides existing code to include per-node box sensitivity.
25 // Opera documentation claims that Opera 9 uses border-box in BackCompat mode.
26 // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box.
27 // IIRC, earlier versions of Opera did in fact use border-box.
28 // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault.
30 if(has("ie") /*|| has("opera")*/){
31 // client code may have to adjust if compatMode varies across iframes
32 geom
.boxModel
= document
.compatMode
== "BackCompat" ? "border-box" : "content-box";
35 geom
.getPadExtents
= function getPadExtents(/*DomNode*/ node
, /*Object*/ computedStyle
){
37 // Returns object with special values specifically useful for node
40 // Returns an object with `w`, `h`, `l`, `t` properties:
41 // | l/t/r/b = left/top/right/bottom padding (respectively)
42 // | w = the total of the left and right padding
43 // | h = the total of the top and bottom padding
44 // If 'node' has position, l/t forms the origin for child nodes.
45 // The w/h are used for calculating boxes.
46 // Normally application code will not need to invoke this
47 // directly, and will use the ...box... functions instead.
49 // computedStyle: Object?
50 // This parameter accepts computed styles object.
51 // If this parameter is omitted, the functions will call
52 // dojo.getComputedStyle to get one. It is a better way, calling
53 // dojo.computedStyle once, and then pass the reference to this
54 // computedStyle parameter. Wherever possible, reuse the returned
55 // object of dojo/dom-style.getComputedStyle().
57 node
= dom
.byId(node
);
58 var s
= computedStyle
|| style
.getComputedStyle(node
), px
= style
.toPixelValue
,
59 l
= px(node
, s
.paddingLeft
), t
= px(node
, s
.paddingTop
), r
= px(node
, s
.paddingRight
), b
= px(node
, s
.paddingBottom
);
60 return {l
: l
, t
: t
, r
: r
, b
: b
, w
: l
+ r
, h
: t
+ b
};
65 geom
.getBorderExtents
= function getBorderExtents(/*DomNode*/ node
, /*Object*/ computedStyle
){
67 // returns an object with properties useful for noting the border
70 // - l/t/r/b = the sum of left/top/right/bottom border (respectively)
71 // - w = the sum of the left and right border
72 // - h = the sum of the top and bottom border
74 // The w/h are used for calculating boxes.
75 // Normally application code will not need to invoke this
76 // directly, and will use the ...box... functions instead.
78 // computedStyle: Object?
79 // This parameter accepts computed styles object.
80 // If this parameter is omitted, the functions will call
81 // dojo.getComputedStyle to get one. It is a better way, calling
82 // dojo.computedStyle once, and then pass the reference to this
83 // computedStyle parameter. Wherever possible, reuse the returned
84 // object of dojo/dom-style.getComputedStyle().
86 node
= dom
.byId(node
);
87 var px
= style
.toPixelValue
, s
= computedStyle
|| style
.getComputedStyle(node
),
88 l
= s
.borderLeftStyle
!= none
? px(node
, s
.borderLeftWidth
) : 0,
89 t
= s
.borderTopStyle
!= none
? px(node
, s
.borderTopWidth
) : 0,
90 r
= s
.borderRightStyle
!= none
? px(node
, s
.borderRightWidth
) : 0,
91 b
= s
.borderBottomStyle
!= none
? px(node
, s
.borderBottomWidth
) : 0;
92 return {l
: l
, t
: t
, r
: r
, b
: b
, w
: l
+ r
, h
: t
+ b
};
95 geom
.getPadBorderExtents
= function getPadBorderExtents(/*DomNode*/ node
, /*Object*/ computedStyle
){
97 // Returns object with properties useful for box fitting with
98 // regards to padding.
100 // - l/t/r/b = the sum of left/top/right/bottom padding and left/top/right/bottom border (respectively)
101 // - w = the sum of the left and right padding and border
102 // - h = the sum of the top and bottom padding and border
104 // The w/h are used for calculating boxes.
105 // Normally application code will not need to invoke this
106 // directly, and will use the ...box... functions instead.
108 // computedStyle: Object?
109 // This parameter accepts computed styles object.
110 // If this parameter is omitted, the functions will call
111 // dojo.getComputedStyle to get one. It is a better way, calling
112 // dojo.computedStyle once, and then pass the reference to this
113 // computedStyle parameter. Wherever possible, reuse the returned
114 // object of dojo/dom-style.getComputedStyle().
116 node
= dom
.byId(node
);
117 var s
= computedStyle
|| style
.getComputedStyle(node
),
118 p
= geom
.getPadExtents(node
, s
),
119 b
= geom
.getBorderExtents(node
, s
);
130 geom
.getMarginExtents
= function getMarginExtents(node
, computedStyle
){
132 // returns object with properties useful for box fitting with
133 // regards to box margins (i.e., the outer-box).
135 // - l/t = marginLeft, marginTop, respectively
136 // - w = total width, margin inclusive
137 // - h = total height, margin inclusive
139 // The w/h are used for calculating boxes.
140 // Normally application code will not need to invoke this
141 // directly, and will use the ...box... functions instead.
143 // computedStyle: Object?
144 // This parameter accepts computed styles object.
145 // If this parameter is omitted, the functions will call
146 // dojo.getComputedStyle to get one. It is a better way, calling
147 // dojo.computedStyle once, and then pass the reference to this
148 // computedStyle parameter. Wherever possible, reuse the returned
149 // object of dojo/dom-style.getComputedStyle().
151 node
= dom
.byId(node
);
152 var s
= computedStyle
|| style
.getComputedStyle(node
), px
= style
.toPixelValue
,
153 l
= px(node
, s
.marginLeft
), t
= px(node
, s
.marginTop
), r
= px(node
, s
.marginRight
), b
= px(node
, s
.marginBottom
);
154 return {l
: l
, t
: t
, r
: r
, b
: b
, w
: l
+ r
, h
: t
+ b
};
157 // Box getters work in any box context because offsetWidth/clientWidth
158 // are invariant wrt box context
160 // They do *not* work for display: inline objects that have padding styles
161 // because the user agent ignores padding (it's bogus styling in any case)
163 // Be careful with IMGs because they are inline or block depending on
164 // browser and browser mode.
166 // Although it would be easier to read, there are not separate versions of
167 // _getMarginBox for each browser because:
168 // 1. the branching is not expensive
169 // 2. factoring the shared code wastes cycles (function call overhead)
170 // 3. duplicating the shared code wastes bytes
172 geom
.getMarginBox
= function getMarginBox(/*DomNode*/ node
, /*Object*/ computedStyle
){
174 // returns an object that encodes the width, height, left and top
175 // positions of the node's margin box.
177 // computedStyle: Object?
178 // This parameter accepts computed styles object.
179 // If this parameter is omitted, the functions will call
180 // dojo.getComputedStyle to get one. It is a better way, calling
181 // dojo.computedStyle once, and then pass the reference to this
182 // computedStyle parameter. Wherever possible, reuse the returned
183 // object of dojo/dom-style.getComputedStyle().
185 node
= dom
.byId(node
);
186 var s
= computedStyle
|| style
.getComputedStyle(node
), me
= geom
.getMarginExtents(node
, s
),
187 l
= node
.offsetLeft
- me
.l
, t
= node
.offsetTop
- me
.t
, p
= node
.parentNode
, px
= style
.toPixelValue
, pcs
;
190 // If offsetParent has a computed overflow != visible, the offsetLeft is decreased
191 // by the parent's border.
192 // We don't want to compute the parent's style, so instead we examine node's
193 // computed left/top which is more stable.
194 var sl
= parseFloat(s
.left
), st
= parseFloat(s
.top
);
195 if(!isNaN(sl
) && !isNaN(st
)){
199 // If child's computed left/top are not parseable as a number (e.g. "auto"), we
200 // have no choice but to examine the parent's computed style.
202 pcs
= style
.getComputedStyle(p
);
203 if(pcs
.overflow
!= "visible"){
204 l
+= pcs
.borderLeftStyle
!= none
? px(node
, pcs
.borderLeftWidth
) : 0;
205 t
+= pcs
.borderTopStyle
!= none
? px(node
, pcs
.borderTopWidth
) : 0;
209 }else if(has("opera") || (has("ie") == 8 && !has("quirks"))){
210 // On Opera and IE 8, offsetLeft/Top includes the parent's border
212 pcs
= style
.getComputedStyle(p
);
213 l
-= pcs
.borderLeftStyle
!= none
? px(node
, pcs
.borderLeftWidth
) : 0;
214 t
-= pcs
.borderTopStyle
!= none
? px(node
, pcs
.borderTopWidth
) : 0;
217 return {l
: l
, t
: t
, w
: node
.offsetWidth
+ me
.w
, h
: node
.offsetHeight
+ me
.h
};
220 geom
.getContentBox
= function getContentBox(node
, computedStyle
){
222 // Returns an object that encodes the width, height, left and top
223 // positions of the node's content box, irrespective of the
224 // current box model.
226 // computedStyle: Object?
227 // This parameter accepts computed styles object.
228 // If this parameter is omitted, the functions will call
229 // dojo.getComputedStyle to get one. It is a better way, calling
230 // dojo.computedStyle once, and then pass the reference to this
231 // computedStyle parameter. Wherever possible, reuse the returned
232 // object of dojo/dom-style.getComputedStyle().
234 // clientWidth/Height are important since the automatically account for scrollbars
235 // fallback to offsetWidth/Height for special cases (see #3378)
236 node
= dom
.byId(node
);
237 var s
= computedStyle
|| style
.getComputedStyle(node
), w
= node
.clientWidth
, h
,
238 pe
= geom
.getPadExtents(node
, s
), be
= geom
.getBorderExtents(node
, s
);
240 w
= node
.offsetWidth
;
241 h
= node
.offsetHeight
;
243 h
= node
.clientHeight
;
246 // On Opera, offsetLeft includes the parent's border
251 return {l
: pe
.l
, t
: pe
.t
, w
: w
- pe
.w
- be
.w
, h
: h
- pe
.h
- be
.h
};
254 // Box setters depend on box context because interpretation of width/height styles
255 // vary wrt box context.
257 // The value of boxModel is used to determine box context.
258 // boxModel can be set directly to change behavior.
260 // Beware of display: inline objects that have padding styles
261 // because the user agent ignores padding (it's a bogus setup anyway)
263 // Be careful with IMGs because they are inline or block depending on
264 // browser and browser mode.
266 // Elements other than DIV may have special quirks, like built-in
267 // margins or padding, or values not detectable via computedStyle.
268 // In particular, margins on TABLE do not seems to appear
269 // at all in computedStyle on Mozilla.
271 function setBox(/*DomNode*/ node
, /*Number?*/ l
, /*Number?*/ t
, /*Number?*/ w
, /*Number?*/ h
, /*String?*/ u
){
273 // sets width/height/left/top in the current (native) box-model
274 // dimensions. Uses the unit passed in u.
276 // DOM Node reference. Id string not supported for performance
279 // left offset from parent.
281 // top offset from parent.
283 // width in current box model.
285 // width in current box model.
287 // unit measure to use for other measures. Defaults to "px".
304 function isButtonTag(/*DomNode*/ node
){
306 // True if the node is BUTTON or INPUT.type="button".
307 return node
.tagName
.toLowerCase() == "button" ||
308 node
.tagName
.toLowerCase() == "input" && (node
.getAttribute("type") || "").toLowerCase() == "button"; // boolean
311 function usesBorderBox(/*DomNode*/ node
){
313 // True if the node uses border-box layout.
315 // We could test the computed style of node to see if a particular box
316 // has been specified, but there are details and we choose not to bother.
318 // TABLE and BUTTON (and INPUT type=button) are always border-box by default.
319 // If you have assigned a different box to either one via CSS then
320 // box functions will break.
322 return geom
.boxModel
== "border-box" || node
.tagName
.toLowerCase() == "table" || isButtonTag(node
); // boolean
325 geom
.setContentSize
= function setContentSize(/*DomNode*/ node
, /*Object*/ box
, /*Object*/ computedStyle
){
327 // Sets the size of the node's contents, irrespective of margins,
328 // padding, or borders.
331 // hash with optional "w", and "h" properties for "width", and "height"
332 // respectively. All specified properties should have numeric values in whole pixels.
333 // computedStyle: Object?
334 // This parameter accepts computed styles object.
335 // If this parameter is omitted, the functions will call
336 // dojo.getComputedStyle to get one. It is a better way, calling
337 // dojo.computedStyle once, and then pass the reference to this
338 // computedStyle parameter. Wherever possible, reuse the returned
339 // object of dojo/dom-style.getComputedStyle().
341 node
= dom
.byId(node
);
342 var w
= box
.w
, h
= box
.h
;
343 if(usesBorderBox(node
)){
344 var pb
= geom
.getPadBorderExtents(node
, computedStyle
);
352 setBox(node
, NaN
, NaN
, w
, h
);
355 var nilExtents
= {l
: 0, t
: 0, w
: 0, h
: 0};
357 geom
.setMarginBox
= function setMarginBox(/*DomNode*/ node
, /*Object*/ box
, /*Object*/ computedStyle
){
359 // sets the size of the node's margin box and placement
360 // (left/top), irrespective of box model. Think of it as a
361 // passthrough to setBox that handles box-model vagaries for
365 // hash with optional "l", "t", "w", and "h" properties for "left", "right", "width", and "height"
366 // respectively. All specified properties should have numeric values in whole pixels.
367 // computedStyle: Object?
368 // This parameter accepts computed styles object.
369 // If this parameter is omitted, the functions will call
370 // dojo.getComputedStyle to get one. It is a better way, calling
371 // dojo.computedStyle once, and then pass the reference to this
372 // computedStyle parameter. Wherever possible, reuse the returned
373 // object of dojo/dom-style.getComputedStyle().
375 node
= dom
.byId(node
);
376 var s
= computedStyle
|| style
.getComputedStyle(node
), w
= box
.w
, h
= box
.h
,
377 // Some elements have special padding, margin, and box-model settings.
378 // To use box functions you may need to set padding, margin explicitly.
379 // Controlling box-model is harder, in a pinch you might set dojo/dom-geometry.boxModel.
380 pb
= usesBorderBox(node
) ? nilExtents
: geom
.getPadBorderExtents(node
, s
),
381 mb
= geom
.getMarginExtents(node
, s
);
383 // on Safari (3.1.2), button nodes with no explicit size have a default margin
384 // setting an explicit size eliminates the margin.
385 // We have to swizzle the width to get correct margin reading.
386 if(isButtonTag(node
)){
388 if(w
>= 0 && !ns
.width
){
391 if(h
>= 0 && !ns
.height
){
397 w
= Math
.max(w
- pb
.w
- mb
.w
, 0);
400 h
= Math
.max(h
- pb
.h
- mb
.h
, 0);
402 setBox(node
, box
.l
, box
.t
, w
, h
);
405 // =============================
407 // =============================
409 geom
.isBodyLtr
= function isBodyLtr(/*Document?*/ doc
){
411 // Returns true if the current language is left-to-right, and false otherwise.
413 // Optional document to query. If unspecified, use win.doc.
416 doc
= doc
|| win
.doc
;
417 return (win
.body(doc
).dir
|| doc
.documentElement
.dir
|| "ltr").toLowerCase() == "ltr"; // Boolean
420 geom
.docScroll
= function docScroll(/*Document?*/ doc
){
422 // Returns an object with {node, x, y} with corresponding offsets.
424 // Optional document to query. If unspecified, use win.doc.
427 doc
= doc
|| win
.doc
;
428 var node
= win
.doc
.parentWindow
|| win
.doc
.defaultView
; // use UI window, not dojo.global window. TODO: use dojo/window::get() except for circular dependency problem
429 return "pageXOffset" in node
? {x
: node
.pageXOffset
, y
: node
.pageYOffset
} :
430 (node
= has("quirks") ? win
.body(doc
) : doc
.documentElement
) &&
431 {x
: geom
.fixIeBiDiScrollLeft(node
.scrollLeft
|| 0, doc
), y
: node
.scrollTop
|| 0 };
435 geom
.getIeDocumentElementOffset
= function getIeDocumentElementOffset(/*Document?*/ doc
){
437 // returns the offset in x and y from the document body to the
438 // visual edge of the page for IE
440 // Optional document to query. If unspecified, use win.doc.
442 // The following values in IE contain an offset:
445 // | node.getBoundingClientRect().left
446 // | node.getBoundingClientRect().top
447 // But other position related values do not contain this offset,
448 // such as node.offsetLeft, node.offsetTop, node.style.left and
449 // node.style.top. The offset is always (2, 2) in LTR direction.
450 // When the body is in RTL direction, the offset counts the width
451 // of left scroll bar's width. This function computes the actual
454 //NOTE: assumes we're being called in an IE browser
456 doc
= doc
|| win
.doc
;
457 var de
= doc
.documentElement
; // only deal with HTML element here, position() handles body/quirks
460 var r
= de
.getBoundingClientRect(), // works well for IE6+
461 l
= r
.left
, t
= r
.top
;
463 l
+= de
.clientLeft
; // scrollbar size in strict/RTL, or,
464 t
+= de
.clientTop
; // HTML border size in strict
467 x
: l
< 0 ? 0 : l
, // FRAME element border size can lead to inaccurate negative values
479 geom
.fixIeBiDiScrollLeft
= function fixIeBiDiScrollLeft(/*Integer*/ scrollLeft
, /*Document?*/ doc
){
481 // In RTL direction, scrollLeft should be a negative value, but IE
482 // returns a positive one. All codes using documentElement.scrollLeft
483 // must call this function to fix this error, otherwise the position
484 // will offset to right when there is a horizontal scrollbar.
485 // scrollLeft: Number
487 // Optional document to query. If unspecified, use win.doc.
490 // In RTL direction, scrollLeft should be a negative value, but IE
491 // returns a positive one. All codes using documentElement.scrollLeft
492 // must call this function to fix this error, otherwise the position
493 // will offset to right when there is a horizontal scrollbar.
495 doc
= doc
|| win
.doc
;
497 if(ie
&& !geom
.isBodyLtr(doc
)){
498 var qk
= has("quirks"),
499 de
= qk
? win
.body(doc
) : doc
.documentElement
,
500 pwin
= win
.global
; // TODO: use winUtils.get(doc) after resolving circular dependency b/w dom-geometry.js and dojo/window.js
501 if(ie
== 6 && !qk
&& pwin
.frameElement
&& de
.scrollHeight
> de
.clientHeight
){
502 scrollLeft
+= de
.clientLeft
; // workaround ie6+strict+rtl+iframe+vertical-scrollbar bug where clientWidth is too small by clientLeft pixels
504 return (ie
< 8 || qk
) ? (scrollLeft
+ de
.clientWidth
- de
.scrollWidth
) : -scrollLeft
; // Integer
506 return scrollLeft
; // Integer
509 geom
.position = function(/*DomNode*/ node
, /*Boolean?*/ includeScroll
){
511 // Gets the position and size of the passed element relative to
512 // the viewport (if includeScroll==false), or relative to the
513 // document root (if includeScroll==true).
516 // Returns an object of the form:
517 // `{ x: 100, y: 300, w: 20, h: 15 }`.
518 // If includeScroll==true, the x and y values will include any
519 // document offsets that may affect the position relative to the
521 // Uses the border-box model (inclusive of border and padding but
522 // not margin). Does not act as a setter.
523 // node: DOMNode|String
524 // includeScroll: Boolean?
527 node
= dom
.byId(node
);
528 var db
= win
.body(node
.ownerDocument
),
529 ret
= node
.getBoundingClientRect();
530 ret
= {x
: ret
.left
, y
: ret
.top
, w
: ret
.right
- ret
.left
, h
: ret
.bottom
- ret
.top
};
533 // On IE<9 there's a 2px offset that we need to adjust for, see dojo.getIeDocumentElementOffset()
534 var offset
= geom
.getIeDocumentElementOffset(node
.ownerDocument
);
536 // fixes the position in IE, quirks mode
537 ret
.x
-= offset
.x
+ (has("quirks") ? db
.clientLeft
+ db
.offsetLeft
: 0);
538 ret
.y
-= offset
.y
+ (has("quirks") ? db
.clientTop
+ db
.offsetTop
: 0);
541 // account for document scrolling
542 // if offsetParent is used, ret value already includes scroll position
543 // so we may have to actually remove that value if !includeScroll
545 var scroll
= geom
.docScroll(node
.ownerDocument
);
550 return ret
; // Object
553 // random "private" functions wildly used throughout the toolkit
555 geom
.getMarginSize
= function getMarginSize(/*DomNode*/ node
, /*Object*/ computedStyle
){
557 // returns an object that encodes the width and height of
558 // the node's margin box
559 // node: DOMNode|String
560 // computedStyle: Object?
561 // This parameter accepts computed styles object.
562 // If this parameter is omitted, the functions will call
563 // dojo.getComputedStyle to get one. It is a better way, calling
564 // dojo.computedStyle once, and then pass the reference to this
565 // computedStyle parameter. Wherever possible, reuse the returned
566 // object of dojo/dom-style.getComputedStyle().
568 node
= dom
.byId(node
);
569 var me
= geom
.getMarginExtents(node
, computedStyle
|| style
.getComputedStyle(node
));
570 var size
= node
.getBoundingClientRect();
572 w
: (size
.right
- size
.left
) + me
.w
,
573 h
: (size
.bottom
- size
.top
) + me
.h
577 geom
.normalizeEvent = function(event
){
579 // Normalizes the geometry of a DOM event, normalizing the pageX, pageY,
580 // offsetX, offsetY, layerX, and layerX properties
582 if(!("layerX" in event
)){
583 event
.layerX
= event
.offsetX
;
584 event
.layerY
= event
.offsetY
;
586 if(!has("dom-addeventlistener")){
588 // FIXME: scroll position query is duped from dojo.html to
589 // avoid dependency on that entire module. Now that HTML is in
590 // Base, we should convert back to something similar there.
591 var se
= event
.target
;
592 var doc
= (se
&& se
.ownerDocument
) || document
;
593 // DO NOT replace the following to use dojo.body(), in IE, document.documentElement should be used
594 // here rather than document.body
595 var docBody
= has("quirks") ? doc
.body
: doc
.documentElement
;
596 var offset
= geom
.getIeDocumentElementOffset(doc
);
597 event
.pageX
= event
.clientX
+ geom
.fixIeBiDiScrollLeft(docBody
.scrollLeft
|| 0, doc
) - offset
.x
;
598 event
.pageY
= event
.clientY
+ (docBody
.scrollTop
|| 0) - offset
.y
;
602 // TODO: evaluate separate getters/setters for position and sizes?