學習 easyui 之四:禁用 linkbutton 問題以後,顏色變灰,可是還能執行onclick事件

1.問題的起源

linkbutton 是 easyui 中經常使用的一個控件,咱們可使用它建立按鈕。用法很簡單,使用 a 元素,標記上 easyui-linkbutton 的類就能夠看到按鈕了。javascript

<a id="btn" class="easyui-linkbutton">這是一個按鈕</a>

看起來就是這個樣子html

或者使用代碼方式。java

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

不過,點了也沒有做用,若是但願有做用,那麼,再爲它添加一個事件處理吧。一般你會使用 jQuery 的方式添加事件處理函數。結果多是這樣。jquery

腳本中的事件註冊。數組

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

看起來一切正常。很快你發現一個新的需求,須要暫時禁用這個按鈕,太簡單了,easyui 中已經提供了禁用按鈕的方法 disable,來讓咱們禁用一下。緩存

直接調用linkbutton的"disable"屬性,代碼變成了這樣。dom

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

按鈕則變成了這樣。函數

再點擊一下,傻眼了吧!提示框照樣彈了出來!。ui

2. linkbutton 是如何禁用按鈕的

easyui 提供了 linkbutton 的源代碼,因此,咱們能夠很方便地看一下,內部是如何實現禁用按鈕的。this

function setButtonState(domElem, disabled) {                    // 設置按鈕狀態
    var data = $.data(domElem, "linkbutton");                   // 獲取easyui對象
    if (disabled) {                                             // 若是禁用
        data.options.disabled = true;                       //設置改easyui對象的options的disabled屬性爲true
        var href = $(domElem).attr("href");                     // 獲取該元素節點的href屬性
        if (href) {                                             //若是存在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    的數組來保存原來的點擊事件處理函數,而後,從原來的對象上取消已經註冊的事件處理函數。

恢復的時候,檢查數據對象上是否有保存的事件處理函數數組,若是存在,遍歷這個數組,將這些事件處理函數從新註冊到元素上。

修改方式分爲二種:

1.修改easyui的源代碼,把源代碼中的setButtonState()方法使用下面的這個替代:

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");
    }
}

2.爲easyui打個補丁,重寫enable和disable:

/**
 * linkbutton方法擴展
 * @param {Object} jq
 */
$.extend($.fn.linkbutton.methods, {
    /**
     * 激活選項(覆蓋重寫)
     * @param {Object} jq
     */
    enable: function(jq){
        return jq.each(function(){
            var state = $.data(this, 'linkbutton');
            if ($(this).hasClass('l-btn-disabled')) {
                var itemData = state._eventsStore;
                //恢復超連接
                if (itemData.href) {
                    $(this).attr("href", itemData.href);
                }
                //回覆點擊事件
                if (itemData.onclicks) {
                    for (var j = 0; j < itemData.onclicks.length; j++) {
                        $(this).bind('click', itemData.onclicks[j]);
                    }
                }
                //設置target爲null,清空存儲的事件處理程序
                itemData.target = null;
                itemData.onclicks = [];
                $(this).removeClass('l-btn-disabled');
            }
        });
    },
    /**
     * 禁用選項(覆蓋重寫)
     * @param {Object} jq
     */
    disable: function(jq){
        return jq.each(function(){
            var state = $.data(this, 'linkbutton');
            if (!state._eventsStore)
                state._eventsStore = {};
            if (!$(this).hasClass('l-btn-disabled')) {
                var eventsStore = {};
                eventsStore.target = this;
                eventsStore.onclicks = [];
                //處理超連接
                var strHref = $(this).attr("href");
                if (strHref) {
                    eventsStore.href = strHref;
                    $(this).attr("href", "javascript:void(0)");
                }
                //處理直接耦合綁定到onclick屬性上的事件
                var onclickStr = $(this).attr("onclick");
                if (onclickStr && onclickStr != "") {
                    eventsStore.onclicks[eventsStore.onclicks.length] = new Function(onclickStr);
                    $(this).attr("onclick", "");
                }
                //處理使用jquery綁定的事件
                var eventDatas = $(this).data("events") || $._data(this, 'events');
                if (eventDatas["click"]) {
                    var eventData = eventDatas["click"];
                    for (var i = 0; i < eventData.length; i++) {
                        if (eventData[i].namespace != "menu") {
                            eventsStore.onclicks[eventsStore.onclicks.length] = eventData[i]["handler"];
                            $(this).unbind('click', eventData[i]["handler"]);
                            i--;
                        }
                    }
                }
                state._eventsStore = eventsStore;
                $(this).addClass('l-btn-disabled');
            }
        });
    }
});

須要注意的是:

下面的兩種寫法有區別:

(1).onclick事件放在<a>標籤元素中.

:<a onClick="addEprjListNormConsumption();" href="javascript:void(0);"  class="easyui-linkbutton "/>,

能夠$(".needDisable").linkbutton("disable"),禁用linkbutton的點擊事件

(2).onclick事件單獨列出來,例如:

<a href="javascript:void(0);"  class="easyui-linkbutton _mu"/>

$("._mu").click(function(){...});

這樣的話,這麼寫$(".needDisable").linkbutton("disable")會禁用按鈕,可是不會禁用onclick事件,解決辦法,在其onclick事件中,寫上return false;便可

參考:http://www.easyui.info/archives/792.html

相關文章
相關標籤/搜索