ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Ext下拉树插件ComboTree源码解析与实战开发指南

Ext下拉树插件ComboTree源码解析与实战开发指南 简介这是一份面向ExtJS前端开发者的自定义组件源码核心是ComboTree_xz.js用于把传统下拉框与树形结构结合实现可展开、折叠并选择层级数据的选择控件适用于组织结构、地区分类等需要展示嵌套关系的业务场景。压缩包内共1个文件为js脚本整体约2KB体量轻巧便于直接引入项目或作为组件扩展的学习范例。资源围绕组件定义、树形数据结构、模板渲染、事件处理、配置项与API等关键环节展开展示了如何继承Ext.form.field.ComboBox并重写onRender、onSelect、loadData等方法配合Ext.data.TreeStore与XTemplate完成节点渲染与交互。已有182人学习适合希望深入理解ExtJS组件系统、数据绑定机制并提升企业级Web应用开发能力的前端工程师参考借鉴。1. 下拉树插件一个被低估的表单交互组件做过后台管理系统的开发者大概都有过这种体验表单里要选一个部门、选一个分类、选一个区域数据本身是树形结构但原生 select 只能平铺用起来别扭得很。有人用级联选择器凑合有人干脆拆成两个下拉框联动代码写得又臭又长。ComboTree 这个 Ext 下拉树插件就是冲着这个场景来的——它把树形结构和下拉框揉在一起点开是一个可展开的树选中后回填到输入框里既保留了树的层级关系又有下拉框的紧凑形态。这份源码适合正在用 Ext 框架做企业级后台的开发者尤其是那些被表单树形选择折磨过的人。拿到手可以直接跑也可以按自己的数据结构改配置。2. 技术选型与目录结构Ext 体系下的插件设计思路2.1 为什么是 Ext 而不是自己造轮子Ext 这套框架在国内企业后台里存活了很长时间原因很实际组件库全、表格和表单能力成熟、主题统一。ComboTree 没有脱离 Ext 的组件体系另起炉灶而是继承自Ext.form.field.ComboBox把下拉面板里的列表替换成树。这样做的好处是所有 ComboBox 已有的配置项——值映射、校验、只读、禁用——都能直接复用不需要重新发明一遍。常见做法是继承Ext.form.field.Picker因为 Picker 本身就是下拉面板的基类ComboBox 也是从它来的。继承 Picker 比继承 ComboBox 更轻少了很多列表相关的冗余逻辑。源码里如果看到extend: Ext.form.field.Picker说明作者走的是这条更干净的路。2.2 目录结构长什么样一份典型的 Ext 插件源码包目录不会太复杂但每个文件都有明确分工ComboTree/ ├── src/ │ ├── ComboTree.js # 插件主类继承 Picker │ └── ComboTree.css # 下拉树面板的样式覆盖 ├── examples/ │ ├── index.html # 演示页面入口 │ ├── app.js # 演示用的 Ext 应用启动逻辑 │ └── data/ │ └── tree-data.json # 模拟的树形数据 ├── resources/ │ └── images/ # 展开收起图标、选中态图标 ├── build/ # 打包后的压缩版本 │ └── ComboTree-all.js └── README.mdsrc下是核心examples是能直接打开的演示build是给生产环境用的。如果你只是想在项目里用把build/ComboTree-all.js和ComboTree.css引进去就行如果要改源码就盯src下的两个文件。2.3 核心类结构拆解插件主类通常包含这几个关键部分Ext.define(Ext.ux.ComboTree, { extend: Ext.form.field.Picker, alias: widget.combotree, // 树形数据源可以是嵌套数组或扁平数组 store: null, // 显示字段对应树节点的 text displayField: text, // 值字段对应树节点的 id valueField: id, // 是否允许选择非叶子节点 allowParentSelect: true, // 下拉面板宽度默认跟输入框一致 pickerWidth: null, // 选中后输入框显示的内容默认取 displayField displayTpl: {text}, // 创建下拉面板返回一个 TreePanel createPicker: function() { var me this; return Ext.create(Ext.tree.Panel, { floating: true, hidden: true, focusOnToFront: false, shadow: false, ownerCt: me, store: me.store, rootVisible: false, listeners: { itemclick: me.onTreeItemClick, scope: me } }); }, // 树节点点击后的回填逻辑 onTreeItemClick: function(view, record) { var me this; if (!me.allowParentSelect record.isLeaf() false) { return; } me.setValue(record.get(me.valueField)); me.setRawValue(record.get(me.displayField)); me.collapse(); me.fireEvent(select, me, record); } });这段代码是整个插件的骨架。createPicker负责把下拉面板造出来返回的是一个Ext.tree.Panel而不是 ComboBox 默认的 BoundList。onTreeItemClick处理点击事件先判断是否允许选父节点然后通过setValue和setRawValue分别设置值和显示文本最后收起面板并抛出select事件供外部监听。参数说明几个容易踩坑的地方rootVisible一般设为false因为下拉树不需要显示根节点否则会多一层无意义的展开focusOnToFront设为false是为了避免面板抢焦点导致输入框失焦allowParentSelect这个配置很关键有些业务场景只允许选叶子节点比如选到“部门”这一层就不能再往下选了这时候把它设成false就行。2.4 数据源的两种接法树形数据源有两种常见格式插件一般都要支持// 格式一嵌套结构children 里放子节点 var nestedData [{ id: 1, text: 技术部, children: [{ id: 1-1, text: 前端组, leaf: true }, { id: 1-2, text: 后端组, leaf: true }] }]; // 格式二扁平结构用 parentId 关联 var flatData [ { id: 1, text: 技术部, parentId: null }, { id: 1-1, text: 前端组, parentId: 1, leaf: true }, { id: 1-2, text: 后端组, parentId: 1, leaf: true } ];嵌套结构直接丢给Ext.data.TreeStore就能用扁平结构需要先转成树或者用TreeStore的parentIdProperty配置自动组装。我一般会在store配置里加一个parentIdProperty: parentId这样后端返回扁平数组也能直接渲染省得在前后端之间来回转换。3. 跑起来从零搭建一个可运行的演示页面3.1 引入依赖和插件文件Ext 项目的入口永远是一个 HTML 页面。新建一个index.html按顺序引入 Ext 的样式、Ext 核心库、插件源码!DOCTYPE html html head meta charsetutf-8 titleComboTree 演示/title !-- Ext 框架样式 -- link relstylesheet hrefext/resources/ext-all.css !-- 插件样式 -- link relstylesheet hrefsrc/ComboTree.css !-- Ext 核心库 -- script srcext/ext-all.js/script !-- 插件源码 -- script srcsrc/ComboTree.js/script /head body div idform-container/div script srcapp.js/script /body /html顺序不能乱Ext 核心库必须在插件之前加载因为插件里Ext.define依赖 Ext 的类系统。样式文件放在最前面避免页面渲染时闪一下无样式的状态。3.2 初始化一个带下拉树的表单app.js里做三件事定义数据、创建 store、把 ComboTree 放进表单。Ext.onReady(function() { // 1. 定义树形数据 var treeData [{ id: 1, text: 华东区, expanded: true, children: [{ id: 1-1, text: 上海, leaf: true }, { id: 1-2, text: 杭州, leaf: true }] }, { id: 2, text: 华南区, children: [{ id: 2-1, text: 广州, leaf: true }, { id: 2-2, text: 深圳, leaf: true }] }]; // 2. 创建 TreeStore var treeStore Ext.create(Ext.data.TreeStore, { root: { expanded: true, children: treeData } }); // 3. 创建表单放入 ComboTree Ext.create(Ext.form.Panel, { renderTo: form-container, title: 区域选择, width: 400, bodyPadding: 20, items: [{ xtype: combotree, fieldLabel: 所属区域, name: regionId, store: treeStore, displayField: text, valueField: id, allowParentSelect: false, pickerWidth: 300, listeners: { select: function(combo, record) { console.log(选中的节点, record.get(text)); } } }], buttons: [{ text: 提交, handler: function() { var form this.up(form).getForm(); console.log(form.getValues()); } }] }); });xtype: combotree能生效是因为插件类里定义了alias: widget.combotree。allowParentSelect: false让“华东区”“华南区”这种父节点不可选只能选到具体的城市。pickerWidth设成 300 是为了让下拉面板比输入框宽一点树形结构缩进后文字不容易被截断。3.3 验证运行结果打开浏览器控制台如果看到表单渲染出来点击下拉箭头能展开树点击“上海”后输入框显示“上海”控制台打印出选中的节点说明整条链路是通的。如果下拉面板弹不出来先检查createPicker有没有返回正确的组件如果树节点点不动看itemclick事件有没有绑定上如果值回填了但显示不对检查displayField和valueField是否跟数据里的字段名对得上。4. 避坑与排查下拉树最容易翻车的几个地方4.1 下拉面板位置偏移或宽度异常现象点击输入框后树面板弹出来了但位置偏到屏幕左上角或者宽度只有几个像素。原因Ext 的 Picker 在alignPicker阶段会读取输入框的宽度和位置如果输入框还没完成布局比如放在hidden的容器里拿到的尺寸就是 0。解决确保 ComboTree 在可见容器里初始化或者手动调一次me.alignPicker()。如果宽度还是不对显式设置pickerWidth别依赖自动计算。4.2 选中父节点后值能取到但显示为空现象allowParentSelect: true时选了父节点getValue()能拿到 id但输入框里什么都不显示。原因setRawValue用的字段跟数据里的字段名不一致或者父节点的displayField字段是空的。解决检查数据里每个节点是否都有text字段父节点也不例外。如果后端返回的字段叫name就把displayField改成name别硬套默认值。4.3 树数据量大时下拉卡顿现象树节点超过几百个点开下拉要等一两秒才渲染出来。原因Ext.tree.Panel默认一次性渲染所有可见节点数据量大时 DOM 操作很重。解决给 TreeStore 开启bufferedRenderer或者把rootVisible设为false减少一层渲染。更彻底的做法是让后端分页返回子节点点开父节点时再异步加载。4.4 表单重置后下拉树的值没清掉现象调用form.reset()后输入框清空了但再次点开下拉之前选中的节点还是高亮状态。原因reset只重置了表单字段的值没有清除树面板里的选中状态。解决在 ComboTree 里重写reset方法除了调用父类逻辑还要拿到 picker 里的 selection model执行deselectAll()。4.5 在 Ext 6 版本里报createPicker未定义现象升级 Ext 版本后插件直接报错说createPicker不是函数。原因Ext 6 之后 Picker 的 API 有调整部分方法签名变了。解决对照当前 Ext 版本的Ext.form.field.Picker文档确认createPicker的返回值要求。常见做法是返回一个带floating: true的组件如果新版要求返回Ext.Component的子类把 TreePanel 包一层就行。5. 二次开发把下拉树改成你要的样子5.1 支持多选和标签回填默认的 ComboTree 是单选但业务里经常需要多选比如给一个角色分配多个区域权限。改造思路是给 TreePanel 加上multiSelect: true然后在onTreeItemClick里收集所有选中节点用分隔符拼成字符串回填到输入框。onTreeItemClick: function(view, record) { var me this; if (!me.multiSelect) { // 单选逻辑不变 me.setValue(record.get(me.valueField)); me.setRawValue(record.get(me.displayField)); me.collapse(); return; } // 多选收集当前所有选中节点 var selected view.getSelectionModel().getSelection(); var values [], texts []; Ext.each(selected, function(item) { values.push(item.get(me.valueField)); texts.push(item.get(me.displayField)); }); me.setValue(values.join(,)); me.setRawValue(texts.join(, )); // 多选时不自动收起让用户继续点 }注意多选模式下不要调collapse()否则点一个就收起来根本没法连续选。另外setValue传的是逗号拼接的字符串后端接收时按逗号拆开就行。如果后端要求数组把values.join(,)改成values即可。5.2 异步加载子节点树数据量大的时候一次性加载全量数据不现实。改造 TreeStore让它在展开节点时再去请求子节点var asyncStore Ext.create(Ext.data.TreeStore, { proxy: { type: ajax, url: /api/region/children, reader: { type: json, rootProperty: data } }, root: { id: root, text: 全部区域, expanded: false }, lazyFill: true });后端接口接收node参数当前展开的节点 id返回该节点的直接子节点列表。lazyFill: true让 TreeStore 在节点展开时才发请求而不是初始化时就把整棵树拉下来。这个改造对后端有要求接口要支持按 parentId 查询返回的数据里每个节点要带leaf字段标识是否还有下级。5.3 自定义节点图标和选中样式Ext 的树节点图标通过iconCls控制可以在数据里给每个节点指定不同的 class{ id: 1-1, text: 上海, leaf: true, iconCls: icon-city // 对应 CSS 里的 .icon-city }然后在ComboTree.css里定义.icon-city { background-image: url(../resources/images/city.png); background-repeat: no-repeat; }选中态的样式覆盖要注意优先级Ext 默认的.x-grid-item-selected权重比较高自定义样式最好加上.x-treepanel前缀或者用!important兜底。我一般会在 CSS 里写.x-treepanel .x-grid-item-selected { background-color: #e6f7ff; }这样只影响下拉树面板不会污染其他表格组件。5.4 一个验证改造是否生效的小技巧改完源码后别急着刷新整个页面。在浏览器控制台里拿到 ComboTree 的实例直接调方法验证// 假设表单里 combotree 的 name 是 regionId var combo Ext.ComponentQuery.query(combotree[nameregionId])[0]; // 手动设置值看显示是否正确 combo.setValue(1-1); // 展开下拉看树面板是否正常 combo.expand(); // 打印当前选中项 console.log(combo.getPicker().getSelectionModel().getSelection());这样能快速定位是数据问题、渲染问题还是事件绑定问题。从那以后我每次改 Ext 插件都强制走一遍「控制台拿实例 → 手动调方法 → 看返回值」的流程比反复刷新页面猜问题快得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表