jquery.ui.position.js 16KB

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