返回深入学习ExtJS 2.2开发系列连载教程目录
既然我们已经通过EventObject来解决不同浏览器的兼容
问题,那么我们怎么用到这个EventObject呢。对于这个EventObject,我们不要做任何的处理,在写监听函数时和以前使用传入的event参数是一模一样的。不同的就是这个参数已经被暗中替换成了EventObject。那是怎么替换的呢?在讲其原理之前,我们先来改写一下4.1节的例子,在那节中,为了兼容,我们写了很多的代码,现在就可以省略。
<a id="test1" style="cursor:hand;" href="http://www.sina.com" >test1,click me!</div>
<script type="text/javascript">
function clickme(e)
{
alert(''normal2:''+ this.id);
e.preventDefault();
} Ext.get(''test1'').on(''click'',clickme);
</script>
在这里我们可以看出只要通过Ext元素的on方法就可以给某个事件注册监听函数。在写监听函数还是和以前一样,不同的是对e的属性的引用。这里的e现在也包含了window.event。它的的一些方面和属性都经过调整而达到兼容性。我们还可以看到on
注册函数也达到浏览器的兼容。
下面我们就三个方面来介绍事件的处理流程:addEvent,fireEvent,removeEvent。
addListener
代码4.5中,我们已经实现了通过Ext的事件机制来为一个元素注册事件监听。使用注册事件的方法是比较的简单的。我们可能通过Ext的元素(包裹过原生DOM元素)的on方法来为其本身的某些事件注册监听函数。我们还可以通过Ext.EventManager这个静态类的on方法来为某元素的某纛事件注册监听函数。如果这样不满足的话,我们还可以通过Ext.addBehaviors来为多个元素的某一些事件注册函数。
我们在对于一个元素进行注册Listener时,我们会调用元素的on方法,即其addListener方法,这个方法只是把自身的dom元素做为第一个参数传入到Ext.EventManager.on中去。而 Ext.EventManager.on 和Ext.EventManager.addListener是相等的,最终的注册监听的任务落在Ext.EventManager.addListener身上。
元素的on方法是一个很重要的方法,它可以通过三种方式来注册事件。Ext.EventManager.on方法是一样的。它们都支持三种使用方式:
1、标准方式:el.on(eventName, fn, scope, options)
它有四个参数,第一个参数是指定事件名,如’click’等。第二参数fn是要进行监听处理的函数或函数名。第三个参数是scope,指定监听函数的作用域,可选项。第四个参数是该监听函数的一些配置操作对象,可选项。如下下表中配置是Ext可以解析的。
配置项 | 说明 |
scope | 和第三个参数是相同的,用来指定监听函数作用域。 |
delay | 指定该函数推迟多长时间后才运行(ms为单位)。 |
buffer | 指定函数推迟的时间,在没有运行之后,后来的注册函数可以修改它。 |
single | 指定该函数是否只运行一次,然后就删除自身。boolen值。 |
normalized | 如果指定为false,就把浏览器事件event传入到监听函数中去。 |
stopPropagation | True就会阻止事件冒泡向上传递。 |
preventDefault | True就会阻止缺省的动作发生。 |
stopEvent | True就会进行preventDefault、stopPropagation两个操作。 |
delegate | Simpe类型的CSS Selector,用来在其父元素中找到合适的元素。 |
表4.6 注册函数的配置项
对于options,我们还可以指定除这些配置项之后的其它的配置,因为它会作为参数传给监听函数的第三个参数。我们来把代码4.5中的JS改成如下:
function clickme(e,t,o) {
alert(''nodeName:''+ t.nodeName+"."+o.privateMsg); }
Ext.get(''test1'').on(''click'',clickme,this,
{preventDefault:true,privateMsg:''privateMsg''});
在这里我们可以看到 preventDefault是在注册监听函数时通过配置项来进行的。同时对于每一个监听函数,都有三个参数,第一个参数是已经包裹过的event。第二个是事件源,即this.target或this.srcElement指向的元素。这个可以通过delegate配置来指定其父节点。
第三个参数是也就是我们传入配置对象。在上例中,我们传入一个私有配置,在监听函数中,我们就可以通过第三个参数来获取它。
2、私有配置的多事件注册:el.on({eName1:{//相关配置},etName2:{//相关配置}})。
这种形式可以只要一个为对象类型的参数,它把函数名和配置都集中在这个参数中。这个参数采用事件名和其相关配置对象为值的属性对来组成一个多事件注册的对象。事件名对应的值是一个配置对象,它和标准方式中options参数相似,不同是的我们要在这个配置对象加上监听函数配置项,它是通过fn的属性名。如下面例子:
el.on({
''click'' :
{ fn: this.onClick, scope: this, delay: 100},
''mouseover'' :
{fn: this.onMouseOver, scope: this },
''mouseout'' :
{ fn: this.onMouseOut, scope: this } });
3、共享配置的多事件注册
这个和第二种方式差不多,因为在多个事件中,我们可以有很多配置都是共同的,这种配置方式就是把这相同的配置共享出来,它也是一个配置参数,不同是它的每个事件名都对应着一个函数,而配置项与它们并列地存放在该对象中。它不能加有私有配置参数,如下例:
el.on({
''click'' : this.onClick,
''mouseover'' : this.onMouseOver,
''mouseout'' : this.onMouseOut,
scope: this
});
上面是el.on函数的使用方法,Ext.EventManager.on和它是一样,只不过是多了一个参数。那么Ext.addBehaviors呢,它怎么为多个元素注册多个事件呢。它采用CSS Selector方式在文档树中找到元素同名为它注册事件,它使用形式如下:
Ext.addBehaviors({
''#foo a@click'' : function(e, t)
{// do something }
''#foo a, #bar span.some-class@mouseover'' : function(){// do something }
}); ,
它把CSS Selector和事件类采用@分隔组成配置对象的属性值,它的值就是要注册的监听函数。这样就可以通过CSS Selector找到一批元素,之后给该元素指定的事件名注册指定的监听函数。这个对于多个元素注册相同的事件处理时是非常有效的。下面我们就通过它来改变一下代码4.6的例子。
function clickme(e,t) {
alert(''nodeName:''+ t.nodeName); }
Ext.addBehaviors(
{ ''a@click'', clickme});
不过这里不能传递配置参数。所以监听函数只有两个参数。它的主要作用是为多个元素相同事件注册相同的处理,这里没有体现其优势。它的实现是采用CSS Selector加复合元素来进行调用元素的on方法,其最终还是调用了Ext.EventManager.addListener。Ext.addBehaviors的实现在第13章进行分析。
我们上面讲到el.on的三个方式使用是也是在Ext.EventManager.addListener实现的,我们来看一下其实现:
addListener : function(element, eventName, fn, scope, options){
if(typeof eventName == "object")
{ ①
var o = eventName;
for(var e in o){
if(propRe.test(e)){ continue; } ⑤
if(typeof o[e] == "function"){ ③
listen(element, e, o, o[e], o.scope);
}else{listen(element, e, o[e]); } ④
}
return;
}
return listen(element, eventName, options, fn, scope); ②
},
因为在el.on中的对应这里的第二个参数:eventName。它分成二种形式,第一种是默认是字符形的事件名。在②处直接传给listen作为事件名。第二种是对象类型,对象类型又分别二种,在③处实现的是共享配置的多事件注册监听,在④处实现是私有配置的多事件注册监听。
我们看到在⑤处,它通过一个正则表达式来检测多个事件注册中参数中是否有表4.6列表的配置项,如果不是话,就把它们认为是事件对应的主体,这个主体是函数类型,就采用③处实现,如果不是就采用④,可以见到在共享配置中是不能有私有配置项的。
现在我们来对应比一下②③④中对listen的调用,它第一个参数是元素,第二个参数都是事件名,而第三个参数就不同的,在②处,它是object类型的配置对象,在③处,它是el.on整个参数都作为配置对象,它包括其它事件名及其监听函数。在④处,它是把当前事件名对应的配置对象做为参数,它包含着当前事件名对应的fn监听函数。
在②处,第四个参数是fn的监听函数,在③处也是找到当前事件对应的监听函数。而④处是没有参数。接下来我们要看一下listen是如何实现的的解析的:
var listen = function(element, ename, opt, fn, scope){
var o = (!opt || typeof opt == "boolean") ?
{} : opt;
fn = fn || o.fn; scope = scope || o.scope;
var el = Ext.getDom(element);
if(!el)
{ throw "Error listening for \"" + ename + ''\". Element "''
+ element + ''" doesn\''t exist.''; } var h = function(e)
{ ①
e = Ext.EventObject.setEvent(e); ②
var t;
if(o.delegate){t = e.getTarget(o.delegate,el); if(!t){return;}}③
else{ t = e.target; }
if(o.stopEvent === true){ e.stopEvent();} ④
if(o.preventDefault === true){ e.preventDefault();}
if(o.stopPropagation === true){ e.stopPropagation();}
if(o.normalized === false){ e = e.browserEvent; } ⑤
fn.call(scope || el, e, t, o); ⑥
};
if(o.delay)
{ h = createDelayed(h, o); } ⑦
if(o.single)
{ h = createSingle(h, el, ename, fn); } if(o.buffer)
{ h = createBuffered(h, o); } addListener(el, ename, fn, h, scope); ⑧
return h;
};
在上面的代码中其①处的h函数就是在⑧处注册到dom元素中指定事件的监听函数,这个函数只有一个参数,就是原生事件event。在②处它通过Ext.EventObject.setEvent(e)来对进行包裹,我们在监听函数使用就是这个包裹过的event。这也就是说来我们编写的监听函数是在这个h函数中调用,这个h函数称为wrap函数,它把开发编写的监听函数进行包裹,之后传到dom元素的事件中去。
开发者编写的函数在⑥处运行。它的格式如下:fn(event,target,options),event是包裹过的,target还可以根据指定的delegate配置项来找到事件源的父节点。对于options,在多事件共享的形式下,它是整个参数,也就是在这里可以看到注册其它事件的注册函数,在多事件私有形式下,它是事件名对应的对象。包括了注册函数本身。
③④⑤则是根据option参数来进行相关的操作的。normalized为true时表明采用原生事件。④是进行解析三个阴止方式的操作。③处是代理当前的Target,它作用监听函数的第二个参数传入。
⑦处是对包裹的函数进行操作,delay指定该函数延迟多长时间后运行,,buffer是指这个函数能缓存多久,在这个缓存的期间是可以被修改的,比如后来的函数会中断它等,single指该函数只运行一次就把把它从监听器中删除。在3.6节进行了比较。
我们来看一下⑧处addListener,这个传入进来的event也是经过处理的。
var addListener = function(el, ename, fn, wrap, scope){
var id = Ext.id(el);
if(!elHash[id])
{ elHash[id] = {}; } var es = elHash[id];
if(!es[ename])
{ es[ename] = ;} var ls = es[ename];
ls.push(
{id: id, ename: ename, fn: fn, wrap: wrap, scope: scope });
E.on(el, ename, wrap);
}
这里是把把注册的事件名和事件处理函数都缓存起来,以务后用。在这里还采用Ext.lib.Event来为dom元素注册事件处理函数。其可以看作是兼容的处理。对于IE采用attachEvent("on" + eventName, fn),对于mozilla则采用addEventListener(eventName, fn,false);来实现。
fireEvent 说明合成事件
对着prototype的fireEvent进行扩展。同时对jquery的trigger进行讲解
http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-EventTypes-complete
一般来说,事件都系统根据用户的操作而动态产生,我们在使用时不需要对事件进行手动触发,也就是通过
程序来触发它,但是在测试过程,我们经常会模拟事件的产生而去检测监听函数的相关操作是否正确,在浏览器中提供了一个fireEvent的方法来进行操作。
fireEvent是通过模拟浏览器的事件产生而进行的
程序去
点击展开示例
// 手工触发DOM元素监听。
fire: function(element, eventName, memo) {
// http://www.nabble.com/firefox-DOM-does-not-have-the-fireEvent-function-t2188792.html
// 在FF中实现IE fireEvent方法
element = $(element);
//不知道为了兼容那一个FF系列的游览器。
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
/*
* initEvent 该方法将初始化 Document.createEvent() 方法创建的合成 Event 对象的 type
* 属性、bubbles 属性和 cancelable 属性。 只有在新创建的 Event 对象被 Document 对象或 Element
* 对象的 dispatchEvent() 方法分派之前,才能调用 Event.initEvent() 方法。
*/
var event;
if (document.createEvent) {
// FF中人工fire,先create event,second: initEvent,then dispatchEvent
event = document.createEvent("HTMLEvents");
// event.initEvent(eventType,canBubble,cancelable)
event.initEvent("dataavailable", true, true);
} else {// IE
event = document.createEventObject();
event.eventType = "ondataavailable";
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent) {// FF
element.dispatchEvent(event);
} else {// IE
element.fireEvent(event.eventType, event);
}
return Event.extend(event);
}removeListener 当我们注册了监听函数之后不要再使用,就应该要把它从事件中删除中。删除事件在IE模型中采用el.detachEvent("on" + eventName, fn);在标准模型中采用el.removeEventListener(eventName, fn,capture)函数。
对于Ext而言,首先来做的就是兼容它们,采用统一的名称来进行除去监听函数,第二是要进行指定只要指定事件名,不指定函数就会除去该事件名的所有监听函数。这是最基础的操作。同时我们还要注意在addListener中,每个事件都被缓存起来,在除去时,还要把它从缓存中去掉。
这个我们可以通过el.un(ename,fn,scope)来进行。如果我们指定scope,那么还要比较其作用域,没有fn,就把当前元素指定的ename所有对应的监听函数都给去掉。
接下来我们看一下removeListener是如何释放内存的。
点击展开示例
var removeListener = function(el, ename, fn, scope){
el = Ext.getDom(el);
var id = Ext.id(el), es = elHash[id], wrap;
if(es){
var ls = es[ename], l;
if(ls){
for(var i = 0, len = ls.length; i < len; i++){
l = ls[i];
if(l.fn == fn && (!scope || l.scope == scope)){
wrap = l.wrap;
E.un(el, ename, wrap);
ls.splice(i, 1);
break;
}
}
}
}
if(ename == "mousewheel" && el.addEventListener && wrap){
el.removeEventListener("DOMMouseScroll", wrap, false);
}
if(ename == "mousedown" && el == document && wrap){ // fix stopped mousedowns on the document
Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
}
} 它首先释放了⑧处的所讲的那部分内存。接下来它调用了E.un(el, ename, hd)释放h的相关内存。