1 | var _ = require('underscore');
|
2 | var Events = require('backbone-events-standalone');
|
3 | var classExtend = require('ampersand-class-extend');
|
4 | var underscoreMixins = require('ampersand-collection-underscore-mixin');
|
5 | var slice = Array.prototype.slice;
|
6 |
|
7 |
|
8 | function SubCollection(collection, spec) {
|
9 | spec || (spec = {});
|
10 | this.collection = collection;
|
11 | this._reset();
|
12 | this._watched = spec.watched || [];
|
13 | this._parseFilters(spec);
|
14 | this._runFilters();
|
15 | this.listenTo(this.collection, 'all', this._onCollectionEvent);
|
16 | }
|
17 |
|
18 |
|
19 | _.extend(SubCollection.prototype, Events, underscoreMixins, {
|
20 |
|
21 | addFilter: function (filter) {
|
22 | this._addFilter();
|
23 | this._runFilters();
|
24 | },
|
25 |
|
26 |
|
27 | removeFilter: function (filter) {
|
28 | this._removeFilter(filter);
|
29 | this._runFilters();
|
30 | },
|
31 |
|
32 |
|
33 | clearFilters: function () {
|
34 | this._reset();
|
35 | this._runFilters();
|
36 | },
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | configure: function (opts, clear) {
|
49 | if (clear) this._reset();
|
50 | this._parseFilters(opts);
|
51 | this._runFilters();
|
52 | },
|
53 |
|
54 |
|
55 | at: function (index) {
|
56 | return this.models[index];
|
57 | },
|
58 |
|
59 |
|
60 | get: function (query, indexName) {
|
61 | var model = this.collection.get(query, indexName);
|
62 | if (model && this.contains(model)) return model;
|
63 | },
|
64 |
|
65 |
|
66 | _removeFilter: function () {
|
67 | var index = this._filters.indexOf(filter);
|
68 | if (index !== -1) {
|
69 | this._filters.splice(index, 1);
|
70 | }
|
71 | },
|
72 |
|
73 |
|
74 | _reset: function () {
|
75 | this._filters = [];
|
76 | this._watched = [];
|
77 | this.models = [];
|
78 | this.limit = undefined;
|
79 | this.offset = undefined;
|
80 | },
|
81 |
|
82 |
|
83 | _addFilter: function (filter) {
|
84 | this._filters.push(filter);
|
85 | },
|
86 |
|
87 |
|
88 | _watch: function (item) {
|
89 | this._watched = _.union(this._watched, _.isArray(item) ? item : [item]);
|
90 | },
|
91 |
|
92 |
|
93 | _unwatch: function (item) {
|
94 | this._watched = _.without(this._watched, item);
|
95 | },
|
96 |
|
97 | _parseFilters: function (spec) {
|
98 | if (spec.where) {
|
99 | _.each(spec.where, function (value, item) {
|
100 | this._addFilter(function (model) {
|
101 | return (model.get ? model.get(item) : model[item]) === value;
|
102 | });
|
103 | }, this);
|
104 |
|
105 | this._watch(_.keys(spec.where));
|
106 | }
|
107 | if (spec.hasOwnProperty('limit')) this.limit = spec.limit;
|
108 | if (spec.hasOwnProperty('offset')) this.offset = spec.offset;
|
109 | if (spec.filter) {
|
110 | this._addFilter(spec.filter, false);
|
111 | }
|
112 | if (spec.filters) {
|
113 | spec.filters.forEach(this._addFilter, this);
|
114 | }
|
115 | if (spec.comparator) {
|
116 | this.comparator = spec.comparator;
|
117 | }
|
118 | },
|
119 |
|
120 | _runFilters: function () {
|
121 |
|
122 | var existingModels = slice.call(this.models);
|
123 | var rootModels = slice.call(this.collection.models);
|
124 | var offset = (this.offset || 0);
|
125 | var newModels, toAdd, toRemove;
|
126 |
|
127 |
|
128 | if (this._filters.length) {
|
129 | newModels = _.reduce(this._filters, function (startingArray, filterFunc) {
|
130 | return startingArray.filter(filterFunc);
|
131 | }, rootModels);
|
132 | } else {
|
133 | newModels = slice.call(rootModels);
|
134 | }
|
135 |
|
136 |
|
137 | if (this.comparator) newModels = _.sortBy(newModels, this.comparator);
|
138 |
|
139 |
|
140 | if (this.limit || this.offset) newModels = newModels.slice(offset, this.limit + offset);
|
141 |
|
142 |
|
143 | toAdd = _.difference(newModels, existingModels);
|
144 | toRemove = _.difference(existingModels, newModels);
|
145 |
|
146 | _.each(toRemove, function (model) {
|
147 | this.trigger('remove', model, this);
|
148 | }, this);
|
149 |
|
150 | _.each(toAdd, function (model) {
|
151 | this.trigger('add', model, this);
|
152 | }, this);
|
153 |
|
154 |
|
155 | if (toAdd.length === 0 && toRemove.length === 0 && !_.isEqual(existingModels, newModels)) {
|
156 | this.trigger('sort', this);
|
157 | }
|
158 |
|
159 |
|
160 | this.models = newModels;
|
161 | },
|
162 |
|
163 | _onCollectionEvent: function (eventName, event) {
|
164 | if (_.contains(this._watched, eventName.split(':')[1]) || _.contains(['add', 'remove'], eventName)) {
|
165 | this._runFilters();
|
166 | }
|
167 | }
|
168 | });
|
169 |
|
170 | Object.defineProperty(SubCollection.prototype, 'length', {
|
171 | get: function () {
|
172 | return this.models.length;
|
173 | },
|
174 |
|
175 | set: function () {
|
176 | return;
|
177 | }
|
178 | });
|
179 |
|
180 | SubCollection.extend = classExtend;
|
181 |
|
182 | module.exports = SubCollection;
|