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