define("dijit/place", [ "dojo/_base/array", // array.forEach array.map array.some "dojo/dom-geometry", // domGeometry.position "dojo/dom-style", // domStyle.getComputedStyle "dojo/_base/kernel", // kernel.deprecated "dojo/_base/window", // win.body "dojo/window", // winUtils.getBox "./main" // dijit (defining dijit.place to match API doc) ], function(array, domGeometry, domStyle, kernel, win, winUtils, dijit){ // module: // dijit/place function _place(/*DomNode*/ node, choices, layoutNode, aroundNodeCoords){ // summary: // Given a list of spots to put node, put it at the first spot where it fits, // of if it doesn't fit anywhere then the place with the least overflow // choices: Array // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} } // Above example says to put the top-left corner of the node at (10,20) // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size) // for things like tooltip, they are displayed differently (and have different dimensions) // based on their orientation relative to the parent. This adjusts the popup based on orientation. // It also passes in the available size for the popup, which is useful for tooltips to // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing // how much the popup had to be modified to fit into the available space. This is used to determine // what the best placement is. // aroundNodeCoords: Object // Size of aroundNode, ex: {w: 200, h: 50} // get {x: 10, y: 10, w: 100, h:100} type obj representing position of // viewport over document var view = winUtils.getBox(node.ownerDocument); // This won't work if the node is inside a
, // so reattach it to win.doc.body. (Otherwise, the positioning will be wrong // and also it might get cutoff) if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ win.body(node.ownerDocument).appendChild(node); } var best = null; array.some(choices, function(choice){ var corner = choice.corner; var pos = choice.pos; var overflow = 0; // calculate amount of space available given specified position of node var spaceAvailable = { w: { 'L': view.l + view.w - pos.x, 'R': pos.x - view.l, 'M': view.w }[corner.charAt(1)], h: { 'T': view.t + view.h - pos.y, 'B': pos.y - view.t, 'M': view.h }[corner.charAt(0)] }; // Clear left/right position settings set earlier so they don't interfere with calculations, // specifically when layoutNode() (a.k.a. Tooltip.orient()) measures natural width of Tooltip var s = node.style; s.left = s.right = "auto"; // configure node to be displayed in given position relative to button // (need to do this in order to get an accurate size for the node, because // a tooltip's size changes based on position, due to triangle) if(layoutNode){ var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords); overflow = typeof res == "undefined" ? 0 : res; } // get node's size var style = node.style; var oldDisplay = style.display; var oldVis = style.visibility; if(style.display == "none"){ style.visibility = "hidden"; style.display = ""; } var bb = domGeometry.position(node); style.display = oldDisplay; style.visibility = oldVis; // coordinates and size of node with specified corner placed at pos, // and clipped by viewport var startXpos = { 'L': pos.x, 'R': pos.x - bb.w, 'M': Math.max(view.l, Math.min(view.l + view.w, pos.x + (bb.w >> 1)) - bb.w) // M orientation is more flexible }[corner.charAt(1)], startYpos = { 'T': pos.y, 'B': pos.y - bb.h, 'M': Math.max(view.t, Math.min(view.t + view.h, pos.y + (bb.h >> 1)) - bb.h) }[corner.charAt(0)], startX = Math.max(view.l, startXpos), startY = Math.max(view.t, startYpos), endX = Math.min(view.l + view.w, startXpos + bb.w), endY = Math.min(view.t + view.h, startYpos + bb.h), width = endX - startX, height = endY - startY; overflow += (bb.w - width) + (bb.h - height); if(best == null || overflow < best.overflow){ best = { corner: corner, aroundCorner: choice.aroundCorner, x: startX, y: startY, w: width, h: height, overflow: overflow, spaceAvailable: spaceAvailable }; } return !overflow; }); // In case the best position is not the last one we checked, need to call // layoutNode() again. if(best.overflow && layoutNode){ layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords); } // And then position the node. Do this last, after the layoutNode() above // has sized the node, due to browser quirks when the viewport is scrolled // (specifically that a Tooltip will shrink to fit as though the window was // scrolled to the left). // // In RTL mode, set style.right rather than style.left so in the common case, // window resizes move the popup along with the aroundNode. var l = domGeometry.isBodyLtr(node.ownerDocument), s = node.style; s.top = best.y + "px"; s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px"; s[l ? "right" : "left"] = "auto"; // needed for FF or else tooltip goes to far left return best; } var place = { // summary: // Code to place a DOMNode relative to another DOMNode. // Load using require(["dijit/place"], function(place){ ... }). at: function(node, pos, corners, padding){ // summary: // Positions one of the node's corners at specified position // such that node is fully visible in viewport. // description: // NOTE: node is assumed to be absolutely or relatively positioned. // node: DOMNode // The node to position // pos: dijit/place.__Position // Object like {x: 10, y: 20} // corners: String[] // Array of Strings representing order to try corners in, like ["TR", "BL"]. // Possible values are: // // - "BL" - bottom left // - "BR" - bottom right // - "TL" - top left // - "TR" - top right // padding: dijit/place.__Position? // optional param to set padding, to put some buffer around the element you want to position. // example: // Try to place node's top right corner at (10,20). // If that makes node go (partially) off screen, then try placing // bottom left corner at (10,20). // | place(node, {x: 10, y: 20}, ["TR", "BL"]) var choices = array.map(corners, function(corner){ var c = { corner: corner, pos: {x:pos.x,y:pos.y} }; if(padding){ c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x; c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y; } return c; }); return _place(node, choices); }, around: function( /*DomNode*/ node, /*DomNode|dijit/place.__Rectangle*/ anchor, /*String[]*/ positions, /*Boolean*/ leftToRight, /*Function?*/ layoutNode){ // summary: // Position node adjacent or kitty-corner to anchor // such that it's fully visible in viewport. // description: // Place node such that corner of node touches a corner of // aroundNode, and that node is fully visible. // anchor: // Either a DOMNode or a rectangle (object with x, y, width, height). // positions: // Ordered list of positions to try matching up. // // - before: places drop down to the left of the anchor node/widget, or to the right in the case // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down // with the top of the anchor, or the bottom of the drop down with bottom of the anchor. // - after: places drop down to the right of the anchor node/widget, or to the left in the case // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down // with the top of the anchor, or the bottom of the drop down with bottom of the anchor. // - before-centered: centers drop down to the left of the anchor node/widget, or to the right // in the case of RTL scripts like Hebrew and Arabic // - after-centered: centers drop down to the right of the anchor node/widget, or to the left // in the case of RTL scripts like Hebrew and Arabic // - above-centered: drop down is centered above anchor node // - above: drop down goes above anchor node, left sides aligned // - above-alt: drop down goes above anchor node, right sides aligned // - below-centered: drop down is centered above anchor node // - below: drop down goes below anchor node // - below-alt: drop down goes below anchor node, right sides aligned // layoutNode: Function(node, aroundNodeCorner, nodeCorner) // For things like tooltip, they are displayed differently (and have different dimensions) // based on their orientation relative to the parent. This adjusts the popup based on orientation. // leftToRight: // True if widget is LTR, false if widget is RTL. Affects the behavior of "above" and "below" // positions slightly. // example: // | placeAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'}); // This will try to position node such that node's top-left corner is at the same position // as the bottom left corner of the aroundNode (ie, put node below // aroundNode, with left edges aligned). If that fails it will try to put // the bottom-right corner of node where the top right corner of aroundNode is // (ie, put node above aroundNode, with right edges aligned) // // if around is a DOMNode (or DOMNode id), convert to coordinates var aroundNodePos = (typeof anchor == "string" || "offsetWidth" in anchor) ? domGeometry.position(anchor, true) : anchor; // Compute position and size of visible part of anchor (it may be partially hidden by ancestor nodes w/scrollbars) if(anchor.parentNode){ // ignore nodes between position:relative and position:absolute var sawPosAbsolute = domStyle.getComputedStyle(anchor).position == "absolute"; var parent = anchor.parentNode; while(parent && parent.nodeType == 1 && parent.nodeName != "BODY"){ //ignoring the body will help performance var parentPos = domGeometry.position(parent, true), pcs = domStyle.getComputedStyle(parent); if(/relative|absolute/.test(pcs.position)){ sawPosAbsolute = false; } if(!sawPosAbsolute && /hidden|auto|scroll/.test(pcs.overflow)){ var bottomYCoord = Math.min(aroundNodePos.y + aroundNodePos.h, parentPos.y + parentPos.h); var rightXCoord = Math.min(aroundNodePos.x + aroundNodePos.w, parentPos.x + parentPos.w); aroundNodePos.x = Math.max(aroundNodePos.x, parentPos.x); aroundNodePos.y = Math.max(aroundNodePos.y, parentPos.y); aroundNodePos.h = bottomYCoord - aroundNodePos.y; aroundNodePos.w = rightXCoord - aroundNodePos.x; } if(pcs.position == "absolute"){ sawPosAbsolute = true; } parent = parent.parentNode; } } var x = aroundNodePos.x, y = aroundNodePos.y, width = "w" in aroundNodePos ? aroundNodePos.w : (aroundNodePos.w = aroundNodePos.width), height = "h" in aroundNodePos ? aroundNodePos.h : (kernel.deprecated("place.around: dijit/place.__Rectangle: { x:"+x+", y:"+y+", height:"+aroundNodePos.height+", width:"+width+" } has been deprecated. Please use { x:"+x+", y:"+y+", h:"+aroundNodePos.height+", w:"+width+" }", "", "2.0"), aroundNodePos.h = aroundNodePos.height); // Convert positions arguments into choices argument for _place() var choices = []; function push(aroundCorner, corner){ choices.push({ aroundCorner: aroundCorner, corner: corner, pos: { x: { 'L': x, 'R': x + width, 'M': x + (width >> 1) }[aroundCorner.charAt(1)], y: { 'T': y, 'B': y + height, 'M': y + (height >> 1) }[aroundCorner.charAt(0)] } }) } array.forEach(positions, function(pos){ var ltr = leftToRight; switch(pos){ case "above-centered": push("TM", "BM"); break; case "below-centered": push("BM", "TM"); break; case "after-centered": ltr = !ltr; // fall through case "before-centered": push(ltr ? "ML" : "MR", ltr ? "MR" : "ML"); break; case "after": ltr = !ltr; // fall through case "before": push(ltr ? "TL" : "TR", ltr ? "TR" : "TL"); push(ltr ? "BL" : "BR", ltr ? "BR" : "BL"); break; case "below-alt": ltr = !ltr; // fall through case "below": // first try to align left borders, next try to align right borders (or reverse for RTL mode) push(ltr ? "BL" : "BR", ltr ? "TL" : "TR"); push(ltr ? "BR" : "BL", ltr ? "TR" : "TL"); break; case "above-alt": ltr = !ltr; // fall through case "above": // first try to align left borders, next try to align right borders (or reverse for RTL mode) push(ltr ? "TL" : "TR", ltr ? "BL" : "BR"); push(ltr ? "TR" : "TL", ltr ? "BR" : "BL"); break; default: // To assist dijit/_base/place, accept arguments of type {aroundCorner: "BL", corner: "TL"}. // Not meant to be used directly. push(pos.aroundCorner, pos.corner); } }); var position = _place(node, choices, layoutNode, {w: width, h: height}); position.aroundNodePos = aroundNodePos; return position; } }; /*===== place.__Position = { // x: Integer // horizontal coordinate in pixels, relative to document body // y: Integer // vertical coordinate in pixels, relative to document body }; place.__Rectangle = { // x: Integer // horizontal offset in pixels, relative to document body // y: Integer // vertical offset in pixels, relative to document body // w: Integer // width in pixels. Can also be specified as "width" for backwards-compatibility. // h: Integer // height in pixels. Can also be specified as "height" for backwards-compatibility. }; =====*/ return dijit.place = place; // setting dijit.place for back-compat, remove for 2.0 });