jquery-ui/ui/position.js

504 lines
15 KiB
JavaScript
Raw Normal View History

/*!
* jQuery UI Position @VERSION
2012-07-04 13:08:08 +00:00
* http://jqueryui.com
*
* Copyright jQuery Foundation and other contributors
2012-08-09 14:13:24 +00:00
* Released under the MIT license.
* http://jquery.org/license
*
2012-09-26 23:06:20 +00:00
* http://api.jqueryui.com/position/
*/
//>>label: Position
//>>group: UI Core
//>>description: Positions elements relative to other elements.
//>>docs: http://api.jqueryui.com/position/
//>>demos: http://jqueryui.com/position/
( function( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( [ "jquery" ], factory );
} else {
// Browser globals
factory( jQuery );
}
}( function( $ ) {
( function() {
$.ui = $.ui || {};
var cachedScrollbarWidth, supportsOffsetFractions,
max = Math.max,
abs = Math.abs,
round = Math.round,
rhorizontal = /left|center|right/,
2011-03-22 17:12:03 +00:00
rvertical = /top|center|bottom/,
roffset = /[\+\-]\d+(\.[\d]+)?%?/,
rposition = /^\w+/,
rpercent = /%$/,
_position = $.fn.position;
// Support: IE <=9 only
supportsOffsetFractions = function() {
var element = $( "<div>" )
.css( "position", "absolute" )
.appendTo( "body" )
.offset( {
top: 1.5,
left: 1.5
} ),
support = element.offset().top === 1.5;
element.remove();
supportsOffsetFractions = function() {
return support;
};
return support;
};
function getOffsets( offsets, width, height ) {
return [
parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
];
}
2012-12-06 20:00:42 +00:00
function parseCss( element, property ) {
return parseInt( $.css( element, property ), 10 ) || 0;
}
2012-12-06 20:00:42 +00:00
function getDimensions( elem ) {
var raw = elem[ 0 ];
2012-12-06 20:00:42 +00:00
if ( raw.nodeType === 9 ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: 0, left: 0 }
};
}
if ( $.isWindow( raw ) ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
};
}
if ( raw.preventDefault ) {
return {
width: 0,
height: 0,
offset: { top: raw.pageY, left: raw.pageX }
};
}
return {
width: elem.outerWidth(),
height: elem.outerHeight(),
offset: elem.offset()
};
}
$.position = {
2011-06-10 20:07:03 +00:00
scrollbarWidth: function() {
if ( cachedScrollbarWidth !== undefined ) {
return cachedScrollbarWidth;
}
2011-06-10 20:07:03 +00:00
var w1, w2,
div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
innerDiv = div.children()[ 0 ];
2011-06-10 20:07:03 +00:00
$( "body" ).append( div );
w1 = innerDiv.offsetWidth;
div.css( "overflow", "scroll" );
w2 = innerDiv.offsetWidth;
if ( w1 === w2 ) {
w2 = div[ 0 ].clientWidth;
}
2011-06-10 20:07:03 +00:00
div.remove();
return ( cachedScrollbarWidth = w1 - w2 );
},
getScrollInfo: function( within ) {
var overflowX = within.isWindow || within.isDocument ? "" :
within.element.css( "overflow-x" ),
overflowY = within.isWindow || within.isDocument ? "" :
within.element.css( "overflow-y" ),
hasOverflowX = overflowX === "scroll" ||
( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
hasOverflowY = overflowY === "scroll" ||
( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
2011-06-10 20:07:03 +00:00
return {
width: hasOverflowY ? $.position.scrollbarWidth() : 0,
height: hasOverflowX ? $.position.scrollbarWidth() : 0
};
},
getWithinInfo: function( element ) {
var withinElement = $( element || window ),
isWindow = $.isWindow( withinElement[ 0 ] ),
isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
hasOffset = !isWindow && !isDocument;
return {
element: withinElement,
isWindow: isWindow,
isDocument: isDocument,
offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: withinElement.outerWidth(),
height: withinElement.outerHeight()
2011-06-10 20:07:03 +00:00
};
}
};
2010-02-14 18:15:32 +00:00
$.fn.position = function( options ) {
if ( !options || !options.of ) {
return _position.apply( this, arguments );
}
2010-02-14 18:15:32 +00:00
// make a copy, we don't want to modify arguments
2010-02-14 18:15:32 +00:00
options = $.extend( {}, options );
2012-12-06 20:00:42 +00:00
var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
target = $( options.of ),
within = $.position.getWithinInfo( options.within ),
scrollInfo = $.position.getScrollInfo( within ),
2010-02-14 18:15:32 +00:00
collision = ( options.collision || "flip" ).split( " " ),
offsets = {};
2012-12-06 20:00:42 +00:00
dimensions = getDimensions( target );
if ( target[ 0 ].preventDefault ) {
// force left top to allow flipping
2010-02-14 18:15:32 +00:00
options.at = "left top";
}
2012-12-06 20:00:42 +00:00
targetWidth = dimensions.width;
targetHeight = dimensions.height;
targetOffset = dimensions.offset;
// clone to reuse original targetOffset later
basePosition = $.extend( {}, targetOffset );
2011-03-21 18:02:00 +00:00
// force my and at to have valid horizontal and vertical positions
2011-06-10 20:07:03 +00:00
// if a value is missing or invalid, it will be converted to center
2010-02-14 18:15:32 +00:00
$.each( [ "my", "at" ], function() {
2011-03-22 17:12:03 +00:00
var pos = ( options[ this ] || "" ).split( " " ),
horizontalOffset,
verticalOffset;
if ( pos.length === 1 ) {
2011-03-22 17:12:03 +00:00
pos = rhorizontal.test( pos[ 0 ] ) ?
pos.concat( [ "center" ] ) :
2011-03-22 17:12:03 +00:00
rvertical.test( pos[ 0 ] ) ?
[ "center" ].concat( pos ) :
[ "center", "center" ];
2010-02-14 18:15:32 +00:00
}
pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
// calculate offsets
horizontalOffset = roffset.exec( pos[ 0 ] );
2011-03-22 17:12:03 +00:00
verticalOffset = roffset.exec( pos[ 1 ] );
offsets[ this ] = [
horizontalOffset ? horizontalOffset[ 0 ] : 0,
verticalOffset ? verticalOffset[ 0 ] : 0
];
// reduce to just the positions without the offsets
options[ this ] = [
rposition.exec( pos[ 0 ] )[ 0 ],
rposition.exec( pos[ 1 ] )[ 0 ]
];
} );
// normalize collision option
2010-02-14 18:15:32 +00:00
if ( collision.length === 1 ) {
collision[ 1 ] = collision[ 0 ];
}
2011-03-22 17:12:03 +00:00
if ( options.at[ 0 ] === "right" ) {
2010-02-14 18:15:32 +00:00
basePosition.left += targetWidth;
} else if ( options.at[ 0 ] === "center" ) {
2010-02-14 18:15:32 +00:00
basePosition.left += targetWidth / 2;
}
2011-03-22 17:12:03 +00:00
if ( options.at[ 1 ] === "bottom" ) {
2010-02-14 18:15:32 +00:00
basePosition.top += targetHeight;
} else if ( options.at[ 1 ] === "center" ) {
2010-02-14 18:15:32 +00:00
basePosition.top += targetHeight / 2;
}
atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
basePosition.left += atOffset[ 0 ];
basePosition.top += atOffset[ 1 ];
return this.each( function() {
var collisionPosition, using,
elem = $( this ),
elemWidth = elem.outerWidth(),
elemHeight = elem.outerHeight(),
marginLeft = parseCss( this, "marginLeft" ),
marginTop = parseCss( this, "marginTop" ),
collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
position = $.extend( {}, basePosition ),
myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
2011-03-22 17:12:03 +00:00
if ( options.my[ 0 ] === "right" ) {
2010-02-14 18:15:32 +00:00
position.left -= elemWidth;
} else if ( options.my[ 0 ] === "center" ) {
2010-02-14 18:15:32 +00:00
position.left -= elemWidth / 2;
}
2011-03-22 17:12:03 +00:00
if ( options.my[ 1 ] === "bottom" ) {
2010-02-14 18:15:32 +00:00
position.top -= elemHeight;
} else if ( options.my[ 1 ] === "center" ) {
2010-02-14 18:15:32 +00:00
position.top -= elemHeight / 2;
}
position.left += myOffset[ 0 ];
position.top += myOffset[ 1 ];
2011-11-02 15:09:00 +00:00
// if the browser doesn't support fractions, then round for consistent results
if ( !supportsOffsetFractions() ) {
position.left = round( position.left );
position.top = round( position.top );
2011-11-02 15:09:00 +00:00
}
collisionPosition = {
marginLeft: marginLeft,
marginTop: marginTop
};
2010-02-14 18:15:32 +00:00
$.each( [ "left", "top" ], function( i, dir ) {
2011-03-22 17:12:03 +00:00
if ( $.ui.position[ collision[ i ] ] ) {
$.ui.position[ collision[ i ] ][ dir ]( position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
my: options.my,
at: options.at,
within: within,
elem: elem
} );
2010-02-14 18:15:32 +00:00
}
} );
if ( options.using ) {
// adds feedback as second argument to using callback, if present
using = function( props ) {
var left = targetOffset.left - position.left,
right = left + targetWidth - elemWidth,
top = targetOffset.top - position.top,
bottom = top + targetHeight - elemHeight,
feedback = {
target: {
element: target,
left: targetOffset.left,
top: targetOffset.top,
width: targetWidth,
height: targetHeight
},
element: {
element: elem,
left: position.left,
top: position.top,
width: elemWidth,
height: elemHeight
},
horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
};
if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
feedback.horizontal = "center";
}
if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
feedback.vertical = "middle";
}
if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
feedback.important = "horizontal";
} else {
feedback.important = "vertical";
}
options.using.call( this, props, feedback );
};
}
elem.offset( $.extend( position, { using: using } ) );
} );
};
$.ui.position = {
fit: {
2010-02-14 18:15:32 +00:00
left: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
outerWidth = within.width,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = withinOffset - collisionPosLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
2012-04-28 21:36:38 +00:00
newOverRight;
// element is wider than within
if ( data.collisionWidth > outerWidth ) {
// element is initially over the left side of within
if ( overLeft > 0 && overRight <= 0 ) {
newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
position.left += overLeft - newOverRight;
// element is initially over right side of within
} else if ( overRight > 0 && overLeft <= 0 ) {
position.left = withinOffset;
// element is initially over both left and right sides of within
} else {
if ( overLeft > overRight ) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
}
// too far left -> align with left edge
} else if ( overLeft > 0 ) {
position.left += overLeft;
// too far right -> align with right edge
} else if ( overRight > 0 ) {
position.left -= overRight;
// adjust based on position and margin
} else {
position.left = max( position.left - collisionPosLeft, position.left );
}
},
2010-02-14 18:15:32 +00:00
top: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
outerHeight = data.within.height,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = withinOffset - collisionPosTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
newOverBottom;
// element is taller than within
if ( data.collisionHeight > outerHeight ) {
// element is initially over the top of within
if ( overTop > 0 && overBottom <= 0 ) {
newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
position.top += overTop - newOverBottom;
// element is initially over bottom of within
} else if ( overBottom > 0 && overTop <= 0 ) {
position.top = withinOffset;
// element is initially over both top and bottom of within
} else {
if ( overTop > overBottom ) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
}
// too far up -> align with top
} else if ( overTop > 0 ) {
position.top += overTop;
// too far down -> align with bottom edge
} else if ( overBottom > 0 ) {
position.top -= overBottom;
// adjust based on position and margin
} else {
position.top = max( position.top - collisionPosTop, position.top );
}
}
},
flip: {
2010-02-14 18:15:32 +00:00
left: function( position, data ) {
var within = data.within,
withinOffset = within.offset.left + within.scrollLeft,
outerWidth = within.width,
offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = collisionPosLeft - offsetLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
2010-02-14 18:15:32 +00:00
myOffset = data.my[ 0 ] === "left" ?
-data.elemWidth :
data.my[ 0 ] === "right" ?
data.elemWidth :
0,
atOffset = data.at[ 0 ] === "left" ?
data.targetWidth :
data.at[ 0 ] === "right" ?
-data.targetWidth :
0,
offset = -2 * data.offset[ 0 ],
newOverRight,
newOverLeft;
if ( overLeft < 0 ) {
newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
position.left += myOffset + atOffset + offset;
}
2013-10-16 18:43:09 +00:00
} else if ( overRight > 0 ) {
newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
position.left += myOffset + atOffset + offset;
}
}
},
2010-02-14 18:15:32 +00:00
top: function( position, data ) {
var within = data.within,
withinOffset = within.offset.top + within.scrollTop,
outerHeight = within.height,
offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = collisionPosTop - offsetTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
top = data.my[ 1 ] === "top",
myOffset = top ?
2010-02-14 18:15:32 +00:00
-data.elemHeight :
data.my[ 1 ] === "bottom" ?
data.elemHeight :
0,
atOffset = data.at[ 1 ] === "top" ?
data.targetHeight :
data.at[ 1 ] === "bottom" ?
-data.targetHeight :
0,
offset = -2 * data.offset[ 1 ],
newOverTop,
newOverBottom;
if ( overTop < 0 ) {
newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
position.top += myOffset + atOffset + offset;
}
2013-10-16 18:43:09 +00:00
} else if ( overBottom > 0 ) {
newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
position.top += myOffset + atOffset + offset;
}
}
}
},
flipfit: {
left: function() {
$.ui.position.flip.left.apply( this, arguments );
$.ui.position.fit.left.apply( this, arguments );
},
top: function() {
$.ui.position.flip.top.apply( this, arguments );
$.ui.position.fit.top.apply( this, arguments );
}
}
};
} )();
return $.ui.position;
} ) );