自定义的TreeComboBox

来源于自己的博客小站“虾米成长记”!转载请注明出处,谢谢!

在项目中遇到一个需求:需要一个能显示树形层级关系的下拉控件。之前的做法是使用一个普通的下拉框控件,控件里的选项通过在其头部添加不同数量的空格来体现树形的层次关系。这是一个比较low的做法,既不美观,也无法做到选项节点的收缩展开。因此,需要做一个能“优美”显示树形选项的下拉框控件。由于项目使用了Extjs,而Extjs中并没有一个像这样的现成的控件,所以我扩展了Ext.form.field.Picker,自定义了一个控件,代码如下:

Ext.define('TreeComboBox', {
    extend: 'Ext.form.field.Picker',
    requires: ['Ext.tree.Panel'],
    store: {},
    tree: {},
    scrollable: false,
    config: {
        maxPickerWidth: 350,
        maxPickerHeight: 350
    },
    listeners: {
        change: function (field, newValue, oldValue) {
            if (!newValue || !newValue.trim()) {
                var root = getTreeData(unitData);
                this.store.setRoot(root);
            } else {
                this.tree.filterBy(newValue, 'text');
            }
        },
        expand: function (field) {
            var root = getTreeData(unitData);
            this.store.setRoot(root);
            this.tree.selectBy(field.getValue(), 'text');
        }
    },
    initComponent: function () {
        var self = this;
        Ext.apply(self, {
            fieldLabel: self.fieldLabel,
            labelWidth: self.labelWidth
        });
        self.callParent();
        this.tree = Ext.create("TreePanel", {
            rootVisible: false,
            width: self.maxPickerWidth,
            height: self.maxPickerHeight,
            scrollable: true,
            floating: true,
            focusOnToFront: false,
            shadow: false,
            useArrows: false,
            store: this.store
        });
        this.tree.on('itemclick', function (view, record) {
            self.setValue(record.get('text'));// 显示值
            self.name = record.get("value");// 隐藏值
            self.collapse();
        });
    },
    createPicker: function () {
        var self = this;
        self.picker = this.tree;
        return self.picker;
    },
    alignPicker: function () {
        var me = this, picker, isAbove, aboveSfx = '-above';
        if (this.isExpanded) {
            picker = me.getPicker();
            if (me.matchFieldWidth) {
                picker.setWidth(680);
            }
            if (picker.isFloating()) {
                picker.alignTo(me.inputEl, "tl-bl?", me.pickerOffset);// ->tl
                isAbove = picker.el.getY() < me.inputEl.getY();
                me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
                picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
            }
        }
    }
});

在如上的代码里可以看到“MyApp.plugin.TreePanel”,这是我扩展了“Ext.tree.Panel”的自定义控件。它的代码很简单,结合了另外一个自定义控件“MyApp.plugin.TreeFilter”,以实现TreeComboBox的文本框可以根据输入的字符自动过滤数据的功能,TreePanel的代码如下:

Ext.define('TreePanel', {
    extend: 'Ext.tree.Panel',
    mixins: ['TreeFilter']
});

TreeFilter的代码如下:

Ext.define("TreeFilter", {
    filterBy: function (text, by) {
        var view = this.getView(), me = this, nodes = [];

        unitTree.cascadeBy(function (tree, view) {
            var currNode = this;
            if (currNode && currNode.data[by] && currNode.data[by].toString().trim() != 'Root' &&
                    currNode.data[by].toString().toLowerCase().indexOf(text.toLowerCase()) > -1) {
                nodes.push(currNode);
            }
        }, null, [me, view]);

        var root = {
            expanded: true,
            children: []
        }
        Ext.each(nodes, function (node) {
            var child = {
                text: node.get("text"),
                value: node.get('value'),
                leaf: true,
                iconCls: 'no-icon'
            }
            root.children.push(child);
        })

        this.store.setRoot(root);
    },
    selectBy: function (text, by) {
        var view = this.getView(), me = this, nodesAndParents = [];
        this.getRootNode().cascadeBy(function (tree, view) {
            var currNode = this;
            if (currNode && currNode.data[by] && currNode.data[by].toString().toLowerCase() === text.toLowerCase()) {
                me.expandPath(currNode.getPath(), {
                    select: true,
                    focus: true
                });
            }
        }, null, [me, view]);
    }
});

这里的TreePanel扩展了“Ext.tree.Panel”,正是想利用它的“tree”的特性以满足需求,只要绑定“Ext.data.TreeStore”对象,就可以显示出树形数据了。这里的TreeStore绑定的数据需要符合如下的数据结构:

{
    expanded: true,
    children: [{
        text: 'zhangsan',
        value: '1',
        leaf: false,
        iconCls: 'no-icon',
        expanded: true,
        children: [{
            text: 'lisi'value: '1',
            leaf: true,
            children: [],
            iconCls: 'no-icon',
            expanded: false
        }],
        {
            text: 'wangwu',
            value: '3',
            leaf: true,
            children: [],
            expanded: false,
            iconCls: 'no-icon'
        }
    }]
}

需要说明的是在我们的项目里数据表中通过pid和id这两个字段来体现数据的层次关系,pid表示父亲节点的id,id表示子节点的id。后台向前台返回包含pid、id、value、text等字段对象的集合,在前台解析返回结果,组成“Tree”结构的数据,见如下代码:

function getTreeData(records) {
    var unitHashMap = new Ext.util.HashMap();

    Ext.each(records, function (item) {
        if (!unitHashMap.containsKey(item.get("PunitId"))) {//父节点不存在
            unitHashMap.add(item.get("PunitId"), [{
                value: item.get("UnitId"),
                text: item.get("UnitName"),
                iconCls: 'no-icon',
                leaf: true,
                expanded: false,
                children: []
            }]);
        } else {//父节点存在
            var unit = unitHashMap.get(item.get("PunitId"));
            unit.push({
                value: item.get("UnitId"),
                text: item.get("UnitName"),
                iconCls: 'no-icon',
                leaf: true,
                expanded: false,
                children: []
            })
        }
    });

    var removeKeys = [];

    unitHashMap.each(function (key, value, length) {
        var result = Ext.Array.findBy(records, function (item, index) {
            return item.get("UnitId") === key;
        });

        if (result) {
            var unit = unitHashMap.get(result.get("PunitId"));
            var child = Ext.Array.findBy(unit, function (item, index) {
                return item.value === key;
            });

            child.leaf = false;
            Ext.each(value, function (v) {
                child.children.push(v);
            });

            removeKeys.push(key);
        }
    })

    Ext.each(removeKeys, function (key) {
        unitHashMap.removeAtKey(key);
    });

    var rootData = {
        expanded: true,
        children: []
    };

    unitHashMap.each(function (key, value, length) {
        Ext.each(value, function (v) {
            rootData.children.push(v);
        });
    });

    return rootData;
}

这里大体地阐述一下上述代码的实现思想:首先通过pid将数据分组,可以获得父节点pid对应的子节点id;然后再遍历pid,寻找pid之间的“父子”关系,如果一个节点是另外一个节点的子节点,就将该节点放到父节点的children字段里。在处理节点关系的过程中需要防止重复数据的产生。本文简单的记录一个“TreeComboBox”的实现过程,文字不多,而想要表达的精华其实都藏在代码里了!
Demo

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,946评论 25 707
  • 说明本次redis集群安装在rhel6.8 64位机器上,redis版本为3.2.8,redis的gem文件版本为...
    读或写阅读 14,692评论 3 9
  • ㈠ 早上六点钟,安城接到了秋阳的电话,说是在她学校附近的肯德基等着的。 朦胧起床套上羽绒服,顶着厚重的晨霾,安城踏...
    柏宋阅读 574评论 5 12
  • 好想说给你听 初恋,总让人们刻骨铭心!或许他并不知道,他虽不是第一个让我动心的...
    等待2017阅读 239评论 0 1
  • “喂 朱朱,干嘛呢快过来陪我,姐妹我又失恋了,嘟嘟嘟”。 啊要死了要死了这才几点啊好不容易等来的黑...
    花花是冻粉条阅读 476评论 0 2