ExtJs源码分析与学习—ExtJs事件机制(三)

这篇讲ExtJs对事件的管理和调用

 

      ExtJs对事件的管理非常强大,主要定义在Ext.EventManager对象(单例模式)中。先看注册事件监听方式,ExtJs提供了几种方式来注册元素监听函数

  • 通过Ext.EventManager.addListener/on函数来为指定元素的某种事件注册监听函数。例如:Ext.EventManager.on('test1','click',clickMe,this);// clickMe是要执行的函数
  • 通过ExtJs 元素的on函数来为自身注册某种事件的监听处理函数。例如:
    Ext.get('test1').on('click', clickMe,this,{preventDefault:true});
  • 通过Ext.addBehaviors函数为多个元素的多个事件注册监听处理函数,该方法在ExtJs源码分析与学习—ExtJs核心代码扩展中已介绍过。 

具体来说可分为
    标准配置方式
el.on(eventName,fn,scope,options);
options参数是事件配置项,各项说明如下:
scope : 可指定执行上下文
delegate :事件代理
stopEvent :阻止冒泡和默认行为
preventDefault :阻止默认行为
stopPropagation :停止事件冒泡
normalized : 仅传原生事件对象
delay :延迟执行
single : 仅执行一次
buffer :延迟执行,多次时最后一次覆盖前一次
target : 指定在父元素上执行


   私有配置多事件注册
el.on({'click':{fn:this.onClick,scope:this,delay:100},
     'mouseover':{fn:this.onMouseOver,scope:this},
     …
    });


  共享配置多事件注册
el.on({'click':this. onClick,
     'mouseover':this.onMouseOver,
     …,
     scope:this});


   多元素多事件注册
Ext. addBehaviors({
'#foo a@click' : function(e, t){
        // do something
},
'#foo a, #bar span.some-class@mouseover' : function(){
        // do something
}
);
注意这种共享配置多事件注册监听函数只有两个参数,不能传递配置对象来进行配置,与前三种不同。

 

上述几种方式最终还是调用Ext.EventManager.addListener函数来注册监听的,该函数源代码如下:

 

Js代码    收藏代码
  1.     /** 
  2.      * 加入一个事件处理函数,方法{@link #on}是其简写方式。 
  3.      * @param {String/HTMLElement} element 要分配的html元素或者其id。 
  4.      * @param {String} eventName 事件处理函数的名称。 
  5.      * @param {Function} fn 事件处理函数。该函数会送入以下的参数 
  6.      * @param {Object} scope (optional) (可选的)事件处理函数执行时所在的作用域。处理函数“this”的上下文 
  7.      * @param {Object} options (optional) (可选的) 包含句柄配置属性的一个对象。该对象可能会下来的属性: 
  8.      * 调用addListener时送入的选项对象。 
  9.      * 
  10. scope : Object 事件处理函数执行时所在的作用域。处理函数“this”的上下文环境。 
  11.      * 
  12. delegate : String 一个简易选择符,用于过滤目标,或是查找目标的子孙。 
  13.      * 
  14. stopEvent : Boolean true表示为阻止事件。即停止传播、阻止默认动作。 
  15.      * 
  16. preventDefault : Boolean true表示为阻止默认动作。 
  17.      * 
  18. stopPropagation : Boolean true表示为阻止事件传播。 
  19.      * 
  20. normalized : Boolean false表示对处理函数送入一个原始、未封装过的浏览器对象而非标准的 
  21.      * 
  22. delay : Number  触发事件后处理函数延时执行的时间。 
  23.      * 
  24. single : Boolean true代表为下次事件触发加入一个要处理的函数,然后再移除本身。 
  25.      * 
  26. buffer : Number 若指定一个毫秒数会把该处理函数安排到{@link Ext.util.DelayedTask}延时之后才执行。  
  27.      */  
  28.   
  29. addListener : function(element, eventName, fn, scope, options){  
  30.         if(typeof eventName == 'object'){//事件名为对象,说明采用配置多事件注册  
  31.             var o = eventName, e, val;  
  32.             for(e in o){  
  33.                 val = o[e];  
  34.                 if(!propRe.test(e)){//非标准模式下不能使用私有自定义的配置项,如options配置了{msg:'提示信息'}  
  35.                     if(Ext.isFunction(val)){  
  36.                         // shared options 共享配置项  
  37.                         listen(element, e, o, val, o.scope);  
  38.                     }else{  
  39.                         // individual options 私有配置项  
  40.                         listen(element, e, val);  
  41.                     }  
  42.                 }  
  43.             }  
  44.         } else {  
  45.             listen(element, eventName, options, fn, scope);//标准配置项  
  46.         }  
  47.     },  

 

 

 该监听函数统一调用了listen,源代码如下:

 

Js代码    收藏代码
  1.     /** 
  2.      * @param {} element 要注册监听事件的元素 
  3.      * @param {} ename 事件名 
  4.      * @param {} opt 配置项 
  5.      * @param {} fn 监听函数 
  6.      * @param {} scope 上下文(作用域) 
  7.      * @return {} 
  8.      */  
  9.     var listen = function(element, ename, opt, fn, scope){  
  10.         var o = (!opt || typeof opt == "boolean") ? {} : opt;//配置对象  
  11.         fn = fn || o.fn; scope = scope || o.scope; //监听函数和作用域  
  12.         var el = Ext.getDom(element);//元素,封装为Ext.Element  
  13.         if(!el){  
  14.             throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';  
  15.         }  
  16.         var h = function(e){//封装之后的监听函数,用来注册到元素的事件中  
  17.             // prevent errors while unload occurring  
  18.             if(!window[xname]){  
  19.                 return;  
  20.             }  
  21.             e = Ext.EventObject.setEvent(e);//把浏览器原生的事件封装为EventObject对象  
  22.             var t;  
  23.             if(o.delegate){//如果指定了delegate配置项,那么就按其指定找到代理事件源  
  24.                 t = e.getTarget(o.delegate, el);  
  25.                 if(!t){  
  26.                     return;  
  27.                 }  
  28.             }else{  
  29.                 t = e.target;  
  30.             }  
  31.             if(o.stopEvent === true){  
  32.                 e.stopEvent();  
  33.             }  
  34.             if(o.preventDefault === true){  
  35.                e.preventDefault();  
  36.             }  
  37.             if(o.stopPropagation === true){  
  38.                 e.stopPropagation();  
  39.             }  
  40.   
  41.             if(o.normalized === false){  
  42.                 e = e.browserEvent;  
  43.             }  
  44.   
  45.             fn.call(scope || el, e, t, o);//调用监听函数,如el.on(eventName,fn,scope,options);则此处调用fn,并传入e, t, o参数  
  46.         };  
  47.         if(o.target){  
  48.             h = createTargeted(h, o);  
  49.         }  
  50.         if(o.delay){  
  51.             h = createDelayed(h, o);  
  52.         }  
  53.         if(o.single){  
  54.             h = createSingle(h, el, ename, fn, scope);  
  55.         }  
  56.         if(o.buffer){  
  57.             h = createBuffered(h, o);  
  58.         }  
  59.   
  60.         addListener(el, ename, fn, h, scope);//注册为DOM元素的某事件监听函数,及浏览器原生事件  
  61.         return h;  
  62. };  

 

在listen函数最后调用了私有函数addListener去注册事件监听函数

 

Js代码    收藏代码
  1.                 /** 
  2.      * There is some jquery work around stuff here that isn't needed in Ext Core. 
  3.      * @param {} el 要注册监听事件的元素,已封装为EventObject 
  4.      * @param {} ename 事件名 
  5.      * @param {} fn 监听函数 
  6.      * @param {} task 延迟任务 
  7.      * @param {} wrap 封装之后的监听函数 
  8.      * @param {} scope 上下文(作用域) 
  9.      * @return {} 
  10.      */  
  11.     function addListener(el, ename, fn, task, wrap, scope){  
  12.         el = Ext.getDom(el);  
  13.         var id = getId(el),// 统一管理事件的id  
  14.             es = Ext.elCache[id].events,//根据元素id找到对应事件监听集合  
  15.             wfn;  
  16.   
  17.         wfn = E.on(el, ename, wrap);// 调用Ext.lib.Event.on添加原生的事件,实现浏览器的兼容  
  18.         es[ename] = es[ename] || [];  
  19.   
  20.         /* 0 = Original Function, 
  21.            1 = Event Manager Wrapped Function, 
  22.            2 = Scope, 
  23.            3 = Adapter Wrapped Function, 
  24.            4 = Buffered Task 
  25.         */  
  26.         es[ename].push([fn, wrap, scope, wfn, task]);//把监听函数保存到监听集合中对应的事件名的集合中  
  27.   
  28.         // this is a workaround for jQuery and should somehow be removed from Ext Core in the future  
  29.         // without breaking ExtJS.  
  30.   
  31.         // workaround for jQuery  
  32.         if(el.addEventListener && ename == "mousewheel"){  
  33.             var args = ["DOMMouseScroll", wrap, false];  
  34.             el.addEventListener.apply(el, args);  
  35.             Ext.EventManager.addListener(WINDOW, 'unload'function(){  
  36.                 el.removeEventListener.apply(el, args);  
  37.             });  
  38.         }  
  39.   
  40.         // fix stopped mousedowns on the document  
  41.         if(el == DOC && ename == "mousedown"){  
  42.             Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);  
  43.         }  
  44. }  

 

以上代码讲解了addListener函数,主要是对原生事件的包装、注册、配置项的处理和事件列表(Ext.elCache[id].events,该列表的初始化定义在Ext.Element.addToCache(new Ext.Element(el), id)方法中,后续会分析)的维护,详细说明请参照代码中的注释。

接下来看删除事件

 

Js代码    收藏代码
  1. /** 
  2.  * 移除事件处理器(event handler),跟简写方式{@link #un}是一样的。 
  3.  * 通常你会更多的使用元素本身{@link Ext.Element#removeListener}的方法。 
  4.  * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically 
  5.  * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version. 
  6.  * @param {String/HTMLElement} el 欲移除事件的html元素或id。 The id or html element from which to remove the listener. 
  7.  * @param {String} eventName 事件名称。The name of the event. 
  8.  * @param {Function} fn 事件的执行那个函数。 The handler function to remove. This must be a reference to the function passed into the {@link #addListener} call. 
  9.  * @param {Object} scope 上下文 If a scope (this reference) was specified when the listener was added, 
  10.  * then this must refer to the same object. 
  11.  */  
  12. removeListener : function(el, eventName, fn, scope){  
  13.     el = Ext.getDom(el);  
  14.     var id = getId(el),//获取el的id,先从缓存中查找  
  15.         f = el && (Ext.elCache[id].events)[eventName] || [],//值为一数组  
  16.         wrap, i, l, k, len, fnc;  
  17.   
  18.     for (i = 0, len = f.length; i < len; i++) {  
  19.   
  20.         /* 0 = Original Function, 
  21.            1 = Event Manager Wrapped Function, 
  22.            2 = Scope, 
  23.            3 = Adapter Wrapped Function, 
  24.            4 = Buffered Task 
  25.         */  
  26.         if (Ext.isArray(fnc = f[i]) && fnc[0] == fn && (!scope || fnc[2] == scope)) {  
  27.             if(fnc[4]) {  
  28.                 fnc[4].cancel();  
  29.             }  
  30.             k = fn.tasks && fn.tasks.length;  
  31.             if(k) {  
  32.                 while(k--) {  
  33.                     fn.tasks[k].cancel();  
  34.                 }  
  35.                 delete fn.tasks;  
  36.             }  
  37.             wrap = fnc[1];  
  38.             E.un(el, eventName, E.extAdapter ? fnc[3] : wrap);  
  39.   
  40.             // jQuery workaround that should be removed from Ext Core  
  41.             if(wrap && el.addEventListener && eventName == "mousewheel"){  
  42.                 el.removeEventListener("DOMMouseScroll", wrap, false);  
  43.             }  
  44.   
  45.             // fix stopped mousedowns on the document  
  46.             if(wrap && el == DOC && eventName == "mousedown"){  
  47.                 Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);  
  48.             }  
  49.   
  50.             f.splice(i, 1);  
  51.             if (f.length === 0) {  
  52.                 delete Ext.elCache[id].events[eventName];  
  53.             }  
  54.             for (k in Ext.elCache[id].events) {  
  55.                 return false;  
  56.             }  
  57.             Ext.elCache[id].events = {};  
  58.             return false;  
  59.         }  
  60.     }  
  61. },  

 

代码中方法 removeAll 的功能为移除某个元素所有的事件,而方法 getListeners 的功能为返回元素el对应的事件名eventName所有的监听函数,请看下面的例子

 

Js代码    收藏代码
  1. "button" value="点击我" id="btn">   
  2.  var buttonEl = Ext.get('btn');  
  3.  buttonEl.on('click',function(e,t,o){alert(e.type + '1');},this);  
  4.  buttonEl.on('click',function(e,t,o){alert(e.type + '2');},this);  
  5.  buttonEl.on('click',function(e,t,o){alert(e.type + '3');},this); var  
  6.  listeners = Ext.EventManager.getListeners(buttonEl,'click');  
  7.  alert(listeners);  

 

再看方法purgeElement : function(el, recurse, eventName)实现的功能是递归删除元素el以及el的子元素上已注册的事件
   参数 el 要删除事件对应的元素 
   参数 recurse 是否删除el子元素上的事件,true为是
   参数 eventName 已注册的事件名,如果没有传入eventName参数,默认则把删除所有的注册的事件

 

 下面看该类中方法onDocumentReady,该方法的简写是Ext.onReady,该方法的功能是当Document准备好的时候触发传入的业务函数。源代码如下

 

Js代码    收藏代码
  1. onDocumentReady : function(fn, scope, options){  
  2.       if (Ext.isReady) { // if it already fired or document.body is present  
  3.           docReadyEvent || (docReadyEvent = new Ext.util.Event());//这样判断是否定义了变量  
  4.           docReadyEvent.addListener(fn, scope, options);//文档已经载入,传入业务函数,并运行  
  5.           docReadyEvent.fire();  
  6.           docReadyEvent.listeners = [];  
  7.       } else {//文档尚未载入,先初始化文档,然后传入业务函数  
  8.           if (!docReadyEvent) {  
  9.               initDocReady();  
  10.           }  
  11.           options = options || {};  
  12.           options.delay = options.delay || 1;  
  13.           docReadyEvent.addListener(fn, scope, options);  
  14.       }  
  15.   },  

 该方法中当DOM文档没有完全载入时,就通过initDocReady等待,并且把参数中的业务函数注册到docReadyEvent对象中,好让在initDocReady函数注册的fireDocReady函数执行。initDocReady的代码如下

 

Js代码    收藏代码
  1.      function initDocReady(){  
  2.         docReadyEvent || (docReadyEvent = new Ext.util.Event());  
  3.         if (DETECT_NATIVE) {//对于基于 Gecko 、WebKit 和 Safari的浏览器进行等待处理,完全载入运行fireDocReady函数  
  4.             DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);  
  5.         }  
  6.         /* 
  7.          * Handle additional (exceptional) detection strategies here 
  8.          * 处理特殊的情况 
  9.          */  
  10.         if (Ext.isIE){//IE浏览器完全载入运行fireDocReady,使用readystatechange作为检测标志  
  11.             //Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME  
  12.             //See if page is already loaded  
  13.             if(!checkReadyState()){  
  14.                 checkReadyState.bindIE = true;  
  15.                 DOC.attachEvent('onreadystatechange', checkReadyState);  
  16.             }  
  17.   
  18.         }else if(Ext.isOpera ){  
  19.             /* Notes: 
  20.              * Opera需要特殊处理,需要判断css是否加载 
  21.                Opera needs special treatment needed here because CSS rules are NOT QUITE 
  22.                available after DOMContentLoaded is raised. 
  23.             */  
  24.   
  25.             //See if page is already loaded and all styleSheets are in place  
  26.             (DOC.readyState == COMPLETE && checkStyleSheets()) ||  
  27.                 DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);  
  28.   
  29.         }else if (Ext.isWebKit){  
  30.             //Fallback for older Webkits without DOMCONTENTLOADED support  
  31.             checkReadyState();  
  32.         }  
  33.         // no matter what, make sure it fires on load  
  34.         // 最无奈的时候,通过load来触发fireDocReady事件  
  35.         E.on(WINDOW, "load", fireDocReady);  
  36. }  

 

接下来是两个自运行函数(闭包函数的处理)
initExtCss 初始化Ext Css,用来区分是何种浏览器或操作系统,加载到body上
supportTests 支持测试,怎么用不太明白

 

       以上是对Ext.EventManager代码中主要方法和功能的分析,至此完成了ExtJs对浏览器事件的封装与调用,接下来讲解ExtJs自定义事件的处理,即ExtJs组件事件。

你可能感兴趣的:(extjs)