ExtJS是一个很不错的Ajax框架,可以用来开发带有华丽外观的富客户端应用,使得我们的b/s应用更加具有活力及生命力。ExtJS是一个用javascript编写,与后台技术无关的前端ajax框架。因此,可以把ExtJS用在.Net、Java、Php等各种开发语言开发的应用中。
最近我们在几个应用都使用到了ExtJS,对公司以前开发的一个OA系统也正在使用ExtJS2.0进行改造,使得整个系统在用户体验上有了非常大的变化。本教程记录了前段时间本人学习ExtJS的一些心得及小结,希望能帮助正在学习或准备学习ExtJS的朋友们快速走进ExtJS2.0的精彩世界。
教程包括ExtJS的新手入门、组件体系结构及使用、ExtJS中各控件的使用方法及示例应用等,是一个非常适合新手的ExtJS入门教程。本教程主要是针对ExtJS2.0进行介绍,全部代码、截图等都是基于ExtJS2.0。
在学习了本教程后,可以下载wlr.easyjf.com这个基于ExtJS2.0开发的单用户Blog系统的源代码,这个系统是我们团队中的williamraym与大峡等人开发的一个演示系统,系统源码整体质量比较高,通过学习这套源代码相邻一定能提高您ExtJS的综合水平。
本教程比较适合ExtJS的新手作为入门教程及手册使用,并在我的博客 http://www.easyjf.com/blog/lengyu/上进行发布;应一些朋友的要求,根据本教程的写作思路,我还编写了比本教程更为详细的《ExtJS实用开发指南》,包含详细的ExtJS框架使用方法、各个控件详细配置参数、属性、方法及事件介绍,与服务器端集成及一个完整的示例应用系统介绍等内容,适合想深入学习ExtJS或正在使用ExtJS进行开发朋友们使用。该《指南》当前在wlr.easyjf.com作为VIP文档发布,供该站的VIP用户阅读及下载。凡是购买了《ExtJS实用开发指南》文档的VIP用户,都可以在该指南印刷版出版后均会免费得到相应的印刷版本。
最后,希望广大网友把阅读本教程中的发现的错误、不足及建议等反馈给我们,让我们一起共同学习、共同进步,下面让我们一起进入精彩的ExtJS世界吧。
第一章、ExtJS简介
ExtJS是一个Ajax框架,是一个用javascript写的,用于在客户端创建丰富多彩的web应用程序界面。ExtJS可以用来开发RIA也即富客户端的AJAX应用,下面是一些使用ExtJS开发的应用程序截图:
(wlr的blog应用)
(ExtJS的表格控件)
(不同主题的ExtJS弹出框效果)
ExtJS是一个用javascript写的,主要用于创建前端用户界面,是一个与后台技术无关的前端ajax框架。因此,可以把ExtJS用在.Net、Java、Php等各种开发语言开发的应用中。
ExtJs最开始基于YUI技术,由开发人员Jack Slocum开发,通过参考Java Swing等机制来组织可视化组件,无论从UI界面上CSS样式的应用,到数据解析上的异常处理,都可算是一款不可多得的JavaScript客户端技术的精品。
要使用ExtJS,那么首先要得到ExtJS库文件,该框架是一个开源的,可以直接从官方网站下载,网址http://extjs.com/download,进入下载页面可以看到大致如图xxx所示的内容,可以选择选择1.1或2.0版本,本教程使用的2.0版本。
图1-1 ExtJs不同版本下载选择页面
单击上图中的【Download ext-2.0.zip】超链接进行下载,把下载得到的ZIP压缩文件解压缩到【D:\ExtCode】目录下,可以得到如如图1-2所示的内容。
图1-2 ExtJS发布包目录
adapter:负责将里面提供第三方底层库(包括Ext自带的底层库)映射为Ext所支持的底层库。
build: 压缩后的ext全部源码(里面分类存放)。
docs: API帮助文档。
exmaples:提供使用ExtJs技术做出的小实例。
resources:Ext UI资源文件目录,如CSS、图片文件都存放在这里面。
source: 无压缩Ext全部的源码(里面分类存放) 遵从Lesser GNU (LGPL) 开源的协议。
Ext-all.js:压缩后的Ext全部源码。
ext-all-debug.js:无压缩的Ext全部的源码(用于调试)。
ext-core.js:压缩后的Ext的核心组件,包括sources/core下的所有类。
ext-core-debug.js:无压缩Ext的核心组件,包括sources/core下的所有类。
2.2、应用ExtJS
应用extjs需要在页面中引入extjs的样式及extjs库文件,样式文件为resources/css/ext-all.css,extjs的js库文件主要包含两个,adapter/ext/ext-base.js及ext-all.js,其中ext-base.js表示框架基础库,ext-all.js是extjs的核心库。adapter表示适配器,也就是说可以有多种适配器,因此,可以把adapter/ext/ext-base.js换成adapter/jquery/ext-jquery-adapter.js,或adapter/prototype/ext-prototype-adapter.js等。因此,要使用ExtJS框架的页面中一般包括下面几句:
<script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="extjs/ext-all.js"></script> |
在ExtJS库文件及页面内容加载完后,ExtJS会执行Ext.onReady中指定的函数,因此可以用,一般情况下每一个用户的ExtJS应用都是从Ext.onReady开始的,使用ExtJS应用程序的代码大致如下:
<script> function fn() { alert(‘ExtJS库已加’); } Ext.onReady(fn); </script> |
fn也可以写成一个匿名函数的形式,因此上面的代码可以改成下面的形式:
<script> function fn() { alert(‘ExtJS库已加载!’); } Ext.onReady(function () { alert(‘ExtJS库已加载!’); } ); </script> |
下面我们写一个最简单的ExtJS应用,在hello.html文件中输入下面的代码:
<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>ExtJS</title> <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" /> <script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="extjs/ext-all.js"></script> <script> Ext.onReady(function() { Ext.MessageBox.alert("hello","Hello,easyjf open source"); }); </script> </head> <body> </body> </html> |
图1-11 hello.html页面效果
进一步,我们可以在页面上显示一个窗口,代码如下:
<script> Ext.onReady(function() { var win=new Ext.Window({title:"hello",width:300,height:200,html:'<h1>Hello,easyjf open source</h1>'}); win.show(); }); </script> |
在浏览hello.html,即可得在屏幕上显示一个窗口,如图xxx所示。
ExtJS由一系列的类库组成,一旦页面成功加载了ExtJS库后,我们就可以在页面中通过javascript调用ExtJS的类及控件来实现需要的功能。ExtJS的类库由以下几部分组成:
底层API(core):底层API中提供了对DOM操作、查询的封装、事件处理、DOM查询器等基础的功能。其它控件都是建立在这些底层api的基础上,底层api位于源代码目录的core子目录中,包括DomHelper.js、Element.js等文件,如图xx所示。
控件(widgets):控件是指可以直接在页面中创建的可视化组件,比如面板、选项板、表格、树、窗口、菜单、工具栏、按钮等等,在我们的应用程序中可以直接通过应用这些控件来实现友好、交互性强的应用程序的UI。控件位于源代码目录的widgets子目录中,包含如图xx所示。
实用工具Utils:Ext提供了很多的实用工具,可以方便我们实现如数据内容格式化、JSON数据解码或反解码、对Date、Array、发送Ajax请求、Cookie管理、CSS管理等扩展等功能,如图所示:
Ext2.0对框架进行了非常大的重构,其中最重要的就是形成了一个结构及层次分明的组件体系,由这些组件形成了Ext的控件,Ext组件是由 Component类定义,每一种组件都有一个指定的xtype属性值,通过该值可以得到一个组件的类型或者是定义一个指定类型的组件。
ExtJS中的组件体系由下图所示:
组件大致可以分成三大类,即基本组件、工具栏组件、表单及元素组件。
基本组件有:
xtype |
Class |
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 窗口 |
工具栏组件有 |
|
toolbar |
Ext.Toolbar 工具栏 |
tbbutton |
Ext.Toolbar.Button 按钮 |
tbfill |
Ext.Toolbar.Fill 文件 |
tbitem |
Ext.Toolbar.Item 工具条项目 |
tbseparator |
Ext.Toolbar.Separator 工具栏分隔符 |
tbspacer |
Ext.Toolbar.Spacer 工具栏空白 |
tbsplit |
Ext.Toolbar.SplitButton 工具栏分隔按钮 |
tbtext |
Ext.Toolbar.TextItem 工具栏文本项 |
表单及字段组件包含 |
|
form |
Ext.FormPanel Form面板 |
checkbox |
Ext.form.Checkbox checkbox录入框 |
combo |
Ext.form.ComboBox combo选择项 |
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 触发录入项 |
组件可以直接通过new 关键子来创建,比如控件一个窗口,使用new Ext.Window(),创建一个表格则使用new Ext.GridPanel()。当然,除了一些普通的组件以外,一般都会在构造函数中通过传递构造参数来创建组件。
组件的构造函数中一般都可以包含一个对象,这个对象包含创建组件所需要的配置属性及值,组件根据构造函数中的参数属性值来初始化组件。比如下面的例子:
var obj={title:"hello",width:300,height:200,html:'Hello,easyjf open source'}; var panel=new Ext.Panel(obj); panel.render("hello"); <div id="hello"> </div> |
运行上面的代码可以实现如下图所示的结果:
可以省掉变量obj,直接写成如下的形式:
var panel=new Ext.Panel({title:"hello",width:300,height:200,html:'<h1>Hello,easyjf open source</h1>'}); panel.render("hello"); |
render方法后面的参数表示页面上的div元素id,也可以直接在参数中通过renderTo参数来省略手动谳用render方法,只需要在构造函数的参数中添加一个renderTo属性即可,如下:
New Ext.Panel({renderTo:"hello",title:"hello",width:300,height:200,html:'<h1>Hello,easyjf open source </h1>'}); |
对于容器中的子元素组件,都支持延迟加载的方式创建控件,此时可以直接通过在需要父组件的构造函数中,通过给属性items传递数组方式实现构造。如下面的代码:
var panel=new Ext.TabPanel({width:300,height:200,items:[ {title:"面板1",height:30},{title:"面板2",height:30},{title:"面板3",height:30}]});panel.render("hello"); |
注意中括号中加粗部份的代码,这些代码定义了TabPanel这个容器控件中的子元素,这里包括三个面板。上面的代码与下面的代码等价:
var panel=new Ext.TabPanel({width:300,height:200,items:[new Ext.Panel( {title:"面板1",height:30}),new Ext.Panel({title:"面板2",height:30}),new Ext.Panel({title:"面板3",height:30})]});panel.render("hello"); |
前者不但省略掉了new Ext.Panel这个构造函数,最重要前者只有在初始化TabPanel的时候,才会创建子面板,而第二种方式则在程序一开始就会创建子面板。也就是说,前者实现的延迟加载。
在ExtJS中,除了一些特殊的组件或类以外,所有的组件在初始化的时候都可以在构造函数使用一个包含属性名称及值的对象,该对象中的信息也就是指组件的配置属性。
比如配置一个面板:
new Ext.Panel({ title:"面板", html"面板内容", height:100} ); |
再比如创建一个按钮:
var b=new Ext.Button({ text:"添加", pressed:true, heigth:30, handler:Ext.emptyFn }); |
再比如创建一个Viewport及其中的内容:
new Ext.Viewport({ layout:"border", items:[{region:"north", title:"面板", html:"面板内容", height:100}, {region:"center", xtype:"grid", title:"学生信息管理", store:troe, cm:colM, store:store, autoExpandColumn:3 } ] }); |
每一个组件除了继承基类中的配置属性以外,还会根据需要增加自己的配置属性,另外子类中有的时候还会把父类的一些配置属性的含义及用途重新定 义。学习及使用ExtJS,其中最关键的是掌握ExtJS中的各个组件的配置属性及具体的含义,这些配置属性在下载下来的ExtJS源码文档中都有详细的 说明,可以通过这个文档详细了解每一个组件的特性,如下图所示:
由于所有的组件都继承自Ext.Component,因此在这里我们列出组件基类Component中的配置属性简单介绍。
配置属性名称 |
类型 |
简介 |
allowDomMove |
Boolean |
当渲染这个组件时是否允许移动Dom节点(默认值为true)。 |
applyTo |
Mixed |
混 合参数,表示把该组件应用指定的对象。参数可以是—节点的id,一个DOM节点或一个存在的元素或与之相对应的在document中已出现的id。当使用 applyTo,也可以提供一个id或CSS的class名称,如果子组件允许它将尝试创建一个。如果指写applyTo选项,所有传递到 renderTo方法的值将被忽略,并且目标元素的父节点将自动指定为这个组件的容器。使用applyTo选项后,则不需要再调用render()方法来 渲染组件。 |
autoShow |
Boolean |
自动显示,如为true,则组件将检查所有隐藏类型的class(如:’x-hidden’ 或’x-hide-display’并在渲染时移除(默认为false)。 |
cls |
String |
给组件添加额外的样式信息,(默认值为''),如果想自定义组件或它的子组件的样式,这个选项是非常有用的。 |
ctCls |
String |
给组件的容器添加额外的样式信息,默认值为'')。 |
disabledClass |
String |
给被禁用的组件添加额外的CSS样式信息,(默认为"x-item-disabled")。 |
hideMode |
String |
组件的隐藏方式,支持的值有’visibility’,也就是css里的visibility,’offsets’负数偏移位置的值和’display’也就是css里的display,默认值为’display’。 |
hideParent |
Boolean |
是否隐藏父容器,该值为true时将会显示或隐藏组件的容器,false时则只隐藏和显示组件本身(默认值为false)。 |
id |
String |
组件的id,默认为一个自动分配置的id。 |
listeners |
Object |
给对象配置多个事件监听器,在对象初始化会初始化这些监听器。 |
plugins |
Object/Array |
一 个对象或数组,将用于增加组件的自定义功能。一个有效的组件插件必须包含一个init方法,该方法可以带一个Ext.Component类型参数。当组件 建立后,如果该组件包含有效的插件,将调用每一个插件的init方法,把组件传递给插件,插件就能够实现对组件的方法调用及事件应用等,从而实现对组件功 能的扩充。 |
renderTo |
Mixed |
混合数据参数,指定要渲染到节点的id,一个DOM的节点或一个已存在的容器。如果使用了这个配置选项,则组件的render()就不是必需的了。 |
stateEvents |
Array |
定义需要保存组件状态信息的事件。当指定的事件发生时,组件会保存它的状态(默认为none),其值为这个组件支持的任意event类型,包含组件自身的或自定义事件。(例如:[‘click’,’customerchange’])。 |
stateId |
String |
组件的状态ID,状态管理器使用该id来管理组件的状态信息,默认值为组件的id。 |
style |
String |
给该组件的元素指定特定的样式信息,有效的参数为Ext.Element.applyStyles中的值。 |
xtype |
String |
指定所要创建组件的xtype,用于构造函数中没有意义。该参数用于在容器组件中创建创建子组件并延迟实例化和渲染时使用。如果是自定义的组件,则需要用Ext.ComponentMgr.registerType来进行注册,才会支持延迟实例化和渲染。 |
el |
Mixed |
相当于applyTo |
关于ExtJS中组件的详细使用说明,包括Component的属性Properties、方法及事件详细,请参考wlr.easyjf.com中的VIP文档《ExtJS组件Component详解(1)、(2)》。
ExtJS提供了一套强大的事件处理机制,通过这些事件处理机制来响应用户的动作、监控控件状 态变化、更新控件视图信息、与服务器进行交互等等。事件统一由Ext.EventManager对象管理,与浏览器W3C标准事件对象Event相对 应,Ext封装了一个Ext.EventObject事件对象。支持事件处理的类(或接口)为Ext.util.Observable,凡是继承该类的组 件或类都支持往对象中添加事件处理及响应功能。
首先我们来看标准html中的事件处理,看下面的html代码:
<script> function a() { alert('some thing'); } </script> <input id="btnAlert" type="button" onclick="a();" value="alert框" /> |
点击这个按钮则会触发onclick事件,并执行onclick事件处理函数中指定的代码,这里直接执行函数a中的代码,也即弹出一个简单的信息提示框。再简单修改一下上面的代码,内容如下:
<script> function a() { alert('some thing'); } window.onload=function(){ document.getElementById("btnAlert").onclick=a; } </script> <input id="btnAlert" type="button" value="alert框" /> |
上面的代码在文档加载的时候,就直接对btnAlert的onclick赋值,非常清晰的指明了按钮btnAlert的onclick事件响应函数为a,注意这里a后面不能使用括号“()”。
ExtJS中组件的事件处理跟上面相似,看下面的代码:
<script> function a(){ alert('some thing'); } Ext.onReady(function(){ Ext.get("btnAlert").addListener("click",a); }); </script> <input id="btnAlert" type="button" value="alert框" /> |
Ext.get("btnAlert")得到一个与页面中按钮btnAlert关联的Ext.Element对象,可以直接调用该对象上的 addListener方法来给对象添加事件,同样实现前面的效果。在调用addListener 方法的代码中,第一个参数表示事件名称,第二个参数表示事件处理器或整个响应函数。
ExtJS支持事件队列,可以往对象的某一个事件中添加多个事件响应函数,看下面的代码:
Ext.onReady(function(){ Ext.get("btnAlert").on("click",a); Ext.get("btnAlert").on("click",a); }); |
addLinster方法的另外一个简写形式是on,由于调用了两次addListener方法,因此当点击按钮的时候会弹出两次信息。
当然,ExtJS还支持事件延迟处理或事件处理缓存等功能,比如下面的代码:
Ext.onReady(function(){ Ext.get("btnAlert").on("click",a,this,{delay:2000}); }); |
由于在调用addListener的时候传递指定的delay为2000,因此当用户点击按钮的时候,不会马上执行事件响应函数,而是在2000毫秒,也就是两秒后才会弹出提示信息框。
当然,在使用Ext的事件时,我们一般是直接在控件上事件,每一个控件包含哪些事件,在什么时候触发,触发时传递的参数等,在ExtJS项目的文档中 都有较为详细的说明。比如对于所有的组件Component,都包含一个beforedestroy事件,该事件会在Ext销毁这一个组件时触发,如果事 件响应函数返回false,则会取消组件的销毁操作。
Ext.onReady(function(){ var win=new Ext.Window({ title:"不能关闭的窗口", height:200, width:300 }); win.on("beforedestroy",function(obj){ alert("想关闭我,这是不可能的!"); obj.show(); return false; }); win.show();}); |
由于在窗口对象的beforedestroy事件响应函数返回值为false,因此执行这段程序,你会发现这个窗口将无法关闭。组件的事件监听器可以直接在组件的配置属性中直接声明,如下面的代码与前面实现的功能一样:
Ext.onReady(function(){ var win=new Ext.Window({ title:"不能关闭的窗口", height:200, width:300, listeners:{"beforedestroy":function(obj){ alert("想关闭我,这是不可能的!"); obj.show(); return false; }} }); win.show();}); |
了解了ExtJS中的基本事件处理及使用方法,就可以在你的应用中随心所欲的进行事件相关处理操作了。
关于ExtJS中事件处理中作用域、事件处理原理、给自定义的组件添加事件、处理相关的Ext.util.Observable及Ext.EventManager类详细介绍,请参考wlr.easyjf.com中的VIP文档《ExtJS中的事件处理详解》。
面板Panel是ExtJS控件的基础,很高级控件都是在面板的基础上扩展的,还有其它大多数控件也都直接或间接有关系。应用程序的界面一般情况下是由一个一个的面板通过不同组织方式形成。
面板由以下几个部分组成,一个顶部工具栏、一个底部工具栏、面板头部、面板尾部、面板主区域几个部分组件。面板类中还内置了面板展开、关闭等功能,并 提供一系列可重用的工具按钮使得我们可以轻松实现自定义的行为,面板可以放入其它任何容器中,面板本身是一个容器,他里面又可以包含各种其它组件。
面板的类名为Ext.Panel,其xtype为panel,下面的代码可以显示出面板的各个组成部分:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"面板头部header", width:300, height:200, html:'<h1>面板主区域</h1>', tbar:[{text:'顶部工具栏topToolbar'}], bbar:[{text:'底部工具栏bottomToolbar'}], buttons:[{text:"按钮位于footer"}] }); }); |
运行该代码,可以得到如图xx所示的输出结果,清楚的表示出了面板的各个组成部分。
一般情况下,顶部工具栏或底部工具栏只需要一个,而面板中一般也很少直接包含按钮,一般会把面板上的按钮直接放到工具栏上面。比如下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"hello", width:300, height:200, html:'<h1>Hello,easyjf open source!</h1>', tbar:[{pressed:true,text:'刷新'}] }); }); |
可以得到如图xx所示的效果,该面板包含面板Header,一个顶部工具栏及面板区域三个部分。
面板中可以有工具栏,工具栏可以位于面板顶部或底部,Ext中工具栏是由Ext.Toolbar类表示。工具栏上可以存放按钮、文本、分隔符等内容。面板对象中内置了很多实用的工具栏,可以直接通过面板的tools配置选项往面板头部加入预定义的工具栏选项。比如下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"hello", width:300, height:200, html:'<h1>Hello,easyjf open source!</h1>', tools:[{ id:"save"}, {id:"help", handler:function(){Ext.Msg.alert('help','please help me!');} }, {id:"close"}], tbar:[{pressed:true,text:'刷新'}] }); }); |
注意我们在Panel的构造函数中设置了tools属性的值,表示在面板头部显示三个工具栏选项按钮,分别是保存"save"、"help"、"close"三种。代码运行的效果图如下:
点击help按钮会执行handler中的函数,显示一个弹出对话框,而点击其它的按钮不会有任何行为产生,因为没有定义他们的heanlder。
除了在面板头部加入这些已经定义好的工具栏选择按钮以外,还可以在顶部或底工具栏中加入各种工具栏选项。这些工具栏选项主要包括按钮、文本、空白、填充条、分隔符等。代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"hello", width:300, height:200, html:'<h1>Hello,easyjf open source!</h1>', tbar:[new Ext.Toolbar.TextItem('工具栏:'), {xtype:"tbfill"}, {pressed:true,text:'添加'}, {xtype:"tbseparator"}, {pressed:true,text:'保存'} ] }); }); |
将会得到如图xx所示的结果:
Ext中的工具栏项目主要包含下面的类:
Ext.Toolbar.Button-按钮,xtype为tbbutton
TextItem-
Ext.Toolbar.Fill-
Separator-
Spacer-
SplitButton-
4.3、选项面板的 TabPanel
在前面的示例中,为了显示一个面板,我们需要在页面上添加一个,然后把 Ext控件渲染到这个div上。VeiwPort代表整个浏览器显示区域,该对象渲染到页面的body区域,并会随着浏览器显示区域的大小自动改变,一个 页面中只能有一个ViewPort实例。看下面的代码:
Ext.onReady(function(){ new Ext.Viewport({ enableTabScroll:true, layout:"fit", items:[{title:"面板", html:"", bbar:[{text:"按钮1"}, {text:"按钮2"}] }] }); }); |
运行上面的代码会得到如图xxx所示的输出结果。
Viewport不需要再指定renderTo,而我们也看到Viewport确实填充了整个浏览器显示区域,并会随着浏览器显示区域大小的改变而改改。
Viewport主要用于应用程序的主界面,可以通过使用不同的布局来搭建出不同风格的应用程序主界面。在Viewport上常用的布局有fit、border等,当然在需要的时候其它布局也会常用。看下面的代码:
Ext.onReady(function(){ new Ext.Viewport({ enableTabScroll:true, layout:"border", items:[{title:"面板", region:"north", height:50, html:"<h1>网站后台管理系统!</h1>" }, {title:"菜单", region:"west", width:200, collapsible:true, html:"菜单栏" }, { xtype:"tabpanel", region:"center", items:[{title:"面板1"}, {title:"面板2"}] } ] }); }); |
运行上面的程序会得如图xx所示的效果。
ExtJS中窗口是由Ext.Window类定义,该类继承自Panel,因此窗口其实是一种特殊的面板Panel。窗口包含了浮动、可拖动、可关闭、最大化、最小化等特性。看下面的代码:
var i=0; function newWin() { var win=new Ext.Window({title:"窗口"+i++, width:400, height:300, maximizable:true}); win.show(); } Ext.onReady(function(){ Ext.get("btn").on("click",newWin); });
|
页面中的html内容:
<input id="btn" type="button" name="add" value="新窗口" /> |
执行上面的代码,当点击按钮“新窗口”的时候,会在页面中显示一个窗口,窗口标题为“窗口x”,窗口可以关闭,可以最大化,点击最大化按钮会最大化窗口,最大化的窗口可以还原,如图xxx所示。
窗口是分组进行管理的,可以对一组窗口进行操作,默认情况下的窗口都在默认的组 Ext.WindowMgr中。窗口分组由类Ext.WindowGroup定义,该类包括bringToFront、getActive、 hideAll、sendToBack等方法用来对分组中的窗口进行操作。
看下面的代码:
var i=0,mygroup; function newWin() { var win=new Ext.Window({title:"窗口"+i++, width:400, height:300, maximizable:true, manager:mygroup}); win.show(); } function toBack() { mygroup.sendToBack(mygroup.getActive()); } function hideAll() { mygroup.hideAll(); } Ext.oReay(function(){ mygroup=new Ext.WindowGroup(); Ext.get("btn").on("click",newWin); Ext.get("btnToBack").on("click",toBack); Ext.get("btnHide").on("click",hideAll); });
|
页面中的html代码
<input id="btn" type="button" name="add" value="新窗口" /> <input id="btnToBack" type="button" name="add" value="放到后台" /> <input id="btnHide" type="button" name="add" value="隐藏所有" /> |
执行上面的代码,先点击几次“新窗口”按钮,可以在页面中显示几个容器,然后拖动这些窗口,让他们在屏幕中不同的位置。然后点“放到后台”按钮,可以实现把最前面的窗口移动该组窗口的最后面去,点击“隐藏所有”按钮,可以隐藏当前打开的所有窗口。如下图所示:
由于传统使用alert、confirm等方法产生的对话框非常古板,不好看。因此,ExtJS提供了一套非常漂亮的对话框,可以使用这些对话框代替传统的alert、confirm等,实现华丽的应用程序界面。
Ext的对话框都封装在Ext.MessageBox类,该类还有一个简写形式即Ext.Msg,可以直接通过Ext.MessageBox或Ext.Msg来直接调用相应的对话框方法来显示Ext对话框。看下面的代码:
Ext.onReady(function(){ Ext.get("btnAlert").on("click",function(){ Ext.MessageBox.alert("请注意","这是ExtJS的提示框"); }); }); |
Html页面中的内容:
<input id="btnAlert" type="button" value="alert框" /> |
执行程序,点击上面的“alert框”按钮,将会在页面上显示如下图所示的对话框。
除了alert以外,Ext还包含confirm、prompt、progress、wait等对话框,另外我们可以根据需要显示自下定义的对 话框。普通对话框一般包括四个参数,比如confirm的方法签名为confirm ( String title, String msg, [Function fn], [Object scope] ) ,参数title表示对话框的标题,参数msg表示对话框中的提示信息,这两个参数是必须的;可选的参数fn表示当关闭对话框后执行的回调函数,参数 scope表示回调函数的执行作用域。回调函数可以包含两个参数,即button与text,button表示点击的按钮,text表示对话框中有活动输 入选项时输入的文本内容。我们可以在回调函数中通过button参数来判断用户作了什么什么选择,可以通过text来读取在对话框中输入的内容。看下面的 例子:]
Ext.onReady(function(){ Ext.get("btn").on("click",function(){ Ext.MessageBox.confirm("请确认","是否真的要删除指定的内容",function(button,text){ alert(button); alert(text); }); }); });
|
Html内容:
<input id="对话框" type="button" value="btn" /> |
点击对话框按钮将会出现下面的对话框,然后选择yes或no则会用传统的提示框输出回调函数中button及text参数的内容。
因此,在实际的应用中,上面的代码可以改成如下的内容:
Ext.onReady(function(){ Ext.get("btnAlert").on("click",function(){ Ext.MessageBox.confirm("请确认","是否真的要删除指定的内容",function(button,text){ if(button=="yes"){ //执行删除操作 alert("成功删除"); } }); }); });
|
这样当用户点击对话框中的yes按钮时,就会执行相应的操作,而选择no则忽略操作。
下面再看看prompt框,我们看下面的代码:
Ext.onReady(function(){ Ext.get("btn").on("click",function(){ Ext.MessageBox.prompt("输入提示框","请输入你的新年愿望:",function(button,text){ if(button=="ok"){ alert("你的新年愿望是:"+text); } else alert("你放弃了录入!"); }); }); }); |
Html页面:
<input id="btn" type="button" value="对话框" /> |
点击上面的“对话框”按钮可以显示如下图所示的内容,如果点击OK按钮则会输入你输入的文本内容,选择cancel按钮则会提示放弃了录入,如下图所示:
在实际应用中,可以直接使用MessageBox的show方法来显示自定义的对话框,如下面的代码:
function save(button) { if(button=="yes") { //执行数据保存操作 } else if(button=="no") { //不保存数据 } else { //取消当前操作 } } Ext.onReady(function(){ Ext.get("btn").on("click",function(){ Ext.Msg.show({ title:'保存数据', msg: '你已经作了一些数据操作,是否要保存当前内容的修改?', buttons: Ext.Msg.YESNOCANCEL, fn: save, icon: Ext.MessageBox.QUESTION}); }); });
|
点击“对话框”按钮可显示一个自定义的保存数据对话框,对话框中包含yes、no、cancel三个按钮,可以在回调函数save中根据点击的按钮执行相应的操作,如图xx所示。
所谓布局就是指容器组件中子元素的分布、排列组合方式。Ext的所有容器组件都支持而局操作,每一个容器都会有一个对应的布局,布局负责管理容器组件中子元素的排列、组合及渲染方式等。
ExtJS的布局基类为Ext.layout.ContainerLayout,其它布局都是继承该类。ExtJS的容器组件包含一个layout及layoutConfig配置属性,这两个属性用来指定容器使用的布局及布局的详细配置信息,如果没有指定容器组件的layout则默认会使用ContainerLayout作为布局,该布局只是简单的把元素放到容器中,有的布局需要layoutConfig配置,有的则不需要layoutConfig配置。看代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", width:400, height:200, layout:"column", items:[{columnWidth:.5, title:"面板1"}, {columnWidth:.5, title:"面板2"}] }); }); |
上面的代码我们创建了一个面板Panel,Panle是一个容器组件,我们使用layout指定该面板使用Column布局。该面板的子元素是两个面板,这两个面板都包含了一个与列布局相关的配置参数属性columnWidth,他们的值都是0.5,也就是每一个面板占一半的宽度。执行上面的程序生成如下图所示的结果:
Ext中的一些容器组件都已经指定所使用的布局,比如TabPanel使用card布局、FormPanel使用form布局,GridPanel中的表格使用column布局等,我们在使用这些组件的时候,不能给这些容器组件再指定另外的布局。
ExtJS2.0一共包含十种布局,常用的布局有border、column、fit、form、card、tabel等布局,下面我们分别对这几种布局作简单的介绍。
Border 布局由类Ext.layout.BorderLayout定义,布局名称为border。该布局把容器分成东南西北中五个区域,分别由east,south, west,north, cente来表示,在往容器中添加子元素的时候,我们只需要指定这些子元素所在的位置,Border布局会自动把子元素放到布局指定的位置。看下面的代码:
Ext.onReady(function(){ new Ext.Viewport({ layout:"border", items:[{region:"north", height:50, title:"顶部面板"}, {region:"south", height:50, title:"底部面板"}, {region:"center", title:"中央面板"}, {region:"west", width:100, title:"左边面板"}, {region:"east", width:100, title:"右边面板"} ] }); }); |
执行上面的代码将会在页面中输出包含上下左右中五个区域的面板,如下图所示:
Column 列布局由Ext.layout.ColumnLayout类定义,名称为column。列布局把整个容器组件看成一列,然后往里面放入子元素的时候,可以通过在子元素中指定使用columnWidth或width来指定子元素所占的列宽度。columnWidth表示使用百分比的形式指定列宽度,而width则是使用绝对象素的方式指定列宽度,在实际应用中可以混合使用两种方式。看下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", layout:"column", width:500, height:100, items:[{title:"列1",width:100}, {title:"列2",width:200}, {title:"列3",width:100}, {title:"列4"} ] } ); }); |
上面的代码在容器组件中放入了四个元素,在容器组件中形成4列,列的宽度分别为100,200,100及剩余宽度,执行结果如下图所示。
也可使用columnWidth来定义子元素所占的列宽度,看下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", layout:"column", width:500, height:100, items:[{title:"列1",columnWidth:.2}, {title:"列2",columnWidth:.3}, {title:"列3",columnWidth:.3}, {title:"列4",columnWidth:.2} ] } ); }); |
注意columnWidth的总和应该为1,执行代码将生成如下图所示的内容:
在实际应用中还可以混合使用,看下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", layout:"column", width:500, height:100, items:[{title:"列1",width:200}, {title:"列2",columnWidth:.3}, {title:"列3",columnWidth:.3}, title:"列4",columnWidth:.4} ] } ); }); |
执行上面的代码将会生成如下图所示的结果:
Column列布局由Ext.layout.ColumnLayout类定义,名称为column。列布局把整个容器组件看成一列,然后往里面放入子元素的时候,可以通过在子元素中指定使用columnWidth或width来指定子元素所占的列宽度。columnWidth表示使用百分比的形式指定列宽度,而width则是使用绝对象素的方式指定列宽度,在实际应用中可以混合使用两种方式。看下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", layout:"column", width:500, height:100, items:[{title:"列1",width:100}, {title:"列2",width:200}, {title:"列3",width:100}, {title:"列4"} ] } ); });
|
上面的代码在容器组件中放入了四个元素,在容器组件中形成4列,列的宽度分别为100,200,100及剩余宽度,执行结果如下图所示。
再看使用Fit布局后的代码,如下:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", layout:"fit", width:500, height:100, items:[{title:"子元素",html:"这是子元素中的内容"} ] } ); }); |
上面的代码指定父容器使用Fit布局,因此子将自动填满整个父容器。输出的图形如下:
如果容器组件中有多个子元素,则只会显示一个元素,如下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", layout:"fit", width:500, height:100, items:[{title:"子元素1",html:"这是子元素1中的内容"}, {title:"子元素2",html:"这是子元素2中的内容"} ] } ); }); |
输出的结果如下:
如果不使用布局Fit,代码如下:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", width:500, height:120, items:[{title:"子元素1",html:"这是子元素1中的内容"}, {title:"子元素2",html:"这是子元素2中的内容"} ] } ); }); |
输出的结果如下图所示:
Form布局由类Ext.layout.FormLayout定义,名称为form,是一种专门用于管理表单中输入字段的布局,这种布局主要用于在程序中创建表单字段或表单元素等使用。看下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", width:300, layout:"form", hideLabels:false, labelAlign:"right", height:120, defaultType: 'textfield', items:[ {fieldLabel:"请输入姓名",name:"name"}, {fieldLabel:"请输入地址",name:"address"}, {fieldLabel:"请输入电话",name:"tel"} ] } ); }); |
上面的代码创建了一个面板,面板使用Form布局,面板中包含三个子元素,这些子元素都是文本框字段,在父容器中还通过hideLabels、labelAlign等配置属性来定义了是否隐藏标签、标签对齐方式等。上面代码的输出结果如下图所示:
可以在容器组件中把hideLabels设置为true,这样将不会显示容器中字段的标签了,如下图所示:
在实际应用中,Ext.form.FormPanel这个类默认布局使用的是Form布局,而且FormPanel还会创建与 <form> 标签相关的组件,因此一般情况下我们直接使用FormPanel即可。上面的例子可改写成如下的形式:
Ext.onReady(function(){ new Ext.form.FormPanel({ renderTo:"hello", title:"容器组件", width:300, labelAlign:"right", height:120, defaultType: 'textfield', items:[ {fieldLabel:"请输入姓名",name:"name"}, {fieldLabel:"请输入地址",name:"address"}, {fieldLabel:"请输入电话",name:"tel"} ] } ); }); |
程序结果与前面使用Ext.Panel并指定form布局的一样,如下图所示:
Accordion布局由类Ext.layout.Accordion定义,名称为accordion,表示可折叠的布局,也就是说使用该布局的容器组件中的子元素是可折叠的形式。来看下面的代码:
Ext.onReady(function(){ new Ext.Panel({ renderTo:"hello", title:"容器组件", width:500, height:200, layout:"accordion", layoutConfig: { animate: true }, items:[{title:"子元素1",html:"这是子元素1中的内容"}, {title:"子元素2",html:"这是子元素2中的内容"}, {title:"子元素3",html:"这是子元素3中的内容"} ] } ); }); |
上面的代码定义了一个容器组件,指定使用Accordion布局,该容器组件中包含三个子元素,在layoutConfig中指定布局配置参数animate为true,表示在执行展开折叠时是否应用动画效果。执行结果将生成如下图所示的界面:
点击每一个子元素的头部名称或右边的按钮,则会展开该面板,并收缩其它已经展开的面板,如下图:
Table布局由类Ext.layout.TableLayout定义,名称为table,该布局负责把容器中的子元素按照类似普通html标签
Ext.onReady(function(){ var panel=new Ext.Panel({ renderTo:"hello", title:"容器组件", width:500, height:200, layout:"table", layoutConfig: { columns: 3 },
items:[{title:"子元素1",html:"这是子元素1中的内容",rowspan:2,height:100}, {title:"子元素2",html:"这是子元素2中的内容",colspan:2}, {title:"子元素3",html:"这是子元素3中的内容"}, {title:"子元素4",html:"这是子元素4中的内容"} ] } ); }); |
上面的代码创建了一个父容器组件,指定使用Table布局,layoutConfig使用columns指定父容器分成3列,子元素中使用rowspan或colspan来指定子元素所横跨的单元格数。程序的运行效果如下图所示:
除了前面介绍的几种布局以外,Ext2.0中还包含其它的Ext.layout.AbsoluteLayout、Ext.layout.AnchorLayout等布局类,这些布局主要作为其它布局的基类使用,一般情况下我们不会在应用中直接使用。另外,我们也可以继承10种布局类的一种,来实现自定义的布局。
关于ExtJS布局的详细说明,请参考wlr.easyjf.com中的VIP文档《ExtJS布局Layout详解(1)、(2)、(3)》。
ExtJS中的表格功能非常强大,包括了排序、缓存、拖动、隐藏某一列、自动显示行号、列汇总、单元格编辑等实用功能。
表格由类Ext.grid.GridPanel定义,继承自Panel,其xtype为grid。ExtJS中,表格Grid必须包含列定义信息,并指定表格的数据存储器Store。表格的列信息由类Ext.grid.ColumnModel定义、而表格的数据存储器由Ext.data.Store定义,数据存储器根据解析的数据不同分为JsonStore、SimpleStroe、GroupingStore等。
我们首先来看最简单的使用表格的代码:
Ext.onReady(function(){ var data=[ [1, 'EasyJWeb', 'EasyJF','www.easyjf.com'], [2, 'jfox', 'huihoo','www.huihoo.org'], [3, 'jdon', 'jdon','www.jdon.com'], [4, 'springside', 'springside','www.springside.org.cn'] ]; var store=new Ext.data.SimpleStore({data:data,fields:["id","name","organization","homepage"]}); var grid = new Ext.grid.GridPanel({ renderTo:"hello", title:"中国Java开源产品及团队", height:150, width:600, columns:[{header:"项目名称",dataIndex:"name"}, {header:"开发团队",dataIndex:"organization"}, {header:"网址",dataIndex:"homepage"}], store:store, autoExpandColumn:2 }); }); |
执行上面的代码,可以得到一个简单的表格,如下图所示:
上面的代码中,第一行“var data=…”用来定义表格中要显示的数据,这是一个[][]二维数组;第二行“var store=…”用来创建一个数据存储,这是GridPanel需要使用配置属性,数据存储器Store负责把各种各样的数据(如二维数组、JSon对象数组、xml文本)等转换成ExtJS的数据记录集Record,关于数据存储器Store我们将在下一章中作专门介绍。第三行“var grid = new Ext.grid.GridPanel(…)”负责创建一个表格,表格包含的列由columns配置属性来描述,columns是一数组,每一行数据元素描述表格的一列信息,表格的列信息包含列头显示文本(header)、列对应的记录集字段(dataIndex)、列是否可排序(sorable)、列的渲染函数(renderer)、宽度(width)、格式化信息(format)等,在上面的列子中只用到了header及dataIndex。
下面我们看简单看看表格的排序及隐藏列特性,简单修改一下上面的代码,内容如下:
Ext.onReady(function(){ var data=[ [1, 'EasyJWeb', 'EasyJF','www.easyjf.com'], [2, 'jfox', 'huihoo','www.huihoo.org'], [3, 'jdon', 'jdon','www.jdon.com'], [4, 'springside', 'springside','www.springside.org.cn'] ]; var store=new Ext.data.SimpleStore({data:data,fields:["id","name","organization","homepage"]}); var colM=new Ext.grid.ColumnModel([{header:"项目名称",dataIndex:"name",sortable:true}, {header:"开发团队",dataIndex:"organization",sortable:true}, {header:"网址",dataIndex:"homepage"}]); var grid = new Ext.grid.GridPanel({ renderTo:"hello", title:"中国Java开源产品及团队", height:200, width:600, cm:colM, store:store, autoExpandColumn:2 }); }); |
直接使用new Ext.grid.ColumnModel来创建表格的列信定义信息,在“项目名称“及“开发团队”列中我们添加了sortable为true的属性,表示该列可以排序,执行上面的代码,我们可以得到一个支持按“项目名称“或“开发团队”的表格,如图xxx所示。
(按项目名称排序)
(可排序的列表头后面小按钮可以弹出操作菜单)
另外,每一列的数据渲染方式还可以自己定义,比如上面的表格中,我们希望用户在表格中点击网址则直接打开这些开源团队的网站,也就是需要给网址这一列添加上超级连接。下面的代码实现这个功能:
function showUrl(value) { return ""+value+""; } Ext.onReady(function(){ var data=[ [1, 'EasyJWeb', 'EasyJF','www.easyjf.com'], [2, 'jfox', 'huihoo','www.huihoo.org'], [3, 'jdon', 'jdon','www.jdon.com'], [4, 'springside', 'springside','www.springside.org.cn'] ]; var store=new Ext.data.SimpleStore({data:data,fields:["id","name","organization","homepage"]}); var colM=new Ext.grid.ColumnModel([{header:"项目名称",dataIndex:"name",sortable:true}, {header:"开发团队",dataIndex:"organization",sortable:true}, {header:"网址",dataIndex:"homepage",renderer:showUrl}]); var grid = new Ext.grid.GridPanel({ renderTo:"hello", title:"中国Java开源产品及团队", height:200, width:600, cm:colM, store:store, autoExpandColumn:2 }); }); |
上面的代码跟前面的示例差别不大,只是在定义“网址”列的时候多了一个renderer属性,即{header:"网址",dataIndex:"homepage",renderer:showUrl}。showUrl是一个自定义的函数,内容就是根据传入的value参数返回一个包含<a>标签的html片段。运行上面的代码显示结果如下图所示:
自定义的列渲染函数可以实现在单元格中显示自己所需要的各种信息,只是的浏览器能处理的html都可以。
除了二级数组以外,表格还能显示其它格式的数据吗?答案是肯定的,下面假如我们的表格数据data定义成了下面的形式:
var data=[{id:1, name:'EasyJWeb', organization:'EasyJF', homepage:'www.easyjf.com'}, {id:2, name:'jfox', organization:'huihoo', homepage:'www.huihoo.org'}, {id:3, name:'jdon', organization:'jdon', homepage:'www.jdon.com'}, {id:4, name:'springside', organization: 'springside', homepage:'www.springside.org.cn'} ];
|
也就是说数据变成了一维数组,数组中的每一个元素是一个对象,这些对象包含name、organization、homepage、id等属性。要让表格显示上面的数据,其实非常简单,只需要把store改成用Ext.data.JsonStore即可,代码如下:
var store=new Ext.data.JsonStore({data:data,fields:["id","name","organization","homepage"]}); var colM=new Ext.grid.ColumnModel([{header:"项目名称",dataIndex:"name",sortable:true}, {header:"开发团队",dataIndex:"organization",sortable:true}, {header:"网址",dataIndex:"homepage",renderer:showUrl}]); var grid = new Ext.grid.GridPanel({ renderTo:"hello", title:"中国Java开源产品及团队", height:200, width:600, cm:colM, store:store, autoExpandColumn:2 }); |
上面的代码得到的结果与前面的一样。当然,表格同样能显示xml格式的数据,假如上面的数据存放成hello.xml文件中,内容如下:
|
为了把这个xml数据用ExtJS的表格Grid进行显示,我们只需要把store部分的内容调整成如下的内容即可:
<?xml version="1.0" encoding="UTF-8"?> <dataset> <row> <id>1</id> <name>EasyJWeb</name> <organization>EasyJF</organization> <homepage>www.easyjf.com</homepage> </row> <row> <id>2</id> <name>jfox</name> <organization>huihoo</organization> <homepage>www.huihoo.org</homepage> </row> <row> <id>3</id> <name>jdon</name> <organization>jdon</organization> <homepage>www.jdon.com</homepage> </row> <row> <id>4</id> <name>springside</name> <organization>springside</organization> <homepage>www.springside.org.cn</homepage> </row> </dataset> |
var store=new Ext.data.Store({ url:"hello.xml", reader:new Ext.data.XmlReader({ record:"row"}, ["id","name","organization","homepage"]) }); |
其它的部分不用改变,完整的代码如下:
function showUrl(value) { return "<a href='http://"+value+"' target='_blank'>"+value+"</a>"; } Ext.onReady(function(){ var store=new Ext.data.Store({ url:"hello.xml", reader:new Ext.data.XmlReader({ record:"row"}, ["id","name","organization","homepage"]) }); var colM=new Ext.grid.ColumnModel([{header:"项目名称",dataIndex:"name",sortable:true}, {header:"开发团队",dataIndex:"organization",sortable:true}, {header:"网址",dataIndex:"homepage",renderer:showUrl}]); var grid = new Ext.grid.GridPanel({ renderTo:"hello", title:"中国Java开源产品及团队", height:200, width:600, cm:colM, store:store, autoExpandColumn:2 }); store.load(); }); |
store.laod()是用来加载数据,执行上面的代码产生的表格与前面的完全一样。
关于表格控件GridPanel的详细说明,请参考wlr.easyjf.com中的VIP文档《ExtJS表格GridPanel详解》。
可编辑表格是指可以直接在表格的单元格对表格的数据进行编辑,ExtJS中的可编辑表格由类Ext.grid.EditorGridPanel表示,xtype为editorgrid。使用EditorGridPanel与使用普通的GridPanel方式一样,区别只是在定义列信息的时候,可以指定某一列使用的编辑即可,下面来看一个简单的示例。
Ext.onReady(function(){ var data=[{id:1, name:'小王', email:'[email protected]', sex:'男', bornDate:'1991-4-4'}, {id:1, name:'小李', email:'[email protected]', sex:'男', bornDate:'1992-5-6'}, {id:1, name:'小兰', email:'[email protected]', sex:'女', bornDate:'1993-3-7'} ]; var store=new Ext.data.JsonStore({ data:data, fields:["id","name","sex","email",{name:"bornDate",type:"date",dateFormat:"Y-n-j"}] }); var colM=new Ext.grid.ColumnModel([{ header:"姓名", dataIndex:"name", sortable:true, editor:new Ext.form.TextField()}, {header:"性别", dataIndex:"sex" },
{header:"出生日期", dataIndex:"bornDate", width:120, renderer:Ext.util.Format.dateRenderer('Y年m月d日')}, {header:"电子邮件", dataIndex:"email", sortable:true, editor:new Ext.form.TextField()} ]); var grid = new Ext.grid.EditorGridPanel({ renderTo:"hello", title:"学生基本信息管理", height:200, width:600, cm:colM, store:store, autoExpandColumn:3 }); }); |
上面的程序首先定义了一个包含学生信息的对象数组,然后创建了一个JsonStore,在创建这个store的时候,指定bornDate列的类型为日期date类型,并使用dateFormat来指定日期信息的格式为"Y-n-j",Y代表年,n代表月,j代表日期。定义表格列模型的时候,对于“姓名”及“电子邮件”列我们使用editor来定义该列使用的编辑器,这里是使用Ext.form.TextField,最后使用new Ext.grid.EditorGridPanel(…)来创建一个可编辑的表格。执行上面的程序可以生成一个表格,双击表格中的“姓名”、或“电子邮件”单元格中的信息可以触发单元格的编辑,可以在单元格的文本框中直接编辑表格中的内容,修改过的单元格会有特殊的标记,如下图所示:
为了能编辑“性别”及“出生日期”列,同样只需要在定义该列的时候指定editor即可。由于出生日期是日期类型,因此我们可以使用日期编辑器来编辑,“性别”一列的数据不应该让用户直接输入,而应该是通过下拉框进行选择。日期编辑器可以直接使用Ext.form.DateField组件,下拉选择框编辑器可以使用Ext.form.ComboBox组件,下面是实现对性别及出生日期等列信息编辑的代码:
var colM=new Ext.grid.ColumnModel([{ header:"姓名", dataIndex:"name", sortable:true, editor:new Ext.form.TextField()}, {header:"性别", dataIndex:"sex", editor:new Ext.form.ComboBox({transform:"sexList", triggerAction: 'all', lazyRender:true}) }, {header:"出生日期", dataIndex:"bornDate", width:120, renderer:Ext.util.Format.dateRenderer('Y年m月d日'), editor:new Ext.form.DateField({format:'Y年m月d日'})}, {header:"电子邮件", dataIndex:"email", sortable:true, editor:new Ext.form.TextField()} ]); var grid = new Ext.grid.EditorGridPanel({ renderTo:"hello", title:"学生基本信息管理", height:200, width:600, cm:colM, store:store, autoExpandColumn:3, clicksToEdit:1 }); |
注意在定义EditorGridPanel的时候,我们增加了一个属性“clicksToEdit:1”,表示点击一次单元格即触发编辑,因为默认情况下该值为2,需要双击单元格才能编辑。为了给ComboBox中填充数据,我们使用设置了该组件的transform配置属性值为sexList,sexList是一个传统的<select>框,我们需要在html页面中直接定义,代码如下:
<select> <option>男</option> <option>女</option> </select> |
执行上面的程序,我们可以得到一个能对表格中所有数据进行编辑的表格了。点击上面的“性别”一列的单元格时,会出现一个下拉选择框,点击“出生日期”一列的单元格时,会出现一个日期数据选择框,如图xxxx所示:
(编辑性别列中的数据)
(编辑出生日期列中的数据)
那么如何保存编辑后的数据呢?答案是直接使用afteredit事件。当对一个单元格进行编辑完之后,就会触发afteredit事件,可以通过该事件处理函数来处理单元格的信息编辑。比如在http://wlr.easyjf.com这个单用户blog示例中,当我们编辑一个日志目录的时候,需要把编辑后的数据保存到服务器,代码如下:
this.grid.on("afteredit",this.afterEdit,this); … afterEdit:function(obj){ var r=obj.record; var id=r.get("id"); var name=r.get("name"); var c=this.record2obj(r); var tree=this.tree; var node=tree.getSelectionModel().getSelectedNode(); if(node && node.id!="root")c.parentId=node.id; if(id=="-1" && name!=""){ topicCategoryService.addTopicCategory(c,function(id){ if(id)r.set("id",id); if(!node)node=tree.root; node.appendChild(new Ext.tree.TreeNode({ id:id, text:c.name, leaf:true })); node.getUI().removeClass('x-tree-node-leaf'); node.getUI().addClass('x-tree-node-expanded'); node.expand(); }); } else if(name!="") { topicCategoryService.updateTopicCategory(r.get("id"),c,function(ret){ if(ret)tree.getNodeById(r.get("id")).setText(c.name); }); } } |
关于可编辑表格控件的详细说明,请参考wlr.easyjf.com中的VIP文档《ExtJS可编辑表格EditorGridPanel详解》。
在实际的应用中,表格中的数据一般都是直接存放在数据库表或服务器的文件中。因此,在使用表格控件的时候经常需要与服务器进行交互。ExtJS使用Ajax方式提供了一套与服务器交互的机制,也就是可以不用刷新页面,就可以访问服务器的程序进行数据读取或数据保存等操作。
比如前面在表格中显示xml文档中数据的例子中,就是一个非常简单的从服务器端读取数据的例子,再回顾一下代码:
var store=new Ext.data.Store({ url:"hello.xml", reader:new Ext.data.XmlReader({ record:"row"}, ["id","name","organization","homepage"]) }); |
因为Sote组件接受一个参数url,如果设置url,则ExtJS会创建一个与服务器交互的Ext.data.HttpProxy对象,该对象通过指定的Connection或Ext.Ajax.request来向服务端发送请求,从而可以读取到服务器端的数据。
经验表明,服务器端产生JSon数据是一种非常不错的选择,也就是说假如服务器的url“student.ejf?cmd=list”产生下面的JSON数据输出:
{results:[{id:1, name:'小王', email:'[email protected]', sex:'男', bornDate:'1991-4-4'}, {id:1, name:'小李', email:'[email protected]', sex:'男', bornDate:'1992-5-6'}, {id:1, name:'小兰', email:'[email protected]', sex:'女', bornDate:'1993-3-7'} ] } |
则前面显示学习信息编辑表格的store可以创建成下面的形式:
var store=new Ext.data.Store({ url:"student.ejf?cmd=list", reader:new Ext.data.JsonReader({ root:"result"}, ["id","name","organization","homepage"]) }); |
或者:
var store=new Ext.data.JsonStore({ url:"student.ejf?cmd=list", root:"result", fields:["id","name","organization","homepage"]}); |
其中root表示包含记录集数据的属性。
如果在运行程序中需要给服务器端发送数据的时候,此时可以直接使用ExtJS中提供的Ext.Ajax对象的request方法。比如下面的代码实现放服务器的student.ejf?cmd=save这个url发起一个请求,并在params中指定发送的Student对象:
var store=new Ext.data.JsonStore({ url:"student.ejf?cmd=list", root:"result", fields:["id","name","organization","homepage"]}); function sFn() { alert('保存成功'); } function fFn() { alert('保存失败'); } Ext.Ajax.request({ url: 'student.ejf?cmd=save’ success: sFn failure: fFn, params: { name: '小李',email: ' [email protected]',bornDate: ' 1992-5-6',sex: '男'} });
|
关于ExtJS中各控件与服务器端如何交互、Ext.Ajax的详细使用说明等请参考wlr.easyjf.com中的VIP文档《ExtJS中客户端控件与服务器控件交互详解》。
在前面的表格应用中,我们已经知道表格的数据是存放类型为Store的数据存储器中,通过指定表格Grid的store属性来设置表格中显示的数据,通过调用store的load或reload方法可以重新加载表格中的数据。ExtJS中用来定义控件中使用数据的API位于Ext.dd命名空间中,本章我们重点对ExtJS中的数据存储Store进行介绍。
1、Record
首先需要明确是,ExtJS中有一个名为Record的类,表格等控件中使用的数据是存放在Record对象中,一个Record可以理解为关系数据表中的一行,也可以称为记录。Record对象中即包含了记录(行中各列)的定义信息(也就是该记录包含哪些字段,每一个字段的数据类型等),同时又包含了记录具体的数据信息(也就是各个字段的值)。
我们来看直接使用Record的代码:
Ext.onReady(function(){ var MyRecord = Ext.data.Record.create([ {name: 'title'}, {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'}, {name: 'lastLoginTime', mapping: 'loginTime', type: 'date'} ]); var r=new MyRecord({ title:"日志标题", username:"easyjf", loginTimes:100, loginTime:new Date() }); alert(MyRecord.getField("username").mapping); alert(MyRecord.getField("lastLoginTime").type); alert(r.data.username); alert(r.get("loginTimes")); });
|
首先使用Record的create方法创建一个记录集MyRecord,MyRecord其实是一个类,该类包含了记录集的定义信息,可以通过MyRecord来创建包含字段值的Record对象。在上面的代码中,最后的几条语句用来输出记录集的相关信息,MyRecord.getField("username")可以得到记录中username列的字段信息,r.get("loginTimes")可以得到记录loginTimes字段的值,而r.data.username同样能得到记录集中username字段的值。
对Record有了一定的了解,那么要操作记录集中的数据就非常简单了,比如r.set(name,value)可以设置记录中某指定字段的值,r. dirty可以得到当前记录是否有字段的值被更改过等等。
Store可以理解为数据存储器,可以理解为客户端的小型数据表,提供缓存等功能。在ExtJS中,GridPanel、ComboBox、DataView等控件一般直接与Store打交道,直接通过store来获得控件中需要展现的数据等。一个Store包含多个Record,同时Store又包含了数据来源,数据解析器等相关信息,Store通过调用具体的数据解析器(DataReader)来解析指定类型或格式的数据(DataProxy),并转换成记录集的形式保存在Store中,作为其它控件的数据输入。
数据存储器由Ext.data.Store类定义,一个完整的数据存储器要知道数据源(DataProxy)及数据解析方式(DataReader)才能工作,在Ext.data.Store类中数据源由proxy配置属性定义、数据解析(读取)器由reader配置属性定义,一个较为按部就班创建Store的代码如下:
var MyRecord = Ext.data.Record.create([ {name: 'title'}, {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'}, {name: 'lastLoginTime', mapping: 'loginTime', type: 'date'} ]); var dataProxy=new Ext.data.HttpProxy({url:"link.ejf"}); var theReader=new Ext.data.JsonReader({ totalProperty: "results", root: "rows", id: "id" },MyRecord); var store=new Ext.data.Store({ proxy:dataProxy, reader:theReader }); store.load();
|
当然,这样的难免代码较多,Store中本身提供了一些快捷创建Store的方式,比如上面的示例代码中可以不用先创建一个HttpProxy,只需要在创建Store的时候指定一个url配置参数,就会自动使用HttpProxy来加载参数。比如,上面的代码可以简化成:
var MyRecord = Ext.data.Record.create([ {name: 'title'}, {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'}, {name: 'lastLoginTime', mapping: 'loginTime', type: 'date'} ]); var theReader=new Ext.data.JsonReader({ totalProperty: "results", root: "rows", id: "id" },MyRecord); var store=new Ext.data.Store({ url:"link.ejf", proxy:dataProxy, reader:theReader }); store.load();
|
虽然不再需要手动创建HttpProxy了,但是仍然需要创建DataReader等,毕竟还是复杂,ExtJS进一步把这种常用的数据存储器进行了封装,在Store类的基础上提供了SimpleStore、SimpleStore、GroupingStore等,直接使用SimpleStore,则上面的代码可以进一步简化成下面的内容:
var store=new Ext.data.JSonStore({ url:"link.ejf?cmd=list", totalProperty: "results", root: "rows", fields:['title', {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'}, {name: 'lastLoginTime', mapping: 'loginTime', type: 'date'} ] }); store.load();
|
DataReader表示数据读取器,也就是数据解析器,其负责把从服务器或者内存数组、xml文档中获得的杂乱信息转换成ExtJS中的记录集Record数据对象,并存储到Store里面的记录集数组中。
数据解析器的基类由Ext.data.DataReader定义,其它具体的数据解析器都是该类的子类,ExtJS中提供了读取二维数组、JSon数据及Xml文档的三种数据解析器,分别用于把内存中的二级数组、JSON格式的数据及XML文档信息解析成记录集。下面简单的介绍:
1)ArrayReader
Ext.data.ArrayReader-数组解析器,用于读取二维数组中的信息,并转换成记录集Record对象。首先看下面的代码:
var MyRecord = Ext.data.Record.create([ {name: 'title', mapping:1}, {name: 'username', mapping:2}, {name: 'loginTimes', type:3} ]); var myReader = new Ext.data.ArrayReader({ id: 0 }, MyRecord);
|
这里定义的myReader可以读取下面的二维数组:
[ [1, '测试', '小王',3], [2, '新年好', 'williamraym',13] ] |
2)JsonReader
Ext.data.JsonReader-Json数据解析器,用于读取JSON格式的数据信息,并转换成记录集Record对象。看下面使用JsonReader的代码:
var MyRecord = Ext.data.Record.create([ {name: 'title'}, {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'} ]); var myReader = new Ext.data.JsonReader({ totalProperty: "results", root: "rows", id: "id" }, MyRecord);
|
这里的JsonReader可以解析下面的JSON数据:
{ 'results': 2, 'rows': [ { id: 1, title: '测试', author: '小王', loginTimes: 3 }, { id: 2, title: 'Ben', author: 'williamraym', loginTimes:13} ] } |
JSonReader还有比较特殊的用法,就是可以把Store中记录集的配置信息存放直接保存在从服务器端返回的JSON数据中,比如下面的例子:
var myReader = new Ext.data.JsonReader(); |
这一个不带任何参数的myReader,可以处理从服务器端返回的下面JSON数据:
{ 'metaData': { totalProperty: 'results', root: 'rows', id: 'id', fields: [ {name: 'title'}, {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'} ] }, 'results': 2, 'rows': [ { id: 1, title: '测试', author: '小王', loginTimes: 3 }, { id: 2, title: '新年好', author: 'williamraym', loginTimes:13}] }
|
3)XmlReader
Ext.data.XmlReader-XML文档数据解析器,用于把XML文档数据转换成记录集Record对象。看下面的代码:
var MyRecord = Ext.data.Record.create([ {name: 'title'}, {name: 'username', mapping: 'author'}, {name: 'loginTimes', type: 'int'} ]); var myReader = new Ext.data.XmlReader({ totalRecords: "results", record: "rows", id: "id" }, MyRecord); |
上面的myReader能够解析下面的xml文档信息:
<topics> <results>2</results> <row> <id>1</id> <title>测试</ title > <author>小王</ author > <loginTimes>3</ loginTimes > </row> <row> <id>2</id> <title>新年好</ title > <author> williamraym </ author > <loginTimes>13</ loginTimes > </row> </topics> |
DataProxy字面解释就是数据代理,也可以理解为数据源,也即从哪儿或如何得到需要交给DataReader解析的数据。数据代理(源)基类由Ext.data.DataProxy定义,在DataProxy的基础,ExtJS提供了Ext.data.MemoryProxy、Ext.data.HttpProxy、Ext.data.ScriptTagProxy等三个分别用于从客户端内存数据、Ajax读取服务器端的数据及从跨域服务器中读取数据等三种实现。
比如像SimpleStore等存储器是直接从从客户端的内存数组中读取数据,此时就可以直接使用Ext.data.MemoryProxy,而大多数需要从服务器端加载的数据直接使用Ext.data.HttpProxy,HttpProxy直接使用Ext.Ajax加载服务器的数据,由于这种请求是不能跨域的,所以要要读取跨域服务器中的数据时就需要使用到Ext.data.ScriptTagProxy。
关于DataProxy的更多内容,请参考http://wlr.easyjf.com的VIP文档中的《ExtJS数据存储Store详解》中的相关内容。
在实际应用中,除了基本的从内存中读取javascript数组对象,从服务器读取JSON数组,从服务器取xml文档等形式的数据外,有时候还需要使用其它的数据读取方式。比如熟悉EasyJWeb中远程Web脚本调用引擎或DWR等框架的都知道,通过这些框架我们可以直接在客户端使用javascript调用服务器端业务组件的方法,并把服务器端的结果返回到客户端,客户端得到的是一个javascript对象或数组。由于这种方式的调用是异步的,因此,相对来说有点特殊,即不能直接使用Ext.data.MemoryProxy,也不能直接使用Ext.data.HttpProxy,当然更不需要Ext.data.ScriptTagProxy,这时候就需要创建自定义的DataProxy及Store,然后使用这个自定义的Store来实现这种基于远程脚本调用引擎的框架得到数据。
在应用程序中,我们经常会涉及到要显示或处理树状结构的对象信息,比如部门信息、地区信息,或者是树状的菜单信息,操作系统中的文件夹信息等。
对于传统的html页面来说,要自己实现显示树比较困难,需要写很多的javascript,特别是对于基于Ajax异步加载的树来说,不但涉及到Ajax数据加载及处理技术,还需要考虑跨浏览器支持等,处理起来非常麻烦。ExtJS中提供了现存的树控件,通过这些控件可以在B/S应用中快速开发出包含树结构信息的应用。
TreePanel基本使用
树控件由Ext.tree.TreePanel类定义,控件的名称为treepanel,TreePanel类继承自Panel面板。在ExtJS中使用树控件其实非常简单,我们先来看下面的代码
Ext.onReady(function(){ var root=new Ext.tree.TreeNode({ id:"root", text:"树的根"}); root.appendChild(new Ext.tree.TreeNode({ id:"c1", text:"子节点" })); var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:root, width:100 }); }); |
代码的第一句使用new Ext.tree.TreeNode类来创建一个树节点,第二句使用树节点的root的appendChild方法来往该节点中加入一个子节点,最后直接使用new Ext.tree.TreePanel来创建一个树面板,要树面板的初始化参数中指定树的root属性值为前面创建的root节点,也就是树根节点。上面的程序执行效果如下图所示:
树的节点信息。ExtJS的树控件提供了对这种功能的支持,你只需要在创建树控件的时候,通过给树指定一个节点加载器,可以用来从服务器端动态加载树的节点信息。我们来看下面的代码:
var root=new Ext.tree.AsyncTreeNode({ id:"root", text:"树的根"}); var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:root, loader: new Ext.tree.TreeLoader({url:"treedata.js"}), width:100 }); |
treedata.js这个url返回的内容如下:
[{ id: 1, text: '子节点1', leaf: true },{ id: 2, text: '儿子节点2', children: [{ id: 3, text: '孙子节点', leaf: true }] }] |
执行上面的程序,可以得到一棵异步加载子节点的树,点击“根节点”会到服务器端加载子节点,如下图所示:
当然上面的程序是一次性加载完了树的所有节点信息,我们也可以实现让每一个节点都支持动态加载的树,只需要在通过服务器请求数据的时候,每次服务器端返回的数据只只包含子节点,而不用把孙子节点也返回即可。比如把上面treedata.js中的内容改为下面的内容:
[{ id: 1, text: '子节点', leaf: false }] |
也就是节点树中只包含一个子节点,而该子节点通过指定leaf值为false (默认情况该值为false),表示该节点不是一个叶子节点,其下面还有指节点。再执行前面的程序,不断点击“子节点”可以得到如下图所示的效果:
当然这是一个无限循环的树,在实际应用中我们服务器端返回的数据是程序动态产生的,因此不可能每一次都产生leaf为false的节点,如果是叶子节点的时候,则需要把返回的JOSN对象中的leaf设置为true。如下所示:
[{ id: 1, text: '子节点', leaf:true }] |
事件处理
当然,仅仅能显示一棵树还不够,我们一般还需要在用户点击树节点的时候执行相应的东西,比如打开某一个连接,执行某一个函数等,这就需要使用到事件处理。比如下面的代码:
Ext.onReady(function(){ var root=new Ext.tree.TreeNode({ id:"root", text:"树的根"}); var c1=new Ext.tree.TreeNode({ id:"c1", text:"子节点" }); root.appendChild(c1); var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:root, width:100 }); tree.on("click",function(node,event){ alert("您点击了"+node.text); } ); c1.on("click",function(node,event){ alert("您点击了"+node.text); } );
}); |
执行上面的程序,当用户点击树控件中的任意节点时,都会弹出一个提示信息框,当用户点击c1这个子节点时,会弹出两次提示信息框。因为我们除了指定tree的click事件响应函数以外,另外又给node节点指定单独的事件响应函数。
当然,如果只是要实现当点击树节点时跳到某一个指定url的功能则非常简单。看下面的代码:
Ext.onReady(function(){ var root=new Ext.tree.TreeNode({ id:"root", href:"http://www.easyjf.com", hrefTarget:"_blank", text:"树的根"}); var c1=new Ext.tree.TreeNode({ id:"c1", href:"http://wlr.easyjf.com", hrefTarget:"_blank", text:"子节点" }); root.appendChild(c1); var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:root, width:100 });
});
|
执行程序,点击树节点,将会在浏览新窗口中打开节点中href指定的链接。
在ExtJS中,不管是叶子节点还是非叶子节点,都统一用TreeNode表表示树的节点。在ExtJS中,有两种类型的树节点。一种节点是普通的简单树节点,由Ext.tree.TreeNode定义,另外一种是需要异步加载子节点信息的树节点,该类由Ext.tree.AsyncTreeNode定义。 看下面的代码:
Ext.onReady(function(){ var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:new Ext.tree.AsyncTreeNode({ text:"根节点" }), width:100 }); }); |
执行程序,点击树中的“根节点”则会一直发现树会尝试加载这个节点的子节点,由这里没有指定树的加载器,所以“根节点”会变成一直处于加载的状态。如下图所示:
对于普通的TreeNode来说,可以通过调用节点的appendChild、removeChild等方法来往该节点中加入子节点或删除子节点等操作。
TreeNode与AsyncTreeNode可以同时使用,比如下面的代码:
Ext.onReady(function(){
var root=new Ext.tree.TreeNode({ id:"root", text:"树的根" }); var c1=new Ext.tree.TreeNode({ text:"子节点1" }) var c2=new Ext.tree.AsyncTreeNode({ text:"子节点2" }); root.appendChild(c1); root.appendChild(c2); var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:root, width:300, loader:new Ext.tree.TreeLoader({ applyLoader:false, url:"treedata.js" }) });
});
|
treedata.js中的内容仍然是:
[{ id: 1, text: '子节点' }]
|
执行上面的程序可以得到一棵如下图所示的树:
另外要在树以外的程序中得到当前选择的节点,可以通过TreePanel的getSelectionModel方法来获得,该方法默认返回的是Ext.tree.DefaultSelectionModel对象,DefaultSelectionModel的getSelectedNode方法返回当前选择的树节点。比如要得到树tree中中当前选择节点,代码如下:
tree.getSelectionModel().getSelectedNode() |
对于ExtJS中的树来说,树加载器TreeLoader是一个比较关键的部件,树加载器由Ext.tree.TreeLoader类定义,只有AsyncTreeNode才会使用TreeLoader。看下面的代码:
Ext.onReady(function(){ var loader=new Ext.tree.TreeLoader({ url:"treedata.js" }); var root=new Ext.tree.AsyncTreeNode({ id:"root", text:"根节点", loader:loader}); var tree=new Ext.tree.TreePanel({ renderTo:"hello", root:root, width:100 });
}); |
首先我们使用Ext.tree.TreeLoader来初始化了一个TreeLoader对象,构造函数中的配置参数url表示获得树节点信息的url。然后在初始化根节点的时候我们使用的是AsyncTreeNode,在该节点中指定该节点的laoder为前面定义的loader。执行这段程序,在点击“根节点”时,会从服务器端指定root节点的子节点信息。
TreeLoader严格来说是针对树的节点来定义的,可以给树中的每一个节点定义不同的TreeLoader,默认情况下,如果一个AsyncTreeNode节点在准备加载子节点的时候,如果该节点上没有定义loader,则会使用TreePanel中定义的loader作为加载器。因此,我们可以直接在TreePanel上面指定loader属性,这样就不需要给每一个节点指定具体的TreeLoader了。因此,上面的代码可以改成如下所示的内容:
在ExtJS自己的TreeLoader中,当要实现从远程服务器端异步加载树节点信息的时候,都是通过请求服务器上的某一个URL来进行的,这个URL返回下面的信息:
[{ id: 1, text: 'A leaf Node', leaf: true },{ id: 2, text: 'A folder Node', children: [{ id: 3, text: 'A child Node', leaf: true }] }] |
假如我们是直接通过类似DWR或EasyJWeb的远程脚本引擎在客户端直接调用服务器的业务方法,直接跳过了WEB(不需要Struts、JSP或其它Web层的代码)这一层,这时我们没有URL,这时该怎么办呢?这就需要使用到自定义的TreeLoader,下面我们通过一个实例来做简单的讲解。
看服务器端的ITopicCategoryService
public interface ITopicCategoryService { List loadCategory(Long id); } |
loadCategory方法返回一个类型为Node的列表,也就是返回指定id的下级分类信节点信息,Node对应树节点的信息,代码如下:
public class Node { private TopicCategory category; Node(TopicCategory category) { this.category = category; } public String getId() { return category.getId().toString(); } public boolean getLeaf() { return category.getChildren().size() < 1; } public String getText() { return category.getName(); } public String getQtip() { return category.getName(); } } |
Node在这里相当于一个简单适配器,其实就是把数据库中的日志分类实体适配成包树节点对象。
把ITopicCategoryService发布成可供客户端远程调用,使用EasyJWeb的话引如下面三个js:
<script type="text/javascript" src="/ejf/easyajax/prototype.js"></script> <script type="text/javascript" src="/ejf/easyajax/engine.js"></script> <script type="text/javascript" src="/ejf/easyajax/topicCategoryService.js"></script> |
使用DWR的话引入下面的两个js:
<script type="text/javascript" src="/dwr/dwr/engine.js "></script> <script type="text/javascript" src="/dwr/dwr/util.js "></script> <script type="text/javascript" src="/dwr/dwr/interface/ topicCategoryService.js "></script> |
这样我们可以在页使用下面的javascrpt来从服务器端获得某一个节点的子节点信息,代码如下:
function test() { topicCategoryService.loadCategory(1,function(ret) { alert("一共有"+ret.length+"个子节点"); } } |
如何让ExtJS的树面板能通过这个远程web脚本方法topicCategoryService.loadCategory来加载异步加载树节点信息呢?其实很简单,跟一般的使用没什么两样,树面板TreePanel的代码如下:
var tree = new Ext.tree.TreePanel({ autoScroll:true, animate:true, width:'100px', height:'300px', enableDD:true, containerScroll: true, loader: loader root: new Ext.tree.AsyncTreeNode({ text: '日志分类', id:'root' }); });
|
然后区别是在loader部分,使用远程Web调用来加载树节点的loader,代码如下:
var loader=new WebInvokeTreeLoader({ fn:topicCategoryService.loadCategory }); loader.on("beforeload",function(l,node){ l.args[0]=(node.id!='root'?node.id:"-1"); }); |
再回顾一下传统的直接通过url加载树节点的TreeLoader代码,如下所示:
var loader=new Ext.tree.TreeLoader({ url:'/topicCategory.ejf?cmd=getCategory&pageSize=-1&treeData=true' }); loader.on("beforeloader",function(loader,node){ loader.baseParams.id=(node.id!='root'?node.id:""); }); |