2 * jQuery UI Position 1.11.2
5 * Copyright 2014 jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
9 * http://api.jqueryui.com/position/
11 (function( factory ) {
12 if ( typeof define === "function" && define.amd ) {
14 // AMD. Register as an anonymous module.
15 define( [ "jquery" ], factory );
26 var cachedScrollbarWidth, supportsOffsetFractions,
30 rhorizontal = /left|center|right/,
31 rvertical = /top|center|bottom/,
32 roffset = /[\+\-]\d+(\.[\d]+)?%?/,
35 _position = $.fn.position;
37 function getOffsets( offsets, width, height ) {
39 parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
40 parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
44 function parseCss( element, property ) {
45 return parseInt( $.css( element, property ), 10 ) || 0;
48 function getDimensions( elem ) {
50 if ( raw.nodeType === 9 ) {
53 height: elem.height(),
54 offset: { top: 0, left: 0 }
57 if ( $.isWindow( raw ) ) {
60 height: elem.height(),
61 offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
64 if ( raw.preventDefault ) {
68 offset: { top: raw.pageY, left: raw.pageX }
72 width: elem.outerWidth(),
73 height: elem.outerHeight(),
79 scrollbarWidth: function() {
80 if ( cachedScrollbarWidth !== undefined ) {
81 return cachedScrollbarWidth;
84 div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
85 innerDiv = div.children()[0];
87 $( "body" ).append( div );
88 w1 = innerDiv.offsetWidth;
89 div.css( "overflow", "scroll" );
91 w2 = innerDiv.offsetWidth;
94 w2 = div[0].clientWidth;
99 return (cachedScrollbarWidth = w1 - w2);
101 getScrollInfo: function( within ) {
102 var overflowX = within.isWindow || within.isDocument ? "" :
103 within.element.css( "overflow-x" ),
104 overflowY = within.isWindow || within.isDocument ? "" :
105 within.element.css( "overflow-y" ),
106 hasOverflowX = overflowX === "scroll" ||
107 ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
108 hasOverflowY = overflowY === "scroll" ||
109 ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
111 width: hasOverflowY ? $.position.scrollbarWidth() : 0,
112 height: hasOverflowX ? $.position.scrollbarWidth() : 0
115 getWithinInfo: function( element ) {
116 var withinElement = $( element || window ),
117 isWindow = $.isWindow( withinElement[0] ),
118 isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
120 element: withinElement,
122 isDocument: isDocument,
123 offset: withinElement.offset() || { left: 0, top: 0 },
124 scrollLeft: withinElement.scrollLeft(),
125 scrollTop: withinElement.scrollTop(),
127 // support: jQuery 1.6.x
128 // jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows
129 width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(),
130 height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight()
135 $.fn.position = function( options ) {
136 if ( !options || !options.of ) {
137 return _position.apply( this, arguments );
140 // make a copy, we don't want to modify arguments
141 options = $.extend( {}, options );
143 var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
144 target = $( options.of ),
145 within = $.position.getWithinInfo( options.within ),
146 scrollInfo = $.position.getScrollInfo( within ),
147 collision = ( options.collision || "flip" ).split( " " ),
150 dimensions = getDimensions( target );
151 if ( target[0].preventDefault ) {
152 // force left top to allow flipping
153 options.at = "left top";
155 targetWidth = dimensions.width;
156 targetHeight = dimensions.height;
157 targetOffset = dimensions.offset;
158 // clone to reuse original targetOffset later
159 basePosition = $.extend( {}, targetOffset );
161 // force my and at to have valid horizontal and vertical positions
162 // if a value is missing or invalid, it will be converted to center
163 $.each( [ "my", "at" ], function() {
164 var pos = ( options[ this ] || "" ).split( " " ),
168 if ( pos.length === 1) {
169 pos = rhorizontal.test( pos[ 0 ] ) ?
170 pos.concat( [ "center" ] ) :
171 rvertical.test( pos[ 0 ] ) ?
172 [ "center" ].concat( pos ) :
173 [ "center", "center" ];
175 pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
176 pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
179 horizontalOffset = roffset.exec( pos[ 0 ] );
180 verticalOffset = roffset.exec( pos[ 1 ] );
182 horizontalOffset ? horizontalOffset[ 0 ] : 0,
183 verticalOffset ? verticalOffset[ 0 ] : 0
186 // reduce to just the positions without the offsets
188 rposition.exec( pos[ 0 ] )[ 0 ],
189 rposition.exec( pos[ 1 ] )[ 0 ]
193 // normalize collision option
194 if ( collision.length === 1 ) {
195 collision[ 1 ] = collision[ 0 ];
198 if ( options.at[ 0 ] === "right" ) {
199 basePosition.left += targetWidth;
200 } else if ( options.at[ 0 ] === "center" ) {
201 basePosition.left += targetWidth / 2;
204 if ( options.at[ 1 ] === "bottom" ) {
205 basePosition.top += targetHeight;
206 } else if ( options.at[ 1 ] === "center" ) {
207 basePosition.top += targetHeight / 2;
210 atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
211 basePosition.left += atOffset[ 0 ];
212 basePosition.top += atOffset[ 1 ];
214 return this.each(function() {
215 var collisionPosition, using,
217 elemWidth = elem.outerWidth(),
218 elemHeight = elem.outerHeight(),
219 marginLeft = parseCss( this, "marginLeft" ),
220 marginTop = parseCss( this, "marginTop" ),
221 collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
222 collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
223 position = $.extend( {}, basePosition ),
224 myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
226 if ( options.my[ 0 ] === "right" ) {
227 position.left -= elemWidth;
228 } else if ( options.my[ 0 ] === "center" ) {
229 position.left -= elemWidth / 2;
232 if ( options.my[ 1 ] === "bottom" ) {
233 position.top -= elemHeight;
234 } else if ( options.my[ 1 ] === "center" ) {
235 position.top -= elemHeight / 2;
238 position.left += myOffset[ 0 ];
239 position.top += myOffset[ 1 ];
241 // if the browser doesn't support fractions, then round for consistent results
242 if ( !supportsOffsetFractions ) {
243 position.left = round( position.left );
244 position.top = round( position.top );
247 collisionPosition = {
248 marginLeft: marginLeft,
252 $.each( [ "left", "top" ], function( i, dir ) {
253 if ( $.ui.position[ collision[ i ] ] ) {
254 $.ui.position[ collision[ i ] ][ dir ]( position, {
255 targetWidth: targetWidth,
256 targetHeight: targetHeight,
257 elemWidth: elemWidth,
258 elemHeight: elemHeight,
259 collisionPosition: collisionPosition,
260 collisionWidth: collisionWidth,
261 collisionHeight: collisionHeight,
262 offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
271 if ( options.using ) {
272 // adds feedback as second argument to using callback, if present
273 using = function( props ) {
274 var left = targetOffset.left - position.left,
275 right = left + targetWidth - elemWidth,
276 top = targetOffset.top - position.top,
277 bottom = top + targetHeight - elemHeight,
281 left: targetOffset.left,
282 top: targetOffset.top,
293 horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
294 vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
296 if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
297 feedback.horizontal = "center";
299 if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
300 feedback.vertical = "middle";
302 if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
303 feedback.important = "horizontal";
305 feedback.important = "vertical";
307 options.using.call( this, props, feedback );
311 elem.offset( $.extend( position, { using: using } ) );
317 left: function( position, data ) {
318 var within = data.within,
319 withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
320 outerWidth = within.width,
321 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
322 overLeft = withinOffset - collisionPosLeft,
323 overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
326 // element is wider than within
327 if ( data.collisionWidth > outerWidth ) {
328 // element is initially over the left side of within
329 if ( overLeft > 0 && overRight <= 0 ) {
330 newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
331 position.left += overLeft - newOverRight;
332 // element is initially over right side of within
333 } else if ( overRight > 0 && overLeft <= 0 ) {
334 position.left = withinOffset;
335 // element is initially over both left and right sides of within
337 if ( overLeft > overRight ) {
338 position.left = withinOffset + outerWidth - data.collisionWidth;
340 position.left = withinOffset;
343 // too far left -> align with left edge
344 } else if ( overLeft > 0 ) {
345 position.left += overLeft;
346 // too far right -> align with right edge
347 } else if ( overRight > 0 ) {
348 position.left -= overRight;
349 // adjust based on position and margin
351 position.left = max( position.left - collisionPosLeft, position.left );
354 top: function( position, data ) {
355 var within = data.within,
356 withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
357 outerHeight = data.within.height,
358 collisionPosTop = position.top - data.collisionPosition.marginTop,
359 overTop = withinOffset - collisionPosTop,
360 overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
363 // element is taller than within
364 if ( data.collisionHeight > outerHeight ) {
365 // element is initially over the top of within
366 if ( overTop > 0 && overBottom <= 0 ) {
367 newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
368 position.top += overTop - newOverBottom;
369 // element is initially over bottom of within
370 } else if ( overBottom > 0 && overTop <= 0 ) {
371 position.top = withinOffset;
372 // element is initially over both top and bottom of within
374 if ( overTop > overBottom ) {
375 position.top = withinOffset + outerHeight - data.collisionHeight;
377 position.top = withinOffset;
380 // too far up -> align with top
381 } else if ( overTop > 0 ) {
382 position.top += overTop;
383 // too far down -> align with bottom edge
384 } else if ( overBottom > 0 ) {
385 position.top -= overBottom;
386 // adjust based on position and margin
388 position.top = max( position.top - collisionPosTop, position.top );
393 left: function( position, data ) {
394 var within = data.within,
395 withinOffset = within.offset.left + within.scrollLeft,
396 outerWidth = within.width,
397 offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
398 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
399 overLeft = collisionPosLeft - offsetLeft,
400 overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
401 myOffset = data.my[ 0 ] === "left" ?
403 data.my[ 0 ] === "right" ?
406 atOffset = data.at[ 0 ] === "left" ?
408 data.at[ 0 ] === "right" ?
411 offset = -2 * data.offset[ 0 ],
415 if ( overLeft < 0 ) {
416 newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
417 if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
418 position.left += myOffset + atOffset + offset;
420 } else if ( overRight > 0 ) {
421 newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
422 if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
423 position.left += myOffset + atOffset + offset;
427 top: function( position, data ) {
428 var within = data.within,
429 withinOffset = within.offset.top + within.scrollTop,
430 outerHeight = within.height,
431 offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
432 collisionPosTop = position.top - data.collisionPosition.marginTop,
433 overTop = collisionPosTop - offsetTop,
434 overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
435 top = data.my[ 1 ] === "top",
438 data.my[ 1 ] === "bottom" ?
441 atOffset = data.at[ 1 ] === "top" ?
443 data.at[ 1 ] === "bottom" ?
446 offset = -2 * data.offset[ 1 ],
450 newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
451 if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
452 position.top += myOffset + atOffset + offset;
454 } else if ( overBottom > 0 ) {
455 newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
456 if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
457 position.top += myOffset + atOffset + offset;
464 $.ui.position.flip.left.apply( this, arguments );
465 $.ui.position.fit.left.apply( this, arguments );
468 $.ui.position.flip.top.apply( this, arguments );
469 $.ui.position.fit.top.apply( this, arguments );
474 // fraction support test
476 var testElement, testElementParent, testElementStyle, offsetLeft, i,
477 body = document.getElementsByTagName( "body" )[ 0 ],
478 div = document.createElement( "div" );
480 //Create a "fake body" for testing based on method used in jQuery.support
481 testElement = document.createElement( body ? "div" : "body" );
483 visibility: "hidden",
491 $.extend( testElementStyle, {
492 position: "absolute",
497 for ( i in testElementStyle ) {
498 testElement.style[ i ] = testElementStyle[ i ];
500 testElement.appendChild( div );
501 testElementParent = body || document.documentElement;
502 testElementParent.insertBefore( testElement, testElementParent.firstChild );
504 div.style.cssText = "position: absolute; left: 10.7432222px;";
506 offsetLeft = $( div ).offset().left;
507 supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11;
509 testElement.innerHTML = "";
510 testElementParent.removeChild( testElement );
515 return $.ui.position;