Tree Panel
是ExtJS中最多能的組件之一,它很是適合用於展現分層的數據。Tree Panel
和Grid Panel
繼承自相同的基類,因此全部從Grid Panel
能得到到的特性、擴展、插件等帶來的好處,在Tree Panel
中也一樣能夠得到。列、列寬調整、拖拽、渲染器、排序、過濾等特性,在兩種組件中都是差很少的工做方式。
讓咱們開始建立一個簡單的樹組件css
Ext.create('Ext.tree.Panel', { renderTo: Ext.getBody(), title: 'Simple Tree', width: 150, height: 150, root: { text: 'Root', expanded: true, children: [ { text: 'Child 1', leaf: true }, { text: 'Child 2', leaf: true }, { text: 'Child 3', expanded: true, children: [ { text: 'Grandchild', leaf: true } ] } ] } });
運行效果如圖 node
這個Tree Panel
直接渲染在document.body上,咱們定義了一個默認展開的根節點,根節點有三個子節點,前兩個子節點是葉子節點,這意味着他們不能擁有本身的子節點了,第三個節點不是葉子節點,它有一個子節點。每一個節點的text
屬性用來設置節點上展現的文字。Tree Panel
內部使用Tree Store
存儲數據。上面的例子中使用了root
配置項做爲使用store的捷徑。若是咱們單獨指定store,代碼像這樣:ajax
var store = Ext.create('Ext.data.TreeStore', { root: { text: 'Root', expanded: true, children: [ { text: 'Child 1', leaf: true }, { text: 'Child 2', leaf: true }, ... ] } }); Ext.create('Ext.tree.Panel', { title: 'Simple Tree', store: store, ... });
上面的例子中咱們在節點上設定了兩三個不一樣的屬性,可是節點究竟是什麼?前面提到,TreePanel綁定了一個TreeStore,Store在ExtJS中的做用是管理Model實例的集合。樹節點是用NodeInterface
裝飾的簡單的模型實例。用NodeInterface
裝飾Model
使Model得到了在樹中使用須要的方法、屬性、字段。下面是個樹節點對象在開發工具中打印的截圖 編程
關於節點的方法、屬性等,請查看API文檔(ps. 每個學習ExtJS的開發者都應該仔細研讀API文檔,這是最好的教材) api
先嚐試一些簡單的改動。把useArrows
設置爲true,Tree Panel
就會隱藏前導線使用箭頭表示節點的展開 服務器
設置rootVisible
屬性爲false,根節點就會被隱藏起來: 數據結構
因爲Tree Panel
也是從Grid Panel
相同的父類繼承的,所以實現多列很容易。app
var tree = Ext.create('Ext.tree.Panel', { renderTo: Ext.getBody(), title: 'TreeGrid', width: 300, height: 150, fields: ['name', 'description'], //注意這裏 columns: [{ xtype: 'treecolumn', text: 'Name', dataIndex: 'name', width: 150, sortable: true }, { text: 'Description', dataIndex: 'description', flex: 1, sortable: true }], root: { name: 'Root', description: 'Root description', expanded: true, children: [{ name: 'Child 1', description: 'Description 1', leaf: true }, { name: 'Child 2', description: 'Description 2', leaf: true }] } });
這裏面的columns
配置項指望獲得一個Ext.grid.column.Column
配置,就跟GridPanel
同樣的。惟一的不一樣就是Tree Panel須要至少一個treecolumn
列,這種列是擁有tree視覺效果的,典型的Tree Panel應該只有一列treecolumn。 ide
fields
配置項會傳遞給tree內置生成的store用。dataIndex
是如何跟列匹配的請仔細看上面例子中的 name
和description
,其實就是和每一個節點附帶的屬性值匹配 工具
若是不配置column,tree會自動生成一列treecolumn,而且它的dataIndex
是text
,而且也自動隱藏了表頭,若是想顯示錶頭,能夠用hideHeaders
配置爲false。(LZ注:看到這裏extjs3和4的tree已經有了本質的不一樣,extjs4的tree本質上就是TreeGrid,只是在只有一列的時候,展示形式爲原來的TreePanel)
tree的根節點不是必須在初始化時設定。後續再添加也能夠:
var tree = Ext.create('Ext.tree.Panel'); tree.setRootNode({ text: 'Root', expanded: true, children: [{ text: 'Child 1', leaf: true }, { text: 'Child 2', leaf: true }] });
儘管對於很小的樹只有默認幾個靜態節點的,這種直接在代碼裏面配置的方式很方便,可是大多數狀況tree仍是有不少節點的。讓咱們看一下如何經過程序添加節點。
var root = tree.getRootNode(); var parent = root.appendChild({ text: 'Parent 1' }); parent.appendChild({ text: 'Child 3', leaf: true }); parent.expand();
每個不是葉節點的節點都有一個appendChild
方法,這個方法接收一個Node類型,或者是Node的配置參數的參數,返回值是新添加的節點對象。上面的例子中也調用了expand
方法展開這個新的父節點。
上面的例子利用內聯的方式,亦可:
var parent = root.appendChild({ text: 'Parent 1', expanded: true, children: [{ text: 'Child 3', leaf: true }] });
有時咱們指望將節點插入到一個特定的位置,而不是在最末端添加。除了appendChild
方法,Ext.data.NodeInterface
還提供了insertBefore
和insertChild
方法。
var child = parent.insertChild(0, { text: 'Child 2.5', leaf: true }); parent.insertBefore({ text: 'Child 2.75', leaf: true }, child.nextSibling);
insertChild
方法須要一個節點位置,新增的節點將會插入到這個位置。insertBefore
方法須要一個節點的引用,新節點將會插入到這個節點以前。
NodeInterface也提供了幾個能夠引用到其餘節點的屬性
nextSibling
previousSibling
parentNode
lastChild
firstChild
childNodes
加載和保存樹上的數據比處理扁平化的數據要複雜一點,由於每一個字段都須要展現層級關係,這一章將會解釋處理這一複雜的工做。
使用tree數據的時候,最重要的就是理解NodeInterface
是如何工做的。每一個tree節點都是一個用NodeInterface
裝飾的Model
實例。假設有個Person Model,它有兩個字段id
和name
:
Ext.define('Person', { extend: 'Ext.data.Model', fields: [ { name: 'id', type: 'int' }, { name: 'name', type: 'string' } ] });
若是隻作這些,Person Model還只是普通的Model,若是取它的字段個數:
1
console.log(Person.prototype.fields.getCount()); //輸出 '2'
可是若是將Person Model應用到TreeStore
之中後,就會有些變化:
var store = Ext.create('Ext.data.TreeStore', { model: 'Person', root: { name: 'Phil' } }); console.log(Person.prototype.fields.getCount()); //輸出 '24'
被TreeStore
使用以後,Person多了22個字段。全部這些字段都是在NodeInterface
中定義的,TreeStore初次實例化Person的時候,這些字段會被加入到Person的原型鏈中。
那這22個字段都是什麼,有什麼用處?讓咱們簡要的看一下NodeInterface
,它用以下字段裝飾Model,這些字段都是存儲tree相關結構和狀態的:
{name: 'parentId', type: idType, defaultValue: null}, {name: 'index', type: 'int', defaultValue: null, persist: false}, {name: 'depth', type: 'int', defaultValue: 0, persist: false}, {name: 'expanded', type: 'bool', defaultValue: false, persist: false}, {name: 'expandable', type: 'bool', defaultValue: true, persist: false}, {name: 'checked', type: 'auto', defaultValue: null, persist: false}, {name: 'leaf', type: 'bool', defaultValue: false}, {name: 'cls', type: 'string', defaultValue: null, persist: false}, {name: 'iconCls', type: 'string', defaultValue: null, persist: false}, {name: 'icon', type: 'string', defaultValue: null, persist: false}, {name: 'root', type: 'boolean', defaultValue: false, persist: false}, {name: 'isLast', type: 'boolean', defaultValue: false, persist: false}, {name: 'isFirst', type: 'boolean', defaultValue: false, persist: false}, {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false}, {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false}, {name: 'loaded', type: 'boolean', defaultValue: false, persist: false}, {name: 'loading', type: 'boolean', defaultValue: false, persist: false}, {name: 'href', type: 'string', defaultValue: null, persist: false}, {name: 'hrefTarget', type: 'string', defaultValue: null, persist: false}, {name: 'qtip', type: 'string', defaultValue: null, persist: false}, {name: 'qtitle', type: 'string', defaultValue: null, persist: false}, {name: 'children', type: 'auto', defaultValue: null, persist: false}
有一點很是重要,就是上面列舉的這些字段都應該看成保留字段。例如,Model中就不容許有一個字段叫作parentId
了,由於當Model用在Tree上時,Model的字段會覆蓋NodeInterface的字段。除非這裏有個合法的需求要覆蓋NodeInterface的字段的持久化屬性。
大多數NodeInterface的字段都默認是persist: false
不持久化的。非持久化字段在TreeStore作保存操做的時候不會被保存。大多數狀況默認的配置是符合需求的,可是若是真的須要覆蓋持久化設置,下面展現瞭如何覆蓋持久化配置。當覆蓋持久化配置的時候,只改變presist
屬性,其餘任何屬性都不要修改
// overriding the persistence of NodeInterface fields in a Model definition Ext.define('Person', { extend: 'Ext.data.Model', fields: [ // Person fields { name: 'id', type: 'int' }, { name: 'name', type: 'string' } // override a non-persistent NodeInterface field to make it persistent { name: 'iconCls', type: 'string', defaultValue: null, persist: true }, ] });
讓咱們深刻的看一下NodeInterface的字段,列舉一下可能須要覆蓋persist
屬性的情景。下面的每一個例子都假設使用了Server Proxy
除非提示不使用。(注:這須要有一些server端編程的知識)
默認持久化的:
parentId
- 用來指定父節點的id,這個字段應該老是持久化,不要覆蓋它 leaf
- 用來指出這個節點是否是葉子節點,所以決定了節點是否是能夠有子節點,最好不要改變它的持久化設置默認不持久化的:
index
- 用來指出當前節點在父節點的全部子節點中的位置,當有節點插入或者移除,它的全部鄰居節點的位置都會更新,若是須要,能夠用這個屬性去持久化樹節點的排列順序。然而若是服務器端使用另外的排序方法,最好把這個字段保留爲非持久化的,當使用WebStorage Proxy
做爲存儲,且須要保留節點順序,那必定要設置爲持久化的。若是使用了本地排序,建議設置非持久化,由於本地排序會改變節點的index
屬性 depth
用來存儲節點在樹中的層級,若是server須要保存節點層級請開啓持久化。使用WebStorage Proxy
的時候建議不要持久化,會多佔用存儲空間。 checked
若是在tree使用checkbox
特性,看業務需求來開啓持久化 expanded
存儲節點的展開收起狀態,要不要持久化看業務需求 expandable
內部使用,不要變動持久化配置 cls
用來給節點增長css類,看業務需求 iconCls
用來給節點icon增長css類,看業務需求 icon
用來自定義節點,看業務需求 root
對根節點的引用,不要變更配置 isLast
標識最後一個節點,此配置通常不須要變更 isFirst
標識第一個節點,此配置通常不須要變更 allowDrop
用來標識可放的節點,此配置不要動 allowDrag
用來標識可拖的節點,此配置不要動 loaded
用來標識子節點是否加載完成,此配置不要動 loading
用來標識子節點是否正在加載中,此配置不要動 href
用來指定節點連接,此配置看業務需求變更 hrefTarget
節點連接的target,此配置看業務需求變更 qtip
指定tooltip
文字,此配置看業務需求變更 qtitle
指定tooltip
的title,此配置看業務需求變更 children
內部使用,不要動有兩種加載數據的方式。一次性加載所有節點和分步加載,當節點過多時,一次加載會有性能問題,並且不必定每一個節點都用到。動態分步加載是指在父節點展開的時候加載子節點。
Tree的內部實現是隻有節點展開的時候加載數據。然而所有的層級關係能夠經過一個嵌套的數據結構一次所有加載,只要配置root節點是展開的便可
Ext.define('Person', { extend: 'Ext.data.Model', fields: [ { name: 'id', type: 'int' }, { name: 'name', type: 'string' } ], proxy: { type: 'ajax', api: { create: 'createPersons', read: 'readPersons', update: 'updatePersons', destroy: 'destroyPersons' } } }); var store = Ext.create('Ext.data.TreeStore', { model: 'Person', root: { name: 'People', expanded: true } }); Ext.create('Ext.tree.Panel', { renderTo: Ext.getBody(), width: 300, height: 200, title: 'People', store: store, columns: [ { xtype: 'treecolumn', header: 'Name', dataIndex: 'name', flex: 1 } ] });
假設readPersons
返回數據以下
{ "success": true, "children": [ { "id": 1, "name": "Phil", "leaf": true }, { "id": 2, "name": "Nico", "expanded": true, "children": [ { "id": 3, "name": "Mitchell", "leaf": true } ]}, { "id": 4, "name": "Sue", "loaded": true } ] }
最終造成的樹就是這樣
須要注意的是:
Sue
,服務器端返回的數據必須是loaded
屬性設置爲true
,不然這個節點會變成可展開的,而且會嘗試向服務器請求它的子節點數據 loaded
是個默認不持久化的屬性,上面一條說了服務器端要返回loaded
爲true,那麼服務器端的其餘返回內容也會影響tree的其餘屬性,好比expanded
,這就須要注意了,服務器返回的有些數據可能會致使錯誤,好比若是服務器返回的數據帶有root
,和可能會致使錯誤。一般建議除了loaded
和expanded
,服務器端不要返回其餘會被樹利用的屬性。對於節點很是多的樹,一般指望動態加載,當點擊父節點的展開icon時再向服務器請求子節點數據。例如上面的例子中假設Sue
沒有被服務器端返回的數據設置爲loaded true
,那麼當它的展開icon點擊時,樹的proxy會嘗試向讀取api readPersons
請求一個這樣的url
1
/readPersons?node=4
這意思是告訴服務器取得id爲4的節點的子節點,返回的數據格式跟一次加載相同:
{ "success": true, "children": [ { "id": 5, "name": "Evan", "leaf": true } ] }
如今樹會變成這樣:
建立、更新、刪除節點都由Proxy自動無縫的處理了。
1 2 3
// Create a new node and append it to the tree: var newPerson = Ext.create('Person', { name: 'Nige', leaf: true }); store.getNodeById(2).appendChild(newPerson);
因爲Model中定義過proxy,Model的save
方法能夠用來持久化節點數據:
1
newPerson.save();
1
store.getNodeById(1).set('name', 'Philip');
1
store.getRootNode().lastChild.remove();
也能夠等建立、更新、刪除了若干個節點以後,由TreeStore的sync
方法一次保存所有
1
store.sync();