冠军

导航

学习 easyui 之四:禁用 linkbutton 问题

 

1.问题的起源

linkbutton 是 easyui 中常用的一个控件,我们可以使用它创建按钮。用法很简单,使用 a 元素,标记上 easyui-linkbutton 的类就可以看到按钮了。

<a id="btn" class="easyui-linkbutton">这是一个按钮</a>

看起来就是这个样子

或者使用代码方式。

$("#btn").linkbutton();

不过,点了也没有作用,如果希望有作用,那么,再为它添加一个事件处理吧。通常你会使用 jQuery 的方式添加事件处理函数。结果可能是这样。

脚本中的事件注册。

   $("#btn").click(function () {
        alert("我被点到了!");
    });

看起来一切正常。很快你发现一个新的需求,需要暂时禁用这个按钮,太简单了,easyui 中已经提供了禁用按钮的方法 disable,来让我们禁用一下。

代码变成了这样。

using( ["parser","linkbutton"], function(){
    $("#btn").linkbutton();

    $("#btn").click(function () {
        alert("我被点到了!");
    });

    $("#btn").linkbutton("disable");
});

按钮则变成了这样。

再点击一下,傻眼了吧!提示框照样弹了出来!。

2. linkbutton 是如何禁用按钮的

easyui 提供了 linkbutton 的源代码,所以,我们可以很方便地看一下,内部是如何实现禁用按钮的。

function setButtonState(domElem, disabled) {                    // 设置按钮状态
    var data = $.data(domElem, "linkbutton");                   // 获取对象的数据
    if (disabled) {                                             // 禁用按钮
        data.options.disabled = true;
        var href = $(domElem).attr("href");                     // 获取超级连接
        if (href) {
            data.href = href;                                   // 保存原来的超级链接
            $(domElem).attr("href", "javascript:void(0)");      // 重新设置
        }
        if (domElem.onclick) {                                  // 是否有点击事件处理
            data.onclick = domElem.onclick;
            domElem.onclick = null;                             // 取消掉
        }
        $(domElem).addClass("l-btn-disabled");                  // 使用样式
    } else {
        data.options.disabled = false;                          // 启用按钮
        if (data.href) {                                        // 恢复原来的超级链接
            $(domElem).attr("href", data.href);
        }
        if (data.onclick) {                                     // 恢复原来的点击事件处理
            domElem.onclick = data.onclick;
        }
        $(domElem).removeClass("l-btn-disabled");
    }
}

可以看到,禁用的时候,首先将原来注册的点击事件处理保存到 data.onlick 中,然后,将元素的 onclick 设置为 null;

已经禁用了,又为什么没有效果呢?

3. 不同的事件处理方式

在 DOM 中,存在着两类不同的事件处理方式,DOM Level 0 方式和 DOM Level 2 方式。

在DOM Level 0的API中,通过在HTML中设置属性,或者在JavaScript中设置一个对象的属性(property)的方法注册事件. 例如,注册事件可以这样进行。

document.getElementById("btn").onclick = function () {
    alert("我被点击啦!");
};

取消事件注册,可以通过将 onclick 重新赋予一个 null 来完成。

而在DOM Level 2模型中,你通过调用那个对象的addEventListener()方法注册事件处理程序.

document.getElementById("btn").addEventListener("click", function () {
    alert("我被点击啦!");
}, false);

取消注册可以通过 removeEventListener 进行。

问题在于,这两种方式是各自独立处理的,互相并不影响。

如果你查看一下 jQuery 的原代码,比如参考一下这篇文章,就会看到,jQuery 使用的正是 DOM Level 2 方式。

if (elem.addEventListener) {
    elem.addEventListener(type, eventHandle, false);

} else if (elem.attachEvent) {
    elem.attachEvent("on" + type, eventHandle);
}

对比 easyui 中对于 linkbutton 的处理代码,我们会看到,由于 easyui 中使用了 DOM Level 0 方式处理按钮的启用和禁用,而 jQuery 则使用 DOM Level 2 方式进行事件的注册,两种方式擦肩而过,所以,我们的禁用失效了。

使用  DOM Level 0 方式,我们可以通过事件名称,方便地取得原来注册的事件处理函数,保存起来,以便以后回复。这正是 linkbutton 现在已经完成的。

使用 DOM Level 2 方式,我们又应该如何获取已经注册的事件处理函数呢?我们有 addEventListener,有 removeEventListener。但是已经注册的事件处理在哪里呢?

让我们看看 jQuery 是如何处理的。

4. jQuery 将我们注册的事件处理函数保存到哪里去了?

在 jQuery 中,为每个 DOM 对象准备了一个数据缓存对象,这个对象的使用方式很巧妙。

首先 jQuery 创建了一个用来规定每个 DOM 对象保存数据的时候使用的属性名,为了唯一,这个属性名使用了各种方式来保证唯一性。

在 jQuery 2.0 中是这样定义的。

expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" )

在 jQuery 1.4.4 中是这样定义的。

expando: "jQuery" + jQuery.now(),

所以,最终,jQuery.expando 可能是类似这样的一个唯一的值:jQuery16101803968874529044,这个值用来作为 DOM 元素的一个扩展属性,避免与其他脚本库扩展属性的冲突。它的值则是这个 DOM 缓存的数据对象的编号。在 jQuery  上则定义了一个名为 cache 的全局缓存对象,使用这个编号来到 cache 对象上找到属于自己的数据缓存对象。

每个元素对应的数据缓存对象上的 events 属性中用来保存这个元素注册的事件处理程序。

events 对象上,再通过事件名称作为索引器,保存对应事件的处理数据,每个事件名称对应的数据是一个数组,数组中是一个一个的事件描述对象。在 jQuery 中定义如下。

handleObj = jQuery.extend({
    type: type,
    origType: origType,
    data: data,
    handler: handler,
    guid: handler.guid,
    selector: selector,
    needsContext: selector && jQuery.expr.match.needsContext.test(selector),
    namespace: namespaces.join(".")
}, handleObjIn);

其中的 handler 就是注册的事件处理函数。

(1 << -1) - 1 jQuery event(上)jQuery event(下)  有详细的说明,可以参考一下。

终于找到了,我们又如何拿到这个 click 事件的处理数组呢?

还有一些小问题要处理,本来,jQuery 提供了一个 $.data 函数可以让我们直接获取元素缓存的数据,包括 jQuery 内部使用的 events 数据。但是,从 jQuery 1.8 开始,使用 $.data 就不可以了。见 jQuery 1.8 Released 这篇文章

$(element).data(“events”): In version 1.6, jQuery separated its internal data from the user’s data to prevent name 
collisions. However, some people were using the internal undocumented “events” data structure
so we made it possible to still retrieve that via .data(). This is now removed in 1.8,
but you can still get to the events data for debugging purposes via $._data(element, "events").
Note that this is not a supported public interface;
the actual data structures may change incompatibly from version to version.

但是,依然提供了一个非支持的接口 $._data(element, "events") 来获取这些数据。

snandy 的 读jQuery之六(缓存数据) 中有比较详细的说明。

5. 修复 linkbutton 的启用和禁用

修改的方式,就是在原来的基础上,增加对于通过 DOM Level 2 方式注册的事件进行处理。

我们检查是否注册了 jQuery 的 click 事件处理函数,如果有,在数据缓存对象上增加一个名为  savedHandlers    的数组来保存原来的点击事件处理函数,然后,从原来的对象上取消已经注册的事件处理函数。

恢复的时候,检查数据对象上是否有保存的事件处理函数数组,如果存在,遍历这个数组,将这些事件处理函数重新注册到元素上。

function setButtonState(domElem, disabled) {                    // 设置按钮状态

    var data = $.data(domElem, "linkbutton");                   // 获取对象的数据
    if (disabled) {                                             // 禁用按钮
        data.options.disabled = true;
        var href = $(domElem).attr("href");                     // 获取超级连接
        if (href) {
            data.href = href;                                   // 保存原来的超级链接
            $(domElem).attr("href", "javascript:void(0)");      // 重新设置
        }
        if (domElem.onclick) {                                  // 是否有点击事件处理
            data.onclick = domElem.onclick;
            domElem.onclick = null;                             // 取消掉
        }
        var eventData = $(domElem).data("events") || $._data(domElem, 'events');
        if (eventData && eventData["click"]) {
            var clickHandlerObjects = eventData["click"];
            data.savedHandlers = [];
            for (var i = 0; i < clickHandlerObjects.length; i++) {
                if (clickHandlerObjects[i].namespace != "menu") {
                    var handler = clickHandlerObjects[i]["handler"];
                    $(domElem).unbind('click', handler);
                    data.savedHandlers.push(handler);
                }
            }
        }

        $(domElem).addClass("l-btn-disabled");                  // 使用样式
    } else {
        data.options.disabled = false;                          // 启用按钮
        if (data.href) {                                        // 恢复原来的超级链接
            $(domElem).attr("href", data.href);
        }
        if (data.onclick) {                                     // 恢复原来的点击事件处理
            domElem.onclick = data.onclick;
        }
        if (data.savedHandlers) {
            for (var i = 0; i < data.savedHandlers.length; i++) {
                $(domElem).click(data.savedHandlers[i]);
            }
        }

        $(domElem).removeClass("l-btn-disabled");
    }
}

6. 为 easyui 打个补丁

如果你希望使用压缩之后的 easyui 脚本库,对修改源代码并没有兴趣,可以到这里下载: 解决linkbutton组件disable方法无法禁用jQuery绑定事件的问题。

本文参考了 世纪之光 这篇文章的实现,特此感谢。

 

posted on 2013-05-13 23:05  冠军  阅读(45468)  评论(22编辑  收藏  举报