I\'m experiencing a problem where the draggable helper is being offset incorrectly, when using draggables + sortables that are placed in floating, relative pos
What is strange is that it seems to work better with jquery-ui 10.4. The difference is that in 10.4 the draggable helper stays in its original div, and is cloned into the sortables but hidden. So calculations are easier to make.
In 11.4, the helper is appended to the sortable over which it is dragged, which makes the precise offset calculations hard to follow. You constantly have to change the parent offset, and keep track of over which sortable it is, over which sortable it was, the position of the sortable and so on. And clearly there is a bug there.
One simple solution would be to get the connectToSortable plugin from 10.4. You'll have to check for unwanted side effects, but quickly it seems to be working. You can use a different name so that you keep the original. Like this:
$.ui.plugin.add("draggable", "connectToSortable104", {
// You take the whole connectToSortable plugin from
// here: https://code.jquery.com/ui/1.10.4/jquery-ui.js
// In 11.4 you'll need to add draggable parameter
// for example: to the event methods,
// start: function(event, ui, draggable)
...
See http://jsfiddle.net/gsnojkbc/2/
EDIT:
I don't think the additional div is what causes the problem, it's really a bug with the way the connectToSortable works in jquery 11.4 that is causing the issue. To allow moving the helper in the sortables and still keep track of the proper offset, you need to readjust some data each time the helper changes div. There's two flaws in the way it's done:
First one is that there's a refreshOffsets method that is common to other events in draggable. It's used for example when you click on a draggable. And so it tries to calculate the offset based on the click. But when calling refreshOffsets from the sortable event, it messes the click offset. This can be solved by changing refreshOffsets method so as not to consider the event.pageX and Y. Like this:
$.ui.draggable.prototype._refreshOffsetsSortable = function(event, draggable){
this.offset = {
top: this.positionAbs.top - this.margins.top,
left: this.positionAbs.left - this.margins.left,
scroll: false,
parent: this._getParentOffset(),
relative: this._getRelativeOffset()
};
this.offset.click = draggable.offset.click;
}
The other problem happens because you have many sortables. Basically the other operation that needs to be done is change the parent offset. The way it's done right now is that it saves the previous parent. Normally it works but if you move too fast, the sequence makes it so that the saved parent is a sortable and not the original parent. You can fix this by saving the parent on drag start, which in any case makes seems to make more sense. Like this:
$.ui.plugin.add( "draggable", "connectToSortableFixed", {
start: function( event, ui, draggable ) {
var uiSortable = $.extend( {}, ui, {
item: draggable.element
});
draggable._parent = this.parent();
...
See here: http://jsfiddle.net/24a8q49j/1/
A simple JavaScript only workaround is to store the offset to the mouse when the dragging starts and then force the ui.helper
to have the same offset to the mouse - always:
[...]
var offset_start = {};
$('.draggable').draggable({
connectToSortable: '.sortable',
helper: 'clone',
revert: true,
start: function(e, ui) {
offset_start = {
x: ui.position.left - e.pageX,
y: ui.position.top - e.pageY
}
},
drag: function(e, ui) {
ui.position.top = e.pageY + offset_start.y
ui.position.left = e.pageX + offset_start.x
}
}).disableSelection();
See the updated jsfiddle.
For the simple case this is an easy workaround, but requires more work if you introduce borders (where the helper cannot leave) etc.
Edit: For your snippet I came up with another solution (because the other one does not work as expected - harder to get the initial offsets than I thought ...): Simply force the item to stay at the mouse pointer regardless of the initial offset after appending the helper to the body. It is not 100% nice, because when starting to drag it might "jump" to the mouse - but then it stays there at least ...
$('.draggable').draggable({
connectToSortable: '.sortable',
helper: 'clone',
revert: 'invalid',
appendTo: 'body',
drag: function(e, ui) {
if (!ui.helper.parent().is('body')) {
ui.helper.appendTo($('body'));
}
ui.position.top = e.pageY - 10;
ui.position.left = e.pageX - 25;
}
}).disableSelection();
$('.sortable').sortable({
connectWith: '.sortable',
revert: 600,
forcePlaceholderSize: true,
placeholder: 'ui-sortable-placeholder',
tolerance: 'pointer'
}).disableSelection();
$('.draggable').draggable({
connectToSortable: '.sortable',
helper: 'clone',
revert: 'invalid',
appendTo: 'body',
scroll: false,
drag: function(e, ui) {
if (!ui.helper.parent().is('body')) {
ui.helper.appendTo($('body'));
}
ui.position.top = e.pageY - 15;
ui.position.left = e.pageX - 25;
}
}).disableSelection()
.sortable-container {
display: inline-block;
width: 100px;
vertical-align: top;
}
.sortable {
cursor: move;
margin: 0;
padding: 0;
list-style-type: none;
vertical-align: top;
border: 1px solid #000;
}
.ui-sortable-placeholder {
background: #ff0000;
}
#draggables {
margin: 0;
padding: 0;
list-style-type: none;
}
.draggable {
margin: 4px;
cursor: move;
color: #fff;
background: #5dd1ff;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.3/jquery-ui.min.js"></script>
<div class='container-fluid'>
<div class="row">
<div class="col-xs-3">
<p>foo</p>
<p>bar</p>
</div>
<div class="col-xs-3">
<p>foo</p>
<p>bar</p>
</div>
<div class="col-xs-6">
<p>foo</p>
<p>bar</p>
</div>
</div>
<div class='row'>
<div class='col-xs-6'>
<div id='sortables'>
<div class='sortable-container'>
<ul class='sortable'>
<li>sortable 1</li>
<li>sortable 2</li>
<li>sortable 3</li>
<li>sortable 4</li>
<li>sortable 5</li>
<li>sortable 6</li>
</ul>
</div>
<div class='sortable-container'>
<ul class='sortable'>
<li>sortable 1</li>
<li>sortable 2</li>
<li>sortable 3</li>
<li>sortable 4</li>
<li>sortable 5</li>
<li>sortable 6</li>
</ul>
</div>
<div class='sortable-container'>
<ul class='sortable'>
<li>sortable 1</li>
<li>sortable 2</li>
<li>sortable 3</li>
<li>sortable 4</li>
<li>sortable 5</li>
<li>sortable 6</li>
</ul>
</div>
</div>
</div>
<div class='col-xs-6'>
<ul id='draggables'>
<li class='draggable'>draggable 1</li>
<li class='draggable'>draggable 2</li>
<li class='draggable'>draggable 3</li>
</ul>
</div>
</div>
</div>
see https://jsfiddle.net/tsLcjw9c/1/
I put a fixed width on the draggables list (100px) so that the draggable helper has the same width as the list items, this prevents the initial horizontal offset.
the big horizontal offset you got while dragging over the sortable container indeed seems to be a bug in jquery UI but it also seems to be exactly 100% of the width of the sortable container, which is why I added the following css:
.sortable .ui-draggable-dragging:not(.ui-sortable-helper){
margin-left:100%;
}
The vertical offset that you got after entering and leaving the sortable lists seemed to be caused by the draggable items' margin of 4px, which I removed. You can reach the same effect with a transparent border and box-sizing:border-box; or either add a padding and set background-clip to padding-box.
Lastly the 600ms animation that you wanted to happen upon dropping the items happened in a strange way in my browser (Chrome), it went to 2 different locations before finally animating towards the correct place. This is why I now disabled the animation. You can try to override the drop event in the sortable jQuery, so that you manually animate towards the correct location.
Here the jsfiddle code:
html
<div class='container-fluid'>
<div class='row'>
<div class='col-xs-6'>
<div id='sortables'>
<div class='sortable-container'>
<ul class='sortable'>
<li>sortable 1</li>
<li>sortable 2</li>
<li>sortable 3</li>
<li>sortable 4</li>
<li>sortable 5</li>
<li>sortable 6</li>
</ul>
</div>
<div class='sortable-container'>
<ul class='sortable'>
<li>sortable 1</li>
<li>sortable 2</li>
<li>sortable 3</li>
<li>sortable 4</li>
<li>sortable 5</li>
<li>sortable 6</li>
</ul>
</div>
<div class='sortable-container'>
<ul class='sortable'>
<li>sortable 1</li>
<li>sortable 2</li>
<li>sortable 3</li>
<li>sortable 4</li>
<li>sortable 5</li>
<li>sortable 6</li>
</ul>
</div>
</div>
</div>
<div class='col-xs-6'>
<ul id='draggables'>
<li class='draggable'>draggable 1</li>
<li class='draggable'>draggable 2</li>
<li class='draggable'>draggable 3</li>
</ul>
</div>
</div>
</div>
jquery
$('.sortable').sortable({
connectWith: '.sortable',
revert: 0,
forcePlaceholderSize: true,
placeholder: 'ui-sortable-placeholder',
tolerance: 'pointer'
}).disableSelection();
$('.draggable').draggable({
connectToSortable: '.sortable',
helper: 'clone',
revert: false
}).disableSelection();
css
.sortable-container {
display: inline-block;
width: 100px;
vertical-align: top;
}
.sortable {
cursor: move;
margin: 0;
padding: 0;
list-style-type: none;
vertical-align: top;
border: 1px solid #000;
}
.ui-sortable-placeholder {
background: #ff0000;
}
#draggables {
margin: 0;
padding: 0;
list-style-type: none;
}
.draggable {
cursor: move;
color: #fff;
background: #5dd1ff;
width:100px;
max-width:100%;
}
.sortable .ui-draggable-dragging:not(.ui-sortable-helper){
margin-left:100%;
}