`
kidiaoer
  • 浏览: 805491 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

深入浅出Ext JS:一个完整的EXT应用

阅读更多
在本文中,我们将综合运用前面所学的知识,开发一个简单的学生信息管理系统(如图12-1所示)。该系统的主要功能包括:显示学生信息、添加学生信息、修改学生信息,以及删除学生信息。这些功能的实现非常简单,我们在这里将演示如何在EXT中实现这些常用功能。
  
  图12-1 学生信息管理系统界面
  12.1 确定整体布局
  在我们动手实现这些功能操作之前,首先应该确定页面的整体布局。在这里,我们用BorderLayout把页面分隔成4个部分:最上方显示系统的名称,最下方显示版权信息,中间部分左侧显示学生信息列表,中间部分右侧中的表单用来添加或修改学生信息。
  实现上述布局效果的代码如代码清单12-1所示。
  代码清单12-1 实现学生信息管理系统的布局
  Ext.onReady(function() {
  // layout start
  var viewport = new Ext.Viewport({
  layout: 'border',
  items: [{
  region: 'north',
  html: 'head'
  }, {
  region: 'center',
  html: 'grid'
  }, {
  region: 'east',
  html: 'form'
  }, {
  region: 'south',
  html: 'foot'
  }]
  });
  // layout end
  });
  我们使用Ext.Viewport为整个页面进行了布局设置,其中每个部分都直接用HTML参数做了标记,现在我们就可以看到这个布局的显示效果如图12-2所示。
  
  图12-2 学生信息管理系统布局效果图
  如图12-2所示,现在我们看到的只是一个空白的框架,每一部分的具体内容都需要我们进一步去实现。无论该学生信息管理系统的功能多么复杂,都不会超出目前框架中的布局设计。接下来,让我们逐一实现各个部分的功能。
  12.2 使用HTML和CSS设置静态信息
  用于显示标题和版权信息的文字都是静态的,我们可以直接调用HTML中设置好的内容。在Ext.Panel中添加静态信息的方式有如下2种。
  q HTML参数:它让我们可以直接在JavaScript脚本中写上静态信息的内容。
  q contentEl参数:它引用页面中某一个div的id,在显示Panel时将这个div中的内容显示在对应的布局域中。
  相对而言,HTML参数更适合简单的内容。如果需要引入复杂格式的静态信息,还是应该使用contentEl参数。在这个示例中,我们就选择了contentEl参数为头部和底部制定静态信息部分的内容,实现方法如代码清单12-2所示。
  代码清单12-2 在布局中设置静态内容
  var viewport = new Ext.Viewport({
  layout: 'border',
  items: [{
  region: 'north',
  contentEl: 'head'
  }, {
  region: 'center',
  html: 'grid'
  }, {
  region: 'east',
  html: 'form'
  }, {
  region: 'south',
  contentEl: 'foot'
  }]
  });
  在上面的代码中,显示在上方的north部分引用的contentEl是'head',它在页面中的内容如下所示。
  
学生信息管理
  显示在下方的south部分引用的contentEl是'foot',它在页面中的内容如下所示。  
    <div id="foot" style="text-align:right;"> - &copy; 2008 <a
href="http://www.family168.com" target="_blank">www.family168.com</a> - </div>
  因为这两部分的标签内容都会在EXT进行页面布局时重新提取和设置,所以我们在开始时不用考虑把这两个div写到页面的什么位置,只要把它们写到页面里,EXT就会自动进行布局,把它们放到预设的位置。
  设置过contentEl后,整个页面就变成了图12-3的样子。我们这里演示的效果比较简单,结合使用了HTML标签和CSS,为标题设置字体(加粗)和字号(变大),版权信息则是右对齐并设置了超链接。在实际项目中,可以把美工设计出来的页面标签直接复制到对应的div下,刷新页面后就会显示在对应的位置。
  
  图12-3 上下部分加入静态信息的效果
  12.3 对学生信息进行数据建模
  接下来我们要实现对学生信息进行实际操作的功能。我们用Java编写后台脚本,用Hsqldb数据库作为保存数据的介质。首先要在数据库中创建学生信息数据表,如代码清单12-3所示。
  代码清单12-3 创建学生信息表
  create table student(
  id bigint, -- 主键
  code varchar(50), -- 学号
  name varchar(50), -- 姓名
  sex integer, -- 性别
  age integer, -- 年龄
  political varchar(50), -- 政治面貌
  origin varchar(50), -- 籍贯
  professional varchar(50) -- 所属系
  );
  alter table student
  add constraint pk_student primary key (id);
  alter table student
  alter column id bigint generated by default as identity (start with 1,increment by 1);
  这张student表中包含8个字段,分别对应学生的各种详细信息。最后两行不是标准的ANSI SQL语句,这是Hsqldb中的特有功能,表示把id字段设置成student表的唯一主键,并由数据库服务器控制自动增长。
  我们在示例中将它作为嵌入式内存数据库,只要把hsqldb-1.8.0_7.jar放到WEB-INF/lib目录下就可以使用了,不必再去安装和配置外部服务器。Hsqldb数据库会在JDBC第一次连接时启动,从WEB-INF/classes目录下的test.scripts和test.properties两个文件中加载初始数据,其后的所有操作都会在内存中进行,唯一的缺陷是以这种方式运行的数据都保存在内存里。一旦服务器关闭就会导致数据丢失,下次重新启动数据库时将无法得知上次执行过何种操作,我们看到的数据依然是从test.scripts和test.properties中读取的初始化数据。有关Hsqldb数据库的详细信息可以去它的官方网站www.hsqldb.org查看。
  在下面的讨论中,我们将尽量使用标准的ANSI SQL语句,保证可以在不同的数据库上正常运行。对于不得不使用Hsqldb数据库的特定功能的SQL语句,我们会对它们单独进行讨论。
  对应已经建立好的数据表结构,我们编写了一个与数据库表结构对应的JavaBean——Stud- ent.java,这个JavaBean中的字段与数据库表中的字段是一一对应的,如代码清单12-4所示。
  代码清单12-4 学生信息领域模型
  package com.family168.student;
  public class Student {
  private long id;
  private String code;
  private String name;
  private int sex;
  private int age;
  private String political;
  private String origin;
  private String professional;
  public long getId() {
  return id;
  }
  public void setId(long id) {
  this.id = id;
  }
  // getter and setter
  }
  这是一个简单的JavaBean,它的每个属性对应了student表中的一个字段,我们将数据库中的字段映射为Java对象,在后面的操作里就可以为它们添加特定的逻辑,封装功能,简化操作。
  直接操作Student.java的类叫做StudentDao.java,DAO是Data Access Object的简写,它主要负责Student.java和数据库之间的数据转换。比如,pagedQuery()会把从数据库中读取的数据放入Student.java对象中,并按照特定的形式返回。Insert()和update()方法则是将Student.java中的数据保存到数据库中。Remove()方法执行的是删除操作,它会根据指定的id从数据库中删除对应的对象。
  这些对数据库的操作实际上都是大同小异的,每次先打开与数据库的连接,然后执行查询或更新操作,最后关闭连接并释放资源。
  我们将获得数据库连接的部分代码封装在一个叫做DbUtils.java的工具类中,如代码清单12-5所示。
  代码清单12-5 数据库工具类
  package com.family168.student;
  import java.sql.*;
  public class DbUtils {
  static {
  try {
  Class.forName("org.hsqldb.jdbcDriver");
  } catch(Exception ex) {
  System.err.println(ex);
  }
  }
  static Connection getConn() throws Exception {
  return DriverManager.getConnection("jdbc:hsqldb:res:/test", "sa", "");
  }
  static void close(ResultSet rs, Statement state, Connection conn) {
  if (rs != null) {
  try {
  rs.close();
  } catch(SQLException ex) {
  ex.printStackTrace();
  }
  rs = null;
  }
  if (state != null) {
  try {
  state.close();
  } catch(SQLException ex) {
  ex.printStackTrace();
  }
  state = null;
  }
  if (conn != null) {
  try {
  conn.close();
  } catch (Exception ex) {
  ex.printStackTrace();
  }
  conn = null;
  }
  }
  }
  这是一个工具类,不需要使用new关键字实例化就可以直接调用其中的方法,static{}静态初始化时加载Hsqldb的JDBC驱动,其后每次执行getConn()方法就可以得到一个与数据库的连接。Close()方法提供了一种关闭数据库连接并释放资源的简便方法,使用它可以一次性关闭ResultSet、Statement和Connection 3个对象,方法内自动检测对象是否为null,可以放心使用。
  在StudentDao.java中统一通过DbUtils.java来获得数据库连接,以此实现对数据库的操作,比如remove()方法中就是先通过DbUtils的getConn()方法获得数据库的连接再进行删除操作的,如下面的代码所示。
  public void remove(long id) throws Exception {
  String sql = "delete from student where id=?";
  Connection conn = DbUtils.getConn();
  PreparedStatement state = conn.prepareStatement(sql);
  state.setLong(1, id);
  state.executeUpdate();
  DbUtils.close(null, state, conn);
  }
  先用DbUtils.getConn()获得数据库链接,然后准备SQL语句对应的Statement。设置指定的id后,调用Statement和executeUpdate()执行删除操作,最后不要忘记用DbUtils. close()关闭与数据库的连接释放资源。
  另外两种更新数据库的方法与remove()相似,insert()和update()分别使用的是SQL中的insert和update语句,再将作为参数的Student.java对象设置到Statement中执行更新,实际代码请参考StudentDao.java中的内容,这里就不再赘述了。
  pagedQuery()中的查询功能比较复杂,需要详细讨论一下,先看一下其中的代码部分,如代码清单12-6所示。
  代码清单12-6 pagedQuery()
  public Page pagedQuery(int start, int limit, String sort, String dir) throws
  Exception {
  String sql = "select limit " + start + " " + limit + " * from student";
  if (sort != null && !sort.equals("") && dir != null && !dir.equals("")) {
  sql += " order by " + sort + " " + dir;
  }
  Connection conn = DbUtils.getConn();
  Statement state = conn.createStatement();
  ResultSet rs = state.executeQuery(sql);
  List result = new ArrayList();
  while (rs.next()) {
  Student student = new Student();
  student.setId(rs.getLong("id"));
  student.setCode(rs.getString("code"));
  student.setName(rs.getString("name"));
  student.setSex(rs.getInt("sex"));
  student.setAge(rs.getInt("age"));
  student.setPolitical(rs.getString("political"));
  student.setOrigin(rs.getString("origin"));
  student.setProfessional(rs.getString("professional"));
  result.add(student);
  }
  rs = state.executeQuery("select count(*) from student");
  int totalCount = 0;
  if (rs.next()) {
  totalCount = rs.getInt(1);
  }
  DbUtils.close(rs, state, conn);
  Page page = new Page(totalCount, result);
  return page;
  }
  先看pagedQuery()方法的4个参数,分别是start、limit、sort和dir。start和limit用来进行分页查询,start表示从第几条数据进行查询,limit表示最多返回几条查询数据。sort和dir用来对查询的结果进行排序,sort表示对哪个字段进行排序,dir表示排序时使用升序还是降序。
  为了实现分页和排序功能,pagedQuery()方法中首先要根据传递过来的参数生成对应功能的SQL语句。
  String sql = "select limit " + start + " " + limit + " * from student";
  上面是从student表中进行查询,并依据start和limit的内容进行分页。这里的"select limit " + start + " " + limit的不是标准的ANSI SQL语句,而是Hsqldb专门为分页查询提供的功能。很不幸的是,标准ANSI SQL中没有提供分页查询的功能,基本上每个主流数据库都提供了自己的分页查询方式,这些方式又完全不相同。如果你想把这个示例转换到其他数据库上,必须将这里的分页查询部分修改为对应数据库的SQL语句。
  if (sort != null && !sort.equals("") && dir != null && !dir.equals("")) {
  sql += " order by " + sort + " " + dir;
  }
  生成排序功能的SQL语句比较简单,只需要判断sort和dir是否为空。如果不为空,就可以直接附加到原来的SQL的后面,对获得的查询结果实现排序。
  得到了需要的SQL语句之后,我们立刻进行查询,通过Statement和ResultSet,把每一条返回的记录都转换成Student.java对象,放入到ArrayList中。
  接下来我们还需要获取数据库中的总记录数,对应的SQL语句为select count(*) from student,这是一条标准的ANSI SQL语句,不用做任何修改就可以在所有的主流数据库上运行。使用这条SQL语句,我们获得了数据库中保存的学生信息的总数。
  现在我们可以关闭数据库连接,把学生信息总数totalCount和本页显示的学生信息列表result放入Page.java对象中并返回。
  至此,我们完成了数据建模部分代码的编写,对应的源代码放在WEB-INF/src目录下,编译后的代码放在WEB-INF/classes目录下。对应的com.family168.student包下的内容供对应的JSP或其他JAVA类调用,通过这些类,我们可以访问数据库获得需要的查询结果,也可以通过这些类对数据库中的数据进行更新。
  12.4 在页面中显示学生信息列表
  后台的数据库和Java代码都已经准备妥当,现在来编写显示学生信息列表的Grid部分的代码,如代码清单12-7所示。
  代码清单12-7 前台Prid部分的实现代码
  var sexRenderer = function(value) {
  if (value == 1) {
  return '男';
  } else if (value == 2) {
  return '女';
  }
  };
  var StudentRecord = Ext.data.Record.create([
  {name: 'id', type: 'int'},
  {name: 'code', type: 'string'},
  {name: 'name', type: 'string'},
  {name: 'sex', type: 'int'},
  {name: 'age', type: 'int'},
  {name: 'political', type: 'string'},
  {name: 'origin', type: 'string'},
  {name: 'professional', type: 'string'}
  ]);
  var store = new Ext.data.Store({
  proxy: new Ext.data.HttpProxy({url: './jsp/list.jsp'}),
  reader: new Ext.data.JsonReader({
  totalProperty: 'totalCount',
  root: 'result'
  },StudentRecord),
  remoteSort: true
  });
  store.load({params:{start:0,limit:15}});
  var columns = new Ext.grid.ColumnModel([
  {header: '学号', dataIndex: 'code'},
  {header: '姓名', dataIndex: 'name'},
  {header: '性别', dataIndex: 'sex', renderer: sexRenderer},
  {header: '年龄', dataIndex: 'age'},
  {header: '政治面貌', dataIndex: 'political'},
  {header: '籍贯', dataIndex: 'origin'},
  {header: '所属系', dataIndex: 'professional'}
  ]);
  columns.defaultSortable = true;
  // grid start
  var grid = new Ext.grid.GridPanel({
  title: '学生信息列表',
  region: 'center',
  loadMask: true,
  store: store,
  cm: columns,
  sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
  viewConfig: {
  forceFit: true
  },
  bbar: new Ext.PagingToolbar({
  pageSize: 15,
  store: store,
  displayInfo: true
  })
  });
  // grid end
  sexRenderer是一个工具函数,它用来在Grid中显示学生的性别。数据库中sex字段的类型为int,我们用1代表“男”,2代表“女”,sexRenderer中使用if语句判断当前行的sex值。在1的情况下显示红色粗体的“男”,在2的情况下显示绿色粗体的“女”。之后sexRenderer会作为ColumnModel的一部分设置到Grid中,负责显示“性别”这一列的内容。
  StudentRecord部分使用Ext.data.Record的create()函数创建了一个类,就像之前在数据建模的过程中我们使用JavaBean对数据库进行映射一样,EXT中也对student的数据进行了封装。这样我们就可以在Grid的store中和form的reader中直接使用这个预定义的类型,避免了重复定义相同的数据类型。
  接下来的Ext.data.Store就利用了上面的StudentRecord类型,处理从后台获得的信息。它使用Ext.data.HttpProxy从jsp/list.jsp中获得学生信息列表的信息,返回信息中的total- Property和root两个参数分别指定了后台数据的记录总数和当前页面显示信息的队列,这些数据最终都会显示在Grid中。
  创建好store之后,随即调用store.load({params:{start:0,limit:15}});进行分页查询,这里传递的两个参数start和limit,表示从第一条记录开始查询,最多获得15条记录。这部分与后台的jsp/list.jsp交互的操作我们留在后面再详细讨论。
  下面的工作是创建Ext.grid.ColumnModel,将Grid中每列显示的数据与store中的数据相对应。建立好列模型后,再调用columns.defaultSortable = true;将所有列都设置成可排序的。排序功能也可以通过为每一列设置sortable:true来实现,但是像那样逐一设置会显得繁琐,不如统一设置方便。
  万事俱备只欠东风,Grid需要的组件部分都准备好了,现在可以创建Grid来显示学生信息列表了。为了不使Grid显得寒酸,除了上面提到的store和columns之外,我们还为Grid设置了标题title。用loadMask:true开启读取数据时的提示功能,这会在每次store去后台读取数据时自动显示等待提示信息。sm: new Ext.grid.RowSelectionModel({singleSelect:true})限制用户每次只能选中一行,这是为了后面与表单form进行互操作所做的准备。viewConfig: {forceFit: true}自动调整每列的宽度,使整个表格更加饱满。最后还为bbar添加了分页工具条,可以用它进行Grid的分页跳转和数据刷新操作。
  Region:'center'表示把这个grid放到BorderLayout的中间位置。接下来,我们调整原来Viewport中的配置,把之前放在中间的Panel替换为我们创建好的Grid,如代码清单12-8所示。
  代码清单12-8 将Grid加入页面
  var viewport = new Ext.Viewport({
  layout: 'border',
  items: [{
  region: 'north',
  contentEl: 'head'
  }, grid, {
  region: 'east',
  html: 'form'
  }, {
  region: 'south',
  contentEl: 'foot'
  }]
  });
  最终的显示效果如图12-4所示。
  
  图12-4 加入Grid后的页面效果
  因为我们没有为右侧的form部分设置宽度,所以Viewport自动为它计算了一个最小宽度,结果位于中间的Grid几乎布满了整个页面。从上图中我们可以看到,Grid中显示了每列对应的数据,“性别”这一列也按照sexRenderer中定义的那样显示得错落有致。现在你可以单击Grid下部的分页工具条上的按钮查看分页查询的效果,也可以单击某一列的首部,查看按列排序的功能。
  看过了页面的效果,我们再回到后台看看为前台提供数据的list.jsp中的内容,如代码清单12-9所示。
  代码清单12-9 list.jsp
  
<%@ page contentType="application/json;charset=utf-8" import="com.family168.student.
*" %><%
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");
    int start = 0;
    try {
        start = Integer.parseInt(request.getParameter("start"));
    } catch(Exception ex) {
        System.err.println(ex);
    }
    int limit = 15;
    try {
        limit = Integer.parseInt(request.getParameter("limit"));
    } catch(Exception ex) {
        System.err.println(ex);
    }
    String sort = request.getParameter("sort");
    String dir = request.getParameter("dir");

    StudentDao dao = StudentDao.getInstance();
    Page pager = dao.pagedQuery(start, limit, sort, dir);

    out.print(pager.toString());
%>

  List.jsp中的代码可以分成三部分,如下所示。
  (1) 第一部分,设置JSP使用的contentType和encoding,因为Ajax在访问后台时,默认使用UTF-8作为请求和响应时传递的数据的默认编码,而在Tomcat服务器中,使用的默认编码是ISO-8859-1。这种编码的差异会在传递中文字符时出现乱码,所以我们首先需要把请求和响应的编码都统一设为UTF-8。
  (2) 第二部分,处理前台传递的参数,包括start、limit、sort、dir等4个参数,这4个参数在12.3节中已经讨论过。start和limit用来进行分页查询,start表示从第几条数据进行查询,limit表示最多返回几条查询数据。sort和dir用来对查询的结果进行排序,sort表示对哪个字段排序,dir表示排序时使用升序还是降序。
  因为使用HTTP协议只能传递字符类型的参数,所以在list.jsp中,我们要对这几个参数进行类型转换。注意,当有可能出现转换失败的情况时,需要为对应的参数设置默认值,我们这里默认设置start为0,limit为15。
  (3) 第三部分,将获得的4个参数传递给StudentDao,获得分页结果对象,最后调用Page的toString()方法,将它生成的内容返还给前台处理。
  我们将StudentDao设计成一个单例模式(Singleton),这样可以在当前系统中保证只创建一个实例,这个实例被其他类所共享,避免了重复创建对象造成的资源浪费。
  关于Page的toString()方法,是为了将分页结果转换成JSON格式,这个功能是通过Student.java和Page.java两个类中的toString()方法来实现的。
  Student.java的代码如下所示:
  public String toString() {
  return "{id:" + id +
  ",code:'" + code +
  "',name:'" + name +
  "',sex:" + sex +
  ",age:" + age +
  ",political:'" + political +
  "',origin:'" + origin +
  "',professional:'" + professional +
  "'}";
  }
  Page.java的代码如下所示:
  public String toString() {
  return "{totalCount:" + totalCount + ",result:" + result + "}";
  }
  通过这两个方法,page.toString()会返回一个符合JSON格式的字符串,并使用之前设置好的UTF-8编码格式发送给前台。前台EXT接收到的内容如代码清单12-10所示。
  代码清单12-10 前台EXT获得的JSON数据
  {
  totalCount:16,
  result:[{
  id:1,
  code:'2002015',
  name:'张光和',
  sex:1,
  age:21,
  political:'团员',
  origin:'湖北省',
  professional:'物流工程学院'
  }, {
  id:2,
  code:'2002002',
  name:'张值强',
  sex:1,
  age:22,
  political:'党员',
  origin:'河南省',
  professional:'动力工程系'
  }]
  }
  totalCount表示数据库中现有学生信息的总数,result表示当前页显示的信息列表,前台获得这些信息之后,就可以把这些数据显示到Grid中。
  12.5 添加表单编辑学生信息
  配置好Grid之后,我们再添加一个表单来编辑学生信息,如代码清单12-11所示。
  代码清单12-11 编辑学生信息的表单
  // form start
  var form = new Ext.form.FormPanel({
  title: '编辑学生信息',
  region: 'east',
  frame: true,
  width: 300,
  autoHeight: true,
  labelAlign: 'right',
  labelWidth: 60,
  defaultType: 'textfield',
  defaults: {
  width: 200,
  allowBlank: false
  },
  items: [{
  xtype: 'hidden',
  name: 'id'
  },{
  fieldLabel: '学号',
  name: 'code'
  },{
  fieldLabel: '姓名',
  name: 'name'
  },{
  fieldLabel: '年龄',
  name: 'age',
  xtype: 'numberfield',
  allowNegative: false
  },{
  fieldLabel: '性别',
  name: 'sexText',
  hiddenName: 'sex',
  xtype: 'combo',
  store: new Ext.data.SimpleStore({
  fields: ['value','text'],
  data: [['1','男'],['2','女']]
  }),
  emptyText: '请选择',
  mode: 'local',
  triggerAction: 'all',
  valueField: 'value',
  displayField: 'text',
  readOnly: true
  },{
  fieldLabel: '政治面貌',
  name: 'political',
  xtype: 'combo',
  store: new Ext.data.SimpleStore({
  fields: ['text'],
  data: [['群众'],['党员'],['团员']]
  }),
  emptyText: '请选择',
  mode: 'local',
  triggerAction: 'all',
  valueField: 'text',
  displayField: 'text',
  readOnly: true
  },{
  fieldLabel: '籍贯',
  name: 'origin'
  },{
  fieldLabel: '所属系',
  name: 'professional'
  }],
  buttons: [{
  text: '添加'
  },{
  text: '清空'
  },{
  text: '删除'
  }]
  });
  // form end
  我们把这个表单的宽度设置为300,让它里面的输入组件对应的文字标签都右对齐,其中的defaults参数很有用,在它里面设置的参数会自动赋予form内部的输入组件,这样内部组件的相同配置只需要配置一次就可以了。我们使用的配置是{ width: 200, allowBlank: false},每个组件的宽度都设置为80,而且不能提交空值。因为我们使用的输入组件大多是textfield,所以把defaultType设置为'textfield'可以不必为每个items设置xtype。
  在对Ext.form.FormPanel进行了这些设置之后,我们可以把学生信息对应的输入控件放到items中了。其中“学号”,“姓名”,“籍贯”,“所属系”都是Textfield类型,因此只需要配置name和fieldLabel。
  需要使用xtype:'hidden'将id字段配置为隐藏域。虽然在页面上看不到它,但是也可以通过后面的loadRecord()方法设置id字段的数据。当进行表单提交时,它里边的数据也会一起发送到后台,它的作用和<input type="hidden" name="id">是一致的。
  “年龄”输入框限制用户只能输入数字,使用了Ext.form.NumberField数字输入组件,对应使用了xtype:'numberfield'。因为人的年龄不可能是负数,所以我们还将allowNegative设置为false,不允许用户输入负数。
  虽然“性别”和“政治面貌”使用的都是Ext.form.ComboBox,但还是有一些不同。
  虽然“性别”这个ComboBox选择的是“男”和“女”,但是实际上传递到后台的是对应的1或2两个整数。因此,在配置ComboBox时需要将displayField和valueField分开使用,还要配置上hiddenName,否则发送到后台的依然是显示在页面上的“男”和“女”,而不是与之对应的1或2两个整数。
  “政治面貌”这个ComboBox就简单了许多,选择的文字和传递给后台的数据是一致的,只需displayField和valueField的配置相同,不需要额外配置hiddenName参数。
  经过了这一系列的配置后,我们得到了一个可以用来编辑学生信息的表单form,效果如图12-5所示。
  到此为止,学生信息管理系统的整个界面已经制作完成,但是还不能利用表单对学生信息执行添加、修改和删除等操作。
  
  图12-5 加入表单后的界面效果
  下面我们要为表单的按钮设置监听事件,让我们在单击相应按钮时执行对应的操作。
  12.6 为表单添加提交事件
  在上面的表单中,我们放置了三个按钮,分别是“添加”、“清空”和“删除”。首先我们为“添加”按钮添加事件,为它的handler参数指定一个处理函数,如代码清单12-12所示。
  代码清单12-12 表单按钮的单击事件
  {
  text: '添加',
  handler: function() {
  if (!form.getForm().isValid()) {
  return;
  }
  if (form.getForm().findField("id").getValue() == "") {
  // 添加
  form.getForm().submit({
  url: './jsp/save.jsp',
  success: function(f, action) {
  if (action.result.success) {
  Ext.Msg.alert('消息', action.result.msg,function(){
  grid.getStore().reload();
  form.getForm().reset();
  form.buttons[0].setText('添加');
  });
  }
  },
  failure: function() {
  Ext.Msg.alert('错误', "添加失败");
  }
  });
  } else {
  // 修改
  form.getForm().submit({
  url: './jsp/save.jsp',
  success: function(f, action) {
  if (action.result.success) {
  Ext.Msg.alert('消息',action.result.msg,function(){
  grid.getStore().reload();
  form.getForm().reset();
  form.buttons[0].setText('添加');
  });
  }
  },
  failure: function() {
  Ext.Msg.alert('错误', "修改失败");
  }
  });
  }
  }
  }
  在handler处理函数中,首先调用form.getForm().isValid()进行数据校验。如果返回false,说明表单中某些输入组件中的数据还无法通过校验,不应该提交这些错误格式的数据,这时我们应该直接跳出函数,中止提交操作。
  如果表单顺利通过数据校验检测,我们便进入下一步,准备向后台提交数据。还记得我们刚才讲过的id对应的隐藏域吗?这里就是通过它的数值来判断本次提交是添加操作还是修改操作。如果form.getForm().findField("id").getValue()为空字符串,就是在进行数据添加;如果它不为空字符串,就是在对一个已有的数据进行修改。
  虽然我们对添加和修改操作进行了区分,但是在实际提交代码时并没有太大区别,只是在提交错误时分别提示“添加失败”和“修改失败”而已。让我们通过代码清单12-13来看看这些数据是如何提交给后台的。
  代码清单12-13 将数据提交给后台
  // 添加
  form.getForm().submit({
  url: './jsp/save.jsp',
  success: function(f, action) {
  if (action.result.success) {
  Ext.Msg.alert('消息', action.result.msg, function() {
  grid.getStore().reload();
  form.getForm().reset();
  form.buttons[0].setText('添加');
  });
  }
  },
  failure: function() {
  Ext.Msg.alert('错误', "添加失败");
  }
  });
  这里的form表示我们前面创建的Ext.form.FormPanel,它的getForm()函数返回Form- Panel内部对应的Ext.form.BasicForm。现在我们调用BasicForm的submit()函数,将内部items中输入组件的值提交给后台的jsp/save.jsp。
  如果后台没有出现异常,而且返回的JSON信息中包含{success:true},那么就会执行success参数对应的处理函数。在success处理函数中,我们创建一个Ext.Msg.alert()显示响应的JSON信息中的msg部分的内容。在用户关闭alert提示框之后,调用grid.getStore().- reload()刷新Grid中的数据,同时调用form.getForm().reset()清空上次提交的数据。
  如果后台出现400或500错误,就会触发failure参数对应的处理函数。这里只是弹出一个alert提示框告诉用户“添加失败”,等待用户对刚才提交失败的信息进行修改或做其他处理。
  在添加和修改信息时,都是提交给后台的jsp/save.jsp进行处理。save.jsp这个文件同时负责添加和修改两个操作,它里面的内容如代码清单12-14所示。
  代码清单12-14 save.jsp
  
<%@ page contentType="application/json;charset=utf-8" import="com.family168.student. 
*" %><%
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");

    String id = request.getParameter("id");
    String code = request.getParameter("code");
    String name = request.getParameter("name");
    String sex = request.getParameter("sex");
    String age = request.getParameter("age");
    String political = request.getParameter("political");
    String origin = request.getParameter("origin");
    String professional = request.getParameter("professional");

    Student student = new Student();
    student.setCode(code);
    student.setName(name);
    student.setSex(Integer.parseInt(sex));
    student.setAge(Integer.parseInt(age));
    student.setPolitical(political);
    student.setOrigin(origin);
    student.setProfessional(professional);

    StudentDao dao = StudentDao.getInstance();
    if (id == null || id.equals("")) {
        dao.insert(student);
    } else {
        student.setId(Long.parseLong(id));
        dao.update(student);
    }
    out.print("{success:true,msg:'保存成功'}");
%>
  与之前讨论过的list.jsp类似,它里面的处理过程也大致分为3步。
  (1) 设置JSP使用的contentType和encoding,把请求和响应的编码都统一为UTF-8。
  (2) 处理前台传递的参数,通过request.getParameter()方法从请求中获得刚刚提交过来的学生信息,创建一个Student.java对象,将对应的学生信息添加到对象中。这个过程中要注意对age和sex两个参数进行数据类型转换,因为从HTTP请求中只能获得字符串,而它们都需要在其后转换为整数类型。
  为了在后面的操作中区分添加和修改操作,我们没有对参数id进行处理。
  (3) 现在我们已经获得了提交数据,并把这些数据都放到了对应的Student.java对象中。现在我们可以判断参数id的值,以此来判断后面要执行的是添加操作还是修改操作。
  与前台EXT提交数据代码相似,如果id == null || id.equals(""),就表明应该执行添加操作,执行StudentDao的insert()方法。否则,应该执行修改操作,执行StudentDao的update()方法。
  最后,只要未出现异常,我们就认为添加或修改操作成功了,直接向response中写入提示成功信息的JSON字符串。
  out.print("{success:true,msg:'保存成功'}");
  12.7 清空表单信息
  再来看看“清空”按钮中handler对应的处理函数。
  {
  text: '清空',
  handler: function() {
  form.getForm().reset();
  form.buttons[0].setText('添加');
  }
  }
  它的作用是调用form.getForm().reset();清空form中的所有数据,然后把form.- buttons[0]也就是form的第一个按钮的文字修改为“添加”。
  Reset将form恢复到初始状态。如果刚才在表单中添加了一些不必要的数据,又不想逐一去删除它们,那么只需要单击这个按钮就能清除所有输入框中的数据了。
  Reset还有一个功能是清空隐藏域id中的数据,因为id在页面上是看不到的,所以无法手工删除它的数据,此时就只好求助于“清空”按钮了。如果想从“修改”状态转换到“添加”状态,也需要它的帮助。
  Form.buttons[0]从表单中取出排在第一位的按钮。我们一共设置了3个按钮,第一个是“添加”按钮,因为在修改学生信息时会把它上面的文字改为“修改”,所以“清空”按钮也要在执行reset()之后把它的文字改为“添加”才行。
  12.8 删除指定的学生信息
  “删除”按钮是表单上的第三个按钮,它的作用是删除当前显示的学生信息。既然是从数据库中删除已有的学生信息,那它就只能在“修改”信息的状态下起作用。因为在没有获得学生信息的id之前,无法执行删除操作。
  “删除”按钮对应的handler处理函数如代码清单12-15所示。
  代码清单12-15  删除学生信息的处理函数
  {
  text: '删除',
  handler: function() {
  var id = form.getForm().findField('id').getValue();
  if (id == '') {
  Ext.Msg.alert('提示', '请选择需要删除的信息。');
  } else {
  Ext.Ajax.request({
  url: './jsp/remove.jsp',
  success: function(response) {
  var json = Ext.decode(response.responseText);
  if (json.success) {
  Ext.Msg.alert('消息', json.msg, function() {
  grid.getStore().reload();
  form.getForm().reset();
  form.buttons[0].setText('添加');
  });
  }
  },
  failure: function() {
  Ext.Msg.alert('错误', "删除失败");
  },
  params: "id=" + id
  });
  }
  }
  }
  处理函数首先要判断form.getForm().findField('id').getValue()的值是否为空,如果id为空,表示还没有选择需要删除的信息,这时会弹出提示信息,同时中止删除操作。
  如果id不为空,表示已经选择了需要删除的信息,这样可以把对应信息的id发送到后台执行删除操作。与“添加”按钮不同的是,向后台发送要删除的信息的id时要通过Ext.Ajax来实现,因为删除操作并不需要将表单中所有输入组件的数据都发送到后台。
  使用Ext.Ajax.request()函数发送信息时,url参数表示发送给后台的jsp/remove.jsp进行处理,params表示将学生信息的id作为参数传递给后台。Success对应的处理方法会在响应成功时执行,与form不同的是,这里不再需要在响应的JSON中设置{success:true},但我们需要使用Ext.decode()函数将响应返回的response.responseText字符串手工转换为JSON对象,然后用Ext.Msg.alert()显示响应中的提示信息。响应成功后,刷新Grid数据,清空表单内容的操作与之前的添加和修改操作相同。
  因为删除操作只向后台的remove.jsp发送需要删除的信息的id,所以remove.jsp中的处理代码也比较简单,如代码清单12-16所示。
  代码清单12-16 remove.jsp
  
<%@ page contentType="application/json;charset=utf-8" import="com.family168.student.
*" %><%
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");

    String id = request.getParameter("id");
    StudentDao dao = StudentDao.getInstance();
    dao.remove(Long.parseLong(id));

    out.print("{success:true,msg:'删除成功'}");
%>

  这里我们还是先要设置请求和响应的编码,然后从请求中获得参数id的值,转换成long长整型,调用StudentDao的remove()方法执行删除操作。如果处理的过程没有出现异常,就说明删除操作成功,最后向响应中写入删除成功的提示信息。
  这样我们就完成了删除指定学生信息的操作。
  12.9 在Grid和Form之间进行数据交互
  我们在上面已经实现了在Grid中显示学生信息列表,也实现了使用Form对学生信息执行添加、修改和删除等操作。但是,Grid和Form之间的数据还无法交互使用。
  一个尚未解决的问题是,如何在Form中显示我们需要修改的学生信息呢?这个问题也适用于删除操作,如果我们不提供选择某一条学生记录的方法,那么修改和删除操作都无法进行。
  在这个示例中,我们希望在单击左侧的Grid时同步更新右边Form中的数据。如果用户单击Grid中的某一行,就会把这行对应的学生信息放到Form中显示,于是我们就能对这条信息进行修改和删除操作了。为此,我们要给Grid添加一个事件监听函数,专门处理鼠标点击事件,如下面的代码所示。
  // 单击修改信息开始
  grid.on('rowclick', function(grid, rowIndex, event) {
  var record = grid.getStore().getAt(rowIndex);
  form.getForm().loadRecord(record);
  form.buttons[0].setText('修改');
  });
  // 单击修改信息结束
  这里监听的事件名为rowclick,它对应Ext.grid.RowSelectionModel的监听事件,每当用户选中Grid中的一行时,就会触发该事件。事件被触发的同时还会执行我们设置的监听函数。
  监听函数预设了3个参数:第一个参数grid表示哪个Grid被点击了;第二个参数rowIndex表示选中了哪一行;event是EXT内部通用的事件对象,我们在这里没有用到。
  在监听函数执行时,首先通过grid.getStore().getAt(rowIndex);获得被选中的这一行对应的record。这个record是保存在store中的数据,Grid上没有显示出来的id也包含在其中。对应的所有学生信息都可以从这个record中获得,但并不需要从record中把学生信息逐一取出来,然后再逐一放到Form中去。Form提供的loadRecord()函数可以一次性将record中的数据赋予Form中的输入组件,只要保证输入组件的name或hiddenName与record中定义的属性一致即可。
  在使用loadRecord()将Grid中选择的数据复制到Form中以后,我们再调用form. buttons[0].setText('修改'); 将Form中的第一个按钮的文字设置为“修改”。这样用户就知道现在提交Form执行的是对某一条学生信息进行修改的操作。如果要继续添加新的学生信息,可以单击“清空”按钮,它会将刚刚从Grid中复制的信息都清除掉,包括id隐藏域中的数据,还会把第一个按钮上的“修改”设置为“添加”。再次输入数据并单击“提交”按钮,这时执行的就是“添加”操作了。
  到此为止,我们用前面学过的知识实现了一个完整的学生信息管理系统。其中涉及BorderLayout的布局应用、Grid的分页显示和数据排序、Form的提交和清空、利用Ajax与后台进行数据交互、通过事件监听实现Grid与Form之间的数据交互等知识。
  这个示例虽然简单,但基本上包含了所有的常见操作,可以供大家练习时参考。
  12.10 本章小结
  本章详细演示了如何实现一个学生信息管理系统。其中简要介绍了如何使用Java访问数据库,以及一些SQL语句的使用方法。重点介绍了如何使EXT与后台进行交互,以实现对数据库信息进行分页显示和后台排序。此外,还讲解了如何在EXT中对数据库信息执行添加、删除、修改和查找等基本操作,并提出了一种Grid与Form之间进行数据交互的方式。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics