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

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

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

遇到的问题:

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

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

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

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

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

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

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

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

 package com.lizhou.bms.controller;

 import com.lizhou.bms.entity.Clazz;
import com.lizhou.bms.entity.Grade;
import net.sf.json.JSONArray; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; /**
* 模拟获取数据
* @author bojiangzhou
* @date 2016/8/8
*/
public class StudentController extends HttpServlet { private static List<Grade> gradeList = new LinkedList<Grade>(); private static List<Clazz> clazzList = new LinkedList<Clazz>(); /**
* 数据源
*/
static {
//年级
Grade g1 = new Grade(1, "一年级");
Grade g2 = new Grade(2, "二年级");
Grade g3 = new Grade(3, "三年级"); gradeList.add(g1);
gradeList.add(g2);
gradeList.add(g3); //班级
Clazz g1c1 = new Clazz(1, 1, "一年级 1班");
Clazz g1c2 = new Clazz(2, 1, "一年级 2班");
Clazz g1c3 = new Clazz(3, 1, "一年级 3班");
Clazz g1c4 = new Clazz(4, 1, "一年级 4班");
Clazz g1c5 = new Clazz(5, 1, "一年级 5班");
Clazz g1c6 = new Clazz(6, 1, "一年级 6班");
Clazz g1c7 = new Clazz(7, 1, "一年级 7班"); Clazz g2c1 = new Clazz(8, 2, "二年级 1班");
Clazz g2c2 = new Clazz(9, 2, "二年级 2班");
Clazz g2c3 = new Clazz(10, 2, "二年级 3班");
Clazz g2c4 = new Clazz(11, 2, "二年级 4班");
Clazz g2c5 = new Clazz(12, 2, "二年级 5班");
Clazz g2c6 = new Clazz(13, 2, "二年级 6班");
Clazz g2c7 = new Clazz(14, 2, "二年级 7班"); Clazz g3c1 = new Clazz(15, 3, "三年级 1班");
Clazz g3c2 = new Clazz(16, 3, "三年级 2班");
Clazz g3c3 = new Clazz(17, 3, "三年级 3班");
Clazz g3c4 = new Clazz(18, 3, "三年级 4班");
Clazz g3c5 = new Clazz(19, 3, "三年级 5班");
Clazz g3c6 = new Clazz(20, 3, "三年级 6班");
Clazz g3c7 = new Clazz(21, 3, "三年级 7班"); clazzList.add(g1c1);
clazzList.add(g1c2);
clazzList.add(g1c3);
clazzList.add(g1c4);
clazzList.add(g1c5);
clazzList.add(g1c6);
clazzList.add(g1c7); clazzList.add(g2c1);
clazzList.add(g2c2);
clazzList.add(g2c3);
clazzList.add(g2c4);
clazzList.add(g2c5);
clazzList.add(g2c6);
clazzList.add(g2c7); clazzList.add(g3c1);
clazzList.add(g3c2);
clazzList.add(g3c3);
clazzList.add(g3c4);
clazzList.add(g3c5);
clazzList.add(g3c6);
clazzList.add(g3c7); } @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //前台传一个method参数,getGradeList即请求获取年级列表,getClazzList即请求获取班级列表
String method = request.getParameter("method"); response.setCharacterEncoding("UTF-8"); if("getGradeList".equals(method)){
JSONArray jsonArray = JSONArray.fromObject(gradeList);
String ret = jsonArray.toString();
response.getWriter().write(ret); } else if("getClazzList".equals(method)){
List<Clazz> clist = new ArrayList<Clazz>();
//年级id
String sgid = request.getParameter("gid");
if(sgid != null){
int gid = Integer.parseInt(sgid); for(Clazz c : clazzList){
if(c.getGid() == gid){
clist.add(c);
}
}
} else{
clist.addAll(clazzList);
}
JSONArray jsonArray = JSONArray.fromObject(clist);
String ret = jsonArray.toString();
response.getWriter().write(ret); }
}
}

然后是最初版的JS代码:

 <%--

   @author bojiangzhou
@date 2016/8/8
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Combobox</title>
<link rel="stylesheet" href="js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="js/ext/ext-all.js"></script>
<script>
Ext.onReady(function () { /**
* 创建年级Combo
*/
Ext.create('Ext.form.ComboBox', {
renderTo: Ext.getBody(),
id: 'gradeId',
displayField: 'name',
valueField: 'id',
editable: false,
readonly: true,
allowBlank: true,
fieldLabel: '选择年级',
margin: '50 10 0 0',
labelAlign: 'right',
triggerAction: 'all', //点击下拉列表时执行的操作
queryMode: 'remote', //store的查询模式
store: Ext.create('Ext.data.JsonStore', {
fields: [
{name: 'id'},
{name: 'name'}
],
autoLoad: true, //启动自动加载
proxy: { //通过ajax代理加载数据
type: 'ajax',
url: 'student?method=getGradeList',
reader: {
type: 'json',
root: 'content'
}
}
}),
listeners: {
'change': function(o, gid){ //change事件
if(gid){
var clazzId = Ext.getCmp("clazzId"); //获取Clazz Combo组件
clazzId.getStore().removeAll(); // 清空已加载列表
clazzId.reset(); // 清空已存在结果 //发生change事件后将年级id传到后台获取该年级下的班级
clazzId.getStore().load({
params: {'gid': gid}
});
}
}
} }); /**
* 创建班级Combo
*/
Ext.create('Ext.form.ComboBox', {
renderTo: Ext.getBody(),
id: 'clazzId',
displayField: 'name',
valueField: 'id',
editable: false,
readonly: true,
allowBlank: true,
fieldLabel: '选择班级',
margin: '50 10 0 0',
labelAlign: 'right',
triggerAction: 'all', //点击下拉列表时执行的操作
queryMode: 'remote', //store的查询模式
store: Ext.create('Ext.data.JsonStore', {
fields: [
{name: 'id'},
{name: 'gid'},
{name: 'name'}
],
autoLoad: true, //启动自动加载
proxy: { //通过ajax代理加载数据
type: 'ajax',
url: 'student?method=getClazzList',
reader: {
type: 'json',
root: 'content'
}
}
})
}); }); </script>
</head>
<body> </body>
</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,再才显示第一个选项的,尤其在加载比较慢的时候就很明显了。所以这两种方式不可取。

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

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

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

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

 <%--

   @author bojiangzhou
@date 2016/8/7
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Combobox</title>
<link rel="stylesheet" href="js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="js/ext/ext-all.js"></script>
<script>
Ext.onReady(function () { /**
* 年级列表
*/
Ext.create('Ext.form.ComboBox', {
renderTo: Ext.getBody(),
id: 'gradeId',
displayField: 'name',
valueField: 'id',
editable: false,
readonly: true,
allowBlank: true,
fieldLabel: '选择年级级',
margin: '50 10 0 0',
labelAlign: 'right',
queryMode: 'local', //本地查询,配置这个属性,在第一次点击下拉列表的时候就不会从服务端加载数据了
triggerAction: 'all',
store: Ext.create('Ext.data.JsonStore', { //Store数据仓库
fields: [
{name: 'id'},
{name: 'name'}
],
autoLoad: true, //第一级列表设置自动加载
proxy: { //通过ajax代理加载数据
type: 'ajax',
url: 'student?method=getGradeList',
reader: {
type: 'json',
root: 'ret'
}
},
listeners: { //注意是store的监听器
'load': function (store, records) { //store的load事件
//设置第一个值为默认值
Ext.getCmp("gradeId").setValue(records[0]);
}
}
}),
listeners: { //这是ComboBox的监听器
'change': function(o, nv){ //change事件
if(nv){
var clazzId = Ext.getCmp("clazzId");
clazzId.getStore().removeAll();// 清空已加载列表
clazzId.reset();// 清空已存在结果 //在年级列表发生改变时将年级ID传到后台,加载该年级下的班级,
//但是每次改变年级时都会从服务器加载,有点消耗服务器资源
clazzId.getStore().load({
params: {'gid': nv}, //参数
callback: function(records, operation, success) { //加载完成调用的函数
//添加一个所有选项
clazzId.getStore().insert(0, {id: 0, name: '所有' });
clazzId.setValue(0); //设置默认第一个
}
});
}
}
} }); /**
* 班级列表
*/
Ext.create('Ext.form.ComboBox', {
renderTo: Ext.getBody(),
id: 'clazzId',
displayField: 'name',
valueField: 'id',
editable: false,
readonly: true,
allowBlank: true,
fieldLabel: '选择年级',
margin: '50 10 0 0',
labelAlign: 'right',
triggerAction: 'all',
queryMode: 'local', //本地加载模式
store: Ext.create('Ext.data.JsonStore', { //Store数据仓库
fields: [
{name: 'id'},
{name: 'name'}
],
autoLoad: false, //设置第二级不自动加载
proxy: {
type: 'ajax',
url: 'student?method=getClazzList',
reader: {
type: 'json',
root: 'content'
}
}
})
});
}); </script>
</head>
<body> </body>
</html>

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

 <%--

   @author bojiangzhou
@date 2016/8/7
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Combobox</title>
<link rel="stylesheet" href="js/ext/resources/css/ext-all.css" />
<script type="text/javascript" src="js/ext/ext-all.js"></script>
<script>
Ext.onReady(function () { var clazzList = {}; //班级列表 /**
* 年级列表
*/
Ext.create('Ext.form.ComboBox', {
renderTo: Ext.getBody(),
id: 'gradeId',
displayField: 'name',
valueField: 'id',
editable: false,
readonly: true,
allowBlank: true,
fieldLabel: '选择年级',
margin: '50 10 0 0',
labelAlign: 'right',
queryMode: 'local', //本地查询,配置这个属性,在第一次点击下拉列表的时候就不会从服务端加载数据了
triggerAction: 'all',
store: Ext.create('Ext.data.JsonStore', { //Store数据仓库
fields: [
{name: 'id'},
{name: 'name'}
],
autoLoad: true, //第一级列表设置自动加载
proxy: { //通过ajax代理加载数据
type: 'ajax',
url: 'student?method=getGradeList',
reader: {
type: 'json',
root: 'ret'
}
},
listeners: { //注意是store的监听器
'load': function (store, gRecords) { //store的load事件 //在Store的load事件中,加载班级的数据,返回成功后进行一些处理
Ext.getCmp("clazzId").getStore().load({
callback: function(records, operation, success) { //加载成功返回后调用的函数
//将年级全部加载出来放到全局中
for(var i = 0;i < records.length;i++){
var gid = records[i].data['gid']; //获取班级所属的年级id
if(!clazzList[gid]){
clazzList[gid] = []; //数组用于存放班级
clazzList[gid].push({id:0, name: '所有'}); //添加一个所有选项
} clazzList[gid].push(records[i]); //将record添加到该年级的数组下
} //要先加载后在设置默认值,由于异步加载,change事件可能会不起作用。
//设置年级的第一个值为默认值
Ext.getCmp("gradeId").setValue(gRecords[0]); //注意是外部的gRecords
}
});
}
}
}),
listeners: { //这是ComboBox的监听器
'change': function(o, nv){ //change事件
if(nv){
var clazzId = Ext.getCmp("clazzId");
clazzId.getStore().removeAll();// 清空已加载列表
clazzId.reset();// 清空已存在结果 if(clazzList[nv]){
//发生change事件后,从班级列表中取出该年级下的班级添加到班级store中
clazzId.getStore().insert(0,clazzList[nv]);
clazzId.setValue(0); //设置第一个值默认,即"所有"
}
}
}
} }); /**
* 班级列表
*/
Ext.create('Ext.form.ComboBox', {
renderTo: Ext.getBody(),
id: 'clazzId',
displayField: 'name',
valueField: 'id',
editable: false,
readonly: true,
allowBlank: true,
fieldLabel: '选择年级',
margin: '50 10 0 0',
labelAlign: 'right',
triggerAction: 'all',
queryMode: 'local', //本地加载模式
store: Ext.create('Ext.data.JsonStore', { //Store数据仓库
fields: [
{name: 'id'},
{name: 'gid'},
{name: 'name'}
],
autoLoad: false, //设置第二级不自动加载
proxy: {
type: 'ajax',
url: 'student?method=getClazzList',
reader: {
type: 'json',
root: 'content'
}
}
})
}); }); </script>
</head>
<body> </body>
</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=''。

ExtJs 之 ComboBox级联使用的更多相关文章

  1. jquery easyui combobox 级联及触发事件,combobox级联

    jquery easyui combobox 级联及触发事件,combobox级联 >>>>>>>>>>>>>>&g ...

  2. Extjs 让combobox写起来更简单

    也已经写了很久时间的extjs ,每次都用到很多的combobox,配置很多东西觉得实在是太麻烦,所以根据常用到的情况写了一个简便的combobox,再次记录下来,以免放在某个地方忘记了找不到了. 定 ...

  3. easyui combobox级联(转载)

    一.创建combobox 有如下几种方式可以创建一个combobox 1.使用select标签,并加上class="easyui-combobox",这种方式比较适用于静态的选项. ...

  4. jQuery easyui combobox级联及内容联想

    1.需求:已有一个下拉框A表示地区,现新增需求,需要在A选择不同地区时,增加一个展示该地区所有城市的下拉框B, 由于城市较多,要求B能实现用户输入和模糊匹配展示功能. 2.实现: (1)首先在A下面把 ...

  5. Devexpress GridControl中combobox级联显示 z

    http://minmin86121.blog.163.com/blog/static/4968115720143163533356/ 在 使用GridControl时,可能会有需求要求某2列显示co ...

  6. Extjs之combobox联动

    Ext.Loader.setConfig({ enabled : true }); Ext.Loader.setPath('Ext.ux', '../extjs/ux'); Ext.require([ ...

  7. WPF MVVM模式下ComboBox级联效果 选择第一项

    MVVM模式下做的省市区的级联效果.通过改变ComboBox执行命令改变市,区. 解决主要问题就是默认选中第一项 1.首先要定义一个属性,继承自INotifyPropertyChanged接口.我这里 ...

  8. combobox级联检索下拉选择框

    1.效果图 2.前端 @{ ViewBag.Title = "Index"; Layout = null; @*自动筛选下拉框*@ <script src="~/S ...

  9. Extjs 中combobox下拉框初始化赋值

    近日在工作中遇到一个需求,要求页面初始化的时候给dataGrid表插入一条数据. 前端使用的是Extjs框架,dataGrid表有四列,其中三列是类型为textbox,普通文本框,另外一列类型是com ...

随机推荐

  1. [课程设计]Scrum 3.7 多鱼点餐系统开发进度(留言板选择方案)

    Scrum 3.7 多鱼点餐系统开发进度(留言板选择方案) 1.团队名称:重案组 2.团队目标:长期经营,积累客户充分准备,伺机而行 3.团队口号:矢志不渝,追求完美 4.团队选题:餐厅到店点餐系统W ...

  2. JSP实现 乘法口诀输出

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  3. The Bottom of a Graph-POJ2553强连通

    The Bottom of a Graph Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 9759 Accepted: 4053 ...

  4. Android Fragment 真正的完全解析(下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和 ...

  5. Reportng报告替代testng

    1,工程导入jar包

  6. Links for Introduction To Calculus

    The links to download the material for the course Introduction To Calculus are provided in the follo ...

  7. System Setup

    vistual studio 2013 download: http://www.microsoft.com/en-us/download/details.aspx?id=48138 http://w ...

  8. 24.Redis2.8主从集群sentinel

    0.集群架构(此处只说两种;本文2种,避免sentinel成为单节点) 第一种: 第二种: 1.下载redis2.8.x版本,2.8.x都是稳定版 redis-2.8.24.tar.gz 2.解压,安 ...

  9. SQL函数

    1,字符串截取拼接 CONCAT(),'****');SUBSTRING_INDEX(c.context,'}',1);SUBSTRING_INDEX(a.task_context,':',-1) a ...

  10. 基于Web的制造追溯系统DEMO

    写在前面 本文不贴任何代码,以图片为主,也许图片更直观,请各位园友谅解! 很久没有写过博客了,这是2016年的第一篇文章:最近主要是忙着完成公司的一个新项目,逛园子的时间都明显少了,只有下班回到家睡觉 ...