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