mirror of
https://github.com/jquery/jquery-ui.git
synced 2024-11-21 11:04:24 +00:00
1337573adc
Draggable: Fixed when cssPosition is being set. Scrolling handles non-doc overflow. _handleScrolling handles scrolling up and left now Made window, doc, and window part of base widget All parts of drag may be stopped via stopPropagation User can now override position of draggable on start, drag, and stop Whitespace fix Moved overflowWidth and overflowHeight into overflow.width and overflow.height
304 lines
8.6 KiB
JavaScript
304 lines
8.6 KiB
JavaScript
/*
|
|
* jQuery UI Draggable @VERSION
|
|
*
|
|
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
* http://jquery.org/license
|
|
*
|
|
* http://docs.jquery.com/UI/Draggable
|
|
*
|
|
* Depends:
|
|
* jquery.ui.core.js
|
|
* jquery.ui.widget.js
|
|
*/
|
|
(function( $, undefined ) {
|
|
|
|
$.widget( "ui.draggable", {
|
|
version: "@VERSION",
|
|
widgetEventPrefix: "drag",
|
|
|
|
options: {
|
|
helper: false,
|
|
scrollSensitivity: 20,
|
|
scrollSpeed: 20
|
|
},
|
|
|
|
// dragEl: element being dragged (original or helper)
|
|
// position: final CSS position of dragEl
|
|
// offset: offset of dragEl
|
|
// startCoords: clientX/Y of the mousedown (offset of pointer)
|
|
// startPosition: CSS position prior to drag start
|
|
// startOffset: offset prior to drag start
|
|
// tempPosition: overridable CSS position of dragEl
|
|
// overflowOffset: offset of scroll parent
|
|
// overflow: object containing width and height keys of scroll parent
|
|
|
|
_create: function() {
|
|
this.scrollParent = this.element.scrollParent();
|
|
|
|
// Static position elements can't be moved with top/left
|
|
if ( this.element.css( "position" ) === "static" ) {
|
|
this.element.css( "position", "relative" );
|
|
}
|
|
|
|
this._bind({ mousedown: "_mouseDown" });
|
|
},
|
|
|
|
// TODO: why is relative handled differently than fixed/absolute?
|
|
_getPosition: function() {
|
|
var left, top, position,
|
|
scrollTop = this.scrollParent.scrollTop(),
|
|
scrollLeft = this.scrollParent.scrollLeft();
|
|
|
|
// If fixed or absolute
|
|
if ( this.cssPosition !== "relative" ) {
|
|
position = this.dragEl.position();
|
|
|
|
// Take into account scrollbar
|
|
position.top -= scrollTop;
|
|
position.left -= scrollLeft
|
|
|
|
return position;
|
|
}
|
|
|
|
// When using relative, css values are checked
|
|
left = this.dragEl.css( "left" );
|
|
top = this.dragEl.css( "top" );
|
|
|
|
// Webkit will give back auto if there is nothing inline yet
|
|
left = ( left === "auto" ) ? 0: parseInt( left, 10 );
|
|
top = ( top === "auto" ) ? 0: parseInt( top, 10 );
|
|
|
|
return {
|
|
left: left - scrollLeft,
|
|
top: top - scrollTop
|
|
};
|
|
},
|
|
|
|
_handleScrolling: function( event ) {
|
|
// TODO: what is expected behavior of absolute/fixed draggable inside a div having overflow:scroll?
|
|
var scrollTop = this.scrollParent.scrollTop(),
|
|
scrollLeft = this.scrollParent.scrollLeft();
|
|
|
|
// overflowOffset is only set when scrollParent is not doc/html
|
|
if ( !this.overflowOffset ) {
|
|
|
|
// Handle vertical scrolling
|
|
if ( ( ( this.overflow.height + scrollTop ) - event.pageY ) < this.options.scrollSensitivity ) {
|
|
this.scrollParent.scrollTop( scrollTop + this.options.scrollSpeed );
|
|
}
|
|
else if ( event.pageY < ( scrollTop + this.options.scrollSensitivity ) ) {
|
|
this.scrollParent.scrollTop( scrollTop - this.options.scrollSpeed );
|
|
}
|
|
|
|
// Handle horizontal scrolling
|
|
if ( ( ( this.overflow.width + scrollLeft ) - event.pageX ) < this.options.scrollSensitivity ) {
|
|
this.scrollParent.scrollLeft( scrollLeft + this.options.scrollSpeed );
|
|
}
|
|
else if ( event.pageX < ( scrollLeft + this.options.scrollSensitivity ) ) {
|
|
this.scrollParent.scrollLeft( scrollLeft - this.options.scrollSpeed );
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
// Handle vertical scrolling
|
|
if ( ( event.pageY + this.options.scrollSensitivity ) > ( this.overflow.height + this.overflowOffset.top ) ) {
|
|
this.scrollParent.scrollTop( scrollTop + this.options.scrollSpeed );
|
|
}
|
|
else if ( ( event.pageY - this.options.scrollSensitivity ) < this.overflowOffset.top ) {
|
|
this.scrollParent.scrollTop( scrollTop - this.options.scrollSpeed );
|
|
}
|
|
|
|
// Handle horizontal scrolling
|
|
if ( ( event.pageX + this.options.scrollSensitivity ) > ( this.overflow.width + this.overflowOffset.left ) ) {
|
|
this.scrollParent.scrollLeft( scrollLeft + this.options.scrollSpeed );
|
|
}
|
|
else if ( ( event.pageX - this.options.scrollSensitivity ) < this.overflowOffset.left ) {
|
|
this.scrollParent.scrollLeft( scrollLeft - this.options.scrollSpeed );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_mouseDown: function( event ) {
|
|
var newLeft, newTop;
|
|
|
|
// Prevent text selection, among other things
|
|
event.preventDefault();
|
|
|
|
// The actual dragging element, should always be a jQuery object
|
|
this.dragEl = this.element;
|
|
|
|
// Helper required
|
|
if ( this.options.helper ) {
|
|
// clone
|
|
if ( this.options.helper === true ) {
|
|
this.dragEl = this.element.clone()
|
|
.removeAttr( "id" )
|
|
.find( "[id]" )
|
|
.removeAttr( "id" )
|
|
.end();
|
|
} else {
|
|
// TODO: figure out the signature for this; see #4957
|
|
this.dragEl = $( this.options.helper() );
|
|
}
|
|
|
|
this.dragEl
|
|
// TODO: should we move this to the stylesheet and use a class?
|
|
.css( "position", "absolute" )
|
|
.appendTo( this.doc[0].body )
|
|
.offset( this.element.offset() );
|
|
}
|
|
|
|
this.cssPosition = this.dragEl.css( "position" );
|
|
|
|
// Cache starting absolute and relative positions
|
|
this.startPosition = this._getPosition();
|
|
this.startOffset = this.dragEl.offset();
|
|
|
|
// Cache current position and offset
|
|
this.position = $.extend( {}, this.startPosition );
|
|
this.offset = $.extend( {}, this.startOffset );
|
|
|
|
this.startCoords = {
|
|
left: event.clientX,
|
|
top: event.clientY
|
|
};
|
|
|
|
// Cache the offset of scrollParent, if required for _handleScrolling
|
|
if ( this.scrollParent[0] != this.doc[0] && this.scrollParent[0].tagName != 'HTML') {
|
|
this.overflowOffset = this.scrollParent.offset();
|
|
}
|
|
|
|
this.overflow = {};
|
|
|
|
this.overflow.height = ( this.scrollParent[0] === this.doc[0] ) ?
|
|
this.win.height() : this.scrollParent.height();
|
|
|
|
this.overflow.width = ( this.scrollParent[0] === this.doc[0] ) ?
|
|
this.win.width() : this.scrollParent.width();
|
|
|
|
this._preparePosition( event );
|
|
|
|
this._trigger( "start", event, this._uiHash() );
|
|
|
|
// TODO: should user be able to change position of draggable, if event stopped?
|
|
// If user stops propagation, leave helper there ( if there's one ), disallow any CSS changes
|
|
if ( event.cancelBubble === true ) {
|
|
this.doc.unbind( "." + this.widgetName );
|
|
return;
|
|
}
|
|
|
|
this._setCss( event );
|
|
|
|
this._bind( this.doc, {
|
|
mousemove: "_mouseMove",
|
|
mouseup: "_mouseUp"
|
|
});
|
|
},
|
|
|
|
_mouseMove: function( event ) {
|
|
var newLeft, newTop;
|
|
|
|
this._preparePosition( event );
|
|
|
|
this._trigger( "drag", event, this._uiHash() );
|
|
|
|
// TODO: should user be able to change position of draggable, if event stopped?
|
|
// If user stops propagation, leave helper there ( if there's one ), disallow any CSS changes
|
|
if ( event.cancelBubble === true ) {
|
|
this.doc.unbind( "." + this.widgetName );
|
|
return;
|
|
}
|
|
|
|
this._setCss( event );
|
|
|
|
// Scroll the scrollParent, if needed
|
|
this._handleScrolling( event );
|
|
},
|
|
|
|
_mouseUp: function( event ) {
|
|
|
|
this._preparePosition( event );
|
|
|
|
this._trigger( "stop", event, this._uiHash() );
|
|
|
|
// TODO: should user be able to change position of draggable, if event stopped?
|
|
// If user stops propagation, leave helper there, disallow any CSS changes
|
|
if ( event.cancelBubble !== true ) {
|
|
|
|
this._setCss( event );
|
|
|
|
if ( this.options.helper ) {
|
|
this.dragEl.remove();
|
|
}
|
|
|
|
}
|
|
|
|
this.doc.unbind( "." + this.widgetName );
|
|
},
|
|
|
|
// Uses event to determine new position of draggable, before any override from callbacks
|
|
_preparePosition: function( event ) {
|
|
var leftDiff = event.clientX - this.startCoords.left,
|
|
topDiff = event.clientY - this.startCoords.top,
|
|
newLeft = leftDiff + this.startPosition.left,
|
|
newTop = topDiff + this.startPosition.top;
|
|
|
|
// Save off new values for .css() in various callbacks using this function
|
|
this.position = {
|
|
left: newLeft,
|
|
top: newTop
|
|
};
|
|
|
|
// Save off values to compare user override against automatic coordinates
|
|
this.tempPosition = {
|
|
left: newLeft,
|
|
top: newTop
|
|
}
|
|
|
|
// Refresh offset cache with new positions
|
|
this.offset.left = this.startOffset.left + newLeft;
|
|
this.offset.top = this.startOffset.top + newTop;
|
|
},
|
|
|
|
// Places draggable where mouse or user from callback indicates
|
|
_setCss: function( event ) {
|
|
var newLeft, newTop;
|
|
|
|
// User overriding left/top so shortcut math is no longer valid
|
|
if ( this.tempPosition.left !== this.position.left || this.tempPosition.top !== this.position.top ) {
|
|
// TODO: can we just store the previous offset values
|
|
// and not go through .offset()?
|
|
// refresh offset using slower functions
|
|
this.offset = this.dragEl.offset();
|
|
}
|
|
|
|
newLeft = this.position.left;
|
|
newTop = this.position.top;
|
|
|
|
// TODO: does this work with nested scrollable parents?
|
|
if ( this.cssPosition !== "fixed" ) {
|
|
newLeft = newLeft + this.scrollParent.scrollLeft();
|
|
newTop = newTop + this.scrollParent.scrollTop();
|
|
}
|
|
|
|
this.dragEl.css({
|
|
left: newLeft + "px",
|
|
top: newTop + "px"
|
|
});
|
|
},
|
|
|
|
_uiHash: function( event ) {
|
|
return {
|
|
position: this.position,
|
|
offset: this.offset
|
|
};
|
|
}
|
|
});
|
|
|
|
})( jQuery );
|