ExtJS 4 樹

Tree Panel是ExtJS中最多能的組件之一,它很是適合用於展現分層的數據。Tree PanelGrid 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

image

這個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,
    ...
});

The Node Interface 節點接口

上面的例子中咱們在節點上設定了兩三個不一樣的屬性,可是節點究竟是什麼?前面提到,TreePanel綁定了一個TreeStore,Store在ExtJS中的做用是管理Model實例的集合。樹節點是用NodeInterface裝飾的簡單的模型實例。用NodeInterface裝飾Model使Model得到了在樹中使用須要的方法、屬性、字段。下面是個樹節點對象在開發工具中打印的截圖 編程

extjs-4-tree-node-interface.png

關於節點的方法、屬性等,請查看API文檔(ps. 每個學習ExtJS的開發者都應該仔細研讀API文檔,這是最好的教材) api

Visually changing your tree 外觀定製

先嚐試一些簡單的改動。把useArrows設置爲true,Tree Panel就會隱藏前導線使用箭頭表示節點的展開 服務器

image

設置rootVisible屬性爲false,根節點就會被隱藏起來: 數據結構

image

Multiple columns 多列

因爲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
        }]
    }
});

image

這裏面的columns配置項指望獲得一個Ext.grid.column.Column配置,就跟GridPanel同樣的。惟一的不一樣就是Tree Panel須要至少一個treecolumn列,這種列是擁有tree視覺效果的,典型的Tree Panel應該只有一列treecolumn。 ide

fields配置項會傳遞給tree內置生成的store用。dataIndex是如何跟列匹配的請仔細看上面例子中的 namedescription,其實就是和每一個節點附帶的屬性值匹配 工具

若是不配置column,tree會自動生成一列treecolumn,而且它的dataIndextext,而且也自動隱藏了表頭,若是想顯示錶頭,能夠用hideHeaders配置爲false。(LZ注:看到這裏extjs3和4的tree已經有了本質的不一樣,extjs4的tree本質上就是TreeGrid,只是在只有一列的時候,展示形式爲原來的TreePanel)

Adding nodes to the tree 添加節點

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方法展開這個新的父節點。

image

上面的例子利用內聯的方式,亦可:

 
var parent = root.appendChild({
    text: 'Parent 1',
    expanded: true,
    children: [{
        text: 'Child 3',
        leaf: true
    }]
});

有時咱們指望將節點插入到一個特定的位置,而不是在最末端添加。除了appendChild方法,Ext.data.NodeInterface還提供了insertBeforeinsertChild方法。

 
var child = parent.insertChild(0, {
    text: 'Child 2.5',
    leaf: true
});

parent.insertBefore({
    text: 'Child 2.75',
    leaf: true
}, child.nextSibling);

insertChild方法須要一個節點位置,新增的節點將會插入到這個位置。insertBefore方法須要一個節點的引用,新節點將會插入到這個節點以前。

image

NodeInterface也提供了幾個能夠引用到其餘節點的屬性

  • nextSibling
  • previousSibling
  • parentNode
  • lastChild
  • firstChild
  • childNodes

Loading and Saving Tree Data using a Proxy 加載和保存樹上的數據

加載和保存樹上的數據比處理扁平化的數據要複雜一點,由於每一個字段都須要展現層級關係,這一章將會解釋處理這一複雜的工做。

NodeInterface Fields

使用tree數據的時候,最重要的就是理解NodeInterface是如何工做的。每一個tree節點都是一個用NodeInterface裝飾的Model實例。假設有個Person Model,它有兩個字段idname

 
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}
NodeInterface Fields are Reserved Names 節點接口的字段都是保留字

有一點很是重要,就是上面列舉的這些字段都應該看成保留字段。例如,Model中就不容許有一個字段叫作parentId了,由於當Model用在Tree上時,Model的字段會覆蓋NodeInterface的字段。除非這裏有個合法的需求要覆蓋NodeInterface的字段的持久化屬性。

Persistent Fields vs Non-persistent Fields and Overriding the Persistence of Fields 持久化字段和非持久化字段,如何覆蓋持久化屬性

大多數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 內部使用,不要動
Loading Data 加載數據

有兩種加載數據的方式。一次性加載所有節點和分步加載,當節點過多時,一次加載會有性能問題,並且不必定每一個節點都用到。動態分步加載是指在父節點展開的時候加載子節點。

Loading the Entire Tree 一次加載

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 }
    ]
}

最終造成的樹就是這樣

image

須要注意的是:

  • 全部非葉子節點,可是又沒有子節點的,例如上面圖中的Sue,服務器端返回的數據必須loaded屬性設置爲true,不然這個節點會變成可展開的,而且會嘗試向服務器請求它的子節點數據
  • 另一個問題,既然loaded是個默認不持久化的屬性,上面一條說了服務器端要返回loaded爲true,那麼服務器端的其餘返回內容也會影響tree的其餘屬性,好比expanded,這就須要注意了,服務器返回的有些數據可能會致使錯誤,好比若是服務器返回的數據帶有root,和可能會致使錯誤。一般建議除了loadedexpanded,服務器端不要返回其餘會被樹利用的屬性。
Dynamically Loading Children When a Node is 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 }
    ]
}

如今樹會變成這樣:

image

Saving Data 保存數據

建立、更新、刪除節點都由Proxy自動無縫的處理了。

Creating a New Node 建立新節點
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();
Updating an Existing Node 更新節點
1
store.getNodeById(1).set('name', 'Philip');
Removing a Node 刪除節點
1
store.getRootNode().lastChild.remove();
Bulk Operations 批處理

也能夠等建立、更新、刪除了若干個節點以後,由TreeStore的sync方法一次保存所有

1
store.sync();
相關文章
相關標籤/搜索