Completed
Push — master ( 7e3f71...c2963c )
by Felipe
39s
created

_.extend._onModelEvent   C

Complexity

Conditions 10
Paths 14

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
c 1
b 0
f 0
nc 14
nop 4
dl 0
loc 24
rs 5.2164

How to fix   Complexity   

Complexity

Complex classes like _.extend._onModelEvent often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import $ from 'jquery';
0 ignored issues
show
introduced by
Definition for rule 'keyword-spacing' was not found
Loading history...
2
import _ from 'underscore';
3
import {
4
  Backbone,
5
  wrapError,
6
  addUnderscoreMethods
7
} from './core.js';
8
import {
9
  Events
10
} from './events.js';
11
import {
12
  Model
13
} from './model.js';
14
15
// Create a local reference to a common array method we'll want to use later.
16
var slice = Array.prototype.slice;
17
18
// Backbone.Collection
19
// -------------------
20
21
// If models tend to represent a single row of data, a Backbone Collection is
22
// more analogous to a table full of data ... or a small slice or page of that
23
// table, or a collection of rows that belong together for a particular reason
24
// -- all of the messages in this particular folder, all of the documents
25
// belonging to this particular author, and so on. Collections maintain
26
// indexes of their models, both in order, and for lookup by `id`.
27
28
// Create a new **Collection**, perhaps to contain a specific type of `model`.
29
// If a `comparator` is specified, the Collection will maintain
30
// its models in sort order, as they're added and removed.
31
var Collection = function (models, options) {
32
  options = options || {};
33
  this.preinitialize.apply(this, arguments);
34
  if (options.model) {
35
    this.model = options.model;
36
  }
37
  if (options.comparator !== void 0) {
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
38
    this.comparator = options.comparator;
39
  }
40
  this._reset();
41
  this.initialize.apply(this, arguments);
42
  if (models) {
43
    this.reset(models, _.extend({
44
      silent: true
45
    }, options));
46
  }
47
};
48
49
// Default options for `Collection#set`.
50
var setOptions = {
51
  add: true,
52
  remove: true,
53
  merge: true
54
};
55
var addOptions = {
56
  add: true,
57
  remove: false
58
};
59
60
// Splices `insert` into `array` at index `at`.
61
var splice = function (array, insert, at) {
62
  at = Math.min(Math.max(at, 0), array.length);
63
  var tail = Array(array.length - at);
64
  var length = insert.length;
65
  var i;
66
  for (i = 0; i < tail.length; i++) {
67
    tail[i] = array[i + at];
68
  }
69
  for (i = 0; i < length; i++) {
70
    array[i + at] = insert[i];
71
  }
72
  for (i = 0; i < tail.length; i++) {
73
    array[i + length + at] = tail[i];
74
  }
75
};
76
77
// Define the Collection's inheritable methods.
78
_.extend(Collection.prototype, Events, {
79
80
  // The default model for a collection is just a **Backbone.Model**.
81
  // This should be overridden in most cases.
82
  model: Model,
83
84
  // preinitialize is an empty function by default. You can override it with a function
85
  // or object.  preinitialize will run before any instantiation logic is run in the Collection.
86
  preinitialize: function () {},
87
88
  // Initialize is an empty function by default. Override it with your own
89
  // initialization logic.
90
  initialize: function () {},
91
92
  // The JSON representation of a Collection is an array of the
93
  // models' attributes.
94
  toJSON: function (options) {
95
    return this.map(function (model) {
96
      return model.toJSON(options);
97
    });
98
  },
99
100
  // Proxy `Backbone.sync` by default.
101
  sync: function () {
102
    return Backbone.sync.apply(this, arguments);
103
  },
104
105
  // Add a model, or list of models to the set. `models` may be Backbone
106
  // Models or raw JavaScript objects to be converted to Models, or any
107
  // combination of the two.
108
  add: function (models, options) {
109
    return this.set(models, _.extend({
110
      merge: false
111
    }, options, addOptions));
112
  },
113
114
  // Remove a model, or a list of models from the set.
115
  remove: function (models, options) {
116
    options = _.extend({}, options);
117
    var singular = !_.isArray(models);
118
    models = singular ? [models] : models.slice();
119
    var removed = this._removeModels(models, options);
120
    if (!options.silent && removed.length) {
121
      options.changes = {
122
        added: [],
123
        merged: [],
124
        removed: removed
125
      };
126
      this.trigger('update', this, options);
127
    }
128
    return singular ? removed[0] : removed;
129
  },
130
131
  // Update a collection by `set`-ing a new list of models, adding new ones,
132
  // removing models that are no longer present, and merging models that
133
  // already exist in the collection, as necessary. Similar to **Model#set**,
134
  // the core operation for updating the data contained by the collection.
135
  set: function (models, options) {
136
    if (models == null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison === instead of ==.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
137
      return;
138
    }
139
140
    options = _.extend({}, setOptions, options);
141
    if (options.parse && !this._isModel(models)) {
142
      models = this.parse(models, options) || [];
143
    }
144
145
    var singular = !_.isArray(models);
146
    models = singular ? [models] : models.slice();
147
148
    var at = options.at;
149
    if (at != null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
150
      at = +at;
151
    }
152
    if (at > this.length) {
153
      at = this.length;
154
    }
155
    if (at < 0) {
156
      at += this.length + 1;
157
    }
158
159
    var set = [];
160
    var toAdd = [];
161
    var toMerge = [];
162
    var toRemove = [];
163
    var modelMap = {};
164
165
    var add = options.add;
166
    var merge = options.merge;
167
    var remove = options.remove;
168
169
    var sort = false;
170
    var sortable = this.comparator && at == null && options.sort !==
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison === instead of ==.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
171
      false;
172
    var sortAttr = _.isString(this.comparator) ? this.comparator :
173
      null;
174
175
    // Turn bare objects into model references, and prevent invalid models
176
    // from being added.
177
    var model, i;
178
    for (i = 0; i < models.length; i++) {
179
      model = models[i];
180
181
      // If a duplicate is found, prevent it from being added and
182
      // optionally merge it into the existing model.
183
      var existing = this.get(model);
184
      if (existing) {
185
        if (merge && model !== existing) {
186
          var attrs = this._isModel(model) ? model.attributes :
187
            model;
188
          if (options.parse) {
0 ignored issues
show
introduced by
Blocks are nested too deeply (4).
Loading history...
189
            attrs = existing.parse(attrs,
190
              options);
191
          }
192
          existing.set(attrs, options);
193
          toMerge.push(existing);
194
          if (sortable && !sort) {
0 ignored issues
show
introduced by
Blocks are nested too deeply (4).
Loading history...
195
            sort = existing.hasChanged(
196
              sortAttr);
197
          }
198
        }
199
        if (!modelMap[existing.cid]) {
200
          modelMap[existing.cid] = true;
201
          set.push(existing);
202
        }
203
        models[i] = existing;
204
205
        // If this is a new, valid model, push it to the `toAdd` list.
206
      } else if (add) {
207
        model = models[i] = this._prepareModel(model, options);
208
        if (model) {
209
          toAdd.push(model);
210
          this._addReference(model, options);
211
          modelMap[model.cid] = true;
212
          set.push(model);
213
        }
214
      }
215
    }
216
217
    // Remove stale models.
218
    if (remove) {
219
      for (i = 0; i < this.length; i++) {
220
        model = this.models[i];
221
        if (!modelMap[model.cid]) {
222
          toRemove.push(model);
223
        }
224
      }
225
      if (toRemove.length) {
226
        this._removeModels(toRemove, options);
227
      }
228
    }
229
230
    // See if sorting is needed, update `length` and splice in new models.
231
    var orderChanged = false;
232
    var replace = !sortable && add && remove;
233
    if (set.length && replace) {
234
      orderChanged = this.length !== set.length || _.some(this.models,
235
        function (m, index) {
236
          return m !== set[index];
237
        });
238
      this.models.length = 0;
239
      splice(this.models, set, 0);
240
      this.length = this.models.length;
241
    } else if (toAdd.length) {
242
      if (sortable) {
243
        sort = true;
244
      }
245
      splice(this.models, toAdd, at == null ? this.length : at);
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison === instead of ==.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
246
      this.length = this.models.length;
247
    }
248
249
    // Silently sort the collection if appropriate.
250
    if (sort) {
251
      this.sort({
252
        silent: true
253
      });
254
    }
255
256
    // Unless silenced, it's time to fire all appropriate add/sort/update events.
257
    if (!options.silent) {
258
      for (i = 0; i < toAdd.length; i++) {
259
        if (at != null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
260
          options.index = at + i;
261
        }
262
        model = toAdd[i];
263
        model.trigger('add', model, this, options);
264
      }
265
      if (sort || orderChanged) {
266
        this.trigger('sort', this,
267
          options);
268
      }
269
      if (toAdd.length || toRemove.length || toMerge.length) {
270
        options.changes = {
271
          added: toAdd,
272
          removed: toRemove,
273
          merged: toMerge
274
        };
275
        this.trigger('update', this, options);
276
      }
277
    }
278
279
    // Return the added (or merged) model (or models).
280
    return singular ? models[0] : models;
281
  },
282
283
  // When you have more items than you want to add or remove individually,
284
  // you can reset the entire set with a new list of models, without firing
285
  // any granular `add` or `remove` events. Fires `reset` when finished.
286
  // Useful for bulk operations and optimizations.
287
  reset: function (models, options) {
288
    options = options ? _.clone(options) : {};
289
    for (var i = 0; i < this.models.length; i++) {
290
      this._removeReference(this.models[i], options);
291
    }
292
    options.previousModels = this.models;
293
    this._reset();
294
    models = this.add(models, _.extend({
295
      silent: true
296
    }, options));
297
    if (!options.silent) {
298
      this.trigger('reset', this, options);
299
    }
300
    return models;
301
  },
302
303
  // Add a model to the end of the collection.
304
  push: function (model, options) {
305
    return this.add(model, _.extend({
306
      at: this.length
307
    }, options));
308
  },
309
310
  // Remove a model from the end of the collection.
311
  pop: function (options) {
312
    var model = this.at(this.length - 1);
313
    return this.remove(model, options);
314
  },
315
316
  // Add a model to the beginning of the collection.
317
  unshift: function (model, options) {
318
    return this.add(model, _.extend({
319
      at: 0
320
    }, options));
321
  },
322
323
  // Remove a model from the beginning of the collection.
324
  shift: function (options) {
325
    var model = this.at(0);
326
    return this.remove(model, options);
327
  },
328
329
  // Slice out a sub-array of models from the collection.
330
  slice: function () {
331
    return slice.apply(this.models, arguments);
332
  },
333
334
  // Get a model from the set by id, cid, model object with id or cid
335
  // properties, or an attributes object that is transformed through modelId.
336
  get: function (obj) {
337
    if (obj == null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison === instead of ==.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
338
      return void 0;
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
339
    }
340
    return this._byId[obj] ||
341
      this._byId[this.modelId(obj.attributes || obj)] ||
342
      obj.cid && this._byId[obj.cid];
343
  },
344
345
  // Returns `true` if the model is in the collection.
346
  has: function (obj) {
347
    return this.get(obj) != null;
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
348
  },
349
350
  // Get the model at the given index.
351
  at: function (index) {
352
    if (index < 0) {
353
      index += this.length;
354
    }
355
    return this.models[index];
356
  },
357
358
  // Return models with matching attributes. Useful for simple cases of
359
  // `filter`.
360
  where: function (attrs, first) {
361
    return this[first ? 'find' : 'filter'](attrs);
362
  },
363
364
  // Return the first model with matching attributes. Useful for simple cases
365
  // of `find`.
366
  findWhere: function (attrs) {
367
    return this.where(attrs, true);
368
  },
369
370
  // Force the collection to re-sort itself. You don't need to call this under
371
  // normal circumstances, as the set will maintain sort order as each item
372
  // is added.
373
  sort: function (options) {
374
    var comparator = this.comparator;
375
    if (!comparator) {
376
      throw new Error(
377
        'Cannot sort a set without a comparator');
378
    }
379
    options = options || {};
380
381
    var length = comparator.length;
382
    if (_.isFunction(comparator)) comparator = _.bind(
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
introduced by
Expected { after 'if' condition.
Loading history...
383
      comparator,
384
      this);
385
386
    // Run sort based on type of `comparator`.
387
    if (length === 1 || _.isString(comparator)) {
388
      this.models = this.sortBy(comparator);
389
    } else {
390
      this.models.sort(comparator);
391
    }
392
    if (!options.silent) {
393
      this.trigger('sort', this, options);
394
    }
395
    return this;
396
  },
397
398
  // Pluck an attribute from each model in the collection.
399
  pluck: function (attr) {
400
    return this.map(attr + '');
401
  },
402
403
  // Fetch the default set of models for this collection, resetting the
404
  // collection when they arrive. If `reset: true` is passed, the response
405
  // data will be passed through the `reset` method instead of `set`.
406
  fetch: function (options) {
407
    options = _.extend({
408
      parse: true
409
    }, options);
410
    var success = options.success;
411
    var collection = this;
0 ignored issues
show
introduced by
Unexpected alias 'collection' for 'this'.
Loading history...
412
    options.success = function (resp) {
413
      var method = options.reset ? 'reset' : 'set';
414
      collection[method](resp, options);
415
      if (success) {
416
        success.call(options.context, collection,
417
          resp,
418
          options);
419
      }
420
      collection.trigger('sync', collection, resp, options);
421
    };
422
    wrapError(this, options);
423
    return this.sync('read', this, options);
424
  },
425
426
  // Create a new instance of a model in this collection. Add the model to the
427
  // collection immediately, unless `wait: true` is passed, in which case we
428
  // wait for the server to agree.
429
  create: function (model, options) {
430
    options = options ? _.clone(options) : {};
431
    var wait = options.wait;
432
    model = this._prepareModel(model, options);
433
    if (!model) {
434
      return false;
435
    }
436
    if (!wait) {
437
      this.add(model, options);
438
    }
439
    var collection = this;
0 ignored issues
show
introduced by
Unexpected alias 'collection' for 'this'.
Loading history...
440
    var success = options.success;
441
    options.success = function (m, resp, callbackOpts) {
442
      if (wait) {
443
        collection.add(m, callbackOpts);
444
      }
445
      if (success) {
446
        success.call(callbackOpts.context, m, resp,
447
          callbackOpts);
448
      }
449
    };
450
    model.save(null, options);
451
    return model;
452
  },
453
454
  // **parse** converts a response into a list of models to be added to the
455
  // collection. The default implementation is just to pass it through.
456
  parse: function (resp, options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
457
    return resp;
458
  },
459
460
  // Create a new collection with an identical list of models as this one.
461
  clone: function () {
462
    return new this.constructor(this.models, {
463
      model: this.model,
464
      comparator: this.comparator
465
    });
466
  },
467
468
  // Define how to uniquely identify models in the collection.
469
  modelId: function (attrs) {
470
    return attrs[this.model.prototype.idAttribute || 'id'];
471
  },
472
473
  // Private method to reset all internal state. Called when the collection
474
  // is first initialized or reset.
475
  _reset: function () {
476
    this.length = 0;
477
    this.models = [];
478
    this._byId = {};
479
  },
480
481
  // Prepare a hash of attributes (or other model) to be added to this
482
  // collection.
483
  _prepareModel: function (attrs, options) {
484
    if (this._isModel(attrs)) {
485
      if (!attrs.collection) {
486
        attrs.collection = this;
487
      }
488
      return attrs;
489
    }
490
    options = options ? _.clone(options) : {};
491
    options.collection = this;
492
    var model = new this.model(attrs, options);
0 ignored issues
show
introduced by
A constructor name should not start with a lowercase letter.
Loading history...
493
    if (!model.validationError) {
494
      return model;
495
    }
496
    this.trigger('invalid', this, model.validationError, options);
497
    return false;
498
  },
499
500
  // Internal method called by both remove and set.
501
  _removeModels: function (models, options) {
502
    var removed = [];
503
    for (var i = 0; i < models.length; i++) {
504
      var model = this.get(models[i]);
505
      if (!model) {
506
        continue;
507
      }
508
509
      var index = this.indexOf(model);
510
      this.models.splice(index, 1);
511
      this.length--;
512
513
      // Remove references before triggering 'remove' event to prevent an
514
      // infinite loop. #3693
515
      delete this._byId[model.cid];
516
      var id = this.modelId(model.attributes);
517
      if (id != null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
518
        delete this._byId[id];
519
      }
520
521
      if (!options.silent) {
522
        options.index = index;
523
        model.trigger('remove', model, this, options);
524
      }
525
526
      removed.push(model);
527
      this._removeReference(model, options);
528
    }
529
    return removed;
530
  },
531
532
  // Method for checking whether an object should be considered a model for
533
  // the purposes of adding to the collection.
534
  _isModel: function (model) {
535
    return model instanceof Model;
536
  },
537
538
  // Internal method to create a model's ties to a collection.
539
  _addReference: function (model, options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
540
    this._byId[model.cid] = model;
541
    var id = this.modelId(model.attributes);
542
    if (id != null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
543
      this._byId[id] = model;
544
    }
545
    model.on('all', this._onModelEvent, this);
546
  },
547
548
  // Internal method to sever a model's ties to a collection.
549
  _removeReference: function (model, options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
550
    delete this._byId[model.cid];
551
    var id = this.modelId(model.attributes);
552
    if (id != null) {
0 ignored issues
show
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
553
      delete this._byId[id];
554
    }
555
    if (this === model.collection) {
556
      delete model.collection;
557
    }
558
    model.off('all', this._onModelEvent, this);
559
  },
560
561
  // Internal method called every time a model in the set fires an event.
562
  // Sets need to update their indexes when models change ids. All other
563
  // events simply proxy through. "add" and "remove" events that originate
564
  // in other collections are ignored.
565
  _onModelEvent: function (event, model, collection, options) {
566
    if (model) {
567
      if ((event === 'add' || event === 'remove') && collection !==
568
        this) {
569
        return;
570
      }
571
      if (event === 'destroy') {
572
        this.remove(model, options);
573
      }
574
      if (event === 'change') {
575
        var prevId = this.modelId(model.previousAttributes());
576
        var id = this.modelId(model.attributes);
577
        if (prevId !== id) {
578
          if (prevId != null) {
0 ignored issues
show
introduced by
Blocks are nested too deeply (4).
Loading history...
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
579
            delete this._byId[prevId];
580
          }
581
          if (id != null) {
0 ignored issues
show
introduced by
Blocks are nested too deeply (4).
Loading history...
introduced by
Use ‘===’ to compare with ‘null’.
Loading history...
Bug Best Practice introduced by
Apart from some edge-cases, it is generally advisable to use the strict comparison !== instead of !=.

The loose comparison such as == or != might produce some weird results for some values, unless you explicitly want to have this behavior here, better use the strict alternative.

Learn more about loose comparison in Javascript.

Loading history...
582
            this._byId[id] = model;
583
          }
584
        }
585
      }
586
    }
587
    this.trigger.apply(this, arguments);
588
  }
589
590
});
591
592
// Underscore methods that we want to implement on the Collection.
593
// 90% of the core usefulness of Backbone Collections is actually implemented
594
// right here:
595
var collectionMethods = {
596
  forEach: 3,
597
  each: 3,
598
  map: 3,
599
  collect: 3,
600
  reduce: 0,
601
  foldl: 0,
602
  inject: 0,
603
  reduceRight: 0,
604
  foldr: 0,
605
  find: 3,
606
  detect: 3,
607
  filter: 3,
608
  select: 3,
609
  reject: 3,
610
  every: 3,
611
  all: 3,
612
  some: 3,
613
  any: 3,
614
  include: 3,
615
  includes: 3,
616
  contains: 3,
617
  invoke: 0,
618
  max: 3,
619
  min: 3,
620
  toArray: 1,
621
  size: 1,
622
  first: 3,
623
  head: 3,
624
  take: 3,
625
  initial: 3,
626
  rest: 3,
627
  tail: 3,
628
  drop: 3,
629
  last: 3,
630
  without: 0,
631
  difference: 0,
632
  indexOf: 3,
633
  shuffle: 1,
634
  lastIndexOf: 3,
635
  isEmpty: 1,
636
  chain: 1,
637
  sample: 3,
638
  partition: 3,
639
  groupBy: 3,
640
  countBy: 3,
641
  sortBy: 3,
642
  indexBy: 3,
643
  findIndex: 3,
644
  findLastIndex: 3
645
};
646
647
// Mix in each Underscore method as a proxy to `Collection#models`.
648
addUnderscoreMethods(Collection, collectionMethods, 'models');
649
650
export {
651
  Collection
652
};
653