Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / data / NodeStore.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.data.NodeStore
17  * @extends Ext.data.AbstractStore
18  * Node Store
19  * @ignore
20  */
21 Ext.define('Ext.data.NodeStore', {
22     extend: 'Ext.data.Store',
23     alias: 'store.node',
24     requires: ['Ext.data.NodeInterface'],
25     
26     /**
27      * @cfg {Ext.data.Model} node
28      * The Record you want to bind this Store to. Note that
29      * this record will be decorated with the Ext.data.NodeInterface if this is not the
30      * case yet.
31      */
32     node: null,
33     
34     /**
35      * @cfg {Boolean} recursive
36      * Set this to true if you want this NodeStore to represent
37      * all the descendents of the node in its flat data collection. This is useful for
38      * rendering a tree structure to a DataView and is being used internally by
39      * the TreeView. Any records that are moved, removed, inserted or appended to the
40      * node at any depth below the node this store is bound to will be automatically
41      * updated in this Store's internal flat data structure.
42      */
43     recursive: false,
44     
45     /** 
46      * @cfg {Boolean} rootVisible
47      * False to not include the root node in this Stores collection.
48      */    
49     rootVisible: false,
50     
51     constructor: function(config) {
52         var me = this,
53             node;
54             
55         config = config || {};
56         Ext.apply(me, config);
57         
58         //<debug>
59         if (Ext.isDefined(me.proxy)) {
60             Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
61                             "decorated with the NodeInterface by setting the node config.");
62         }
63         //</debug>
64
65         config.proxy = {type: 'proxy'};
66         me.callParent([config]);
67
68         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
69         
70         node = me.node;
71         if (node) {
72             me.node = null;
73             me.setNode(node);
74         }
75     },
76     
77     setNode: function(node) {
78         var me = this;
79         
80         if (me.node && me.node != node) {
81             // We want to unbind our listeners on the old node
82             me.mun(me.node, {
83                 expand: me.onNodeExpand,
84                 collapse: me.onNodeCollapse,
85                 append: me.onNodeAppend,
86                 insert: me.onNodeInsert,
87                 remove: me.onNodeRemove,
88                 sort: me.onNodeSort,
89                 scope: me
90             });
91             me.node = null;
92         }
93         
94         if (node) {
95             Ext.data.NodeInterface.decorate(node);
96             me.removeAll();
97             if (me.rootVisible) {
98                 me.add(node);
99             }
100             me.mon(node, {
101                 expand: me.onNodeExpand,
102                 collapse: me.onNodeCollapse,
103                 append: me.onNodeAppend,
104                 insert: me.onNodeInsert,
105                 remove: me.onNodeRemove,
106                 sort: me.onNodeSort,
107                 scope: me
108             });
109             me.node = node;
110             if (node.isExpanded() && node.isLoaded()) {
111                 me.onNodeExpand(node, node.childNodes, true);
112             }
113         }
114     },
115     
116     onNodeSort: function(node, childNodes) {
117         var me = this;
118         
119         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
120             me.onNodeCollapse(node, childNodes, true);
121             me.onNodeExpand(node, childNodes, true);
122         }
123     },
124     
125     onNodeExpand: function(parent, records, suppressEvent) {
126         var me = this,
127             insertIndex = me.indexOf(parent) + 1,
128             ln = records ? records.length : 0,
129             i, record;
130             
131         if (!me.recursive && parent !== me.node) {
132             return;
133         }
134         
135         if (!me.isVisible(parent)) {
136             return;
137         }
138
139         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
140             return;
141         }
142         
143         if (ln) {
144             me.insert(insertIndex, records);
145             for (i = 0; i < ln; i++) {
146                 record = records[i];
147                 if (record.isExpanded()) {
148                     if (record.isLoaded()) {
149                         // Take a shortcut                        
150                         me.onNodeExpand(record, record.childNodes, true);
151                     }
152                     else {
153                         record.set('expanded', false);
154                         record.expand();
155                     }
156                 }
157             }
158         }
159
160         if (!suppressEvent) {
161             me.fireEvent('expand', parent, records);
162         }
163     },
164
165     onNodeCollapse: function(parent, records, suppressEvent) {
166         var me = this,
167             ln = records.length,
168             collapseIndex = me.indexOf(parent) + 1,
169             i, record;
170             
171         if (!me.recursive && parent !== me.node) {
172             return;
173         }
174         
175         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
176             return;
177         }
178
179         for (i = 0; i < ln; i++) {
180             record = records[i];
181             me.remove(record);
182             if (record.isExpanded()) {
183                 me.onNodeCollapse(record, record.childNodes, true);
184             }
185         }
186         
187         if (!suppressEvent) {
188             me.fireEvent('collapse', parent, records, collapseIndex);
189         }
190     },
191     
192     onNodeAppend: function(parent, node, index) {
193         var me = this,
194             refNode, sibling;
195
196         if (me.isVisible(node)) {
197             if (index === 0) {
198                 refNode = parent;
199             } else {
200                 sibling = node.previousSibling;
201                 while (sibling.isExpanded() && sibling.lastChild) {
202                     sibling = sibling.lastChild;
203                 }
204                 refNode = sibling;
205             }
206             me.insert(me.indexOf(refNode) + 1, node);
207             if (!node.isLeaf() && node.isExpanded()) {
208                 if (node.isLoaded()) {
209                     // Take a shortcut                        
210                     me.onNodeExpand(node, node.childNodes, true);
211                 }
212                 else {
213                     node.set('expanded', false);
214                     node.expand();
215                 }
216             }
217         } 
218     },
219     
220     onNodeInsert: function(parent, node, refNode) {
221         var me = this,
222             index = this.indexOf(refNode);
223             
224         if (index != -1 && me.isVisible(node)) {
225             me.insert(index, node);
226             if (!node.isLeaf() && node.isExpanded()) {
227                 if (node.isLoaded()) {
228                     // Take a shortcut                        
229                     me.onNodeExpand(node, node.childNodes, true);
230                 }
231                 else {
232                     node.set('expanded', false);
233                     node.expand();
234                 }
235             }
236         }
237     },
238     
239     onNodeRemove: function(parent, node, index) {
240         var me = this;
241         if (me.indexOf(node) != -1) {
242             if (!node.isLeaf() && node.isExpanded()) {
243                 me.onNodeCollapse(node, node.childNodes, true);
244             }            
245             me.remove(node);
246         }
247     },
248     
249     isVisible: function(node) {
250         var parent = node.parentNode;
251         while (parent) {
252             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
253                 return true;
254             }
255             
256             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
257                 return false;
258             }
259             
260             parent = parent.parentNode;
261         }
262         return true;
263     }
264 });