js_stack 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c58ef329ca812c0185ee2706edc5e25f18e4428
4
- data.tar.gz: 444921de4e39f611a736bf99434b05c09787eebb
3
+ metadata.gz: 109a21b1d4dbcac6bc91e568c3bce32477405e38
4
+ data.tar.gz: 064f5b1736ef2ac8c4f6a0ae072f211bdeb93749
5
5
  SHA512:
6
- metadata.gz: fdcb232af4b94e8fae77ed2a9b4712fa824d9c57809a504ce685a761bc9887c85757305351ef348315d2b98d9773f5961aac4ddc92c1321cff4a3f69ecc0d5d3
7
- data.tar.gz: b1f94eff91d83389944dab447dbde9769f46ffb2b862b0b5d7bf27acfd276ff343920bb23dd7578f68d05081ff517c937a2847e09d114dcccc00d65c836ae7bb
6
+ metadata.gz: d60ef1d9ff3db827d27b746d19f4dcb0e63f20fb0eeeb2cd24338ae519d7e0193e31dfd7059535243aba873ee7d569d458c9004a9846b7dfc700b18d7b345d0f
7
+ data.tar.gz: faf2447bcb752361755b4ddcc7aeba9bcba99ae776f8ab1dbff1c7b6769f602aef4a19c97a720c54d085f6557cce35a0e82064a3f7f6abc971c367b0c959e547
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ # 1.6.0
4
+
5
+ * update marionette 2.3.0 -> 2.3.1 [@gvl]
6
+ * update backbone.virtualcollection 0.5.3 -> 0.6.0 [@gvl]
7
+ * update underscore.string 2.4.0 -> 3.0.1 [@gvl]
8
+
3
9
  # 1.5.0
4
10
 
5
11
  * update js-routes to cover 1.0.0 version [@gvl]
data/README.md CHANGED
@@ -48,7 +48,7 @@ Examples:
48
48
  | Library | Versions | Changelog | Homepage |
49
49
  | :-----: | :------: | :-------: | :------: |
50
50
  | backbone | **1.1.2**, 1.0.0 | [changelog](http://backbonejs.org/#changelog) | [homepage](http://backbonejs.org/) |
51
- | marionette | **2.3.0**, 2.2.2, 2.1.0, 2.0.3, 1.8.8, 1.7.4, 1.6.4, 1.5.1, 1.4.1, 1.1.0 | [changelog](https://github.com/marionettejs/backbone.marionette/blob/master/changelog.md) | [homepage](http://marionettejs.com/) |
51
+ | marionette | **2.3.1**, 2.2.2, 2.1.0, 2.0.3, 1.8.8, 1.7.4, 1.6.4, 1.5.1, 1.4.1, 1.1.0 | [changelog](https://github.com/marionettejs/backbone.marionette/blob/master/changelog.md) | [homepage](http://marionettejs.com/) |
52
52
  | underscore | **1.7.0**, 1.6.0, 1.5.2 | [changelog](http://underscorejs.org/#changelog) | [homepage](http://underscorejs.org/) |
53
53
  | hamlcoffee | **1.16** | [changelog](https://github.com/netzpirat/haml_coffee_assets/blob/master/CHANGELOG.md) | [homepage](https://github.com/netzpirat/haml_coffee_assets) |
54
54
  | js-routes | **1.0.0** | [changelog](https://github.com/railsware/js-routes/blob/master/CHANGELOG.md) | [homepage](https://github.com/railsware/js-routes) |
@@ -65,11 +65,11 @@ Examples:
65
65
  | backbone routefilter | **0.2.1** | [changelog](https://github.com/boazsender/backbone.routefilter#release-history) | [homepage](https://github.com/boazsender/backbone.routefilter) |
66
66
  | backbone stickit | **0.8.0**, 0.7.0, 0.6.3 | [changelog](http://nytimes.github.io/backbone.stickit/#change-log) | [homepage](http://nytimes.github.io/backbone.stickit/) |
67
67
  | backbone validation | **0.9.1**, 0.8.1 | [changelog](https://github.com/thedersen/backbone.validation#release-notes) | [homepage](https://github.com/thedersen/backbone.validation) |
68
- | backbone virtualcollection | **0.5.3**, 0.4.15 | [changelog](https://github.com/p3drosola/Backbone.VirtualCollection#changelog) | [homepage](https://github.com/p3drosola/Backbone.VirtualCollection) |
68
+ | backbone virtualcollection | **0.6.0**, 0.5.3, 0.4.15 | [changelog](https://github.com/p3drosola/Backbone.VirtualCollection/wiki/Changelog) | [homepage](https://github.com/p3drosola/Backbone.VirtualCollection) |
69
69
  | cocktail | **0.5.8** | None | [homepage](https://github.com/onsi/cocktail) |
70
- | momentjs | **2.8.3** | [changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) | [homepage](https://github.com/derekprior/momentjs-rails) |
70
+ | momentjs | **2.9.0** | [changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) | [homepage](https://github.com/derekprior/momentjs-rails) |
71
71
  | underscore inflections | **0.2.1** | None | [homepage](https://github.com/geetarista/underscore.inflections) |
72
- | underscore string | **2.4.0**, 2.3.2 | [changelog](https://github.com/epeli/underscore.string/blob/master/CHANGELOG.markdown) | [homepage](http://epeli.github.io/underscore.string/) |
72
+ | underscore string | **3.0.1**, 2.4.0, 2.3.2 | [changelog](https://github.com/epeli/underscore.string/blob/master/CHANGELOG.markdown) | [homepage](http://epeli.github.io/underscore.string/) |
73
73
 
74
74
  ## Contributing
75
75
 
@@ -1,3 +1,3 @@
1
1
  module JsStack
2
- VERSION = '1.5.0'
2
+ VERSION = '1.6.0'
3
3
  end
@@ -1,8 +1,8 @@
1
1
  // MarionetteJS (Backbone.Marionette)
2
2
  // ----------------------------------
3
- // v2.3.0
3
+ // v2.3.1
4
4
  //
5
- // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
5
+ // Copyright (c)2015 Derick Bailey, Muted Solutions, LLC.
6
6
  // Distributed under MIT license
7
7
  //
8
8
  // http://marionettejs.com
@@ -38,7 +38,7 @@
38
38
  /* istanbul ignore next */
39
39
  // Backbone.BabySitter
40
40
  // -------------------
41
- // v0.1.4
41
+ // v0.1.5
42
42
  //
43
43
  // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
44
44
  // Distributed under MIT license
@@ -167,7 +167,7 @@
167
167
  // return the public API
168
168
  return Container;
169
169
  }(Backbone, _);
170
- Backbone.ChildViewContainer.VERSION = "0.1.4";
170
+ Backbone.ChildViewContainer.VERSION = "0.1.5";
171
171
  Backbone.ChildViewContainer.noConflict = function() {
172
172
  Backbone.ChildViewContainer = previousChildViewContainer;
173
173
  return this;
@@ -493,7 +493,7 @@
493
493
 
494
494
  var Marionette = Backbone.Marionette = {};
495
495
 
496
- Marionette.VERSION = '2.3.0';
496
+ Marionette.VERSION = '2.3.1';
497
497
 
498
498
  Marionette.noConflict = function() {
499
499
  root.Marionette = previousMarionette;
@@ -544,6 +544,17 @@
544
544
  return Marionette.getOption(this, optionName);
545
545
  };
546
546
 
547
+ // Similar to `_.result`, this is a simple helper
548
+ // If a function is provided we call it with context
549
+ // otherwise just return the value. If the value is
550
+ // undefined return a default value
551
+ Marionette._getValue = function(value, context, params) {
552
+ if (_.isFunction(value)) {
553
+ value = value.apply(context, params);
554
+ }
555
+ return value;
556
+ };
557
+
547
558
  // Marionette.normalizeMethods
548
559
  // ----------------------
549
560
 
@@ -802,7 +813,7 @@
802
813
  if (!entity || !bindings) { return; }
803
814
 
804
815
  // type-check bindings
805
- if (!_.isFunction(bindings) && !_.isObject(bindings)) {
816
+ if (!_.isObject(bindings)) {
806
817
  throw new Marionette.Error({
807
818
  message: 'Bindings must be an object or function.',
808
819
  url: 'marionette.functions.html#marionettebindentityevents'
@@ -810,9 +821,7 @@
810
821
  }
811
822
 
812
823
  // allow the bindings to be a function
813
- if (_.isFunction(bindings)) {
814
- bindings = bindings.call(target);
815
- }
824
+ bindings = Marionette._getValue(bindings, target);
816
825
 
817
826
  // iterate the bindings and bind them
818
827
  _.each(bindings, function(methods, evt) {
@@ -1206,24 +1215,18 @@
1206
1215
  }
1207
1216
  },
1208
1217
 
1209
- // Override this method to change how the region finds the
1210
- // DOM element that it manages. Return a jQuery selector object.
1218
+ // Override this method to change how the region finds the DOM
1219
+ // element that it manages. Return a jQuery selector object scoped
1220
+ // to a provided parent el or the document if none exists.
1211
1221
  getEl: function(el) {
1212
- return Backbone.$(el);
1222
+ return Backbone.$(el, Marionette._getValue(this.options.parentEl, this));
1213
1223
  },
1214
1224
 
1215
1225
  // Override this method to change how the new view is
1216
1226
  // appended to the `$el` that the region is managing
1217
1227
  attachHtml: function(view) {
1218
- // empty the node and append new view
1219
- // We can not use `.innerHTML` due to the fact that IE
1220
- // will not let us clear the html of tables and selects.
1221
- // We also do not want to use the more declarative `empty` method
1222
- // that jquery exposes since `.empty` loops over all of the children DOM
1223
- // nodes and unsets the listeners on each node. While this seems like
1224
- // a desirable thing, it comes at quite a high perf cost. For that reason
1225
- // we are simply clearing the html contents of the node.
1226
- this.$el.html('');
1228
+ this.$el.contents().detach();
1229
+
1227
1230
  this.el.appendChild(view.el);
1228
1231
  },
1229
1232
 
@@ -1349,28 +1352,7 @@
1349
1352
  options.el = regionConfig.selector;
1350
1353
  }
1351
1354
 
1352
- var region = new RegionClass(options);
1353
-
1354
- // override the `getEl` function if we have a parentEl
1355
- // this must be overridden to ensure the selector is found
1356
- // on the first use of the region. if we try to assign the
1357
- // region's `el` to `parentEl.find(selector)` in the object
1358
- // literal to build the region, the element will not be
1359
- // guaranteed to be in the DOM already, and will cause problems
1360
- if (regionConfig.parentEl) {
1361
- region.getEl = function(el) {
1362
- if (_.isObject(el)) {
1363
- return Backbone.$(el);
1364
- }
1365
- var parentEl = regionConfig.parentEl;
1366
- if (_.isFunction(parentEl)) {
1367
- parentEl = parentEl();
1368
- }
1369
- return parentEl.find(el);
1370
- };
1371
- }
1372
-
1373
- return region;
1355
+ return new RegionClass(options);
1374
1356
  },
1375
1357
 
1376
1358
  // Build the region directly from a given `RegionClass`
@@ -1397,26 +1379,19 @@
1397
1379
  // each key becomes the region name, and each value is
1398
1380
  // the region definition.
1399
1381
  addRegions: function(regionDefinitions, defaults) {
1400
- if (_.isFunction(regionDefinitions)) {
1401
- regionDefinitions = regionDefinitions.apply(this, arguments);
1402
- }
1382
+ regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments);
1403
1383
 
1404
- var regions = {};
1405
-
1406
- _.each(regionDefinitions, function(definition, name) {
1384
+ return _.reduce(regionDefinitions, function(regions, definition, name) {
1407
1385
  if (_.isString(definition)) {
1408
1386
  definition = {selector: definition};
1409
1387
  }
1410
-
1411
1388
  if (definition.selector) {
1412
1389
  definition = _.defaults({}, definition, defaults);
1413
1390
  }
1414
1391
 
1415
- var region = this.addRegion(name, definition);
1416
- regions[name] = region;
1417
- }, this);
1418
-
1419
- return regions;
1392
+ regions[name] = this.addRegion(name, definition);
1393
+ return regions;
1394
+ }, {}, this);
1420
1395
  },
1421
1396
 
1422
1397
  // Add an individual region to the region manager,
@@ -1627,12 +1602,7 @@
1627
1602
  });
1628
1603
  }
1629
1604
 
1630
- var templateFunc;
1631
- if (typeof template === 'function') {
1632
- templateFunc = template;
1633
- } else {
1634
- templateFunc = Marionette.TemplateCache.get(template);
1635
- }
1605
+ var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template);
1636
1606
 
1637
1607
  return templateFunc(data);
1638
1608
  }
@@ -1645,11 +1615,12 @@
1645
1615
 
1646
1616
  // The core view class that other Marionette views extend from.
1647
1617
  Marionette.View = Backbone.View.extend({
1618
+ isDestroyed: false,
1648
1619
 
1649
1620
  constructor: function(options) {
1650
1621
  _.bindAll(this, 'render');
1651
1622
 
1652
- options = _.isFunction(options) ? options.call(this) : options;
1623
+ options = Marionette._getValue(options, this);
1653
1624
 
1654
1625
  // this exposes view options to the view initializer
1655
1626
  // this is a backfill since backbone removed the assignment
@@ -1687,9 +1658,7 @@
1687
1658
  mixinTemplateHelpers: function(target) {
1688
1659
  target = target || {};
1689
1660
  var templateHelpers = this.getOption('templateHelpers');
1690
- if (_.isFunction(templateHelpers)) {
1691
- templateHelpers = templateHelpers.call(this);
1692
- }
1661
+ templateHelpers = Marionette._getValue(templateHelpers, this);
1693
1662
  return _.extend(target, templateHelpers);
1694
1663
  },
1695
1664
 
@@ -1741,8 +1710,7 @@
1741
1710
 
1742
1711
  // internal method to delegate DOM events and triggers
1743
1712
  _delegateDOMEvents: function(eventsArg) {
1744
- var events = eventsArg || this.events;
1745
- if (_.isFunction(events)) { events = events.call(this); }
1713
+ var events = Marionette._getValue(eventsArg || this.events, this);
1746
1714
 
1747
1715
  // normalize ui keys
1748
1716
  events = this.normalizeUIKeys(events);
@@ -1845,8 +1813,7 @@
1845
1813
  this.ui = {};
1846
1814
 
1847
1815
  // bind each of the selectors
1848
- _.each(_.keys(bindings), function(key) {
1849
- var selector = bindings[key];
1816
+ _.each(bindings, function(selector, key) {
1850
1817
  this.ui[key] = this.$(selector);
1851
1818
  }, this);
1852
1819
  },
@@ -2342,9 +2309,7 @@
2342
2309
  // in order to keep the children in sync with the collection.
2343
2310
  addChild: function(child, ChildView, index) {
2344
2311
  var childViewOptions = this.getOption('childViewOptions');
2345
- if (_.isFunction(childViewOptions)) {
2346
- childViewOptions = childViewOptions.call(this, child, index);
2347
- }
2312
+ childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]);
2348
2313
 
2349
2314
  var view = this.buildChildView(child, ChildView, childViewOptions);
2350
2315
 
@@ -2368,22 +2333,14 @@
2368
2333
  if (increment) {
2369
2334
  // assign the index to the view
2370
2335
  view._index = index;
2371
-
2372
- // increment the index of views after this one
2373
- this.children.each(function (laterView) {
2374
- if (laterView._index >= view._index) {
2375
- laterView._index++;
2376
- }
2377
- });
2378
- }
2379
- else {
2380
- // decrement the index of views after this one
2381
- this.children.each(function (laterView) {
2382
- if (laterView._index >= view._index) {
2383
- laterView._index--;
2384
- }
2385
- });
2386
2336
  }
2337
+
2338
+ // update the indexes of views after this one
2339
+ this.children.each(function (laterView) {
2340
+ if (laterView._index >= view._index) {
2341
+ laterView._index += increment ? 1 : -1;
2342
+ }
2343
+ });
2387
2344
  },
2388
2345
 
2389
2346
 
@@ -2707,7 +2664,7 @@
2707
2664
  var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
2708
2665
  if (childViewContainer) {
2709
2666
 
2710
- var selector = _.isFunction(childViewContainer) ? childViewContainer.call(containerView) : childViewContainer;
2667
+ var selector = Marionette._getValue(childViewContainer, containerView);
2711
2668
 
2712
2669
  if (selector.charAt(0) === '@' && containerView.ui) {
2713
2670
  container = containerView.ui[selector.substr(4)];
@@ -2824,7 +2781,7 @@
2824
2781
  _buildRegions: function(regions) {
2825
2782
  var defaults = {
2826
2783
  regionClass: this.getOption('regionClass'),
2827
- parentEl: _.partial(_.result, this, '$el')
2784
+ parentEl: _.partial(_.result, this, 'el')
2828
2785
  };
2829
2786
 
2830
2787
  return this.regionManager.addRegions(regions, defaults);
@@ -2836,19 +2793,13 @@
2836
2793
  var regions;
2837
2794
  this._initRegionManager();
2838
2795
 
2839
- if (_.isFunction(this.regions)) {
2840
- regions = this.regions(options);
2841
- } else {
2842
- regions = this.regions || {};
2843
- }
2796
+ regions = Marionette._getValue(this.regions, this, [options]) || {};
2844
2797
 
2845
2798
  // Enable users to define `regions` as instance options.
2846
2799
  var regionOptions = this.getOption.call(options, 'regions');
2847
2800
 
2848
2801
  // enable region options to be a function
2849
- if (_.isFunction(regionOptions)) {
2850
- regionOptions = regionOptions.call(this, options);
2851
- }
2802
+ regionOptions = Marionette._getValue(regionOptions, this, [options]);
2852
2803
 
2853
2804
  _.extend(regions, regionOptions);
2854
2805
 
@@ -2957,6 +2908,8 @@
2957
2908
  // method for things to work properly.
2958
2909
 
2959
2910
  Marionette.Behaviors = (function(Marionette, _) {
2911
+ // Borrow event splitter from Backbone
2912
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
2960
2913
 
2961
2914
  function Behaviors(view, behaviors) {
2962
2915
 
@@ -2983,12 +2936,12 @@
2983
2936
 
2984
2937
  behaviorEvents: function(behaviorEvents, behaviors) {
2985
2938
  var _behaviorsEvents = {};
2986
- var viewUI = _.result(this, 'ui');
2939
+ var viewUI = this._uiBindings || _.result(this, 'ui');
2987
2940
 
2988
2941
  _.each(behaviors, function(b, i) {
2989
2942
  var _events = {};
2990
2943
  var behaviorEvents = _.clone(_.result(b, 'events')) || {};
2991
- var behaviorUI = _.result(b, 'ui');
2944
+ var behaviorUI = b._uiBindings || _.result(b, 'ui');
2992
2945
 
2993
2946
  // Construct an internal UI hash first using
2994
2947
  // the views UI hash and then the behaviors UI hash.
@@ -3001,21 +2954,25 @@
3001
2954
  // a user to use the @ui. syntax.
3002
2955
  behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui);
3003
2956
 
3004
- _.each(_.keys(behaviorEvents), function(key) {
3005
- // Append white-space at the end of each key to prevent behavior key collisions.
3006
- // This is relying on the fact that backbone events considers "click .foo" the same as
3007
- // "click .foo ".
2957
+ var j = 0;
2958
+ _.each(behaviorEvents, function(behaviour, key) {
2959
+ var match = key.match(delegateEventSplitter);
2960
+
2961
+ // Set event name to be namespaced using the view cid,
2962
+ // the behavior index, and the behavior event index
2963
+ // to generate a non colliding event namespace
2964
+ // http://api.jquery.com/event.namespace/
2965
+ var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join(''),
2966
+ selector = match[2];
3008
2967
 
3009
- // +2 is used because new Array(1) or 0 is "" and not " "
3010
- var whitespace = (new Array(i + 2)).join(' ');
3011
- var eventKey = key + whitespace;
3012
- var handler = _.isFunction(behaviorEvents[key]) ? behaviorEvents[key] : b[behaviorEvents[key]];
2968
+ var eventKey = eventName + selector;
2969
+ var handler = _.isFunction(behaviour) ? behaviour : b[behaviour];
3013
2970
 
3014
2971
  _events[eventKey] = _.bind(handler, b);
3015
- });
2972
+ }, this);
3016
2973
 
3017
2974
  _behaviorsEvents = _.extend(_behaviorsEvents, _events);
3018
- });
2975
+ }, this);
3019
2976
 
3020
2977
  return _behaviorsEvents;
3021
2978
  }
@@ -3050,7 +3007,7 @@
3050
3007
  }
3051
3008
 
3052
3009
  // Get behavior class can be either a flat object or a method
3053
- return _.isFunction(Behaviors.behaviorsLookup) ? Behaviors.behaviorsLookup.apply(this, arguments)[key] : Behaviors.behaviorsLookup[key];
3010
+ return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key];
3054
3011
  },
3055
3012
 
3056
3013
  // Iterate over the behaviors object, for each behavior
@@ -3101,7 +3058,7 @@
3101
3058
 
3102
3059
  triggersHash = Marionette.normalizeUIKeys(triggersHash, ui);
3103
3060
 
3104
- _.each(triggersHash, _.partial(this._setHandlerForBehavior, behavior, i), this);
3061
+ _.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i));
3105
3062
  },
3106
3063
 
3107
3064
  // Internal method to create and assign the trigger handler for a given
@@ -1 +1 @@
1
- //= require js_stack/base/marionette/2.3.0
1
+ //= require js_stack/base/marionette/2.3.1
@@ -0,0 +1,202 @@
1
+ // Available under the MIT License (MIT)
2
+
3
+ var VirtualCollection,
4
+ Backbone = require('backbone'),
5
+ _ = require('underscore');
6
+
7
+ VirtualCollection = Backbone.Collection.extend({
8
+
9
+ constructor: function (collection, options) {
10
+ options = options || {};
11
+ this.collection = collection;
12
+
13
+ if (options.comparator !== undefined) this.comparator = options.comparator;
14
+ if (options.close_with) this.bindLifecycle(options.close_with, 'close'); // Marionette 1.*
15
+ if (options.destroy_with) this.bindLifecycle(options.destroy_with, 'destroy'); // Marionette 2.*
16
+ if (!this.model) this.model = collection.model;
17
+
18
+ this.accepts = VirtualCollection.buildFilter(options.filter);
19
+ this._rebuildIndex();
20
+ this.listenTo(this.collection, 'add', this._onAdd);
21
+ this.listenTo(this.collection, 'remove', this._onRemove);
22
+ this.listenTo(this.collection, 'change', this._onChange);
23
+ this.listenTo(this.collection, 'reset', this._onReset);
24
+ this.listenTo(this.collection, 'sort', this._onSort);
25
+
26
+ this.initialize.apply(this, arguments);
27
+ },
28
+
29
+ // Marionette 1.*
30
+ bindLifecycle: function (view, method_name) {
31
+ view.on(method_name, _.bind(this.stopListening, this));
32
+ },
33
+
34
+ updateFilter: function (filter) {
35
+ this.accepts = VirtualCollection.buildFilter(filter);
36
+ this._rebuildIndex();
37
+ this.trigger('filter', this, filter);
38
+ this.trigger('reset', this, filter);
39
+ return this;
40
+ },
41
+
42
+ _rebuildIndex: function () {
43
+ for(idx in this.models) {
44
+ this.models[idx].off('all', this._onModelEvent, this);
45
+ }
46
+ this._reset();
47
+ this.collection.each(function (model, i) {
48
+ if (this.accepts(model, i)) {
49
+ model.on('all', this._onModelEvent, this);
50
+ this.models.push(model);
51
+ this._byId[model.cid] = model;
52
+ if (model.id) this._byId[model.id] = model;
53
+ }
54
+ }, this);
55
+ this.length = this.models.length;
56
+
57
+ if (this.comparator) this.sort({silent: true});
58
+ },
59
+
60
+ orderViaParent: function (options) {
61
+ this.models = this.collection.filter(function (model) {
62
+ return (this._byId[model.cid] !== undefined);
63
+ }, this);
64
+ if (!options.silent) this.trigger('sort', this, options);
65
+ },
66
+
67
+ _onSort: function (collection, options) {
68
+ if (this.comparator !== undefined) return;
69
+ this.orderViaParent(options);
70
+ },
71
+
72
+ _onAdd: function (model, collection, options) {
73
+ var already_here = this.get(model);
74
+ if (!already_here && this.accepts(model, options.index)) {
75
+ this._indexAdd(model);
76
+ model.on('all', this._onModelEvent, this);
77
+ this.trigger('add', model, this, options);
78
+ }
79
+ },
80
+
81
+ _onRemove: function (model, collection, options) {
82
+ if (!this.get(model)) return;
83
+
84
+ var i = this._indexRemove(model)
85
+ , options_clone = _.clone(options);
86
+ options_clone.index = i;
87
+ model.off('all', this._onModelEvent, this);
88
+ this.trigger('remove', model, this, options_clone);
89
+ },
90
+
91
+ _onChange: function (model, options) {
92
+ if (!model || !options) return; // ignore malformed arguments coming from custom events
93
+ var already_here = this.get(model);
94
+
95
+ if (this.accepts(model, options.index)) {
96
+ if (already_here) {
97
+ this.trigger('change', model, this, options);
98
+ } else {
99
+ this._indexAdd(model);
100
+ this.trigger('add', model, this, options);
101
+ }
102
+ } else {
103
+ if (already_here) {
104
+ var i = this._indexRemove(model)
105
+ , options_clone = _.clone(options);
106
+ options_clone.index = i;
107
+ this.trigger('remove', model, this, options_clone);
108
+ }
109
+ }
110
+ },
111
+
112
+ _onReset: function (collection, options) {
113
+ this._rebuildIndex();
114
+ this.trigger('reset', this, options);
115
+ },
116
+
117
+ sortedIndex: function (model, value, context) {
118
+ var iterator = _.isFunction(value) ? value : function(target) {
119
+ return target.get(value);
120
+ };
121
+
122
+ if (iterator.length == 1) {
123
+ return _.sortedIndex(this.models, model, iterator, context);
124
+ } else {
125
+ return sortedIndexTwo(this.models, model, iterator, context);
126
+ }
127
+ },
128
+
129
+ _indexAdd: function (model) {
130
+ if (this.get(model)) return;
131
+ var i;
132
+ // uses a binsearch to find the right index
133
+ if (this.comparator) {
134
+ i = this.sortedIndex(model, this.comparator, this);
135
+ } else if (this.comparator === undefined) {
136
+ i = this.sortedIndex(model, function (target) {
137
+ //TODO: indexOf traverses the array every time the iterator is called
138
+ return this.collection.indexOf(target);
139
+ }, this);
140
+ } else {
141
+ i = this.length;
142
+ }
143
+ this.models.splice(i, 0, model);
144
+ this._byId[model.cid] = model;
145
+ if (model.id) this._byId[model.id] = model;
146
+ this.length += 1;
147
+ },
148
+
149
+ _indexRemove: function (model) {
150
+ model.off('all', this._onModelEvent, this);
151
+ var i = this.indexOf(model);
152
+ if (i === -1) return i;
153
+ this.models.splice(i, 1);
154
+ delete this._byId[model.cid];
155
+ if (model.id) delete this._byId[model.id];
156
+ this.length -= 1;
157
+ return i;
158
+ }
159
+
160
+ }, { // static props
161
+
162
+ buildFilter: function (options) {
163
+ if (!options) {
164
+ return function () {
165
+ return true;
166
+ };
167
+ } else if (_.isFunction(options)) {
168
+ return options;
169
+ } else if (options.constructor === Object) {
170
+ return function (model) {
171
+ return !Boolean(_(Object.keys(options)).detect(function (key) {
172
+ return model.get(key) !== options[key];
173
+ }));
174
+ };
175
+ }
176
+ }
177
+ });
178
+
179
+ // methods that alter data should proxy to the parent collection
180
+ _.each(['add', 'remove', 'set', 'reset', 'push', 'pop', 'unshift', 'shift', 'slice', 'sync', 'fetch'], function (method_name) {
181
+ VirtualCollection.prototype[method_name] = function () {
182
+ return this.collection[method_name].apply(this.collection, _.toArray(arguments));
183
+ };
184
+ });
185
+
186
+ /**
187
+
188
+ Equivalent to _.sortedIndex, but for comparators with two arguments
189
+
190
+ **/
191
+ function sortedIndexTwo (array, obj, iterator, context) {
192
+ var low = 0, high = array.length;
193
+ while (low < high) {
194
+ var mid = (low + high) >>> 1;
195
+ iterator.call(context, obj, array[mid]) > 0 ? low = mid + 1 : high = mid;
196
+ }
197
+ return low;
198
+ }
199
+
200
+ _.extend(VirtualCollection.prototype, Backbone.Events);
201
+
202
+ module.exports = VirtualCollection;
@@ -1 +1 @@
1
- //= require js_stack/plugins/backbone/virtualcollection/0.5.3
1
+ //= require js_stack/plugins/backbone/virtualcollection/0.6.0