刚接触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. FileItem类的常用方法

    FileItem类的常用方法: 1.  boolean isFormField() isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果 ...

  2. webstorm增加less开发插件

    原文  http://a317222029201405212739.iteye.com/blog/2174140 引自 http://www.tuicool.com/articles/aeye6rY ...

  3. ADB server didn't ACK * failed to start daemon *

    问题描述:在eclipse的Logcat出现错误 [2014-01-08 14:00:07 - adb] ADB server didn't ACK [2014-01-08 14:00:07 - ad ...

  4. 一个ubuntu phper的自我修养(atom)

    将atom打造成二十一世纪最装那啥的php IDE 之前在windows平台使用的php IDE一直是eclipse for php,因为之前做java开发,所以对eclipse很有感情,debug. ...

  5. cf 710 E Generate a String

    题意: 开始你有数字$0$,你可以用代价$x$将该数字加$1$或减$1$(当$x > 0$时),或用代价$y$将该数字变为$2x$,那么问得到数字$n$所需的最少代价是多少. 数据范围$1 \l ...

  6. 注释声明:TODO HACK XXX FIXME REVIEW

    注释有时候也可以用来给一段代码声明额外的信息.这些声明的格式以单个单词打头并紧跟一个冒号.可以使用的声明如下. TODO: 说明代码还未完成.应当包含下一步要做的事情. HACK: 表明代码实现走了一 ...

  7. 第一代intel核显id:0046的10.9驱动安装详解(转)

    一代0046 intel核显hd1000m 10.8的驱动已经失效了,开不了QE/CI的 从tonymac找来的驱动,并完善一下 直接上驱动啦 安装步骤务必按照顺序进行,不然是驱动不起来的 第一步:首 ...

  8. VS2013 带命令行参数的调试问题 解决方案

    int main(int argc,char* argv[]) argc是命令行总的参数个数,argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数命令行后面跟的用户输入的参数 比如:  ...

  9. background-position (转)

    http://blog.csdn.net/JeamKing/article/details/5617088   注:这是别人博客链接地址  具体效果图片可以查看此链接 语法:background-po ...

  10. iOS textField输入金额的限制,小数点前9位,后面两位

    iOS textField输入金额的限制,小数点前9位,后面两位,如果不加小数点,最大位数是9位,加上小数点,最大位数是12位,超出最大位数可删除 - (BOOL)textField:(UITextF ...