place.js.uncompressed.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. define("dijit/place", [
  2. "dojo/_base/array", // array.forEach array.map array.some
  3. "dojo/dom-geometry", // domGeometry.position
  4. "dojo/dom-style", // domStyle.getComputedStyle
  5. "dojo/_base/kernel", // kernel.deprecated
  6. "dojo/_base/window", // win.body
  7. "dojo/window", // winUtils.getBox
  8. "./main" // dijit (defining dijit.place to match API doc)
  9. ], function(array, domGeometry, domStyle, kernel, win, winUtils, dijit){
  10. // module:
  11. // dijit/place
  12. function _place(/*DomNode*/ node, choices, layoutNode, aroundNodeCoords){
  13. // summary:
  14. // Given a list of spots to put node, put it at the first spot where it fits,
  15. // of if it doesn't fit anywhere then the place with the least overflow
  16. // choices: Array
  17. // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
  18. // Above example says to put the top-left corner of the node at (10,20)
  19. // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
  20. // for things like tooltip, they are displayed differently (and have different dimensions)
  21. // based on their orientation relative to the parent. This adjusts the popup based on orientation.
  22. // It also passes in the available size for the popup, which is useful for tooltips to
  23. // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing
  24. // how much the popup had to be modified to fit into the available space. This is used to determine
  25. // what the best placement is.
  26. // aroundNodeCoords: Object
  27. // Size of aroundNode, ex: {w: 200, h: 50}
  28. // get {x: 10, y: 10, w: 100, h:100} type obj representing position of
  29. // viewport over document
  30. var view = winUtils.getBox(node.ownerDocument);
  31. // This won't work if the node is inside a <div style="position: relative">,
  32. // so reattach it to win.doc.body. (Otherwise, the positioning will be wrong
  33. // and also it might get cutoff)
  34. if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){
  35. win.body(node.ownerDocument).appendChild(node);
  36. }
  37. var best = null;
  38. array.some(choices, function(choice){
  39. var corner = choice.corner;
  40. var pos = choice.pos;
  41. var overflow = 0;
  42. // calculate amount of space available given specified position of node
  43. var spaceAvailable = {
  44. w: {
  45. 'L': view.l + view.w - pos.x,
  46. 'R': pos.x - view.l,
  47. 'M': view.w
  48. }[corner.charAt(1)],
  49. h: {
  50. 'T': view.t + view.h - pos.y,
  51. 'B': pos.y - view.t,
  52. 'M': view.h
  53. }[corner.charAt(0)]
  54. };
  55. // Clear left/right position settings set earlier so they don't interfere with calculations,
  56. // specifically when layoutNode() (a.k.a. Tooltip.orient()) measures natural width of Tooltip
  57. var s = node.style;
  58. s.left = s.right = "auto";
  59. // configure node to be displayed in given position relative to button
  60. // (need to do this in order to get an accurate size for the node, because
  61. // a tooltip's size changes based on position, due to triangle)
  62. if(layoutNode){
  63. var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
  64. overflow = typeof res == "undefined" ? 0 : res;
  65. }
  66. // get node's size
  67. var style = node.style;
  68. var oldDisplay = style.display;
  69. var oldVis = style.visibility;
  70. if(style.display == "none"){
  71. style.visibility = "hidden";
  72. style.display = "";
  73. }
  74. var bb = domGeometry.position(node);
  75. style.display = oldDisplay;
  76. style.visibility = oldVis;
  77. // coordinates and size of node with specified corner placed at pos,
  78. // and clipped by viewport
  79. var
  80. startXpos = {
  81. 'L': pos.x,
  82. 'R': pos.x - bb.w,
  83. 'M': Math.max(view.l, Math.min(view.l + view.w, pos.x + (bb.w >> 1)) - bb.w) // M orientation is more flexible
  84. }[corner.charAt(1)],
  85. startYpos = {
  86. 'T': pos.y,
  87. 'B': pos.y - bb.h,
  88. 'M': Math.max(view.t, Math.min(view.t + view.h, pos.y + (bb.h >> 1)) - bb.h)
  89. }[corner.charAt(0)],
  90. startX = Math.max(view.l, startXpos),
  91. startY = Math.max(view.t, startYpos),
  92. endX = Math.min(view.l + view.w, startXpos + bb.w),
  93. endY = Math.min(view.t + view.h, startYpos + bb.h),
  94. width = endX - startX,
  95. height = endY - startY;
  96. overflow += (bb.w - width) + (bb.h - height);
  97. if(best == null || overflow < best.overflow){
  98. best = {
  99. corner: corner,
  100. aroundCorner: choice.aroundCorner,
  101. x: startX,
  102. y: startY,
  103. w: width,
  104. h: height,
  105. overflow: overflow,
  106. spaceAvailable: spaceAvailable
  107. };
  108. }
  109. return !overflow;
  110. });
  111. // In case the best position is not the last one we checked, need to call
  112. // layoutNode() again.
  113. if(best.overflow && layoutNode){
  114. layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords);
  115. }
  116. // And then position the node. Do this last, after the layoutNode() above
  117. // has sized the node, due to browser quirks when the viewport is scrolled
  118. // (specifically that a Tooltip will shrink to fit as though the window was
  119. // scrolled to the left).
  120. //
  121. // In RTL mode, set style.right rather than style.left so in the common case,
  122. // window resizes move the popup along with the aroundNode.
  123. var l = domGeometry.isBodyLtr(node.ownerDocument),
  124. s = node.style;
  125. s.top = best.y + "px";
  126. s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
  127. s[l ? "right" : "left"] = "auto"; // needed for FF or else tooltip goes to far left
  128. return best;
  129. }
  130. var place = {
  131. // summary:
  132. // Code to place a DOMNode relative to another DOMNode.
  133. // Load using require(["dijit/place"], function(place){ ... }).
  134. at: function(node, pos, corners, padding){
  135. // summary:
  136. // Positions one of the node's corners at specified position
  137. // such that node is fully visible in viewport.
  138. // description:
  139. // NOTE: node is assumed to be absolutely or relatively positioned.
  140. // node: DOMNode
  141. // The node to position
  142. // pos: dijit/place.__Position
  143. // Object like {x: 10, y: 20}
  144. // corners: String[]
  145. // Array of Strings representing order to try corners in, like ["TR", "BL"].
  146. // Possible values are:
  147. //
  148. // - "BL" - bottom left
  149. // - "BR" - bottom right
  150. // - "TL" - top left
  151. // - "TR" - top right
  152. // padding: dijit/place.__Position?
  153. // optional param to set padding, to put some buffer around the element you want to position.
  154. // example:
  155. // Try to place node's top right corner at (10,20).
  156. // If that makes node go (partially) off screen, then try placing
  157. // bottom left corner at (10,20).
  158. // | place(node, {x: 10, y: 20}, ["TR", "BL"])
  159. var choices = array.map(corners, function(corner){
  160. var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
  161. if(padding){
  162. c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
  163. c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
  164. }
  165. return c;
  166. });
  167. return _place(node, choices);
  168. },
  169. around: function(
  170. /*DomNode*/ node,
  171. /*DomNode|dijit/place.__Rectangle*/ anchor,
  172. /*String[]*/ positions,
  173. /*Boolean*/ leftToRight,
  174. /*Function?*/ layoutNode){
  175. // summary:
  176. // Position node adjacent or kitty-corner to anchor
  177. // such that it's fully visible in viewport.
  178. // description:
  179. // Place node such that corner of node touches a corner of
  180. // aroundNode, and that node is fully visible.
  181. // anchor:
  182. // Either a DOMNode or a rectangle (object with x, y, width, height).
  183. // positions:
  184. // Ordered list of positions to try matching up.
  185. //
  186. // - before: places drop down to the left of the anchor node/widget, or to the right in the case
  187. // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down
  188. // with the top of the anchor, or the bottom of the drop down with bottom of the anchor.
  189. // - after: places drop down to the right of the anchor node/widget, or to the left in the case
  190. // of RTL scripts like Hebrew and Arabic; aligns either the top of the drop down
  191. // with the top of the anchor, or the bottom of the drop down with bottom of the anchor.
  192. // - before-centered: centers drop down to the left of the anchor node/widget, or to the right
  193. // in the case of RTL scripts like Hebrew and Arabic
  194. // - after-centered: centers drop down to the right of the anchor node/widget, or to the left
  195. // in the case of RTL scripts like Hebrew and Arabic
  196. // - above-centered: drop down is centered above anchor node
  197. // - above: drop down goes above anchor node, left sides aligned
  198. // - above-alt: drop down goes above anchor node, right sides aligned
  199. // - below-centered: drop down is centered above anchor node
  200. // - below: drop down goes below anchor node
  201. // - below-alt: drop down goes below anchor node, right sides aligned
  202. // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
  203. // For things like tooltip, they are displayed differently (and have different dimensions)
  204. // based on their orientation relative to the parent. This adjusts the popup based on orientation.
  205. // leftToRight:
  206. // True if widget is LTR, false if widget is RTL. Affects the behavior of "above" and "below"
  207. // positions slightly.
  208. // example:
  209. // | placeAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
  210. // This will try to position node such that node's top-left corner is at the same position
  211. // as the bottom left corner of the aroundNode (ie, put node below
  212. // aroundNode, with left edges aligned). If that fails it will try to put
  213. // the bottom-right corner of node where the top right corner of aroundNode is
  214. // (ie, put node above aroundNode, with right edges aligned)
  215. //
  216. // if around is a DOMNode (or DOMNode id), convert to coordinates
  217. var aroundNodePos = (typeof anchor == "string" || "offsetWidth" in anchor)
  218. ? domGeometry.position(anchor, true)
  219. : anchor;
  220. // Compute position and size of visible part of anchor (it may be partially hidden by ancestor nodes w/scrollbars)
  221. if(anchor.parentNode){
  222. // ignore nodes between position:relative and position:absolute
  223. var sawPosAbsolute = domStyle.getComputedStyle(anchor).position == "absolute";
  224. var parent = anchor.parentNode;
  225. while(parent && parent.nodeType == 1 && parent.nodeName != "BODY"){ //ignoring the body will help performance
  226. var parentPos = domGeometry.position(parent, true),
  227. pcs = domStyle.getComputedStyle(parent);
  228. if(/relative|absolute/.test(pcs.position)){
  229. sawPosAbsolute = false;
  230. }
  231. if(!sawPosAbsolute && /hidden|auto|scroll/.test(pcs.overflow)){
  232. var bottomYCoord = Math.min(aroundNodePos.y + aroundNodePos.h, parentPos.y + parentPos.h);
  233. var rightXCoord = Math.min(aroundNodePos.x + aroundNodePos.w, parentPos.x + parentPos.w);
  234. aroundNodePos.x = Math.max(aroundNodePos.x, parentPos.x);
  235. aroundNodePos.y = Math.max(aroundNodePos.y, parentPos.y);
  236. aroundNodePos.h = bottomYCoord - aroundNodePos.y;
  237. aroundNodePos.w = rightXCoord - aroundNodePos.x;
  238. }
  239. if(pcs.position == "absolute"){
  240. sawPosAbsolute = true;
  241. }
  242. parent = parent.parentNode;
  243. }
  244. }
  245. var x = aroundNodePos.x,
  246. y = aroundNodePos.y,
  247. width = "w" in aroundNodePos ? aroundNodePos.w : (aroundNodePos.w = aroundNodePos.width),
  248. 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);
  249. // Convert positions arguments into choices argument for _place()
  250. var choices = [];
  251. function push(aroundCorner, corner){
  252. choices.push({
  253. aroundCorner: aroundCorner,
  254. corner: corner,
  255. pos: {
  256. x: {
  257. 'L': x,
  258. 'R': x + width,
  259. 'M': x + (width >> 1)
  260. }[aroundCorner.charAt(1)],
  261. y: {
  262. 'T': y,
  263. 'B': y + height,
  264. 'M': y + (height >> 1)
  265. }[aroundCorner.charAt(0)]
  266. }
  267. })
  268. }
  269. array.forEach(positions, function(pos){
  270. var ltr = leftToRight;
  271. switch(pos){
  272. case "above-centered":
  273. push("TM", "BM");
  274. break;
  275. case "below-centered":
  276. push("BM", "TM");
  277. break;
  278. case "after-centered":
  279. ltr = !ltr;
  280. // fall through
  281. case "before-centered":
  282. push(ltr ? "ML" : "MR", ltr ? "MR" : "ML");
  283. break;
  284. case "after":
  285. ltr = !ltr;
  286. // fall through
  287. case "before":
  288. push(ltr ? "TL" : "TR", ltr ? "TR" : "TL");
  289. push(ltr ? "BL" : "BR", ltr ? "BR" : "BL");
  290. break;
  291. case "below-alt":
  292. ltr = !ltr;
  293. // fall through
  294. case "below":
  295. // first try to align left borders, next try to align right borders (or reverse for RTL mode)
  296. push(ltr ? "BL" : "BR", ltr ? "TL" : "TR");
  297. push(ltr ? "BR" : "BL", ltr ? "TR" : "TL");
  298. break;
  299. case "above-alt":
  300. ltr = !ltr;
  301. // fall through
  302. case "above":
  303. // first try to align left borders, next try to align right borders (or reverse for RTL mode)
  304. push(ltr ? "TL" : "TR", ltr ? "BL" : "BR");
  305. push(ltr ? "TR" : "TL", ltr ? "BR" : "BL");
  306. break;
  307. default:
  308. // To assist dijit/_base/place, accept arguments of type {aroundCorner: "BL", corner: "TL"}.
  309. // Not meant to be used directly.
  310. push(pos.aroundCorner, pos.corner);
  311. }
  312. });
  313. var position = _place(node, choices, layoutNode, {w: width, h: height});
  314. position.aroundNodePos = aroundNodePos;
  315. return position;
  316. }
  317. };
  318. /*=====
  319. place.__Position = {
  320. // x: Integer
  321. // horizontal coordinate in pixels, relative to document body
  322. // y: Integer
  323. // vertical coordinate in pixels, relative to document body
  324. };
  325. place.__Rectangle = {
  326. // x: Integer
  327. // horizontal offset in pixels, relative to document body
  328. // y: Integer
  329. // vertical offset in pixels, relative to document body
  330. // w: Integer
  331. // width in pixels. Can also be specified as "width" for backwards-compatibility.
  332. // h: Integer
  333. // height in pixels. Can also be specified as "height" for backwards-compatibility.
  334. };
  335. =====*/
  336. return dijit.place = place; // setting dijit.place for back-compat, remove for 2.0
  337. });