Undev/redmine-stuff-to-do-plugin

View on GitHub
assets/javascripts/ui/ui.droppable.js

Summary

Maintainability
F
2 wks
Test Coverage
/*
 * jQuery UI Droppable @VERSION
 *
 * Copyright (c) 2008 AUTHORS.txt (http://ui.jquery.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Droppables
 *
 * Depends:
 *    ui.core.js
 *    ui.draggable.js
 */
(function($) {

$.widget("ui.droppable", {

    _init: function() {

        var o = this.options, accept = o.accept;
        this.isover = 0; this.isout = 1;

        this.options.accept = this.options.accept && $.isFunction(this.options.accept) ? this.options.accept : function(d) {
            return d.is(accept);
        };

        //Store the droppable's proportions
        this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };

        // Add the reference and positions to the manager
        $.ui.ddmanager.droppables[this.options.scope] = $.ui.ddmanager.droppables[this.options.scope] || [];
        $.ui.ddmanager.droppables[this.options.scope].push(this);

        (this.options.cssNamespace && this.element.addClass(this.options.cssNamespace+"-droppable"));

    },

    destroy: function() {
        var drop = $.ui.ddmanager.droppables[this.options.scope];
        for ( var i = 0; i < drop.length; i++ )
            if ( drop[i] == this )
                drop.splice(i, 1);

        this.element
            .removeClass("ui-droppable-disabled")
            .removeData("droppable")
            .unbind(".droppable");
    },

    _setData: function(key, value) {

        if(key == 'accept') {
            this.options.accept = value && $.isFunction(value) ? value : function(d) {
                return d.is(accept);
            };
        } else {
            $.widget.prototype._setData.apply(this, arguments);
        }

    },

    _activate: function(event) {

        var draggable = $.ui.ddmanager.current;
        $.ui.plugin.call(this, 'activate', [event, this.ui(draggable)]);
        if(draggable) this.element.triggerHandler("dropactivate", [event, this.ui(draggable)], this.options.activate);

    },

    _deactivate: function(event) {

        var draggable = $.ui.ddmanager.current;
        $.ui.plugin.call(this, 'deactivate', [event, this.ui(draggable)]);
        if(draggable) this.element.triggerHandler("dropdeactivate", [event, this.ui(draggable)], this.options.deactivate);

    },

    _over: function(event) {

        var draggable = $.ui.ddmanager.current;
        if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element

        if (this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
            $.ui.plugin.call(this, 'over', [event, this.ui(draggable)]);
            this.element.triggerHandler("dropover", [event, this.ui(draggable)], this.options.over);
        }

    },

    _out: function(event) {

        var draggable = $.ui.ddmanager.current;
        if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element

        if (this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
            $.ui.plugin.call(this, 'out', [event, this.ui(draggable)]);
            this.element.triggerHandler("dropout", [event, this.ui(draggable)], this.options.out);
        }

    },

    _drop: function(event,custom) {

        var draggable = custom || $.ui.ddmanager.current;
        if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element

        var childrenIntersection = false;
        this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
            var inst = $.data(this, 'droppable');
            if(inst.options.greedy && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)) {
                childrenIntersection = true; return false;
            }
        });
        if(childrenIntersection) return false;

        if(this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
            $.ui.plugin.call(this, 'drop', [event, this.ui(draggable)]);
            this.element.triggerHandler("drop", [event, this.ui(draggable)], this.options.drop);
            return this.element;
        }

        return false;

    },

    plugins: {},

    ui: function(c) {
        return {
            draggable: (c.currentItem || c.element),
            helper: c.helper,
            position: c.position,
            absolutePosition: c.positionAbs,
            options: this.options,
            element: this.element
        };
    }

});

$.extend($.ui.droppable, {
    version: "@VERSION",
    defaults: {
        activeClass: null,
        cssNamespace: 'ui',
        greedy: false,
        hoverClass: null,
        scope: 'default',
        tolerance: 'intersect'
    }
});

$.ui.intersect = function(draggable, droppable, toleranceMode) {

    if (!droppable.offset) return false;

    var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
        y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
    var l = droppable.offset.left, r = l + droppable.proportions.width,
        t = droppable.offset.top, b = t + droppable.proportions.height;

    switch (toleranceMode) {
        case 'fit':
            return (l < x1 && x2 < r
                && t < y1 && y2 < b);
            break;
        case 'intersect':
            return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
                && x2 - (draggable.helperProportions.width / 2) < r // Left Half
                && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
                && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
            break;
        case 'pointer':
            var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
                draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
                isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
            return isOver;
            break;
        case 'touch':
            return (
                    (y1 >= t && y1 <= b) ||    // Top edge touching
                    (y2 >= t && y2 <= b) ||    // Bottom edge touching
                    (y1 < t && y2 > b)        // Surrounded vertically
                ) && (
                    (x1 >= l && x1 <= r) ||    // Left edge touching
                    (x2 >= l && x2 <= r) ||    // Right edge touching
                    (x1 < l && x2 > r)        // Surrounded horizontally
                );
            break;
        default:
            return false;
            break;
        }

};

/*
    This manager tracks offsets of draggables and droppables
*/
$.ui.ddmanager = {
    current: null,
    droppables: { 'default': [] },
    prepareOffsets: function(t, event) {

        var m = $.ui.ddmanager.droppables[t.options.scope];
        var type = event ? event.type : null; // workaround for #2317
        var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();

        droppablesLoop: for (var i = 0; i < m.length; i++) {

            if(m[i].options.disabled || (t && !m[i].options.accept.call(m[i].element,(t.currentItem || t.element)))) continue;    //No disabled and non-accepted
            for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
            m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue;                                     //If the element is not visible, continue

            m[i].offset = m[i].element.offset();
            m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };

            if(type == "dragstart" || type == "sortactivate") m[i]._activate.call(m[i], event);                                         //Activate the droppable if used directly from draggables

        }

    },
    drop: function(draggable, event) {

        var dropped = false;
        $.each($.ui.ddmanager.droppables[draggable.options.scope], function() {

            if(!this.options) return;
            if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
                dropped = this._drop.call(this, event);

            if (!this.options.disabled && this.visible && this.options.accept.call(this.element,(draggable.currentItem || draggable.element))) {
                this.isout = 1; this.isover = 0;
                this._deactivate.call(this, event);
            }

        });
        return dropped;

    },
    drag: function(draggable, event) {

        //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
        if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);

        //Run through all droppables and check their positions based on specific tolerance options

        $.each($.ui.ddmanager.droppables[draggable.options.scope], function() {

            if(this.options.disabled || this.greedyChild || !this.visible) return;
            var intersects = $.ui.intersect(draggable, this, this.options.tolerance);

            var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
            if(!c) return;

            var parentInstance;
            if (this.options.greedy) {
                var parent = this.element.parents(':data(droppable):eq(0)');
                if (parent.length) {
                    parentInstance = $.data(parent[0], 'droppable');
                    parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
                }
            }

            // we just moved into a greedy child
            if (parentInstance && c == 'isover') {
                parentInstance['isover'] = 0;
                parentInstance['isout'] = 1;
                parentInstance._out.call(parentInstance, event);
            }

            this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
            this[c == "isover" ? "_over" : "_out"].call(this, event);

            // we just moved out of a greedy child
            if (parentInstance && c == 'isout') {
                parentInstance['isout'] = 0;
                parentInstance['isover'] = 1;
                parentInstance._over.call(parentInstance, event);
            }
        });

    }
};

/*
 * Droppable Extensions
 */

$.ui.plugin.add("droppable", "activeClass", {
    activate: function(event, ui) {
        $(this).addClass(ui.options.activeClass);
    },
    deactivate: function(event, ui) {
        $(this).removeClass(ui.options.activeClass);
    },
    drop: function(event, ui) {
        $(this).removeClass(ui.options.activeClass);
    }
});

$.ui.plugin.add("droppable", "hoverClass", {
    over: function(event, ui) {
        $(this).addClass(ui.options.hoverClass);
    },
    out: function(event, ui) {
        $(this).removeClass(ui.options.hoverClass);
    },
    drop: function(event, ui) {
        $(this).removeClass(ui.options.hoverClass);
    }
});

})(jQuery);