Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draggable code not working with hammer.js

I've successfully implemented jQueryUI draggable, but as soon as I add hammer.js code, the draggable code no longer works.

It is not as soon as I include hammer.js, but as soon as I use the script.

Why is this? How can I get them both to work?

Both the draggable and hammer are applied to .dataCard and #main

The draggable code works fine here ( with hammer implementation commented out ): http://goo.gl/MO5Pde

Here is an example of the draggable code:

$('#main').draggable({
    axis:'y',
    revert:true,
    start: function(event, ui){
        topValue = ui.position.top;
    },
    drag: function(event, ui){
            if(pastBreakpoint === false){
                $('#searchInput').blur();
                if(topValue > ui.position.top) return false;
            if(ui.position.top >= 161){
                if(pastBreakpoint === false){
                pastBreakpoint = true;
                    if($('.loadingRefresh').length === 0) $('#main').before('<div class="loadingRefresh"></div>');
                    else{
                    $('.loadingRefresh').remove();
                        $('#main').before('<div class="loadingRefresh"></div>');
                    }
                    $('.loadingRefresh').fadeIn();
                    $('#main').mouseup();
                    setTimeout(function(){
                       location.reload();
                    }, 1000);
                 }
              }
            }   
        }
    });

Here is the hammer code uncommented and the draggable code not working: http://goo.gl/994pxF

Here is the hammer code:

var hammertime = Hammer(document.getElementById('main'), {
    transform_always_block: true,
    transform_min_scale: 0
});

var posX = 0,
    posY = 0,
    lastPosX = 0,
    lastPosY = 0,
    bufferX = 0,
    bufferY = 0,
    scale = 1,
    last_scale = 1;


hammertime.on('touch transform transformend', function(ev) {

    if ((" " + ev.target.className + " ").indexOf(" dataCard ") < 0) return;

    else manageMultitouch(ev, ev.target); }); 

    function manageMultitouch(ev, element) { 

        switch (ev.type) { 
            case 'touch': 
                last_scale = scale;
                                return;
             case 'transform':
                scale = Math.min(last_scale * ev.gesture.scale, 10);
                break;
            }

            if(scale <= 0.5) $(element).hide('clip');

            if(scale > 1.0) $(element).addClass('focused');

            var transform = "translate(" + 0 + "px," + 0 + "px) " + "scale(" + 1 + "," + scale + ")";
            var style = element.style;
            style.transform = transform;
            style.oTransform = transform;
            style.msTransform = transform;
            style.mozTransform = transform;
            style.webkitTransform = transform;
     }  
like image 616
gomangomango Avatar asked Feb 13 '14 02:02

gomangomango


2 Answers

I had the same problem in my app, even with touch punch included. I had to do a good research to find what was the problem stopping the jquery ui drag. The problem occurring is a preventDefault set at the event ( only when hammer is included ) changing the result of a trigger method from jquery ui.

Well, lets get back a little bit: The first method you should see is the _mouseMove(), which is connected with the mousemove event. The drag will be trigged only when the condition (this._mouseStart(this._mouseDownEvent, event) !== false) be true.

_mouseMove: function (event) {
    // IE mouseup check - mouseup happened when mouse was out of window
    if ($.ui.ie && (!document.documentMode || document.documentMode < 9) && !event.button) {
        return this._mouseUp(event);
    }

    if (this._mouseStarted) {
        this._mouseDrag(event);
        return event.preventDefault();
    }

    if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
        this._mouseStarted =
            (this._mouseStart(this._mouseDownEvent, event) !== false);
        (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
    }

    return !this._mouseStarted;
}

The next method will create the helper ( element's clone ), set some css in the element and return true ( value we expect ), unless this._trigger("start", event) returns false.

_mouseStart: function(event) {
    var o = this.options;
    //Create and append the visible helper
    this.helper = this._createHelper(event);
    this.helper.addClass("ui-draggable-dragging");
    //Cache the helper size
    this._cacheHelperProportions();
    //If ddmanager is used for droppables, set the global draggable
    if($.ui.ddmanager) {
        $.ui.ddmanager.current = this;
    }

    /*
     * - Position generation -
     * This block generates everything position related - it's the core of draggables.
     */
    //Cache the margins of the original element
    this._cacheMargins();
    //Store the helper's css position
    this.cssPosition = this.helper.css( "position" );
    this.scrollParent = this.helper.scrollParent();
    this.offsetParent = this.helper.offsetParent();
    this.offsetParentCssPosition = this.offsetParent.css( "position" );
    //The element's absolute position on the page minus margins
    this.offset = this.positionAbs = this.element.offset();
    this.offset = {
        top: this.offset.top - this.margins.top,
        left: this.offset.left - this.margins.left
    };
    //Reset scroll cache
    this.offset.scroll = false;
    $.extend(this.offset, {
        click: { //Where the click happened, relative to the element
            left: event.pageX - this.offset.left,
            top: event.pageY - this.offset.top
        },
        parent: this._getParentOffset(),
        relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
    });
    //Generate the original position
    this.originalPosition = this.position = this._generatePosition(event);
    this.originalPageX = event.pageX;
    this.originalPageY = event.pageY;
    //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
    (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
    //Set a containment if given in the options
    this._setContainment();
    //Trigger event + callbacks
    if(this._trigger("start", event) === false) {
        this._clear();
        return false;
    }
    //Recache the helper size
    this._cacheHelperProportions();
    //Prepare the droppable offsets
    if ($.ui.ddmanager && !o.dropBehaviour) {
        $.ui.ddmanager.prepareOffsets(this, event);
    }
    this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
    //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
    if ( $.ui.ddmanager ) {
        $.ui.ddmanager.dragStart(this, event);
    }

    return true;
}

Below is the first _trigger called, its from drag widget.

_trigger: function (type, event, ui) {
        ui = ui || this._uiHash();
        $.ui.plugin.call(this, type, [event, ui]);
        //The absolute position has to be recalculated after plugins
        if(type === "drag") {
            this.positionAbs = this._convertPositionTo("absolute");
        }
        return $.Widget.prototype._trigger.call(this, type, event, ui);
    }

At this point the result will call another trigger method (this time from the $.Widget) and that's the point where we have the problem.

 _trigger: function (type, event, data) {
    var prop, orig,
        callback = this.options[type];

    data = data || {};
    event = $.Event(event);
    event.type = (type === this.widgetEventPrefix ?
        type :
        this.widgetEventPrefix + type).toLowerCase();
    // the original event may come from any element
    // so we need to reset the target on the new event
    event.target = this.element[0];

    // copy original event properties over to the new event
    orig = event.originalEvent;
    if (orig) {
        for (prop in orig) {
            if (!(prop in event)) {
                event[prop] = orig[prop];
            }
        }
    }
    return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false || event.isDefaultPrevented());
}

return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false || event.isDefaultPrevented());

Our problem is exactly at this line. More specific the || before event.isDefaultPrevented(). When hammer is included the method event.isDefaultPrevented() is resulting true, once the value is denied before return, the final value would be false. (Without the hammer included the event.isDefaultPrevented() returns false as expected.) Backing in our _moseMouve(), instead of calling the _mouseDrag() method it'll invoke _mouseUp(). U can see it will unbind the events and call _mouseStop().

_mouseUp: function (event) {
    $(document)
        .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
        .unbind("mouseup."+this.widgetName, this._mouseUpDelegate);

    if (this._mouseStarted) {
        this._mouseStarted = false;

        if (event.target === this._mouseDownEvent.target) {
            $.data(event.target, this.widgetName + ".preventClickEvent", true);
        }

        this._mouseStop(event);
    }

    return false;
}

If you change the OR (||) operator by an AND (&&) it'll work fine. Off course it's not a little change, I had been testing it and until this moment I haven't find any problem at all. The line would be like this:

return !($.isFunction(callback) && callback.apply(this.element[0], [event].concat(data)) === false && event.isDefaultPrevented());

As I said, its not 100% secure, but until now I didn't find a reason to keep || instead of &&. I'll keep testing it for a few days. Besides I've already sent an email to the lead developer from jquery ui asking about it.

like image 179
André Junges Avatar answered Sep 22 '22 23:09

André Junges


Similar problem for me using it with isomorphic smartclient.

I fixed it by handling the start event and resetting isDefaultPrevented to false

$(element).draggable({
  start: function (event, ui) {
    event.isDefaultPrevented = function () { return false; }
  }
});
like image 28
lycandroid Avatar answered Sep 24 '22 23:09

lycandroid