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             //年级id
107             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>

 

三、再说说triggerAction

下面是文档对triggerAction的说明,刚开始不怎么明白这个属性的用途,只知道设置为all的时候能查询出数据来,设置成query的时候就查不出来了....

后来看到一本书上的例子才对它的用法理解了,triggerAction一般来说会和allQuery、queryParam两个属性配合使用,而且一般combobox是可编辑的,这几个参数是用于输入查询的。

在triggerAction:'all'的时候,点击下拉列表的时候会根据allQuery的值查询所有相关的数据,queryParam和allQuery可以理解成键和值关系。

比如配置:queryParam:'grade', allQuery:'一年级', triggerAction:'all',点击下拉列表时,注意是点击下拉列表的时候,就会向后台请求,并带上参数:grade='一年级',然后后台就可以根据这组参数查询该年级下的班级返回来。

如果你在combo中输入值,且配置了minChars,比如:minChars:3,则在你输入的字符数大于3的时候就会自动向后台发送请求,并带上参数:grade='你输入的值',然后查询。

设置triggerAction:'query'的时候,在点击下拉列表的时候,发送的参数就是grade='你输入的值',如果没有输入,相当于发送grade=''。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM