UNPKG

dojo

Version:

Dojo core is a powerful, lightweight library that makes common tasks quicker and easier. Animate elements, manipulate the DOM, and query with easy CSS syntax, all without sacrificing performance.

552 lines (494 loc) 21.9 kB
define(["./sniff", "./_base/window","./dom", "./dom-style"], function(has, win, dom, style){ // module: // dojo/dom-geometry // the result object var geom = { // summary: // This module defines the core dojo DOM geometry API. }; // Box functions will assume this model. // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode. // Can be set to change behavior of box setters. // can be either: // "border-box" // "content-box" (default) geom.boxModel = "content-box"; // We punt per-node box mode testing completely. // If anybody cares, we can provide an additional (optional) unit // that overrides existing code to include per-node box sensitivity. // Opera documentation claims that Opera 9 uses border-box in BackCompat mode. // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box. // IIRC, earlier versions of Opera did in fact use border-box. // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault. if(has("ie") /*|| has("opera")*/){ // client code may have to adjust if compatMode varies across iframes geom.boxModel = document.compatMode == "BackCompat" ? "border-box" : "content-box"; } geom.getPadExtents = function getPadExtents(/*DomNode*/ node, /*Object*/ computedStyle){ // summary: // Returns object with special values specifically useful for node // fitting. // description: // Returns an object with `w`, `h`, `l`, `t` properties: // | l/t/r/b = left/top/right/bottom padding (respectively) // | w = the total of the left and right padding // | h = the total of the top and bottom padding // If 'node' has position, l/t forms the origin for child nodes. // The w/h are used for calculating boxes. // Normally application code will not need to invoke this // directly, and will use the ...box... functions instead. // node: DOMNode // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var s = computedStyle || style.getComputedStyle(node), px = style.toPixelValue, l = px(node, s.paddingLeft), t = px(node, s.paddingTop), r = px(node, s.paddingRight), b = px(node, s.paddingBottom); return {l: l, t: t, r: r, b: b, w: l + r, h: t + b}; }; var none = "none"; geom.getBorderExtents = function getBorderExtents(/*DomNode*/ node, /*Object*/ computedStyle){ // summary: // returns an object with properties useful for noting the border // dimensions. // description: // - l/t/r/b = the sum of left/top/right/bottom border (respectively) // - w = the sum of the left and right border // - h = the sum of the top and bottom border // // The w/h are used for calculating boxes. // Normally application code will not need to invoke this // directly, and will use the ...box... functions instead. // node: DOMNode // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var px = style.toPixelValue, s = computedStyle || style.getComputedStyle(node), l = s.borderLeftStyle != none ? px(node, s.borderLeftWidth) : 0, t = s.borderTopStyle != none ? px(node, s.borderTopWidth) : 0, r = s.borderRightStyle != none ? px(node, s.borderRightWidth) : 0, b = s.borderBottomStyle != none ? px(node, s.borderBottomWidth) : 0; return {l: l, t: t, r: r, b: b, w: l + r, h: t + b}; }; geom.getPadBorderExtents = function getPadBorderExtents(/*DomNode*/ node, /*Object*/ computedStyle){ // summary: // Returns object with properties useful for box fitting with // regards to padding. // description: // - l/t/r/b = the sum of left/top/right/bottom padding and left/top/right/bottom border (respectively) // - w = the sum of the left and right padding and border // - h = the sum of the top and bottom padding and border // // The w/h are used for calculating boxes. // Normally application code will not need to invoke this // directly, and will use the ...box... functions instead. // node: DOMNode // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var s = computedStyle || style.getComputedStyle(node), p = geom.getPadExtents(node, s), b = geom.getBorderExtents(node, s); return { l: p.l + b.l, t: p.t + b.t, r: p.r + b.r, b: p.b + b.b, w: p.w + b.w, h: p.h + b.h }; }; geom.getMarginExtents = function getMarginExtents(node, computedStyle){ // summary: // returns object with properties useful for box fitting with // regards to box margins (i.e., the outer-box). // // - l/t = marginLeft, marginTop, respectively // - w = total width, margin inclusive // - h = total height, margin inclusive // // The w/h are used for calculating boxes. // Normally application code will not need to invoke this // directly, and will use the ...box... functions instead. // node: DOMNode // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var s = computedStyle || style.getComputedStyle(node), px = style.toPixelValue, l = px(node, s.marginLeft), t = px(node, s.marginTop), r = px(node, s.marginRight), b = px(node, s.marginBottom); return {l: l, t: t, r: r, b: b, w: l + r, h: t + b}; }; // Box getters work in any box context because offsetWidth/clientWidth // are invariant wrt box context // // They do *not* work for display: inline objects that have padding styles // because the user agent ignores padding (it's bogus styling in any case) // // Be careful with IMGs because they are inline or block depending on // browser and browser mode. // Although it would be easier to read, there are not separate versions of // _getMarginBox for each browser because: // 1. the branching is not expensive // 2. factoring the shared code wastes cycles (function call overhead) // 3. duplicating the shared code wastes bytes geom.getMarginBox = function getMarginBox(/*DomNode*/ node, /*Object*/ computedStyle){ // summary: // returns an object that encodes the width, height, left and top // positions of the node's margin box. // node: DOMNode // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var s = computedStyle || style.getComputedStyle(node), me = geom.getMarginExtents(node, s), l = node.offsetLeft - me.l, t = node.offsetTop - me.t, p = node.parentNode, px = style.toPixelValue, pcs; if((has("ie") == 8 && !has("quirks"))){ // IE 8 offsetLeft/Top includes the parent's border if(p){ pcs = style.getComputedStyle(p); l -= pcs.borderLeftStyle != none ? px(node, pcs.borderLeftWidth) : 0; t -= pcs.borderTopStyle != none ? px(node, pcs.borderTopWidth) : 0; } } return {l: l, t: t, w: node.offsetWidth + me.w, h: node.offsetHeight + me.h}; }; geom.getContentBox = function getContentBox(node, computedStyle){ // summary: // Returns an object that encodes the width, height, left and top // positions of the node's content box, irrespective of the // current box model. // node: DOMNode // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). // clientWidth/Height are important since the automatically account for scrollbars // fallback to offsetWidth/Height for special cases (see #3378) node = dom.byId(node); var s = computedStyle || style.getComputedStyle(node), w = node.clientWidth, h, pe = geom.getPadExtents(node, s), be = geom.getBorderExtents(node, s), l = node.offsetLeft + pe.l + be.l, t = node.offsetTop + pe.t + be.t; if(!w){ w = node.offsetWidth - be.w; h = node.offsetHeight - be.h; }else{ h = node.clientHeight; } if((has("ie") == 8 && !has("quirks"))){ // IE 8 offsetLeft/Top includes the parent's border var p = node.parentNode, px = style.toPixelValue, pcs; if(p){ pcs = style.getComputedStyle(p); l -= pcs.borderLeftStyle != none ? px(node, pcs.borderLeftWidth) : 0; t -= pcs.borderTopStyle != none ? px(node, pcs.borderTopWidth) : 0; } } return {l: l, t: t, w: w - pe.w, h: h - pe.h}; }; // Box setters depend on box context because interpretation of width/height styles // vary wrt box context. // // The value of boxModel is used to determine box context. // boxModel can be set directly to change behavior. // // Beware of display: inline objects that have padding styles // because the user agent ignores padding (it's a bogus setup anyway) // // Be careful with IMGs because they are inline or block depending on // browser and browser mode. // // Elements other than DIV may have special quirks, like built-in // margins or padding, or values not detectable via computedStyle. // In particular, margins on TABLE do not seems to appear // at all in computedStyle on Mozilla. function setBox(/*DomNode*/ node, /*Number?*/ l, /*Number?*/ t, /*Number?*/ w, /*Number?*/ h, /*String?*/ u){ // summary: // sets width/height/left/top in the current (native) box-model // dimensions. Uses the unit passed in u. // node: // DOM Node reference. Id string not supported for performance // reasons. // l: // left offset from parent. // t: // top offset from parent. // w: // width in current box model. // h: // width in current box model. // u: // unit measure to use for other measures. Defaults to "px". u = u || "px"; var s = node.style; if(!isNaN(l)){ s.left = l + u; } if(!isNaN(t)){ s.top = t + u; } if(w >= 0){ s.width = w + u; } if(h >= 0){ s.height = h + u; } } function isButtonTag(/*DomNode*/ node){ // summary: // True if the node is BUTTON or INPUT.type="button". return node.tagName.toLowerCase() == "button" || node.tagName.toLowerCase() == "input" && (node.getAttribute("type") || "").toLowerCase() == "button"; // boolean } function usesBorderBox(/*DomNode*/ node){ // summary: // True if the node uses border-box layout. // We could test the computed style of node to see if a particular box // has been specified, but there are details and we choose not to bother. // TABLE and BUTTON (and INPUT type=button) are always border-box by default. // If you have assigned a different box to either one via CSS then // box functions will break. return geom.boxModel == "border-box" || node.tagName.toLowerCase() == "table" || isButtonTag(node); // boolean } geom.setContentSize = function setContentSize(/*DomNode*/ node, /*Object*/ box, /*Object*/ computedStyle){ // summary: // Sets the size of the node's contents, irrespective of margins, // padding, or borders. // node: DOMNode // box: Object // hash with optional "w", and "h" properties for "width", and "height" // respectively. All specified properties should have numeric values in whole pixels. // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var w = box.w, h = box.h; if(usesBorderBox(node)){ var pb = geom.getPadBorderExtents(node, computedStyle); if(w >= 0){ w += pb.w; } if(h >= 0){ h += pb.h; } } setBox(node, NaN, NaN, w, h); }; var nilExtents = {l: 0, t: 0, w: 0, h: 0}; geom.setMarginBox = function setMarginBox(/*DomNode*/ node, /*Object*/ box, /*Object*/ computedStyle){ // summary: // sets the size of the node's margin box and placement // (left/top), irrespective of box model. Think of it as a // passthrough to setBox that handles box-model vagaries for // you. // node: DOMNode // box: Object // hash with optional "l", "t", "w", and "h" properties for "left", "right", "width", and "height" // respectively. All specified properties should have numeric values in whole pixels. // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var s = computedStyle || style.getComputedStyle(node), w = box.w, h = box.h, // Some elements have special padding, margin, and box-model settings. // To use box functions you may need to set padding, margin explicitly. // Controlling box-model is harder, in a pinch you might set dojo/dom-geometry.boxModel. pb = usesBorderBox(node) ? nilExtents : geom.getPadBorderExtents(node, s), mb = geom.getMarginExtents(node, s); if(has("webkit")){ // on Safari (3.1.2), button nodes with no explicit size have a default margin // setting an explicit size eliminates the margin. // We have to swizzle the width to get correct margin reading. if(isButtonTag(node)){ var ns = node.style; if(w >= 0 && !ns.width){ ns.width = "4px"; } if(h >= 0 && !ns.height){ ns.height = "4px"; } } } if(w >= 0){ w = Math.max(w - pb.w - mb.w, 0); } if(h >= 0){ h = Math.max(h - pb.h - mb.h, 0); } setBox(node, box.l, box.t, w, h); }; // ============================= // Positioning // ============================= geom.isBodyLtr = function isBodyLtr(/*Document?*/ doc){ // summary: // Returns true if the current language is left-to-right, and false otherwise. // doc: Document? // Optional document to query. If unspecified, use win.doc. // returns: Boolean doc = doc || win.doc; return (win.body(doc).dir || doc.documentElement.dir || "ltr").toLowerCase() == "ltr"; // Boolean }; geom.docScroll = function docScroll(/*Document?*/ doc){ // summary: // Returns an object with {node, x, y} with corresponding offsets. // doc: Document? // Optional document to query. If unspecified, use win.doc. // returns: Object doc = doc || win.doc; 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 return "pageXOffset" in node ? {x: node.pageXOffset, y: node.pageYOffset } : (node = has("quirks") ? win.body(doc) : doc.documentElement) && {x: geom.fixIeBiDiScrollLeft(node.scrollLeft || 0, doc), y: node.scrollTop || 0 }; }; geom.getIeDocumentElementOffset = function(/*Document?*/ doc){ // summary: // Deprecated method previously used for IE6-IE7. Now, just returns `{x:0, y:0}`. return { x: 0, y: 0 }; }; geom.fixIeBiDiScrollLeft = function fixIeBiDiScrollLeft(/*Integer*/ scrollLeft, /*Document?*/ doc){ // summary: // In RTL direction, scrollLeft should be a negative value, but IE // returns a positive one. All codes using documentElement.scrollLeft // must call this function to fix this error, otherwise the position // will offset to right when there is a horizontal scrollbar. // scrollLeft: Number // doc: Document? // Optional document to query. If unspecified, use win.doc. // returns: Number // In RTL direction, scrollLeft should be a negative value, but IE // returns a positive one. All codes using documentElement.scrollLeft // must call this function to fix this error, otherwise the position // will offset to right when there is a horizontal scrollbar. doc = doc || win.doc; var ie = has("ie"); if(ie && !geom.isBodyLtr(doc)){ var qk = has("quirks"), de = qk ? win.body(doc) : doc.documentElement, pwin = win.global; // TODO: use winUtils.get(doc) after resolving circular dependency b/w dom-geometry.js and dojo/window.js if(ie == 6 && !qk && pwin.frameElement && de.scrollHeight > de.clientHeight){ scrollLeft += de.clientLeft; // workaround ie6+strict+rtl+iframe+vertical-scrollbar bug where clientWidth is too small by clientLeft pixels } return (ie < 8 || qk) ? (scrollLeft + de.clientWidth - de.scrollWidth) : -scrollLeft; // Integer } return scrollLeft; // Integer }; geom.position = function(/*DomNode*/ node, /*Boolean?*/ includeScroll){ // summary: // Gets the position and size of the passed element relative to // the viewport (if includeScroll==false), or relative to the // document root (if includeScroll==true). // // description: // Returns an object of the form: // `{ x: 100, y: 300, w: 20, h: 15 }`. // If includeScroll==true, the x and y values will include any // document offsets that may affect the position relative to the // viewport. // Uses the border-box model (inclusive of border and padding but // not margin). Does not act as a setter. // node: DOMNode|String // includeScroll: Boolean? // returns: Object node = dom.byId(node); var db = win.body(node.ownerDocument), ret = node.getBoundingClientRect(); ret = {x: ret.left, y: ret.top, w: ret.right - ret.left, h: ret.bottom - ret.top}; if(has("ie") < 9){ // fixes the position in IE, quirks mode ret.x -= (has("quirks") ? db.clientLeft + db.offsetLeft : 0); ret.y -= (has("quirks") ? db.clientTop + db.offsetTop : 0); } // account for document scrolling // if offsetParent is used, ret value already includes scroll position // so we may have to actually remove that value if !includeScroll if(includeScroll){ var scroll = geom.docScroll(node.ownerDocument); ret.x += scroll.x; ret.y += scroll.y; } return ret; // Object }; // random "private" functions wildly used throughout the toolkit geom.getMarginSize = function getMarginSize(/*DomNode*/ node, /*Object*/ computedStyle){ // summary: // returns an object that encodes the width and height of // the node's margin box // node: DOMNode|String // computedStyle: Object? // This parameter accepts computed styles object. // If this parameter is omitted, the functions will call // dojo/dom-style.getComputedStyle to get one. It is a better way, calling // dojo/dom-style.getComputedStyle once, and then pass the reference to this // computedStyle parameter. Wherever possible, reuse the returned // object of dojo/dom-style.getComputedStyle(). node = dom.byId(node); var me = geom.getMarginExtents(node, computedStyle || style.getComputedStyle(node)); var size = node.getBoundingClientRect(); return { w: (size.right - size.left) + me.w, h: (size.bottom - size.top) + me.h }; }; geom.normalizeEvent = function(event){ // summary: // Normalizes the geometry of a DOM event, normalizing the pageX, pageY, // offsetX, offsetY, layerX, and layerX properties // event: Object if(!("layerX" in event)){ event.layerX = event.offsetX; event.layerY = event.offsetY; } if(!("pageX" in event)){ // FIXME: scroll position query is duped from dojo/_base/html to // avoid dependency on that entire module. Now that HTML is in // Base, we should convert back to something similar there. var se = event.target; var doc = (se && se.ownerDocument) || document; // DO NOT replace the following to use dojo/_base/window.body(), in IE, document.documentElement should be used // here rather than document.body var docBody = has("quirks") ? doc.body : doc.documentElement; event.pageX = event.clientX + geom.fixIeBiDiScrollLeft(docBody.scrollLeft || 0, doc); event.pageY = event.clientY + (docBody.scrollTop || 0); } }; // TODO: evaluate separate getters/setters for position and sizes? return geom; });