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();