377 lines
12 KiB
JavaScript
377 lines
12 KiB
JavaScript
/*
|
|
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 <div style="position: relative">,
|
|
// 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;
|
|
};
|
|
|
|
}
|