@converse/skeletor 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -25
- package/dist/skeletor.js +9232 -5096
- package/dist/skeletor.js.map +1 -1
- package/dist/skeletor.min.js +74 -3
- package/dist/skeletor.min.js.map +1 -1
- package/package.json +21 -11
- package/src/collection.js +540 -395
- package/src/drivers/sessionStorage.js +222 -219
- package/src/element.js +132 -82
- package/src/eventemitter.js +187 -0
- package/src/helpers.js +143 -118
- package/src/{main.js → index.js} +21 -17
- package/src/listening.js +81 -0
- package/src/model.js +340 -238
- package/src/storage.js +222 -230
- package/src/types/collection.d.ts +366 -0
- package/src/types/collection.d.ts.map +1 -0
- package/src/types/drivers/sessionStorage.d.ts +37 -0
- package/src/types/drivers/sessionStorage.d.ts.map +1 -0
- package/src/types/element.d.ts +115 -0
- package/src/types/element.d.ts.map +1 -0
- package/src/types/eventemitter.d.ts +93 -0
- package/src/types/eventemitter.d.ts.map +1 -0
- package/src/types/helpers.d.ts +62 -0
- package/src/types/helpers.d.ts.map +1 -0
- package/src/types/index.d.ts +19 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/listening.d.ts +40 -0
- package/src/types/listening.d.ts.map +1 -0
- package/src/types/model.d.ts +230 -0
- package/src/types/model.d.ts.map +1 -0
- package/src/types/storage.d.ts +35 -0
- package/src/types/storage.d.ts.map +1 -0
- package/src/types/utils/events.d.ts +28 -0
- package/src/types/utils/events.d.ts.map +1 -0
- package/src/utils/events.js +174 -0
- package/src/drivers/sessionStorage.js~ +0 -307
- package/src/events.js +0 -328
- package/src/history.js +0 -301
- package/src/overview.js +0 -191
- package/src/router.js +0 -119
- package/src/view.js +0 -267
package/src/collection.js
CHANGED
|
@@ -1,131 +1,172 @@
|
|
|
1
|
-
|
|
2
|
-
// (c) 2010-2019 Jeremy Ashkenas and DocumentCloud
|
|
3
|
-
// Backbone may be freely distributed under the MIT license.
|
|
4
|
-
|
|
5
|
-
// Collection
|
|
6
|
-
// ----------
|
|
7
|
-
|
|
8
|
-
// If models tend to represent a single row of data, a Collection is
|
|
9
|
-
// more analogous to a table full of data ... or a small slice or page of that
|
|
10
|
-
// table, or a collection of rows that belong together for a particular reason
|
|
11
|
-
// -- all of the messages in this particular folder, all of the documents
|
|
12
|
-
// belonging to this particular author, and so on. Collections maintain
|
|
13
|
-
// indexes of their models, both in order, and for lookup by `id`.
|
|
14
|
-
|
|
15
|
-
import { inherits, getResolveablePromise, getSyncMethod, wrapError } from './helpers.js';
|
|
16
|
-
import { Events } from './events.js';
|
|
1
|
+
import { getResolveablePromise, getSyncMethod, wrapError } from './helpers.js';
|
|
17
2
|
import { Model } from './model.js';
|
|
18
|
-
import clone from
|
|
3
|
+
import clone from 'lodash-es/clone.js';
|
|
19
4
|
import countBy from 'lodash-es/countBy.js';
|
|
20
|
-
import difference from 'lodash-es/difference.js';
|
|
21
|
-
import every from 'lodash-es/every.js';
|
|
22
|
-
import extend from "lodash-es/extend.js";
|
|
23
|
-
import findIndex from 'lodash-es/findIndex.js';
|
|
24
|
-
import findLastIndex from 'lodash-es/findLastIndex.js';
|
|
25
5
|
import groupBy from 'lodash-es/groupBy.js';
|
|
26
|
-
import
|
|
27
|
-
import isEmpty from "lodash-es/isEmpty.js";
|
|
28
|
-
import isFunction from "lodash-es/isFunction.js";
|
|
6
|
+
import isFunction from 'lodash-es/isFunction.js';
|
|
29
7
|
import isString from 'lodash-es/isString.js';
|
|
30
8
|
import keyBy from 'lodash-es/keyBy.js';
|
|
31
|
-
import lastIndexOf from 'lodash-es/lastIndexOf.js';
|
|
32
|
-
import some from 'lodash-es/some.js';
|
|
33
9
|
import sortBy from 'lodash-es/sortBy.js';
|
|
10
|
+
import EventEmitter from './eventemitter.js';
|
|
34
11
|
|
|
35
12
|
const slice = Array.prototype.slice;
|
|
36
13
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
14
|
+
// Default options for `Collection#set`.
|
|
15
|
+
const setOptions = { add: true, remove: true, merge: true };
|
|
16
|
+
const addOptions = { add: true, remove: false };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Record.<string, any>} Options
|
|
20
|
+
* @typedef {Record.<string, any>} Attributes
|
|
21
|
+
*
|
|
22
|
+
* @typedef {import('./storage.js').default} Storage
|
|
23
|
+
*
|
|
24
|
+
* @typedef {Record.<string, any>} CollectionOptions
|
|
25
|
+
* @property {Model} [model]
|
|
26
|
+
* @property {Function} [comparator]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* If models tend to represent a single row of data, a Collection is
|
|
31
|
+
* more analogous to a table full of data ... or a small slice or page of that
|
|
32
|
+
* table, or a collection of rows that belong together for a particular reason
|
|
33
|
+
* -- all of the messages in this particular folder, all of the documents
|
|
34
|
+
* belonging to this particular author, and so on. Collections maintain
|
|
35
|
+
* indexes of their models, both in order, and for lookup by `id`.
|
|
36
|
+
*/
|
|
37
|
+
class Collection extends EventEmitter(Object) {
|
|
38
|
+
/**
|
|
39
|
+
* Create a new **Collection**, perhaps to contain a specific type of `model`.
|
|
40
|
+
* If a `comparator` is specified, the Collection will maintain
|
|
41
|
+
* its models in sort order, as they're added and removed.
|
|
42
|
+
* @param {Model[]} [models]
|
|
43
|
+
* @param {CollectionOptions} [options]
|
|
44
|
+
*/
|
|
45
|
+
constructor(models, options) {
|
|
46
|
+
super();
|
|
47
|
+
options || (options = {});
|
|
48
|
+
this.preinitialize.apply(this, arguments);
|
|
49
|
+
if (options.model) this._model = options.model;
|
|
50
|
+
if (options.comparator !== undefined) this.comparator = options.comparator;
|
|
51
|
+
this._reset();
|
|
52
|
+
this.initialize.apply(this, arguments);
|
|
53
|
+
if (models) this.reset(models, Object.assign({ silent: true }, options));
|
|
54
|
+
|
|
55
|
+
this[Symbol.iterator] = this.values;
|
|
56
|
+
}
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
/**
|
|
59
|
+
* @param {Storage} storage
|
|
60
|
+
*/
|
|
61
|
+
set browserStorage(storage) {
|
|
62
|
+
this._browserStorage = storage;
|
|
63
|
+
}
|
|
51
64
|
|
|
65
|
+
/**
|
|
66
|
+
* @returns {Storage} storage
|
|
67
|
+
*/
|
|
68
|
+
get browserStorage() {
|
|
69
|
+
return this._browserStorage;
|
|
70
|
+
}
|
|
52
71
|
|
|
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
|
-
|
|
72
|
+
/**
|
|
73
|
+
* The default model for a collection is just a **Model**.
|
|
74
|
+
* This should be overridden in most cases.
|
|
75
|
+
* @returns {typeof Model}
|
|
76
|
+
*/
|
|
77
|
+
get model() {
|
|
78
|
+
return this._model ?? Model;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Model} model
|
|
83
|
+
*/
|
|
84
|
+
set model(model) {
|
|
85
|
+
this._model = model;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get length() {
|
|
89
|
+
return this.models.length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* preinitialize is an empty function by default. You can override it with a function
|
|
94
|
+
* or object. preinitialize will run before any instantiation logic is run in the Collection.
|
|
95
|
+
*/
|
|
96
|
+
preinitialize() {}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Initialize is an empty function by default. Override it with your own
|
|
100
|
+
* initialization logic.
|
|
101
|
+
*/
|
|
102
|
+
initialize() {}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The JSON representation of a Collection is an array of the
|
|
106
|
+
* models' attributes.
|
|
107
|
+
*@param {Options} options
|
|
108
|
+
*/
|
|
109
|
+
toJSON(options) {
|
|
110
|
+
return this.map(function (model) {
|
|
111
|
+
return model.toJSON(options);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
*@param {string} method
|
|
117
|
+
*@param {Model|Collection} model
|
|
118
|
+
*@param {Options} options
|
|
119
|
+
*/
|
|
120
|
+
sync(method, model, options) {
|
|
92
121
|
return getSyncMethod(this)(method, model, options);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Add a model, or list of models to the set. `models` may be
|
|
126
|
+
* Models or raw JavaScript objects to be converted to Models, or any
|
|
127
|
+
* combination of the two.
|
|
128
|
+
*@param {Model[]|Model|Attributes|Attributes[]} models
|
|
129
|
+
*@param {Options} options
|
|
130
|
+
*/
|
|
131
|
+
add(models, options) {
|
|
132
|
+
return this.set(models, Object.assign({ merge: false }, options, addOptions));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Remove a model, or a list of models from the set.
|
|
137
|
+
* @param {Model|Model[]} models
|
|
138
|
+
* @param {Options} options
|
|
139
|
+
*/
|
|
140
|
+
remove(models, options) {
|
|
141
|
+
options = Object.assign({}, options);
|
|
105
142
|
const singular = !Array.isArray(models);
|
|
106
|
-
|
|
107
|
-
const removed = this._removeModels(
|
|
143
|
+
const modelsArray = singular ? [models] : /** @type {Model[]} */ (models).slice();
|
|
144
|
+
const removed = this._removeModels(modelsArray, options);
|
|
108
145
|
if (!options.silent && removed.length) {
|
|
109
|
-
options.changes = {added: [], merged: [], removed: removed};
|
|
146
|
+
options.changes = { added: [], merged: [], removed: removed };
|
|
110
147
|
this.trigger('update', this, options);
|
|
111
148
|
}
|
|
112
149
|
return singular ? removed[0] : removed;
|
|
113
|
-
}
|
|
150
|
+
}
|
|
114
151
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Update a collection by `set`-ing a new list of models, adding new ones,
|
|
154
|
+
* removing models that are no longer present, and merging models that
|
|
155
|
+
* already exist in the collection, as necessary. Similar to **Model#set**,
|
|
156
|
+
* the core operation for updating the data contained by the collection.
|
|
157
|
+
*@param {Model[]|Model|Attributes|Attributes[]} models
|
|
158
|
+
* @param {Options} options
|
|
159
|
+
*/
|
|
160
|
+
set(models, options) {
|
|
120
161
|
if (models == null) return;
|
|
121
162
|
|
|
122
|
-
options =
|
|
163
|
+
options = Object.assign({}, setOptions, options);
|
|
123
164
|
if (options.parse && !this._isModel(models)) {
|
|
124
165
|
models = this.parse(models, options) || [];
|
|
125
166
|
}
|
|
126
167
|
|
|
127
168
|
const singular = !Array.isArray(models);
|
|
128
|
-
models = singular ? [models] : models.slice();
|
|
169
|
+
models = singular ? [/** @type {Model} */ (models)] : /** @type {Model[]} */ (models).slice();
|
|
129
170
|
|
|
130
171
|
let at = options.at;
|
|
131
172
|
if (at != null) at = +at;
|
|
@@ -169,7 +210,7 @@ Object.assign(Collection.prototype, Events, {
|
|
|
169
210
|
}
|
|
170
211
|
models[i] = existing;
|
|
171
212
|
|
|
172
|
-
|
|
213
|
+
// If this is a new, valid model, push it to the `toAdd` list.
|
|
173
214
|
} else if (add) {
|
|
174
215
|
model = models[i] = this._prepareModel(model, options);
|
|
175
216
|
if (model) {
|
|
@@ -193,19 +234,20 @@ Object.assign(Collection.prototype, Events, {
|
|
|
193
234
|
// See if sorting is needed, update `length` and splice in new models.
|
|
194
235
|
let orderChanged = false;
|
|
195
236
|
const replace = !sortable && add && remove;
|
|
237
|
+
|
|
196
238
|
if (set.length && replace) {
|
|
197
|
-
orderChanged = this.length !== set.length ||
|
|
239
|
+
orderChanged = this.length !== set.length || this.models.some((m, idx) => m !== set[idx]);
|
|
198
240
|
this.models.length = 0;
|
|
199
|
-
|
|
200
|
-
this.length = this.models.length;
|
|
241
|
+
this.models.splice(0, 0, ...set);
|
|
201
242
|
} else if (toAdd.length) {
|
|
202
243
|
if (sortable) sort = true;
|
|
203
|
-
|
|
204
|
-
|
|
244
|
+
let idx = at == null ? this.length : at;
|
|
245
|
+
idx = Math.min(Math.max(idx, 0), this.models.length);
|
|
246
|
+
this.models.splice(idx, 0, ...toAdd);
|
|
205
247
|
}
|
|
206
248
|
|
|
207
249
|
// Silently sort the collection if appropriate.
|
|
208
|
-
if (sort) this.sort({silent: true});
|
|
250
|
+
if (sort) this.sort({ silent: true });
|
|
209
251
|
|
|
210
252
|
// Unless silenced, it's time to fire all appropriate add/sort/update events.
|
|
211
253
|
if (!options.silent) {
|
|
@@ -219,7 +261,7 @@ Object.assign(Collection.prototype, Events, {
|
|
|
219
261
|
options.changes = {
|
|
220
262
|
added: toAdd,
|
|
221
263
|
removed: toRemove,
|
|
222
|
-
merged: toMerge
|
|
264
|
+
merged: toMerge,
|
|
223
265
|
};
|
|
224
266
|
this.trigger('update', this, options);
|
|
225
267
|
}
|
|
@@ -227,242 +269,304 @@ Object.assign(Collection.prototype, Events, {
|
|
|
227
269
|
|
|
228
270
|
// Return the added (or merged) model (or models).
|
|
229
271
|
return singular ? models[0] : models;
|
|
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
|
-
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async clearStore(options = {}, filter = (o) => o) {
|
|
275
|
+
await Promise.all(
|
|
276
|
+
this.models.filter(filter).map((m) => {
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
m.destroy(
|
|
279
|
+
Object.assign(options, {
|
|
280
|
+
'success': resolve,
|
|
281
|
+
'error': (m, e) => {
|
|
282
|
+
console.error(e);
|
|
283
|
+
resolve();
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
await this.browserStorage.clear();
|
|
291
|
+
this.reset();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* When you have more items than you want to add or remove individually,
|
|
296
|
+
* you can reset the entire set with a new list of models, without firing
|
|
297
|
+
* any granular `add` or `remove` events. Fires `reset` when finished.
|
|
298
|
+
* Useful for bulk operations and optimizations.
|
|
299
|
+
* @param {Model|Model[]} [models]
|
|
300
|
+
* @param {Options} [options]
|
|
301
|
+
*/
|
|
302
|
+
reset(models, options) {
|
|
255
303
|
options = options ? clone(options) : {};
|
|
256
304
|
for (let i = 0; i < this.models.length; i++) {
|
|
257
305
|
this._removeReference(this.models[i], options);
|
|
258
306
|
}
|
|
259
307
|
options.previousModels = this.models;
|
|
260
308
|
this._reset();
|
|
261
|
-
models = this.add(models,
|
|
309
|
+
models = this.add(models, Object.assign({ silent: true }, options));
|
|
262
310
|
if (!options.silent) this.trigger('reset', this, options);
|
|
263
311
|
return models;
|
|
264
|
-
}
|
|
312
|
+
}
|
|
265
313
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Add a model to the end of the collection.
|
|
316
|
+
* @param {Model} model
|
|
317
|
+
* @param {Options} [options]
|
|
318
|
+
*/
|
|
319
|
+
push(model, options) {
|
|
320
|
+
return this.add(model, Object.assign({ at: this.length }, options));
|
|
321
|
+
}
|
|
270
322
|
|
|
271
|
-
|
|
272
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Remove a model from the end of the collection.
|
|
325
|
+
* @param {Options} [options]
|
|
326
|
+
*/
|
|
327
|
+
pop(options) {
|
|
273
328
|
const model = this.at(this.length - 1);
|
|
274
329
|
return this.remove(model, options);
|
|
275
|
-
}
|
|
330
|
+
}
|
|
276
331
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Add a model to the beginning of the collection.
|
|
334
|
+
* @param {Model} model
|
|
335
|
+
* @param {Options} [options]
|
|
336
|
+
*/
|
|
337
|
+
unshift(model, options) {
|
|
338
|
+
return this.add(model, Object.assign({ at: 0 }, options));
|
|
339
|
+
}
|
|
281
340
|
|
|
282
|
-
|
|
283
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Remove a model from the beginning of the collection.
|
|
343
|
+
* @param {Options} [options]
|
|
344
|
+
*/
|
|
345
|
+
shift(options) {
|
|
284
346
|
const model = this.at(0);
|
|
285
347
|
return this.remove(model, options);
|
|
286
|
-
}
|
|
348
|
+
}
|
|
287
349
|
|
|
288
|
-
|
|
289
|
-
slice
|
|
350
|
+
/** Slice out a sub-array of models from the collection. */
|
|
351
|
+
slice() {
|
|
290
352
|
return slice.apply(this.models, arguments);
|
|
291
|
-
}
|
|
353
|
+
}
|
|
292
354
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
355
|
+
/**
|
|
356
|
+
* @param {Function|Object} callback
|
|
357
|
+
* @param {any} thisArg
|
|
358
|
+
*/
|
|
359
|
+
filter(callback, thisArg) {
|
|
360
|
+
return this.models.filter(isFunction(callback) ? callback : (m) => m.matches(callback), thisArg);
|
|
361
|
+
}
|
|
299
362
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
363
|
+
/**
|
|
364
|
+
* @param {Function} pred
|
|
365
|
+
*/
|
|
366
|
+
every(pred) {
|
|
367
|
+
if (isFunction(pred)) {
|
|
368
|
+
return this.models.map((m) => m.attributes).every(pred);
|
|
369
|
+
} else {
|
|
370
|
+
return this.models.every((m) => m.matches(pred));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
303
373
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
374
|
+
/**
|
|
375
|
+
* @param {Model[]} values
|
|
376
|
+
*/
|
|
377
|
+
difference(values) {
|
|
378
|
+
return this.models.filter((m) => !values.includes(m));
|
|
379
|
+
}
|
|
307
380
|
|
|
308
|
-
max
|
|
381
|
+
max() {
|
|
309
382
|
return Math.max.apply(Math, this.models);
|
|
310
|
-
}
|
|
383
|
+
}
|
|
311
384
|
|
|
312
|
-
min
|
|
385
|
+
min() {
|
|
313
386
|
return Math.min.apply(Math, this.models);
|
|
314
|
-
}
|
|
387
|
+
}
|
|
315
388
|
|
|
316
|
-
drop
|
|
389
|
+
drop(n = 1) {
|
|
317
390
|
return this.models.slice(n);
|
|
318
|
-
}
|
|
391
|
+
}
|
|
319
392
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
393
|
+
/**
|
|
394
|
+
* @param {Function|Object} pred
|
|
395
|
+
*/
|
|
396
|
+
some(pred) {
|
|
397
|
+
if (isFunction(pred)) {
|
|
398
|
+
return this.models.map((m) => m.attributes).some(pred);
|
|
399
|
+
} else {
|
|
400
|
+
return this.models.some((m) => m.matches(pred));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
323
403
|
|
|
324
|
-
sortBy
|
|
404
|
+
sortBy(iteratee) {
|
|
325
405
|
return sortBy(
|
|
326
406
|
this.models,
|
|
327
|
-
isFunction(iteratee) ? iteratee : m => isString(iteratee) ? m.get(iteratee) : m.matches(iteratee),
|
|
407
|
+
isFunction(iteratee) ? iteratee : (m) => (isString(iteratee) ? m.get(iteratee) : m.matches(iteratee)),
|
|
328
408
|
);
|
|
329
|
-
}
|
|
409
|
+
}
|
|
330
410
|
|
|
331
|
-
isEmpty
|
|
332
|
-
return
|
|
333
|
-
}
|
|
411
|
+
isEmpty() {
|
|
412
|
+
return !this.models.length;
|
|
413
|
+
}
|
|
334
414
|
|
|
335
|
-
keyBy
|
|
415
|
+
keyBy(iteratee) {
|
|
336
416
|
return keyBy(this.models, iteratee);
|
|
337
|
-
}
|
|
417
|
+
}
|
|
338
418
|
|
|
339
|
-
each
|
|
419
|
+
each(callback, thisArg) {
|
|
340
420
|
return this.forEach(callback, thisArg);
|
|
341
|
-
}
|
|
421
|
+
}
|
|
342
422
|
|
|
343
|
-
forEach
|
|
423
|
+
forEach(callback, thisArg) {
|
|
344
424
|
return this.models.forEach(callback, thisArg);
|
|
345
|
-
}
|
|
425
|
+
}
|
|
346
426
|
|
|
347
|
-
includes
|
|
427
|
+
includes(item) {
|
|
348
428
|
return this.models.includes(item);
|
|
349
|
-
}
|
|
429
|
+
}
|
|
350
430
|
|
|
351
|
-
size
|
|
431
|
+
size() {
|
|
352
432
|
return this.models.length;
|
|
353
|
-
}
|
|
433
|
+
}
|
|
354
434
|
|
|
355
|
-
countBy
|
|
356
|
-
return countBy(
|
|
357
|
-
|
|
358
|
-
isFunction(f) ? f : m => isString(f) ? m.get(f) : m.matches(f),
|
|
359
|
-
);
|
|
360
|
-
},
|
|
435
|
+
countBy(f) {
|
|
436
|
+
return countBy(this.models, isFunction(f) ? f : (m) => (isString(f) ? m.get(f) : m.matches(f)));
|
|
437
|
+
}
|
|
361
438
|
|
|
362
|
-
groupBy
|
|
363
|
-
return groupBy(
|
|
364
|
-
|
|
365
|
-
isFunction(pred) ? pred : m => isString(pred) ? m.get(pred) : m.matches(pred),
|
|
366
|
-
);
|
|
367
|
-
},
|
|
439
|
+
groupBy(pred) {
|
|
440
|
+
return groupBy(this.models, isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
|
|
441
|
+
}
|
|
368
442
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
443
|
+
/**
|
|
444
|
+
* @param {number} fromIndex
|
|
445
|
+
*/
|
|
446
|
+
indexOf(fromIndex) {
|
|
447
|
+
return this.models.indexOf(fromIndex);
|
|
448
|
+
}
|
|
372
449
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
450
|
+
/**
|
|
451
|
+
* @param {Function|string|RegExp} pred
|
|
452
|
+
* @param {number} fromIndex
|
|
453
|
+
*/
|
|
454
|
+
findLastIndex(pred, fromIndex) {
|
|
455
|
+
return this.models.findLastIndex(
|
|
456
|
+
isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)),
|
|
457
|
+
fromIndex,
|
|
378
458
|
);
|
|
379
|
-
}
|
|
459
|
+
}
|
|
380
460
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
461
|
+
/**
|
|
462
|
+
* @param {number} fromIndex
|
|
463
|
+
*/
|
|
464
|
+
lastIndexOf(fromIndex) {
|
|
465
|
+
return this.models.lastIndexOf(fromIndex);
|
|
466
|
+
}
|
|
384
467
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
);
|
|
390
|
-
}
|
|
468
|
+
/**
|
|
469
|
+
* @param {Function|string|RegExp} pred
|
|
470
|
+
*/
|
|
471
|
+
findIndex(pred) {
|
|
472
|
+
return this.models.findIndex(isFunction(pred) ? pred : (m) => (isString(pred) ? m.get(pred) : m.matches(pred)));
|
|
473
|
+
}
|
|
391
474
|
|
|
392
|
-
last
|
|
475
|
+
last() {
|
|
393
476
|
const length = this.models == null ? 0 : this.models.length;
|
|
394
477
|
return length ? this.models[length - 1] : undefined;
|
|
395
|
-
}
|
|
478
|
+
}
|
|
396
479
|
|
|
397
|
-
head
|
|
480
|
+
head() {
|
|
398
481
|
return this.models[0];
|
|
399
|
-
}
|
|
482
|
+
}
|
|
400
483
|
|
|
401
|
-
first
|
|
484
|
+
first() {
|
|
402
485
|
return this.head();
|
|
403
|
-
}
|
|
486
|
+
}
|
|
404
487
|
|
|
405
|
-
map
|
|
406
|
-
return this.models.map(
|
|
407
|
-
|
|
408
|
-
thisArg
|
|
409
|
-
);
|
|
410
|
-
},
|
|
488
|
+
map(cb, thisArg) {
|
|
489
|
+
return this.models.map(isFunction(cb) ? cb : (m) => (isString(cb) ? m.get(cb) : m.matches(cb)), thisArg);
|
|
490
|
+
}
|
|
411
491
|
|
|
412
|
-
reduce
|
|
492
|
+
reduce(callback, initialValue) {
|
|
413
493
|
return this.models.reduce(callback, initialValue || this.models[0]);
|
|
414
|
-
}
|
|
494
|
+
}
|
|
415
495
|
|
|
416
|
-
reduceRight
|
|
496
|
+
reduceRight(callback, initialValue) {
|
|
417
497
|
return this.models.reduceRight(callback, initialValue || this.models[0]);
|
|
418
|
-
}
|
|
498
|
+
}
|
|
419
499
|
|
|
420
|
-
toArray
|
|
500
|
+
toArray() {
|
|
421
501
|
return Array.from(this.models);
|
|
422
|
-
}
|
|
502
|
+
}
|
|
423
503
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
504
|
+
/**
|
|
505
|
+
* Get a model from the set by id, cid, model object with id or cid
|
|
506
|
+
* properties, or an attributes object that is transformed through modelId.
|
|
507
|
+
* @param {string|number|Object|Model} obj
|
|
508
|
+
*/
|
|
509
|
+
get(obj) {
|
|
427
510
|
if (obj == null) return undefined;
|
|
428
|
-
return
|
|
511
|
+
return (
|
|
512
|
+
this._byId[obj] ||
|
|
429
513
|
this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] ||
|
|
430
|
-
obj.cid && this._byId[obj.cid]
|
|
431
|
-
|
|
514
|
+
(obj.cid && this._byId[obj.cid])
|
|
515
|
+
);
|
|
516
|
+
}
|
|
432
517
|
|
|
433
|
-
|
|
434
|
-
|
|
518
|
+
/**
|
|
519
|
+
* Returns `true` if the model is in the collection.
|
|
520
|
+
* @param {string|number|Object|Model} obj
|
|
521
|
+
*/
|
|
522
|
+
has(obj) {
|
|
435
523
|
return this.get(obj) != null;
|
|
436
|
-
}
|
|
524
|
+
}
|
|
437
525
|
|
|
438
|
-
|
|
439
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Get the model at the given index.
|
|
528
|
+
* @param {number} index
|
|
529
|
+
*/
|
|
530
|
+
at(index) {
|
|
440
531
|
if (index < 0) index += this.length;
|
|
441
532
|
return this.models[index];
|
|
442
|
-
}
|
|
533
|
+
}
|
|
443
534
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Return models with matching attributes. Useful for simple cases of
|
|
537
|
+
* `filter`.
|
|
538
|
+
* @param {Attributes} attrs
|
|
539
|
+
* @param {boolean} [first]
|
|
540
|
+
*/
|
|
541
|
+
where(attrs, first) {
|
|
447
542
|
return this[first ? 'find' : 'filter'](attrs);
|
|
448
|
-
}
|
|
543
|
+
}
|
|
449
544
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
545
|
+
/**
|
|
546
|
+
* Return the first model with matching attributes. Useful for simple cases
|
|
547
|
+
* of `find`.
|
|
548
|
+
* @param {Attributes} attrs
|
|
549
|
+
*/
|
|
550
|
+
findWhere(attrs) {
|
|
453
551
|
return this.where(attrs, true);
|
|
454
|
-
}
|
|
552
|
+
}
|
|
455
553
|
|
|
456
|
-
|
|
457
|
-
|
|
554
|
+
/**
|
|
555
|
+
* @param {Attributes} predicate
|
|
556
|
+
* @param {number} [fromIndex]
|
|
557
|
+
*/
|
|
558
|
+
find(predicate, fromIndex) {
|
|
559
|
+
const pred = isFunction(predicate) ? predicate : (m) => m.matches(predicate);
|
|
458
560
|
return this.models.find(pred, fromIndex);
|
|
459
|
-
}
|
|
460
|
-
|
|
561
|
+
}
|
|
461
562
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Force the collection to re-sort itself. You don't need to call this under
|
|
565
|
+
* normal circumstances, as the set will maintain sort order as each item
|
|
566
|
+
* is added.
|
|
567
|
+
* @param {Options} [options]
|
|
568
|
+
*/
|
|
569
|
+
sort(options) {
|
|
466
570
|
let comparator = this.comparator;
|
|
467
571
|
if (!comparator) throw new Error('Cannot sort a set without a comparator');
|
|
468
572
|
options || (options = {});
|
|
@@ -478,22 +582,29 @@ Object.assign(Collection.prototype, Events, {
|
|
|
478
582
|
}
|
|
479
583
|
if (!options.silent) this.trigger('sort', this, options);
|
|
480
584
|
return this;
|
|
481
|
-
}
|
|
585
|
+
}
|
|
482
586
|
|
|
483
|
-
|
|
484
|
-
|
|
587
|
+
/**
|
|
588
|
+
* Pluck an attribute from each model in the collection.
|
|
589
|
+
* @param {string} attr
|
|
590
|
+
*/
|
|
591
|
+
pluck(attr) {
|
|
485
592
|
return this.map(attr + '');
|
|
486
|
-
}
|
|
593
|
+
}
|
|
487
594
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Fetch the default set of models for this collection, resetting the
|
|
597
|
+
* collection when they arrive. If `reset: true` is passed, the response
|
|
598
|
+
* data will be passed through the `reset` method instead of `set`.
|
|
599
|
+
* @param {Options} options
|
|
600
|
+
*/
|
|
601
|
+
fetch(options) {
|
|
602
|
+
options = Object.assign({ parse: true }, options);
|
|
493
603
|
const success = options.success;
|
|
604
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
494
605
|
const collection = this;
|
|
495
606
|
const promise = options.promise && getResolveablePromise();
|
|
496
|
-
options.success = function(resp) {
|
|
607
|
+
options.success = function (resp) {
|
|
497
608
|
const method = options.reset ? 'reset' : 'set';
|
|
498
609
|
collection[method](resp, options);
|
|
499
610
|
if (success) success.call(options.context, collection, resp, options);
|
|
@@ -502,12 +613,16 @@ Object.assign(Collection.prototype, Events, {
|
|
|
502
613
|
};
|
|
503
614
|
wrapError(this, options);
|
|
504
615
|
return promise ? promise : this.sync('read', this, options);
|
|
505
|
-
}
|
|
616
|
+
}
|
|
506
617
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
618
|
+
/**
|
|
619
|
+
* Create a new instance of a model in this collection. Add the model to the
|
|
620
|
+
* collection immediately, unless `wait: true` is passed, in which case we
|
|
621
|
+
* wait for the server to agree.
|
|
622
|
+
* @param {Model|Attributes} model
|
|
623
|
+
* @param {Options} [options]
|
|
624
|
+
*/
|
|
625
|
+
create(model, options) {
|
|
511
626
|
options = options ? clone(options) : {};
|
|
512
627
|
const wait = options.wait;
|
|
513
628
|
const return_promise = options.promise;
|
|
@@ -516,10 +631,11 @@ Object.assign(Collection.prototype, Events, {
|
|
|
516
631
|
model = this._prepareModel(model, options);
|
|
517
632
|
if (!model) return false;
|
|
518
633
|
if (!wait) this.add(model, options);
|
|
634
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
519
635
|
const collection = this;
|
|
520
636
|
const success = options.success;
|
|
521
637
|
const error = options.error;
|
|
522
|
-
options.success = function(m, resp, callbackOpts) {
|
|
638
|
+
options.success = function (m, resp, callbackOpts) {
|
|
523
639
|
if (wait) {
|
|
524
640
|
collection.add(m, callbackOpts);
|
|
525
641
|
}
|
|
@@ -530,78 +646,96 @@ Object.assign(Collection.prototype, Events, {
|
|
|
530
646
|
promise.resolve(m);
|
|
531
647
|
}
|
|
532
648
|
};
|
|
533
|
-
options.error = function(model, e, options) {
|
|
649
|
+
options.error = function (model, e, options) {
|
|
534
650
|
error && error.call(options.context, model, e, options);
|
|
535
651
|
return_promise && promise.reject(e);
|
|
536
|
-
}
|
|
652
|
+
};
|
|
537
653
|
|
|
538
|
-
model.save(null, Object.assign(options, {'promise': false}));
|
|
654
|
+
model.save(null, Object.assign(options, { 'promise': false }));
|
|
539
655
|
if (return_promise) {
|
|
540
656
|
return promise;
|
|
541
657
|
} else {
|
|
542
658
|
return model;
|
|
543
659
|
}
|
|
544
|
-
}
|
|
660
|
+
}
|
|
545
661
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
662
|
+
/**
|
|
663
|
+
* **parse** converts a response into a list of models to be added to the
|
|
664
|
+
* collection. The default implementation is just to pass it through.
|
|
665
|
+
* @param {Object} resp
|
|
666
|
+
* @param {Options} [options]
|
|
667
|
+
*/
|
|
668
|
+
parse(resp, options) {
|
|
549
669
|
return resp;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Create a new collection with an identical list of models as this one.
|
|
553
|
-
clone: function() {
|
|
554
|
-
return new this.constructor(this.models, {
|
|
555
|
-
model: this.model,
|
|
556
|
-
comparator: this.comparator
|
|
557
|
-
});
|
|
558
|
-
},
|
|
670
|
+
}
|
|
559
671
|
|
|
560
|
-
|
|
561
|
-
|
|
672
|
+
/**
|
|
673
|
+
* Define how to uniquely identify models in the collection.
|
|
674
|
+
* @param {Attributes} attrs
|
|
675
|
+
*/
|
|
676
|
+
modelId(attrs) {
|
|
562
677
|
return attrs[this.model.prototype?.idAttribute || 'id'];
|
|
563
|
-
}
|
|
678
|
+
}
|
|
564
679
|
|
|
565
|
-
|
|
566
|
-
values
|
|
680
|
+
/** Get an iterator of all models in this collection. */
|
|
681
|
+
values() {
|
|
567
682
|
return new CollectionIterator(this, ITERATOR_VALUES);
|
|
568
|
-
}
|
|
683
|
+
}
|
|
569
684
|
|
|
570
|
-
|
|
571
|
-
keys
|
|
685
|
+
/** Get an iterator of all model IDs in this collection. */
|
|
686
|
+
keys() {
|
|
572
687
|
return new CollectionIterator(this, ITERATOR_KEYS);
|
|
573
|
-
}
|
|
688
|
+
}
|
|
574
689
|
|
|
575
|
-
|
|
576
|
-
entries
|
|
690
|
+
/** Get an iterator of all [ID, model] tuples in this collection. */
|
|
691
|
+
entries() {
|
|
577
692
|
return new CollectionIterator(this, ITERATOR_KEYSVALUES);
|
|
578
|
-
}
|
|
693
|
+
}
|
|
579
694
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
695
|
+
/**
|
|
696
|
+
* Private method to reset all internal state. Called when the collection
|
|
697
|
+
* is first initialized or reset.
|
|
698
|
+
*/
|
|
699
|
+
_reset() {
|
|
584
700
|
this.models = [];
|
|
585
|
-
this._byId
|
|
586
|
-
}
|
|
701
|
+
this._byId = {};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* @param {Attributes} attrs
|
|
706
|
+
* @param {Options} [options]
|
|
707
|
+
*/
|
|
708
|
+
createModel(attrs, options) {
|
|
709
|
+
const Klass = this.model;
|
|
710
|
+
return new Klass(attrs, options);
|
|
711
|
+
}
|
|
587
712
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
713
|
+
/**
|
|
714
|
+
* Prepare a hash of attributes (or other model) to be added to this
|
|
715
|
+
* collection.
|
|
716
|
+
* @param {Attributes|Model} attrs
|
|
717
|
+
* @param {Options} [options]
|
|
718
|
+
* @return {Model}
|
|
719
|
+
*/
|
|
720
|
+
_prepareModel(attrs, options) {
|
|
591
721
|
if (this._isModel(attrs)) {
|
|
592
722
|
if (!attrs.collection) attrs.collection = this;
|
|
593
|
-
return attrs;
|
|
723
|
+
return /** @type {Model} */ (attrs);
|
|
594
724
|
}
|
|
595
725
|
options = options ? clone(options) : {};
|
|
596
726
|
options.collection = this;
|
|
597
|
-
const model =
|
|
727
|
+
const model = this.createModel(attrs, options);
|
|
598
728
|
if (!model.validationError) return model;
|
|
599
729
|
this.trigger('invalid', this, model.validationError, options);
|
|
600
|
-
return
|
|
601
|
-
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
602
732
|
|
|
603
|
-
|
|
604
|
-
|
|
733
|
+
/**
|
|
734
|
+
* Internal method called by both remove and set.
|
|
735
|
+
* @param {Model[]} models
|
|
736
|
+
* @param {Options} [options]
|
|
737
|
+
*/
|
|
738
|
+
_removeModels(models, options) {
|
|
605
739
|
const removed = [];
|
|
606
740
|
for (let i = 0; i < models.length; i++) {
|
|
607
741
|
const model = this.get(models[i]);
|
|
@@ -609,7 +743,6 @@ Object.assign(Collection.prototype, Events, {
|
|
|
609
743
|
|
|
610
744
|
const index = this.indexOf(model);
|
|
611
745
|
this.models.splice(index, 1);
|
|
612
|
-
this.length--;
|
|
613
746
|
|
|
614
747
|
// Remove references before triggering 'remove' event to prevent an
|
|
615
748
|
// infinite loop. #3693
|
|
@@ -626,36 +759,55 @@ Object.assign(Collection.prototype, Events, {
|
|
|
626
759
|
this._removeReference(model, options);
|
|
627
760
|
}
|
|
628
761
|
return removed;
|
|
629
|
-
}
|
|
762
|
+
}
|
|
630
763
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
764
|
+
/**
|
|
765
|
+
* Method for checking whether an object should be considered a model for
|
|
766
|
+
* the purposes of adding to the collection.
|
|
767
|
+
* @param {any} model
|
|
768
|
+
*/
|
|
769
|
+
_isModel(model) {
|
|
634
770
|
return model instanceof Model;
|
|
635
|
-
}
|
|
771
|
+
}
|
|
636
772
|
|
|
637
|
-
|
|
638
|
-
|
|
773
|
+
/**
|
|
774
|
+
* Internal method to create a model's ties to a collection.
|
|
775
|
+
* @param {Model} model
|
|
776
|
+
* @param {Options} [options]
|
|
777
|
+
*/
|
|
778
|
+
_addReference(model, options) {
|
|
639
779
|
this._byId[model.cid] = model;
|
|
640
780
|
const id = this.modelId(model.attributes);
|
|
641
781
|
if (id != null) this._byId[id] = model;
|
|
642
782
|
model.on('all', this._onModelEvent, this);
|
|
643
|
-
}
|
|
783
|
+
}
|
|
644
784
|
|
|
645
|
-
|
|
646
|
-
|
|
785
|
+
/**
|
|
786
|
+
* Internal method to sever a model's ties to a collection.
|
|
787
|
+
* @private
|
|
788
|
+
* @param {Model} model
|
|
789
|
+
* @param {Options} [options]
|
|
790
|
+
*/
|
|
791
|
+
_removeReference(model, options) {
|
|
647
792
|
delete this._byId[model.cid];
|
|
648
793
|
const id = this.modelId(model.attributes);
|
|
649
794
|
if (id != null) delete this._byId[id];
|
|
650
795
|
if (this === model.collection) delete model.collection;
|
|
651
796
|
model.off('all', this._onModelEvent, this);
|
|
652
|
-
}
|
|
797
|
+
}
|
|
653
798
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Internal method called every time a model in the set fires an event.
|
|
801
|
+
* Sets need to update their indexes when models change ids. All other
|
|
802
|
+
* events simply proxy through. "add" and "remove" events that originate
|
|
803
|
+
* in other collections are ignored.
|
|
804
|
+
* @private
|
|
805
|
+
* @param {any} event
|
|
806
|
+
* @param {Model} model
|
|
807
|
+
* @param {Collection} collection
|
|
808
|
+
* @param {Options} [options]
|
|
809
|
+
*/
|
|
810
|
+
_onModelEvent(event, model, collection, options) {
|
|
659
811
|
if (model) {
|
|
660
812
|
if ((event === 'add' || event === 'remove') && collection !== this) return;
|
|
661
813
|
if (event === 'destroy') this.remove(model, options);
|
|
@@ -670,30 +822,8 @@ Object.assign(Collection.prototype, Events, {
|
|
|
670
822
|
}
|
|
671
823
|
this.trigger.apply(this, arguments);
|
|
672
824
|
}
|
|
673
|
-
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
// Defining an @@iterator method implements JavaScript's Iterable protocol.
|
|
677
|
-
// In modern ES2015 browsers, this value is found at Symbol.iterator.
|
|
678
|
-
/* global Symbol */
|
|
679
|
-
const $$iterator = typeof Symbol === 'function' && Symbol.iterator;
|
|
680
|
-
if ($$iterator) {
|
|
681
|
-
Collection.prototype[$$iterator] = Collection.prototype.values;
|
|
682
825
|
}
|
|
683
826
|
|
|
684
|
-
// CollectionIterator
|
|
685
|
-
// ------------------
|
|
686
|
-
|
|
687
|
-
// A CollectionIterator implements JavaScript's Iterator protocol, allowing the
|
|
688
|
-
// use of `for of` loops in modern browsers and interoperation between
|
|
689
|
-
// Collection and other JavaScript functions and third-party libraries
|
|
690
|
-
// which can operate on Iterables.
|
|
691
|
-
const CollectionIterator = function(collection, kind) {
|
|
692
|
-
this._collection = collection;
|
|
693
|
-
this._kind = kind;
|
|
694
|
-
this._index = 0;
|
|
695
|
-
};
|
|
696
|
-
|
|
697
827
|
// This "enum" defines the three possible kinds of values which can be emitted
|
|
698
828
|
// by a CollectionIterator that correspond to the values(), keys() and entries()
|
|
699
829
|
// methods on Collection, respectively.
|
|
@@ -701,40 +831,55 @@ const ITERATOR_VALUES = 1;
|
|
|
701
831
|
const ITERATOR_KEYS = 2;
|
|
702
832
|
const ITERATOR_KEYSVALUES = 3;
|
|
703
833
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
834
|
+
class CollectionIterator {
|
|
835
|
+
/**
|
|
836
|
+
* A CollectionIterator implements JavaScript's Iterator protocol, allowing the
|
|
837
|
+
* use of `for of` loops in modern browsers and interoperation between
|
|
838
|
+
* Collection and other JavaScript functions and third-party libraries
|
|
839
|
+
* which can operate on Iterables.
|
|
840
|
+
* @param {Collection} collection
|
|
841
|
+
* @param {Number} kind
|
|
842
|
+
*/
|
|
843
|
+
constructor(collection, kind) {
|
|
844
|
+
this._collection = collection;
|
|
845
|
+
this._kind = kind;
|
|
846
|
+
this._index = 0;
|
|
847
|
+
}
|
|
710
848
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
849
|
+
next() {
|
|
850
|
+
if (this._collection) {
|
|
851
|
+
// Only continue iterating if the iterated collection is long enough.
|
|
852
|
+
if (this._index < this._collection.length) {
|
|
853
|
+
const model = this._collection.at(this._index);
|
|
854
|
+
this._index++;
|
|
855
|
+
|
|
856
|
+
// Construct a value depending on what kind of values should be iterated.
|
|
857
|
+
let value;
|
|
858
|
+
if (this._kind === ITERATOR_VALUES) {
|
|
859
|
+
value = model;
|
|
860
|
+
} else {
|
|
861
|
+
const id = this._collection.modelId(model.attributes);
|
|
862
|
+
if (this._kind === ITERATOR_KEYS) {
|
|
863
|
+
value = id;
|
|
864
|
+
} else {
|
|
865
|
+
// ITERATOR_KEYSVALUES
|
|
866
|
+
value = [id, model];
|
|
867
|
+
}
|
|
729
868
|
}
|
|
869
|
+
return { value: value, done: false };
|
|
730
870
|
}
|
|
731
|
-
|
|
871
|
+
|
|
872
|
+
// Once exhausted, remove the reference to the collection so future
|
|
873
|
+
// calls to the next method always return done.
|
|
874
|
+
this._collection = undefined;
|
|
732
875
|
}
|
|
733
876
|
|
|
734
|
-
|
|
735
|
-
// calls to the next method always return done.
|
|
736
|
-
this._collection = undefined;
|
|
877
|
+
return { value: undefined, done: true };
|
|
737
878
|
}
|
|
738
879
|
|
|
739
|
-
|
|
740
|
-
|
|
880
|
+
[Symbol.iterator]() {
|
|
881
|
+
return this;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export { Collection };
|