What is going on with jQuery UI Sortable on a touch screen device?
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.