開發(fā)指南)
簡介這是一份面向ExtJS前端開發(fā)者的自定義組件源碼核心是ComboTree_xz.js用于把傳統(tǒng)下拉框與樹形結構結合實現(xiàn)可展開、折疊并選擇層級數(shù)據(jù)的選擇控件適用于組織結構、地區(qū)分類等需要展示嵌套關系的業(yè)務場景。壓縮包內(nèi)共1個文件為js腳本整體約2KB體量輕巧便于直接引入項目或作為組件擴展的學習范例。資源圍繞組件定義、樹形數(shù)據(jù)結構、模板渲染、事件處理、配置項與API等關鍵環(huán)節(jié)展開展示了如何繼承Ext.form.field.ComboBox并重寫onRender、onSelect、loadData等方法配合Ext.data.TreeStore與XTemplate完成節(jié)點渲染與交互。已有182人學習適合希望深入理解ExtJS組件系統(tǒng)、數(shù)據(jù)綁定機制并提升企業(yè)級Web應用開發(fā)能力的前端工程師參考借鑒。1. 下拉樹插件一個被低估的表單交互組件做過后臺管理系統(tǒng)的開發(fā)者大概都有過這種體驗表單里要選一個部門、選一個分類、選一個區(qū)域數(shù)據(jù)本身是樹形結構但原生 select 只能平鋪用起來別扭得很。有人用級聯(lián)選擇器湊合有人干脆拆成兩個下拉框聯(lián)動代碼寫得又臭又長。ComboTree 這個 Ext 下拉樹插件就是沖著這個場景來的——它把樹形結構和下拉框揉在一起點開是一個可展開的樹選中后回填到輸入框里既保留了樹的層級關系又有下拉框的緊湊形態(tài)。這份源碼適合正在用 Ext 框架做企業(yè)級后臺的開發(fā)者尤其是那些被表單樹形選擇折磨過的人。拿到手可以直接跑也可以按自己的數(shù)據(jù)結構改配置。2. 技術選型與目錄結構Ext 體系下的插件設計思路2.1 為什么是 Ext 而不是自己造輪子Ext 這套框架在國內(nèi)企業(yè)后臺里存活了很長時間原因很實際組件庫全、表格和表單能力成熟、主題統(tǒng)一。ComboTree 沒有脫離 Ext 的組件體系另起爐灶而是繼承自Ext.form.field.ComboBox把下拉面板里的列表替換成樹。這樣做的好處是所有 ComboBox 已有的配置項——值映射、校驗、只讀、禁用——都能直接復用不需要重新發(fā)明一遍。常見做法是繼承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 # 模擬的樹形數(shù)據(jù) ├── resources/ │ └── images/ # 展開收起圖標、選中態(tài)圖標 ├── build/ # 打包后的壓縮版本 │ └── ComboTree-all.js └── README.mdsrc下是核心examples是能直接打開的演示build是給生產(chǎn)環(huán)境用的。如果你只是想在項目里用把build/ComboTree-all.js和ComboTree.css引進去就行如果要改源碼就盯src下的兩個文件。2.3 核心類結構拆解插件主類通常包含這幾個關鍵部分Ext.define(Ext.ux.ComboTree, { extend: Ext.form.field.Picker, alias: widget.combotree, // 樹形數(shù)據(jù)源可以是嵌套數(shù)組或扁平數(shù)組 store: null, // 顯示字段對應樹節(jié)點的 text displayField: text, // 值字段對應樹節(jié)點的 id valueField: id, // 是否允許選擇非葉子節(jié)點 allowParentSelect: true, // 下拉面板寬度默認跟輸入框一致 pickerWidth: null, // 選中后輸入框顯示的內(nèi)容默認取 displayField displayTpl: {text}, // 創(chuàng)建下拉面板返回一個 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 } }); }, // 樹節(jié)點點擊后的回填邏輯 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處理點擊事件先判斷是否允許選父節(jié)點然后通過setValue和setRawValue分別設置值和顯示文本最后收起面板并拋出select事件供外部監(jiān)聽。參數(shù)說明幾個容易踩坑的地方rootVisible一般設為false因為下拉樹不需要顯示根節(jié)點否則會多一層無意義的展開focusOnToFront設為false是為了避免面板搶焦點導致輸入框失焦allowParentSelect這個配置很關鍵有些業(yè)務場景只允許選葉子節(jié)點比如選到“部門”這一層就不能再往下選了這時候把它設成false就行。2.4 數(shù)據(jù)源的兩種接法樹形數(shù)據(jù)源有兩種常見格式插件一般都要支持// 格式一嵌套結構children 里放子節(jié)點 var nestedData [{ id: 1, text: 技術部, children: [{ id: 1-1, text: 前端組, leaf: true }, { id: 1-2, text: 后端組, leaf: true }] }]; // 格式二扁平結構用 parentId 關聯(lián) 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這樣后端返回扁平數(shù)組也能直接渲染省得在前后端之間來回轉換。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 的類系統(tǒng)。樣式文件放在最前面避免頁面渲染時閃一下無樣式的狀態(tài)。3.2 初始化一個帶下拉樹的表單app.js里做三件事定義數(shù)據(jù)、創(chuàng)建 store、把 ComboTree 放進表單。Ext.onReady(function() { // 1. 定義樹形數(shù)據(jù) var treeData [{ id: 1, text: 華東區(qū), expanded: true, children: [{ id: 1-1, text: 上海, leaf: true }, { id: 1-2, text: 杭州, leaf: true }] }, { id: 2, text: 華南區(qū), children: [{ id: 2-1, text: 廣州, leaf: true }, { id: 2-2, text: 深圳, leaf: true }] }]; // 2. 創(chuàng)建 TreeStore var treeStore Ext.create(Ext.data.TreeStore, { root: { expanded: true, children: treeData } }); // 3. 創(chuàng)建表單放入 ComboTree Ext.create(Ext.form.Panel, { renderTo: form-container, title: 區(qū)域選擇, width: 400, bodyPadding: 20, items: [{ xtype: combotree, fieldLabel: 所屬區(qū)域, name: regionId, store: treeStore, displayField: text, valueField: id, allowParentSelect: false, pickerWidth: 300, listeners: { select: function(combo, record) { console.log(選中的節(jié)點, record.get(text)); } } }], buttons: [{ text: 提交, handler: function() { var form this.up(form).getForm(); console.log(form.getValues()); } }] }); });xtype: combotree能生效是因為插件類里定義了alias: widget.combotree。allowParentSelect: false讓“華東區(qū)”“華南區(qū)”這種父節(jié)點不可選只能選到具體的城市。pickerWidth設成 300 是為了讓下拉面板比輸入框?qū)捯稽c樹形結構縮進后文字不容易被截斷。3.3 驗證運行結果打開瀏覽器控制臺如果看到表單渲染出來點擊下拉箭頭能展開樹點擊“上?!焙筝斎肟蝻@示“上?!笨刂婆_打印出選中的節(jié)點說明整條鏈路是通的。如果下拉面板彈不出來先檢查createPicker有沒有返回正確的組件如果樹節(jié)點點不動看itemclick事件有沒有綁定上如果值回填了但顯示不對檢查displayField和valueField是否跟數(shù)據(jù)里的字段名對得上。4. 避坑與排查下拉樹最容易翻車的幾個地方4.1 下拉面板位置偏移或?qū)挾犬惓,F(xiàn)象點擊輸入框后樹面板彈出來了但位置偏到屏幕左上角或者寬度只有幾個像素。原因Ext 的 Picker 在alignPicker階段會讀取輸入框的寬度和位置如果輸入框還沒完成布局比如放在hidden的容器里拿到的尺寸就是 0。解決確保 ComboTree 在可見容器里初始化或者手動調(diào)一次me.alignPicker()。如果寬度還是不對顯式設置pickerWidth別依賴自動計算。4.2 選中父節(jié)點后值能取到但顯示為空現(xiàn)象allowParentSelect: true時選了父節(jié)點getValue()能拿到 id但輸入框里什么都不顯示。原因setRawValue用的字段跟數(shù)據(jù)里的字段名不一致或者父節(jié)點的displayField字段是空的。解決檢查數(shù)據(jù)里每個節(jié)點是否都有text字段父節(jié)點也不例外。如果后端返回的字段叫name就把displayField改成name別硬套默認值。4.3 樹數(shù)據(jù)量大時下拉卡頓現(xiàn)象樹節(jié)點超過幾百個點開下拉要等一兩秒才渲染出來。原因Ext.tree.Panel默認一次性渲染所有可見節(jié)點數(shù)據(jù)量大時 DOM 操作很重。解決給 TreeStore 開啟bufferedRenderer或者把rootVisible設為false減少一層渲染。更徹底的做法是讓后端分頁返回子節(jié)點點開父節(jié)點時再異步加載。4.4 表單重置后下拉樹的值沒清掉現(xiàn)象調(diào)用form.reset()后輸入框清空了但再次點開下拉之前選中的節(jié)點還是高亮狀態(tài)。原因reset只重置了表單字段的值沒有清除樹面板里的選中狀態(tài)。解決在 ComboTree 里重寫reset方法除了調(diào)用父類邏輯還要拿到 picker 里的 selection model執(zhí)行deselectAll()。4.5 在 Ext 6 版本里報createPicker未定義現(xiàn)象升級 Ext 版本后插件直接報錯說createPicker不是函數(shù)。原因Ext 6 之后 Picker 的 API 有調(diào)整部分方法簽名變了。解決對照當前 Ext 版本的Ext.form.field.Picker文檔確認createPicker的返回值要求。常見做法是返回一個帶floating: true的組件如果新版要求返回Ext.Component的子類把 TreePanel 包一層就行。5. 二次開發(fā)把下拉樹改成你要的樣子5.1 支持多選和標簽回填默認的 ComboTree 是單選但業(yè)務里經(jīng)常需要多選比如給一個角色分配多個區(qū)域權限。改造思路是給 TreePanel 加上multiSelect: true然后在onTreeItemClick里收集所有選中節(jié)點用分隔符拼成字符串回填到輸入框。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; } // 多選收集當前所有選中節(jié)點 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(, )); // 多選時不自動收起讓用戶繼續(xù)點 }注意多選模式下不要調(diào)collapse()否則點一個就收起來根本沒法連續(xù)選。另外setValue傳的是逗號拼接的字符串后端接收時按逗號拆開就行。如果后端要求數(shù)組把values.join(,)改成values即可。5.2 異步加載子節(jié)點樹數(shù)據(jù)量大的時候一次性加載全量數(shù)據(jù)不現(xiàn)實。改造 TreeStore讓它在展開節(jié)點時再去請求子節(jié)點var asyncStore Ext.create(Ext.data.TreeStore, { proxy: { type: ajax, url: /api/region/children, reader: { type: json, rootProperty: data } }, root: { id: root, text: 全部區(qū)域, expanded: false }, lazyFill: true });后端接口接收node參數(shù)當前展開的節(jié)點 id返回該節(jié)點的直接子節(jié)點列表。lazyFill: true讓 TreeStore 在節(jié)點展開時才發(fā)請求而不是初始化時就把整棵樹拉下來。這個改造對后端有要求接口要支持按 parentId 查詢返回的數(shù)據(jù)里每個節(jié)點要帶leaf字段標識是否還有下級。5.3 自定義節(jié)點圖標和選中樣式Ext 的樹節(jié)點圖標通過iconCls控制可以在數(shù)據(jù)里給每個節(jié)點指定不同的 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; }選中態(tài)的樣式覆蓋要注意優(yōu)先級Ext 默認的.x-grid-item-selected權重比較高自定義樣式最好加上.x-treepanel前綴或者用!important兜底。我一般會在 CSS 里寫.x-treepanel .x-grid-item-selected { background-color: #e6f7ff; }這樣只影響下拉樹面板不會污染其他表格組件。5.4 一個驗證改造是否生效的小技巧改完源碼后別急著刷新整個頁面。在瀏覽器控制臺里拿到 ComboTree 的實例直接調(diào)方法驗證// 假設表單里 combotree 的 name 是 regionId var combo Ext.ComponentQuery.query(combotree[nameregionId])[0]; // 手動設置值看顯示是否正確 combo.setValue(1-1); // 展開下拉看樹面板是否正常 combo.expand(); // 打印當前選中項 console.log(combo.getPicker().getSelectionModel().getSelection());這樣能快速定位是數(shù)據(jù)問題、渲染問題還是事件綁定問題。從那以后我每次改 Ext 插件都強制走一遍「控制臺拿實例 → 手動調(diào)方法 → 看返回值」的流程比反復刷新頁面猜問題快得多。希望幫到你。本文還有配套的精品資源點擊獲取