Here is the part from jQuery UI documentation for the tolerance
option:
This is the way the reordering behaves during drag. Possible values: 'intersect', 'pointer'. In some setups, 'pointer' is more natural.
intersect: draggable overlaps the droppable at least 50%
The default is intersect
, but if the mouse pointer is not above the sortable items, the sort does not happen, no matter that the dragged element is at least 50% over another sortable item (the functionality I expect) This happens on the demo page as well (linked above)
Is this a bug in jQuery UI, or am I understanding it wrong?
For those with the same problem here's the workaround I use:
$('#sortable').sortable({
axis: 'x',
sort: function (event, ui) {
var that = $(this),
w = ui.helper.outerWidth();
that.children().each(function () {
if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder'))
return true;
// If overlap is more than half of the dragged item
var dist = Math.abs(ui.position.left - $(this).position().left),
before = ui.position.left > $(this).position().left;
if ((w - dist) > (w / 2) && (dist < w)) {
if (before)
$('.ui-sortable-placeholder', that).insertBefore($(this));
else
$('.ui-sortable-placeholder', that).insertAfter($(this));
return false;
}
});
},
});
This works for a horizontal sortable, for a vertical one change outerWidth
to outerHeight
and position.left
to position.top
everywhere.
Here's a complete working example
I have unevenly sized sortable items and this makes things event worse. I've modified dioslaska's code to change the order once the bottom of the handler passes the bottom of the child (when moving down) and the same for the tops ...
sort: function (event, ui) {
var that = $(this),
draggedHeight = ui.helper.outerHeight(),
originalTop = ui.originalPosition.top,
draggedTop = ui.helper.position().top,
draggedBottom = draggedTop + draggedHeight;
that.children().each(function () {
if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder')) {
return true;
}
var childTop = $(this).position().top,
childHeight = $(this).outerHeight(),
childBottom = childTop + childHeight,
isChildAfter = originalTop < childTop,
largeTop,largeBottom,smallTop,smallBottom;
if (childHeight > draggedHeight) {
largeTop = childTop;
largeBottom = childTop + childHeight;
smallTop = draggedTop;
smallBottom = draggedBottom;
} else {
smallTop = childTop;
smallBottom = childTop + childHeight;
largeTop = draggedTop;
largeBottom = draggedBottom;
}
if (smallBottom > largeTop || smallTop < largeBottom) {
if (isChildAfter && draggedBottom >= childBottom) {
$('.ui-sortable-placeholder', that).insertAfter($(this));
} else if (!isChildAfter && draggedTop <= childTop) {
$('.ui-sortable-placeholder', that).insertBefore($(this));
return false;
}
}
});
}
You can see the overlap detection is a little trickier than before as you may be dragging a large item over a small item or vice versa.
It's not perfect, but it is a huge improvement over the default functionality.
Here's an addition to @dioslaska's function. If you need to sort in a grid (e.g. your elements are floated), you can check the child element's top position to see if it's in the same "row" like so:
sort: function (event, ui) {
var self = $(this),
width = ui.helper.outerWidth(),
top = ui.helper.position().top;//changed to ;
self.children().each(function () {
if ($(this).hasClass('ui-sortable-helper') || $(this).hasClass('ui-sortable-placeholder')) {
return true;
}
// If overlap is more than half of the dragged item
var distance = Math.abs(ui.position.left - $(this).position().left),
before = ui.position.left > $(this).position().left;
if ((width - distance) > (width / 2) && (distance < width) && $(this).position().top === top) {
if (before) {
$('.ui-sortable-placeholder', self).insertBefore($(this));
} else {
$('.ui-sortable-placeholder', self).insertAfter($(this));
}
return false;
}
});
}
Here is my implementation of the sort
method for grid layout based on Pythagorean theorem and distance between compared elements' centers:
$("#sortable").sortable({
...
sort: sort_jQueryBug8342Fix
});
function sort_jQueryBug8342Fix(e, ui) {
// ui-sortable-helper: element being dragged
// ui-sortable-placeholder: invisible item on the original place of dragged item
var container = $(this);
var placeholder = container.children('.ui-sortable-placeholder');
var draggedCenterX = ui.helper.position().left + ui.helper.outerWidth()/2;
var draggedCenterY = ui.helper.position().top + ui.helper.outerHeight()/2;
container.children().each(function () {
var item = $(this);
if(!item.hasClass( 'ui-sortable-helper' ) && !item.hasClass( 'ui-sortable-placeholder' ) ) {
var itemCenterX = item.position().left + item.outerWidth()/2;
var itemCenterY = item.position().top + item.outerHeight()/2;
// Pythagorean theorem
var distanceBetweenCenters = Math.sqrt(Math.pow(itemCenterX - draggedCenterX, 2) + Math.pow(itemCenterY - draggedCenterY, 2));
var minDimension = Math.min(item.outerWidth(), item.outerHeight(), ui.helper.outerWidth(), ui.helper.outerHeight());
var overlaps = distanceBetweenCenters < (minDimension / 2);
if (overlaps) {
if (placeholder.index() > item.index()) {
placeholder.insertBefore(item);
} else {
placeholder.insertAfter(item);
}
container.sortable('refreshPositions');
return false;
}
}
});
}
It works great for grid layouts and items with close width and height sizes, like image thumbnails of 4:3 or 16:10 ratio. In this case it needs 50-60% overlap to trigger sorting. If you have very wide elements, like 300x50 pixels (or elements of different size), then I suppose it will work too, but you would have to overlap them by X axis more, like 90%.
Note: if you are sorting Dropzone.js elements, then && item.hasClass('dz-preview')
should be added to first if
statement.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With