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 getWithinInfo: function( element
) {
72 var withinElement
= $( element
|| window
),
73 isWindow
= $.isWindow( withinElement
[0] );
75 element
: withinElement
,
77 offset
: withinElement
.offset() || { left
: 0, top
: 0 },
78 scrollLeft
: withinElement
.scrollLeft(),
79 scrollTop
: withinElement
.scrollTop(),
80 width
: isWindow
? withinElement
.width() : withinElement
.outerWidth(),
81 height
: isWindow
? withinElement
.height() : withinElement
.outerHeight()
86 $.fn
.position = function( options
) {
87 if ( !options
|| !options
.of ) {
88 return _position
.apply( this, arguments
);
91 // make a copy, we don't want to modify arguments
92 options
= $.extend( {}, options
);
94 var atOffset
, targetWidth
, targetHeight
, targetOffset
, basePosition
,
95 target
= $( options
.of ),
96 within
= $.position
.getWithinInfo( options
.within
),
97 scrollInfo
= $.position
.getScrollInfo( within
),
98 targetElem
= target
[0],
99 collision
= ( options
.collision
|| "flip" ).split( " " ),
102 if ( targetElem
.nodeType
=== 9 ) {
103 targetWidth
= target
.width();
104 targetHeight
= target
.height();
105 targetOffset
= { top
: 0, left
: 0 };
106 } else if ( $.isWindow( targetElem
) ) {
107 targetWidth
= target
.width();
108 targetHeight
= target
.height();
109 targetOffset
= { top
: target
.scrollTop(), left
: target
.scrollLeft() };
110 } else if ( targetElem
.preventDefault
) {
111 // force left top to allow flipping
112 options
.at
= "left top";
113 targetWidth
= targetHeight
= 0;
114 targetOffset
= { top
: targetElem
.pageY
, left
: targetElem
.pageX
};
116 targetWidth
= target
.outerWidth();
117 targetHeight
= target
.outerHeight();
118 targetOffset
= target
.offset();
120 // clone to reuse original targetOffset later
121 basePosition
= $.extend( {}, targetOffset
);
123 // force my and at to have valid horizontal and vertical positions
124 // if a value is missing or invalid, it will be converted to center
125 $.each( [ "my", "at" ], function() {
126 var pos
= ( options
[ this ] || "" ).split( " " ),
130 if ( pos
.length
=== 1) {
131 pos
= rhorizontal
.test( pos
[ 0 ] ) ?
132 pos
.concat( [ "center" ] ) :
133 rvertical
.test( pos
[ 0 ] ) ?
134 [ "center" ].concat( pos
) :
135 [ "center", "center" ];
137 pos
[ 0 ] = rhorizontal
.test( pos
[ 0 ] ) ? pos
[ 0 ] : "center";
138 pos
[ 1 ] = rvertical
.test( pos
[ 1 ] ) ? pos
[ 1 ] : "center";
141 horizontalOffset
= roffset
.exec( pos
[ 0 ] );
142 verticalOffset
= roffset
.exec( pos
[ 1 ] );
144 horizontalOffset
? horizontalOffset
[ 0 ] : 0,
145 verticalOffset
? verticalOffset
[ 0 ] : 0
148 // reduce to just the positions without the offsets
150 rposition
.exec( pos
[ 0 ] )[ 0 ],
151 rposition
.exec( pos
[ 1 ] )[ 0 ]
155 // normalize collision option
156 if ( collision
.length
=== 1 ) {
157 collision
[ 1 ] = collision
[ 0 ];
160 if ( options
.at
[ 0 ] === "right" ) {
161 basePosition
.left
+= targetWidth
;
162 } else if ( options
.at
[ 0 ] === "center" ) {
163 basePosition
.left
+= targetWidth
/ 2;
166 if ( options
.at
[ 1 ] === "bottom" ) {
167 basePosition
.top
+= targetHeight
;
168 } else if ( options
.at
[ 1 ] === "center" ) {
169 basePosition
.top
+= targetHeight
/ 2;
172 atOffset
= getOffsets( offsets
.at
, targetWidth
, targetHeight
);
173 basePosition
.left
+= atOffset
[ 0 ];
174 basePosition
.top
+= atOffset
[ 1 ];
176 return this.each(function() {
177 var collisionPosition
, using
,
179 elemWidth
= elem
.outerWidth(),
180 elemHeight
= elem
.outerHeight(),
181 marginLeft
= parseCss( this, "marginLeft" ),
182 marginTop
= parseCss( this, "marginTop" ),
183 collisionWidth
= elemWidth
+ marginLeft
+ parseCss( this, "marginRight" ) + scrollInfo
.width
,
184 collisionHeight
= elemHeight
+ marginTop
+ parseCss( this, "marginBottom" ) + scrollInfo
.height
,
185 position
= $.extend( {}, basePosition
),
186 myOffset
= getOffsets( offsets
.my
, elem
.outerWidth(), elem
.outerHeight() );
188 if ( options
.my
[ 0 ] === "right" ) {
189 position
.left
-= elemWidth
;
190 } else if ( options
.my
[ 0 ] === "center" ) {
191 position
.left
-= elemWidth
/ 2;
194 if ( options
.my
[ 1 ] === "bottom" ) {
195 position
.top
-= elemHeight
;
196 } else if ( options
.my
[ 1 ] === "center" ) {
197 position
.top
-= elemHeight
/ 2;
200 position
.left
+= myOffset
[ 0 ];
201 position
.top
+= myOffset
[ 1 ];
203 // if the browser doesn't support fractions, then round for consistent results
204 if ( !$.support
.offsetFractions
) {
205 position
.left
= round( position
.left
);
206 position
.top
= round( position
.top
);
209 collisionPosition
= {
210 marginLeft
: marginLeft
,
214 $.each( [ "left", "top" ], function( i
, dir
) {
215 if ( $.ui
.position
[ collision
[ i
] ] ) {
216 $.ui
.position
[ collision
[ i
] ][ dir
]( position
, {
217 targetWidth
: targetWidth
,
218 targetHeight
: targetHeight
,
219 elemWidth
: elemWidth
,
220 elemHeight
: elemHeight
,
221 collisionPosition
: collisionPosition
,
222 collisionWidth
: collisionWidth
,
223 collisionHeight
: collisionHeight
,
224 offset
: [ atOffset
[ 0 ] + myOffset
[ 0 ], atOffset
[ 1 ] + myOffset
[ 1 ] ],
233 if ( $.fn
.bgiframe
) {
237 if ( options
.using
) {
238 // adds feedback as second argument to using callback, if present
239 using = function( props
) {
240 var left
= targetOffset
.left
- position
.left
,
241 right
= left
+ targetWidth
- elemWidth
,
242 top
= targetOffset
.top
- position
.top
,
243 bottom
= top
+ targetHeight
- elemHeight
,
247 left
: targetOffset
.left
,
248 top
: targetOffset
.top
,
259 horizontal
: right
< 0 ? "left" : left
> 0 ? "right" : "center",
260 vertical
: bottom
< 0 ? "top" : top
> 0 ? "bottom" : "middle"
262 if ( targetWidth
< elemWidth
&& abs( left
+ right
) < targetWidth
) {
263 feedback
.horizontal
= "center";
265 if ( targetHeight
< elemHeight
&& abs( top
+ bottom
) < targetHeight
) {
266 feedback
.vertical
= "middle";
268 if ( max( abs( left
), abs( right
) ) > max( abs( top
), abs( bottom
) ) ) {
269 feedback
.important
= "horizontal";
271 feedback
.important
= "vertical";
273 options
.using
.call( this, props
, feedback
);
277 elem
.offset( $.extend( position
, { using
: using
} ) );
283 left: function( position
, data
) {
284 var within
= data
.within
,
285 withinOffset
= within
.isWindow
? within
.scrollLeft
: within
.offset
.left
,
286 outerWidth
= within
.width
,
287 collisionPosLeft
= position
.left
- data
.collisionPosition
.marginLeft
,
288 overLeft
= withinOffset
- collisionPosLeft
,
289 overRight
= collisionPosLeft
+ data
.collisionWidth
- outerWidth
- withinOffset
,
292 // element is wider than within
293 if ( data
.collisionWidth
> outerWidth
) {
294 // element is initially over the left side of within
295 if ( overLeft
> 0 && overRight
<= 0 ) {
296 newOverRight
= position
.left
+ overLeft
+ data
.collisionWidth
- outerWidth
- withinOffset
;
297 position
.left
+= overLeft
- newOverRight
;
298 // element is initially over right side of within
299 } else if ( overRight
> 0 && overLeft
<= 0 ) {
300 position
.left
= withinOffset
;
301 // element is initially over both left and right sides of within
303 if ( overLeft
> overRight
) {
304 position
.left
= withinOffset
+ outerWidth
- data
.collisionWidth
;
306 position
.left
= withinOffset
;
309 // too far left -> align with left edge
310 } else if ( overLeft
> 0 ) {
311 position
.left
+= overLeft
;
312 // too far right -> align with right edge
313 } else if ( overRight
> 0 ) {
314 position
.left
-= overRight
;
315 // adjust based on position and margin
317 position
.left
= max( position
.left
- collisionPosLeft
, position
.left
);
320 top: function( position
, data
) {
321 var within
= data
.within
,
322 withinOffset
= within
.isWindow
? within
.scrollTop
: within
.offset
.top
,
323 outerHeight
= data
.within
.height
,
324 collisionPosTop
= position
.top
- data
.collisionPosition
.marginTop
,
325 overTop
= withinOffset
- collisionPosTop
,
326 overBottom
= collisionPosTop
+ data
.collisionHeight
- outerHeight
- withinOffset
,
329 // element is taller than within
330 if ( data
.collisionHeight
> outerHeight
) {
331 // element is initially over the top of within
332 if ( overTop
> 0 && overBottom
<= 0 ) {
333 newOverBottom
= position
.top
+ overTop
+ data
.collisionHeight
- outerHeight
- withinOffset
;
334 position
.top
+= overTop
- newOverBottom
;
335 // element is initially over bottom of within
336 } else if ( overBottom
> 0 && overTop
<= 0 ) {
337 position
.top
= withinOffset
;
338 // element is initially over both top and bottom of within
340 if ( overTop
> overBottom
) {
341 position
.top
= withinOffset
+ outerHeight
- data
.collisionHeight
;
343 position
.top
= withinOffset
;
346 // too far up -> align with top
347 } else if ( overTop
> 0 ) {
348 position
.top
+= overTop
;
349 // too far down -> align with bottom edge
350 } else if ( overBottom
> 0 ) {
351 position
.top
-= overBottom
;
352 // adjust based on position and margin
354 position
.top
= max( position
.top
- collisionPosTop
, position
.top
);
359 left: function( position
, data
) {
360 var within
= data
.within
,
361 withinOffset
= within
.offset
.left
+ within
.scrollLeft
,
362 outerWidth
= within
.width
,
363 offsetLeft
= within
.isWindow
? within
.scrollLeft
: within
.offset
.left
,
364 collisionPosLeft
= position
.left
- data
.collisionPosition
.marginLeft
,
365 overLeft
= collisionPosLeft
- offsetLeft
,
366 overRight
= collisionPosLeft
+ data
.collisionWidth
- outerWidth
- offsetLeft
,
367 myOffset
= data
.my
[ 0 ] === "left" ?
369 data
.my
[ 0 ] === "right" ?
372 atOffset
= data
.at
[ 0 ] === "left" ?
374 data
.at
[ 0 ] === "right" ?
377 offset
= -2 * data
.offset
[ 0 ],
381 if ( overLeft
< 0 ) {
382 newOverRight
= position
.left
+ myOffset
+ atOffset
+ offset
+ data
.collisionWidth
- outerWidth
- withinOffset
;
383 if ( newOverRight
< 0 || newOverRight
< abs( overLeft
) ) {
384 position
.left
+= myOffset
+ atOffset
+ offset
;
387 else if ( overRight
> 0 ) {
388 newOverLeft
= position
.left
- data
.collisionPosition
.marginLeft
+ myOffset
+ atOffset
+ offset
- offsetLeft
;
389 if ( newOverLeft
> 0 || abs( newOverLeft
) < overRight
) {
390 position
.left
+= myOffset
+ atOffset
+ offset
;
394 top: function( position
, data
) {
395 var within
= data
.within
,
396 withinOffset
= within
.offset
.top
+ within
.scrollTop
,
397 outerHeight
= within
.height
,
398 offsetTop
= within
.isWindow
? within
.scrollTop
: within
.offset
.top
,
399 collisionPosTop
= position
.top
- data
.collisionPosition
.marginTop
,
400 overTop
= collisionPosTop
- offsetTop
,
401 overBottom
= collisionPosTop
+ data
.collisionHeight
- outerHeight
- offsetTop
,
402 top
= data
.my
[ 1 ] === "top",
405 data
.my
[ 1 ] === "bottom" ?
408 atOffset
= data
.at
[ 1 ] === "top" ?
410 data
.at
[ 1 ] === "bottom" ?
413 offset
= -2 * data
.offset
[ 1 ],
417 newOverBottom
= position
.top
+ myOffset
+ atOffset
+ offset
+ data
.collisionHeight
- outerHeight
- withinOffset
;
418 if ( ( position
.top
+ myOffset
+ atOffset
+ offset
) > overTop
&& ( newOverBottom
< 0 || newOverBottom
< abs( overTop
) ) ) {
419 position
.top
+= myOffset
+ atOffset
+ offset
;
422 else if ( overBottom
> 0 ) {
423 newOverTop
= position
.top
- data
.collisionPosition
.marginTop
+ myOffset
+ atOffset
+ offset
- offsetTop
;
424 if ( ( position
.top
+ myOffset
+ atOffset
+ offset
) > overBottom
&& ( newOverTop
> 0 || abs( newOverTop
) < overBottom
) ) {
425 position
.top
+= myOffset
+ atOffset
+ offset
;
432 $.ui
.position
.flip
.left
.apply( this, arguments
);
433 $.ui
.position
.fit
.left
.apply( this, arguments
);
436 $.ui
.position
.flip
.top
.apply( this, arguments
);
437 $.ui
.position
.fit
.top
.apply( this, arguments
);
442 // fraction support test
444 var testElement
, testElementParent
, testElementStyle
, offsetLeft
, i
,
445 body
= document
.getElementsByTagName( "body" )[ 0 ],
446 div
= document
.createElement( "div" );
448 //Create a "fake body" for testing based on method used in jQuery.support
449 testElement
= document
.createElement( body
? "div" : "body" );
451 visibility
: "hidden",
459 $.extend( testElementStyle
, {
460 position
: "absolute",
465 for ( i
in testElementStyle
) {
466 testElement
.style
[ i
] = testElementStyle
[ i
];
468 testElement
.appendChild( div
);
469 testElementParent
= body
|| document
.documentElement
;
470 testElementParent
.insertBefore( testElement
, testElementParent
.firstChild
);
472 div
.style
.cssText
= "position: absolute; left: 10.7432222px;";
474 offsetLeft
= $( div
).offset().left
;
475 $.support
.offsetFractions
= offsetLeft
> 10 && offsetLeft
< 11;
477 testElement
.innerHTML
= "";
478 testElementParent
.removeChild( testElement
);
482 if ( $.uiBackCompat
!== false ) {
485 var _position
= $.fn
.position
;
486 $.fn
.position = function( options
) {
487 if ( !options
|| !options
.offset
) {
488 return _position
.call( this, options
);
490 var offset
= options
.offset
.split( " " ),
491 at
= options
.at
.split( " " );
492 if ( offset
.length
=== 1 ) {
493 offset
[ 1 ] = offset
[ 0 ];
495 if ( /^\d/.test( offset
[ 0 ] ) ) {
496 offset
[ 0 ] = "+" + offset
[ 0 ];
498 if ( /^\d/.test( offset
[ 1 ] ) ) {
499 offset
[ 1 ] = "+" + offset
[ 1 ];
501 if ( at
.length
=== 1 ) {
502 if ( /left|center|right/.test( at
[ 0 ] ) ) {
509 return _position
.call( this, $.extend( options
, {
510 at
: at
[ 0 ] + offset
[ 0 ] + " " + at
[ 1 ] + offset
[ 1 ],