jqueryjquery-uiknockout.jsjquery-eventsknockout-sortable

Trigger a click event from jQuery UI to knockout.js


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:

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

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.

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

    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.