2 * jQuery UI Position 1.9.2
5 * Copyright 2012 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+%?/,
24 _position = $.fn.position;
26 function getOffsets( offsets, width, height ) {
28 parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
29 parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
32 function parseCss( element, property ) {
33 return parseInt( $.css( element, property ), 10 ) || 0;
37 scrollbarWidth: function() {
38 if ( cachedScrollbarWidth !== undefined ) {
39 return cachedScrollbarWidth;
42 div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
43 innerDiv = div.children()[0];
45 $( "body" ).append( div );
46 w1 = innerDiv.offsetWidth;
47 div.css( "overflow", "scroll" );
49 w2 = innerDiv.offsetWidth;
52 w2 = div[0].clientWidth;
57 return (cachedScrollbarWidth = w1 - w2);
59 getScrollInfo: function( within ) {
60 var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
61 overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
62 hasOverflowX = overflowX === "scroll" ||
63 ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
64 hasOverflowY = overflowY === "scroll" ||
65 ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
67 width: hasOverflowX ? $.position.scrollbarWidth() : 0,
68 height: hasOverflowY ? $.position.scrollbarWidth() : 0
71 isWindow: function( obj ) {
72 return obj != null && obj === obj.window;
74 getWithinInfo: function( element ) {
75 var withinElement = $( element || window ),
76 isWindow = this.isWindow( withinElement[0] ),
77 isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
78 hasOffset = !isWindow && !isDocument;
80 element: withinElement,
82 offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
83 scrollLeft: withinElement.scrollLeft(),
84 scrollTop: withinElement.scrollTop(),
85 width: isWindow ? withinElement.width() : withinElement.outerWidth(),
86 height: isWindow ? withinElement.height() : withinElement.outerHeight()
91 $.fn.position = function( options ) {
92 if ( !options || !options.of ) {
93 return _position.apply( this, arguments );
96 // make a copy, we don't want to modify arguments
97 options = $.extend( {}, options );
99 var atOffset, targetWidth, targetHeight, targetOffset, basePosition,
100 target = $( options.of ),
101 within = $.position.getWithinInfo( options.within ),
102 scrollInfo = $.position.getScrollInfo( within ),
103 targetElem = target[0],
104 collision = ( options.collision || "flip" ).split( " " ),
107 if ( targetElem.nodeType === 9 ) {
108 targetWidth = target.width();
109 targetHeight = target.height();
110 targetOffset = { top: 0, left: 0 };
111 } else if ( $.position.isWindow( targetElem ) ) {
112 targetWidth = target.width();
113 targetHeight = target.height();
114 targetOffset = { top: target.scrollTop(), left: target.scrollLeft() };
115 } else if ( targetElem.preventDefault ) {
116 // force left top to allow flipping
117 options.at = "left top";
118 targetWidth = targetHeight = 0;
119 targetOffset = { top: targetElem.pageY, left: targetElem.pageX };
121 targetWidth = target.outerWidth();
122 targetHeight = target.outerHeight();
123 targetOffset = target.offset();
125 // clone to reuse original targetOffset later
126 basePosition = $.extend( {}, targetOffset );
128 // force my and at to have valid horizontal and vertical positions
129 // if a value is missing or invalid, it will be converted to center
130 $.each( [ "my", "at" ], function() {
131 var pos = ( options[ this ] || "" ).split( " " ),
135 if ( pos.length === 1) {
136 pos = rhorizontal.test( pos[ 0 ] ) ?
137 pos.concat( [ "center" ] ) :
138 rvertical.test( pos[ 0 ] ) ?
139 [ "center" ].concat( pos ) :
140 [ "center", "center" ];
142 pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
143 pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
146 horizontalOffset = roffset.exec( pos[ 0 ] );
147 verticalOffset = roffset.exec( pos[ 1 ] );
149 horizontalOffset ? horizontalOffset[ 0 ] : 0,
150 verticalOffset ? verticalOffset[ 0 ] : 0
153 // reduce to just the positions without the offsets
155 rposition.exec( pos[ 0 ] )[ 0 ],
156 rposition.exec( pos[ 1 ] )[ 0 ]
160 // normalize collision option
161 if ( collision.length === 1 ) {
162 collision[ 1 ] = collision[ 0 ];
165 if ( options.at[ 0 ] === "right" ) {
166 basePosition.left += targetWidth;
167 } else if ( options.at[ 0 ] === "center" ) {
168 basePosition.left += targetWidth / 2;
171 if ( options.at[ 1 ] === "bottom" ) {
172 basePosition.top += targetHeight;
173 } else if ( options.at[ 1 ] === "center" ) {
174 basePosition.top += targetHeight / 2;
177 atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
178 basePosition.left += atOffset[ 0 ];
179 basePosition.top += atOffset[ 1 ];
181 return this.each(function() {
182 var collisionPosition, using,
184 elemWidth = elem.outerWidth(),
185 elemHeight = elem.outerHeight(),
186 marginLeft = parseCss( this, "marginLeft" ),
187 marginTop = parseCss( this, "marginTop" ),
188 collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
189 collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
190 position = $.extend( {}, basePosition ),
191 myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
193 if ( options.my[ 0 ] === "right" ) {
194 position.left -= elemWidth;
195 } else if ( options.my[ 0 ] === "center" ) {
196 position.left -= elemWidth / 2;
199 if ( options.my[ 1 ] === "bottom" ) {
200 position.top -= elemHeight;
201 } else if ( options.my[ 1 ] === "center" ) {
202 position.top -= elemHeight / 2;
205 position.left += myOffset[ 0 ];
206 position.top += myOffset[ 1 ];
208 // if the browser doesn't support fractions, then round for consistent results
209 if ( !$.support.offsetFractions ) {
210 position.left = round( position.left );
211 position.top = round( position.top );
214 collisionPosition = {
215 marginLeft: marginLeft,
219 $.each( [ "left", "top" ], function( i, dir ) {
220 if ( $.ui.position[ collision[ i ] ] ) {
221 $.ui.position[ collision[ i ] ][ dir ]( position, {
222 targetWidth: targetWidth,
223 targetHeight: targetHeight,
224 elemWidth: elemWidth,
225 elemHeight: elemHeight,
226 collisionPosition: collisionPosition,
227 collisionWidth: collisionWidth,
228 collisionHeight: collisionHeight,
229 offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
238 if ( $.fn.bgiframe ) {
242 if ( options.using ) {
243 // adds feedback as second argument to using callback, if present
244 using = function( props ) {
245 var left = targetOffset.left - position.left,
246 right = left + targetWidth - elemWidth,
247 top = targetOffset.top - position.top,
248 bottom = top + targetHeight - elemHeight,
252 left: targetOffset.left,
253 top: targetOffset.top,
264 horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
265 vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
267 if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
268 feedback.horizontal = "center";
270 if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
271 feedback.vertical = "middle";
273 if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
274 feedback.important = "horizontal";
276 feedback.important = "vertical";
278 options.using.call( this, props, feedback );
282 elem.offset( $.extend( position, { using: using } ) );
288 left: function( position, data ) {
289 var within = data.within,
290 withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
291 outerWidth = within.width,
292 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
293 overLeft = withinOffset - collisionPosLeft,
294 overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
297 // element is wider than within
298 if ( data.collisionWidth > outerWidth ) {
299 // element is initially over the left side of within
300 if ( overLeft > 0 && overRight <= 0 ) {
301 newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
302 position.left += overLeft - newOverRight;
303 // element is initially over right side of within
304 } else if ( overRight > 0 && overLeft <= 0 ) {
305 position.left = withinOffset;
306 // element is initially over both left and right sides of within
308 if ( overLeft > overRight ) {
309 position.left = withinOffset + outerWidth - data.collisionWidth;
311 position.left = withinOffset;
314 // too far left -> align with left edge
315 } else if ( overLeft > 0 ) {
316 position.left += overLeft;
317 // too far right -> align with right edge
318 } else if ( overRight > 0 ) {
319 position.left -= overRight;
320 // adjust based on position and margin
322 position.left = max( position.left - collisionPosLeft, position.left );
325 top: function( position, data ) {
326 var within = data.within,
327 withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
328 outerHeight = data.within.height,
329 collisionPosTop = position.top - data.collisionPosition.marginTop,
330 overTop = withinOffset - collisionPosTop,
331 overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
334 // element is taller than within
335 if ( data.collisionHeight > outerHeight ) {
336 // element is initially over the top of within
337 if ( overTop > 0 && overBottom <= 0 ) {
338 newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
339 position.top += overTop - newOverBottom;
340 // element is initially over bottom of within
341 } else if ( overBottom > 0 && overTop <= 0 ) {
342 position.top = withinOffset;
343 // element is initially over both top and bottom of within
345 if ( overTop > overBottom ) {
346 position.top = withinOffset + outerHeight - data.collisionHeight;
348 position.top = withinOffset;
351 // too far up -> align with top
352 } else if ( overTop > 0 ) {
353 position.top += overTop;
354 // too far down -> align with bottom edge
355 } else if ( overBottom > 0 ) {
356 position.top -= overBottom;
357 // adjust based on position and margin
359 position.top = max( position.top - collisionPosTop, position.top );
364 left: function( position, data ) {
365 var within = data.within,
366 withinOffset = within.offset.left + within.scrollLeft,
367 outerWidth = within.width,
368 offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
369 collisionPosLeft = position.left - data.collisionPosition.marginLeft,
370 overLeft = collisionPosLeft - offsetLeft,
371 overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
372 myOffset = data.my[ 0 ] === "left" ?
374 data.my[ 0 ] === "right" ?
377 atOffset = data.at[ 0 ] === "left" ?
379 data.at[ 0 ] === "right" ?
382 offset = -2 * data.offset[ 0 ],
386 if ( overLeft < 0 ) {
387 newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
388 if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
389 position.left += myOffset + atOffset + offset;
392 else if ( overRight > 0 ) {
393 newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
394 if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
395 position.left += myOffset + atOffset + offset;
399 top: function( position, data ) {
400 var within = data.within,
401 withinOffset = within.offset.top + within.scrollTop,
402 outerHeight = within.height,
403 offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
404 collisionPosTop = position.top - data.collisionPosition.marginTop,
405 overTop = collisionPosTop - offsetTop,
406 overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
407 top = data.my[ 1 ] === "top",
410 data.my[ 1 ] === "bottom" ?
413 atOffset = data.at[ 1 ] === "top" ?
415 data.at[ 1 ] === "bottom" ?
418 offset = -2 * data.offset[ 1 ],
422 newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
423 if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
424 position.top += myOffset + atOffset + offset;
427 else if ( overBottom > 0 ) {
428 newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
429 if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
430 position.top += myOffset + atOffset + offset;
437 $.ui.position.flip.left.apply( this, arguments );
438 $.ui.position.fit.left.apply( this, arguments );
441 $.ui.position.flip.top.apply( this, arguments );
442 $.ui.position.fit.top.apply( this, arguments );
447 // fraction support test
449 var testElement, testElementParent, testElementStyle, offsetLeft, i,
450 body = document.getElementsByTagName( "body" )[ 0 ],
451 div = document.createElement( "div" );
453 //Create a "fake body" for testing based on method used in jQuery.support
454 testElement = document.createElement( body ? "div" : "body" );
456 visibility: "hidden",
464 $.extend( testElementStyle, {
465 position: "absolute",
470 for ( i in testElementStyle ) {
471 testElement.style[ i ] = testElementStyle[ i ];
473 testElement.appendChild( div );
474 testElementParent = body || document.documentElement;
475 testElementParent.insertBefore( testElement, testElementParent.firstChild );
477 div.style.cssText = "position: absolute; left: 10.7432222px;";
479 offsetLeft = $( div ).offset().left;
480 $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
482 testElement.innerHTML = "";
483 testElementParent.removeChild( testElement );
487 if ( $.uiBackCompat !== false ) {
490 var _position = $.fn.position;
491 $.fn.position = function( options ) {
492 if ( !options || !options.offset ) {
493 return _position.call( this, options );
495 var offset = options.offset.split( " " ),
496 at = options.at.split( " " );
497 if ( offset.length === 1 ) {
498 offset[ 1 ] = offset[ 0 ];
500 if ( /^\d/.test( offset[ 0 ] ) ) {
501 offset[ 0 ] = "+" + offset[ 0 ];
503 if ( /^\d/.test( offset[ 1 ] ) ) {
504 offset[ 1 ] = "+" + offset[ 1 ];
506 if ( at.length === 1 ) {
507 if ( /left|center|right/.test( at[ 0 ] ) ) {
514 return _position.call( this, $.extend( options, {
515 at: at[ 0 ] + offset[ 0 ] + " " + at[ 1 ] + offset[ 1 ],