小强赵的个人站点

精进自己,服务他人

jQuery UI 的 Drop 嵌套解决方案

拖和放是连个复合事件,由多个事件组成,所以不能用普通的事件处理来处理拖放遇到的问题,其中一个问题就是接收拖拽的元素是一组层叠嵌套的元素时,会出现多次响应 drop 事件的问题,这里提供一个解决此问题的方案。

解决思路

通过一个静态辅助对象来解决此问题。droppable 事件有 overout 事件,事件触发时记录进入了哪些元素又从哪些元素中出来,这些记录分别保存在两个数组中,每次触发事件后调用一个函数处理这些数据,得到最后 hover 的元素,这个元素就可以被视为需要接收拖拽的元素。

代码

静态类的代码如下:

var dragDropFramework = {
    // 当处于激活态时需要添加的Class
    activeClass: 'active',
    // 处于激活状态的 DOM 元素,默认为 null,没有激活元素时也为 null
    activeElement: null,
    /**
     * 清理本次或上次拖拽产生的数据
     */
    clean: function () {
        this._overElements = [];
        this._outElements = [];
        // 先移除当前激活态元素的激活样式
        if (this.activeElement !== null) {
            $(this.activeElement).removeClass(this.activeClass);
        }
        this.activeElement = null;
    },
    /**
     * 将 hover 的元素推送到队列中,并且计算当前的激活元素
     * 注:为了解决“内层元素溢出时拖动时可能先进入内层元素后进入外层元素”的问题,
     *    添加 over 元素时会做内外排序处理
     *
     * @param {Object} element over过的元素
     */
    pushOverElements: function (element) {
        // 如果新 hover 的元素包含已存在的元素,那么放在已存在元素的前面
        var overElements = this._overElements;
        for (var i = 0; i < overElements.length; i++) {
            var overItem = overElements[i];
            // 判断 overItem 是否在 element中
            if (this._isIn(overItem, element)) {
                overElements.splice(i, 0, element);
                break;
            }
        }
        if (i === overElements.length) {
            this._overElements.push(element);
        }
        this._resetActiveElement();
    },
    /**
     * 将 out 的元素推送到队列中,并且计算当前的激活元素
     *
     * @param {Object} element out的元素
     */
    pushOutElements: function (element) {
        this._outElements.push(element);
        this._resetActiveElement();
    },
    /**
     * 激活元素改变时触发的默认事件
     *
     * @param {Object} oldElement 改变前的激活元素
     * @param {Object} newElement 改变后的激活元素
     * @private
     */
    _activeElementChange: function (oldElement, newElement) {
        var activeClass = this.activeClass;
        // 先移除当前激活态元素的激活样式
        if (oldElement !== null) {
            $(oldElement).removeClass(activeClass);
        }

        // 添加当前激活态元素的激活样式
        if (newElement !== null) {
            $(newElement).addClass(activeClass);
        }
    },
    /**
     * 判断参数是否为函数
     *
     * @param {Function} fn 待判断的函数
     * @returns {boolean} 是否是函数
     * @private
     */
    _isFuntion: function (fn) {
        return Object.prototype.toString.call(fn) === 'object Function';
    },
    /**
     * 判断第一个元素是否在第二个元素中
     *
     * @param {Object} son 子元素
     * @param {Object} father 父元素
     * @returns {boolean} 判断结果
     * @private
     */
    _isIn: function (son, father) {
        var result = false;
        var parent;
        do {
            parent = son.parentNode;
            if (parent === father) {
                result = true;
                break;
            }
            son = parent;
        } while (parent.tagName !== 'BODY');

        return result;
    },
    // hover 过的元素队列,当 out 队列中有 hover 种的元素时或将两边的元素都移除
    _overElements: [],
    // out  的元素队列
    _outElements: [],
    /**
     * 根据 over 和 out 队列数据,重设激活态元素
     * 注:当激活元素有改变时,会调用 activeElementChange 自定义回调函数
     * @private
     */
    _resetActiveElement: function () {
        // 将 out 队列中有的元素从 over 和 out 队列中移除
        var outElements = this._outElements;
        var overElements = this._overElements;
        for (var i = 0; i < outElements.length; i++) {
            var outItem = outElements[i];
            for (var j = 0; j < overElements.length; j++) {
                var overItem = overElements[j];
                if (outItem === overItem) {
                    outElements.splice(i, 1);
                    overElements.splice(j, 1);
                    break;
                }
            }
        }
        // 取最后 hover 的元素
        var result;
        if (overElements.length !== 0) {
            result = overElements[overElements.length - 1];
        }
        else {
            result = null;
        }
        var oldActiveElement = this.activeElement;
        this.activeElement = result;
        if (oldActiveElement !== result) {
            // 自定义回调函数
            if (this._isFuntion(this.activeElementChange)) {
                this.activeElementChange(oldActiveElement, result);
            }
            this._activeElementChange(oldActiveElement, result);
        }
    }
};

示例

示例模拟了三种情况:

这三种情况基本可以覆盖实际应用的全部场景,查看示例,打开控制台可以到更多关于事件的信息。