第2章 EXT框架基础
2.1 Ext的事件和类
Text事件分为两种类型:自定义事件和浏览器事件
所有继承自Ext.util.Observable类的控件都可以支持事件。
下面通过继承Ext.util.Observable来实现一个支持事件的对象,实现过程如代码清单2-1所示。
程序的效果就是在浏览器上显示三个按钮,当你点击不同的按钮时,会触发不同的自定义事件。
代码清单2-1:完整的源代码Person.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML4.01 Transitional//EN">
<html>
<head>
<title>person.html</title>
<metahttp-equiv="keywords"content="keyword1,keyword2,keyword3">
<metahttp-equiv="description" content="this is my page">
<metahttp-equiv="content-type" content="text/html;charset=UTF-8">
<linkrel="stylesheet" type="text/css"href="scripts/resources/css/ext-all.css">
<script type="text/javascript"src="scripts/adapter/ext/ext-base.js"></script>
<script type="text/javascript"src="scripts/ext-all.js"></script>
<scripttype="text/javascript">
//Person类
Person= function(name){
this.name=name;
this.addEvents('walk','eat','sleep');
}
Ext.extend(Person,Ext.util.Observable,{
info:function(event){
returnthis.name+' is '+event+'ing';
}
})
//添加事件监听器
varperson=new Person('Lingo');
person.on('eat',function(breakfast,lunch,supper){
Ext.Msg.alert('event',person.name+'要吃'+breakfast+','+lunch+'和'+supper);
});
person.on('walk',function(){
Ext.Msg.alert('event',person.name+'饭后百步走,活到99');
});
person.on('sleep',function(time){
Ext.Msg.alert('event',person.name+'从'+time.format('H')+'点开始睡觉了');
});
</script>
</head>
<body>
<input type="button" id="btnWalk" value="散步" />
<inputtype="button" id="btnEat" value="吃饭" />
<inputtype="button" id="btnSleep" value="睡觉" />
<script>
//触发person的事件
Ext.get('btnWalk ').on('click',function(){
person.fireEvent('walk');
});
Ext.get('btnEat ').on('click',function(){
person.fireEvent('eat','早餐','中餐','晚餐');
});
Ext.get('btnSleep ').on('click',function(){
person.fireEvent('sleep',newDate());
});
</script>
</body>
</html>
以上代码首先实现了一个名为Person的对象,它有一个属性name。初始化时调用this.addEvents()函数定义了三个事件:walk、eat和sleep,然后让Person继承了Ext.util.Observable。(默然说话:那个info的定义并没在有程序里用到,我认为可以省略,不过我在我的代码引用info了,大家可以注意观察一下我是咋用的)
接下来我们创建了一个Person的实例,并为这个实例添加了对三个事件的监听器。on()就是addListener()的简写形式,第一个参数传递事件名称,第二个参数传递事件处理函数。
再接下来就是HTML的<body>部分,我们做了三个按钮(默然说话:纠结的三个按钮,原书上写得太含混,让我猜了好长时间,我在这里修改了一下三个按钮的名字,便于大家识别。)。
最后,又是一段Script代码,它们获得对应的按钮,并触发对应的事件,在这里,我们直接使用fireEvent()来完成,第一个参数是事件的名称,后面的参数可以是任意个,与你所定义的事件处理函数的参数一一对应。
Ext可以通过on()(即addListener的简写)来注册事件监听函数,也可以使用un()(即removeListener()的简写)来删除某个事件对应的监听函数。示例如下:
var fn=function{
//此处省略5000行代码
}
person.on(‘fuck’,fn);//注册’fuck’事件
person.un(‘fuck’,fn);//删除’fuck’事件
浏览器事件就是传统意义上的鼠标键盘事件,它们与页面元素紧密相关。Ext使用Ext.EventManager、Ext.EventObject和Ext.lib.Event对原生浏览器事件进行了封装。
Event中定义了以下几个主要的函数。
q getX()、getY()、getXY()获得发生的事件在页面中的坐标位置,getXY()返回的是一个数组。getXY()[0]就是x坐标,getXY()[1]就是y坐标。
q getTarget()获得事件的目标元素,该函数用来统一IE和其他浏览器使用的e.target和e.srcElement。
q preventDefault()函数用于取消浏览器对当前事件所执行的默认操作。例如,在自定义右键菜单时就需要使用这个函数,防止单击鼠标右键时弹出浏览器自身的右键菜单。
q stopPropagation()函数的作用是停止事件传递,这是与浏览器中HTML元素事件的传递机制相关的。内层的HTML元素触发的事件会传递给外层的HTML元素,调用stopPropagation()函数会中断这个传递过程。
q stopEvent()可以停止一个事件,它调用了preventDefault()和stopPropagation()两个函数。
q onAvailable()函数有3个参数:id,fn和scope。它的作用是当id对应的HTML元素可用时执行fn。
q resolveTextNode()函数会返回事件相关的HTML元素。它先尝试获得e.relatedTarget。如果不存在,就通过e.type来判断事件类型。如果是’mouseout’,就返回e.toElement;如果是’mouseover’就返回e.fromElement。
Ext.util.Observable在Ext事件模型体系中有举足轻重的地位,位于Ext组件的顶端,为Ext组件提供处理事件的最基本功能。下面主要讨论一下与事件有关的高级功能。
首先,当注册事件时,可以使用复合式参数,如下面的代码所示。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML4.01 Transitional//EN">
<html>
<head>
<title>Ext.util.Observable复合式参数的使用</title>
<metahttp-equiv="keywords"content="keyword1,keyword2,keyword3">
<metahttp-equiv="description" content="this is my page">
<metahttp-equiv="content-type" content="text/html;charset=UTF-8">
<linkrel="stylesheet" type="text/css"href="scripts/resources/css/ext-all.css">
<script type="text/javascript"src="scripts/adapter/ext/ext-base.js"></script>
<script type="text/javascript"src="scripts/ext-all.js"></script>
<scripttype="text/javascript">
//fn函数我定义在这里
varfn=fungion(e,el,args){
alert('函数被调用');
alert(args.testId);
}
</script>
</head>
<body>
<!--这里是测试按钮 -->
<input type="button" id="test" value="测试" />
<scripttype="text/javascript">
Ext.get('test').on('click',fn,this,{
single:true,//这个函数是否仅执行一次,true表示仅执行一次
delay:500,//延迟500ms
testId:4//自定义参数,此处仅作为一个演示
});
</script>
</body>
</html>
这个示例的运行结果是,在第一次单击test按钮时,会弹出一个alert框,里面写着“函数被调用了”,接着又弹出一个alert框,里面写着“4”(默然说话:fn里就是这样写的)。这些和我们平时写事件函数没什么区别,但有两件事情不太一样,第一件是,第一个alert框的弹出会出现延迟0.5秒,第二件是,你再点那个测试按钮,对话框不会再弹出来了。这一切都是由第四个参数造成的。
single:true表示这个事件处理函数只执行一次;delay:500表示函数会在事件发生之后500毫秒被执行。而testId:4则是第二个alert框中显示的4的由来。
在实际程序中,我们可能需要给事件处理函数传入一些参数,那么形如testId:4这样的参数就很有用处了。它会被fn函数的第三个参数传递到函数中,然后直接通过形如args.testId的方式来获得传入的值。(默然说话:前两个参数是什么?第一个参数是Ext.EventObject对象,第二个参数好象是事件源,由于没有找到确实的文字描述,暂时不作定论)
另外,我们也可以使用on()定义多个事件监听器,如下面代码所示:
Ext.get('test2').on({
'click':{
fn:fn
},
'mouseover':{
fn:fn,
single:true,
delay:1000,
testId:5
}
});
Ext.util.Observable还有一个重要的功能,就是可以为某个事件设置拦截器,统一管理方法的触发。我们使用capture()和releaseCapture()来实现这个功能。
我们在Person的示例中使用capture()函数拦截事件的触发,完整代码如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML4.01 Transitional//EN">
<html>
<head>
<title>person.html</title>
<metahttp-equiv="keywords" content="keyword1,keyword2,keyword3">
<metahttp-equiv="description" content="this is my page">
<metahttp-equiv="content-type" content="text/html;charset=UTF-8">
<linkrel="stylesheet" type="text/css"href="../scripts/resources/css/ext-all.css">
<script type="text/javascript"src="../scripts/adapter/ext/ext-base.js"></script>
<script type="text/javascript"src="../scripts/ext-all.js"></script>
<scripttype="text/javascript">
//Person类
Person= function(name){
this.name=name;
this.addEvents('walk','eat','sleep');
}
Ext.extend(Person,Ext.util.Observable,{
info:function(event){
returnthis.name+' is '+event+'ing';
}
})
//添加事件监听器
varperson=new Person('Lingo');
person.on('eat',function(breakfast,lunch,supper){
Ext.Msg.alert('event',person.name+'要吃'+breakfast+','+lunch+'和'+supper);
});
person.on('walk',function(){
Ext.Msg.alert('event',person.name+'饭后百步走,活到99');
});
person.on('sleep',function(time){
Ext.Msg.alert('event',person.name+'从'+time.format('H')+'开始睡觉了');
});
</script>
</head>
<body>
<input id="walk" type="button" value="散步" />
<input id="sleep" type="button" value="睡觉" />
<input id="eat" type="button" value="吃饭" />
<input id="capture" type="button" value="启动拦截" />
<script type="text/javascript">
//触发person的事件
Ext.get('walk').on('click',function(){
person.fireEvent('walk');
});
Ext.get('sleep').on('click',function(){
person.fireEvent('walk');
});
Ext.get('eat').on('click',function(){
person.fireEvent('eat');
});
//拦截器的添加
Ext.get('capture').on('click',function(){
Ext.util.Observable.capture(person,function(){
alert('捕获1');
returntrue;
});
});
</script>
</body>
</html>
上面的代码就是在Person代码的基础上添加了一个按钮“启动拦截”,和一段拦截的事件处理函数。其作用就是当你点击“启动拦截”按钮之后,你会看到所有Person事件代码运行之前,会先弹出一个“捕获1”的警告框,之后才是原来的事件处理函数代码的执行。这个拦截代码如下:
Ext.get('capture').on('click',function(){
Ext.util.Observable.capture(person,function(){
alert('捕获1');
returntrue;
});
});
大家看到函数最后返回了一个true。如果这个拦截函数最后返回false,那将会终止所有的事件代码的执行。
releaseCapture()函数是capture()函数的反向操作,它会一次性清除所有的拦截函数。
suspendEvents()可以暂停某个对象中所有事件的发生,而resumeEvents()可以恢复某个对象中被暂停的事件。它们可以帮助我们统一管理某一对象的事件。
作为事件管理器,Ext.EventManager定义了一系列事件的处理函数,其中最常用的当属onDocumentReady、onWindowResize和onTextResize。其中onDocumentReady就是我们经常见到的Ext.onReady(),它会在页面文档渲染完毕但图片等还未下载时调用启动函数。
Ext.onReady()可以有效避免由于网页元素尚未完全加载完毕而出现“’null’为空或不是对象”的错误(我们前面几个例子,都是把事件监听代码放在了对应元素后面,就是这个原因),使用Ext.onReady()就可以把启动时要运行的代码放在网页的开头了。
onWindowResize()的作用是监听浏览器窗口的大小改变,它会提醒我们浏览器窗口的大小在何时发生了改变,改变后的大小是多少。
onTextResize()会在<body>标签中添加一个包含测试文字的div标签,然后监听测试文字的大小。如果测试文字的大小发生改变,就执行监听函数,并发送给监听函数两个参数,第一个是改变前的文字大小,第二个是改变的的文字大小。
需要注意的是,因为onTextResize()需要操作标签,所以这个事件只能写在Ext.onReady()中,否则有可能会出现错误。下面是完整的演示代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML4.01 Transitional//EN">
<html>
<head>
<title>event_manager.html</title>
<metahttp-equiv="keywords"content="keyword1,keyword2,keyword3">
<metahttp-equiv="description" content="this is my page">
<metahttp-equiv="content-type" content="text/html;charset=UTF-8">
<linkrel="stylesheet" type="text/css"href="../scripts/resources/css/ext-all.css">
<script type="text/javascript" src="../scripts/adapter/ext/ext-base.js"></script>
<script type="text/javascript"src="../scripts/ext-all.js"></script>
<!--<link rel="stylesheet" type="text/css"href="./styles.css">-->
<scripttype="text/javascript">
Ext.onReady(function(){
Ext.Msg.alert('消息',Ext.get('test'));
Ext.EventManager.onTextResize(function(oldSize,newSize){
alert('oldSize:'+oldSize+',newSize:'+newSize);
});
});
Ext.EventManager.onWindowResize(function(width,height){
alert('width:'+width+',height:'+height);
});
</script>
</head>
<body>
<button id="test" >test</button>
</body>
</html>
Ext.EventObject是对事件的封装,它将EXT自定义事件和浏览器事件结合在一起使用。此外,它还提供了丰富的辅助工具函数,帮助我们获得事件相关的信息。
Ext.EventObject定义了一系列的功能按键,处理按键事件时不用再去硬背ASCII码(默然说话:但是要硬背英语单词,呵呵~我早就背得ASCII了,现在又要让我去记单词,烦!)
名称 | ASCII码 | 名称 | ASCII码 |
BACKSPACE | 8 | PAGEDOWN | 34 |
TAB | 9 | END | 35 |
RETURN | 13 | HOME | 36 |
ENTER | 13 | LEFT | 37 |
SHIFT | 16 | UP | 38 |
CONTROL | 17 | RIGHT | 39 |
ESC | 27 | DOWN | 40 |
SPACE | 32 | DELETE | 46 |
PAGEUP | 33 | F5 | 116 |
下面的代码监听一个文本框,如果按了空格,就会弹alert:(默然说话:从这里开始,我不再贴完整代码了,请参考前面完整代码的模式,来补全后面的代码,让程序正常运行,这对大家也是一个锻炼,有助于大家更深刻的理解代码的含义,对吧?)
Ext.get('event_object_test').on('keypress',function(e){
if(e.keyCode==Ext.EventObject.SPACE){//书上使用e.charCode,经实测在IE下不可行
Ext.Msg.alert('info','你按了空格');
}
});
如果我们想得到原始的浏览器事件,可以通过Ext.EventObject的browserEvent来获得。
另外,Ext.EventObject也有getPageX(),getPageY()、getPageXY()、getTarget()和getRelatedTarget()方法,这些方法实际上都是通过调用Ext.lib.Event实现的。
Ext.EventObject还有altKey、ctrlKey和shiftKey用来判断是否有功能键被按下,也可以使用hasModifier()判断是否有功能键被按下。这个功能一般要与其他的按键状态相配合,用于判断组合按键的情况。
Ext.EventObject提供的另一个有趣的功能函数名称为getWheelDelta(),可以获得鼠标滚轮的delta值。在下面的示例中,我们监听了mousewheel事件,在滚轮转动时动态修改了上面例子中文本框的宽度,这样又给我们提供了一种提升用户体验的方法,如下面的代码所示(默然说话:很爽哦,不信你试试?)。
Ext.get(document.body).on('mousewheel',function(e){
var delta=e.getWheelDelta();
var test=Ext.get('event_object_test');
width=test.getWidth();
test.setWidth(width+delta*500,true);
});
2.2 Ext的核心组件
Ext.Component是Ext中所有组件的基类,它的所有子类都自动享有标准Ext组件的生命周期,包括创建、渲染和销毁。它们也自动支持了标准的隐藏/显示以及启用/禁用等操作。
在Ext组件树中,每个组件都对应一个xtype属性,在Ext中可以通过xtype属性直接指定在某处使用的组件。(默然说话:Ext组件树可以参看书上23页的图2-3)
所有的组件都允许在Ext.Container及其子类中进行延迟渲染(lazy render),也可以把组件注册到Ext.ComponentMgr中,这样就可以在任何地方使用Ext.getCmp()函数直接根据id获得对应的组件。
所有可视化的组件都是Ext.Component的子类,这样我们就能把它们放到layout中进行布局管理。
表2-2 基本组件
xtype | 组件名称 | 描述 |
box | Ext.BoxComponent | 具有边框属性的组件 |
button | Ext.Button | 按钮 |
colorpalette | Ext.ColorPalette | 调色板 |
component | Ext.Component | 组件 |
container | Ext.Container | 容器 |
cycle | Ext.CycleButton | 循环按钮 |
dataview | Ext.DataView | 数据显示视图 |
datepicker | Ext.DatePicker | 日期选择面板 |
editor | Ext.Editor | 编辑器 |
editorgrid | Ext.grid.EditorGridPanel | 可编辑的表格 |
grid | Ext.grid.GridPanel | 表格 |
paging | Ext.PagingToolbar | 分页工具条 |
panel | Ext.Panel | 面板(可进行子布局) |
progress | Ext.ProgressBar | 进度条 |
splitbutton | Ext.SplitButton | 可下拉的按钮 |
tabpanel | Ext.TabPanel | 选项面板 |
treepanel | Ext.tree.TreePanel | 树 |
viewport | Ext.ViewPort | 视图 |
window | Ext.Window | 窗口 |
表2-3 工具条组件
xtype | 组件名称 | 描述 |
toolbar | Ext.Toolbar | 工具条 |
tbfill | Ext.Toolbar.Fill | 右对齐填充’->’ |
tbitem | Ext.Toolbar.Item | 工具条项目 |
tbseparator | Ext.Toolbar.Separator | 工具条分隔符’-’ |
tbspacer | Ext.Toolbar.Spacer | 工具条空白 |
tbtext | Ext.Toolbar.TextItem | 工具条文本项 |
表2-4 表单组件
xtype | 组件名称 | 描述 |
form | Ext.FormPanel | 表单面板 |
checkbox | Ext.form.Checkbox | 多选框 |
combo | Ext.form.ComboBox | 下拉列表 |
datefield | Ext.form.DateField | 日期选择项 |
field | Ext.form.Field | 输入框 |
fieldset | Ext.form.FieldSet | 组 |
hidden | Ext.form.Hidden | 表单隐藏域 |
htmleditor | Ext.form.HtmlEditor | HTML编辑器 |
numberfield | Ext.form.NumberField | 数字编辑器 |
radio | Ext.form.Radio | 单选框 |
textarea | Ext.form.TextArea | 区域文本框 |
textfield | Ext.form.TextField | 表单文本框 |
timefield | Ext.form.TimeField | 时间录入项 |
trigger | Ext.form.TriggerField | 触发录入项 |
可以使用pageX、pageY、x、y为Ext.BoxComponent指定具体的坐标,也使用width和height为Ext.BoxComponent指定长度和宽度,或者使用autoHeight和autoWidth让Ext.BoxComponent根据自身内容自动调整长度和高度。
下面的代码在网页里画了一个红色的框框:
<body>
<div id="test"></div>
<script type="text/javascript">
var box=new Ext.BoxComponent({
el:'test',//一个div的id名字
style:'background-color:red;position:absolute;',//样式表
pageX:100,//left的值
pageY:50,//top的值
width:200,
height:150
});
box.render();
</script>
</body>
Ext.Container继承自Ext.BoxComponent,它提供了两个重要的参数layout和items。layout参数指定当前组件使用何种布局,items参数中包含的是当前组件中的所有子组件。
所有的容器组件(如各种面板)都是Ext.Container的子类。
Ext.Panel是Ext中经常用到的一个组件,它直接继承自Ext.Container。我们可以使用title参数定义它的标题,使用tbar和bbar设置上下位置的工具条,使用collapseFirst、collapsed、collapsedClst和collapsible设置与面板折叠相关的配置。除此之外,还可以使用floating和shadow设置浮动阴影效果,使用HTML直接设置面板内容。
现在我们来设置一个包含浮动阴影的、可拖放、可折叠、设置了大小、位置、标题和内容的Ext.Panel:
<body>
<div id="test"></div>
<script type="text/javascript">
var panel=new Ext.Panel({
el:'test',
title:'这是标题',
floating:true,
shadow:true,
draggable:true,
collapsible:true,
html:'<h1>这是面板内容</h1>',
pageX:500,
pageY:50,
width:200,
height:150
});
panel.render();
</script>
</body>
<body>
<div id="test"></div>
<div id="test2"></div>
<script type="text/javascript">
vartabs=new Ext.TabPanel({
renderTo:document.body,
height:100
});
tabs.add({
title:'标题1',
html:'内容1',
closable:true
});
tabs.add({
id:Ext.id(),
title:'标题2',
html:'内容2',
closable:true
});
tabs.activate(0);
</script>
</body>