TreeStore.html
24.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js"><span id='Ext-data-TreeStore'>/**
</span> * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
* It provides convenience methods for loading nodes, as well as the ability to use
* the hierarchical tree structure combined with a store. This class is generally used
* in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
* the Tree for convenience.
*
* # Using Models
*
* If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
* The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
* in the {@link Ext.data.NodeInterface} documentation.
*
* # Reading Nested Data
*
* For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
* so the reader can find nested data for each node (if a root is not specified, it will default to
* 'children'). This will tell the tree to look for any nested tree nodes by the same keyword, i.e., 'children'.
* If a root is specified in the config make sure that any nested nodes with children have the same name.
* Note that setting {@link #defaultRootProperty} accomplishes the same thing.
*/
Ext.define('Ext.data.TreeStore', {
extend: 'Ext.data.AbstractStore',
alias: 'store.tree',
requires: [
'Ext.util.Sorter',
'Ext.data.Tree',
'Ext.data.NodeInterface'
],
<span id='Ext-data-TreeStore-cfg-root'> /**
</span> * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
* The root node for this store. For example:
*
* root: {
* expanded: true,
* text: "My Root",
* children: [
* { text: "Child 1", leaf: true },
* { text: "Child 2", expanded: true, children: [
* { text: "GrandChild", leaf: true }
* ] }
* ]
* }
*
* Setting the `root` config option is the same as calling {@link #setRootNode}.
*/
<span id='Ext-data-TreeStore-cfg-clearOnLoad'> /**
</span> * @cfg {Boolean} [clearOnLoad=true]
* Remove previously existing child nodes before loading.
*/
clearOnLoad : true,
<span id='Ext-data-TreeStore-cfg-clearRemovedOnLoad'> /**
</span> * @cfg {Boolean} [clearRemovedOnLoad=true]
* If `true`, when a node is reloaded, any records in the {@link #removed} record collection that were previously descendants of the node being reloaded will be cleared from the {@link #removed} collection.
* Only applicable if {@link #clearOnLoad} is `true`.
*/
clearRemovedOnLoad: true,
<span id='Ext-data-TreeStore-cfg-nodeParam'> /**
</span> * @cfg {String} [nodeParam="node"]
* The name of the parameter sent to the server which contains the identifier of the node.
*/
nodeParam: 'node',
<span id='Ext-data-TreeStore-cfg-defaultRootId'> /**
</span> * @cfg {String} [defaultRootId="root"]
* The default root id.
*/
defaultRootId: 'root',
<span id='Ext-data-TreeStore-cfg-defaultRootProperty'> /**
</span> * @cfg {String} [defaultRootProperty="children"]
* The root property to specify on the reader if one is not explicitly defined.
*/
defaultRootProperty: 'children',
// Keep a copy of the default so we know if it's been changed in a subclass/config
rootProperty: 'children',
<span id='Ext-data-TreeStore-cfg-folderSort'> /**
</span> * @cfg {Boolean} [folderSort=false]
* Set to true to automatically prepend a leaf sorter.
*/
folderSort: false,
constructor: function(config) {
var me = this,
root,
fields,
defaultRoot;
config = Ext.apply({}, config);
<span id='Ext-data-TreeStore-property-fields'> /**
</span> * If we have no fields declare for the store, add some defaults.
* These will be ignored if a model is explicitly specified.
*/
fields = config.fields || me.fields;
if (!fields) {
config.fields = [
{name: 'text', type: 'string'}
];
defaultRoot = config.defaultRootProperty || me.defaultRootProperty;
if (defaultRoot !== me.defaultRootProperty) {
config.fields.push({
name: defaultRoot,
type: 'auto',
defaultValue: null,
persist: false
});
}
}
me.callParent([config]);
// We create our data tree.
me.tree = new Ext.data.Tree();
me.relayEvents(me.tree, [
<span id='Ext-data-TreeStore-event-append'> /**
</span> * @event append
* @inheritdoc Ext.data.Tree#append
*/
"append",
<span id='Ext-data-TreeStore-event-remove'> /**
</span> * @event remove
* @inheritdoc Ext.data.Tree#remove
*/
"remove",
<span id='Ext-data-TreeStore-event-move'> /**
</span> * @event move
* @inheritdoc Ext.data.Tree#move
*/
"move",
<span id='Ext-data-TreeStore-event-insert'> /**
</span> * @event insert
* @inheritdoc Ext.data.Tree#insert
*/
"insert",
<span id='Ext-data-TreeStore-event-beforeappend'> /**
</span> * @event beforeappend
* @inheritdoc Ext.data.Tree#beforeappend
*/
"beforeappend",
<span id='Ext-data-TreeStore-event-beforeremove'> /**
</span> * @event beforeremove
* @inheritdoc Ext.data.Tree#beforeremove
*/
"beforeremove",
<span id='Ext-data-TreeStore-event-beforemove'> /**
</span> * @event beforemove
* @inheritdoc Ext.data.Tree#beforemove
*/
"beforemove",
<span id='Ext-data-TreeStore-event-beforeinsert'> /**
</span> * @event beforeinsert
* @inheritdoc Ext.data.Tree#beforeinsert
*/
"beforeinsert",
<span id='Ext-data-TreeStore-event-expand'> /**
</span> * @event expand
* @inheritdoc Ext.data.Tree#expand
*/
"expand",
<span id='Ext-data-TreeStore-event-collapse'> /**
</span> * @event collapse
* @inheritdoc Ext.data.Tree#collapse
*/
"collapse",
<span id='Ext-data-TreeStore-event-beforeexpand'> /**
</span> * @event beforeexpand
* @inheritdoc Ext.data.Tree#beforeexpand
*/
"beforeexpand",
<span id='Ext-data-TreeStore-event-beforecollapse'> /**
</span> * @event beforecollapse
* @inheritdoc Ext.data.Tree#beforecollapse
*/
"beforecollapse",
<span id='Ext-data-TreeStore-event-sort'> /**
</span> * @event sort
* @inheritdoc Ext.data.Tree#sort
*/
"sort",
<span id='Ext-data-TreeStore-event-rootchange'> /**
</span> * @event rootchange
* @inheritdoc Ext.data.Tree#rootchange
*/
"rootchange"
]);
me.tree.on({
scope: me,
remove: me.onNodeRemove,
// this event must follow the relay to beforeitemexpand to allow users to
// cancel the expand:
beforeexpand: me.onBeforeNodeExpand,
beforecollapse: me.onBeforeNodeCollapse,
append: me.onNodeAdded,
insert: me.onNodeAdded,
sort: me.onNodeSort
});
me.onBeforeSort();
root = me.root;
if (root) {
delete me.root;
me.setRootNode(root);
}
//<deprecated since=0.99>
if (Ext.isDefined(me.nodeParameter)) {
if (Ext.isDefined(Ext.global.console)) {
Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
}
me.nodeParam = me.nodeParameter;
delete me.nodeParameter;
}
//</deprecated>
},
// inherit docs
setProxy: function(proxy) {
var reader,
needsRoot;
if (proxy instanceof Ext.data.proxy.Proxy) {
// proxy instance, check if a root was set
needsRoot = Ext.isEmpty(proxy.getReader().root);
} else if (Ext.isString(proxy)) {
// string type, means a reader can't be set
needsRoot = true;
} else {
// object, check if a reader and a root were specified.
reader = proxy.reader;
needsRoot = !(reader && !Ext.isEmpty(reader.root));
}
proxy = this.callParent(arguments);
if (needsRoot) {
reader = proxy.getReader();
reader.root = this.defaultRootProperty;
// force rebuild
reader.buildExtractors(true);
}
},
// inherit docs
onBeforeSort: function() {
if (this.folderSort) {
this.sort({
property: 'leaf',
direction: 'ASC'
}, 'prepend', false);
}
},
<span id='Ext-data-TreeStore-method-onBeforeNodeExpand'> /**
</span> * Called before a node is expanded.
* @private
* @param {Ext.data.NodeInterface} node The node being expanded.
* @param {Function} callback The function to run after the expand finishes
* @param {Object} scope The scope in which to run the callback function
*/
onBeforeNodeExpand: function(node, callback, scope) {
if (node.isLoaded()) {
Ext.callback(callback, scope || node, [node.childNodes]);
}
else if (node.isLoading()) {
this.on('load', function() {
Ext.callback(callback, scope || node, [node.childNodes]);
}, this, {single: true});
}
else {
this.read({
node: node,
callback: function() {
Ext.callback(callback, scope || node, [node.childNodes]);
}
});
}
},
//inherit docs
getNewRecords: function() {
return Ext.Array.filter(this.tree.flatten(), this.filterNew);
},
//inherit docs
getUpdatedRecords: function() {
return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
},
<span id='Ext-data-TreeStore-method-onBeforeNodeCollapse'> /**
</span> * Called before a node is collapsed.
* @private
* @param {Ext.data.NodeInterface} node The node being collapsed.
* @param {Function} callback The function to run after the collapse finishes
* @param {Object} scope The scope in which to run the callback function
*/
onBeforeNodeCollapse: function(node, callback, scope) {
callback.call(scope || node, node.childNodes);
},
onNodeRemove: function(parent, node, isMove) {
var me = this,
removed = me.removed;
if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
removed.push(node);
}
if (me.autoSync && !me.autoSyncSuspended && !isMove) {
me.sync();
}
},
onNodeAdded: function(parent, node) {
var me = this,
proxy = me.getProxy(),
reader = proxy.getReader(),
data = node.raw || node[node.persistenceProperty],
dataRoot;
Ext.Array.remove(me.removed, node);
if (!node.isLeaf()) {
dataRoot = reader.getRoot(data);
if (dataRoot) {
me.fillNode(node, reader.extractData(dataRoot));
delete data[reader.root];
}
}
if (me.autoSync && !me.autoSyncSuspended && (node.phantom || node.dirty)) {
me.sync();
}
},
onNodeSort: function() {
if(this.autoSync && !this.autoSyncSuspended) {
this.sync();
}
},
<span id='Ext-data-TreeStore-method-setRootNode'> /**
</span> * Sets the root node for this store. See also the {@link #root} config option.
* @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
* @return {Ext.data.NodeInterface} The new root
*/
setRootNode: function(root, /* private */ preventLoad) {
var me = this,
model = me.model,
idProperty = model.prototype.idProperty
root = root || {};
if (!root.isModel) {
// create a default rootNode and create internal data struct.
Ext.applyIf(root, {
id: me.defaultRootId,
text: 'Root',
allowDrag: false
});
if (root[idProperty] === undefined) {
root[idProperty] = me.defaultRootId;
}
Ext.data.NodeInterface.decorate(model);
root = Ext.ModelManager.create(root, model);
} else if (root.isModel && !root.isNode) {
Ext.data.NodeInterface.decorate(model);
}
// Because we have decorated the model with new fields,
// we need to build new extactor functions on the reader.
me.getProxy().getReader().buildExtractors(true);
// When we add the root to the tree, it will automaticaly get the NodeInterface
me.tree.setRootNode(root);
// If the user has set expanded: true on the root, we want to call the expand function
if (preventLoad !== true && !root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
me.load({
node: root
});
}
return root;
},
<span id='Ext-data-TreeStore-method-getRootNode'> /**
</span> * Returns the root node for this tree.
* @return {Ext.data.NodeInterface}
*/
getRootNode: function() {
return this.tree.getRootNode();
},
<span id='Ext-data-TreeStore-method-getNodeById'> /**
</span> * Returns the record node by id
* @return {Ext.data.NodeInterface}
*/
getNodeById: function(id) {
return this.tree.getNodeById(id);
},
// inherit docs
getById: function(id) {
return this.getNodeById(id);
},
<span id='Ext-data-TreeStore-method-load'> /**
</span> * Loads the Store using its configured {@link #proxy}.
* @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
* object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
* The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
* default to the root node.
*/
load: function(options) {
options = options || {};
options.params = options.params || {};
var me = this,
node = options.node || me.tree.getRootNode();
// If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
// create one for them.
if (!node) {
node = me.setRootNode({
expanded: true
}, true);
}
// Assign the ID of the Operation so that a REST proxy can create the correct URL
options.id = node.getId();
if (me.clearOnLoad) {
if(me.clearRemovedOnLoad) {
// clear from the removed array any nodes that were descendants of the node being reloaded so that they do not get saved on next sync.
me.clearRemoved(node);
}
// temporarily remove the onNodeRemove event listener so that when removeAll is called, the removed nodes do not get added to the removed array
me.tree.un('remove', me.onNodeRemove, me);
// remove all the nodes
node.removeAll(false);
// reattach the onNodeRemove listener
me.tree.on('remove', me.onNodeRemove, me);
}
Ext.applyIf(options, {
node: node
});
options.params[me.nodeParam] = node ? node.getId() : 'root';
if (node) {
node.set('loading', true);
}
return me.callParent([options]);
},
<span id='Ext-data-TreeStore-method-clearRemoved'> /**
</span> * Removes all records that used to be descendants of the passed node from the removed array
* @private
* @param {Ext.data.NodeInterface} node
*/
clearRemoved: function(node) {
var me = this,
removed = me.removed,
id = node.getId(),
removedLength = removed.length,
i = removedLength,
recordsToClear = {},
newRemoved = [],
removedHash = {},
removedNode,
targetNode,
targetId;
if(node === me.getRootNode()) {
// if the passed node is the root node, just reset the removed array
me.removed = [];
return;
}
// add removed records to a hash so they can be easily retrieved by id later
for(; i--;) {
removedNode = removed[i];
removedHash[removedNode.getId()] = removedNode;
}
for(i = removedLength; i--;) {
removedNode = removed[i];
targetNode = removedNode;
while(targetNode && targetNode.getId() !== id) {
// walk up the parent hierarchy until we find the passed node or until we get to the root node
targetId = targetNode.get('parentId');
targetNode = targetNode.parentNode || me.getNodeById(targetId) || removedHash[targetId];
}
if(targetNode) {
// removed node was previously a descendant of the passed node - add it to the records to clear from "removed" later
recordsToClear[removedNode.getId()] = removedNode;
}
}
// create a new removed array containing only the records that are not in recordsToClear
for(i = 0; i < removedLength; i++) {
removedNode = removed[i];
if(!recordsToClear[removedNode.getId()]) {
newRemoved.push(removedNode);
}
}
me.removed = newRemoved;
},
<span id='Ext-data-TreeStore-method-fillNode'> /**
</span> * Fills a node with a series of child records.
* @private
* @param {Ext.data.NodeInterface} node The node to fill
* @param {Ext.data.Model[]} newNodes The records to add
*/
fillNode: function(node, newNodes) {
var me = this,
ln = newNodes ? newNodes.length : 0,
sorters = me.sorters,
i, sortCollection,
needsIndexSort = false,
performLocalSort = ln && me.sortOnLoad && !me.remoteSort && sorters && sorters.items && sorters.items.length,
node1, node2;
// See if there are any differing index values in the new nodes. If not, then we do not have to sortByIndex
for (i = 1; i < ln; i++) {
node1 = newNodes[i];
node2 = newNodes[i - 1];
needsIndexSort = node1[node1.persistenceProperty].index != node2[node2.persistenceProperty].index;
if (needsIndexSort) {
break;
}
}
// If there is a set of local sorters defined.
if (performLocalSort) {
// If sorting by index is needed, sort by index first
if (needsIndexSort) {
me.sorters.insert(0, me.indexSorter);
}
sortCollection = new Ext.util.MixedCollection();
sortCollection.addAll(newNodes);
sortCollection.sort(me.sorters.items);
newNodes = sortCollection.items;
// Remove the index sorter
me.sorters.remove(me.indexSorter);
} else if (needsIndexSort) {
Ext.Array.sort(newNodes, me.sortByIndex);
}
node.set('loaded', true);
for (i = 0; i < ln; i++) {
node.appendChild(newNodes[i], undefined, true);
}
return newNodes;
},
<span id='Ext-data-TreeStore-method-sortByIndex'> /**
</span> * Sorter function for sorting records in index order
* @private
* @param {Ext.data.NodeInterface} node1
* @param {Ext.data.NodeInterface} node2
* @return {Number}
*/
sortByIndex: function(node1, node2) {
return node1[node1.persistenceProperty].index - node2[node2.persistenceProperty].index;
},
// inherit docs
onProxyLoad: function(operation) {
var me = this,
successful = operation.wasSuccessful(),
records = operation.getRecords(),
node = operation.node;
me.loading = false;
node.set('loading', false);
if (successful) {
if (!me.clearOnLoad) {
records = me.cleanRecords(node, records);
}
records = me.fillNode(node, records);
}
// The load event has an extra node parameter
// (differing from the load event described in AbstractStore)
<span id='Ext-data-TreeStore-event-load'> /**
</span> * @event load
* Fires whenever the store reads data from a remote data source.
* @param {Ext.data.TreeStore} this
* @param {Ext.data.NodeInterface} node The node that was loaded.
* @param {Ext.data.Model[]} records An array of records.
* @param {Boolean} successful True if the operation was successful.
*/
// deprecate read?
me.fireEvent('read', me, operation.node, records, successful);
me.fireEvent('load', me, operation.node, records, successful);
//this is a callback that would have been passed to the 'read' function and is optional
Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
},
onCreateRecords: function(records) {
this.callParent(arguments);
var i = 0,
len = records.length,
tree = this.tree,
node;
for (; i < len; ++i) {
node = records[i];
tree.onNodeIdChanged(node, null, node.getId());
}
},
cleanRecords: function(node, records){
var nodeHash = {},
childNodes = node.childNodes,
i = 0,
len = childNodes.length,
out = [],
rec;
// build a hash of all the childNodes under the current node for performance
for (; i < len; ++i) {
nodeHash[childNodes[i].getId()] = true;
}
for (i = 0, len = records.length; i < len; ++i) {
rec = records[i];
if (!nodeHash[rec.getId()]) {
out.push(rec);
}
}
return out;
},
// inherit docs
removeAll: function() {
var root = this.getRootNode();
if (root) {
root.destroy(true);
}
this.fireEvent('clear', this);
},
// inherit docs
doSort: function(sorterFn) {
var me = this;
if (me.remoteSort) {
//the load function will pick up the new sorters and request the sorted data from the proxy
me.load();
} else {
me.tree.sort(sorterFn, true);
me.fireEvent('datachanged', me);
me.fireEvent('refresh', me);
}
me.fireEvent('sort', me);
}
}, function() {
var proto = this.prototype;
proto.indexSorter = new Ext.util.Sorter({
sorterFn: proto.sortByIndex
});
});
</pre>
</body>
</html>