place.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit._base.place"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit._base.place"] = true;
  8. dojo.provide("dijit._base.place");
  9. dojo.require("dojo.window");
  10. dojo.require("dojo.AdapterRegistry");
  11. dijit.getViewport = function(){
  12. // summary:
  13. // Returns the dimensions and scroll position of the viewable area of a browser window
  14. return dojo.window.getBox();
  15. };
  16. /*=====
  17. dijit.__Position = function(){
  18. // x: Integer
  19. // horizontal coordinate in pixels, relative to document body
  20. // y: Integer
  21. // vertical coordinate in pixels, relative to document body
  22. thix.x = x;
  23. this.y = y;
  24. }
  25. =====*/
  26. dijit.placeOnScreen = function(
  27. /* DomNode */ node,
  28. /* dijit.__Position */ pos,
  29. /* String[] */ corners,
  30. /* dijit.__Position? */ padding){
  31. // summary:
  32. // Positions one of the node's corners at specified position
  33. // such that node is fully visible in viewport.
  34. // description:
  35. // NOTE: node is assumed to be absolutely or relatively positioned.
  36. // pos:
  37. // Object like {x: 10, y: 20}
  38. // corners:
  39. // Array of Strings representing order to try corners in, like ["TR", "BL"].
  40. // Possible values are:
  41. // * "BL" - bottom left
  42. // * "BR" - bottom right
  43. // * "TL" - top left
  44. // * "TR" - top right
  45. // padding:
  46. // set padding to put some buffer around the element you want to position.
  47. // example:
  48. // Try to place node's top right corner at (10,20).
  49. // If that makes node go (partially) off screen, then try placing
  50. // bottom left corner at (10,20).
  51. // | placeOnScreen(node, {x: 10, y: 20}, ["TR", "BL"])
  52. var choices = dojo.map(corners, function(corner){
  53. var c = { corner: corner, pos: {x:pos.x,y:pos.y} };
  54. if(padding){
  55. c.pos.x += corner.charAt(1) == 'L' ? padding.x : -padding.x;
  56. c.pos.y += corner.charAt(0) == 'T' ? padding.y : -padding.y;
  57. }
  58. return c;
  59. });
  60. return dijit._place(node, choices);
  61. }
  62. dijit._place = function(/*DomNode*/ node, choices, layoutNode, /*Object*/ aroundNodeCoords){
  63. // summary:
  64. // Given a list of spots to put node, put it at the first spot where it fits,
  65. // of if it doesn't fit anywhere then the place with the least overflow
  66. // choices: Array
  67. // Array of elements like: {corner: 'TL', pos: {x: 10, y: 20} }
  68. // Above example says to put the top-left corner of the node at (10,20)
  69. // layoutNode: Function(node, aroundNodeCorner, nodeCorner, size)
  70. // for things like tooltip, they are displayed differently (and have different dimensions)
  71. // based on their orientation relative to the parent. This adjusts the popup based on orientation.
  72. // It also passes in the available size for the popup, which is useful for tooltips to
  73. // tell them that their width is limited to a certain amount. layoutNode() may return a value expressing
  74. // how much the popup had to be modified to fit into the available space. This is used to determine
  75. // what the best placement is.
  76. // aroundNodeCoords: Object
  77. // Size of aroundNode, ex: {w: 200, h: 50}
  78. // get {x: 10, y: 10, w: 100, h:100} type obj representing position of
  79. // viewport over document
  80. var view = dojo.window.getBox();
  81. // This won't work if the node is inside a <div style="position: relative">,
  82. // so reattach it to dojo.doc.body. (Otherwise, the positioning will be wrong
  83. // and also it might get cutoff)
  84. if(!node.parentNode || String(node.parentNode.tagName).toLowerCase() != "body"){
  85. dojo.body().appendChild(node);
  86. }
  87. var best = null;
  88. dojo.some(choices, function(choice){
  89. var corner = choice.corner;
  90. var pos = choice.pos;
  91. var overflow = 0;
  92. // calculate amount of space available given specified position of node
  93. var spaceAvailable = {
  94. w: corner.charAt(1) == 'L' ? (view.l + view.w) - pos.x : pos.x - view.l,
  95. h: corner.charAt(1) == 'T' ? (view.t + view.h) - pos.y : pos.y - view.t
  96. };
  97. // configure node to be displayed in given position relative to button
  98. // (need to do this in order to get an accurate size for the node, because
  99. // a tooltip's size changes based on position, due to triangle)
  100. if(layoutNode){
  101. var res = layoutNode(node, choice.aroundCorner, corner, spaceAvailable, aroundNodeCoords);
  102. overflow = typeof res == "undefined" ? 0 : res;
  103. }
  104. // get node's size
  105. var style = node.style;
  106. var oldDisplay = style.display;
  107. var oldVis = style.visibility;
  108. style.visibility = "hidden";
  109. style.display = "";
  110. var mb = dojo.marginBox(node);
  111. style.display = oldDisplay;
  112. style.visibility = oldVis;
  113. // coordinates and size of node with specified corner placed at pos,
  114. // and clipped by viewport
  115. var startX = Math.max(view.l, corner.charAt(1) == 'L' ? pos.x : (pos.x - mb.w)),
  116. startY = Math.max(view.t, corner.charAt(0) == 'T' ? pos.y : (pos.y - mb.h)),
  117. endX = Math.min(view.l + view.w, corner.charAt(1) == 'L' ? (startX + mb.w) : pos.x),
  118. endY = Math.min(view.t + view.h, corner.charAt(0) == 'T' ? (startY + mb.h) : pos.y),
  119. width = endX - startX,
  120. height = endY - startY;
  121. overflow += (mb.w - width) + (mb.h - height);
  122. if(best == null || overflow < best.overflow){
  123. best = {
  124. corner: corner,
  125. aroundCorner: choice.aroundCorner,
  126. x: startX,
  127. y: startY,
  128. w: width,
  129. h: height,
  130. overflow: overflow,
  131. spaceAvailable: spaceAvailable
  132. };
  133. }
  134. return !overflow;
  135. });
  136. // In case the best position is not the last one we checked, need to call
  137. // layoutNode() again.
  138. if(best.overflow && layoutNode){
  139. layoutNode(node, best.aroundCorner, best.corner, best.spaceAvailable, aroundNodeCoords);
  140. }
  141. // And then position the node. Do this last, after the layoutNode() above
  142. // has sized the node, due to browser quirks when the viewport is scrolled
  143. // (specifically that a Tooltip will shrink to fit as though the window was
  144. // scrolled to the left).
  145. //
  146. // In RTL mode, set style.right rather than style.left so in the common case,
  147. // window resizes move the popup along with the aroundNode.
  148. var l = dojo._isBodyLtr(),
  149. s = node.style;
  150. s.top = best.y + "px";
  151. s[l ? "left" : "right"] = (l ? best.x : view.w - best.x - best.w) + "px";
  152. s[l ? "right" : "left"] = "auto"; // needed for FF or else tooltip goes to far left
  153. return best;
  154. }
  155. dijit.placeOnScreenAroundNode = function(
  156. /* DomNode */ node,
  157. /* DomNode */ aroundNode,
  158. /* Object */ aroundCorners,
  159. /* Function? */ layoutNode){
  160. // summary:
  161. // Position node adjacent or kitty-corner to aroundNode
  162. // such that it's fully visible in viewport.
  163. //
  164. // description:
  165. // Place node such that corner of node touches a corner of
  166. // aroundNode, and that node is fully visible.
  167. //
  168. // aroundCorners:
  169. // Ordered list of pairs of corners to try matching up.
  170. // Each pair of corners is represented as a key/value in the hash,
  171. // where the key corresponds to the aroundNode's corner, and
  172. // the value corresponds to the node's corner:
  173. //
  174. // | { aroundNodeCorner1: nodeCorner1, aroundNodeCorner2: nodeCorner2, ...}
  175. //
  176. // The following strings are used to represent the four corners:
  177. // * "BL" - bottom left
  178. // * "BR" - bottom right
  179. // * "TL" - top left
  180. // * "TR" - top right
  181. //
  182. // layoutNode: Function(node, aroundNodeCorner, nodeCorner)
  183. // For things like tooltip, they are displayed differently (and have different dimensions)
  184. // based on their orientation relative to the parent. This adjusts the popup based on orientation.
  185. //
  186. // example:
  187. // | dijit.placeOnScreenAroundNode(node, aroundNode, {'BL':'TL', 'TR':'BR'});
  188. // This will try to position node such that node's top-left corner is at the same position
  189. // as the bottom left corner of the aroundNode (ie, put node below
  190. // aroundNode, with left edges aligned). If that fails it will try to put
  191. // the bottom-right corner of node where the top right corner of aroundNode is
  192. // (ie, put node above aroundNode, with right edges aligned)
  193. //
  194. // get coordinates of aroundNode
  195. aroundNode = dojo.byId(aroundNode);
  196. var aroundNodePos = dojo.position(aroundNode, true);
  197. // place the node around the calculated rectangle
  198. return dijit._placeOnScreenAroundRect(node,
  199. aroundNodePos.x, aroundNodePos.y, aroundNodePos.w, aroundNodePos.h, // rectangle
  200. aroundCorners, layoutNode);
  201. };
  202. /*=====
  203. dijit.__Rectangle = function(){
  204. // x: Integer
  205. // horizontal offset in pixels, relative to document body
  206. // y: Integer
  207. // vertical offset in pixels, relative to document body
  208. // width: Integer
  209. // width in pixels
  210. // height: Integer
  211. // height in pixels
  212. this.x = x;
  213. this.y = y;
  214. this.width = width;
  215. this.height = height;
  216. }
  217. =====*/
  218. dijit.placeOnScreenAroundRectangle = function(
  219. /* DomNode */ node,
  220. /* dijit.__Rectangle */ aroundRect,
  221. /* Object */ aroundCorners,
  222. /* Function */ layoutNode){
  223. // summary:
  224. // Like dijit.placeOnScreenAroundNode(), except that the "around"
  225. // parameter is an arbitrary rectangle on the screen (x, y, width, height)
  226. // instead of a dom node.
  227. return dijit._placeOnScreenAroundRect(node,
  228. aroundRect.x, aroundRect.y, aroundRect.width, aroundRect.height, // rectangle
  229. aroundCorners, layoutNode);
  230. };
  231. dijit._placeOnScreenAroundRect = function(
  232. /* DomNode */ node,
  233. /* Number */ x,
  234. /* Number */ y,
  235. /* Number */ width,
  236. /* Number */ height,
  237. /* Object */ aroundCorners,
  238. /* Function */ layoutNode){
  239. // summary:
  240. // Like dijit.placeOnScreenAroundNode(), except it accepts coordinates
  241. // of a rectangle to place node adjacent to.
  242. // TODO: combine with placeOnScreenAroundRectangle()
  243. // Generate list of possible positions for node
  244. var choices = [];
  245. for(var nodeCorner in aroundCorners){
  246. choices.push( {
  247. aroundCorner: nodeCorner,
  248. corner: aroundCorners[nodeCorner],
  249. pos: {
  250. x: x + (nodeCorner.charAt(1) == 'L' ? 0 : width),
  251. y: y + (nodeCorner.charAt(0) == 'T' ? 0 : height)
  252. }
  253. });
  254. }
  255. return dijit._place(node, choices, layoutNode, {w: width, h: height});
  256. };
  257. dijit.placementRegistry= new dojo.AdapterRegistry();
  258. dijit.placementRegistry.register("node",
  259. function(n, x){
  260. return typeof x == "object" &&
  261. typeof x.offsetWidth != "undefined" && typeof x.offsetHeight != "undefined";
  262. },
  263. dijit.placeOnScreenAroundNode);
  264. dijit.placementRegistry.register("rect",
  265. function(n, x){
  266. return typeof x == "object" &&
  267. "x" in x && "y" in x && "width" in x && "height" in x;
  268. },
  269. dijit.placeOnScreenAroundRectangle);
  270. dijit.placeOnScreenAroundElement = function(
  271. /* DomNode */ node,
  272. /* Object */ aroundElement,
  273. /* Object */ aroundCorners,
  274. /* Function */ layoutNode){
  275. // summary:
  276. // Like dijit.placeOnScreenAroundNode(), except it accepts an arbitrary object
  277. // for the "around" argument and finds a proper processor to place a node.
  278. return dijit.placementRegistry.match.apply(dijit.placementRegistry, arguments);
  279. };
  280. dijit.getPopupAroundAlignment = function(/*Array*/ position, /*Boolean*/ leftToRight){
  281. // summary:
  282. // Transforms the passed array of preferred positions into a format suitable for passing as the aroundCorners argument to dijit.placeOnScreenAroundElement.
  283. //
  284. // position: String[]
  285. // This variable controls the position of the drop down.
  286. // It's an array of strings with the following values:
  287. //
  288. // * before: places drop down to the left of the target node/widget, or to the right in
  289. // the case of RTL scripts like Hebrew and Arabic
  290. // * after: places drop down to the right of the target node/widget, or to the left in
  291. // the case of RTL scripts like Hebrew and Arabic
  292. // * above: drop down goes above target node
  293. // * below: drop down goes below target node
  294. //
  295. // The list is positions is tried, in order, until a position is found where the drop down fits
  296. // within the viewport.
  297. //
  298. // leftToRight: Boolean
  299. // Whether the popup will be displaying in leftToRight mode.
  300. //
  301. var align = {};
  302. dojo.forEach(position, function(pos){
  303. switch(pos){
  304. case "after":
  305. align[leftToRight ? "BR" : "BL"] = leftToRight ? "BL" : "BR";
  306. break;
  307. case "before":
  308. align[leftToRight ? "BL" : "BR"] = leftToRight ? "BR" : "BL";
  309. break;
  310. case "below-alt":
  311. leftToRight = !leftToRight;
  312. // fall through
  313. case "below":
  314. // first try to align left borders, next try to align right borders (or reverse for RTL mode)
  315. align[leftToRight ? "BL" : "BR"] = leftToRight ? "TL" : "TR";
  316. align[leftToRight ? "BR" : "BL"] = leftToRight ? "TR" : "TL";
  317. break;
  318. case "above-alt":
  319. leftToRight = !leftToRight;
  320. // fall through
  321. case "above":
  322. default:
  323. // first try to align left borders, next try to align right borders (or reverse for RTL mode)
  324. align[leftToRight ? "TL" : "TR"] = leftToRight ? "BL" : "BR";
  325. align[leftToRight ? "TR" : "TL"] = leftToRight ? "BR" : "BL";
  326. break;
  327. }
  328. });
  329. return align;
  330. };
  331. }