观察者:
观察的主体是用来进行检测部分的功能,它是在类库中实现的共有功能。对于采用观察者模式事件来讲,首先我们定义被观察者的接口,比如是要进行扩展,就是实现什么的接口或按什么格式的来编写注册的功能。Ext组件的事件机制采用是的规定格式,这个规定是格式是规定函数名和函数的参数形式。
对于这个规定,Observable类给出一个addEvent方法用来每个需要事件能力组件在初始化时就进行格式规定。
addEvents : function(o){
if(!this.events){ this.events = {};}
if(typeof o == ''string''){
for(var i = 0, a = arguments, v; v = a[i]; i++){
if(!this.events[a[i]]){ o[a[i]] = true; } }}
else{Ext.applyIf(this.events, o); }
},
这个方法是为了让组件在实现自己的事件能力时规定它的事件函数的格式。它有两种应用形式,如下:
形式一:addEvents(''click'',''dblclick'',''change'')
形式二:addEvents({click:true,dblclick: dblclickEvent})
第一种每个参数都是字符形的事件名,第二种形式是采用JSON对象把所有的事件和其事件对象对应起来。它通过继承方法成为每个实例化的组件的方法,也就是说它把所有的事件都存放在当前组件的events中。但是无论采用形式一还是采用形式二。其生成的事件对象都只能说是一个伪对象。因为默认的方式这个事件对象采用true来标识。形式二的第二种用法是一般都不会用的,因为在开发时,组件实例的很多事件都不会被使用。而在生成组件实例的时候就为其每个事件都生成事件对象是不值得的。这样会占用内存和影响效率。它这种把组件对象的实现推迟到addListener中创建是有一定考虑的。
在这里我们只看到了事件名,但是我们并不没有看到如何去规定事件注册函数的格式,这个在组件初始化中只是通过注释的方式来进行说明,如在Component类initComponet方法中就有如下
this.addEvents( /** @event disable
* Fires after the component is disabled.
* @param {Ext.Component} this */
''disable'',.. ..}
这样的注释就是用来规定函数的参数。如这个disable的事件只有一个参数就是当前组件,这个参数是组件在调用这个事件回调函数时传入的,开发者在编写事件回调函数可以使用这个参数。这个和浏览器中事件监听函数中参数event是一样的意思。其实对于这个的事件名,也可以不用通过addEvents声明,可以通过注释来声明,在addListener函数中如果没有找到对应的事件名就会自动创建该事件名的。
对于每个事件,它的监听函数的格式(即参数)是通过组件那些需要扩展的地方来实现的。在组件中,如果什么地方需要开发进行它的私有功能的扩展,那么就在这个地方执行开发者注册的事件函数。
这个调用就是根据上面指定的事件名来找到其对应的监听函数(可能有多个)。组件要判断这个事件是否注册了监听函数和如何找到它们就是通过Observable类中的fireEvent方法:
fireEvent : function(){
if(this.eventsSuspended !== true){ //支持全局的挂起事件
var ce = this.events[arguments[0].toLowerCase()];
if(typeof ce == "object"){
return ce.fire.apply(ce,Array.prototype.slice.call(arguments,1));}①
}
return true;
},
fireEvent用在组件需要扩展的地方进行检测是否注册事件监听及运行它们。我们已经看出它的一个参数是事件名,先通过这个事件名在当前组件注册的事件中找其对应的事件对象起来,一般来说都可以找到,因为在使用fireEvent时是根据规定事件名来指定的。如果找到该事件名对应是对象,那么就通过事件对象内部的fire方法来执行它。在默认的时候,它的事件对象都是 boolean类型的true。如果其是object类型,那么就是在开发通过注册方法进行了事件监听函数注册。
我们看一下①处的传入参数的方式,它会把fireEvent所有的参数除去第一个事件名的参数传入到事件对象中的所有监听函数中去。也就是组件在使用fireEvent时候给定的参数就是为了给事件的监听函数规定其参数格式。这个体现在addEvents中就是采用注释的方式来说明。
我们对应着上面的''disable''的事件名,而component类中的disable方法中就有着如下的代码:this.fireEvent("disable", this);它是用来调用执行开发者注册的事件监听,也就是观察者模式的监测点。如果组件指定类,那么在其类的代码中就是对应于建立这样的监测点,用来调用执行事件监听函数。
对于组件的事件能力,也就只需要这两个步骤。我们在编写组件时,如果想让它拥有事件能力,那么首先得继承于Observable类,之后,再按这二步来进行注册事件名和建立监测点。在第二章的leftMenu类中就是这样采用的。
对于观察的主体,它还提供一个其它的方法来进行相关的操作,比如我要暂定事件或重定启动事件。它提供如下三个方法:
方法名
作用及使用说明
方法名:suspendEvents
作用及使用说明:它是用来暂定该组件的事件能力。通过改变代码(3.19中)this.eventsSuspended属性值为true。
方法名:resumeEvents
作用及使用说明:对于暂定事件能力的组件,重新启动它的事件能力,通过改变代码this.eventsSuspended属性值为false.
方法名:relayEvents
作用及使用说明:它的格式如下:relayEvents(o,events),它是用来把当前组件中的事件对象逐个地转移注册到o参数指定的对象(组件或元素等)上去。它们对应的关系就是events参数集合中指定的事件名。这就要求o对象和当前组件都有着相关的事件名。在gridPanel中就有应用。
被观察者
被观察者就是开发者所编写的事件监听函数。对于这些监听函数,我们得按指定的格式来编写。这个格式是对于每个事件都是不同的。那么编写完成之后,我们就得把它们注册到相对应的事件对象中去。好让组件能监测到它,并在需要的时候去执行它来完成私有功能。
这个注册动作就是由Observable类的addListener完成:
addListener : function(eventName, fn, scope, o){
if(typeof eventName == "object"){
o = eventName;
for(var e in o){
if(this.filterOptRe.test(e)){ continue; } ①
if(typeof o[e]=="function"){this.addListener(e,o[e],o.scope,o);}②
else{this.addListener(e, o[e].fn, o[e].scope, o[e]);s} ③
}
return;
}
o = (!o || typeof o == "boolean") ? {} : o; ④
eventName = eventName.toLowerCase();
var ce = this.events[eventName] || true;
if(typeof ce == "boolean"){ ce = new Ext.util.Event(this, eventName);
this.events[eventName] = ce; } ⑤
ce.addListener(fn, scope, o); ⑥
},
开发就是通过它来实现向该组件指定的事件中注册监听函数。和元素的事件注册的使用方法是一模一样的,它也有三个方式的注册形式,不同的它的o中只能解析四个配置项,这个通过①处的filterOptRe正则表达式/^(?:scope|delay|buffer|single)$/就可以看出来它只支持scope|delay|buffer|single四个配置项,它的含义和元素事件是也相同。
和元素事件一样,它也一个缩写的方法名on。它的代码的层次也是和元素的事件注册方法相同,不同的地方在②③处是采用递归调用当前函数来实现注册多个监听函数。其注册单个监听函数代码部分在④到⑥处。在⑤处,我们可以看出在addEvents中没有生成事件对象推迟到这里实现。同时没有指定事件名,也会在这里生成与事件相对应的事件对象。
被观察者最主要的的任务就是编写并注册该监听函数。这个注册就得是开发者在具体使用该组件中而进行注册用的。
当我们不需要事件的监听时,我们就要把它从事件去剔除出去。这个我们可以通过removeListener和purgeListeners方法来实现,它们不同的地方在于removeListener清除指定事件名及函数单个剔除(当然只指定事件名,就会清除该事件名所有监听函数),而purgeListeners是清除该组件所有事件的监听函数。
listeners: {
selectionchange: {
fn: function(dv,nodes){
var l = nodes.length;
var s = l != 1 ? ''s'' : '''';
panel.setTitle(''Simple DataView (''+l+'' item''+s+'' selected)'');
}
}
}