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