學習 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綁定事件的問題。

本文參考了 世紀之光 這篇文章的實現,特此感謝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM