I am using jQuery UI Sortable.
I am developing a mobile only website, so I looked around how to map touch events to mouse events for this to work, and ended up using some code by Oleg Slobodskoi.
This appeared to work nicely. However, on a mobile device (I am using an iPhone to test, but it also happens on the iPhone simulator), when you attempt to shift a second item (i.e. the next item to sort after you have sorted one already), its initial position is offset either side by about the width of the element.
When I attempt the second time to drag one of these pink boxes, instead of appearing underneath my finger and shifting with it, it starts about the width of a box away, to the left or right, and then jumps under my finger.
I've set up an example on jsFiddle. Hit the attach button to add jQuery Sortable to the list items.
I have tried many things, including playing with the change
event and attempting to realign it back, but I couldn't get it to reliably work.
How can I stop this problem?
jQueryUI provides sortable() method to reorder elements in list or grid using the mouse. This method performs sortability action based upon an operation string passed as the first parameter.
Super simple solution for this one. I wish I could say it was super simple to find, but no... it took a while.
To clarify the symptom, it's that the initially-dragged element is always the one that is dragged on subsequent drags. If you start out dragging b
, on subsequent drags b
is the one that always moved. Likewise for a
and c
.
This made me question if perhaps the event was being "recycled." I confirmed that the pageX
and pageY
values were correct on the touchstart
(and touchmove
) event, but the values getting to _mouseDown
in Sortable were wrong. So, I went to jquery.ui.mouse.js
and looked at _mouseDown
there. I confirmed that the proper values were coming through, but that the handler was exiting at this line at the top of the method:
// don't let more than one widget handle mouseStart
if( mouseHandled ) { return };
So, I started looking at mouseHandled
. There was only one line where this was reset back to false
- the following listener at the top:
$( document ).mouseup( function( e ) {
mouseHandled = false;
});
I realized I must be close. I looked back at the _touchEnd
method in the compatibility add-in you're using:
_touchEnd: function(event) {
this.element.unbind("touchmove." + this.widgetName).unbind("touchend." + this.widgetName);
this._mouseUp(event);
},
Note that _mouseUp
is only called on the widget itself -- not the document
! Thus, clearly mouseHandled
was never being reset. So, I added a document
dispatch line to _touchEnd
:
_touchEnd: function(event) {
this.element.unbind("touchmove." + this.widgetName).unbind("touchend." + this.widgetName);
this._mouseUp(event);
$(document).trigger('mouseup', event);
},
And presto, everything worked correctly.
So, in summary, this one line is the magic one:
$(document).trigger('mouseup', event);
Working forked fiddle here [direct link for iOS viewing].
Note: I also changed this line:
/ iPad | iPhone /.test(navigator.userAgent) && (function($) {
To:
/iPad|iPhone|iPod/.test(navigator.userAgent) && (function($) {
Because it didn't work properly with spaces, and you should include support for the iPod touch.
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