公司的项目,用到了ExtJS3,而其原生的ComboBox只能选择列表,不能操作树,如果把部门-员工这样的层级结构用ComboBox来展示确实太不直观了。 好在之前在网上搜集了一些关于实现下拉数的资料,但是,貌似都不太适合自己的需求。
结合网上的资料,搞了又重复的造了一个下拉树,加上对ComboBox本身的覆盖重载,可以实现同时获取隐藏域的值(id)和可见域的值(name)。
先说下,对ComboBox的重载。
Ext.override(Ext.form.ComboBox, {
putValue : function(text, value) {
try {
var combo = this;
var displayField = combo.displayField || 'name';
var valueField = combo.valueField || 'id';
var index = combo.store.findExact(valueField, value);
if (index == -1) {
var r = new combo.store.recordType({});
r.set(valueField, value);
r.set(displayField, text);
combo.store.add(r);
} else {
var r = combo.store.getAt(index);
if (r.get(displayField) != text) {
r.set(displayField, text);
}
}
combo.setValue(value);
} catch (err) {
alert(err);
}
},
onRender : function(ct, position) {
if (this.hiddenName && !Ext.isDefined(this.submitValue)) {
this.submitValue = false;
}
Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
if (this.hiddenName) {
this.hiddenField = this.el.insertSibling({
tag : 'input',
type : 'hidden',
name : this.hiddenName,
id : (this.hiddenId || Ext.id())
}, 'before', true);
}
if (this.visibleName) {
this.el.set({
name : this.visibleName
});
}
if (Ext.isGecko) {
this.el.dom.setAttribute('autocomplete', 'off');
}
if (!this.lazyInit) {
this.initList();
} else {
this.on('focus', this.initList, this, {
single : true
});
}
}
});
这里新增加了一个“putValue”方法,用于在“编辑”表单上,loadRecord之后,调用本方法给ComboBox赋值。 因为通常情况下,针对远程的ComboBox,直接loadRecord之后显示的是一串id值。
另外,我给ComboBox还加了一个“visibleName”的属性,用来取ComboBox的displayField对应的值,也就是可见的那个值,习惯性以“**Name”来命名,例如”employeeName”,valueField对应的hiddenName通常命名成”employeeId”。
好,背景情况大概就是这样了。
/**
* 下拉树,支持ComboBox和TreePanel的大部分配置参数,<br>
* 另新增两个配置参数:sm和autoCollpase,其中sm为SelectionModel的简写,支持:'all','folder','leaf';<br>
* autoCollapse,布尔值,是否在ComboBox关闭的时候自动关闭树<br>
* 特点是可以给树设定最小高度,自动高度,无滚动条支持。
*
* @class Ext.ux.TreeComboBox
* @extends Ext.form.ComboBox
* @author xiaosilent<xiaosilent@gmail.com>
*/
Ext.ux.TreeComboBox = Ext.extend(Ext.form.ComboBox, {
folderText : '',
folderChar : '',
checkable : false,
editable : true,
sm : 'all',
mode : 'local',
triggerAction : 'all',
displayField : 'name',
valueField : 'id',
minHeight : 180,
rootVisible : false,
autoCollapse : false,
store : new Ext.data.SimpleStore({
fields : ['id', 'name'],
data : [[]]
}),
initList : function() {
if (this.list) {
return;
}
this.list = new Ext.Layer({
cls : 'x-combo-list',
constrain : true
});
this.list.setWidth(Math.max(this.wrap.getWidth(), 100));
this.view = new Ext.DataView({});
var combo = this;
combo.tree = new Ext.tree.TreePanel({
border : false,
root : this.root || new Ext.tree.AsyncTreeNode({
text : '全部',
id : '0'
}),
loader : this.loader,
renderTo : this.list.id,
autoScroll : this.autoScroll || true,
height : this.height,
rootVisible : this.rootVisible || false,
bodyCfg : {
style : 'background-color:#ffffff; height:' + this.minHeight + 'px; min-height:' + this.minHeight + 'px;'
},
tbar : [{
text : '展开',
handler : function() {
combo.tree.expandAll();
}
}, '-'],
listeners : {
'click' : function(node) {
if (node.isLeaf()) {
if (combo.sm == 'folder') {
return;
}
} else {
if (combo.sm == 'leaf') {
return;
}
}
if (typeof(node.attributes.checked) == 'undefined') {
var tttext, iiid;
if (!node.isLeaf() && combo.folderText.length > 0) {
tttext = '[' + combo.folderText + ']' + node.text;
} else {
tttext = node.attributes.parentsText ? node.attributes.parentsText : node.text;
}
if (!node.isLeaf() && combo.folderChar.length > 0) {
iiid = combo.folderChar + '_' + node.id;
} else {
iiid = node.id;
}
combo.putValue(tttext, iiid);
combo.collapse();
combo.fireEvent('select', combo, node);
combo.el.dom.focus();
} else {
node.ui.toggleCheck();
}
}
}
});
var tbar = combo.tree.getTopToolbar();
if (combo.tree.loader.baseParams.check == true) {
tbar.add({
text : '全选',
handler : function() {
combo.tree.root.cascade(function() {
this.ui.toggleCheck(true);
});
tbar.items.get(5).handler.call(this);
}
});
}
tbar.add({
text : '清空',
handler : function() {
combo.tree.root.cascade(function() {
this.ui.toggleCheck(false);
});
combo.clearValue();
combo.collapse();
}
});
if (combo.tree.loader.baseParams.check == true) {
tbar.add('-', {
text : '确认',
handler : function() {
if (combo.tree.loader.baseParams.check == true) {
var checked = combo.tree.getChecked();
var names = "", ids = "";
for (var i = 0; i < checked.length; i++) {
var node = checked[i];
if (!node.isLeaf() && combo.folderText.length > 0) {
names += "[" + combo.folderText + "]" + node.text + ",";
} else {
names += node.text + ",";
}
if (!node.isLeaf() && combo.folderChar.length > 0) {
ids += combo.folderChar + "_" + node.id + ",";
} else {
ids += node.id + ",";
}
}
if (names.length > 1) {
names = names.substring(0, names.length - 1);
}
if (ids.length > 1) {
ids = ids.substring(0, ids.length - 1);
}
combo.putValue(names, ids);
combo.setValue(ids);
combo.fireEvent('select', combo);
combo.fireEvent('change', combo, ids);
}
combo.collapse();
}
});
}
tbar.doLayout();
this.tree.root.on('load', function(node) {
if (combo.autoSelect && node.childNodes.length == 1) {
function selectFirstNode() {
node.childNodes[0].select();
combo.tree.fireEvent('click', node.childNodes[0]);
node.un('expand', selectFirstNode);
}
node.on('expand', selectFirstNode);
}
});
this.innerList = this.list.createChild();
},
onKeyUp : function(e) {
var k = e.getKey();
if (this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())) {
this.lastKey = k;
this.dqTask.delay(500);
// 将这里强制修改成500,而不是this.queryDelay,因为mode是指定的local啊~~~
}
Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
},
doQuery : function(q, forceAll) {
q = Ext.isEmpty(q) ? '' : q;
var qe = {
query : q,
forceAll : forceAll,
combo : this,
cancel : false
};
if (this.fireEvent('beforequery', qe) === false || qe.cancel) {
return false;
}
q = qe.query;
forceAll = qe.forceAll;
if (q.length >= this.minChars) {
if (this.lastQuery != q) {
this.lastQuery = q;
if (q.length >= this.minChars) {
combo = qe.combo;
combo.tree.loader.baseParams.query = q;
combo.tree.root.reload();
}
this.expand();
} else {
this.selectedIndex = -1;
this.onLoad();
}
}
},
collapse : function() {
if (this.autoCollapse) {
this.tree.collapseAll();
}
Ext.ux.TreeComboBox.superclass.collapse.call(this);
}
});
Ext.reg('treecombo', Ext.ux.TreeComboBox);
这里有几个约定
1、就是这对loader的baseParams,如果里面有:check:true,那么就认为是一个带复选框的树形结构,那么,tbar上就增加了“全选”和“确认”两个按钮。这里要求loader对应的url能够识别check这个参数,并在check=true的时候,返回的JSON上,带“checked”属性。
当然我实际应用的时候,baseParams里还会加上checked参数,提供一个“默认选中”的id列表,url可以识别并把这些id对应的node的checked属性设置成true,展开树时就默认选上了。
2、关于sm的约定:这只有在baseParams里面没有check:true参数时才有效。 默认sm是’all’,表示leaf和folder节点都可以被选中,当被设置成’leaf’或’folder’之后,则只有对应的节点才可以被选中。 比如我们在选择员工的时候,按照部门-员工的层级结构展开,把sm设置成’leaf’就表示只能选择员工而不能选择部门啦。