You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
407 lines
16 KiB
407 lines
16 KiB
define([ |
|
"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 |
|
"./Viewport", // getEffectiveBox |
|
"./main" // dijit (defining dijit.place to match API doc) |
|
], function(array, domGeometry, domStyle, kernel, win, Viewport, 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 = Viewport.getEffectiveBox(node.ownerDocument); |
|
|
|
// This won't work if the node is inside a <div style="position: relative">, |
|
// so reattach it to <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). |
|
|
|
var top = best.y, |
|
side = best.x, |
|
body = win.body(node.ownerDocument); |
|
|
|
if(/relative|absolute/.test(domStyle.get(body, "position"))){ |
|
// compensate for margin on <body>, see #16148 |
|
top -= domStyle.get(body, "marginTop"); |
|
side -= domStyle.get(body, "marginLeft"); |
|
} |
|
|
|
var s = node.style; |
|
s.top = top + "px"; |
|
s.left = side + "px"; |
|
s.right = "auto"; // needed for FF or else tooltip goes to far left |
|
|
|
return best; |
|
} |
|
|
|
var reverse = { |
|
// Map from corner to kitty-corner |
|
"TL": "BR", |
|
"TR": "BL", |
|
"BL": "TR", |
|
"BR": "TL" |
|
}; |
|
|
|
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, layoutNode){ |
|
// summary: |
|
// Positions node kitty-corner to the rectangle centered at (pos.x, pos.y) with width and height of |
|
// padding.x * 2 and padding.y * 2, or zero if padding not specified. Picks first corner in corners[] |
|
// where node is fully visible, or the corner where it's most visible. |
|
// |
|
// 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 of the node 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. |
|
// Defaults to zero. |
|
// 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: |
|
// 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, |
|
aroundCorner: reverse[corner], // so TooltipDialog.orient() gets aroundCorner argument set |
|
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, layoutNode); |
|
}, |
|
|
|
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, ['below', 'above-alt']); |
|
// 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; |
|
if(typeof anchor == "string" || "offsetWidth" in anchor || "ownerSVGElement" in anchor){ |
|
aroundNodePos = domGeometry.position(anchor, true); |
|
|
|
// For above and below dropdowns, subtract width of border so that popup and aroundNode borders |
|
// overlap, preventing a double-border effect. Unfortunately, difficult to measure the border |
|
// width of either anchor or popup because in both cases the border may be on an inner node. |
|
if(/^(above|below)/.test(positions[0])){ |
|
var anchorBorder = domGeometry.getBorderExtents(anchor), |
|
anchorChildBorder = anchor.firstChild ? domGeometry.getBorderExtents(anchor.firstChild) : {t:0,l:0,b:0,r:0}, |
|
nodeBorder = domGeometry.getBorderExtents(node), |
|
nodeChildBorder = node.firstChild ? domGeometry.getBorderExtents(node.firstChild) : {t:0,l:0,b:0,r:0}; |
|
aroundNodePos.y += Math.min(anchorBorder.t + anchorChildBorder.t, nodeBorder.t + nodeChildBorder.t); |
|
aroundNodePos.h -= Math.min(anchorBorder.t + anchorChildBorder.t, nodeBorder.t+ nodeChildBorder.t) + |
|
Math.min(anchorBorder.b + anchorChildBorder.b, nodeBorder.b + nodeChildBorder.b); |
|
} |
|
}else{ |
|
aroundNodePos = 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. Remove for 2.0. |
|
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 |
|
});
|
|
|