jquery.ui.position.js 15 KB

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