jquery.ui.position.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /*!
  2. * jQuery UI Position v1.10.0
  3. * http://jqueryui.com
  4. *
  5. * Copyright 2013 jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. *
  9. * http://api.jqueryui.com/position/
  10. */
  11. (function( $, undefined ) {
  12. $.ui = $.ui || {};
  13. var cachedScrollbarWidth,
  14. max = Math.max,
  15. abs = Math.abs,
  16. round = Math.round,
  17. rhorizontal = /left|center|right/,
  18. rvertical = /top|center|bottom/,
  19. roffset = /[\+\-]\d+%?/,
  20. rposition = /^\w+/,
  21. rpercent = /%$/,
  22. _position = $.fn.position;
  23. function getOffsets( offsets, width, height ) {
  24. return [
  25. parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
  26. parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
  27. ];
  28. }
  29. function parseCss( element, property ) {
  30. return parseInt( $.css( element, property ), 10 ) || 0;
  31. }
  32. function getDimensions( elem ) {
  33. var raw = elem[0];
  34. if ( raw.nodeType === 9 ) {
  35. return {
  36. width: elem.width(),
  37. height: elem.height(),
  38. offset: { top: 0, left: 0 }
  39. };
  40. }
  41. if ( $.isWindow( raw ) ) {
  42. return {
  43. width: elem.width(),
  44. height: elem.height(),
  45. offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
  46. };
  47. }
  48. if ( raw.preventDefault ) {
  49. return {
  50. width: 0,
  51. height: 0,
  52. offset: { top: raw.pageY, left: raw.pageX }
  53. };
  54. }
  55. return {
  56. width: elem.outerWidth(),
  57. height: elem.outerHeight(),
  58. offset: elem.offset()
  59. };
  60. }
  61. $.position = {
  62. scrollbarWidth: function() {
  63. if ( cachedScrollbarWidth !== undefined ) {
  64. return cachedScrollbarWidth;
  65. }
  66. var w1, w2,
  67. div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
  68. innerDiv = div.children()[0];
  69. $( "body" ).append( div );
  70. w1 = innerDiv.offsetWidth;
  71. div.css( "overflow", "scroll" );
  72. w2 = innerDiv.offsetWidth;
  73. if ( w1 === w2 ) {
  74. w2 = div[0].clientWidth;
  75. }
  76. div.remove();
  77. return (cachedScrollbarWidth = w1 - w2);
  78. },
  79. getScrollInfo: function( within ) {
  80. var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
  81. overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
  82. hasOverflowX = overflowX === "scroll" ||
  83. ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
  84. hasOverflowY = overflowY === "scroll" ||
  85. ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
  86. return {
  87. width: hasOverflowX ? $.position.scrollbarWidth() : 0,
  88. height: hasOverflowY ? $.position.scrollbarWidth() : 0
  89. };
  90. },
  91. getWithinInfo: function( element ) {
  92. var withinElement = $( element || window ),
  93. isWindow = $.isWindow( withinElement[0] );
  94. return {
  95. element: withinElement,
  96. isWindow: isWindow,
  97. offset: withinElement.offset() || { left: 0, top: 0 },
  98. scrollLeft: withinElement.scrollLeft(),
  99. scrollTop: withinElement.scrollTop(),
  100. width: isWindow ? withinElement.width() : withinElement.outerWidth(),
  101. height: isWindow ? withinElement.height() : withinElement.outerHeight()
  102. };
  103. }
  104. };
  105. $.fn.position = function( options ) {
  106. if ( !options || !options.of ) {
  107. return _position.apply( this, arguments );
  108. }
  109. // make a copy, we don't want to modify arguments
  110. options = $.extend( {}, options );
  111. var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
  112. target = $( options.of ),
  113. within = $.position.getWithinInfo( options.within ),
  114. scrollInfo = $.position.getScrollInfo( within ),
  115. collision = ( options.collision || "flip" ).split( " " ),
  116. offsets = {};
  117. dimensions = getDimensions( target );
  118. if ( target[0].preventDefault ) {
  119. // force left top to allow flipping
  120. options.at = "left top";
  121. }
  122. targetWidth = dimensions.width;
  123. targetHeight = dimensions.height;
  124. targetOffset = dimensions.offset;
  125. // clone to reuse original targetOffset later
  126. basePosition = $.extend( {}, targetOffset );
  127. // force my and at to have valid horizontal and vertical positions
  128. // if a value is missing or invalid, it will be converted to center
  129. $.each( [ "my", "at" ], function() {
  130. var pos = ( options[ this ] || "" ).split( " " ),
  131. horizontalOffset,
  132. verticalOffset;
  133. if ( pos.length === 1) {
  134. pos = rhorizontal.test( pos[ 0 ] ) ?
  135. pos.concat( [ "center" ] ) :
  136. rvertical.test( pos[ 0 ] ) ?
  137. [ "center" ].concat( pos ) :
  138. [ "center", "center" ];
  139. }
  140. pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
  141. pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
  142. // calculate offsets
  143. horizontalOffset = roffset.exec( pos[ 0 ] );
  144. verticalOffset = roffset.exec( pos[ 1 ] );
  145. offsets[ this ] = [
  146. horizontalOffset ? horizontalOffset[ 0 ] : 0,
  147. verticalOffset ? verticalOffset[ 0 ] : 0
  148. ];
  149. // reduce to just the positions without the offsets
  150. options[ this ] = [
  151. rposition.exec( pos[ 0 ] )[ 0 ],
  152. rposition.exec( pos[ 1 ] )[ 0 ]
  153. ];
  154. });
  155. // normalize collision option
  156. if ( collision.length === 1 ) {
  157. collision[ 1 ] = collision[ 0 ];
  158. }
  159. if ( options.at[ 0 ] === "right" ) {
  160. basePosition.left += targetWidth;
  161. } else if ( options.at[ 0 ] === "center" ) {
  162. basePosition.left += targetWidth / 2;
  163. }
  164. if ( options.at[ 1 ] === "bottom" ) {
  165. basePosition.top += targetHeight;
  166. } else if ( options.at[ 1 ] === "center" ) {
  167. basePosition.top += targetHeight / 2;
  168. }
  169. atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
  170. basePosition.left += atOffset[ 0 ];
  171. basePosition.top += atOffset[ 1 ];
  172. return this.each(function() {
  173. var collisionPosition, using,
  174. elem = $( this ),
  175. elemWidth = elem.outerWidth(),
  176. elemHeight = elem.outerHeight(),
  177. marginLeft = parseCss( this, "marginLeft" ),
  178. marginTop = parseCss( this, "marginTop" ),
  179. collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
  180. collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
  181. position = $.extend( {}, basePosition ),
  182. myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
  183. if ( options.my[ 0 ] === "right" ) {
  184. position.left -= elemWidth;
  185. } else if ( options.my[ 0 ] === "center" ) {
  186. position.left -= elemWidth / 2;
  187. }
  188. if ( options.my[ 1 ] === "bottom" ) {
  189. position.top -= elemHeight;
  190. } else if ( options.my[ 1 ] === "center" ) {
  191. position.top -= elemHeight / 2;
  192. }
  193. position.left += myOffset[ 0 ];
  194. position.top += myOffset[ 1 ];
  195. // if the browser doesn't support fractions, then round for consistent results
  196. if ( !$.support.offsetFractions ) {
  197. position.left = round( position.left );
  198. position.top = round( position.top );
  199. }
  200. collisionPosition = {
  201. marginLeft: marginLeft,
  202. marginTop: marginTop
  203. };
  204. $.each( [ "left", "top" ], function( i, dir ) {
  205. if ( $.ui.position[ collision[ i ] ] ) {
  206. $.ui.position[ collision[ i ] ][ dir ]( position, {
  207. targetWidth: targetWidth,
  208. targetHeight: targetHeight,
  209. elemWidth: elemWidth,
  210. elemHeight: elemHeight,
  211. collisionPosition: collisionPosition,
  212. collisionWidth: collisionWidth,
  213. collisionHeight: collisionHeight,
  214. offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
  215. my: options.my,
  216. at: options.at,
  217. within: within,
  218. elem : elem
  219. });
  220. }
  221. });
  222. if ( options.using ) {
  223. // adds feedback as second argument to using callback, if present
  224. using = function( props ) {
  225. var left = targetOffset.left - position.left,
  226. right = left + targetWidth - elemWidth,
  227. top = targetOffset.top - position.top,
  228. bottom = top + targetHeight - elemHeight,
  229. feedback = {
  230. target: {
  231. element: target,
  232. left: targetOffset.left,
  233. top: targetOffset.top,
  234. width: targetWidth,
  235. height: targetHeight
  236. },
  237. element: {
  238. element: elem,
  239. left: position.left,
  240. top: position.top,
  241. width: elemWidth,
  242. height: elemHeight
  243. },
  244. horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
  245. vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
  246. };
  247. if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
  248. feedback.horizontal = "center";
  249. }
  250. if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
  251. feedback.vertical = "middle";
  252. }
  253. if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
  254. feedback.important = "horizontal";
  255. } else {
  256. feedback.important = "vertical";
  257. }
  258. options.using.call( this, props, feedback );
  259. };
  260. }
  261. elem.offset( $.extend( position, { using: using } ) );
  262. });
  263. };
  264. $.ui.position = {
  265. fit: {
  266. left: function( position, data ) {
  267. var within = data.within,
  268. withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
  269. outerWidth = within.width,
  270. collisionPosLeft = position.left - data.collisionPosition.marginLeft,
  271. overLeft = withinOffset - collisionPosLeft,
  272. overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
  273. newOverRight;
  274. // element is wider than within
  275. if ( data.collisionWidth > outerWidth ) {
  276. // element is initially over the left side of within
  277. if ( overLeft > 0 && overRight <= 0 ) {
  278. newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
  279. position.left += overLeft - newOverRight;
  280. // element is initially over right side of within
  281. } else if ( overRight > 0 && overLeft <= 0 ) {
  282. position.left = withinOffset;
  283. // element is initially over both left and right sides of within
  284. } else {
  285. if ( overLeft > overRight ) {
  286. position.left = withinOffset + outerWidth - data.collisionWidth;
  287. } else {
  288. position.left = withinOffset;
  289. }
  290. }
  291. // too far left -> align with left edge
  292. } else if ( overLeft > 0 ) {
  293. position.left += overLeft;
  294. // too far right -> align with right edge
  295. } else if ( overRight > 0 ) {
  296. position.left -= overRight;
  297. // adjust based on position and margin
  298. } else {
  299. position.left = max( position.left - collisionPosLeft, position.left );
  300. }
  301. },
  302. top: function( position, data ) {
  303. var within = data.within,
  304. withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
  305. outerHeight = data.within.height,
  306. collisionPosTop = position.top - data.collisionPosition.marginTop,
  307. overTop = withinOffset - collisionPosTop,
  308. overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
  309. newOverBottom;
  310. // element is taller than within
  311. if ( data.collisionHeight > outerHeight ) {
  312. // element is initially over the top of within
  313. if ( overTop > 0 && overBottom <= 0 ) {
  314. newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
  315. position.top += overTop - newOverBottom;
  316. // element is initially over bottom of within
  317. } else if ( overBottom > 0 && overTop <= 0 ) {
  318. position.top = withinOffset;
  319. // element is initially over both top and bottom of within
  320. } else {
  321. if ( overTop > overBottom ) {
  322. position.top = withinOffset + outerHeight - data.collisionHeight;
  323. } else {
  324. position.top = withinOffset;
  325. }
  326. }
  327. // too far up -> align with top
  328. } else if ( overTop > 0 ) {
  329. position.top += overTop;
  330. // too far down -> align with bottom edge
  331. } else if ( overBottom > 0 ) {
  332. position.top -= overBottom;
  333. // adjust based on position and margin
  334. } else {
  335. position.top = max( position.top - collisionPosTop, position.top );
  336. }
  337. }
  338. },
  339. flip: {
  340. left: function( position, data ) {
  341. var within = data.within,
  342. withinOffset = within.offset.left + within.scrollLeft,
  343. outerWidth = within.width,
  344. offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
  345. collisionPosLeft = position.left - data.collisionPosition.marginLeft,
  346. overLeft = collisionPosLeft - offsetLeft,
  347. overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
  348. myOffset = data.my[ 0 ] === "left" ?
  349. -data.elemWidth :
  350. data.my[ 0 ] === "right" ?
  351. data.elemWidth :
  352. 0,
  353. atOffset = data.at[ 0 ] === "left" ?
  354. data.targetWidth :
  355. data.at[ 0 ] === "right" ?
  356. -data.targetWidth :
  357. 0,
  358. offset = -2 * data.offset[ 0 ],
  359. newOverRight,
  360. newOverLeft;
  361. if ( overLeft < 0 ) {
  362. newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
  363. if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
  364. position.left += myOffset + atOffset + offset;
  365. }
  366. }
  367. else if ( overRight > 0 ) {
  368. newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
  369. if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
  370. position.left += myOffset + atOffset + offset;
  371. }
  372. }
  373. },
  374. top: function( position, data ) {
  375. var within = data.within,
  376. withinOffset = within.offset.top + within.scrollTop,
  377. outerHeight = within.height,
  378. offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
  379. collisionPosTop = position.top - data.collisionPosition.marginTop,
  380. overTop = collisionPosTop - offsetTop,
  381. overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
  382. top = data.my[ 1 ] === "top",
  383. myOffset = top ?
  384. -data.elemHeight :
  385. data.my[ 1 ] === "bottom" ?
  386. data.elemHeight :
  387. 0,
  388. atOffset = data.at[ 1 ] === "top" ?
  389. data.targetHeight :
  390. data.at[ 1 ] === "bottom" ?
  391. -data.targetHeight :
  392. 0,
  393. offset = -2 * data.offset[ 1 ],
  394. newOverTop,
  395. newOverBottom;
  396. if ( overTop < 0 ) {
  397. newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
  398. if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
  399. position.top += myOffset + atOffset + offset;
  400. }
  401. }
  402. else if ( overBottom > 0 ) {
  403. newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
  404. if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
  405. position.top += myOffset + atOffset + offset;
  406. }
  407. }
  408. }
  409. },
  410. flipfit: {
  411. left: function() {
  412. $.ui.position.flip.left.apply( this, arguments );
  413. $.ui.position.fit.left.apply( this, arguments );
  414. },
  415. top: function() {
  416. $.ui.position.flip.top.apply( this, arguments );
  417. $.ui.position.fit.top.apply( this, arguments );
  418. }
  419. }
  420. };
  421. // fraction support test
  422. (function () {
  423. var testElement, testElementParent, testElementStyle, offsetLeft, i,
  424. body = document.getElementsByTagName( "body" )[ 0 ],
  425. div = document.createElement( "div" );
  426. //Create a "fake body" for testing based on method used in jQuery.support
  427. testElement = document.createElement( body ? "div" : "body" );
  428. testElementStyle = {
  429. visibility: "hidden",
  430. width: 0,
  431. height: 0,
  432. border: 0,
  433. margin: 0,
  434. background: "none"
  435. };
  436. if ( body ) {
  437. $.extend( testElementStyle, {
  438. position: "absolute",
  439. left: "-1000px",
  440. top: "-1000px"
  441. });
  442. }
  443. for ( i in testElementStyle ) {
  444. testElement.style[ i ] = testElementStyle[ i ];
  445. }
  446. testElement.appendChild( div );
  447. testElementParent = body || document.documentElement;
  448. testElementParent.insertBefore( testElement, testElementParent.firstChild );
  449. div.style.cssText = "position: absolute; left: 10.7432222px;";
  450. offsetLeft = $( div ).offset().left;
  451. $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
  452. testElement.innerHTML = "";
  453. testElementParent.removeChild( testElement );
  454. })();
  455. }( jQuery ) );