js_stack 1.5.0 → 1.6.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.
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