28

Is there any fix to make Jquery-ui sortable work on touch devices based on Android or IOS?

Alexey Zakharov
  • 24,694
  • 42
  • 126
  • 197

5 Answers5

36

I suggest jQuery UI Touch Punch. I've tested it on iOS 5 and Android 2.3 and it works great on both.

balexand
  • 9,549
  • 7
  • 41
  • 36
  • 3
    Actually, this is the only solution which work for me without any problems. – Goldie Jun 11 '12 at 08:37
  • 4
    I actually have problems using touch-punch. After activating draggable and resizabble on a div, after deactivating it default browser touch events (pinch to zoom, swipe to move the screen, etc) events no longer work if they touch the area of a div that was once resizable or draggable. – Hoffmann Aug 28 '12 at 13:58
  • 1
    Works like a charm – Brett Gregson Apr 12 '16 at 09:07
29

The other answer is great but unfortunately it will only work on iOS devices.

Also there was is a breakage caused by later versions of jquery.ui that meant that _touchEnd events were not correctly resetting an internal flag (mouseHandled) in mouse.ui and this was causing exceptions.

Both of these problems should now be fixed with this code.

/*
 * Content-Type:text/javascript
 *
 * A bridge between iPad and iPhone touch events and jquery draggable, 
 * sortable etc. mouse interactions.
 * @author Oleg Slobodskoi  
 * 
 * modified by John Hardy to use with any touch device
 * fixed breakage caused by jquery.ui so that mouseHandled internal flag is reset 
 * before each touchStart event
 * 
 */
(function( $ ) {

    $.support.touch = typeof Touch === 'object';

    if (!$.support.touch) {
        return;
    }

    var proto =  $.ui.mouse.prototype,
    _mouseInit = proto._mouseInit;

    $.extend( proto, {
        _mouseInit: function() {
            this.element
            .bind( "touchstart." + this.widgetName, $.proxy( this, "_touchStart" ) );
            _mouseInit.apply( this, arguments );
        },

        _touchStart: function( event ) {
            if ( event.originalEvent.targetTouches.length != 1 ) {
                return false;
            }

            this.element
            .bind( "touchmove." + this.widgetName, $.proxy( this, "_touchMove" ) )
            .bind( "touchend." + this.widgetName, $.proxy( this, "_touchEnd" ) );

            this._modifyEvent( event );

            $( document ).trigger($.Event("mouseup")); //reset mouseHandled flag in ui.mouse
            this._mouseDown( event );

            return false;           
        },

        _touchMove: function( event ) {
            this._modifyEvent( event );
            this._mouseMove( event );   
        },

        _touchEnd: function( event ) {
            this.element
            .unbind( "touchmove." + this.widgetName )
            .unbind( "touchend." + this.widgetName );
            this._mouseUp( event ); 
        },

        _modifyEvent: function( event ) {
            event.which = 1;
            var target = event.originalEvent.targetTouches[0];
            event.pageX = target.clientX;
            event.pageY = target.clientY;
        }

    });

})( jQuery );
Community
  • 1
  • 1
John Hardy
  • 406
  • 3
  • 5
  • 1
    Awesome, this appears to work. I do get some console errors in the ipad though `Line 62: (TypeError: Result of expression 'event.originalEvent' [undefined] is not an object` But it still seems to work. I'll see if I can fix that js error – asgeo1 Aug 31 '11 at 12:01
  • 2
    This code works, however it conflicts with the regular click (for instance the ok button of a dialog). Is it possible to only execute draggable when the user keeps the finger on the item for 500ms? – Mike Sep 13 '11 at 16:11
  • 2
    @Mike - That sounds like an issue I was having too. I found just by removing the call to `return false` in the above code, that allowed the click events to work as normal on the dialog – asgeo1 Sep 19 '11 at 07:29
  • 2
    @asgeo1 Yes, that fixed the problem. They keep using `return false`, it does not do what most people think it does. – Mike Sep 21 '11 at 08:53
  • This script was almost working, but behaved kind of flaky for me. I finally found an article talking about the mouse position of events and figured out I needed to change the _modifyEvent to change the page like this: event.pageX = target.clientX + document.body.scrollLeft; event.pageY = target.clientY + document.body.scrollTop; - with that tweak it works even if I have scrolled my page. I only tested on iOS - not sure if it works on other mobile devices. – nimblegorilla Jan 06 '12 at 06:55
  • 1
    This broke down for me when using the handle option on sortable. I patched it up by using the _mouseCapture event. My changes are at https://gist.github.com/2416927 – Daniel Evans Apr 18 '12 at 22:07
  • @DanielEvans could you tell me how to use this code? Do I have to modify some function in JQuery library ? – Ashika Umanga Umagiliya Jul 17 '12 at 06:00
  • 1
    @AshikaUmangaUmagiliya You need to include this javascript after you have included jQuery and jQuery-ui. – Daniel Evans Jul 20 '12 at 21:22
  • Where I need to use this code. I have same problem, my jQuery-ui sortable not working with mobile. – subir biswas May 29 '20 at 11:26
3

is this meant to replace the mouse.ui js code or to be called after that javascript is loaded? I am unable to get it to work for me on an Android tablet.

EDIT for anyone finding this in the future - got this to work for a Samsung Galaxy Android tablet with the following code:

    /iPad|iPhone|Android/.test( navigator.userAgent ) && (function( $ ) {

var proto =  $.ui.mouse.prototype,
_mouseInit = proto._mouseInit;

$.extend( proto, {
    _mouseInit: function() {
        this.element
        .bind( "touchstart." + this.widgetName, $.proxy( this, "_touchStart" ) );
        _mouseInit.apply( this, arguments );
    },

    _touchStart: function( event ) {
        /* if ( event.originalEvent.targetTouches.length != 1 ) {
            return false;
        } */

        this.element
        .bind( "touchmove." + this.widgetName, $.proxy( this, "_touchMove" ) )
        .bind( "touchend." + this.widgetName, $.proxy( this, "_touchEnd" ) );

        this._modifyEvent( event );

        $( document ).trigger($.Event("mouseup")); //reset mouseHandled flag in ui.mouse
        this._mouseDown( event );

        //return false;           
    },

    _touchMove: function( event ) {
        this._modifyEvent( event );
        this._mouseMove( event );   
    },

    _touchEnd: function( event ) {
        this.element
        .unbind( "touchmove." + this.widgetName )
        .unbind( "touchend." + this.widgetName );
        this._mouseUp( event ); 
    },

    _modifyEvent: function( event ) {
        event.which = 1;
        var target = event.originalEvent.targetTouches[0];
        event.pageX = target.clientX;
        event.pageY = target.clientY;
    }

});

})( jQuery );
2

I'm using this snippet below in conjunction with jquery-sortable which does allow the drag sort to happen on my iPhone. I am having a problem after I finish the first sort however as any scrolling on the list at all is detected as a drag.

EDIT - see here as well http://bugs.jqueryui.com/ticket/4143 EDIT 2 - I was able to get this working if I use the entire row as the handle. It also fixed a problem I was having where the offset was incorrect after scrolling.

/*
 * A bridge between iPad and iPhone touch events and jquery draggable, sortable etc. mouse interactions.
 * @author Oleg Slobodskoi  
 */
/iPad|iPhone/.test( navigator.userAgent ) && (function( $ ) {

    var proto =  $.ui.mouse.prototype,
        _mouseInit = proto._mouseInit;

    $.extend( proto, {
        _mouseInit: function() {
            this.element
                .bind( "touchstart." + this.widgetName, $.proxy( this, "_touchStart" ) );

            _mouseInit.apply( this, arguments );
        },

        _touchStart: function( event ) {
            if ( event.originalEvent.targetTouches.length != 1 ) {
                return false;
            }

            this.element
                .bind( "touchmove." + this.widgetName, $.proxy( this, "_touchMove" ) )
                .bind( "touchend." + this.widgetName, $.proxy( this, "_touchEnd" ) );

            this._modifyEvent( event );

            this._mouseDown( event );

            return false;           
        },

        _touchMove: function( event ) {
            this._modifyEvent( event );
            this._mouseMove( event );   
        },

        _touchEnd: function( event ) {
            this.element
                .unbind( "touchmove." + this.widgetName )
                .unbind( "touchend." + this.widgetName );
            this._mouseUp( event ); 
        },

        _modifyEvent: function( event ) {
            event.which = 1;
            var target = event.originalEvent.targetTouches[0];
            event.pageX = target.clientX;
            event.pageY = target.clientY;
        }

    });

})( jQuery );
Bryan
  • 356
  • 2
  • 6
-1

Using Touch Punch is as easy as 1, 2… Just follow these simple steps to enable touch events in your jQuery UI app:

Include jQuery and jQuery UI on your page.

Include Touch Punch after jQuery UI and before its first use.

Please note that if you are using jQuery UI's components, Touch Punch must be included after jquery.ui.mouse.js, as Touch Punch modifies its behavior.

There is no 3. Just use jQuery UI as expected and watch it work at the touch of a finger.

$('#widget').draggable();

Tested on iPad, iPhone, Android and other touch-enabled mobile devices.