ExtJS框架基礎:事件模型及其常用功能


前言

工作中用ExtJS有一段時間了,Ext豐富的UI組件大大的提高了開發B/S應用的效率。雖然近期工作中天天都用到ExtJS,但很少對ExtJS框架原理性的東西進行過深入學習,這兩天花了些時間學習了下。我並不推薦大家去研究ExtJS框架的源碼,雖然可以學習其中的思想和原理,但太浪費精力了,除非你要自己寫框架。

對於ExtJS這種框架,非遇到“雜症”的時候我覺得也沒必要去研究其源碼和底層的原理,對其一些機制大致有個概念,懂得怎么用就行,這也是本篇博文的主要目的。

Ext自己的事件機制

Ext中的事件遵循樹狀模型,和事件相關的類主要有這么幾個:Ext.util.Observable、Ext.lib.Event、Ext.EventManager和Ext.EventObject。

Ext使用Ext.lib.Event、Ext.EventManager和Ext.EventObject對原生瀏覽器事件進行了封裝,最后給我們用的是一套統一的跨瀏覽器的通用事件接口。HTML元素本身已經支持事件,為什么基本上所有的主流JS框架都要實現自己的事件機制呢?一個最主要的原因是HTML元素對事件的處理是通過簡單的單一綁定實現的,如果不進行封裝,事件只能綁定到一個事件處理句柄。如下面代碼所示:

var e = document.getElementById("test");
e.onclick = function() { alert("handler1") };
e.onclick = function() { alert("handler2") };

單擊test按鈕后會發現只會彈出一個顯示"handler2"的提示框,因為第一個被覆蓋。而使用像Ext、jQuery這樣的框架就不用擔心這個問題,同一個事件可以依次綁定多個事件處理句柄,如下代碼所示:

Ext.onReady(function () {
    var test = Ext.get("test");
    test.on("click", function () {
        alert("handler1");
    });
    test.on("click", function () {
        alert("handler2");
    });
});

Ext實現自己的事件機制,原因很多,比如為了兼容不同瀏覽器之間的差異等。Ext對原生瀏覽器事件的封裝都在上面所說的幾個類中,如果在項目中要熟練應用Ext,是非常有必要了解一下和事件相關的類和常用函數的。下面開始介紹這些類和它們的功能。

Ext.util.Observable

Ext.util.Observable在Ext事件模型中有着舉足輕重的地位,位於Ext組件的頂端,為Ext組件提供處理事件的最基本的功能。所有繼承自Ext.util.Observable類的控件都可以支持事件。可以為這些繼承了Ext.util.Observable的對象定義一些事件,然后為這此事件配置監聽器。當某個事件觸發時,Ext會自動調用對應的監聽器,這些就是Ext的事件模型。
下面通過繼承Ext.util.Observable來實現一個支持事件的對象:
Ext.onReady(function () {
    //定義一個Person類。
    function Person(name) {
        this.name = name;
        this.addEvents("walk", "eat");
        this.superclass.constructor.call(this);
    }

    //1、讓Person繼承Ext.util.Observable的所有屬性,
    //   這樣Person類構造器中的addEvents和Person.superclass.constructor.call()在實例創建時才會起作用。
    //   Person的實例就可以應用Ext的事件相關的on、un等方法和在Person類構造器中的addEvents和Person.superclass.constructor.call()了。
    //2、添加一個info()函數,讓它返回Person信息。
    Ext.extend(Person, Ext.util.Observable, {
        info: function (event) {
            return this.name + " is " + (event ? "ing" : "doing nothing") + ".";
        }
    });

    //1、創建一個Person實例,然后為它的事件配置好監聽器。
    //2、on是addListener的簡寫,un是removeListener簡寫
    var person = new Person("Liam");
    person.on("walk", function () {
        this.state = "walk";
        Ext.Msg.alert("event", this.name + " is walking.");
    });
    person.on("eat", function (meal) {
        this.state = "eat";
        Ext.Msg.alert("event", this.name + " is eating " + meal + ".");
    });

    //測試效果
    Ext.get("btnWalk").on("click", function () {
        person.fireEvent("walk");
    });
    Ext.get("btnEat").on("click", function () {
        person.fireEvent("eat", "breakfast");
    });
    Ext.get("btnInfo").on("click", function () {
        Ext.Msg.alert("info", person.info(person.state));
    });
});

以上代碼展示了在Ext中如何通過繼承Ext.util.Observable給一個類自定義事件,到這,我們大概也了解了addListener/on、addEvents和fireEvent這些函數的基本用法,removeListener/un函數相關內容還會在本文后面介紹。如果要了解Ext.util.Observable的其他細節,可看看Ext官方API文檔的介紹。

Ext.lib.Event

Ext.lib.Event是一個工具類,它封裝了不同瀏覽器的事件處理函數,為上層組件提供了統一功能接口。
對於這個工具類,Ext自帶的文檔中沒有關於這個類的說明,實際中也很少直接用到這個類,只是與事件相關的那些操作最后都會歸結為對這些底層函數的調用。
Ext.lib.Event中定義了以下幾個主要函數。

getX()、getY()、getXY(),獲得發生的事件在頁面中的坐標位置:

Ext.get("test").on("click", function () {
    alert(this.getX() + "," + this.getY());
});

getTarget(),返回事件的目標元素,該函數用來統一IE和其他瀏覽器使用的e.target和e.srcElement:

Ext.get("test").on("click", function (e) {
    var test = e.getTarget();
    alert(test.value);
});

on()和un(),這兩個函數就不用多說了。

preventDefault(),用於取消瀏覽器當前事件所執行的默認操作,比如阻止頁面跳轉。使用這個函數,我是不是可以阻止彈出瀏覽器鼠標右鍵菜單呢?我用下面的代碼試了下,結果右鍵菜單並沒有被阻止,誰能告訴我為什么?

//鼠標右鍵事件沒有被阻止?
Ext.getDoc().on("mousedown ", function (e) {
    if (e.button == "2")
        e.preventDefault();
});

stopPropagation(),停止事件傳遞。比如divTest元素訂閱了click事件,它的子元素btnTest被click時,父元素divTest的click事件也會被觸發,stopPropagation()就是用來阻止這種事件冒泡的發生:

Ext.get("divTest").on("click", function () {
    alert("divTest clicked!");
});
Ext.get("btnTest").on("click", function (e) {
    alert("btnTest clicked!");
    //阻止事件冒泡
    e.stopPropagation();
});

 stopEvent(),停止一個事件,相當於調用preventDefault()和stopPropagation()兩個函數。

另外還有一些幾乎用不上的函數onAvailable()、getRelatedTarget()等,就不再一一介紹了。

再次說明一下,Ext.lib.Event這個類實際中很少直接用到,用的只是上面講的一些底層通用函數,並供一些其它和事件相關的類如Ext.EventManager和Ext.EventObject的底層的調用。

Ext.EventManager

Ext.EventManager,作為事件管理器,定義了一系列事件相關的處理函數。其中最常用的就是onDocumentReady和onWindowResize了。

我們常用的Ext.onReady()就是Ext.EventManager.onDocumentReady()的簡寫形式,它會在頁面文檔渲染完畢但圖片等資源文件還未下載時調用啟動函數。

這里有必要提一下眾所周知人人共憤的window.onresize事件:

function resizeProcess(width, height) {
    var p = document.createElement("p");
    p.innerText ="時間:" + new Date().toLocaleTimeString() + ", 寬:" + width + ", 高:" + height;
    document.body.appendChild(p);
}
//原生瀏覽器resize事件
window.onresize = function () {
    resizeProcess(document.documentElement.clientWidth, document.documentElement.clientWidth);
}

當為window.onresize添加了事件處理函數resizeProcess后,會發現resizeProcess會被執行多次,尤其是IE6、IE7、IE8,還會出現假死,動不動就崩掉。

如圖,IE8瀏覽器會直接死掉。真心深惡通絕IE6、IE7、IE8,要是有朝一日能因為IE11的出現,IE6到IE10都被消滅,那該是多么大快人心的事!
window.onresize事件處理函數被多次乃至無數次觸發的問題,網上有不少解決方案,但稍微理想點的方案用起來都挺麻煩。Ext.EventManager下的onWindowResize事件處理函數就非常好的解決了這個問題:

Ext.onReady(function () {
    function resizeProcess(width, height) {
        var p = document.createElement("p");
        p.innerText = "時間:" + new Date().toLocaleTimeString() + ", 寬:" + width + ", 高:" + height;
        document.body.appendChild(p);
    }
    //Ext封裝的resize事件
    Ext.EventManager.onWindowResize(function (width, height) {
        resizeProcess(width, height);
    });
});

 

如圖,每次改變窗口大小,resizeProcess只執行了一次。

Ext.EventManager還有on/addListener、un/removeListener等函數,這些函數都是都過Ext.lib.Event實現的,這里就不再累述了。

Ext.EventObject

Ext.EventObject是對事件的封裝,它提供了豐富的工具函數,幫助我們獲得事件相關的信息。通過Ext.EventObject幫助文檔可以了解到,它包含的許多函數都與Ext.lib.Event中的函數功能是相同甚至同名的,如getPageX()、getPageY()、getPageXY()和getTarget()等,這些函數實際上都是通過Ext.lib.Event實現的。

Ext.EventObject對Ext.lib.Event擴展的部分是對鼠標事件和按鍵事件的增強,定義了一系列按鍵,可以用來判斷某個鍵是否被按下:

Ext.get("text").on("keypress", function (e) {
    if (e.getKey() == Ext.EventObject.SPACE) {
        Ext.Msg.alert("提示", "你按了空格鍵!");
    }
});

Ext.EventObject將瀏覽器事件和自定義事件結合在一起使用,是對事件的封裝。如果要獲得瀏覽器原始的事件,可通過Ext.EventObject的browserEvent獲得。但這種原生事件在不同瀏覽器中可能會有很大差異,所以Ext.EventObject雖然提供該功能,但一般不建議使用。

給Ext組件添加事件處理函數

添加原生瀏覽器事件處理函數

我們已經知道可以通過 on/addListener的方式給HTML元素添加事件處理函數,Ext組件也可以通過這種方式添加,如下代碼所示:
var text = new Ext.form.TextField({
    id: "text", renderTo: Ext.getBody()
});
Ext.get("text").on("mouseover", function (e) {
    alert("mouse over.");
});
//也可以一次添加多個事件處理函數:
Ext.get("text").on({
    "mouseover": function (e) {
        alert("mouse over.");
    },
    "mouseout": function (e) {
        alert("mouse out.");
    }
});

這種方式可以給任何原生瀏覽器所支持的事件添加處理函數。但這種方式不能用於容器類的Ext組件,如Ext.form.FieldSet、Ext.form.FormPanel和Ext.Toolbar等。

添加Ext組件事件處理函數

幾乎所有Ext組件根據自身的特性對原生事件都行了擴展,另外封裝了一套屬於自己的事件,這些事件的處理函數會能接收到與該組件相關的事件參數信息。下面代碼是給Ext組件添加事件的兩種方式:

var text1 = new Ext.form.TextField({
    id: "text1", renderTo: Ext.getBody()
});
//任何一個關於導航類鍵(arrows、tab、enter、esc等)被敲擊則觸發此事件
Ext.getCmp("tex1t").on("specialkey", function (field,e) {
    alert(field.getValue() + "," + e.getKey());
});

//也可以在組件創建的時候添加事件處理函數:
var text2 = new Ext.form.TextField({
    id: "text2", renderTo: Ext.getBody(),
    listeners: {
        change: function (field, newValue, oldValue) {
            alert("change:" + newValue);
        },
        blur: function (field) {
            alert("blur:" + field.getValue());
        }
    }
});

但這種方式並不支持所有的原生瀏覽器事件,比如給 Ext.form.TextField 組件通過上面的方式添加 mosuseover 事件處理函數是沒有效果的。

還有一種通過 handler 屬性給 Ext 按鈕組件添加事件的方式,這種方式只針對Ext按鈕組件,如下:

var button = new Ext.Button({
    id: 'button',
    text:'按鈕',
    renderTo: Ext.getBody(),
    handler: function () {
        alert("Clicked!!!");
    }
});

移除事件處理函數

我們已經知道可通過un/removeListener移除某個事件處理函數。值得注意的事,對於原生瀏覽器事件,用Ext.fly獲得元素的方式添加的事件處理函數必須用Ext.fly獲得元素的方式移除,同理,Ext.get也是一樣。但一般我們用Ext.fly而不用Ext.get獲得元素的方式添加事件處理函數,原因Ext.fly更省內存。對於Ext組件事件,則必須通過Ext.getCmp獲得組件的方式移除事件處理函數。如下代碼所示:

var text = new Ext.form.TextField({
    id: "text", renderTo: Ext.getBody(),
    listeners: {
        change: function (field, n, o) {
            alert("new value : " + n);
        }
    }
});
//事件處理函數
var handlerFn = function (e) {
    alert("mouse over.");
};

//添加mouseover事件處理函數。
Ext.get("text").on("mouseover", handlerFn);
//移除mouseover事件指定引用的處理函數。
Ext.get("text").removeListener("mouseover", handlerFn);
//移除mouseover事件所有的處理函數。
Ext.get("text").removeListener("mouseover");
//用fly獲得元素的方式不能移除mouseover處理函數,因為該處理函數是通過get獲取元素添加的。
Ext.fly("text").removeListener("mouseover");
//同樣,用getCmp獲得組件的方式也不能移除mouseover處理函數。
Ext.getCmp("text").removeListener("mouseover");
//移除text元素所有原生瀏覽器事件的所有處理函數。
Ext.get("text").removeAllListeners();
//獲得組件的方式移除change事件所有的處理函數。
Ext.getCmp("text").removeListener("change");

對事件的一些額外的控制

事件的額外控制包括讓事件只被觸發一次、延遲事件處理和控制多次觸發事件的間隔等。通過on/addListener函數的第4個參數的屬性來實現,讓我們通過下面代碼來看看常見的幾個:

var button = new Ext.Button({
    id: 'button',
    text: '按鈕',
    renderTo: Ext.getBody()
});
button.on("click",
    function () {
        var el = document.createElement("p");
        el.innerHTML = new Date().toLocaleTimeString();
        document.body.appendChild(el);
    }, this, {
        single: true,//只會執行一次單擊事件。
        buffer: 1000, //間隔1秒響應,在響應前點擊無效。
        delay: 1000,//從事件觸發開始,1后才會執行處理函數。
        stopPropagattion: true,//事件不會向上傳遞(即停止事件冒泡)。
        preventDefault: true //停止事件默認操作。
        //...
    }
);

 

結束語

ExtJS的事件模型比較復雜,提供的事件處理函數也非常之多,本文短短篇幅不可能面面具到,只是把常用的做了簡單介紹。本人用ExtJS也不久,不免有錯差。
希望園友們不吝指教,多多交流,隨手點個推薦,以助大家在ExtJS學習之路上快束進步。

 

參考:
《深入淺出 Ext JS》
Ext JS API


免責聲明!

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



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