代码贴出来如下,没什么好说的。具体看后面的如何应用。
Ext.ns('Ext.ux.form');
/**
* customized field for ComboBox dropdown tree
*
* @class Ext.ux.form.ComboBoxTree
* @extends Ext.form.TwinTriggerField
*/
Ext.ux.form.ComboBoxTree = Ext.extend(Ext.form.TwinTriggerField,{
//list dropdown Fx,boolean or object
animate : {easing:'easeIn',duration:0.75},
defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"},
editable : false,
//true to display the clear value trigger, default to false
enableClearValue : false,
//this hiddenField's init value if exist
hiddenValue : '',
//this field's display init value
displayValue : '',
//dropdown list's width,default to this field's width
listWidth : undefined,
//dropdown list's height,if tree's height was speciallized ignore this
listHeight : undefined,
maxListHeight : 225,
minListWidth : 70,
handleHeight : 8,
listClass : '',
selectedClass : 'x-combo-selected',
shadow : 'sides',
listAlign : 'tl-bl?',
listEmptyText : '',
resizable : false,
trigger1Class : 'x-form-clear-trigger',
hideTrigger1 : true,
trigger2Class : 'x-form-arrow-trigger',
initComponent : function(){
this.addEvents(
/**
* @event expand
* Fires when the dropdown list is expanded
* @param {Ext.ux.form.ComboBoxTree}
*/
'expand',
/**
* @event collapse
* Fires when the dropdown list is collapsed
* @param {Ext.ux.form.ComboBoxTree}
*/
'collapse',
/**
* @event treenodeselect
* Fires when the tree node is selected
* @param {Ext.ux.form.ComboBoxTree}
* @param {Ext.tree.TreeNode}
* @param {Ext.EventObject}
*/
'treenodeselect',
/**
* @event beforecollapse
* Fires when the dropdown list before collapsed
* @param {Ext.ux.form.ComboBoxTree}
*/
'beforecollapse',
/**
* @event beforeexpand
* Fires when the dropdown list before expanded
* @param {Ext.ux.form.ComboBoxTree}
*/
'beforeexpand',
/**
* @event clearvalue
* Fires when the clear value trigger is clicked
* @param {Ext.ux.form.ComboBoxTree}
* @param {String} the value will be cleared
*/
'clearvalue'
);
Ext.ux.form.ComboBoxTree.superclass.initComponent.call(this);
//user both do not specialized treePanel'height and comboboxtree's listHeight,it's means dropdown list auto fit height
this.tree.height = Ext.isDefined(this.tree.height) ? this.tree.height : this.listHeight;
//disabled autoHeight
this.tree.autoHeight = false;
//always autoScroll = true
this.tree.autoScroll = true;
this.tree.containerScroll = true;
this.tree.border = false;
//if this.tree.height == undefined ,force to expand tree'root if user do not specialized expanded = true
this.tree.root.expanded = !Ext.isDefined(this.tree.height) ? true : this.tree.root.expanded;
this.tplId = Ext.id();
this.tpl = '<div id="'+this.tplId+'"></div>';
this.onTrigger2Click = this.onTriggerClick;
this.onTrigger1Click = this.clearValue;
},
//private
onRender : function(ct, position){
if(this.hiddenName && !Ext.isDefined(this.submitValue)){
this.submitValue = false;
}
Ext.ux.form.ComboBoxTree.superclass.onRender.call(this,ct,position);
if(this.hiddenName){
var formItem = this.el.findParent('.x-form-item',4,true);
if(formItem){
//create a Ext.form.Hidden field as a sibling of this, in order to Ext.form.BasicForm.load(..)
this.hiddenField = formItem.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
id: (this.hiddenId||this.hiddenName)}, 'after', true);
var hf = new Ext.form.Hidden({applyTo:this.hiddenField});
var basicForm = this.getOwnerForm(this);
if(basicForm){
basicForm.add(hf);
}
}else{
this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
id: (this.hiddenId||this.hiddenName)}, 'before', true);
}
}
if(Ext.isGecko){
this.el.dom.setAttribute('autocomplete', 'off');
}
this.initList();
},
//private
initList : function(){
if(!this.list){
var cls = 'x-combo-list',
listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
if (!zindex) {
zindex = this.getParentZIndex();
}
this.list = new Ext.Layer({
parentEl: listParent,
shadow: this.shadow,
cls: [cls, this.listClass].join(' '),
constrain:false,
zindex: (zindex || 12000) + 5
});
var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
this.list.setSize(lw, 0);
//specialized the tree's width for scroll -x
this.tree.width = lw - 1;
this.list.swallowEvent('mousewheel');
this.assetHeight = 0;
if(this.syncFont !== false){
this.list.setStyle('font-size', this.el.getStyle('font-size'));
}
if(this.title){
this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
this.assetHeight += this.header.getHeight();
}
this.innerList = this.list.createChild({cls:cls+'-inner'});
this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
this.view = new Ext.DataView({
applyTo: this.innerList,
tpl: this.tpl,
singleSelect: true,
selectedClass: this.selectedClass,
itemSelector: this.itemSelector || '.' + cls + '-item',
emptyText: this.listEmptyText,
deferEmptyText: false,
listeners: {
'afterrender' : function(){
this.tpl.overwrite(this.el,[]);
}
}
});
this.mon(this.view, {
containerclick : this.onViewClick,
click : this.onViewClick,
scope :this
});
if(this.resizable){
this.resizer = new Ext.Resizable(this.list, {
pinned:true, handles:'se'
});
this.mon(this.resizer, 'resize', function(r, w, h){
this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
this.listWidth = w;
this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
this.restrictHeight();
}, this);
}
}
},
//private
initEvents : function(){
Ext.ux.form.ComboBoxTree.superclass.initEvents.call(this);
this.tree.on({
scope:this,
'render' : function(){
this.tree.getTreeEl().on('click',function(e,t,o){
if(e.getTarget('.x-tree-ec-icon', 1)){//is click tree's collapse/expand trigger icon area?
this.isTreeNodeClicked = false;
}else{
this.isTreeNodeClicked = true;
}
},this);
},
'click' : function(n,e){
if(this.enableClearValue){
this.triggers[0].show();
}
this.setValue(n.text);
if(this.hiddenField){
this.setHiddenValue(Ext.value(n.id,''));
}
this.fireEvent('treenodeselect',this,n,e);
}
});
this.tree.root.on({
scope:this,
'expand' : {
fn : function(){
if(!Ext.isDefined(this.tree.height)){//dropdown list auto height
this.restrictHeight();
}
},
single: true
}
});
this.on({
'beforecollapse' : {
fn : function() {
if(!this.isExpanded()){
return;
}
return this.isTreeNodeClicked;
}
},
'beforedestroy' : {
fn : function() {
this.purgeListeners();
this.tree.purgeListeners();
}
},
'beforeexpand' : {
fn : function() {
if(this.tree.rendered && !this.tree.getRootNode().isExpanded()){
this.tree.getRootNode().expand(false,true);
}
},
single : true
},
'focus' : {
fn : function(){
if (!this.tree.rendered && this.tplId) {
this.tree.render(this.tplId);
}
},
single : true
}
});
},
// private
initValue : function(){
Ext.ux.form.ComboBoxTree.superclass.initValue.call(this);
if(this.hiddenField){
this.setHiddenValue(Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, ''));
}
},
//private, to get this field's owner BasicForm
getOwnerForm : function(c){
var formPanel = this.findDirectFormOwnerCt(c);
if(formPanel && formPanel.getForm()){
return formPanel.getForm();
}else{
return null;
}
},
//private, to find this field's direct formPanel ownerCt
findDirectFormOwnerCt : function(c){
if(c.ownerCt){
if(c.ownerCt.getXType() == 'form'){
return c.ownerCt;
}else{
return this.findDirectFormOwnerCt(c.ownerCt);
}
}else{
return null;
}
},
//private
getListParent : function(){
return document.body;
},
getParentZIndex : function(){
var zindex;
if (this.ownerCt){
this.findParentBy(function(ct){
zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
return !!zindex;
});
}
return zindex;
},
// private
restrictHeight : function(){
this.innerList.dom.style.height = '';
var inner = this.innerList.dom,
pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight,
h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad;
h = Math.min(h, space, this.maxListHeight);
this.innerList.setHeight(h);
this.list.beginUpdate();
this.list.setHeight(h+pad);
this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
this.list.endUpdate();
},
//private
isExpanded : function(){
return this.list && this.list.isVisible();
},
//private
onViewClick : function(doFocus){
var index = this.view.getSelectedIndexes()[0];
if(!index){
this.collapse();
}
if(doFocus == true){
this.el.focus();
}
},
// private
onDestroy : function(){
Ext.destroy(
this.resizer,
this.view,
this.list
);
Ext.destroyMembers(this, 'hiddenField');
Ext.ux.form.ComboBoxTree.superclass.onDestroy.call(this);
},
//override
setValue : function(val){
val = Ext.value(val,'');
if(val != ''){
if(this.enableClearValue){
this.triggers[0].show();
}
}else{
if(this.enableClearValue){
this.triggers[0].hide();
}
}
Ext.ux.form.ComboBoxTree.superclass.setValue.call(this,val);
},
//private
setHiddenValue : function(val){
val = Ext.value(val,'');
if(val != ''){
if(this.enableClearValue){
this.triggers[0].show();
}
}else{
if(this.enableClearValue){
this.triggers[0].hide();
}
}
if(this.hiddenField){
this.hiddenField.value = val;
this.value = val;
}
return this;
},
//override
reset : function(){
Ext.ux.form.ComboBoxTree.superclass.reset.call(this);
if(this.hiddenField){
this.setHiddenValue('');
}
},
//clear value by click clear value trigger
clearValue : function(){
if(this.enableClearValue){
var v = this.value;
if(this.hiddenField){
v = this.hiddenField.value;
this.hiddenField.value = '';
}
this.setValue('');
this.validate();
this.fireEvent('clearvalue', this, v);
}
},
getTree : function(){
return this.tree;
},
//override
getValue : function(){
if(this.hiddenField){
return Ext.isDefined(this.value) ? this.value : '';
}else{
return Ext.form.ComboBox.superclass.getValue.call(this);
}
},
collapse : function(){
if(!this.isExpanded()){
return;
}
if(this.enableClearValue && Ext.value(this.value,'') != ''){
this.triggers[0].show();
}
if(this.fireEvent('beforecollapse',this) !== false){
this.list.hide();
Ext.getDoc().un('mousewheel', this.collapseIf, this);
Ext.getDoc().un('mousedown', this.collapseIf, this);
this.fireEvent('collapse', this);
}
},
expand : function(){
if(this.isExpanded() || !this.hasFocus){
return;
}
if(this.fireEvent('beforeexpand', this) !== false){
this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
// zindex can change, re-check it and set it if necessary
var listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
zindex = parseInt(Ext.fly(listParent).getStyle('z-index') ,10);
if (!zindex){
zindex = this.getParentZIndex();
}
if (zindex) {
this.list.setZIndex(zindex + 5);
}
this.triggers[0].hide();
this.list.show(this.animate);
if(Ext.isGecko2){
this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
}
this.mon(Ext.getDoc(), {
scope: this,
mousewheel: this.collapseIf,
mousedown: this.collapseIf
});
this.fireEvent('expand', this);
}
},
// private
collapseIf : function(e){
if(this.isExpanded() && !this.isDestroyed && !e.within(this.wrap) && !e.within(this.list)){
this.isTreeNodeClicked = true;
this.collapse();
}
},
onTriggerClick : function(e){
if(e.getTarget('#' + this.el.id, 1)){
return;
}
if(this.readOnly || this.disabled){
return;
}
if(this.isExpanded()){
this.collapse();
this.el.focus();
}else {
this.onFocus({});
this.expand();
this.restrictHeight();
this.el.focus();
}
},
//override, to prevent trigger hidden when set readOnly = true
updateEditState: function(){
if(this.rendered){
if (this.readOnly) {
this.el.dom.readOnly = true;
this.el.addClass('x-trigger-noedit');
this.mun(this.el, 'click', this.onTriggerClick, this);
} else {
if (!this.editable) {
this.el.dom.readOnly = true;
this.el.addClass('x-trigger-noedit');
this.mon(this.el, 'click', this.onTriggerClick, this);
} else {
this.el.dom.readOnly = false;
this.el.removeClass('x-trigger-noedit');
this.mun(this.el, 'click', this.onTriggerClick, this);
}
this.trigger.setDisplayed(!this.hideTrigger);
}
this.onResize(this.width || this.wrap.getWidth());
}
}
});
Ext.reg('xcomboboxtree',Ext.ux.form.ComboBoxTree);
具体使用如下:
items:[{
xtype:'xcomboboxtree',
fieldLabel:'父节点',
enableClearValue:true,//显示清除值的trigger
hiddenName:'foo',
width:120,//该域的长度
listWidth:220,//下拉框的长度
listHeight:200,//下拉框的高度
tree:new Ext.tree.TreePanel({
root:new Ext.tree.AsyncTreeNode({
id:'0',
text:'ExtJs-3.2'
}),
loader:new Ext.tree.TreeLoader({
dataUrl:'tree-data.json',
preloadChildren:true
}),
}),
listeners:{
'treenodeselect':function(combotree,node){
combotree.setValue(node.text);
combotree.setHiddenValue(node.id);
}
}
}]
效果:


1 楼
charsunny
2011-06-19
谢谢分享,总算找到一个可用的了~
2 楼
jianxia801
2012-06-25
哥们你这个确实不错;我给你扩展了一下;实现了可以设置和获得选中的树节点。添加了如下两个方法:
getSelectedNode:function(){
var nodeId=this.getValue();
var selectedNode=null;
alert(nodeId);
if(nodeId!=""){
selectedNode=this.tree.getNodeById(nodeId);
}
return selectedNode;
},
setSelectedNode:function(nodeId){
var selectedNode=this.tree.getNodeById(nodeId);
selectedNode.select();
},
主要用于设置选中树节点!
getSelectedNode:function(){
var nodeId=this.getValue();
var selectedNode=null;
alert(nodeId);
if(nodeId!=""){
selectedNode=this.tree.getNodeById(nodeId);
}
return selectedNode;
},
setSelectedNode:function(nodeId){
var selectedNode=this.tree.getNodeById(nodeId);
selectedNode.select();
},
主要用于设置选中树节点!