/* Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit._base.place"] = true; dojo.provide("dijit._base.place"); dojo.require("dojo.window"); dojo.require("dojo.AdapterRegistry"); dijit.getViewport = function(){ // summary: // Returns the dimensions and scroll position of the viewable area of a browser window return dojo.window.getBox(); }; /*===== dijit.__Position = function(){ // x: Integer // horizontal coordinate in pixels, relative to document body // y: Integer // vertical coordinate in pixels, relative to document body thix.x = x; this.y = y; } =====*/ dijit.placeOnScreen = function( /* DomNode */ node, /* dijit.__Position */ pos, /* String[] */ corners, /* dijit.__Position? */ 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. // pos: // Object like {x: 10, y: 20} // corners: // 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: // 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). // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"]) var choices = dojo.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 dijit._place(node, choices); } dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ 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 = dojo.window.getBox(); // This won't work if the node is inside a
, // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong // and also it might get cutoff) if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){ dojo.body().appendChild(node); } var best = null; dojo.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: corner.charAt(1) == 'L' ? (view.l + view.w) - pos.x : pos.x - view.l, h: corner.charAt(1) == 'T' ? (view.t + view.h) - pos.y : pos.y - view.t }; // 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; style.visibility = "hidden"; style.display = ""; var mb = dojo.marginBox(node); style.display = oldDisplay; style.visibility = oldVis; // coordinates and size of node with specified corner placed at pos, // and clipped by viewport var startX = Math.max(view.l, corner.charAt(1) == 'L' ? pos.x : (pos.x - mb.w)), startY = Math.max(view.t, corner.charAt(0) == 'T' ? pos.y : (pos.y - mb.h)), endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x), endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y), width = endX - startX, height = endY - startY; overflow += (mb.w - width) + (mb.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 = dojo._isBodyLtr(), s = node.style; s.top = best.y + "px"; s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px"; return best; } dijit.placeOnScreenAroundNode = function( /* DomNode */ node, /* DomNode */ aroundNode, /* Object */ aroundCorners, /* Function? */ layoutNode){ // summary: // Position node adjacent or kitty-corner to aroundNode // 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. // // aroundCorners: // Ordered list of pairs of corners to try matching up. // Each pair of corners is represented as a key/value in the hash, // where the key corresponds to the aroundNode's corner, and // the value corresponds to the node's corner: // // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...} // // The following strings are used to represent the four corners: // * "BL" - bottom left // * "BR" - bottom right // * "TL" - top left // * "TR" - top right // // 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. // // example: // | dijit.placeOnScreenAroundNode(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) // // get coordinates of aroundNode aroundNode = dojo.byId(aroundNode); var aroundNodePos = dojo.position(aroundNode, true); // place the node around the calculated rectangle return dijit._placeOnScreenAroundRect(node, aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle aroundCorners, layoutNode); }; /*===== dijit.__Rectangle = function(){ // x: Integer // horizontal offset in pixels, relative to document body // y: Integer // vertical offset in pixels, relative to document body // width: Integer // width in pixels // height: Integer // height in pixels this.x = x; this.y = y; this.width = width; this.height = height; } =====*/ dijit.placeOnScreenAroundRectangle = function( /* DomNode */ node, /* dijit.__Rectangle */ aroundRect, /* Object */ aroundCorners, /* Function */ layoutNode){ // summary: // Like dijit.placeOnScreenAroundNode(), except that the "around" // parameter is an arbitrary rectangle on the screen (x, y, width, height) // instead of a dom node. return dijit._placeOnScreenAroundRect(node, aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle aroundCorners, layoutNode); }; dijit._placeOnScreenAroundRect = function( /* DomNode */ node, /* Number */ x, /* Number */ y, /* Number */ width, /* Number */ height, /* Object */ aroundCorners, /* Function */ layoutNode){ // summary: // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates // of a rectangle to place node adjacent to. // TODO: combine with placeOnScreenAroundRectangle() // Generate list of possible positions for node var choices = []; for(var nodeCorner in aroundCorners){ choices.push( { aroundCorner: nodeCorner, corner: aroundCorners[nodeCorner], pos: { x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width), y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height) } }); } return dijit._place(node, choices, layoutNode, {w: width, h: height}); }; dijit.placementRegistry= new dojo.AdapterRegistry(); dijit.placementRegistry.register("node", function(n, x){ return typeof x == "object" && typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined"; }, dijit.placeOnScreenAroundNode); dijit.placementRegistry.register("rect", function(n, x){ return typeof x == "object" && "x" in x && "y" in x && "width" in x && "height" in x; }, dijit.placeOnScreenAroundRectangle); dijit.placeOnScreenAroundElement = function( /* DomNode */ node, /* Object */ aroundElement, /* Object */ aroundCorners, /* Function */ layoutNode){ // summary: // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object // for the "around" argument and finds a proper processor to place a node. return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments); }; dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){ // summary: // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement. // // position: String[] // This variable controls the position of the drop down. // It's an array of strings with the following values: // // * before: places drop down to the left of the target node/widget, or to the right in // the case of RTL scripts like Hebrew and Arabic // * after: places drop down to the right of the target node/widget, or to the left in // the case of RTL scripts like Hebrew and Arabic // * above: drop down goes above target node // * below: drop down goes below target node // // The list is positions is tried, in order, until a position is found where the drop down fits // within the viewport. // // leftToRight: Boolean // Whether the popup will be displaying in leftToRight mode. // var align = {}; dojo.forEach(position, function(pos){ switch(pos){ case "after": align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR"; break; case "before": align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL"; break; case "below-alt": leftToRight = !leftToRight; // fall through case "below": // first try to align left borders, next try to align right borders (or reverse for RTL mode) align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR"; align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL"; break; case "above-alt": leftToRight = !leftToRight; // fall through case "above": default: // first try to align left borders, next try to align right borders (or reverse for RTL mode) align[leftToRight ? "TL" : "TR"] = leftToRight ? "BL" : "BR"; align[leftToRight ? "TR" : "TL"] = leftToRight ? "BR" : "BL"; break; } }); return align; }; }