]> git.proxmox.com Git - sencha-touch.git/blob - src/src/data/NodeStore.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / data / NodeStore.js
1 /**
2 * @private
3 */
4 Ext.define('Ext.data.NodeStore', {
5 extend: 'Ext.data.Store',
6 alias: 'store.node',
7 requires: ['Ext.data.NodeInterface'],
8
9 config: {
10 /**
11 * @cfg {Ext.data.Model} node The Record you want to bind this Store to. Note that
12 * this record will be decorated with the {@link Ext.data.NodeInterface} if this is not the
13 * case yet.
14 * @accessor
15 */
16 node: null,
17
18 /**
19 * @cfg {Boolean} recursive Set this to `true` if you want this NodeStore to represent
20 * all the descendants of the node in its flat data collection. This is useful for
21 * rendering a tree structure to a DataView and is being used internally by
22 * the TreeView. Any records that are moved, removed, inserted or appended to the
23 * node at any depth below the node this store is bound to will be automatically
24 * updated in this Store's internal flat data structure.
25 * @accessor
26 */
27 recursive: false,
28
29 /**
30 * @cfg {Boolean} rootVisible `false` to not include the root node in this Stores collection.
31 * @accessor
32 */
33 rootVisible: false,
34
35 sorters: undefined,
36 filters: undefined,
37
38 /**
39 * @cfg {Boolean} folderSort
40 * Set to `true` to automatically prepend a leaf sorter.
41 */
42 folderSort: false
43 },
44
45 afterEdit: function(record, modifiedFields) {
46 if (modifiedFields) {
47 if (modifiedFields.indexOf('loaded') !== -1) {
48 return this.add(this.retrieveChildNodes(record));
49 }
50 if (modifiedFields.indexOf('expanded') !== -1) {
51 return this.filter();
52 }
53 if (modifiedFields.indexOf('sorted') !== -1) {
54 return this.sort();
55 }
56 }
57 this.callParent(arguments);
58 },
59
60 onNodeAppend: function(parent, node) {
61 this.add([node].concat(this.retrieveChildNodes(node)));
62 },
63
64 onNodeInsert: function(parent, node) {
65 this.add([node].concat(this.retrieveChildNodes(node)));
66 },
67
68 onNodeRemove: function(parent, node) {
69 this.remove([node].concat(this.retrieveChildNodes(node)));
70 },
71
72 onNodeSort: function() {
73 this.sort();
74 },
75
76 updateFolderSort: function(folderSort) {
77 if (folderSort) {
78 this.setGrouper(function(node) {
79 if (node.isLeaf()) {
80 return 1;
81 }
82 return 0;
83 });
84 } else {
85 this.setGrouper(null);
86 }
87 },
88
89 createDataCollection: function() {
90 var collection = this.callParent();
91 collection.handleSort = Ext.Function.bind(this.handleTreeSort, this, [collection], true);
92 collection.findInsertionIndex = Ext.Function.bind(this.handleTreeInsertionIndex, this, [collection, collection.findInsertionIndex], true);
93 return collection;
94 },
95
96 handleTreeInsertionIndex: function(items, item, collection, originalFn) {
97 return originalFn.call(collection, items, item, this.treeSortFn);
98 },
99
100 handleTreeSort: function(data) {
101 Ext.Array.sort(data, this.treeSortFn);
102 return data;
103 },
104
105 /**
106 * This is a custom tree sorting algorithm. It uses the index property on each node to determine
107 * how to sort siblings. It uses the depth property plus the index to create a weight for each node.
108 * This weight algorithm has the limitation of not being able to go more then 80 levels in depth, or
109 * more then 10k nodes per parent. The end result is a flat collection being correctly sorted based
110 * on this one single sort function.
111 * @param {Ext.data.NodeInterface} node1
112 * @param {Ext.data.NodeInterface} node2
113 * @return {Number}
114 * @private
115 */
116 treeSortFn: function(node1, node2) {
117 // A shortcut for siblings
118 if (node1.parentNode === node2.parentNode) {
119 return (node1.data.index < node2.data.index) ? -1 : 1;
120 }
121
122 // @NOTE: with the following algorithm we can only go 80 levels deep in the tree
123 // and each node can contain 10000 direct children max
124 var weight1 = 0,
125 weight2 = 0,
126 parent1 = node1,
127 parent2 = node2;
128
129 while (parent1) {
130 weight1 += (Math.pow(10, (parent1.data.depth+1) * -4) * (parent1.data.index+1));
131 parent1 = parent1.parentNode;
132 }
133 while (parent2) {
134 weight2 += (Math.pow(10, (parent2.data.depth+1) * -4) * (parent2.data.index+1));
135 parent2 = parent2.parentNode;
136 }
137
138 if (weight1 > weight2) {
139 return 1;
140 } else if (weight1 < weight2) {
141 return -1;
142 }
143 return (node1.data.index > node2.data.index) ? 1 : -1;
144 },
145
146 applyFilters: function(filters) {
147 var me = this;
148 return function(item) {
149 return me.isVisible(item);
150 };
151 },
152
153 applyProxy: function(proxy) {
154 //<debug>
155 if (proxy) {
156 Ext.Logger.warn("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
157 "decorated with the NodeInterface by setting the node config.");
158 }
159 //</debug>
160 },
161
162 applyNode: function(node) {
163 if (node) {
164 node = Ext.data.NodeInterface.decorate(node);
165 }
166 return node;
167 },
168
169 updateNode: function(node, oldNode) {
170 if (oldNode && !oldNode.isDestroyed) {
171 oldNode.un({
172 append : 'onNodeAppend',
173 insert : 'onNodeInsert',
174 remove : 'onNodeRemove',
175 load : 'onNodeLoad',
176 scope: this
177 });
178 oldNode.unjoin(this);
179 }
180
181 if (node) {
182 node.on({
183 scope : this,
184 append : 'onNodeAppend',
185 insert : 'onNodeInsert',
186 remove : 'onNodeRemove',
187 load : 'onNodeLoad'
188 });
189
190 node.join(this);
191
192 var data = [];
193 if (node.childNodes.length) {
194 data = data.concat(this.retrieveChildNodes(node));
195 }
196 if (this.getRootVisible()) {
197 data.push(node);
198 } else if (node.isLoaded() || node.isLoading()) {
199 node.set('expanded', true);
200 }
201
202 this.data.clear();
203 this.fireEvent('clear', this);
204
205 this.suspendEvents();
206 this.add(data);
207 this.resumeEvents();
208
209 if(data.length === 0) {
210 this.loaded = node.loaded = true;
211 }
212
213 this.fireEvent('refresh', this, this.data);
214 }
215 },
216
217 /**
218 * Private method used to deeply retrieve the children of a record without recursion.
219 * @private
220 * @param {Ext.data.NodeInterface} root
221 * @return {Array}
222 */
223 retrieveChildNodes: function(root) {
224 var node = this.getNode(),
225 recursive = this.getRecursive(),
226 added = [],
227 child = root;
228
229 if (!root.childNodes.length || (!recursive && root !== node)) {
230 return added;
231 }
232
233 if (!recursive) {
234 return root.childNodes;
235 }
236
237 while (child) {
238 if (child._added) {
239 delete child._added;
240 if (child === root) {
241 break;
242 } else {
243 child = child.nextSibling || child.parentNode;
244 }
245 } else {
246 if (child !== root) {
247 added.push(child);
248 }
249 if (child.firstChild) {
250 child._added = true;
251 child = child.firstChild;
252 } else {
253 child = child.nextSibling || child.parentNode;
254 }
255 }
256 }
257
258 return added;
259 },
260
261 /**
262 * @param {Object} node
263 * @return {Boolean}
264 */
265 isVisible: function(node) {
266 var parent = node.parentNode;
267
268 if (!this.getRecursive() && parent !== this.getNode()) {
269 return false;
270 }
271
272 while (parent) {
273 if (!parent.isExpanded()) {
274 return false;
275 }
276
277 //we need to check this because for a nodestore the node is not likely to be the root
278 //so we stop going up the chain when we hit the original node as we don't care about any
279 //ancestors above the configured node
280 if (parent === this.getNode()) {
281 break;
282 }
283
284 parent = parent.parentNode;
285 }
286 return true;
287 }
288 });