开始组件学习之路
按钮与日期选择器
按钮控件用Ext.Button来表示,有三种类型:提交(submit)、重置(reset)和普通按钮(button).对于按钮来说,最重要的就是触发单击事件了,有点像古代青楼里的妓女,习惯了被人指点,可令的人
按照OOP的习惯,我们会这样定义按钮:
var btn = new Ext.Button();
btn.setText("确定"); //按钮上的文字
btn.type="submit"; //按钮类型
btn.setHandler(function() //按钮被单击后执行本方法
{
Ext.Msg.alert("按钮","按钮测试,效果真好");
});
btn.render(Ext.getBody()); //将按钮放在指定位置显示
太完美了,完全吻合面向对象编程的思想,创建,设置属性、响应事情,如行云流水,一气呵成。但是,等下,Extjs更加推崇下面的做法,完全通过配置来实现。
var btn = new Ext.Button({
renderTo:Ext.getBody(),
text:"确定",
type:"submit",
handler:function()
{
Ext.Msg.alert("按钮","按钮测试,效果真好");
}
});
构建Button时,传递一个json对象,renderTo是一个经常被使用的属性,用于指定当前组件渲染在什么位置,一般作为指定对象的子节点,本例中的Ext.getBody()方法返回document.body对象,所以,btn按钮将是body的子节点,对应的方法时render().而handler属性则对应setHandler()方法,用来定义按钮被单击之后的处理函数。
下面的另外一些配置能增强按钮的效果:
pressed:true 使按钮处于按下的状态
disabled:true 使按钮处于禁用状态
minWidth:100 设置按钮的最小宽度
icon:"../imgs/133.gif" 设置按钮的背景图片,属性值是图片名称
iconCls:"bk" 同上,属性值是类选择器名称
三、日期选择器Ext.DatePicker
平时用的最多的就是获取用户选择的日期,这是通过该类的getValue()方法来的到。其他很多配置选项主要是用来做个性化和本地化,只和表现有关,和功能无关
var dp = new Ext.DatePicker({
renderTo:Ext.getBody(),
minDate:Date.parseDate("2009-1-1","Y-m-d"),
maxDate:Date.parseDate("2009-12-30","Y-m-d"),
value:Date.parseDate("2009-12-30","Y-m-d"),
handler:function(){
Ext.MessageBox.alert("日期",Ext.util.Format.date(this.getValue(),'Y-m-d'));
}
});
前面讲过的配置选项就不重复了,以后也不会重复。minDate和maxDate是配置可选日期的最小值和最大值,value是日期控件显示后的初始值。通过getValue()方法得到用户选择的日期,再通过Ext.util.Format.date格式化。
数据与ComboBox
数据在这里是动词
我说的"数据",其实就是如何获取数据。这本该是一个动词
在Extjs中,对于选择控件来说,用Ext.data命名空间中的类来组织和管理数据项。
Ext.data最主要的功能是获取和组织数据结构,并和特定控件联系起来,于是,Ext.data成了数据的来源,控件负责显示数据。他们分工是如此明确,但是又如此密不可分。以至于拆开任何一方都天理不容。
我们现在的迫切任务是理解Ext.data里的类,在该命名空间中,有三个东东非常厉害也非常难懂,分别是DataProxy、DataReader和Store。理解这三个类成了灵活应用Extjs的关键,那么,我该怎么解释才能达到最理想的效果?
DataProxy:获取想要的数据,通过他能得到来自不同地方的数据,如数组、远程服务器,并组织成不同的格式。
DataReader:定义数据项的逻辑结构,一个数据项有很多列,每列的名称是什么,分别是什么数据类型,都由该类来定义。另外,还负责对不同格式的数据进行读取和解析。
Store:存储器,用于整合Proxy和Reader,控件索取数据时通常和他打交道。
Ext.data.DataProxy类
proxy是代理的意思,很多时候,聪明的架构师和设计者为了屏蔽底层的差异,给用户提供一个统一的访问接口,会设计一个名为"proxy"的类。这种设计哲学在架构中普通应用,并且解决了很多实际问题,代码也变得更加优雅,并有效降低代码侵入。
Ext.data.DataProxy是获取数据的代理,数据可能来自于内存,可能来自于同一域的远程服务器数据,更有可能来自于不同域的远程服务器数据。
但是,在实际应用中,我们不会直接使用Ext.data.DataProxy,而是使用他的子类:MemoryProxy、HttpProxy和ScriptTagProxy,他们的作用分别是:
MermoryProxy:获取来自内存的数据,可以是数组、json或者xml
HttpProxy:使用HTTP协议通过ajax从远程服务器获取数据的代理,需要指定url
ScriptTagProxy:功能和HttpProxy一样,但支持跨域获取数据,只是实现时有点偷鸡摸狗
var cities = [
[1, "长沙市"],
[2, "株洲市"],
[3, "湘潭市"],
[4, "邵阳市"]
];
在二维数组中,每一个城市保存了两个值:值以表示城市编号,作为实际值,值二表示城市名称,作为显示值。然后我们将data构建出一个MemoryProxy对象;
var proxy = new Ext.data.MemoryProxy(data);
三、Ext.data.DataReader类
DataReader从来不是单独行动的,他没有太多的自主权,总是看DataProxy行事。总体来说,DataReader用来定义数据项(行)的逻辑结构,主要信息有:列的逻辑名称(name)、列的数据类型(type)、列与数据源的索引映射(mapping)等,另外,还包含一些元数据,如分页信息。
实际上,每一个数据项都是一个Ext.data.Record(记录)对象,而数据项的列信息则是通过Ext.data.Record来定义的。Ext.data.Record并没有固定的结构,他保存的是一个json对象数组,数组的元素个数由列的数量来决定。在上面的例子中,城市包含ID和名称,所以,必须在数组中定义两个元素,基本就是这个样子:
var City = Ext.data.Record.create([
{name:"cid",type:"int" mapping:0},
{name:"cname",type:"string",mapping:1}
]);
我们定义了一个City的结构,通过Ext.data.Record.create创建,参数是一个json对象数组,name和type分别表示每一列的名称和数据类型,mapping是列值和数组元素的映射关系。
Record创建好后,必须和DataReader关联,DataReader也同样有三个子类:Ext.data.ArrayReader、Ext.data.JsonReader、Ext.data.XmlReader.我之前说过DataReader从来不单独行动,使用哪一个子类主要取决于DataProxy中封装的数据类型,如果是数组,则使用Ext.data.ArrayReader;如果是json,则使用Ext.data.JsonReader;如果是xml,则使用Ext.data.XmlReader。在本教程中,我打算将xml封杀。我不喜欢这个东西.相对而言,我更热衷于轻量级的jsonObject
在本例中,我们处理的数据类型是数组,所以自然要使用ArrayReader
var reader = new Ext.data.ArrayReader({},City);
构造ArrayReader对象时,构造函数的第一个参数就是元数据,第二个参数则是Record,也可以一步到位:
var reader = new Ext.data.ArrayReader({},
[
{name:"cid",type:"int",mapping:0},
{name:"cname",type:"string",mapping:1}
]);
四、Ext.data.Store类
这个类相对简单,不需要面对数据和结构,只是把DataProxy和DataReader整合在一起,这样一来,数据有了,结构有了,俨然就是一张数据表,想一想数据库中的物理表是不是就是这样的呢?嗯,非常像。
典型的写法像这样:
var store = new Ext.data.Sotre({
proxy:proxy,
reader:reader
});
到了这一步,别以为大功告成了,其实这时候数据并没有加载到Store中,默认情况下,Store采取延时加载,必须显式调用load()方法,当然,我们也可以采取即时加载策略,按如下配置即可
var store = new Ext.data.Sotre({
proxy:proxy,
reader:reader,
autoLoad:true//即时加载数据
});
这个类之间的关系
Ext.data.Store的主要目的是在内存中建立一张数据表,填充到组件中,而数据列主要是由Record,并被DataReader重新封装,数据是由DataProxy定义,封装各种不同格式的数据。
五、下拉列表框
下拉列表框被定义成ComboBox类,位于Ext.form命名空间,和Button不同,他是一个表单域组件,常用语表单中。
一个比较典型的ComboBox的定义方法
var combobox = new Ext.form.ComboBox({
renderTo:Ext.getBody(),
triggerAction:"all",
store:store,
displayField:"cid",
mode:"local",
emptyText:"请选择湖南城市"
});
triggerAction:是否开启自动查询的功能,为all表示不开启,为query表示开启,默认为query;
stroe:不用解释了,和前面的Ext.data.Store对象挂钩,定义数据源;
displayField:关联Record的某一个逻辑列名作为显示值,本例为城市名称;
valueField:关联Record的某一个逻辑列名作为实际值,本例为城市ID;
mode:可选值有local和remote,如果数据来自本地,用local,如果数据来自远程服务器,必须用remote,默认为remote;
emptyText:没有选择任何选项的情况文本框中的默认文字。
六、得到下拉列表框的值
ComboBox定义了两个方法,其中,getValue()用于返回实际值,getRawValue()用于返回显示值,如果让我们自己来,估计永远永远想不到显示值和getRawValue()方法有联系
我在页面上放置了一个按钮,点击按钮显示下拉列表框的值:
var btn = new Ext.Button({
text:"列表框的值",
renderTo:Ext.getBody(),
handler:function()
{
Ext.Msg.alert("值","实际值:"+combobox.getValue()+";显示值:"+combobox.getRawValue());
}
});
八、小结
本章真的很重要,一定要想方设法看明白,然偶一字一字将例子敲出来,再理解其中的意思。以后,只要碰到数据显示的问题,我就会把这章的东西搬出来,稍微改动应用到另一个知识点中去。
而且,ComboBox的东西也不止这些,应该说,ComboBox算是一个比较复杂的组件,我们现在接触的只是最基本的用法,在后面我会继续和您一起深入探讨。
Ajax与ComboBox
一、Ajax
Extjs对ajax的支持相当全面而完善,只要涉及到与服务器的交互,都是通过ajax来完成的。如果没有ajax,Extjs也会失去他现有的光彩。当然,在提交表单的时候,Extjs并不强迫我们使用ajax,但是,使用ajax仍然是一个不错的选择
Extjs对ajax的支持主要体现在两个方面,其一,使用专门的类封装ajax底层,提供一个简单的接口供用户调用,能大大简化ajax技术的实现;第二种方法更直接,很多组件本身就内置对ajax的支持,只要设置几个配置选项就万事大吉,当然,HttpProxy功不可抹。
我在本章会中点讲述ComboBox如何通过ajax从服务器获取数据并初始化选项,这是一个激动人心的功能,前提是您应该先掌握第十章的内容
二、Ext.Ajax类
具体来说,Ext.Ajax并不是一个类,而是一个对象,所以,我们并不需要实例化,Ext.Ajax是Ext.data.Connection类型的对象
Ext.Ajax有一个非常重要的方法:request(),该方法是我们实现ajax的基础,他的基本使用方法如下:
Ext.Ajax.request({
url:"../TimeServlet",
success:function(response,config)
{
alert(config.url+","+config.method);
Ext.MessageBox.alert("result",response.responseText);
},
failure:function()
{
Ext.MessageBox("result","请求失败");
}.
method:"post",
params:{name:"朱国民"}
});
TimeServlet.java
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println(request.getParameter("name")+",服务器事件:"+new Date().toLocalString()+",请校对!");
out.flush();
out.close();
这几个配置选项很容易理解,url是接受请求的组件,此处为Servlet,请求将发送到该组件进行处理;method是请求的类型,不是get就是post;params则是请求参数,是一个json对象,可以传递更多的参数;而是success和failure是回调函数,如果请求成功,自动调用success()方法,失败则调用failure()方法。
在success回调方法中,有两个参数response和config,其中,从服务器返回的数据保存在response.responseText属性中,而config则保存Ext.Ajax.request()方法的配置选项信息。
建议大家使用post发送请求,中文更容易处理,如果使用get可能会面临更多的问题
接下来演示服务器返回json对象的情形:
Ext.Ajax.request({
url:"../TimeServlet",
success:function(response.config)
{
alert(config.url+","+config.method);
var json = Ext.util.JSON.decode(response.responseText);
Ext.MessageBox.alert("result",json.authot+","+json.time);
},
faliure:function()
{
Ext.MessageBox.alert("result","请求失败");
},
method:"post",
params:{name:"朱国民"}
});
TimeServlet.java
String name = request.getParameter("name");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWritet();
out.println("{'author: '+name+',time:'+new Date().toLocaleString()}");
out.flush();
out.close();
由Ext.util.JSON.decode 负责转成json对象。
三、Ajax文件上传
ajax文件上传一般遵循下面三个步骤:
1、创建文件上传表单;
2、调用Ext.Ajax.request()方法实现文件上传:
3、定义文件上传处理器,并结合开源文件上传组件(如cos),将数据流转换成文件和参数。
但在开发的时候,上面的顺序倒过来的。我打算结合代码来说明整个实现过程
第一步:定义Web组件,完成文件上传的请求,当然,在上传文件的时候,也会有其他的数据,我们照单全收。要注意的是,Extjs不负责将流数据保存为物理文件,我们得自己写代码解析。为了简化代码,使用开原文件上传组件值的推荐。
request,
"c"\\",//文件上传后保存的位置
10*1024*1024,//允许的最大文件大小
"utf-8'//编码
第二部:设计上传界面
<form id="upform">
请选择文件:<br><input type="file" name="f"><br>
文件描述:<br><textarea rows="5" cols="70" name="desc"></textarea><br>
<input type="button" id="btn" value="上传">
</form>
这个表单貌似精简了许多,没有为form指定method和action,更没有指定enctype,如果您细心点,肯定会在doFormUpload方法中发现玄机,这些属性通通在代码中设定了,我们单独提取出来看个究竟:
var form = Ext.getDom(o.form);//获取表单对象
form.target = id;//这句话很关键,是连接表单和iframe的桥梁
form.method = "POST"//文件上传必须设置成post请求
form.enctype = from.encoding = 'multipart/formdata';//这也是文件上传必须设置的
if(url)
{
form.action = url;//表单提交的地址
}
第三部:这一步当然是关键,他衔接了第一步的Web组件和第二部的界面。从界面获取数据并提交到服务器,这个操作由Ext.Ajax.request()方法完成。
Ext.get("btn").on("click",function(){
Ext.Ajax.request({
url:"../FileUpServlet",
isUpload:true,
form:"upform",
success:function(response,config)
{
Ext.MessageBox.alert("fileup","文件上传成功");
}
});
});
isUpload属性表明这是一个文件上传的请求,form是表单标签的id,请求成功调用success方法
四、你来自远方
ComboBox的选项数据可以来自于本地(local),同样也可以从服务器获取数据并填充至选项中,这一切当然归功于ajax,设计得如此简洁和巧妙,无任何框架能出Extjs其右
问题又要回到第十章,我们使用Store来创建一个类似于数据表的结构,DataProxy负责取到数据,DataReader负责定义记录结构,因为要远程获取数据,所以应该使用HttpProxy,假设我们从服务器返回的是json格式的数据,用于保存城市信息,因此必须使用JsonReader来解析,于是就有了下面的代码:
var proxy = new Ext.data.HttpProxy({url:"../CityJsonServlet"});
var city = Ext.data.Record.create([
{name:"cid",type:"int",mapping:"cid"},
{name:"cname",type:"string",mapping:"cname"}
]);
var reader = new Ext.data.JsonReader({},City);
var store = new Ext.data.Store({
proxy : proxy,
reader: reader,
autoLoad:true
})
CityJsonServlet.java
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
String json="[{cid:1,cname:'长沙市'},{cid:2,cname:'株洲市'},{cid:3,cname:'湘潭市'},{cid:4,cname:'邵阳市'},{cid:5,cname:'娄底市'}]";
out.println(json);
out.flush();
out.close();
在City结构中,mapping不再是索引,而是json对象的属性名,在逻辑列名与json之间建立映射。最后,将数据与结构整合到Stroe。
城下的工作就是将数据编程ComboBox的数据项了。大家可以比较一下下面的代码与前面学习过的代码之间的区别
var combo = new Ext.form.ComboBox({
store : store,
emptyText:"请选择城市",
mode:"remote",
triggerAction:"all",
displayField:"cname",
valueField:"cid",
renderTo:"cid",
renderTo:Ext.getBody()
});
这些配置选项是那么熟悉。现在,让我们擦亮眼睛找出前后的差别:mode的值由原来的local变成了remote:没有autoLoad选项,ComboBox自动加载数据。太强了,聪明得让我吃惊
。
不管数据来自哪里,ComboBox的定义都没有太大的出入,最大的不同就是mode配置项的区别,如果数据来自内存,设置成local,如果没有数据来自远程服务器,设置成remote,记住啊,默认是remote。
如果希望关闭ComboBox的编辑功能,设置readOnly:true即可。
第十二章:分页与ComboBox
一、关于分页
分页是查询最常见的操作之一,比较普遍的作法是要多少数据从数据库中找多少数据。Extjs对分页的支持十分强大,为了防止刷新,都采用ajax实现,服务器负责将数据从数据库中取出,传到客户端,Extjs再根据服务器返回的信息自动分页。
前面说过,Extjs支持三种格式的数据:数组、json、xml,后两者支持分页查询,而使用json是大家喜欢和熟悉的作法。
我们使用ComboBox来演示分页的例子,可以说是前任古人后无来者,哈,其实都一样,后面讲使用GridPanel分页显示数据大同小异。这一章懂了,我就不相信后面的你不懂---简直是不可能的事
Extjs设计得分页导航条很漂亮,功能一应俱全,更有意思的是,如果您的要求超高,还可以再分页导航条上放置自己的控件,这是后话。
二、从Servlet获取当前页数据
为了能够让ComboBox认识Servlet返回的数据,必须按照ComboBox规定的格式来定义json对象,就相当于二者之间达成的协议,只有遵循共同的标准大家才能互通互话。返回数据的格式应该像下面这样定义:
{totalProperty:17,root:[{did:0,dname:'部门0'},{did:1,dname:'部门1'},{did:2,dname:'部门2'},{did:3,dname:'部门3'},{did:4,dname:'部门4'},{did:5,dname:'部门5'}]}
totalProperty是分页中必须要用的总记录数,如果提供一个页大小就好了,这样就能通过"总记录树%页大小==0?总记录数/页大小:总记录数/页大小+1"得到总页数,如此一来,天兵天将全齐了,分页也就应声而出。
root是一个json对象的数组,包含了详细的数据,root中元素的个数由页大小决定。如果写简单点就是这样啦:
{totalProperty:17,root[{},{},{},{}]}
另外,既然是分页,提供分页参数是不可避免的,ComboBox会自动提供两个参数:start和limit,start是从第几条开始,limit是取出多少条。如果页大小是5,第一页的参数值应该是start = 0,limit= 5;第二页的参数值应该是start=5,limit=5;第三页的参数值应该是start=10,limit=5;依此类推。事实上,这只是我帮助大家理解才讲的如此啰嗦,ComboBox已经完成了绝大部分工作,我们只要指定页大小(pageSize)就可以了。但是,在Servlet中,我们必须得到start和limit的值,不然怎么分页?
首先我们来看看ComboBoxServlet的servlet类
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//起始索引
int start = Integer.parseInt(request.getParameter("start"));
//页大小
int limit = Integer.parseInt(request.getParameter("limit"));
System.out.println(start+","+limit);
//总记录条数,如果是数据库则要通过count计算出来
int totalProperty = 17;
String fmt = "{did:%d,dname:'%s'}";
StringBuffer s = new StringBuffer("{totalProperty:")
s.append(totalProperty).append(",root:[");
int end =start+limit;
//因为不是查找数据库,所以需要多加一个判断
if(end>totalProperty) end = totalProperty;//如果是数据库,本行要删除
for(int i=start;i<end;i++)
{
s.append(Stirng.format(fmt,i,"部门"+i));
if(i<end-1)
{
s.append(",");//各json对象用","隔开,最后一个不要
}
}
s.append("]}");
out.println(s.toString());
out.flush();
out.close();
三、创建ComboBox
如果再重复讲解ComboBox的用法,相信您会睡着去吧,为了保持您一如既往的激情,我决定不做傻事。我把代码贴出来,不同点做个标记,再详细向您解释。
Ext.onReady(functiuon(){
var proxy = new Ext.data.HttpProxy({url:"../ComboBoxServlet"});
var City = Ext.data.Record.create([
{name:"did",type:"int",mapping:"did"},
{name:"dname",type:"String",mapping:"dname"}
]);
var reader=new Ext.data.JsonReader({
totalProperty:"totalProperty",//总记录数
root:"root"//所有的数据(json对象数组)
},City);
var store = new Ext.data.Store({
proxy:proxy,
reader:reader,
autoLoad:true
});
var combo=new Ext.form.ComboBox({
store:store,
emptyText:"请选择部门",
mode:"remote",
pageSize:5,
triggerAction:"all",
displayField:"dname",
valueField:"did",
renderTo:Ext.getBody(),
readOnly:true,
listWidth:300
});
})
粗斜体部分是ComboBox分页功能额外加的,构造JsonReader对象的第一个参数以前都为空,现在我放了一个json对象,明眼人一看就知道了,totalProperty和root两个属性与Servlet返回的json对象的两个属性一一对应