你的位置:首页 > Java教程

[Java教程]ExtJs 之 ComboBox级联使用


刚接触ExtJs不到一周,项目使用ExtJs框架,有个版块用到了combobox的级联(两级),遇到了一系列的问题,两天来一直查API、网络资料,终于解决了。

先列出遇到的一系列问题(也许你也遇到过!),再看是如何一步步解决这些问题的,最后给出个人觉得ExtJs的ComboBox级联的最佳方案。

***首先声明,测试使用[年级]和[班级]的级联,数据从服务端获取。最终效果是:年级列表显示所有年级,默认显示第一个年级;班级列表显示第一个年级下的班级,默认显示"所有";***

遇到的问题:

1.为何每次点击班级列表时就把所有的班级加载出来了,但切换另一个年级后就正常了?

2.打开火狐的Firebug可以看到,班级列表已经加载一次了,但点击下拉列表框后又加载了一次,怎么回事?其实点击年级列表也会再加载一次的,why?

3.如何为combobox设置一个默认值?

4.如何为combobox添加一个值(“所有”)

5.想在监听事件afterrender或者change事件中来处理上述问题,觉得不是你想的那样?

6.queryMode、triggerAction、autoLoad这些属性怎么配合使用?

 

 ----------解决-------------------------------------------------------------------------------------------------------------------------------------------------------------

先贴出测试的Servlet类:主要用于获取年级列表和班级列表,数据是静态的,以JSON格式返回。

 1 package com.lizhou.bms.controller; 2  3 import com.lizhou.bms.entity.Clazz; 4 import com.lizhou.bms.entity.Grade; 5 import net.sf.json.JSONArray; 6  7 import javax.servlet.ServletException; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 import java.io.IOException; 12 import java.util.ArrayList; 13 import java.util.LinkedList; 14 import java.util.List; 15  16 /** 17  * 模拟获取数据 18  * @author bojiangzhou 19  * @date 2016/8/8 20 */ 21 public class StudentController extends HttpServlet { 22  23   private static List<Grade> gradeList = new LinkedList<Grade>(); 24  25   private static List<Clazz> clazzList = new LinkedList<Clazz>(); 26  27   /** 28    * 数据源 29   */ 30   static { 31     //年级 32     Grade g1 = new Grade(1, "一年级"); 33     Grade g2 = new Grade(2, "二年级"); 34     Grade g3 = new Grade(3, "三年级"); 35  36     gradeList.add(g1); 37     gradeList.add(g2); 38     gradeList.add(g3); 39  40     //班级 41     Clazz g1c1 = new Clazz(1, 1, "一年级 1班"); 42     Clazz g1c2 = new Clazz(2, 1, "一年级 2班"); 43     Clazz g1c3 = new Clazz(3, 1, "一年级 3班"); 44     Clazz g1c4 = new Clazz(4, 1, "一年级 4班"); 45     Clazz g1c5 = new Clazz(5, 1, "一年级 5班"); 46     Clazz g1c6 = new Clazz(6, 1, "一年级 6班"); 47     Clazz g1c7 = new Clazz(7, 1, "一年级 7班"); 48  49     Clazz g2c1 = new Clazz(8, 2, "二年级 1班"); 50     Clazz g2c2 = new Clazz(9, 2, "二年级 2班"); 51     Clazz g2c3 = new Clazz(10, 2, "二年级 3班"); 52     Clazz g2c4 = new Clazz(11, 2, "二年级 4班"); 53     Clazz g2c5 = new Clazz(12, 2, "二年级 5班"); 54     Clazz g2c6 = new Clazz(13, 2, "二年级 6班"); 55     Clazz g2c7 = new Clazz(14, 2, "二年级 7班"); 56  57     Clazz g3c1 = new Clazz(15, 3, "三年级 1班"); 58     Clazz g3c2 = new Clazz(16, 3, "三年级 2班"); 59     Clazz g3c3 = new Clazz(17, 3, "三年级 3班"); 60     Clazz g3c4 = new Clazz(18, 3, "三年级 4班"); 61     Clazz g3c5 = new Clazz(19, 3, "三年级 5班"); 62     Clazz g3c6 = new Clazz(20, 3, "三年级 6班"); 63     Clazz g3c7 = new Clazz(21, 3, "三年级 7班"); 64  65     clazzList.add(g1c1); 66     clazzList.add(g1c2); 67     clazzList.add(g1c3); 68     clazzList.add(g1c4); 69     clazzList.add(g1c5); 70     clazzList.add(g1c6); 71     clazzList.add(g1c7); 72  73     clazzList.add(g2c1); 74     clazzList.add(g2c2); 75     clazzList.add(g2c3); 76     clazzList.add(g2c4); 77     clazzList.add(g2c5); 78     clazzList.add(g2c6); 79     clazzList.add(g2c7); 80  81     clazzList.add(g3c1); 82     clazzList.add(g3c2); 83     clazzList.add(g3c3); 84     clazzList.add(g3c4); 85     clazzList.add(g3c5); 86     clazzList.add(g3c6); 87     clazzList.add(g3c7); 88  89   } 90  91   @Override 92   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 93  94     //前台传一个method参数,getGradeList即请求获取年级列表,getClazzList即请求获取班级列表 95     String method = request.getParameter("method"); 96  97     response.setCharacterEncoding("UTF-8"); 98  99     if("getGradeList".equals(method)){100       JSONArray jsonArray = JSONArray.fromObject(gradeList);101       String ret = jsonArray.toString();102       response.getWriter().write(ret);103 104     } else if("getClazzList".equals(method)){105       List<Clazz> clist = new ArrayList<Clazz>();106       //年级id107       String sgid = request.getParameter("gid");108       if(sgid != null){109         int gid = Integer.parseInt(sgid);110 111         for(Clazz c : clazzList){112           if(c.getGid() == gid){113             clist.add(c);114           }115         }116       } else{117         clist.addAll(clazzList);118       }119       JSONArray jsonArray = JSONArray.fromObject(clist);120       String ret = jsonArray.toString();121       response.getWriter().write(ret);122 123     }124   }125 }

 

然后是最初版的JS代码:

 1 <%-- 2   3  @author bojiangzhou 4  @date 2016/8/8 5 --%> 6 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 7 <%@ page isELIgnored="false" %> 8 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 9 <html> 10 <head> 11   <title>Combobox</title> 12   <link rel="stylesheet" href="js/ext/resources/css/ext-all.css" /> 13   <script type="text/javascript" src="js/ext/ext-all.js"></script> 14   <script> 15     Ext.onReady(function () { 16  17       /** 18        * 创建年级Combo 19       */ 20       Ext.create('Ext.form.ComboBox', { 21         renderTo: Ext.getBody(), 22         id: 'gradeId', 23         displayField: 'name', 24         valueField: 'id', 25         editable: false, 26         readonly: true, 27         allowBlank: true, 28         fieldLabel: '选择年级', 29         margin: '50 10 0 0', 30         labelAlign: 'right', 31         triggerAction: 'all',  //点击下拉列表时执行的操作 32         queryMode: 'remote',  //store的查询模式 33         store: Ext.create('Ext.data.JsonStore', { 34           fields: [ 35             {name: 'id'}, 36             {name: 'name'} 37           ], 38           autoLoad: true, //启动自动加载 39           proxy: {    //通过ajax代理加载数据 40             type: 'ajax', 41             url: 'student?method=getGradeList', 42             reader: { 43               type: 'json', 44               root: 'content' 45             } 46           } 47         }), 48         listeners: { 49           'change': function(o, gid){ //change事件 50             if(gid){ 51               var clazzId = Ext.getCmp("clazzId");  //获取Clazz Combo组件 52               clazzId.getStore().removeAll(); // 清空已加载列表 53               clazzId.reset();  // 清空已存在结果 54  55               //发生change事件后将年级id传到后台获取该年级下的班级 56               clazzId.getStore().load({ 57                 params: {'gid': gid} 58               }); 59             } 60           } 61         } 62  63       }); 64  65       /** 66        * 创建班级Combo 67       */ 68       Ext.create('Ext.form.ComboBox', { 69         renderTo: Ext.getBody(), 70         id: 'clazzId', 71         displayField: 'name', 72         valueField: 'id', 73         editable: false, 74         readonly: true, 75         allowBlank: true, 76         fieldLabel: '选择班级', 77         margin: '50 10 0 0', 78         labelAlign: 'right', 79         triggerAction: 'all',  //点击下拉列表时执行的操作 80         queryMode: 'remote',  //store的查询模式 81         store: Ext.create('Ext.data.JsonStore', { 82           fields: [ 83             {name: 'id'}, 84             {name: 'gid'}, 85             {name: 'name'} 86           ], 87           autoLoad: true, //启动自动加载 88           proxy: {    //通过ajax代理加载数据 89             type: 'ajax', 90             url: 'student?method=getClazzList', 91             reader: { 92               type: 'json', 93               root: 'content' 94             } 95           } 96         }) 97       }); 98  99 100     });101 102   </script>103 </head>104 <body>105 106 </body>107 </html>

 

一、queryMode、autoLoad

第一次刷新页面显示效果如下:可以看到两个列表都没有默认值,其次是一开始就发送了两次请求,也就是说已经将年级和班级的数据加载进来了(而且还是所有数据)。

然后点击班级列表,选择一年级,看到如下效果:点击年级下拉列表的时候又发送了一次请求的,然后这个时候会触发年级combobox的change事件,加载班级列表,可以看到请求已经发送过去了,年级id也传过去了,那班级列表按理说应该是一年级的班级;

再看第二张图片的效果:点击下拉列表框的时候也同样发送了一次请求,而班级显示的是所有班级,这就是出现的问题了,为什么会这样呢?

从第一次刷新页面来说整个过程:首先刷新页面,因为配置的store为自动加载(autoLoad: true),所以在刷新页面的时候,会自动将数据加载到store中,然后渲染到列表里。

然后点击年级列表,因为我们设置的queryMode: 'remote',(remote是默认属性值);个人理解:queryMode属性决定着当【第一次】点击下拉列表的时候,列表的查询模式,remote即从远程加载,相当于点击下拉列表的时候又加载了一次,这就是点击列表的时候为什么又发送了一次请求的原因。queryModel的另一个属性值是'local',从本地加载;我的理解是,数据如果已经从远端加载到store中了(比如autoLoad,年级列表change事件触发加载班级列表),所谓的local就是当第一次点击下拉列表的时候直接从store中获取数据,而相对的,remote则会从远端加载,而且会覆盖掉store中的数据。

再是点击班级列表,虽然点击年级列表触发了change事件来使班级列表加载当前年级下的班级,原因上面已经说了,点击班级列表的时候,同样重新发送了一个请求加载了所有的班级,所以之前的被覆盖了。

解决办法:将二者的queryMode设置为local,使其从Store中获取数据,年级列表自动加载,设置为local后点击下拉列表时不会再发送一次请求;但是班级列表是与年级列表联动的,所以在没有年级列表的时候,我不希望显示班级列表,那么可以设置班级ComboBox的store的autoLoad:false,让其不自动加载,只有在选择年级的时候才去加载相应年级下的班级。这样一来刷新页面的时候就只发送了一次加载年级的请求,班级只会在选择年级后加载,但是每次还是会发送请求的。

 

 二、如何为让年级列表默认选择第一个,班级列表默认显示"所有"

让第一级列表(年级列表)默认显示第一条,刚开始想的办法是给班级Combobox加一个afterrender事件,即组件渲染完成后给年级列表设置第一个选项,这样也会触发change事件,就能加载班级了;

或者给年级列表添加一个属性value=1,默认选择第一个选项,但是第一次不会加载班级,没有触发change事件。这两种方式都有一个小问题,就是刷新页面的时候,会看到列表框首先显示的1,再才显示第一个选项的,尤其在加载比较慢的时候就很明显了。所以这两种方式不可取。

1 listeners: {2   'afterrender': function (o) {3     var gradeId = Ext.getCmp("gradeId");  //获取Grade Combo组件4     gradeId.setValue(1);5   }6 }

再说说如何为班级列表插入一个选项"所有",之前尝试过很多种方式都不行,然后想了一个不算好的办法可以在后台获取到数据后,再向集合中插入一个含有"所有"的对象,就能直接加载过来了,但是这种方式不是很好。其实主要是添加的时机不对,导致没有添加进去。

最后经过一系列的测试,对于数据的操作应放在Store的load事件中来操作,就都正常了,Store本身就是数据仓库,所以在ComboBox上做的操作都有所不妥。

看最后解决上述问题的代码:注意看注释部分,是解决问题的关键。

 1 <%-- 2   3  @author bojiangzhou 4  @date 2016/8/7 5 --%> 6 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 7 <%@ page isELIgnored="false" %> 8 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 9 <html> 10 <head> 11   <title>Combobox</title> 12   <link rel="stylesheet" href="js/ext/resources/css/ext-all.css" /> 13   <script type="text/javascript" src="js/ext/ext-all.js"></script> 14   <script> 15     Ext.onReady(function () { 16  17       /** 18        * 年级列表 19       */ 20       Ext.create('Ext.form.ComboBox', { 21         renderTo: Ext.getBody(), 22         id: 'gradeId', 23         displayField: 'name', 24         valueField: 'id', 25         editable: false, 26         readonly: true, 27         allowBlank: true, 28         fieldLabel: '选择年级级', 29         margin: '50 10 0 0', 30         labelAlign: 'right', 31         queryMode: 'local',   //本地查询,配置这个属性,在第一次点击下拉列表的时候就不会从服务端加载数据了 32         triggerAction: 'all', 33         store: Ext.create('Ext.data.JsonStore', {  //Store数据仓库 34           fields: [ 35             {name: 'id'}, 36             {name: 'name'} 37           ], 38           autoLoad: true,   //第一级列表设置自动加载 39           proxy: {      //通过ajax代理加载数据 40             type: 'ajax', 41             url: 'student?method=getGradeList', 42             reader: { 43               type: 'json', 44               root: 'ret' 45             } 46           }, 47           listeners: {  //注意是store的监听器 48             'load': function (store, records) { //store的load事件 49               //设置第一个值为默认值 50               Ext.getCmp("gradeId").setValue(records[0]); 51             } 52           } 53         }), 54         listeners: {    //这是ComboBox的监听器 55           'change': function(o, nv){ //change事件 56             if(nv){ 57               var clazzId = Ext.getCmp("clazzId"); 58               clazzId.getStore().removeAll();// 清空已加载列表 59               clazzId.reset();// 清空已存在结果 60  61               //在年级列表发生改变时将年级ID传到后台,加载该年级下的班级, 62               //但是每次改变年级时都会从服务器加载,有点消耗服务器资源 63               clazzId.getStore().load({ 64                 params: {'gid': nv},  //参数 65                 callback: function(records, operation, success) { //加载完成调用的函数 66                   //添加一个所有选项 67                   clazzId.getStore().insert(0, {id: 0, name: '所有' }); 68                   clazzId.setValue(0); //设置默认第一个 69                 } 70               }); 71             } 72           } 73         } 74  75       }); 76  77       /** 78        * 班级列表 79       */ 80       Ext.create('Ext.form.ComboBox', { 81         renderTo: Ext.getBody(), 82         id: 'clazzId', 83         displayField: 'name', 84         valueField: 'id', 85         editable: false, 86         readonly: true, 87         allowBlank: true, 88         fieldLabel: '选择年级', 89         margin: '50 10 0 0', 90         labelAlign: 'right', 91         triggerAction: 'all', 92         queryMode: 'local',   //本地加载模式 93         store: Ext.create('Ext.data.JsonStore', {  //Store数据仓库 94           fields: [ 95             {name: 'id'}, 96             {name: 'name'} 97           ], 98           autoLoad: false,  //设置第二级不自动加载 99           proxy: {100             type: 'ajax',101             url: 'student?method=getClazzList',102             reader: {103               type: 'json',104               root: 'content'105             }106           }107         })108       });109     });110 111   </script>112 </head>113 <body>114 115 </body>116 </html>

 

上面的代码还有一个问题就是每次都会从服务端加载班级列表,会消耗服务端资源,这对于大型系统来说还是应该优化下的,于是我将数据加载到本地,每次用的时候就去取,整个过程只会向服务端发送两次请求。注意看注释部分!

 1 <%-- 2   3  @author bojiangzhou 4  @date 2016/8/7 5 --%> 6 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 7 <%@ page isELIgnored="false" %> 8 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 9 <html> 10 <head> 11   <title>Combobox</title> 12   <link rel="stylesheet" href="js/ext/resources/css/ext-all.css" /> 13   <script type="text/javascript" src="js/ext/ext-all.js"></script> 14   <script> 15     Ext.onReady(function () { 16  17       var clazzList = {}; //班级列表 18  19       /** 20        * 年级列表 21       */ 22       Ext.create('Ext.form.ComboBox', { 23         renderTo: Ext.getBody(), 24         id: 'gradeId', 25         displayField: 'name', 26         valueField: 'id', 27         editable: false, 28         readonly: true, 29         allowBlank: true, 30         fieldLabel: '选择年级', 31         margin: '50 10 0 0', 32         labelAlign: 'right', 33         queryMode: 'local',   //本地查询,配置这个属性,在第一次点击下拉列表的时候就不会从服务端加载数据了 34         triggerAction: 'all', 35         store: Ext.create('Ext.data.JsonStore', {  //Store数据仓库 36           fields: [ 37             {name: 'id'}, 38             {name: 'name'} 39           ], 40           autoLoad: true,   //第一级列表设置自动加载 41           proxy: {      //通过ajax代理加载数据 42             type: 'ajax', 43             url: 'student?method=getGradeList', 44             reader: { 45               type: 'json', 46               root: 'ret' 47             } 48           }, 49           listeners: {    //注意是store的监听器 50             'load': function (store, gRecords) { //store的load事件 51  52               //在Store的load事件中,加载班级的数据,返回成功后进行一些处理 53               Ext.getCmp("clazzId").getStore().load({ 54                 callback: function(records, operation, success) {  //加载成功返回后调用的函数 55                   //将年级全部加载出来放到全局中 56                   for(var i = 0;i < records.length;i++){ 57                     var gid = records[i].data['gid'];  //获取班级所属的年级id 58                     if(!clazzList[gid]){ 59                       clazzList[gid] = [];  //数组用于存放班级 60                       clazzList[gid].push({id:0, name: '所有'});  //添加一个所有选项 61                     } 62  63                     clazzList[gid].push(records[i]);  //将record添加到该年级的数组下 64                   } 65  66                   //要先加载后在设置默认值,由于异步加载,change事件可能会不起作用。 67                   //设置年级的第一个值为默认值 68                   Ext.getCmp("gradeId").setValue(gRecords[0]);  //注意是外部的gRecords 69                 } 70               }); 71             } 72           } 73         }), 74         listeners: {    //这是ComboBox的监听器 75           'change': function(o, nv){ //change事件 76             if(nv){ 77               var clazzId = Ext.getCmp("clazzId"); 78               clazzId.getStore().removeAll();// 清空已加载列表 79               clazzId.reset();// 清空已存在结果 80  81               if(clazzList[nv]){ 82                 //发生change事件后,从班级列表中取出该年级下的班级添加到班级store中 83                 clazzId.getStore().insert(0,clazzList[nv]); 84                 clazzId.setValue(0);  //设置第一个值默认,即"所有" 85               } 86             } 87           } 88         } 89  90       }); 91  92       /** 93        * 班级列表 94       */ 95       Ext.create('Ext.form.ComboBox', { 96         renderTo: Ext.getBody(), 97         id: 'clazzId', 98         displayField: 'name', 99         valueField: 'id',100         editable: false,101         readonly: true,102         allowBlank: true,103         fieldLabel: '选择年级',104         margin: '50 10 0 0',105         labelAlign: 'right',106         triggerAction: 'all',107         queryMode: 'local',   //本地加载模式108         store: Ext.create('Ext.data.JsonStore', {  //Store数据仓库109           fields: [110             {name: 'id'},111             {name: 'gid'},112             {name: 'name'}113           ],114           autoLoad: false,  //设置第二级不自动加载115           proxy: {116             type: 'ajax',117             url: 'student?method=getClazzList',118             reader: {119               type: 'json',120               root: 'content'121             }122           }123         })124       });125 126 127     });128 129   </script>130 </head>131 <body>132 133 </body>134 </html>

 

初学ExtJs,以上纯属个人见解,若有不当之处,还请指出O(∩_∩)O

OK!!!