js_stack 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,340 @@
1
+ (function (global) {
2
+ 'use strict';
3
+
4
+ var _ = global._, Backbone = global.Backbone, vc, iterators, proxy;
5
+
6
+ if ((!_ || !Backbone) && (typeof require !== 'undefined')) {
7
+ _ = require('underscore');
8
+ Backbone = require('backbone');
9
+ }
10
+
11
+ iterators = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
12
+ 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
13
+ 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
14
+ 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
15
+ 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
16
+ 'isEmpty', 'chain'];
17
+
18
+ proxy = ['add', 'remove'];
19
+
20
+ /**
21
+ * Constructor for the virtual collection
22
+ * @param {Collection} collection
23
+ * @param {Function|Object} filter function, or hash of properties to match
24
+ * @param {Object} options
25
+ * @param {[Function|Object]} filter function, or hash of properties to match
26
+ * @param {[Function|String]} comparator
27
+ */
28
+ function VirtualCollection(collection, options) {
29
+ this.collection = collection;
30
+ options = options || {};
31
+ this.comparator = options.comparator;
32
+
33
+ _.bindAll(this, 'each', 'map', 'get', 'at', 'indexOf', 'sort', 'closeWith',
34
+ '_rebuildIndex', '_models', '_onAdd', '_onRemove', '_onChange', '_onReset',
35
+ '_indexAdd', '_indexRemove');
36
+
37
+ // set filter
38
+ this.filterFunction = VirtualCollection.buildFilter(options.filter);
39
+
40
+ // build index
41
+ this._rebuildIndex();
42
+
43
+ this.listenTo(this.collection, 'add', this._onAdd, this);
44
+ this.listenTo(this.collection, 'remove', this._onRemove, this);
45
+ this.listenTo(this.collection, 'change', this._onChange, this);
46
+ this.listenTo(this.collection, 'reset', this._onReset, this);
47
+
48
+ if (options.close_with) {
49
+ this.closeWith(options.close_with);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * [static] Returns a function that returns true for models that meet the specified conditions
55
+ * @param {Object} hash of model attributes or {Function} filter
56
+ * @return {Function} filtering function
57
+ */
58
+ VirtualCollection.buildFilter = function (filter) {
59
+ if (!filter) {
60
+ // If no filter is passed, all models should be added
61
+ return function () { return true; };
62
+ } else if (_.isFunction(filter)) {
63
+ // If filter is passed a function, just return it
64
+ return filter;
65
+ } else if (filter.constructor === Object) {
66
+ // If filter is a hash of attributes, return a function that checks each of them
67
+ return function (model) {
68
+ return !Boolean(_(Object.keys(filter)).detect(function (key) {
69
+ return model.get(key) !== filter[key];
70
+ }));
71
+ };
72
+ }
73
+ };
74
+
75
+ vc = VirtualCollection.prototype;
76
+
77
+
78
+ // mix in Underscore method as proxies
79
+ _.each(iterators, function (method) {
80
+ vc[method] = function () {
81
+ var args = Array.prototype.slice.call(arguments);
82
+ args.unshift(this._models());
83
+ return _[method].apply(_, args);
84
+ };
85
+ });
86
+
87
+ // proxy functions to parent
88
+ _.each(proxy, function (method) {
89
+ vc[method] = function () {
90
+ var args = Array.prototype.slice.call(arguments);
91
+ return this.collection[method].apply(this.collection, args);
92
+ };
93
+ });
94
+
95
+ /**
96
+ * Returns a model if it belongs to the virtual collection
97
+ * @param {String} id
98
+ * @return {Model}
99
+ */
100
+ vc.get = function (id) {
101
+ var model = this.collection.get(id);
102
+ if (model && this.filterFunction(model)) {
103
+ return model;
104
+ }
105
+ };
106
+
107
+ /**
108
+ * Returns the model at the position in the index
109
+ * @param {Number} index
110
+ * @return {Model}
111
+ */
112
+ vc.at = function (index) {
113
+ return this.collection.get(this.index[index]);
114
+ };
115
+
116
+ vc.where = Backbone.Collection.prototype.where;
117
+ vc.findWhere = Backbone.Collection.prototype.findWhere;
118
+
119
+ /**
120
+ * Returns the index of the model in the virtual collection
121
+ * @param {Model} model
122
+ * @return {Number} index
123
+ */
124
+ vc.indexOf = function (model) {
125
+ return this.index.indexOf(model.cid);
126
+ };
127
+
128
+ /**
129
+ * Returns a JSON representation of all the models in the index
130
+ * @return {Array} JSON models
131
+ */
132
+ vc.toJSON = function() {
133
+ return _.map(this._models(), function(model) {
134
+ return model.toJSON();
135
+ });
136
+ };
137
+
138
+ /**
139
+ * Sorts the models in the virtual collection
140
+ *
141
+ * You only need to trigger this manually if you change the comparator
142
+ * @param {Object} options
143
+ * @return {VirtualCollection}
144
+ */
145
+ vc.sort = function (options) {
146
+ var self = this;
147
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
148
+ options = options || {};
149
+
150
+ // Run sort based on type of `comparator`.
151
+ if (_.isString(this.comparator)) {
152
+ this.index = _.sortBy(this.index, function (cid) {
153
+ var model = this.collection.get(cid);
154
+ return model.get(this.comparator);
155
+ }, this);
156
+ } else if (this.comparator.length === 1) {
157
+ this.index = _.sortBy(this.index, function (cid) {
158
+ var model = this.collection.get(cid);
159
+ return this.comparator.call(self, model);
160
+ }, this);
161
+ } else {
162
+ this.index.sort(function (cid1, cid2) {
163
+ var model1 = self.collection.get(cid1),
164
+ model2 = self.collection.get(cid2);
165
+
166
+ return self.comparator.call(self, model1, model2);
167
+ });
168
+ }
169
+
170
+ if (!options.silent) this.trigger('sort', this, options);
171
+ return this;
172
+ };
173
+
174
+ /**
175
+ * Change the filter and update collection
176
+ *
177
+ * @param {Object} hash of model attributes or {Function} filter
178
+ * @return {VirtualCollection}
179
+ */
180
+
181
+ vc.updateFilter = function(filter){
182
+ // Reset the filter
183
+ this.filterFunction = VirtualCollection.buildFilter(filter);
184
+
185
+ // Update the models
186
+ this._rebuildIndex();
187
+
188
+ // Trigger filter event
189
+ this.trigger('filter', this, filter);
190
+
191
+ return this;
192
+ };
193
+
194
+ /**
195
+ * A utility function for unbiding listeners
196
+ * @param {View} view (marionette view)
197
+ */
198
+ vc.closeWith = function (view) {
199
+ view.on('close', function () {
200
+ this.stopListening();
201
+ }, this);
202
+ };
203
+
204
+ // private
205
+
206
+ vc._rebuildIndex = function () {
207
+ this.index = [];
208
+ this.collection.each(function (model, index) {
209
+ if (this.filterFunction(model, index)) {
210
+ this.index.push(model.cid);
211
+ }
212
+ }, this);
213
+ if (this.comparator) {
214
+ this.sort({silent: true});
215
+ }
216
+ this.length = this.index.length;
217
+ };
218
+
219
+ /**
220
+ * Returns an array of models in the virtual collection
221
+ * @return {Array}
222
+ */
223
+ vc._models = function () {
224
+ return _.map(this.index, function (cid) {
225
+ return this.collection.get(cid);
226
+ }, this);
227
+ };
228
+
229
+ /**
230
+ * Handles the collection:add event
231
+ * May update the virtual collection's index
232
+ * @param {Model} model
233
+ * @return {undefined}
234
+ */
235
+ vc._onAdd = function (model, collection, options) {
236
+ if (this.filterFunction(model)) {
237
+ this._indexAdd(model);
238
+ this.trigger('add', model, this, options);
239
+ }
240
+ };
241
+
242
+ /**
243
+ * Handles the collection:remove event
244
+ * May update the virtual collection's index
245
+ * @param {Model} model
246
+ * @return {undefined}
247
+ */
248
+ vc._onRemove = function (model, collection, options) {
249
+ if (_(this.index).contains(model.cid)) {
250
+ this._indexRemove(model);
251
+ this.trigger('remove', model, this, options);
252
+ }
253
+ };
254
+
255
+ /**
256
+ * Handles the collection:change event
257
+ * May update the virtual collection's index
258
+ * @param {Model} model
259
+ * @param {Object} object
260
+ */
261
+ vc._onChange = function (model, options) {
262
+ var already_here = _.contains(this.index, model.cid);
263
+ if (this.filterFunction(model)) {
264
+ if (already_here) {
265
+ this.trigger('change', model, this, options);
266
+ } else {
267
+ this._indexAdd(model);
268
+ this.trigger('add', model, this, options);
269
+ }
270
+ } else {
271
+ if (already_here) {
272
+ this._indexRemove(model);
273
+ this.trigger('remove', model, this, options);
274
+ }
275
+ }
276
+ };
277
+
278
+ /**
279
+ * Handles the collection:reset event
280
+ * @param {Collection} collection
281
+ * @param {Object} object
282
+ */
283
+ vc._onReset = function (collection, options) {
284
+ this._rebuildIndex();
285
+ this.trigger('reset', this, options);
286
+ };
287
+
288
+ /**
289
+ * Adds a model to the virtual collection index
290
+ * Inserting it in the correct order
291
+ * @param {Model} model
292
+ * @return {undefined}
293
+ */
294
+ vc._indexAdd = function (model) {
295
+ if (this.index.indexOf(model.cid) === -1) {
296
+
297
+ if (!this.comparator) { // order inherit's from parent collection
298
+ var i, orig_index = this.collection.indexOf(model);
299
+ for (i = 0; i < this.length; i++) {
300
+ if (this.collection.indexOf(this.collection.get(this.index[i])) > orig_index) {
301
+ break;
302
+ }
303
+ }
304
+ this.index.splice(i, 0, model.cid);
305
+
306
+ } else { // the virtual collection has a custom order
307
+ this.index.push(model.cid);
308
+ this.sort({silent: true});
309
+ }
310
+ this.length = this.index.length;
311
+ }
312
+ };
313
+
314
+ /**
315
+ * Removes a model from the virtual collection index
316
+ * @param {Model} model
317
+ * @return {undefined}
318
+ */
319
+ vc._indexRemove = function (model) {
320
+ var i = this.index.indexOf(model.cid);
321
+ if (i !== -1) {
322
+ this.index.splice(i, 1);
323
+ this.length = this.index.length;
324
+ }
325
+ };
326
+
327
+ if (!_ && (typeof require !== 'undefined')) {
328
+ _ = require('underscore');
329
+ }
330
+ if (!Backbone && (typeof require !== 'undefined')) {
331
+ Backbone = require('backbone');
332
+ }
333
+ if (typeof module !== 'undefined' && module.exports) {
334
+ module.exports = VirtualCollection;
335
+ }
336
+ _.extend(vc, Backbone.Events);
337
+
338
+ Backbone.VirtualCollection = VirtualCollection;
339
+
340
+ }(this));
@@ -1 +1 @@
1
- //= require js_stack/backbone.virtualcollection/backbone.virtualcollection-0.4.5
1
+ //= require js_stack/backbone.virtualcollection/backbone.virtualcollection-0.4.8