Question

Using the code example from Jquery-ui sortable doesn't work on touch devices based on Android or IOS to touch enable jQuery UI sortables on iOS devices, there is a problem when registering a knockout.js click handler as well as the jQuery UI sortable on the same elements. The knockout.js handler doesn't fire on touch enabled devices, but does on desktops/laptops.

Adding a flag called moved it is possible to track when a click handler needs triggered, marked with // TRIGGER HERE below:

/*
 * 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
  moved = true;

  $.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 );

      moved = false;

      return false;
    },

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

    _touchEnd: function( event ) {
      this.element
      .unbind( "touchmove." + this.widgetName )
      .unbind( "touchend." + this.widgetName );
      this._mouseUp( event );
      if (! moved) {
        // TRIGGER HERE
      }
    },

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

  });

})( jQuery );

The problem is, how do you trigger a click event to knockout.js from jQuery UI?

I've tried this.element.click(), this.element.get().click(), this.element.trigger("click"), etc to no avail.

Update:

Hacked the code to:

  • track the actual target as html.element doesn't appear to be the correct one
  • trigger click events on the correct target

Now it works fine with knockout.js's click event.

/*
 * 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
  moved = true,
  currentTarget = null;

  $.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 );

      moved = false;

      return false;
    },

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

    _touchEnd: function( event ) {
      this.element
      .unbind( "touchmove." + this.widgetName )
      .unbind( "touchend." + this.widgetName );
      this._mouseUp( event );
      if (! moved) {
        $(currentTarget).click();
      }
    },

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

  });

})( jQuery );
Was it helpful?

Solution

In the code you posted you return false from the touchstart event. In touch enabled devices the touchstart event fires first and the click fired approx 300ms later.

If you return false from an event handler this is the same as calling event.preventDefault() and event.stopPropagation() so the touchstart is effectively cancelling the click. This is not a problem on desktops since touchstart never fires.

http://jsfiddle.net/madcapnmckay/HkbwV/2/

Possible solutions.

  • Instead of returning false simply call event.stopPropagation() (if that will work). Use the event binding to bind touchstart instead of click.

<div data-bind="event : { touchstart: somfunction }"></div>

  • In the place you marked trigger a custom event that is subscribed to by the event binding.

You may also consider writing a touchOrClick custom binding that detects whether touchstart is available and selectively binds to it or the click event.

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top