前言
工作中用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.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组件添加事件处理函数
添加原生浏览器事件处理函数
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