热门:网页模板.net视频教程JQueryMVCjsonExtJs源码示例三级联动JQuery菜单
您现在的位置:.Net中文社区>> AJAX编程>>正文内容

ExtJS 2.2基本事件扩展

发布时间:2009年01月10日点击数: 彭仁夔
返回深入学习ExtJS 2.2开发系列连载教程目录
在上一节中, 我们对事件的基本流程及EventObject的进行讲解和扩展。通过Ext的扩展,我们在使用事件注册和编写事件函数时就特别地方便。但是那只是对事件的基本流程进行的兼容和扩展处理。

在说到实用的功能,它还提供了许多其它的能力,首先对于JS代码来说,都应该在DOM元素完全载入之后才能运行,它提供了Ext.domReady的实现。对于window的自适应地改变元素的大小,它也提供了实现。

另外还提供了对于键盘按键和鼠标点击事件的一些应用处理。对于键盘按键,它实现了为元素注册按键响应类和按键导航类。对于鼠标点击事件,又专门给出了重复点击的处理。 这样都是Ext的基础应用,在它的源码中,有很多地方都使用到它们。

4.3.1 DomReady处理
JS框架都会对文档中dom元素进行操作,而JavaScript代码基本上都是运行在文档的head之中。它们运行时,很有可能文档中的Dom元素还没有完全载入。如果这个时候访问或查找到元素,本来预定能访问或能运行的代码结果却因为在不同的浏览器和网络速度的环境下会出现不同的的异外。为了解决这个问题,在运行和dom元素有关的代码,就应该先等待所有的dom元素完全载入之后才能运行。

但是head的JS代码是一载入就运行的,怎么办呢?只能让程序来实现自动判断Dom文档是否完全载入,载入的之后才执行。这样的实现可以在head的代码通过setInterval隔很短时间就进行判断其dom元素是否已经载入了,如果没有,就继续隔一段时间再要运行。如下面的代码:
var a=function(){

     if(document.readyState != "loaded")

setInterval(a,13);}

else{//对dom元素操作的代码}

如果每个函数都这样写,那是有很多重复的劳动。还有这种判断dom元素是否已经全部载入也是和很多浏览器不兼容的。于是就应该提供统一的入口。所有函数都通过这里。在Ext中,它提供了Ext.onReady的方法来实现这个等待判断的工作,而实现的应用代码则是通过函数的参数传入到onReady中去。Ext.onReady实质上是指向Ext.EventManager.onDocumentReady。我们要看一下onDocumentReady是怎么实现的。
onDocumentReady : function(fn, scope, options){

       if(!docReadyEvent){ initDocReady();}

       if(docReadyState || Ext.isReady){ // if it already fired

           options || (options = {});

           fn.defer(options.delay||0, scope); }


else{docReadyEvent.addListener(fn, scope, options); }

        },

这段代码也是分成二个部分。一部分是dom完全载入之后所要做的事,一部分是dom还没有完全载入之前做的。当没有完全载入时就通过initDocReady来等待。同时把参数中的函数注册到docReadyEvent的事件机制中。这里我们要注意的在initDocReady中它是通过setInterval或通过各个不同版本浏览器的load的事件来进入触发,initDocReady执行完之后,很有可能文档还没有完全载入呢,但是它的docReadyEvent已经生成,就可以往它的内部保存传入的函数,待到完全载入时,只要通过docReadyEvent来运行保存在它内部的函数就可以了。

点击展开示例
上面针对当前几大浏览器进行了不同的domReady的判断。与Ext2.0版本相比,它把IE的实现还是改成和其它框架一样的实现,可见这种方式更好,不然人家的代码怎么能获奖呢。这里是实现生成docReadyEvent对象并等待完全载入。载入之后就会调用fireDocReady函数。

点击展开示例

在fireDocReady首先是志标识位,如果采用load事件的来等待载入的话,浏览器在load之后如果没有清除其相关的注册监听函数,在这里就清除。如果是采用setinterval来间隔运行一个函数判断是否载入的话,就清除其interval。

对于onReady中传入来运行的函数,通过docReadyEvent的fire来执行它。执行完成之后,把它从事件docReadyEvent内部清除出去。

相对于2.0来讲,这代码还是蛮清楚简单的。在开发时,我们首先要使用就是它了,我们一般是把功能函数都写成一个函数,之后作为参数传入,也有对于简单的代码,我们也会采用Ext.onReady(function(){//代码});的方式来进行等待。这两种方式的应用在第二章中的login和main.js中都采用过。

4.3.2 Resize事件
在开发的过程,我们都希望浏览器的窗体改变了大小时,我们的页面地也能进行相对应地调整以适合浏览器的大小。在viewPort中就是采用这样方式来自适合不同的像素的屏幕。对于一般地来说,改变窗体的大小而进行相对应于调整,我们可以采用window的resize事件,这个事件不是DOM2标准,但是主流浏览器都支持它。

通过这个事件能注册监听函数,但是这个监听函数得知道当前的窗体的大小。这个可能通过window的innerHeight或innerWidth属性来获得。对于IE浏览器,我们可以采用document.documentElement或document.body的clientHeight、clientWidth来获得。documentElement和body是一回事,只不过是documentElement应用在CSS1Compat模式的浏览器下。

我们已经明白了window窗体改变大小的关键部分,那我们来看viewport是如何实现的,这是viewPort实现代码:
Ext.EventManager.onWindowResize(this.fireResize, this);

      .. .. ..

    fireResize : function(w, h){

        this.fireEvent('resize', this, w, h, w, h); }

它在initCompoent中调用了onWindowResize函数,其参数是函数fireResize,它只是注册一个事件。同时我们还看到这个fireResize函数有二个参数,它们是如何传入的。我们来看一下onWindowResize的实现
onWindowResize : function(fn, scope, options){

    if(!resizeEvent){

          resizeEvent = new Ext.util.Event();

          resizeTask = new Ext.util.DelayedTask(function(){

              resizeEvent.fire(D.getViewWidth(), D.getViewHeight()); }
);

          E.on(window, "resize", this.fireWindowResize, this);

       }


      resizeEvent.addListener(fn, scope, options);

     },

这个函数和DomReady的实现有点相似,它通过resizeEvent来保存传入的参数(函数),不同的是这里它多了一个resizeTask,这是因为它是延迟一段时间运行,那个在这个延迟的时间里又改生了改变,就不会执行上一个任务,而是继续等待下一个任务的执行。对于改变大小,它能提供效率。

当然这段代码中少不了向window注册resize的监听函数。在运行fireWindowResize之前肯定会执行把当做参数传入的函数保存在resizeEvent之中。这部分工作完成之后,我们来看一下fireWindowResize:
fireWindowResize : function(){

       if(resizeEvent){

         if((Ext.isIE||Ext.isAir) && resizeTask){ resizeTask.delay(50); }

else{resizeEvent.fire(D.getViewWidth(), D.getViewHeight());}}

}
,

这个函数触发执行注册的resize事件处理函数fn(参数伟入的)。把window当前的宽度和高度做为参数传入到这个注册的处理函数中。这也是为什么fireResize中有两个参数。

Ext还提供一个类型的textResize方法,当文本的大小改变了,那么就触发传入的函数,不同的它在这里是采用setInterval来进行检测。在这里就不多介绍。

①②③④⑤⑥⑦⑧⑨⑩

4.3.3注册按键
在开发时,经常会用键盘的按键来代替鼠标的点击,比如提交,采用了鼠标点击提交按钮有的时候用户会觉得不是很方便。很多都采用回车键来代替。这只是一个简单的使用。但富客户端的菜单中,每个项都可以采用ctrl+按键来快速取得其功能。对于单键,我们可能采用element.on(‘keydown’,fn)就可以,但对于采用组合键就较复杂一点。Ext.keyMap为我们提供很便利的方法。

我们只要通过短短的一句就可以为一个元素注册多个按键,同时还可以包括组合键。如
var map = new Ext.KeyMap("my-element", [

    {key: [10,13],fn: function(){ alert("Return was pressed"); }},

{key: "abc", fn: function(){ alert('a, b or c was pressed'); }},  

{key: "\t", ctrl:true, shift:true,

        fn: function(){alert('Control + shift + tab was pressed.'); }}

]);

这个例子演示三种应用,一是单键和一个元素的注册,当按下回车时,就会弹出"Return was pressed,,第二种是多个按键和一个元素注册,当按下a,b,c中任何一个,都会弹出a, b or c was presse。第三种是组合键,当按下Control + shift + tab。就会弹出'Control + shift + tab was pressed。使用Ext.KeyMap非常简单,只要给元素注册其键就可以了。那么还得了解其是如何进行的。首先我们从其构建函数分析:
Ext.KeyMap = function(el, config, eventName){

    this.el  = Ext.get(el);

    this.eventName = eventName || "keydown";

    this.bindings = [];

    if(config){ this.addBinding(config);  }//转换成回调函数  

    this.enable();//把上面转换成函数注册事件的监听中

};

这个函数分成三步,先是找到元素,之后把config参数转换成事件的监听处理函数。第三步就是注册监听。第一个参数el可以为dom元素的id,或dom元素或Ext的元素。第二参数是直接传给addBinding。它在这里可以省略。在构建之后再通过addBinding来配置其根关的按键信息。eventName默认是按下键,如果想注册keyup就得通过参数传入。

下面我们先看一下addBinding是如何把在config中找到监听函数。
点击展开示例
addBinding的config参数对象支持key/shift/ctril/alt/fn/handler/scope七个属性。其中shift/ctril/alt表明是否采用了组合键,如果为true则是采用了。key可以为string或数组。为多个非shift/ctril/alt按键注册触发当前指定元素的事件。fn/handle是同样的功能,只要使用一个,它就是按键触发的事件处理函数。scope是事件处理函数的作用域。

①可以看其支持数组的形式,如前面的例子。②则是config对象所支持的属性了,它支持七个参数。③把string的key转换成数组的形式。④是按键的事件处理函数。这个事件函数实质上是包裹了config中的fn/handle。其首先进行判断,如果指定了shift/ctril/alt一个键为true的话,那么事件e中就要有对应的keycode(如shiftKey),没有的话就表明事件不满足当前的按键条件。满足的话,还要判断其它的非shift/ctril/alt按键是否也在指定的key的数组中,不在的话也不满足。如果都满足的话,那么调用注册的fn函数了。同时还要根据其stopEvent属性判断是否阻止默认的动作。

⑦则表明无论调用多少次这个函数都没有关系,它只是把转换的之后的回调函数写入this.bindings的数组中。如果注册监听时要用就到数组中去取。这也就是为什么说其config可以不在构建函数中传入的原因。

接下来我们看一下enable把这个包裹过的回调函数注册到指定元素的事件名上去:
enable: function(){

       if(!this.enabled){          

           this.el.on(this.eventName, this.handleKeyDown, this);

           this.enabled = true;}


    },

它是通过调用el.on来完成注册监听,不过它的回调函数是handleKeyDown:
handleKeyDown : function(e){

    if(this.enabled){

     var b = this.bindings;

        for(var i = 0, len = b.length;i < len; i++){b[i].call(this,e);} }

}
,

在handleKeyDown,它会遍历调用this.bindings中每一个函数。这样通过keyMap的整个流程就分析完成。我们可以通过其on函数来进行注册按键事件。我们来说明一下其使用方法:

函数名

使用说明

isEnabled

keyMap注册的按键是否启用

enable

启用该keyMap注册的按键事件。

disable

禁用该keyMap注册的按键事件。

addBinding

它只有一个参数,这个参数是数组的话,就是注册多条配置对象,这个配置对象只要包括keyMap的七个配置项中一些就可以。

on

注册按键,它有三个参数,第一个参数是可以为字符形式的Ascill值或数组形式的数值,或含有配项的对象,第二个参数是函数,第三个是作用域

4.5 keyMap的常用方法。

4.3.4注册导航键
在对按钮的应用中,还有很大一部分是对导航键的应用,如在表格的选择中,经常会采用上下方向键来进行上下移动高亮选择条到指定的行数。在编辑中,我们会采用pageup和pagedown进行定位到编辑的开始或结尾处。还在在Tree的应用中,我们也会采用上下左右方向键来进行导航。

导航的原理是很简单的,只要我们为元素注册如方向键之类的按键的keydown的监听函数就可以了。不过其中还是有一些问题是要注意。Ext.keyNav把所有的要做的事都帮我们做好了,我们只要简单的实现它给定的接口就可以。

这个接口就是在Ext.keyNav中注册"left","right","up","down","pageUp","pageDown","del","home","end", "enter", "esc","tab"这些函数中一些函数就可以了,如果指定的函数名注册了方法,那么在该元素之上按下相对应的键就会执行它。我们通过一个小例子来说明一下:
var nav = new Ext.KeyNav("my-element", {

    "left" : function(e){  this.moveLeft();  },

    "right" : function(e){ this.moveRight(e.ctrlKey); },

    "enter" : function(e){ this.save();   },

scope : this});

对于my-element元素,当用户按下left键时就会调用moveLeft方法,按下回车键时,就会调用save方法。我们来通过其构建函数分析一下它是怎么流转的。
Ext.KeyNav = function(el, config){

    this.el = Ext.get(el);

    Ext.apply(this, config);

    if(!this.disabled){ this.disabled = true;    this.enable();  }

};

它首先是找到指定的el元素,之后把config配置对象中所有项都应用到当前对象中来,接着它调用this.enable()方法进行注册事件监听函数。在这里this.disabled一般都是为false,因为在其prototype中默认是false。除非是在config中进行配置。在config中,我们不光是可以配置上面给出的十二个按键的接口,还可以配置disabled、defaultEventAction、和forceKeyDown。在代码中再具体去说明它们的用处。

现在我们要分析this.enable()方法,它完成注册keydown的事件:
enable: function(){

       if(this.disabled){        

             if(this.forceKeyDown || Ext.isIE || Ext.isAir){

                  this.el.on("keydown", this.relay,  this);}


else{

this.el.on("keydown", this.prepareEvent,  this);

                 this.el.on("keypress", this.relay,  this); }


         this.disabled = false;

        }


       },

这里先判断是当前组件是否禁用,只有在disabled之后才需要重新去注册监听。接下我们可以看到forceKeyDown,这是因为在IE中不会对keypress事件进行特殊键(即非字母数字键)进行冒泡。设定它为true会强迫它采用keydown事件来代替keypress事件。其实我们不要去管它,在enable中,对于IE和air,它都会自动采用keydown来进行处理。的而对于其它的浏览器,采用keypress进行注册。

我们要来看一下其调用其relay的方法,
relay : function(e){

         var k = e.getKey();

         var h = this.keyToHandler[k];

          if(h && this[h]){

              if(this.doRelay(e, this[h], h) !== true){

                  e[this.defaultEventAction]();       }


            }


    },

这里keyToHandler是把left这样代表的键名和与其对应的键值对应起来。把event中传入的键值在这个keyToHandler的Hash表中找,看能不能找到对应的键名,找到了就运行当前keyNav中的注册的同名函数,每个键名默认是false。如果注册的函数返回false,就会调用指定的阻止默认缺省的方法,这个方法名是defaultEventAction配置项中指定,这里它默认是stopEvent。

导航键在组件中应用很多。在后面的章节,我们分别来讲解。

①②③④⑤⑥⑦⑧⑨⑨⑩

4.3.5鼠标按住事件
鼠标按住是按住一个元素多长时间后才执行给它注册的处理函数,比如有下拉的图标,习惯上不是点击就会展开来,而是按住它一定时间, 才会展开,这个在下拉菜单的按钮中是经常用到的。Ext.util.ClickRepeater就是用来进行这样的操作的,它的应场景不是很多。

Ext.util.ClickRepeater继承于observable类,这个类在下一节中将会讲到,继承于它就会得到事件能力。它有两个参数,第一个参数是指定元素,第二个参数是进行配置。具体要求的配置项如下:

配置项

说明

delay

指定的按下多长时间后才运行注册的处理函数

interval

delay差不多,指定多长时间后才运行注册处理函数,20ms 默认

pressClass

鼠标按下的之后的CSS样式。

accelerate

true时会,会出现先按下要长一点时间才运行,后按下短一点

preventDefault

阻止click默认的动作发生,默认是true.

stopDefault

阻止默认的动作发生和冒泡操作。默认是false.

handler

用来指定鼠按下一定时间后才执行的函数。

我们先来看一下其构建函数:

点击展开示例

这个我们看出它注册了三个事件。一般来说都是使用click事件。当鼠标按下时就执行handleMouseDown函数。同时还默认地阻止元素click事件的默认动作发生。如果指定了handler,把它注册在当前的对象的click事件的监听函数。

接着从handleMouseDown函数起,它发生在元素被按下的那一刻。
handleMouseDown : function(){

    clearTimeout(this.timer);

     this.el.blur();

     if(this.pressClass){ this.el.addClass(this.pressClass);}

     this.mousedownTime = new Date();

      Ext.getDoc().on("mouseup", this.handleMouseUp, this);

      this.el.on("mouseout", this.handleMouseOut, this);

      this.fireEvent("mousedown", this);

      this.fireEvent("click", this);

      if (this.accelerate) {  this.delay = 400; }

      this.timer = this.click.defer(this.delay || this.interval, this);

},

按下了鼠标,该元素首先失去焦点,同时把指定的按下的样式应用到当前元素。注册鼠标键松开时和移出当前元素的事件。最后按指定的时间来延迟运行click方法。
click : function(){

        this.fireEvent("click", this);

        this.timer = this.click.defer(this.accelerate ?

            this.easeOutExpo(this.mousedownTime.getElapsed(),

                400, -390,  12000) : this.interval, this);

    },

click中就是执行当前类的click的事件监听函数。如果指定handler,也就是注册在这个事件上,对于一般的使用该类,都是使用click事件注册监听来进行按下的功能处理。handleMouseUp是先判断如果没有执行指定的click的注册函数就取消,这就是还没有按到一定的时间就松开了键。它剩下要做就是恢复没有按下时的样式和删除在handleMouseDown中注册的事件。

在handleMouseOut中的处理有点不同,它也是恢复没有按下时的样式和没有执行指定的click的注册函数就取消,但是它注册了一个mouseover,接下来如果再次经过这个元素,不要按下就会触发click中注册的函数。

这个类在ToggleButton中有应用 .

本站热点业务

更多模板/案例展示

关于我们 | 联系我们 | 团队日志 | 网站地图 | 网站合作