3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * The TreePanel provides tree-structured UI representation of tree-structured data.
17 * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
18 * multiple columns through the {@link #columns} configuration.
20 * Simple TreePanel using inline data.
22 * {@img Ext.tree.Panel/Ext.tree.Panel1.png Ext.tree.Panel component}
26 * var store = Ext.create('Ext.data.TreeStore', {
30 * { text: "detention", leaf: true },
31 * { text: "homework", expanded: true, children: [
32 * { text: "book report", leaf: true },
33 * { text: "alegrbra", leaf: true}
35 * { text: "buy lottery tickets", leaf: true }
40 * Ext.create('Ext.tree.Panel', {
41 * title: 'Simple Tree',
46 * renderTo: Ext.getBody()
49 Ext.define('Ext.tree.Panel', {
50 extend: 'Ext.panel.Table',
51 alias: 'widget.treepanel',
52 alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
53 requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
57 treeCls: Ext.baseCSSPrefix + 'tree-panel',
59 deferRowRender: false,
62 * @cfg {Boolean} lines False to disable tree lines. Defaults to true.
67 * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree. Defaults to false.
72 * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded. Defaults to false.
82 * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
86 * @cfg {Boolean} rootVisible False to hide the root node. Defaults to true.
91 * @cfg {Boolean} displayField The field inside the model that will be used as the node's text. Defaults to 'text'.
96 * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
97 * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
98 * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
99 * to that store. For example:
101 * Ext.create('Ext.tree.Panel', {
102 * title: 'Simple Tree',
107 * { text: "Child 1", leaf: true },
108 * { text: "Child 2", leaf: true }
111 * renderTo: Ext.getBody()
116 // Required for the Lockable Mixin. These are the configurations which will be copied to the
117 // normal and locked sub tablepanels
118 normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
119 lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
122 * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
126 * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
129 constructor: function(config) {
130 config = config || {};
131 if (config.animate === undefined) {
132 config.animate = Ext.enableFx;
134 this.enableAnimations = config.animate;
135 delete config.animate;
137 this.callParent([config]);
140 initComponent: function() {
145 cls.push(Ext.baseCSSPrefix + 'tree-arrows');
150 cls.push(Ext.baseCSSPrefix + 'tree-lines');
151 } else if (!me.useArrows) {
152 cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
155 if (Ext.isString(me.store)) {
156 me.store = Ext.StoreMgr.lookup(me.store);
157 } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
158 me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
162 folderSort: me.folderSort
164 } else if (me.root) {
165 me.store = Ext.data.StoreManager.lookup(me.store);
166 me.store.setRootNode(me.root);
167 if (me.folderSort !== undefined) {
168 me.store.folderSort = me.folderSort;
173 // I'm not sure if we want to this. It might be confusing
174 // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
175 // me.rootVisible = false;
178 me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
179 rootVisible: me.rootVisible,
180 animate: me.enableAnimations,
181 singleExpand: me.singleExpand,
182 node: me.store.getRootNode(),
183 hideHeaders: me.hideHeaders
188 rootchange: me.onRootChange,
192 me.relayEvents(me.store, [
196 * @param {Ext.data.Store} store This Store
197 * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
203 * Fires whenever the store reads data from a remote data source.
204 * @param {Ext.data.store} this
205 * @param {Array} records An array of records
206 * @param {Boolean} successful True if the operation was successful.
214 * Fires when a new child node is appended to a node in the tree.
215 * @param {Tree} tree The owner tree
216 * @param {Node} parent The parent node
217 * @param {Node} node The newly appended node
218 * @param {Number} index The index of the newly appended node
220 append: me.createRelayer('itemappend'),
224 * Fires when a child node is removed from a node in the tree
225 * @param {Tree} tree The owner tree
226 * @param {Node} parent The parent node
227 * @param {Node} node The child node removed
229 remove: me.createRelayer('itemremove'),
233 * Fires when a node is moved to a new location in the tree
234 * @param {Tree} tree The owner tree
235 * @param {Node} node The node moved
236 * @param {Node} oldParent The old parent of this node
237 * @param {Node} newParent The new parent of this node
238 * @param {Number} index The index it was moved to
240 move: me.createRelayer('itemmove'),
244 * Fires when a new child node is inserted in a node in tree
245 * @param {Tree} tree The owner tree
246 * @param {Node} parent The parent node
247 * @param {Node} node The child node inserted
248 * @param {Node} refNode The child node the node was inserted before
250 insert: me.createRelayer('iteminsert'),
253 * @event beforeitemappend
254 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
255 * @param {Tree} tree The owner tree
256 * @param {Node} parent The parent node
257 * @param {Node} node The child node to be appended
259 beforeappend: me.createRelayer('beforeitemappend'),
262 * @event beforeitemremove
263 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
264 * @param {Tree} tree The owner tree
265 * @param {Node} parent The parent node
266 * @param {Node} node The child node to be removed
268 beforeremove: me.createRelayer('beforeitemremove'),
271 * @event beforeitemmove
272 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
273 * @param {Tree} tree The owner tree
274 * @param {Node} node The node being moved
275 * @param {Node} oldParent The parent of the node
276 * @param {Node} newParent The new parent the node is moving to
277 * @param {Number} index The index it is being moved to
279 beforemove: me.createRelayer('beforeitemmove'),
282 * @event beforeiteminsert
283 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
284 * @param {Tree} tree The owner tree
285 * @param {Node} parent The parent node
286 * @param {Node} node The child node to be inserted
287 * @param {Node} refNode The child node the node is being inserted before
289 beforeinsert: me.createRelayer('beforeiteminsert'),
293 * Fires when a node is expanded.
294 * @param {Node} this The expanding node
296 expand: me.createRelayer('itemexpand'),
299 * @event itemcollapse
300 * Fires when a node is collapsed.
301 * @param {Node} this The collapsing node
303 collapse: me.createRelayer('itemcollapse'),
306 * @event beforeitemexpand
307 * Fires before a node is expanded.
308 * @param {Node} this The expanding node
310 beforeexpand: me.createRelayer('beforeitemexpand'),
313 * @event beforeitemcollapse
314 * Fires before a node is collapsed.
315 * @param {Node} this The collapsing node
317 beforecollapse: me.createRelayer('beforeitemcollapse')
320 // If the user specifies the headers collection manually then dont inject our own
322 if (me.initialConfig.hideHeaders === undefined) {
323 me.hideHeaders = true;
326 xtype : 'treecolumn',
329 dataIndex: me.displayField
336 me.cls = cls.join(' ');
339 me.relayEvents(me.getView(), [
342 * Fires when a node with a checkbox's checked property changes
343 * @param {Ext.data.Model} node The node who's checked property was changed
344 * @param {Boolean} checked The node's new checked state
349 // If the root is not visible and there is no rootnode defined, then just lets load the store
350 if (!me.getView().rootVisible && !me.getRootNode()) {
361 setRootNode: function() {
362 return this.store.setRootNode.apply(this.store, arguments);
365 getRootNode: function() {
366 return this.store.getRootNode();
369 onRootChange: function(root) {
370 this.view.setRootNode(root);
374 * Retrieve an array of checked records.
375 * @return {Array} An array containing the checked records
377 getChecked: function() {
378 return this.getView().getChecked();
381 isItemChecked: function(rec) {
382 return rec.get('checked');
387 * @param {Function} callback (optional) A function to execute when the expand finishes.
388 * @param {Object} scope (optional) The scope of the callback function
390 expandAll : function(callback, scope) {
391 var root = this.getRootNode();
393 root.expand(true, callback, scope);
399 * @param {Function} callback (optional) A function to execute when the collapse finishes.
400 * @param {Object} scope (optional) The scope of the callback function
402 collapseAll : function(callback, scope) {
403 var root = this.getRootNode();
405 if (this.getView().rootVisible) {
406 root.collapse(true, callback, scope);
409 root.collapseChildren(true, callback, scope);
415 * Expand the tree to the path of a particular node.
416 * @param {String} path The path to expand. The path should include a leading separator.
417 * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
418 * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
419 * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
420 * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
421 * @param {Object} scope (optional) The scope of the callback function
423 expandPath: function(path, field, separator, callback, scope) {
425 current = me.getRootNode(),
431 field = field || me.getRootNode().idProperty;
432 separator = separator || '/';
434 if (Ext.isEmpty(path)) {
435 Ext.callback(callback, scope || me, [false, null]);
439 keys = path.split(separator);
440 if (current.get(field) != keys[1]) {
442 Ext.callback(callback, scope || me, [false, current]);
446 expander = function(){
447 if (++index === keys.length) {
448 Ext.callback(callback, scope || me, [true, current]);
451 var node = current.findChild(field, keys[index]);
453 Ext.callback(callback, scope || me, [false, current]);
457 current.expand(false, expander);
459 current.expand(false, expander);
463 * Expand the tree to the path of a particular node, then selecti t.
464 * @param {String} path The path to select. The path should include a leading separator.
465 * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
466 * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
467 * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
468 * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
469 * @param {Object} scope (optional) The scope of the callback function
471 selectPath: function(path, field, separator, callback, scope) {
476 field = field || me.getRootNode().idProperty;
477 separator = separator || '/';
479 keys = path.split(separator);
482 me.expandPath(keys.join(separator), field, separator, function(success, node){
483 var doSuccess = false;
484 if (success && node) {
485 node = node.findChild(field, last);
487 me.getSelectionModel().select(node);
488 Ext.callback(callback, scope || me, [true, node]);
491 } else if (node === me.getRootNode()) {
494 Ext.callback(callback, scope || me, [doSuccess, node]);