dom-geometry.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. define("dojo/dom-geometry", ["./_base/sniff", "./_base/window","./dom", "./dom-style"],
  2. function(has, win, dom, style){
  3. // module:
  4. // dojo/dom-geometry
  5. // summary:
  6. // This module defines the core dojo DOM geometry API.
  7. var geom = {}; // the result object
  8. // Box functions will assume this model.
  9. // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode.
  10. // Can be set to change behavior of box setters.
  11. // can be either:
  12. // "border-box"
  13. // "content-box" (default)
  14. geom.boxModel = "content-box";
  15. // We punt per-node box mode testing completely.
  16. // If anybody cares, we can provide an additional (optional) unit
  17. // that overrides existing code to include per-node box sensitivity.
  18. // Opera documentation claims that Opera 9 uses border-box in BackCompat mode.
  19. // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box.
  20. // IIRC, earlier versions of Opera did in fact use border-box.
  21. // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault.
  22. if(has("ie") /*|| has("opera")*/){
  23. // client code may have to adjust if compatMode varies across iframes
  24. geom.boxModel = document.compatMode == "BackCompat" ? "border-box" : "content-box";
  25. }
  26. // =============================
  27. // Box Functions
  28. // =============================
  29. /*=====
  30. dojo.getPadExtents = function(node, computedStyle){
  31. // summary:
  32. // Returns object with special values specifically useful for node
  33. // fitting.
  34. // description:
  35. // Returns an object with `w`, `h`, `l`, `t` properties:
  36. // | l/t/r/b = left/top/right/bottom padding (respectively)
  37. // | w = the total of the left and right padding
  38. // | h = the total of the top and bottom padding
  39. // If 'node' has position, l/t forms the origin for child nodes.
  40. // The w/h are used for calculating boxes.
  41. // Normally application code will not need to invoke this
  42. // directly, and will use the ...box... functions instead.
  43. // node: DOMNode
  44. // computedStyle: Object?
  45. // This parameter accepts computed styles object.
  46. // If this parameter is omitted, the functions will call
  47. // dojo.getComputedStyle to get one. It is a better way, calling
  48. // dojo.computedStyle once, and then pass the reference to this
  49. // computedStyle parameter. Wherever possible, reuse the returned
  50. // object of dojo.getComputedStyle.
  51. };
  52. =====*/
  53. /*=====
  54. dojo._getPadExtents = function(node, computedStyle){
  55. // summary:
  56. // Existing alias for `dojo.getPadExtents`. Deprecated, will be removed in 2.0.
  57. };
  58. =====*/
  59. /*=====
  60. dojo.getBorderExtents = function(node, computedStyle){
  61. // summary:
  62. // returns an object with properties useful for noting the border
  63. // dimensions.
  64. // description:
  65. // * l/t/r/b = the sum of left/top/right/bottom border (respectively)
  66. // * w = the sum of the left and right border
  67. // * h = the sum of the top and bottom border
  68. //
  69. // The w/h are used for calculating boxes.
  70. // Normally application code will not need to invoke this
  71. // directly, and will use the ...box... functions instead.
  72. // node: DOMNode
  73. // computedStyle: Object?
  74. // This parameter accepts computed styles object.
  75. // If this parameter is omitted, the functions will call
  76. // dojo.getComputedStyle to get one. It is a better way, calling
  77. // dojo.computedStyle once, and then pass the reference to this
  78. // computedStyle parameter. Wherever possible, reuse the returned
  79. // object of dojo.getComputedStyle.
  80. };
  81. =====*/
  82. /*=====
  83. dojo._getBorderExtents = function(node, computedStyle){
  84. // summary:
  85. // Existing alias for `dojo.getBorderExtents`. Deprecated, will be removed in 2.0.
  86. };
  87. =====*/
  88. /*=====
  89. dojo.getPadBorderExtents = function(node, computedStyle){
  90. // summary:
  91. // Returns object with properties useful for box fitting with
  92. // regards to padding.
  93. // description:
  94. // * l/t/r/b = the sum of left/top/right/bottom padding and left/top/right/bottom border (respectively)
  95. // * w = the sum of the left and right padding and border
  96. // * h = the sum of the top and bottom padding and border
  97. //
  98. // The w/h are used for calculating boxes.
  99. // Normally application code will not need to invoke this
  100. // directly, and will use the ...box... functions instead.
  101. // node: DOMNode
  102. // computedStyle: Object?
  103. // This parameter accepts computed styles object.
  104. // If this parameter is omitted, the functions will call
  105. // dojo.getComputedStyle to get one. It is a better way, calling
  106. // dojo.computedStyle once, and then pass the reference to this
  107. // computedStyle parameter. Wherever possible, reuse the returned
  108. // object of dojo.getComputedStyle.
  109. };
  110. =====*/
  111. /*=====
  112. dojo._getPadBorderExtents = function(node, computedStyle){
  113. // summary:
  114. // Existing alias for `dojo.getPadBorderExtents`. Deprecated, will be removed in 2.0.
  115. };
  116. =====*/
  117. /*=====
  118. dojo.getMarginExtents = function(node, computedStyle){
  119. // summary:
  120. // returns object with properties useful for box fitting with
  121. // regards to box margins (i.e., the outer-box).
  122. //
  123. // * l/t = marginLeft, marginTop, respectively
  124. // * w = total width, margin inclusive
  125. // * h = total height, margin inclusive
  126. //
  127. // The w/h are used for calculating boxes.
  128. // Normally application code will not need to invoke this
  129. // directly, and will use the ...box... functions instead.
  130. // node: DOMNode
  131. // computedStyle: Object?
  132. // This parameter accepts computed styles object.
  133. // If this parameter is omitted, the functions will call
  134. // dojo.getComputedStyle to get one. It is a better way, calling
  135. // dojo.computedStyle once, and then pass the reference to this
  136. // computedStyle parameter. Wherever possible, reuse the returned
  137. // object of dojo.getComputedStyle.
  138. };
  139. =====*/
  140. /*=====
  141. dojo._getMarginExtents = function(node, computedStyle){
  142. // summary:
  143. // Existing alias for `dojo.getMarginExtents`. Deprecated, will be removed in 2.0.
  144. };
  145. =====*/
  146. /*=====
  147. dojo.getMarginSize = function(node, computedStyle){
  148. // summary:
  149. // returns an object that encodes the width and height of
  150. // the node's margin box
  151. // node: DOMNode|String
  152. // computedStyle: Object?
  153. // This parameter accepts computed styles object.
  154. // If this parameter is omitted, the functions will call
  155. // dojo.getComputedStyle to get one. It is a better way, calling
  156. // dojo.computedStyle once, and then pass the reference to this
  157. // computedStyle parameter. Wherever possible, reuse the returned
  158. // object of dojo.getComputedStyle.
  159. };
  160. =====*/
  161. /*=====
  162. dojo._getMarginSize = function(node, computedStyle){
  163. // summary:
  164. // Existing alias for `dojo.getMarginSize`. Deprecated, will be removed in 2.0.
  165. };
  166. =====*/
  167. /*=====
  168. dojo.getMarginBox = function(node, computedStyle){
  169. // summary:
  170. // returns an object that encodes the width, height, left and top
  171. // positions of the node's margin box.
  172. // node: DOMNode
  173. // computedStyle: Object?
  174. // This parameter accepts computed styles object.
  175. // If this parameter is omitted, the functions will call
  176. // dojo.getComputedStyle to get one. It is a better way, calling
  177. // dojo.computedStyle once, and then pass the reference to this
  178. // computedStyle parameter. Wherever possible, reuse the returned
  179. // object of dojo.getComputedStyle.
  180. };
  181. =====*/
  182. /*=====
  183. dojo._getMarginBox = function(node, computedStyle){
  184. // summary:
  185. // Existing alias for `dojo.getMarginBox`. Deprecated, will be removed in 2.0.
  186. };
  187. =====*/
  188. /*=====
  189. dojo.setMarginBox = function(node, box, computedStyle){
  190. // summary:
  191. // sets the size of the node's margin box and placement
  192. // (left/top), irrespective of box model. Think of it as a
  193. // passthrough to setBox that handles box-model vagaries for
  194. // you.
  195. // node: DOMNode
  196. // box: Object
  197. // hash with optional "l", "t", "w", and "h" properties for "left", "right", "width", and "height"
  198. // respectively. All specified properties should have numeric values in whole pixels.
  199. // computedStyle: Object?
  200. // This parameter accepts computed styles object.
  201. // If this parameter is omitted, the functions will call
  202. // dojo.getComputedStyle to get one. It is a better way, calling
  203. // dojo.computedStyle once, and then pass the reference to this
  204. // computedStyle parameter. Wherever possible, reuse the returned
  205. // object of dojo.getComputedStyle.
  206. };
  207. =====*/
  208. /*=====
  209. dojo.getContentBox = function(node, computedStyle){
  210. // summary:
  211. // Returns an object that encodes the width, height, left and top
  212. // positions of the node's content box, irrespective of the
  213. // current box model.
  214. // node: DOMNode
  215. // computedStyle: Object?
  216. // This parameter accepts computed styles object.
  217. // If this parameter is omitted, the functions will call
  218. // dojo.getComputedStyle to get one. It is a better way, calling
  219. // dojo.computedStyle once, and then pass the reference to this
  220. // computedStyle parameter. Wherever possible, reuse the returned
  221. // object of dojo.getComputedStyle.
  222. };
  223. =====*/
  224. /*=====
  225. dojo._getContentBox = function(node, computedStyle){
  226. // summary:
  227. // Existing alias for `dojo.getContentBox`. Deprecated, will be removed in 2.0.
  228. };
  229. =====*/
  230. /*=====
  231. dojo.setContentSize = function(node, box, computedStyle){
  232. // summary:
  233. // Sets the size of the node's contents, irrespective of margins,
  234. // padding, or borders.
  235. // node: DOMNode
  236. // box: Object
  237. // hash with optional "w", and "h" properties for "width", and "height"
  238. // respectively. All specified properties should have numeric values in whole pixels.
  239. // computedStyle: Object?
  240. // This parameter accepts computed styles object.
  241. // If this parameter is omitted, the functions will call
  242. // dojo.getComputedStyle to get one. It is a better way, calling
  243. // dojo.computedStyle once, and then pass the reference to this
  244. // computedStyle parameter. Wherever possible, reuse the returned
  245. // object of dojo.getComputedStyle.
  246. };
  247. =====*/
  248. /*=====
  249. dojo.isBodyLtr = function(){
  250. // summary:
  251. // Returns true if the current language is left-to-right, and false otherwise.
  252. // returns: Boolean
  253. };
  254. =====*/
  255. /*=====
  256. dojo._isBodyLtr = function(){
  257. // summary:
  258. // Existing alias for `dojo.isBodyLtr`. Deprecated, will be removed in 2.0.
  259. };
  260. =====*/
  261. /*=====
  262. dojo.docScroll = function(){
  263. // summary:
  264. // Returns an object with {node, x, y} with corresponding offsets.
  265. // returns: Object
  266. };
  267. =====*/
  268. /*=====
  269. dojo._docScroll = function(){
  270. // summary:
  271. // Existing alias for `dojo.docScroll`. Deprecated, will be removed in 2.0.
  272. };
  273. =====*/
  274. /*=====
  275. dojo.getIeDocumentElementOffset = function(){
  276. // summary:
  277. // returns the offset in x and y from the document body to the
  278. // visual edge of the page for IE
  279. // description:
  280. // The following values in IE contain an offset:
  281. // | event.clientX
  282. // | event.clientY
  283. // | node.getBoundingClientRect().left
  284. // | node.getBoundingClientRect().top
  285. // But other position related values do not contain this offset,
  286. // such as node.offsetLeft, node.offsetTop, node.style.left and
  287. // node.style.top. The offset is always (2, 2) in LTR direction.
  288. // When the body is in RTL direction, the offset counts the width
  289. // of left scroll bar's width. This function computes the actual
  290. // offset.
  291. };
  292. =====*/
  293. /*=====
  294. dojo._getIeDocumentElementOffset = function(){
  295. // summary:
  296. // Existing alias for `dojo.getIeDocumentElementOffset`. Deprecated, will be removed in 2.0.
  297. };
  298. =====*/
  299. /*=====
  300. dojo.fixIeBiDiScrollLeft = function(scrollLeft){
  301. // summary:
  302. // In RTL direction, scrollLeft should be a negative value, but IE
  303. // returns a positive one. All codes using documentElement.scrollLeft
  304. // must call this function to fix this error, otherwise the position
  305. // will offset to right when there is a horizontal scrollbar.
  306. // scrollLeft: NUmber
  307. // returns: Number
  308. };
  309. =====*/
  310. /*=====
  311. dojo._fixIeBiDiScrollLeft = function(scrollLeft){
  312. // summary:
  313. // Existing alias for `dojo.fixIeBiDiScrollLeft`. Deprecated, will be removed in 2.0.
  314. };
  315. =====*/
  316. /*=====
  317. dojo.position = function(node, includeScroll){
  318. // summary:
  319. // Gets the position and size of the passed element relative to
  320. // the viewport (if includeScroll==false), or relative to the
  321. // document root (if includeScroll==true).
  322. //
  323. // description:
  324. // Returns an object of the form:
  325. // { x: 100, y: 300, w: 20, h: 15 }
  326. // If includeScroll==true, the x and y values will include any
  327. // document offsets that may affect the position relative to the
  328. // viewport.
  329. // Uses the border-box model (inclusive of border and padding but
  330. // not margin). Does not act as a setter.
  331. // node: DOMNode|String
  332. // includeScroll: Boolean?
  333. // returns: Object
  334. };
  335. =====*/
  336. geom.getPadExtents = function getPadExtents(/*DomNode*/node, /*Object*/computedStyle){
  337. node = dom.byId(node);
  338. var s = computedStyle || style.getComputedStyle(node), px = style.toPixelValue,
  339. l = px(node, s.paddingLeft), t = px(node, s.paddingTop), r = px(node, s.paddingRight), b = px(node, s.paddingBottom);
  340. return {l: l, t: t, r: r, b: b, w: l + r, h: t + b};
  341. };
  342. var none = "none";
  343. geom.getBorderExtents = function getBorderExtents(/*DomNode*/node, /*Object*/computedStyle){
  344. node = dom.byId(node);
  345. var px = style.toPixelValue, s = computedStyle || style.getComputedStyle(node),
  346. l = s.borderLeftStyle != none ? px(node, s.borderLeftWidth) : 0,
  347. t = s.borderTopStyle != none ? px(node, s.borderTopWidth) : 0,
  348. r = s.borderRightStyle != none ? px(node, s.borderRightWidth) : 0,
  349. b = s.borderBottomStyle != none ? px(node, s.borderBottomWidth) : 0;
  350. return {l: l, t: t, r: r, b: b, w: l + r, h: t + b};
  351. };
  352. geom.getPadBorderExtents = function getPadBorderExtents(/*DomNode*/node, /*Object*/computedStyle){
  353. node = dom.byId(node);
  354. var s = computedStyle || style.getComputedStyle(node),
  355. p = geom.getPadExtents(node, s),
  356. b = geom.getBorderExtents(node, s);
  357. return {
  358. l: p.l + b.l,
  359. t: p.t + b.t,
  360. r: p.r + b.r,
  361. b: p.b + b.b,
  362. w: p.w + b.w,
  363. h: p.h + b.h
  364. };
  365. };
  366. geom.getMarginExtents = function getMarginExtents(node, computedStyle){
  367. node = dom.byId(node);
  368. var s = computedStyle || style.getComputedStyle(node), px = style.toPixelValue,
  369. l = px(node, s.marginLeft), t = px(node, s.marginTop), r = px(node, s.marginRight), b = px(node, s.marginBottom);
  370. if(has("webkit") && (s.position != "absolute")){
  371. // FIXME: Safari's version of the computed right margin
  372. // is the space between our right edge and the right edge
  373. // of our offsetParent.
  374. // What we are looking for is the actual margin value as
  375. // determined by CSS.
  376. // Hack solution is to assume left/right margins are the same.
  377. r = l;
  378. }
  379. return {l: l, t: t, r: r, b: b, w: l + r, h: t + b};
  380. };
  381. // Box getters work in any box context because offsetWidth/clientWidth
  382. // are invariant wrt box context
  383. //
  384. // They do *not* work for display: inline objects that have padding styles
  385. // because the user agent ignores padding (it's bogus styling in any case)
  386. //
  387. // Be careful with IMGs because they are inline or block depending on
  388. // browser and browser mode.
  389. // Although it would be easier to read, there are not separate versions of
  390. // _getMarginBox for each browser because:
  391. // 1. the branching is not expensive
  392. // 2. factoring the shared code wastes cycles (function call overhead)
  393. // 3. duplicating the shared code wastes bytes
  394. geom.getMarginBox = function getMarginBox(/*DomNode*/node, /*Object*/computedStyle){
  395. // summary:
  396. // returns an object that encodes the width, height, left and top
  397. // positions of the node's margin box.
  398. node = dom.byId(node);
  399. var s = computedStyle || style.getComputedStyle(node), me = geom.getMarginExtents(node, s),
  400. l = node.offsetLeft - me.l, t = node.offsetTop - me.t, p = node.parentNode, px = style.toPixelValue, pcs;
  401. if(has("mozilla")){
  402. // Mozilla:
  403. // If offsetParent has a computed overflow != visible, the offsetLeft is decreased
  404. // by the parent's border.
  405. // We don't want to compute the parent's style, so instead we examine node's
  406. // computed left/top which is more stable.
  407. var sl = parseFloat(s.left), st = parseFloat(s.top);
  408. if(!isNaN(sl) && !isNaN(st)){
  409. l = sl, t = st;
  410. }else{
  411. // If child's computed left/top are not parseable as a number (e.g. "auto"), we
  412. // have no choice but to examine the parent's computed style.
  413. if(p && p.style){
  414. pcs = style.getComputedStyle(p);
  415. if(pcs.overflow != "visible"){
  416. l += pcs.borderLeftStyle != none ? px(node, pcs.borderLeftWidth) : 0;
  417. t += pcs.borderTopStyle != none ? px(node, pcs.borderTopWidth) : 0;
  418. }
  419. }
  420. }
  421. }else if(has("opera") || (has("ie") == 8 && !has("quirks"))){
  422. // On Opera and IE 8, offsetLeft/Top includes the parent's border
  423. if(p){
  424. pcs = style.getComputedStyle(p);
  425. l -= pcs.borderLeftStyle != none ? px(node, pcs.borderLeftWidth) : 0;
  426. t -= pcs.borderTopStyle != none ? px(node, pcs.borderTopWidth) : 0;
  427. }
  428. }
  429. return {l: l, t: t, w: node.offsetWidth + me.w, h: node.offsetHeight + me.h};
  430. };
  431. geom.getContentBox = function getContentBox(node, computedStyle){
  432. // clientWidth/Height are important since the automatically account for scrollbars
  433. // fallback to offsetWidth/Height for special cases (see #3378)
  434. node = dom.byId(node);
  435. var s = computedStyle || style.getComputedStyle(node), w = node.clientWidth, h,
  436. pe = geom.getPadExtents(node, s), be = geom.getBorderExtents(node, s);
  437. if(!w){
  438. w = node.offsetWidth;
  439. h = node.offsetHeight;
  440. }else{
  441. h = node.clientHeight;
  442. be.w = be.h = 0;
  443. }
  444. // On Opera, offsetLeft includes the parent's border
  445. if(has("opera")){
  446. pe.l += be.l;
  447. pe.t += be.t;
  448. }
  449. return {l: pe.l, t: pe.t, w: w - pe.w - be.w, h: h - pe.h - be.h};
  450. };
  451. // Box setters depend on box context because interpretation of width/height styles
  452. // vary wrt box context.
  453. //
  454. // The value of dojo.boxModel is used to determine box context.
  455. // dojo.boxModel can be set directly to change behavior.
  456. //
  457. // Beware of display: inline objects that have padding styles
  458. // because the user agent ignores padding (it's a bogus setup anyway)
  459. //
  460. // Be careful with IMGs because they are inline or block depending on
  461. // browser and browser mode.
  462. //
  463. // Elements other than DIV may have special quirks, like built-in
  464. // margins or padding, or values not detectable via computedStyle.
  465. // In particular, margins on TABLE do not seems to appear
  466. // at all in computedStyle on Mozilla.
  467. function setBox(/*DomNode*/node, /*Number?*/l, /*Number?*/t, /*Number?*/w, /*Number?*/h, /*String?*/u){
  468. // summary:
  469. // sets width/height/left/top in the current (native) box-model
  470. // dimensions. Uses the unit passed in u.
  471. // node:
  472. // DOM Node reference. Id string not supported for performance
  473. // reasons.
  474. // l:
  475. // left offset from parent.
  476. // t:
  477. // top offset from parent.
  478. // w:
  479. // width in current box model.
  480. // h:
  481. // width in current box model.
  482. // u:
  483. // unit measure to use for other measures. Defaults to "px".
  484. u = u || "px";
  485. var s = node.style;
  486. if(!isNaN(l)){
  487. s.left = l + u;
  488. }
  489. if(!isNaN(t)){
  490. s.top = t + u;
  491. }
  492. if(w >= 0){
  493. s.width = w + u;
  494. }
  495. if(h >= 0){
  496. s.height = h + u;
  497. }
  498. }
  499. function isButtonTag(/*DomNode*/node){
  500. // summary:
  501. // True if the node is BUTTON or INPUT.type="button".
  502. return node.tagName.toLowerCase() == "button" ||
  503. node.tagName.toLowerCase() == "input" && (node.getAttribute("type") || "").toLowerCase() == "button"; // boolean
  504. }
  505. function usesBorderBox(/*DomNode*/node){
  506. // summary:
  507. // True if the node uses border-box layout.
  508. // We could test the computed style of node to see if a particular box
  509. // has been specified, but there are details and we choose not to bother.
  510. // TABLE and BUTTON (and INPUT type=button) are always border-box by default.
  511. // If you have assigned a different box to either one via CSS then
  512. // box functions will break.
  513. return geom.boxModel == "border-box" || node.tagName.toLowerCase() == "table" || isButtonTag(node); // boolean
  514. }
  515. geom.setContentSize = function setContentSize(/*DomNode*/node, /*Object*/box, /*Object*/computedStyle){
  516. // summary:
  517. // Sets the size of the node's contents, irrespective of margins,
  518. // padding, or borders.
  519. node = dom.byId(node);
  520. var w = box.w, h = box.h;
  521. if(usesBorderBox(node)){
  522. var pb = geom.getPadBorderExtents(node, computedStyle);
  523. if(w >= 0){
  524. w += pb.w;
  525. }
  526. if(h >= 0){
  527. h += pb.h;
  528. }
  529. }
  530. setBox(node, NaN, NaN, w, h);
  531. };
  532. var nilExtents = {l: 0, t: 0, w: 0, h: 0};
  533. geom.setMarginBox = function setMarginBox(/*DomNode*/node, /*Object*/box, /*Object*/computedStyle){
  534. node = dom.byId(node);
  535. var s = computedStyle || style.getComputedStyle(node), w = box.w, h = box.h,
  536. // Some elements have special padding, margin, and box-model settings.
  537. // To use box functions you may need to set padding, margin explicitly.
  538. // Controlling box-model is harder, in a pinch you might set dojo.boxModel.
  539. pb = usesBorderBox(node) ? nilExtents : geom.getPadBorderExtents(node, s),
  540. mb = geom.getMarginExtents(node, s);
  541. if(has("webkit")){
  542. // on Safari (3.1.2), button nodes with no explicit size have a default margin
  543. // setting an explicit size eliminates the margin.
  544. // We have to swizzle the width to get correct margin reading.
  545. if(isButtonTag(node)){
  546. var ns = node.style;
  547. if(w >= 0 && !ns.width){
  548. ns.width = "4px";
  549. }
  550. if(h >= 0 && !ns.height){
  551. ns.height = "4px";
  552. }
  553. }
  554. }
  555. if(w >= 0){
  556. w = Math.max(w - pb.w - mb.w, 0);
  557. }
  558. if(h >= 0){
  559. h = Math.max(h - pb.h - mb.h, 0);
  560. }
  561. setBox(node, box.l, box.t, w, h);
  562. };
  563. // =============================
  564. // Positioning
  565. // =============================
  566. geom.isBodyLtr = function isBodyLtr(){
  567. return (win.body().dir || win.doc.documentElement.dir || "ltr").toLowerCase() == "ltr"; // Boolean
  568. };
  569. geom.docScroll = function docScroll(){
  570. var node = win.doc.parentWindow || win.doc.defaultView; // use UI window, not dojo.global window
  571. return "pageXOffset" in node ? {x: node.pageXOffset, y: node.pageYOffset } :
  572. (node = has("quirks") ? win.body() : win.doc.documentElement,
  573. {x: geom.fixIeBiDiScrollLeft(node.scrollLeft || 0), y: node.scrollTop || 0 });
  574. };
  575. geom.getIeDocumentElementOffset = function getIeDocumentElementOffset(){
  576. //NOTE: assumes we're being called in an IE browser
  577. var de = win.doc.documentElement; // only deal with HTML element here, position() handles body/quirks
  578. if(has("ie") < 8){
  579. var r = de.getBoundingClientRect(), // works well for IE6+
  580. l = r.left, t = r.top;
  581. if(has("ie") < 7){
  582. l += de.clientLeft; // scrollbar size in strict/RTL, or,
  583. t += de.clientTop; // HTML border size in strict
  584. }
  585. return {
  586. x: l < 0 ? 0 : l, // FRAME element border size can lead to inaccurate negative values
  587. y: t < 0 ? 0 : t
  588. };
  589. }else{
  590. return {
  591. x: 0,
  592. y: 0
  593. };
  594. }
  595. };
  596. geom.fixIeBiDiScrollLeft = function fixIeBiDiScrollLeft(/*Integer*/ scrollLeft){
  597. // In RTL direction, scrollLeft should be a negative value, but IE
  598. // returns a positive one. All codes using documentElement.scrollLeft
  599. // must call this function to fix this error, otherwise the position
  600. // will offset to right when there is a horizontal scrollbar.
  601. var ie = has("ie");
  602. if(ie && !geom.isBodyLtr()){
  603. var qk = has("quirks"),
  604. de = qk ? win.body() : win.doc.documentElement;
  605. if(ie == 6 && !qk && win.global.frameElement && de.scrollHeight > de.clientHeight){
  606. scrollLeft += de.clientLeft; // workaround ie6+strict+rtl+iframe+vertical-scrollbar bug where clientWidth is too small by clientLeft pixels
  607. }
  608. return (ie < 8 || qk) ? (scrollLeft + de.clientWidth - de.scrollWidth) : -scrollLeft; // Integer
  609. }
  610. return scrollLeft; // Integer
  611. };
  612. geom.position = function(/*DomNode*/node, /*Boolean?*/includeScroll){
  613. node = dom.byId(node);
  614. var db = win.body(),
  615. dh = db.parentNode,
  616. ret = node.getBoundingClientRect();
  617. ret = {x: ret.left, y: ret.top, w: ret.right - ret.left, h: ret.bottom - ret.top};
  618. if(has("ie") < 9){
  619. // On IE<9 there's a 2px offset that we need to adjust for, see dojo.getIeDocumentElementOffset()
  620. var offset = geom.getIeDocumentElementOffset();
  621. // fixes the position in IE, quirks mode
  622. ret.x -= offset.x + (has("quirks") ? db.clientLeft + db.offsetLeft : 0);
  623. ret.y -= offset.y + (has("quirks") ? db.clientTop + db.offsetTop : 0);
  624. }else if(has("ff") == 3){
  625. // In FF3 you have to subtract the document element margins.
  626. // Fixed in FF3.5 though.
  627. var cs = style.getComputedStyle(dh), px = style.toPixelValue;
  628. ret.x -= px(dh, cs.marginLeft) + px(dh, cs.borderLeftWidth);
  629. ret.y -= px(dh, cs.marginTop) + px(dh, cs.borderTopWidth);
  630. }
  631. // account for document scrolling
  632. // if offsetParent is used, ret value already includes scroll position
  633. // so we may have to actually remove that value if !includeScroll
  634. if(includeScroll){
  635. var scroll = geom.docScroll();
  636. ret.x += scroll.x;
  637. ret.y += scroll.y;
  638. }
  639. return ret; // Object
  640. };
  641. // random "private" functions wildly used throughout the toolkit
  642. geom.getMarginSize = function getMarginSize(/*DomNode*/node, /*Object*/computedStyle){
  643. node = dom.byId(node);
  644. var me = geom.getMarginExtents(node, computedStyle || style.getComputedStyle(node));
  645. var size = node.getBoundingClientRect();
  646. return {
  647. w: (size.right - size.left) + me.w,
  648. h: (size.bottom - size.top) + me.h
  649. }
  650. };
  651. geom.normalizeEvent = function(event){
  652. // summary:
  653. // Normalizes the geometry of a DOM event, normalizing the pageX, pageY,
  654. // offsetX, offsetY, layerX, and layerX properties
  655. // event: Object
  656. if(!("layerX" in event)){
  657. event.layerX = event.offsetX;
  658. event.layerY = event.offsetY;
  659. }
  660. if(!has("dom-addeventlistener")){
  661. // old IE version
  662. // FIXME: scroll position query is duped from dojo.html to
  663. // avoid dependency on that entire module. Now that HTML is in
  664. // Base, we should convert back to something similar there.
  665. var se = event.target;
  666. var doc = (se && se.ownerDocument) || document;
  667. // DO NOT replace the following to use dojo.body(), in IE, document.documentElement should be used
  668. // here rather than document.body
  669. var docBody = has("quirks") ? doc.body : doc.documentElement;
  670. var offset = geom.getIeDocumentElementOffset();
  671. event.pageX = event.clientX + geom.fixIeBiDiScrollLeft(docBody.scrollLeft || 0) - offset.x;
  672. event.pageY = event.clientY + (docBody.scrollTop || 0) - offset.y;
  673. }
  674. };
  675. // TODO: evaluate separate getters/setters for position and sizes?
  676. return geom;
  677. });