`

[Ext扩展]QM.ui.TreeCombo:多功能下拉树列表,内含文档与示例

    博客分类:
  • Ext
阅读更多
有句老话叫不重复造轮子,既然网上已经有下拉树的扩展,为什么还要再做一个呢?答案很简单,网上那些满足不了我的需要。简单来说,本扩展UI组件具备以下功能:
  • 宽度自适应 下拉列表宽度可根据树的大小进行自动调整
  • 延迟加载 默认采用,提高页面渲染速度
  • 自动寻路 下拉列表展开时会将选中树结点的按其路径展开
  • 键盘导航 支持使用上下左右回车ESC、TAB进行操作
  • 智能过滤 支持使用汉字和拼音首字母对树进行过滤
  • 默认值支持 可以为组件设置默认值,组件会自动找到对应结点
  • 忽略父节点 默认情况只有选择子节点才有效

示例放到Ext根路径下即可查看,内含详细使用说明,效果图如下:

Ext版本已测试3.1.1、3.2.0
IE6下页面显示问题解决方案:设置css如下
body {
   font-size:13px;
}
(这是IE的问题,不用怀疑)
,Firefox、Chrome测试正常
源代码:
/**
 *
 * 支持功能:
 * 1.自动宽度和高度调整;弹出层会根据依据树的宽高调整自身宽高,注意:设置listWidth属性后弹出
 * 层宽度将固定,树面板在需要时会出现水平滚动条;设置minListWidth属性可限定弹出层最小宽度,
 * 默认为TreeCombo的宽度;
 * 2.自动寻找选中结点:弹出层展开后会根据当前值找到对应结点并根据其树中路径展开树,其余结点将
 * 收缩。
 * 3.程序赋值:通过setValue(String nodeId);方法可通过代码设置TreeCombo的Value值,此时程序会
 * 自动找到树节点id对应的树节点text并将其显示到输入域中,如果找不到对应树节点,输入域会显示
 * 配置项valueNotFoundText的值提示这是个程序错误!
 * 4.自动检测:输入域默认是可以编辑的,ComboBox失去焦点后自动检测当前输入域中内容是否有对应的树
 * 结点,有的话设置value值为对应结点,没有则自动退回到上一次失去焦点时的状态;
 * 5.键盘导航:down(选择上一节点)、up(选择下一结点)、enter(选中当前选择结点)、left(收
 * 缩当前结点)、right(展开当前结点)、esc(取消编辑)、tab(取消编辑并跳转到下一个输入域);
 * 6.忽略父节点:ignoreFolder可设置父节点不能被选中,此属性为true时用户点击父节点时面板不会
 * 收缩、过滤时也会忽略父节点;
 * 也不会被赋值,调用setValue方法时如果找到的是父节点输入域会显示配置项valueNotFoundText的值;
 * 7.拼音首字母过滤:用户对输入域编辑时树节点都会根据输入域中字母进行拼音首字母过滤,匹配结点
 * 将保留并展开,内置缓存处理以提高多次查询的检索速度,可与键盘导航功能同时使用,最大限度提高
 * 数据录入速度;
 * 8.延迟初始化:默认情况下lazyInit属性为true,此时tree会在TreeCombo获得焦点后才进行创建,这样做
 * 可以提高页面加载速度。
 * 9.可输入状态下输入域允许粘贴,失去焦点后组件会找到对应树节点并为值域(value)赋值,找不到显示组件
 * 的emptyText
 * 10.默认值支持:如果需要默认值可以将结点id赋给value,默认情况下因为使用延迟加载,默认值只有在组件
 * 获得焦点后才会通过树找到要结点text属性并显示到界面上,如果需要加载后就显示初始值可将lazyInit设置
 * 为false
 * 
 * 注意事项:
 * 1.如果树结点不是由root一次性加载,功能479将无法适用,此时请配置TreeCombo的editable属性为false;
 * 2.不同与官方的ComboBox,此下拉框没有forceSelection配置项(该配置项为false时用户可自定义下
 * 拉框内容),因为我不知道允许用户自定义内容有什么意义;
 * 3.为ComboBox配置tree属性必须配置Object直接量对象(用{}来声明的对象)不能是new创建的实例,
 * TreeCombo会负责树对象的创建和销毁;
 * 4.树的根结点是不可见的,此时树在创建时就会执行root的expand方法开始加载其子结点,因而树的root
 * 结点必须以new Ext.tree.AsyncTreeNode方式进行声明,否则无法添加事件判断其加载完成状态。
 * 5.如果树节点过多,过滤功能将严重影响性能,此时可设置
 * 
 * v1.1改动:
 * 1.添加一个配置项maxHeight用于控制列表的最大高度
 * 
 * @author chemzqm@gmail.com
 * @version 1.0.0
 * @createTime 2010-04-24 16:40:30
 * 
 */
Ext.ns('QM.ui');

QM.ui.TreeCombo = Ext.extend(Ext.form.TriggerField, {
    shadow: 'sides',
    /**
     * @cfg minListWidth 弹出层最小宽度,必须大于ComboBox宽度,与listWidth同时使用时无效
     */
	/**
	 * @cfg listWidth 弹出层固定宽度,设置后弹出层不根据树宽度进行调整,必须大于ComboBox宽度
	 *
	 */
	/**
	 * @cfg hiddenName 隐藏表单域名,form方式提交时需要,负责把value传到后台
	 */
    /**
     * @cfg listClass 阴影层样式
     */

    listAlign: 'tl-bl?',
	queryDelay:300,//查询函数缓冲时间(缓冲时间内再次调用将取消上次调用)
    valueNotFoundText: '没有指定数据',
    triggerAction: 'all',//下拉按钮点击时查询条件'all'查询出所有数据 'query'根据输入项进行前端匹配
    ignoreFolder: true,//父节点不做为数据源
    lazyInit: true,//控件获得焦点时才会初始化下拉框包括树
	loadingText:'加载中...',
    emptyText:'请选择...',
	forceSelection: true,//输入框的值只能是列表存有的值
	enableQuery : true,
	
	
    initComponent: function(){
		this.hiddenPkgs = [];//隐藏的分支节点
        QM.ui.TreeCombo.superclass.initComponent.call(this);
        this.addEvents('expand', 'collapse','beforeselect','select');
    },
    onRender: function(ct, position){
        QM.ui.TreeCombo.superclass.onRender.call(this, ct, position);
		if(this.hiddenName){
            this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
                    id: (this.hiddenId||this.hiddenName)}, 'before', true);
        }
        if (this.lazyInit) {
            this.on('focus', this.initList, this, {
                single: true
            });
        }
        else {
            this.initList();
        }
    },
	
	initEvents : function(){
        QM.ui.TreeCombo.superclass.initEvents.call(this);
		this.keyNav = new Ext.KeyNav(this.el, {
            "up" : this.onKeyDown,
            "down" : function(e){
                if (!this.isExpanded()) {
					this.onTriggerClick();
				}
				else {
					this.onKeyDown(e);
				}
            },
			"left":this.onKeyDown,
			"right":this.onKeyDown,
			"enter":function(){
				var node = this.tree.selModel.getSelectedNode();
				this.onTreeClick(node);
			},
            "esc" : function(e){
                this.collapse();
            },
            "tab" : function(e){
                this.collapse();
                return true;
            },
            scope : this,
            forceKeyDown : true
        });
        this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
        if(!this.enableKeyEvents){
            this.mon(this.el, 'keyup', this.onKeyUp, this);
        }
	},
	//上下左右回车让TreeSelectionModel来辅助实现
	onKeyDown:function(e){
		var sm = this.tree.getSelectionModel();
		if(sm){
			sm.onKeyDown(e);
		}
		this.el.focus();
	},
	initQuery:function(){
		this.doQuery(this.getRawValue());
	},
	onKeyUp : function(e){
        var k = e.getKey();
        if(this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())){
            this.dqTask.delay(this.queryDelay);
        }
        Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
    },
    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 (this.ownerCt && !zindex) {//找到父容器定义的z-index
                this.findParentBy(function(ct){
                    zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
                    return !!zindex;
                });
            }
            this.list = new Ext.Layer({
                parentEl: listParent,
                shadow: this.shadow,
                cls: [cls, this.listClass].join(' '),
                constrain: false,
                zindex: (zindex || 12000) + 5
            });
			if(!this.minListWidth){
				this.minListWidth = this.wrap.getWidth();
			}
			this.list.setStyle('width', this.minListWidth);
            this.list.setStyle('height', 'auto');
            this.list.swallowEvent('mousewheel');
            this.innerList = this.list.createChild({
                cls: cls + '-inner'
            });
            this.initInner();
        }
    },
    initInner: function(){
        Ext.apply(this.tree, {
            applyTo: this.innerList,
            border: false,
            rootVisible: false,
			autoScroll: true
        });
		var root = this.tree.root;
		if(root instanceof Ext.tree.AsyncTreeNode){
			root.on('beforeload',this.onBeforeRootLoad,this,{single:true});		
			root.on('load',this.onRootLoad,this,{single:true});
		}
        this.tree = Ext.create(this.tree, 'treepanel');
		this.tree.on({//加载完毕后再给树添加监听
            scope: this,
            expandnode: this.onTreeResize,
            collapsenode: this.onTreeResize,
            click: this.onTreeClick
        });
		if(this.editable){
			this.filter = new QM.ux.TreeFilter(this.tree,{
				ignoreFolder:this.ignoreFolder,
				clearAction:'collapse'
			});
		}
		if(this.value) {
			this.setValue(this.value);
		}
    },
	//@private
	onRootLoad:function(){
		this.isLoading = false;
		if (this.value) {
			this.setValue(this.value);
		}
		this.innerList.child('.loading-indicator').remove();
		if(this.isExpanded()){
			this.onLoad();
		}
	},
	//@private
	onTreeResize:function(){
		if(this.isExpanded()&&this.isQuerying!==true){
			this.restrict();
			this.el.focus();
		}
	},
	//@private
    onBeforeRootLoad : function(){
		this.isLoading = true;
        this.innerList.insertFirst({
			 tag:'div',
			 cls:'loading-indicator',
		    html:this.loadingText
		});
    },
	//@private
    onLoad: function(){
        if (!this.hasFocus) {
            return;
        }	
        this.expand();       
		if(!this.selectByNode(this.value, true)){		
            this.selectByNode(this.tree.root.firstChild, true);//没有的话选中第一个结点
        }
		if (this.editable) {
            this.el.focus();
        }
    },
    isExpanded: function(){
        return this.list && this.list.isVisible();
    },
    expand: function(){
        if (this.isExpanded() || !this.hasFocus) {
            return;
        }        
        this.list.show();
        this.mon(Ext.getDoc(), {
            scope: this,
            mousewheel: this.collapseIf,
            mousedown: this.collapseIf
        });
        this.fireEvent('expand', this);
    },
    collapseIf: function(e){
        if (!e.within(this.wrap) && !e.within(this.list)) {
            this.collapse();
        }
    },
	//@public
    collapse: function(){
        if (!this.isExpanded()) {
            return;
        }
        this.list.hide();
        Ext.getDoc().un('mousewheel', this.collapseIf, this);
        Ext.getDoc().un('mousedown', this.collapseIf, this);
        this.fireEvent('collapse', this);
    },
    //重置弹出层宽度
    restrict: function(){
        this.innerList.dom.style.width = '10px';//外层挤压,值太小会被Chrome忽略
        var body = this.tree.body.dom;
        	wpad = this.list.getFrameWidth('lr'),//边宽
 			  wa = Math.max(body.clientWidth, body.offsetWidth, body.scrollWidth), 
			   w = Math.max(wa, this.minListWidth - wpad);
			   w = this.listWidth ? this.listWidth : w;
			  lh = this.list.getHeight();
			   h = (this.maxHeight&&this.maxHeight<lh)?this.maxHeight:lh;//获取高度
		this.list.setHeight(h);
		this.innerList.setHeight(h);
        this.list.setWidth(w + wpad);
        this.innerList.setWidth(w);
        this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
        return;
        
    },
    getListParent: function(){
        return document.body;
    },
    onTriggerClick: function(){
        if (this.readOnly || this.disabled) {
            return;
        }
        if (this.isExpanded()) {
            this.collapse();
            this.el.focus();
        }
        else {
            this.onFocus({});
			if(this.filter){
				this.filter.clear();		
			}
			this.onLoad();		
        }
    },
    doQuery: function(q){
        q = Ext.isEmpty(q) ? '' : q;
        if (!this.isLoading && this.filter) {
			this.filter.filter(q);
		}
		if(this.filter.isCleared()){
			this.tree.root.firstChild.select();
		}
		else if(this.filter.hasMatch()){
			this.filter.matches[0].select();
		}
		this.el.focus();
		this.expand();
        this.restrict();
    },
    onTreeClick: function(node){
		if(this.fireEvent('beforeselect', this, node) !== false){
			if (this.ignoreFolder && !node.leaf) 
				return;
			this.setValue(node);
			this.collapse();
			this.fireEvent('select', this, node);
		}
    },
    //@public 确保树已加载所需结点再调用此方法,如果传的是id但是找不到结点value域将置空
    setValue: function(node){//根据TreeNode的id或者TreeNode对象设置值,显示TreeNode的text属性
        if (!this.tree.rendered||this.isLoading) {
            return null;
        }
		if(typeof node == 'string'){
			node = this.tree.getNodeById(node);
		}		
		var text;
		if(!node||(this.ignoreFolder && !node.leaf)){
			text = this.valueNotFoundText;
		} else {
            text = node.text;
        }
        QM.ui.TreeCombo.superclass.setValue.call(this, text);
		if(this.hiddenField){
            this.hiddenField.value = node.id;
        }
		this.lastSelectionText = text;
        this.value = node?node.id:'';	
        return this;
    },
	getValue : function(){
       return Ext.isDefined(this.value) ? this.value : '';
    },
	clearValue : function(){
		if(this.hiddenField){
            this.hiddenField.value = '';
        }
        this.setRawValue('');
        this.lastSelectionText = '';
        this.value = '';
	},
	// private
    validateBlur : function(){
        return !this.list || !this.list.isVisible();
    },
	//检查输入值是不是列表里有的,有的话设置对应value
    beforeBlur : function(){
		var val = this.getRawValue();
        node = this.tree.root.findChild('text',val,true);	   
        if(!node){
            if(val.length > 0 && val != this.emptyText){
                this.el.dom.value = Ext.value(this.lastSelectionText, '');//值空或是上一次输入
            }else{//输入域清空,所有值清空
                this.clearValue();
            }
        }else if(node){
            this.setValue(node);
        }
    },
	//根据id值或node对象选择到相应node并显示出来,scrollIntoView是否需要滚动
	selectByNode : function(node, scrollIntoView){
        if(!Ext.isEmpty(node, true)){
			if (typeof node == 'string') {
				node = this.tree.getNodeById(node);
			}
            if(node){
				this.tree.collapseAll();
				this.tree.expandPath(node.getPath());//只展开选中结点
				node.select();
				if(scrollIntoView===true)
					node.ensureVisible();
                return true;
            }
        }
        return false;
    },
    // private
    postBlur  : function(){
        QM.ui.TreeCombo.superclass.postBlur.call(this);
        this.collapse();
        this.inKeyMode = false;
    },
	onDestroy: function(){
		if (this.dqTask){
            this.dqTask.cancel();
            this.dqTask = null;
        }		
        Ext.destroy(this.tree,this.list,this.filter);
        QM.ui.TreeCombo.superclass.onDestroy.call(this);
    }
});

Ext.reg('treecombo', QM.ui.TreeCombo);

  • 大小: 8.3 KB
11
0
分享到:
评论
6 楼 lai555 2012-03-09  
有没有文档呢,请问,比如我要得到选择的值
5 楼 jy02534655 2011-12-22  
不晓得为啥用vs2010调试的时候不能加载远程数据,显示不出来数据。求解是ext3.20
4 楼 ahuixuan 2011-12-14  
谢谢了!非常好的例子
3 楼 jrs320 2011-08-11  
楼主辛苦了,我用了你这个控件,也做了些修改,很不错!现在有个问题:这个控件放在formpanel容器中,在加载表单时,这个控件无法初始对应的值(就是在控件渲染前,已经有值了,怎么显示初始值以及赋值),我至今还未找到解决的办法,希望楼主能解决下,非常感谢啊!
2 楼 zhangjunfun78 2011-07-21  
支持楼主,辛苦了,这个控件非常好用,不过建议你将beforeBlur 方法中对应值的地方改为根据id对应,否则,如果出现同名的节点,下次就会自动选中第一个同名的节点。
1 楼 guyuelong2010 2011-06-28  
楼主,谢谢了,用了挺好的,就是你这个最大高度,他不好使啊,当数据多时超过屏幕了就看不到底下的数据了,可以修改一下吗。

相关推荐

    Extjs4 下拉树 TreeCombo

    ExtJS 4 下拉树(TreeCombo)是一种组合控件,它将传统的下拉框与树形结构结合在一起,提供了一种在有限空间内展示层级数据的高效方式。这种控件在很多场合都非常实用,例如在需要用户选择分类或者层级结构的场景中...

    [Ext 3.x + Ext 2.x] 下拉树 Ext.ux.ComboBoxTree

    【Ext 3.x + Ext 2.x 下拉树 Ext.ux.ComboBoxTree】是基于ExtJS框架的一个组件,它结合了下拉框(ComboBox)和树形控件(TreePanel)的功能,提供了一种用户友好的选择界面。在网页应用中,这种控件常用于展示层级...

    EXT dojochina文本框示例Ext.form.TextField.rar

    8. **组合框模式**:`Ext.form.TextField`还可以扩展为`Ext.form.ComboBox`,实现下拉选择功能,这在需要用户从预定义选项中选择时非常有用。 在压缩包中的`Ext.form.TextField`文件夹,很可能是包含了一个或者多个...

    EXT核心API详解.doc

    EXT核心API详解主要涵盖了一系列与EXT.js库相关的类和对象,EXT.js是一个强大的JavaScript UI框架,用于构建富客户端Web应用程序。以下是对各个类的详细解释: 1. **Ext类**:EXT库的基础类,提供了许多实用的方法...

    Ext下拉树、下拉表格

    在Ext中,下拉树是将传统的下拉列表与树形控件相结合的一种方式,用户可以点击输入框打开一个包含树结构的下拉菜单。这种组件常用于需要层次结构选择的场景,例如组织架构、文件目录等。创建下拉树通常涉及以下步骤...

    EXT dojoChina按钮控件示例 Ext.Button.rar

    EXT dojoChina按钮控件示例 Ext.Button.rarEXT dojoChina按钮控件示例 Ext.Button.rarEXT dojoChina按钮控件示例 Ext.Button.rarEXT dojoChina按钮控件示例 Ext.Button.rar

    Ext.tree.TreeLoader附带封装的json类

    总之,`Ext.tree.TreeLoader`与JSON数据的结合使得在Ext JS中创建动态、可扩展的树形视图变得简单高效。通过理解和应用这些概念,开发者能够构建出更加交互丰富的前端应用。希望这个概述能帮助你更深入地了解`...

    ExtJs3下拉树

    ExtJs3下拉树 分两种方式调用:第一种: xtype : 'combotree', name : 'dm', fieldLabel : 'dm', tree : this.ct this.ct = new Ext.tree.TreePanel({ autoScroll : true, height : 250, border : false, ...

    Extjs4.X下comboboxTree下拉树型菜单,完美支持多选、单选,绝对好用

    在ExtJS 4.x框架中,ComboboxTree是一种特殊的组件,它将传统的下拉框与树形结构结合在一起,提供了一种更为灵活的用户输入方式。这种组件在数据选择上非常实用,尤其当数据层级关系复杂时,可以方便地进行多选或...

    ext js 下拉树

    下拉树(Dropdown Tree)是Ext JS中的一种特殊控件,它结合了下拉列表和树结构的功能,通常用于展示层次化的数据,并让用户从中选择一个或多个项。 下拉树的基本结构由两部分组成:一个文本框和一个关联的下拉面板...

    extjs-Ext.ux.form.LovCombo下拉框

    EXTJS的`ComboBox`默认支持单选,而`Ext.ux.form.LovCombo`通过扩展实现了多选功能,允许用户在下拉列表中选择多个选项。这对于数据筛选或者关联数据选择场景非常有用。 至于"extjs"标签,这表明整个话题是关于...

    ExtJs xtype一览

    - **`combo` (Ext.form.ComboBox)**: 下拉框组件,提供了一个下拉列表供用户选择。 - **`datefield` (Ext.form.DateField)**: 日期选择项组件,用于输入日期值。 - **`timefield` (Ext.form.TimeField)**: 时间录入...

    Ext.DataView 图片列表显示

    var reader = new Ext.data.JsonReader({totalProperty:'totalProperty',root:'root'},record); store = new Ext.data.Store({ proxy:proxy, reader:reader }); //尾 分页 var pagebar = new Ext....

    ext 下拉树

    在Ext 4.0版本中,下拉树的实现主要依赖于几个关键组件:`Ext.tree.Panel`(树面板)、`Ext.form.field.Tree`(树形字段)以及可能用到的`Ext.data.TreeStore`(树存储)。下面我们将详细探讨这些知识点: 1. **Ext...

    JMeterPlugins.jar

    JMeter导入jmx运行脚本时出现这样的错误jmeter.save.SaveService: Conversion error .../lib/ext ==&gt; JMeterPlugins.jar

    TreeCombo,Ext TreeCombo 树形 下拉框,树形下拉框

    Ext TreeCombo是一种UI组件,它将传统的下拉框与树形控件(Tree)结合起来,提供了一种交互式的、具有层级结构的下拉选择。用户在输入框中输入文字,可以筛选出符合输入的树节点,同时,点击下拉按钮会展示整个树形...

    ExtJS-3.4.0系列目录

    你可以通过Ext.toolbar.Toolbar创建一个工具栏,并添加各种元素,如按钮、分割符、下拉列表等。 - **菜单栏(Menu)**:提供下拉式菜单,用于放置选项和子菜单。Ext.menu.Menu允许你创建可定制的多级菜单,可与工具...

    可编辑表格Ext.grid.EditorGridPanel

    2. Ext.grid.EditorGridPanel:EditorGridPanel是Ext JS中的一种网格组件,它扩展了GridPanel,增加了单元格级别的编辑功能。用户可以直接在表格中修改数据,而无需跳转到单独的编辑页面。 二、核心特性 1. 可编辑...

    Ext.ux.tree.treegrid异步加载

    在ExtJS框架中,`Ext.ux.tree.TreeGrid`组件是一种结合了树形结构与表格显示特性的控件,适用于展示具有层级关系的数据。通过这种组件,用户可以在一个界面上同时查看数据的层次结构以及具体数据内容。 #### 二、...

Global site tag (gtag.js) - Google Analytics