前言

工作中用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

ExtJS框架基础:事件模型及其常用功能的更多相关文章

  1. Django框架——基础之模型系统(ORM的介绍和字段及字段参数)

    1.ORM简介 1.1 ORM的概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM ...

  2. Django框架基础-MTV模型

    一个小问题: 什么是根目录:就是没有路径,只有域名..url(r'^$') 补充一张关于wsgiref模块的图片 一.MTV模型 Django的MTV分别代表: Model(模型):和数据库相关的,负 ...

  3. Django框架——基础之模型系统(ORM相关操作)

    ------------恢复内容开始------------ 1.必定会的十三条! 1.1记忆方法一:(按字母顺序记忆)   <1> all(): 查询所有结果 <2> cou ...

  4. python轻量级orm框架 peewee常用功能速查

    peewee常用功能速查 peewee 简介 Peewee是一种简单而小的ORM.它有很少的(但富有表现力的)概念,使它易于学习和直观的使用. 常见orm数据库框架 Django ORM peewee ...

  5. 深入浅出ExtJS 第二章 Ext框架基础

    2.1 面向对象的基础架构(对象模型) 2.1.1 创建类 >.定义一个类: Ext.define('demo.Demo',{ name:'Lingo', hello:function () { ...

  6. 【JavaScript框架封装】实现一个类似于JQuery的基础框架、事件框架、CSS框架、属性框架、内容框架、动画框架整体架构的搭建

    /* * @Author: 我爱科技论坛 * @Time: 20180715 * @Desc: 实现一个类似于JQuery功能的框架 * V 1.0: 实现了基础框架.事件框架.CSS框架.属性框架. ...

  7. JAVA基础语法:常用功能符以及循环结构和分支结构(转载)

    3.JAVA基础语法:常用功能符以及循环结构和分支结构 1.常用功能符 注释 ("文字"是被注释的部分) //文字 单行注释 /文字/ 多行注释 算术运算符 + - * / / 整 ...

  8. JavaScript常用的事件模型

    一.事件绑定模型 DOM0事件模型 1.内联模型(行内绑定):将函数名直接作为HTML标签中事件属性的属性值 <button id="btn" onclick="f ...

  9. Javascript事件模型系列(一)事件及事件的三种模型

    一.开篇 在学习javascript之初,就在网上看过不少介绍javascript事件的文章,毕竟是js基础中的基础,文章零零散散有不少,但遗憾的是没有看到比较全面的系列文章.犹记得去年这个时候,参加 ...

随机推荐

  1. Django中ORM介绍和字段及字段参数 Object Relational Mapping(ORM)

    Django中ORM介绍和字段及字段参数   Object Relational Mapping(ORM) ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简 ...

  2. .NET 控制Windows文件和目录访问权限研究(FileSystemAccessRule)

    前一段时间学习了.net 控制windows文件和目录权限的相关内容,期间做了一些总结.想把这方面的研究跟大家分享,一起学习.其中不免得有些用词不太标准的地方,希望大家留言指正,我加以修改. 首先,我 ...

  3. [20171225]RMAN-06808: SECTION SIZE cannot be used when piece limit is in effect.txt

    [20171225]RMAN-06808: SECTION SIZE cannot be used when piece limit is in effect.txt --//朋友拿我的一些例子来测试 ...

  4. python第二十三天-----作业中

    #!usr/bin/env python #-*-coding:utf-8-*- # Author calmyan import os ,sys,time from core import trans ...

  5. 如何使用C语言的面向对象

    我们都知道,C++才是面向对象的语言,但是C语言是否能使用面向对象的功能? (1)继承性 typedef struct _parent { int data_parent; }Parent; type ...

  6. HTTP协议响应码及get请求和post请求比较

    HTTP协议响应码 1XX:信息响应类,表示接收到请求并且继续处理 2XX:处理成功响应类,表示动作被成功接受.理解和接受 200 OK:表示从客户端发来的请求在服务器端被正常处理了 204 No C ...

  7. Python基础知识:while循环

    1.在循环中使用continue输出1-10之间的奇数 num=0 while num <10: num += 1 if num %2 == 0: #--%--运算符,相除返回余数 contin ...

  8. February 10th, 2018 Week 6th Saturday

    It is not enough to have a good mind. The main thing is to use it well. 头脑聪明还不够,重要的是好好运用. From Rene ...

  9. ABP模块运行解析

    从官方创建一份ASP.NET CORE 2.0的项目,并加入源码调试,可以看出如下图的加载顺序 1.ABP是通过什么样的机制加载的 既然ABP中模块需要添加DLL到引用中,又要加入DependsOn在 ...

  10. ZooKeeper学习总结 第二篇:ZooKeeper深入探讨

    其实zookeeper系列的学习总结很早就写完了,这段时间在准备找工作的事情,就一直没有更新了.下边给大家送上,文中如有不恰当的地方,欢迎给予指证,不胜感谢!. 1. 数据模型 1.1. 只适合存储小 ...