From c866e455373028a62a0956455a229fef63e91fac Mon Sep 17 00:00:00 2001 From: "A. Wells" Date: Thu, 16 Feb 2017 13:48:45 -0500 Subject: [PATCH] Sortable: Fix various scrolling issues * Created _scroll extension point and migrated scroll code from _mouseDrag * Cleaned up logic for scrolled * Fixed appendTo functionality to match documentation * Remove unnecessary function calls * Move set-up position functions to appropriate place * Base scrollParent on placeholder and not helper * Update scrollParent when switching containers Fixes #3173 Fixes #15165 Fixes #15166 Fixes #15167 Fixes #15168 Fixes #15169 Fixes #15170 Closes gh-1793 --- ui/widgets/sortable.js | 297 +++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 129 deletions(-) diff --git a/ui/widgets/sortable.js b/ui/widgets/sortable.js index fcb33ace145..78fad26b9b4 100644 --- a/ui/widgets/sortable.js +++ b/ui/widgets/sortable.js @@ -195,6 +195,11 @@ return $.widget( "ui.sortable", $.ui.mouse, { // mouseCapture this.refreshPositions(); + //Prepare the dragged items parent + this.appendTo = $( o.appendTo !== "parent" ? + o.appendTo : + this.currentItem.parent() ); + //Create and append the visible helper this.helper = this._createHelper( event ); @@ -209,9 +214,6 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Cache the margins of the original element this._cacheMargins(); - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { @@ -236,15 +238,6 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.helper.css( "position", "absolute" ); this.cssPosition = this.helper.css( "position" ); - $.extend( this.offset, { - parent: this._getParentOffset() - } ); - - //Generate the original position - this.originalPosition = this._generatePosition( event ); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); @@ -263,6 +256,13 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Create the placeholder this._createPlaceholder(); + //Get the next scrolling parent + this.scrollParent = this.placeholder.scrollParent(); + + $.extend( this.offset, { + parent: this._getParentOffset() + } ); + //Set a containment if given in the options if ( o.containment ) { this._setContainment(); @@ -330,77 +330,82 @@ return $.widget( "ui.sortable", $.ui.mouse, { this._addClass( this.helper, "ui-sortable-helper" ); - // Execute the drag once - this causes the helper not to be visiblebefore getting its - // correct position - this._mouseDrag( event ); - return true; + //Move the helper, if needed + if ( !this.helper.parent().is( this.appendTo ) ) { + this.helper.detach().appendTo( this.appendTo ); - }, + //Update position + this.offset.parent = this._getParentOffset(); + } - _mouseDrag: function( event ) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; + //Generate the original position + this.position = this.originalPosition = this._generatePosition( event ); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + this.lastPositionAbs = this.positionAbs = this._convertPositionTo( "absolute" ); - //Compute the helpers position - this.position = this._generatePosition( event ); - this.positionAbs = this._convertPositionTo( "absolute" ); + this._mouseDrag( event ); - if ( !this.lastPositionAbs ) { - this.lastPositionAbs = this.positionAbs; - } + return true; - //Do scrolling - if ( this.options.scroll ) { - if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ].tagName !== "HTML" ) { + }, - if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - - event.pageY < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; - } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; - } + _scroll: function( event ) { + var o = this.options, + scrolled = false; - if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - - event.pageX < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; - } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; - } + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { - } else { + if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - + event.pageY < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; + } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; + } - if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); - } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); - } + if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - + event.pageX < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; + } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; + } - if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() - o.scrollSpeed - ); - } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() + o.scrollSpeed - ); - } + } else { + if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); + } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); } - if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); + if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() - o.scrollSpeed + ); + } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() + o.scrollSpeed + ); } + } - //Regenerate the absolute position used for position checks + return scrolled; + }, + + _mouseDrag: function( event ) { + var i, item, itemElement, intersection, + o = this.options; + + //Compute the helpers position + this.position = this._generatePosition( event ); this.positionAbs = this._convertPositionTo( "absolute" ); //Set the helper position @@ -411,56 +416,79 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.helper[ 0 ].style.top = this.position.top + "px"; } - //Rearrange - for ( i = this.items.length - 1; i >= 0; i-- ) { + //Post events to containers + this._contactContainers( event ); - //Cache variables and intersection, continue if no intersection - item = this.items[ i ]; - itemElement = item.item[ 0 ]; - intersection = this._intersectsWithPointer( item ); - if ( !intersection ) { - continue; - } + if ( this.innermostContainer !== null ) { - // Only put the placeholder inside the current Container, skip all - // items from other containers. This works because when moving - // an item from one container to another the - // currentContainer is switched before the placeholder is moved. - // - // Without this, moving items in "sub-sortables" can cause - // the placeholder to jitter between the outer and inner container. - if ( item.instance !== this.currentContainer ) { - continue; + //Do scrolling + if ( o.scroll ) { + if ( this._scroll( event ) !== false ) { + + //Update item positions used in position checks + this._refreshItemPositions( true ); + + if ( $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( this, event ); + } + } } - // Cannot intersect with itself - // no useless actions that have been done before - // no action if the item moved is the parent of the item checked - if ( itemElement !== this.currentItem[ 0 ] && - this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement && - !$.contains( this.placeholder[ 0 ], itemElement ) && - ( this.options.type === "semi-dynamic" ? - !$.contains( this.element[ 0 ], itemElement ) : - true - ) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { - this._rearrange( event, item ); - } else { - break; + this.dragDirection = { + vertical: this._getDragVerticalDirection(), + horizontal: this._getDragHorizontalDirection() + }; + + //Rearrange + for ( i = this.items.length - 1; i >= 0; i-- ) { + + //Cache variables and intersection, continue if no intersection + item = this.items[ i ]; + itemElement = item.item[ 0 ]; + intersection = this._intersectsWithPointer( item ); + if ( !intersection ) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items from other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this, moving items in "sub-sortables" can cause + // the placeholder to jitter between the outer and inner container. + if ( item.instance !== this.currentContainer ) { + continue; } - this._trigger( "change", event, this._uiHash() ); - break; + // Cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if ( itemElement !== this.currentItem[ 0 ] && + this.placeholder[ intersection === 1 ? + "next" : "prev" ]()[ 0 ] !== itemElement && + !$.contains( this.placeholder[ 0 ], itemElement ) && + ( this.options.type === "semi-dynamic" ? + !$.contains( this.element[ 0 ], itemElement ) : + true + ) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if ( this.options.tolerance === "pointer" || + this._intersectsWithSides( item ) ) { + this._rearrange( event, item ); + } else { + break; + } + + this._trigger( "change", event, this._uiHash() ); + break; + } } } - //Post events to containers - this._contactContainers( event ); - //Interconnect with droppables if ( $.ui.ddmanager ) { $.ui.ddmanager.drag( this, event ); @@ -663,8 +691,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { return false; } - verticalDirection = this._getDragVerticalDirection(); - horizontalDirection = this._getDragHorizontalDirection(); + verticalDirection = this.dragDirection.vertical; + horizontalDirection = this.dragDirection.horizontal; return this.floating ? ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) @@ -678,8 +706,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.offset.click.top, item.top + ( item.height / 2 ), item.height ), isOverRightHalf = this._isOverAxis( this.positionAbs.left + this.offset.click.left, item.left + ( item.width / 2 ), item.width ), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); + verticalDirection = this.dragDirection.vertical, + horizontalDirection = this.dragDirection.horizontal; if ( this.floating && horizontalDirection ) { return ( ( horizontalDirection === "right" && isOverRightHalf ) || @@ -821,26 +849,14 @@ return $.widget( "ui.sortable", $.ui.mouse, { }, - refreshPositions: function( fast ) { - - // Determine whether items are being displayed horizontally - this.floating = this.items.length ? - this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : - false; - - //This has to be redone because due to the item being moved out/into the offsetParent, - // the offsetParent's position will change - if ( this.offsetParent && this.helper ) { - this.offset.parent = this._getParentOffset(); - } - + _refreshItemPositions: function( fast ) { var i, item, t, p; for ( i = this.items.length - 1; i >= 0; i-- ) { item = this.items[ i ]; //We ignore calculating positions of all connected containers when we're not over them - if ( item.instance !== this.currentContainer && this.currentContainer && + if ( this.currentContainer && item.instance !== this.currentContainer && item.item[ 0 ] !== this.currentItem[ 0 ] ) { continue; } @@ -858,6 +874,20 @@ return $.widget( "ui.sortable", $.ui.mouse, { item.left = p.left; item.top = p.top; } + }, + + refreshPositions: function( fast ) { + + // Determine whether items are being displayed horizontally + this.floating = this.items.length ? + this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : + false; + + if ( this.innermostContainer !== null ) { + this._refreshItemPositions( fast ); + } + + var i, p; if ( this.options.custom && this.options.custom.refreshContainers ) { this.options.custom.refreshContainers.call( this ); @@ -1003,6 +1033,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { } + this.innermostContainer = innermostContainer; + // If no intersecting containers found, return if ( !innermostContainer ) { return; @@ -1071,6 +1103,15 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Update the placeholder this.options.placeholder.update( this.currentContainer, this.placeholder ); + //Update scrollParent + this.scrollParent = this.placeholder.scrollParent(); + + //Update overflowOffset + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { + this.overflowOffset = this.scrollParent.offset(); + } + this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); this.containers[ innermostIndex ].containerCache.over = 1; } @@ -1086,9 +1127,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Add the helper to the DOM if that didn't happen already if ( !helper.parents( "body" ).length ) { - $( o.appendTo !== "parent" ? - o.appendTo : - this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] ); + this.appendTo[ 0 ].appendChild( helper[ 0 ] ); } if ( helper[ 0 ] === this.currentItem[ 0 ] ) {