Draggable code not working with hammer.js

hammer.js, javascript, jquery, jquery-ui, jquery-ui-draggable

Solution

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.

Problem

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; } ```

Original source