2 * jQuery UI Position 1.10.4
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( $, undefined ) {
15 var cachedScrollbarWidth,
19 rhorizontal = /left|center|right/,
20 rvertical = /top|center|bottom/,
21 roffset = /[\+\-]\d+(\.[\d]+)?%?/,
24 _position = $.fn.position;
26 function getOffsets( offsets, width, height ) {
28 parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
29 parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
33 function parseCss( element, property ) {
34 return parseInt( $.css( element, property ), 10 ) || 0;
37 function getDimensions( elem ) {
39 if ( raw.nodeType === 9 ) {
42 height: elem.height(),
43 offset: { top: 0, left: 0 }
46 if ( $.isWindow( raw ) ) {
49 height: elem.height(),
50 offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
53 if ( raw.preventDefault ) {
57 offset: { top: raw.pageY, left: raw.pageX }
61 width: elem.outerWidth(),
62 height: elem.outerHeight(),
68 scrollbarWidth: function() {
69 if ( cachedScrollbarWidth !== undefined ) {
70 return cachedScrollbarWidth;
73 div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
74 innerDiv = div.children()[0];
76 $( "body" ).append( div );
77 w1 = innerDiv.offsetWidth;
78 div.css( "overflow", "scroll" );
80 w2 = innerDiv.offsetWidth;
83 w2 = div[0].clientWidth;
88 return (cachedScrollbarWidth = w1 - w2);
90 getScrollInfo: function( within ) {
91 var overflowX = within.isWindow || within.isDocument ? "" :
92 within.element.css( "overflow-x" ),
93 overflowY = within.isWindow || within.isDocument ? "" :
94 within.element.css( "overflow-y" ),
95 hasOverflowX = overflowX === "scroll" ||
96 ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
97 hasOverflowY = overflowY === "scroll" ||
98 ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
100 width: hasOverflowY ? $.position.scrollbarWidth() : 0,
101 height: hasOverflowX ? $.position.scrollbarWidth() : 0
104 getWithinInfo: function( element ) {
105 var withinElement = $( element || window ),
106 isWindow = $.isWindow( withinElement[0] ),
107 isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
109 element: withinElement,
111 isDocument: isDocument,
112 offset: withinElement.offset() || { left: 0, top: 0 },
113 scrollLeft: withinElement.scrollLeft(),
114 scrollTop: withinElement.scrollTop(),
115 width: isWindow ? withinElement.width() : withinElement.outerWidth(),
116 height: isWindow ? withinElement.height() : withinElement.outerHeight()
121 $.fn.position = function( options ) {
122 if ( !options || !options.of ) {
123 return _position.apply( this, arguments );
126 // make a copy, we don't want to modify arguments
127 options = $.extend( {}, options );
129 var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
130 target = $( options.of ),
131 within = $.position.getWithinInfo( options.within ),
132 scrollInfo = $.position.getScrollInfo( within ),
133 collision = ( options.collision || "flip" ).split( " " ),
136 dimensions = getDimensions( target );
137 if ( target[0].preventDefault ) {
138 // force left top to allow flipping
139 options.at = "left top";
141 targetWidth = dimensions.width;
142 targetHeight = dimensions.height;
143 targetOffset = dimensions.offset;
144 // clone to reuse original targetOffset later
145 basePosition = $.extend( {}, targetOffset );
147 // force my and at to have valid horizontal and vertical positions
148 // if a value is missing or invalid, it will be converted to center
149 $.each( [ "my", "at" ], function() {
150 var pos = ( options[ this ] || "" ).split( " " ),
154 if ( pos.length === 1) {
155 pos = rhorizontal.test( pos[ 0 ] ) ?
156 pos.concat( [ "center" ] ) :
157 rvertical.test( pos[ 0 ] ) ?
158 [ "center" ].concat( pos ) :
159 [ "center", "center" ];
161 pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
162 pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
165 horizontalOffset = roffset.exec( pos[ 0 ] );
166 verticalOffset = roffset.exec( pos[ 1 ] );
168 horizontalOffset ? horizontalOffset[ 0 ] : 0,
169 verticalOffset ? verticalOffset[ 0 ] : 0
172 // reduce to just the positions without the offsets
174 rposition.exec( pos[ 0 ] )[ 0 ],
175 rposition.exec( pos[ 1 ] )[ 0 ]
179 // normalize collision option
180 if ( collision.length === 1 ) {
181 collision[ 1 ] = collision[ 0 ];
184 if ( options.at[ 0 ] === "right" ) {
185 basePosition.left += targetWidth;
186 } else if ( options.at[ 0 ] === "center" ) {
187 basePosition.left += targetWidth / 2;
190 if ( options.at[ 1 ] === "bottom" ) {
191 basePosition.top += targetHeight;
192 } else if ( options.at[ 1 ] === "center" ) {
193 basePosition.top += targetHeight / 2;
196 atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
197 basePosition.left += atOffset[ 0 ];
198 basePosition.top += atOffset[ 1 ];
200 return this.each(function() {
201 var collisionPosition, using,
203 elemWidth = elem.outerWidth(),
204 elemHeight = elem.outerHeight(),
205 marginLeft = parseCss( this, "marginLeft" ),
206 marginTop = parseCss( this, "marginTop" ),
207 collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
208 collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
209 position = $.extend( {}, basePosition ),
210 myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
212 if ( options.my[ 0 ] === "right" ) {
213 position.left -= elemWidth;
214 } else if ( options.my[ 0 ] === "center" ) {
215 position.left -= elemWidth / 2;
218 if ( options.my[ 1 ] === "bottom" ) {
219 position.top -= elemHeight;
220 } else if ( options.my[ 1 ] === "center" ) {
221 position.top -= elemHeight / 2;
224 position.left += myOffset[ 0 ];
225 position.top += myOffset[ 1 ];
227 // if the browser doesn't support fractions, then round for consistent results
228 if ( !$.support.offsetFractions ) {
229 position.left = round( position.left );
230 position.top = round( position.top );
233 collisionPosition = {
234 marginLeft: marginLeft,
238 $.each( [ "left", "top" ], function( i, dir ) {
239 if ( $.ui.position[ collision[ i ] ] ) {
240 $.ui.position[ collision[ i ] ][ dir ]( position, {
241 targetWidth: targetWidth,
242 targetHeight: targetHeight,
243 elemWidth: elemWidth,
244 elemHeight: elemHeight,
245 collisionPosition: collisionPosition,
246 collisionWidth: collisionWidth,
247 collisionHeight: collisionHeight,
248 offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
257 if ( options.using ) {
258 // adds feedback as second argument to using callback, if present
259 using = function( props ) {
260 var left = targetOffset.left - position.left,
261 right = left + targetWidth - elemWidth,
262 top = targetOffset.top - position.top,
263 bottom = top + targetHeight - elemHeight,
267 left: targetOffset.left,
268 top: targetOffset.top,
279 horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
280 vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
282 if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
283 feedback.horizontal = "center";
285 if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
286 feedback.vertical = "middle";
288 if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
289 feedback.important = "horizontal";
291 feedback.important = "vertical";
293 options.using.call( this, props, feedback );
297 elem.offset( $.extend( position, { using: using } ) );
303 left: function( position, data ) {
304 var within = data.within,
305 withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
306 outerWidth = within.width,
307 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
308 overLeft = withinOffset - collisionPosLeft,
309 overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
312 // element is wider than within
313 if ( data.collisionWidth > outerWidth ) {
314 // element is initially over the left side of within
315 if ( overLeft > 0 && overRight <= 0 ) {
316 newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
317 position.left += overLeft - newOverRight;
318 // element is initially over right side of within
319 } else if ( overRight > 0 && overLeft <= 0 ) {
320 position.left = withinOffset;
321 // element is initially over both left and right sides of within
323 if ( overLeft > overRight ) {
324 position.left = withinOffset + outerWidth - data.collisionWidth;
326 position.left = withinOffset;
329 // too far left -> align with left edge
330 } else if ( overLeft > 0 ) {
331 position.left += overLeft;
332 // too far right -> align with right edge
333 } else if ( overRight > 0 ) {
334 position.left -= overRight;
335 // adjust based on position and margin
337 position.left = max( position.left - collisionPosLeft, position.left );
340 top: function( position, data ) {
341 var within = data.within,
342 withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
343 outerHeight = data.within.height,
344 collisionPosTop = position.top - data.collisionPosition.marginTop,
345 overTop = withinOffset - collisionPosTop,
346 overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
349 // element is taller than within
350 if ( data.collisionHeight > outerHeight ) {
351 // element is initially over the top of within
352 if ( overTop > 0 && overBottom <= 0 ) {
353 newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
354 position.top += overTop - newOverBottom;
355 // element is initially over bottom of within
356 } else if ( overBottom > 0 && overTop <= 0 ) {
357 position.top = withinOffset;
358 // element is initially over both top and bottom of within
360 if ( overTop > overBottom ) {
361 position.top = withinOffset + outerHeight - data.collisionHeight;
363 position.top = withinOffset;
366 // too far up -> align with top
367 } else if ( overTop > 0 ) {
368 position.top += overTop;
369 // too far down -> align with bottom edge
370 } else if ( overBottom > 0 ) {
371 position.top -= overBottom;
372 // adjust based on position and margin
374 position.top = max( position.top - collisionPosTop, position.top );
379 left: function( position, data ) {
380 var within = data.within,
381 withinOffset = within.offset.left + within.scrollLeft,
382 outerWidth = within.width,
383 offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
384 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
385 overLeft = collisionPosLeft - offsetLeft,
386 overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
387 myOffset = data.my[ 0 ] === "left" ?
389 data.my[ 0 ] === "right" ?
392 atOffset = data.at[ 0 ] === "left" ?
394 data.at[ 0 ] === "right" ?
397 offset = -2 * data.offset[ 0 ],
401 if ( overLeft < 0 ) {
402 newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
403 if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
404 position.left += myOffset + atOffset + offset;
407 else if ( overRight > 0 ) {
408 newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
409 if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
410 position.left += myOffset + atOffset + offset;
414 top: function( position, data ) {
415 var within = data.within,
416 withinOffset = within.offset.top + within.scrollTop,
417 outerHeight = within.height,
418 offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
419 collisionPosTop = position.top - data.collisionPosition.marginTop,
420 overTop = collisionPosTop - offsetTop,
421 overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
422 top = data.my[ 1 ] === "top",
425 data.my[ 1 ] === "bottom" ?
428 atOffset = data.at[ 1 ] === "top" ?
430 data.at[ 1 ] === "bottom" ?
433 offset = -2 * data.offset[ 1 ],
437 newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
438 if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
439 position.top += myOffset + atOffset + offset;
442 else if ( overBottom > 0 ) {
443 newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
444 if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
445 position.top += myOffset + atOffset + offset;
452 $.ui.position.flip.left.apply( this, arguments );
453 $.ui.position.fit.left.apply( this, arguments );
456 $.ui.position.flip.top.apply( this, arguments );
457 $.ui.position.fit.top.apply( this, arguments );
462 // fraction support test
464 var testElement, testElementParent, testElementStyle, offsetLeft, i,
465 body = document.getElementsByTagName( "body" )[ 0 ],
466 div = document.createElement( "div" );
468 //Create a "fake body" for testing based on method used in jQuery.support
469 testElement = document.createElement( body ? "div" : "body" );
471 visibility: "hidden",
479 $.extend( testElementStyle, {
480 position: "absolute",
485 for ( i in testElementStyle ) {
486 testElement.style[ i ] = testElementStyle[ i ];
488 testElement.appendChild( div );
489 testElementParent = body || document.documentElement;
490 testElementParent.insertBefore( testElement, testElementParent.firstChild );
492 div.style.cssText = "position: absolute; left: 10.7432222px;";
494 offsetLeft = $( div ).offset().left;
495 $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
497 testElement.innerHTML = "";
498 testElementParent.removeChild( testElement );