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:
23 * var store = Ext.create('Ext.data.TreeStore', {
27 * { text: "detention", leaf: true },
28 * { text: "homework", expanded: true, children: [
29 * { text: "book report", leaf: true },
30 * { text: "alegrbra", leaf: true}
32 * { text: "buy lottery tickets", leaf: true }
37 * Ext.create('Ext.tree.Panel', {
38 * title: 'Simple Tree',
43 * renderTo: Ext.getBody()
46 * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
47 * {@link Ext.data.NodeInterface NodeInterface} config options.
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.
67 * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
72 * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
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.
91 * @cfg {Boolean} displayField The field inside the model that will be used as the node's 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, [
195 * @alias Ext.data.Store#beforeload
201 * @alias Ext.data.Store#load
209 * @alias Ext.data.TreeStore#append
211 append: me.createRelayer('itemappend'),
215 * @alias Ext.data.TreeStore#remove
217 remove: me.createRelayer('itemremove'),
221 * @alias Ext.data.TreeStore#move
223 move: me.createRelayer('itemmove'),
227 * @alias Ext.data.TreeStore#insert
229 insert: me.createRelayer('iteminsert'),
232 * @event beforeitemappend
233 * @alias Ext.data.TreeStore#beforeappend
235 beforeappend: me.createRelayer('beforeitemappend'),
238 * @event beforeitemremove
239 * @alias Ext.data.TreeStore#beforeremove
241 beforeremove: me.createRelayer('beforeitemremove'),
244 * @event beforeitemmove
245 * @alias Ext.data.TreeStore#beforemove
247 beforemove: me.createRelayer('beforeitemmove'),
250 * @event beforeiteminsert
251 * @alias Ext.data.TreeStore#beforeinsert
253 beforeinsert: me.createRelayer('beforeiteminsert'),
257 * @alias Ext.data.TreeStore#expand
259 expand: me.createRelayer('itemexpand'),
262 * @event itemcollapse
263 * @alias Ext.data.TreeStore#collapse
265 collapse: me.createRelayer('itemcollapse'),
268 * @event beforeitemexpand
269 * @alias Ext.data.TreeStore#beforeexpand
271 beforeexpand: me.createRelayer('beforeitemexpand'),
274 * @event beforeitemcollapse
275 * @alias Ext.data.TreeStore#beforecollapse
277 beforecollapse: me.createRelayer('beforeitemcollapse')
280 // If the user specifies the headers collection manually then dont inject our own
282 if (me.initialConfig.hideHeaders === undefined) {
283 me.hideHeaders = true;
286 xtype : 'treecolumn',
289 dataIndex: me.displayField
296 me.cls = cls.join(' ');
299 me.relayEvents(me.getView(), [
302 * Fires when a node with a checkbox's checked property changes
303 * @param {Ext.data.Model} node The node who's checked property was changed
304 * @param {Boolean} checked The node's new checked state
309 // If the root is not visible and there is no rootnode defined, then just lets load the store
310 if (!me.getView().rootVisible && !me.getRootNode()) {
322 * Sets root node of this tree.
323 * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
324 * @return {Ext.data.NodeInterface} The new root
326 setRootNode: function() {
327 return this.store.setRootNode.apply(this.store, arguments);
331 * Returns the root node for this tree.
332 * @return {Ext.data.NodeInterface}
334 getRootNode: function() {
335 return this.store.getRootNode();
338 onRootChange: function(root) {
339 this.view.setRootNode(root);
343 * Retrieve an array of checked records.
344 * @return {Ext.data.Model[]} An array containing the checked records
346 getChecked: function() {
347 return this.getView().getChecked();
350 isItemChecked: function(rec) {
351 return rec.get('checked');
356 * @param {Function} callback (optional) A function to execute when the expand finishes.
357 * @param {Object} scope (optional) The scope of the callback function
359 expandAll : function(callback, scope) {
360 var root = this.getRootNode(),
361 animate = this.enableAnimations,
362 view = this.getView();
365 view.beginBulkUpdate();
367 root.expand(true, callback, scope);
369 view.endBulkUpdate();
376 * @param {Function} callback (optional) A function to execute when the collapse finishes.
377 * @param {Object} scope (optional) The scope of the callback function
379 collapseAll : function(callback, scope) {
380 var root = this.getRootNode(),
381 animate = this.enableAnimations,
382 view = this.getView();
386 view.beginBulkUpdate();
388 if (view.rootVisible) {
389 root.collapse(true, callback, scope);
391 root.collapseChildren(true, callback, scope);
394 view.endBulkUpdate();
400 * Expand the tree to the path of a particular node.
401 * @param {String} path The path to expand. The path should include a leading separator.
402 * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
403 * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
404 * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
405 * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
406 * @param {Object} scope (optional) The scope of the callback function
408 expandPath: function(path, field, separator, callback, scope) {
410 current = me.getRootNode(),
416 field = field || me.getRootNode().idProperty;
417 separator = separator || '/';
419 if (Ext.isEmpty(path)) {
420 Ext.callback(callback, scope || me, [false, null]);
424 keys = path.split(separator);
425 if (current.get(field) != keys[1]) {
427 Ext.callback(callback, scope || me, [false, current]);
431 expander = function(){
432 if (++index === keys.length) {
433 Ext.callback(callback, scope || me, [true, current]);
436 var node = current.findChild(field, keys[index]);
438 Ext.callback(callback, scope || me, [false, current]);
442 current.expand(false, expander);
444 current.expand(false, expander);
448 * Expand the tree to the path of a particular node, then select it.
449 * @param {String} path The path to select. The path should include a leading separator.
450 * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
451 * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
452 * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
453 * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
454 * @param {Object} scope (optional) The scope of the callback function
456 selectPath: function(path, field, separator, callback, scope) {
461 field = field || me.getRootNode().idProperty;
462 separator = separator || '/';
464 keys = path.split(separator);
467 me.expandPath(keys.join(separator), field, separator, function(success, node){
468 var doSuccess = false;
469 if (success && node) {
470 node = node.findChild(field, last);
472 me.getSelectionModel().select(node);
473 Ext.callback(callback, scope || me, [true, node]);
476 } else if (node === me.getRootNode()) {
479 Ext.callback(callback, scope || me, [doSuccess, node]);