2
* jQuery UI Position 1.11.4
5
* Copyright 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 ( 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 ( 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;