backbone-filtered-collection 1.0.0 → 1.2.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 +7 -0
- data/.gitignore +1 -0
- data/.rvmrc +1 -49
- data/.travis.yml +8 -0
- data/LICENSE.txt +17 -17
- data/README.md +36 -3
- data/lib/backbone/filtered_collection/version.rb +1 -1
- data/spec/javascripts/backbone-filtered-collection-spec.js +214 -101
- data/vendor/assets/javascripts/backbone-filtered-collection.js +66 -21
- metadata +14 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 652f34d8646e018db86ec95f4722b8b115a0078f
|
4
|
+
data.tar.gz: bf1d2c926f9e1e0135984b78613d3ddc36a055e8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 69c6b95b7d3d05da2521ced7e1cb26fe970efb633869d9459cc52907f8a02105d982f5f500e07c9bd33f48b6ec9cc3ddb6982b3a574c67f16c8c6b353a1de4fc
|
7
|
+
data.tar.gz: f1c0fde67a77d7e28c15e4e7808d4d6c4ee4feb22a4d67cf520c5b119472845e89e08f05dafeb9700b7b23139e0d4109a0751f7d70e9bf3d5f08c8d787efdb88
|
data/.gitignore
CHANGED
data/.rvmrc
CHANGED
@@ -1,49 +1 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
-
# development environment upon cd'ing into the directory
|
5
|
-
|
6
|
-
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
-
environment_id="1.9.3-head@filtered-collection"
|
8
|
-
|
9
|
-
#
|
10
|
-
# First we attempt to load the desired environment directly from the environment
|
11
|
-
# file. This is very fast and efficicent compared to running through the entire
|
12
|
-
# CLI and selector. If you want feedback on which environment was used then
|
13
|
-
# insert the word 'use' after --create as this triggers verbose mode.
|
14
|
-
#
|
15
|
-
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
16
|
-
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then
|
17
|
-
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
18
|
-
|
19
|
-
[[ -s ".rvm/hooks/after_use" ]] && . ".rvm/hooks/after_use"
|
20
|
-
else
|
21
|
-
# If the environment file has not yet been created, use the RVM CLI to select.
|
22
|
-
rvm --create "$environment_id"
|
23
|
-
fi
|
24
|
-
|
25
|
-
#
|
26
|
-
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
27
|
-
# it be automatically loaded. Uncomment the following and adjust the filename if
|
28
|
-
# necessary.
|
29
|
-
#
|
30
|
-
# filename=".gems"
|
31
|
-
# if [[ -s "$filename" ]] ; then
|
32
|
-
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
33
|
-
# fi
|
34
|
-
|
35
|
-
#
|
36
|
-
# If you use bundler and would like to run bundle each time you enter the
|
37
|
-
# directory, you can uncomment the following code.
|
38
|
-
#
|
39
|
-
# # Ensure that Bundler is installed. Install it if it is not.
|
40
|
-
# if ! command -v bundle >/dev/null; then
|
41
|
-
# printf "The rubygem 'bundler' is not installed. Installing it now.\n"
|
42
|
-
# gem install bundler
|
43
|
-
# fi
|
44
|
-
#
|
45
|
-
# # Bundle while reducing excess noise.
|
46
|
-
# printf "Bundling your gems. This may take a few minutes on a fresh clone.\n"
|
47
|
-
# bundle | grep -v '^Using ' | grep -v ' is complete' | sed '/^$/d'
|
48
|
-
#
|
49
|
-
|
1
|
+
rvm use ruby-2.0.0-p247@filtered-collection --create
|
data/.travis.yml
ADDED
data/LICENSE.txt
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
1
3
|
Copyright (c) 2013 Dmitriy Likhten
|
2
4
|
|
3
|
-
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
4
11
|
|
5
|
-
|
6
|
-
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
15
22
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Filtered Collection
|
2
2
|
|
3
|
+
[](https://travis-ci.org/dlikhten/filtered-collection)
|
4
|
+
|
3
5
|
This is a simple filtered collection implemented using
|
4
6
|
Backbone.Collection. The goal here is to create a collection which,
|
5
7
|
given a filter function, will just contain elements of the original
|
@@ -19,21 +21,28 @@ these guys.
|
|
19
21
|
|
20
22
|
With bundler
|
21
23
|
|
22
|
-
gem 'backbone-filtered-collection'
|
24
|
+
gem 'backbone-filtered-collection'
|
23
25
|
|
24
26
|
Inside your sprockets file:
|
25
27
|
|
26
28
|
//= require backbone-filtered-collection
|
27
29
|
|
30
|
+
# Installation anywhere else
|
31
|
+
|
32
|
+
Download the [source][1], minify as you see fit by your minification strategy.
|
33
|
+
|
28
34
|
# Usage
|
29
35
|
|
30
36
|
var YourCollection = Backbone.Collection.extend({model: YourModel});
|
31
37
|
var YourFilteredCollection = Backbone.FilteredCollection.extend({model: YourModel});
|
38
|
+
|
32
39
|
var allItems = new YourCollection(...);
|
40
|
+
|
33
41
|
// note the null, backbone collections want the pre-populated model here
|
34
42
|
// we can't do that since this collection does not accept mutations, it
|
35
43
|
// only mutates as a proxy for the underlying collection
|
36
44
|
var filteredItems = new YourFilteredCollection(null, {collection: allItems});
|
45
|
+
|
37
46
|
var filteredItems.setFilter(function(item) { return item.get('included') == true;});
|
38
47
|
|
39
48
|
And now filteredItems contains only those items that pass the filter.
|
@@ -50,6 +59,28 @@ Same goes for remove and reset.
|
|
50
59
|
|
51
60
|
To clear the filtering completely, pass the value false to setFilter.
|
52
61
|
|
62
|
+
## Filters
|
63
|
+
|
64
|
+
- `setFilter(function() {})`: Set the given function as the filter. Same api as `_.each`.
|
65
|
+
- `setFilter(false)`: Turn filtering off. This collection will have all elements of the original collection.
|
66
|
+
- `setFilter()`: Re-filter. Don't change the filter function but execute it on all elements. Useful after the original collection was modified via `silent: true`
|
67
|
+
|
68
|
+
## Events
|
69
|
+
|
70
|
+
The collection will create events much like a regular collection. There are a few to note:
|
71
|
+
|
72
|
+
- `add`: An object was added to the collection (via filter OR via orig collection)
|
73
|
+
- `remove`: An object was removed from the collection (via filter OR via orig collection)
|
74
|
+
- `reset`: The original collection was reset, filtering happened
|
75
|
+
- `sort`: The collection was sorted. No changes in models represented.
|
76
|
+
- `change`: An object in the collection was changed. The object was already accepted by the filter, and is still.
|
77
|
+
- `filter-complete`: Filtering was completed. If you are not listening to add/remove then just listen to filter-complete and reset your views.
|
78
|
+
|
79
|
+
## Change Collection
|
80
|
+
|
81
|
+
You can change the underlying collection if you really need to by invoking `#resetWith(newCollection)`, only one `reset`
|
82
|
+
event will be triggered with the new data.
|
83
|
+
|
53
84
|
# Testing
|
54
85
|
|
55
86
|
bundle install
|
@@ -59,8 +90,10 @@ I also included a .rvmrc file incase you have rvm installed.
|
|
59
90
|
|
60
91
|
# Contributing
|
61
92
|
|
62
|
-
Please, do not contribute without a spec. Being tested is critically important
|
63
|
-
to this project, as it's a framework level component, and so its failure
|
93
|
+
Please, do not contribute without a spec. Being tested is critically important
|
94
|
+
to this project, as it's a framework level component, and so its failure
|
64
95
|
will be damn hard to detect.
|
65
96
|
|
66
97
|
Also, no tab characters, 2 spaces only. Minifiers can handle this stuff for you.
|
98
|
+
|
99
|
+
[1]: https://raw.github.com/dlikhten/filtered-collection/master/vendor/assets/javascripts/backbone-filtered-collection.js
|
@@ -7,10 +7,6 @@ describe("Backbone.FilteredCollection", function() {
|
|
7
7
|
model: TehModel
|
8
8
|
});
|
9
9
|
|
10
|
-
var ModelCollection = Backbone.FilteredCollection.extend({
|
11
|
-
model: TehModel
|
12
|
-
});
|
13
|
-
|
14
10
|
var allModels;
|
15
11
|
var collection;
|
16
12
|
|
@@ -20,12 +16,9 @@ describe("Backbone.FilteredCollection", function() {
|
|
20
16
|
}
|
21
17
|
};
|
22
18
|
|
23
|
-
var oddFilter = function(model) {
|
24
|
-
return model.get("value") % 2 == 1;
|
25
|
-
}
|
26
19
|
var evenFilter = function(model) {
|
27
20
|
return model.get("value") % 2 == 0;
|
28
|
-
}
|
21
|
+
};
|
29
22
|
|
30
23
|
beforeEach(function() {
|
31
24
|
allModels = new RegularModelCollection();
|
@@ -33,20 +26,23 @@ describe("Backbone.FilteredCollection", function() {
|
|
33
26
|
allModels.add(new TehModel({id: i, value: i}));
|
34
27
|
}
|
35
28
|
|
36
|
-
collection = new
|
29
|
+
collection = new Backbone.FilteredCollection(null, {collection: allModels});
|
30
|
+
});
|
31
|
+
|
32
|
+
afterEach(function() {
|
33
|
+
if (collection) collection.off(null, null, null);
|
37
34
|
});
|
38
35
|
|
39
36
|
describe("#setFilter", function() {
|
40
|
-
it("
|
37
|
+
it("filters the given model", function() {
|
41
38
|
collection.setFilter(createLessthanFilter(5));
|
42
39
|
|
43
40
|
expect(collection.length).toEqual(5);
|
44
41
|
expect(collection.at(0).get('value')).toEqual(0);
|
45
42
|
});
|
46
43
|
|
47
|
-
it("
|
44
|
+
it("uses the given filter", function() {
|
48
45
|
collection.setFilter(createLessthanFilter(5));
|
49
|
-
|
50
46
|
collection.setFilter(function(model) {
|
51
47
|
return model.get('value') > 7;
|
52
48
|
});
|
@@ -55,7 +51,7 @@ describe("Backbone.FilteredCollection", function() {
|
|
55
51
|
expect(collection.at(0).get('value')).toEqual(8);
|
56
52
|
});
|
57
53
|
|
58
|
-
it("
|
54
|
+
it("accepts false to remove all filters", function() {
|
59
55
|
collection.setFilter(createLessthanFilter(5));
|
60
56
|
expect(collection.length).toEqual(5);
|
61
57
|
collection.setFilter(undefined); // no change
|
@@ -66,46 +62,43 @@ describe("Backbone.FilteredCollection", function() {
|
|
66
62
|
expect(collection.length).toEqual(10);
|
67
63
|
});
|
68
64
|
|
69
|
-
it("
|
65
|
+
it("works correctly after filtering is changed constantly", function() {
|
70
66
|
collection.setFilter(createLessthanFilter(0));
|
71
67
|
expect(collection.models.length).toEqual(0);
|
72
68
|
|
73
69
|
collection.setFilter(createLessthanFilter(3));
|
74
70
|
expect(collection.models.length).toEqual(3);
|
75
|
-
expect(collection.
|
76
|
-
expect(collection.
|
77
|
-
expect(collection.
|
71
|
+
expect(collection.at(0).get("value")).toEqual(0);
|
72
|
+
expect(collection.at(1).get("value")).toEqual(1);
|
73
|
+
expect(collection.at(2).get("value")).toEqual(2);
|
78
74
|
|
79
75
|
collection.setFilter(evenFilter);
|
80
76
|
expect(collection.models.length).toEqual(5);
|
81
|
-
expect(collection.
|
82
|
-
expect(collection.
|
83
|
-
expect(collection.
|
84
|
-
expect(collection.
|
85
|
-
expect(collection.
|
77
|
+
expect(collection.at(0).get("value")).toEqual(0);
|
78
|
+
expect(collection.at(1).get("value")).toEqual(2);
|
79
|
+
expect(collection.at(2).get("value")).toEqual(4);
|
80
|
+
expect(collection.at(3).get("value")).toEqual(6);
|
81
|
+
expect(collection.at(4).get("value")).toEqual(8);
|
86
82
|
});
|
87
83
|
|
88
|
-
it("
|
89
|
-
|
90
|
-
collection.on("filter-complete",
|
91
|
-
count += 1;
|
92
|
-
});
|
84
|
+
it("does not trigger a filter-complete event if options.silent is true", function() {
|
85
|
+
var eventSpy = jasmine.createSpy('filter-complete listener');
|
86
|
+
collection.on("filter-complete", eventSpy);
|
93
87
|
|
94
88
|
collection.setFilter(createLessthanFilter(0), {silent: true});
|
95
|
-
|
96
|
-
expect(count).toEqual(0);
|
89
|
+
expect(eventSpy).not.toHaveBeenCalled();
|
97
90
|
});
|
98
91
|
});
|
99
92
|
|
100
93
|
describe("event:add", function() {
|
101
|
-
it("
|
94
|
+
it("does not add an already filtered out new object", function() {
|
102
95
|
collection.setFilter(createLessthanFilter(5));
|
103
96
|
expect(collection.length).toEqual(5);
|
104
97
|
allModels.add(new TehModel({value: 6}));
|
105
98
|
expect(collection.length).toEqual(5);
|
106
99
|
});
|
107
100
|
|
108
|
-
it("
|
101
|
+
it("adds the new object, since it passes the filter", function() {
|
109
102
|
collection.setFilter(createLessthanFilter(5));
|
110
103
|
expect(collection.length).toEqual(5);
|
111
104
|
allModels.add(new TehModel({value: 1}));
|
@@ -113,7 +106,7 @@ describe("Backbone.FilteredCollection", function() {
|
|
113
106
|
expect(collection.at(5).get('value')).toEqual(1);
|
114
107
|
});
|
115
108
|
|
116
|
-
it("
|
109
|
+
it("adds the new object to the correct location", function() {
|
117
110
|
collection.setFilter(createLessthanFilter(5));
|
118
111
|
expect(collection.length).toEqual(5);
|
119
112
|
allModels.add(new TehModel({value: 4}), {at: 0});
|
@@ -121,23 +114,18 @@ describe("Backbone.FilteredCollection", function() {
|
|
121
114
|
expect(collection.at(0).get('value')).toEqual(4);
|
122
115
|
});
|
123
116
|
|
124
|
-
it("
|
117
|
+
it("triggers an add event if the object was added", function() {
|
125
118
|
collection.setFilter(createLessthanFilter(5));
|
126
|
-
expect(collection.length).toEqual(5);
|
127
|
-
|
128
119
|
var newModel = new TehModel({value: 3});
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
expect(options.index).toEqual(0);
|
133
|
-
count += 1;
|
134
|
-
});
|
120
|
+
var eventSpy = jasmine.createSpy('reset event listener');
|
121
|
+
|
122
|
+
collection.on("add", eventSpy);
|
135
123
|
allModels.add(newModel, {at: 0});
|
136
124
|
|
137
|
-
expect(
|
125
|
+
expect(eventSpy).toHaveBeenCalledWith(newModel, collection, {index: 0})
|
138
126
|
});
|
139
127
|
|
140
|
-
it("
|
128
|
+
it("re-numbers elements properly in the mapping according to what the atual indices are in the original collection", function() {
|
141
129
|
collection.setFilter(createLessthanFilter(10));
|
142
130
|
expect(collection.length).toEqual(10);
|
143
131
|
|
@@ -148,14 +136,14 @@ describe("Backbone.FilteredCollection", function() {
|
|
148
136
|
});
|
149
137
|
|
150
138
|
describe("event:remove", function() {
|
151
|
-
it("
|
139
|
+
it("is a no-op if the item removed is already not filtered", function() {
|
152
140
|
collection.setFilter(createLessthanFilter(5));
|
153
141
|
expect(collection.length).toEqual(5);
|
154
142
|
allModels.remove(allModels.at(6));
|
155
143
|
expect(collection.length).toEqual(5);
|
156
144
|
});
|
157
145
|
|
158
|
-
it("
|
146
|
+
it("removes the model that was removed from the underlying collection", function() {
|
159
147
|
collection.setFilter(createLessthanFilter(5));
|
160
148
|
expect(collection.length).toEqual(5);
|
161
149
|
allModels.remove(allModels.at(4));
|
@@ -163,7 +151,7 @@ describe("Backbone.FilteredCollection", function() {
|
|
163
151
|
expect(collection.at(collection.length - 1).get('value')).toEqual(3);
|
164
152
|
});
|
165
153
|
|
166
|
-
it("should re-number elements
|
154
|
+
it("should re-number elements properly in the mapping according to what the actual indices are in the original collection", function() {
|
167
155
|
collection.setFilter(createLessthanFilter(10));
|
168
156
|
expect(collection.length).toEqual(10);
|
169
157
|
|
@@ -174,7 +162,7 @@ describe("Backbone.FilteredCollection", function() {
|
|
174
162
|
});
|
175
163
|
|
176
164
|
describe("event:reset", function() {
|
177
|
-
it("
|
165
|
+
it("resets all models to those in the underlying collection", function() {
|
178
166
|
collection.setFilter(createLessthanFilter(15));
|
179
167
|
var newAll = [];
|
180
168
|
for (var i = 10; i < 20; i++) {
|
@@ -184,10 +172,20 @@ describe("Backbone.FilteredCollection", function() {
|
|
184
172
|
expect(collection.length).toEqual(5);
|
185
173
|
expect(collection.at(4).get('value')).toEqual(14);
|
186
174
|
});
|
175
|
+
|
176
|
+
it("triggers exactly one reset event", function() {
|
177
|
+
var eventSpy = jasmine.createSpy('reset event listener');
|
178
|
+
collection.on('reset', eventSpy);
|
179
|
+
|
180
|
+
allModels.reset([{id: 11, value: 11}]);
|
181
|
+
|
182
|
+
expect(eventSpy).toHaveBeenCalledWith(collection);
|
183
|
+
expect(eventSpy.callCount).toEqual(1);
|
184
|
+
});
|
187
185
|
});
|
188
186
|
|
189
187
|
describe("event:sort", function() {
|
190
|
-
it("
|
188
|
+
it("continues to filter the collection, except with a new order", function() {
|
191
189
|
collection.setFilter(createLessthanFilter(5));
|
192
190
|
allModels.comparator = function(v1, v2) {
|
193
191
|
return v2.get("value") - v1.get("value");
|
@@ -204,25 +202,26 @@ describe("Backbone.FilteredCollection", function() {
|
|
204
202
|
});
|
205
203
|
|
206
204
|
describe("event:filter-complete", function() {
|
207
|
-
it("
|
208
|
-
var
|
209
|
-
collection.on("filter-complete",
|
210
|
-
|
211
|
-
});
|
205
|
+
it("triggers when the underlying collection triggers it (thus we're done filtering too)", function() {
|
206
|
+
var eventSpy = jasmine.createSpy('filter-complete event listener');
|
207
|
+
collection.on("filter-complete", eventSpy);
|
208
|
+
|
212
209
|
allModels.trigger("filter-complete");
|
213
|
-
|
210
|
+
|
211
|
+
expect(eventSpy).toHaveBeenCalled();
|
214
212
|
});
|
215
213
|
|
216
|
-
it("
|
217
|
-
var
|
218
|
-
collection.on("filter-complete",
|
219
|
-
|
220
|
-
});
|
214
|
+
it("triggers once only at the end of a filter", function() {
|
215
|
+
var eventSpy = jasmine.createSpy('filter-complete event listener');
|
216
|
+
collection.on("filter-complete", eventSpy);
|
217
|
+
|
221
218
|
collection.setFilter(createLessthanFilter(3));
|
222
|
-
|
219
|
+
|
220
|
+
expect(eventSpy).toHaveBeenCalled();
|
221
|
+
expect(eventSpy.callCount).toEqual(1);
|
223
222
|
});
|
224
223
|
|
225
|
-
it("
|
224
|
+
it("triggers once when a change is propagated from an underlying model", function() {
|
226
225
|
var filterFired = 0;
|
227
226
|
collection.on("filter-complete", function() {
|
228
227
|
filterFired += 1;
|
@@ -230,51 +229,51 @@ describe("Backbone.FilteredCollection", function() {
|
|
230
229
|
collection.setFilter(createLessthanFilter(3));
|
231
230
|
filterFired = 0;
|
232
231
|
|
233
|
-
collection.
|
232
|
+
collection.at(0).trigger("change", collection.at(0), allModels)
|
234
233
|
expect(filterFired).toEqual(1);
|
235
234
|
});
|
236
235
|
});
|
237
236
|
|
238
237
|
describe("model - event:destroy", function() {
|
239
|
-
it("
|
238
|
+
it("just removes the model from the base collection like normal, and raise no problems with the filter", function() {
|
240
239
|
collection.setFilter(createLessthanFilter(5));
|
241
|
-
origModelZero = collection.
|
240
|
+
origModelZero = collection.at(0);
|
242
241
|
// simulate an ajax destroy
|
243
|
-
origModelZero.trigger("destroy", origModelZero, origModelZero.collection)
|
242
|
+
origModelZero.trigger("destroy", origModelZero, origModelZero.collection);
|
244
243
|
|
245
|
-
expect(collection.
|
244
|
+
expect(collection.at(0).get("value")).toEqual(1);
|
246
245
|
});
|
247
246
|
|
248
|
-
it("
|
247
|
+
it("removes elements from the model as events occur", function() {
|
249
248
|
collection.setFilter(createLessthanFilter(10));
|
250
249
|
|
251
250
|
// start removing in weird orders, make sure vents are done properly
|
252
|
-
model = collection.
|
253
|
-
model.trigger("destroy", model, model.collection)
|
254
|
-
expect(collection.
|
251
|
+
model = collection.at(0);
|
252
|
+
model.trigger("destroy", model, model.collection);
|
253
|
+
expect(collection.at(0).get("value")).toEqual(1);
|
255
254
|
|
256
|
-
model = collection.
|
257
|
-
model.trigger("destroy", model, model.collection)
|
258
|
-
expect(collection.
|
255
|
+
model = collection.at(3);
|
256
|
+
model.trigger("destroy", model, model.collection);
|
257
|
+
expect(collection.at(3).get("value")).toEqual(5);
|
259
258
|
|
260
|
-
model = collection.
|
261
|
-
model.trigger("destroy", model, model.collection)
|
262
|
-
expect(collection.
|
259
|
+
model = collection.at(3);
|
260
|
+
model.trigger("destroy", model, model.collection);
|
261
|
+
expect(collection.at(3).get("value")).toEqual(6);
|
263
262
|
|
264
|
-
model = collection.
|
265
|
-
model.trigger("destroy", model, model.collection)
|
266
|
-
expect(collection.
|
263
|
+
model = collection.at(3);
|
264
|
+
model.trigger("destroy", model, model.collection);
|
265
|
+
expect(collection.at(3).get("value")).toEqual(7);
|
267
266
|
|
268
|
-
model = collection.
|
269
|
-
model.trigger("destroy", model, model.collection)
|
270
|
-
expect(collection.
|
267
|
+
model = collection.at(2);
|
268
|
+
model.trigger("destroy", model, model.collection);
|
269
|
+
expect(collection.at(2).get("value")).toEqual(7);
|
271
270
|
|
272
|
-
model = collection.
|
273
|
-
model.trigger("destroy", model, model.collection)
|
274
|
-
expect(collection.
|
271
|
+
model = collection.at(1);
|
272
|
+
model.trigger("destroy", model, model.collection);
|
273
|
+
expect(collection.at(1).get("value")).toEqual(7);
|
275
274
|
});
|
276
275
|
|
277
|
-
it("
|
276
|
+
it("triggers remove events for every deleted model", function() {
|
278
277
|
collection.setFilter(createLessthanFilter(10));
|
279
278
|
var lastModelRemoved = null;
|
280
279
|
var count = 0;
|
@@ -285,37 +284,37 @@ describe("Backbone.FilteredCollection", function() {
|
|
285
284
|
|
286
285
|
// start removing in weird orders, make sure vents are done properly
|
287
286
|
count = 0;
|
288
|
-
model = collection.
|
287
|
+
model = collection.at(0);
|
289
288
|
model.trigger("destroy", model, model.collection)
|
290
289
|
expect(lastModelRemoved).toEqual(model);
|
291
290
|
expect(count).toEqual(1);
|
292
291
|
|
293
292
|
count = 0;
|
294
|
-
model = collection.
|
293
|
+
model = collection.at(3);
|
295
294
|
model.trigger("destroy", model, model.collection)
|
296
295
|
expect(lastModelRemoved).toEqual(model);
|
297
296
|
expect(count).toEqual(1);
|
298
297
|
|
299
298
|
count = 0;
|
300
|
-
model = collection.
|
299
|
+
model = collection.at(3);
|
301
300
|
model.trigger("destroy", model, model.collection)
|
302
301
|
expect(lastModelRemoved).toEqual(model);
|
303
302
|
expect(count).toEqual(1);
|
304
303
|
|
305
304
|
count = 0;
|
306
|
-
model = collection.
|
305
|
+
model = collection.at(3);
|
307
306
|
model.trigger("destroy", model, model.collection)
|
308
307
|
expect(lastModelRemoved).toEqual(model);
|
309
308
|
expect(count).toEqual(1);
|
310
309
|
|
311
310
|
count = 0;
|
312
|
-
model = collection.
|
311
|
+
model = collection.at(2);
|
313
312
|
model.trigger("destroy", model, model.collection)
|
314
313
|
expect(lastModelRemoved).toEqual(model);
|
315
314
|
expect(count).toEqual(1);
|
316
315
|
|
317
316
|
count = 0;
|
318
|
-
model = collection.
|
317
|
+
model = collection.at(1);
|
319
318
|
model.trigger("destroy", model, model.collection)
|
320
319
|
expect(lastModelRemoved).toEqual(model);
|
321
320
|
expect(count).toEqual(1);
|
@@ -323,31 +322,145 @@ describe("Backbone.FilteredCollection", function() {
|
|
323
322
|
});
|
324
323
|
|
325
324
|
describe("model - event:change", function() {
|
326
|
-
|
325
|
+
var changeSpy, addSpy, removeSpy;
|
326
|
+
|
327
|
+
beforeEach(function() {
|
328
|
+
changeSpy = jasmine.createSpy("change listener");
|
329
|
+
addSpy = jasmine.createSpy("add listener");
|
330
|
+
removeSpy = jasmine.createSpy("remove listener");
|
331
|
+
});
|
332
|
+
|
333
|
+
it("removes the model because it failed the filter post change, triggers remove event", function() {
|
327
334
|
collection.setFilter(createLessthanFilter(5));
|
328
|
-
|
335
|
+
collection.on("change", changeSpy);
|
336
|
+
collection.on("add", addSpy);
|
337
|
+
collection.on("remove", removeSpy);
|
338
|
+
|
339
|
+
origModelZero = collection.at(0);
|
329
340
|
origModelZero.set("value", 10)
|
330
341
|
|
331
342
|
expect(collection.models.length).toEqual(4)
|
332
|
-
expect(collection.
|
343
|
+
expect(collection.at(0).get("value")).toEqual(1)
|
344
|
+
expect(changeSpy).not.toHaveBeenCalled();
|
345
|
+
expect(addSpy).not.toHaveBeenCalled();
|
346
|
+
expect(removeSpy).toHaveBeenCalledWith(origModelZero, collection, jasmine.any(Object));
|
333
347
|
});
|
334
348
|
|
335
|
-
it("
|
349
|
+
it("does not alter the collection if the model is still passing, triggers change event", function() {
|
336
350
|
collection.setFilter(createLessthanFilter(5));
|
337
|
-
|
351
|
+
collection.on("change", changeSpy);
|
352
|
+
collection.on("add", addSpy);
|
353
|
+
collection.on("remove", removeSpy);
|
354
|
+
|
355
|
+
origModelZero = collection.at(0);
|
338
356
|
origModelZero.set("value", 3)
|
339
357
|
|
340
358
|
expect(collection.models.length).toEqual(5)
|
341
|
-
expect(collection.
|
359
|
+
expect(collection.at(0).get("value")).toEqual(3)
|
360
|
+
expect(changeSpy).toHaveBeenCalledWith(origModelZero, collection);
|
361
|
+
expect(addSpy).not.toHaveBeenCalled();
|
362
|
+
expect(removeSpy).not.toHaveBeenCalled();
|
342
363
|
});
|
343
364
|
|
344
|
-
it("
|
365
|
+
it("adds the model that is now passing the filter, triggers add event", function() {
|
345
366
|
collection.setFilter(createLessthanFilter(5));
|
346
|
-
|
367
|
+
collection.on("change", changeSpy);
|
368
|
+
collection.on("add", addSpy);
|
369
|
+
collection.on("remove", removeSpy);
|
370
|
+
|
371
|
+
origModelZero = allModels.at(9);
|
347
372
|
origModelZero.set("value", 2)
|
348
373
|
|
349
374
|
expect(collection.models.length).toEqual(6)
|
350
|
-
expect(collection.
|
375
|
+
expect(collection.at(5).get("value")).toEqual(2)
|
376
|
+
expect(changeSpy).not.toHaveBeenCalled();
|
377
|
+
expect(addSpy).toHaveBeenCalledWith(origModelZero, collection, jasmine.any(Object));
|
378
|
+
expect(removeSpy).not.toHaveBeenCalled();
|
379
|
+
});
|
380
|
+
});
|
381
|
+
|
382
|
+
describe("#resetWith", function() {
|
383
|
+
var moreModels;
|
384
|
+
|
385
|
+
beforeEach(function(){
|
386
|
+
moreModels = new RegularModelCollection();
|
387
|
+
for(var i = 10; i < 16; i++) {
|
388
|
+
moreModels.add(new TehModel({id: i, value: i}));
|
389
|
+
}
|
390
|
+
collection.setFilter(evenFilter);
|
391
|
+
});
|
392
|
+
|
393
|
+
it("updates the collection's length to match that of the new collection", function() {
|
394
|
+
var lengthBefore = collection.length;
|
395
|
+
collection.resetWith(moreModels);
|
396
|
+
var lengthAfter = collection.length;
|
397
|
+
|
398
|
+
expect(lengthBefore).toEqual(5); // even models
|
399
|
+
expect(lengthAfter).toEqual(3); // even models
|
400
|
+
});
|
401
|
+
|
402
|
+
it("handles add events on the new collection", function() {
|
403
|
+
collection.resetWith(moreModels);
|
404
|
+
|
405
|
+
var lengthBefore = collection.length;
|
406
|
+
newModel = new TehModel({id: 16, value: 16});
|
407
|
+
moreModels.add(newModel);
|
408
|
+
var lengthAfter = collection.length;
|
409
|
+
|
410
|
+
expect(lengthAfter).toEqual(lengthBefore + 1);
|
411
|
+
expect(collection.indexOf(newModel)).toBeGreaterThan(-1);
|
412
|
+
});
|
413
|
+
|
414
|
+
it("handles remove events on the new collection", function() {
|
415
|
+
collection.resetWith(moreModels);
|
416
|
+
|
417
|
+
var lengthBefore = collection.length;
|
418
|
+
var toRemove = moreModels.get(12);
|
419
|
+
moreModels.remove(toRemove);
|
420
|
+
var lengthAfter = collection.length;
|
421
|
+
|
422
|
+
expect(lengthAfter).toEqual(lengthBefore - 1);
|
423
|
+
expect(collection.indexOf(toRemove)).toEqual(-1)
|
424
|
+
});
|
425
|
+
|
426
|
+
it("handles change events on the new collection", function() {
|
427
|
+
collection.resetWith(moreModels);
|
428
|
+
|
429
|
+
var lengthBefore = collection.length;
|
430
|
+
var changeModel = moreModels.get(10);
|
431
|
+
changeModel.set('value', 11);
|
432
|
+
var lengthAfter = collection.length;
|
433
|
+
|
434
|
+
expect(lengthAfter).toEqual(lengthBefore - 1);
|
435
|
+
expect(collection.indexOf(changeModel)).toEqual(-1);
|
436
|
+
});
|
437
|
+
|
438
|
+
it("ignores the old collection events", function() {
|
439
|
+
var eventSpy = jasmine.createSpy('all event listener');
|
440
|
+
|
441
|
+
collection.resetWith(moreModels);
|
442
|
+
|
443
|
+
collection.on('all', eventSpy);
|
444
|
+
|
445
|
+
allModels.add(new TehModel({id: 16, value: 16}));
|
446
|
+
allModels.remove(allModels.at(0));
|
447
|
+
allModels.get(2).set("value", "3");
|
448
|
+
allModels.comparator = function(a, b) { return 0 };
|
449
|
+
allModels.sort();
|
450
|
+
allModels.reset([{value: 5}]);
|
451
|
+
|
452
|
+
expect(eventSpy).not.toHaveBeenCalled();
|
453
|
+
});
|
454
|
+
|
455
|
+
it("fire a reset with the new filtered data, exactly once", function() {
|
456
|
+
var eventSpy = jasmine.createSpy('reset event listener');
|
457
|
+
|
458
|
+
collection.on('reset', eventSpy);
|
459
|
+
|
460
|
+
collection.resetWith(moreModels);
|
461
|
+
|
462
|
+
expect(eventSpy).toHaveBeenCalledWith(collection);
|
463
|
+
expect(eventSpy.callCount).toEqual(1);
|
351
464
|
});
|
352
465
|
});
|
353
466
|
});
|
@@ -1,25 +1,27 @@
|
|
1
1
|
/*
|
2
|
-
|
2
|
+
The MIT License (MIT)
|
3
3
|
|
4
|
-
|
5
|
-
this software and associated documentation files (the "Software"), to deal in
|
6
|
-
the Software without restriction, including without limitation the rights to
|
7
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
8
|
-
of the Software, and to permit persons to whom the Software is furnished to do
|
9
|
-
so, subject to the following conditions:
|
4
|
+
Copyright (c) 2013 Dmitriy Likhten
|
10
5
|
|
11
|
-
|
12
|
-
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
13
15
|
|
14
16
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
17
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
18
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
19
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
20
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
-
SOFTWARE.
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
21
23
|
*/
|
22
|
-
/* version 1.
|
24
|
+
/* version 1.2.0 */
|
23
25
|
(function(_, Backbone) {
|
24
26
|
var defaultFilter = function() {return true;};
|
25
27
|
/**
|
@@ -44,17 +46,44 @@ SOFTWARE.
|
|
44
46
|
collectionFilter: null
|
45
47
|
,defaultFilter: defaultFilter
|
46
48
|
|
49
|
+
/**
|
50
|
+
Default initializer.
|
51
|
+
|
52
|
+
@param {Boolean} [models=false] - By default we must to use a falsy value here.
|
53
|
+
@param {Object} data - Options.
|
54
|
+
@param {Backbone.Collection} data.collection - Collection to be filtered
|
55
|
+
@param {Function} data.collectionFilter - Filter to be applied to the collection.
|
56
|
+
|
57
|
+
@constructor
|
58
|
+
**/
|
47
59
|
,initialize: function(models, data) {
|
48
60
|
if (models) throw "models cannot be set directly, unfortunately first argument is the models.";
|
61
|
+
|
49
62
|
this.collection = data.collection;
|
50
63
|
this.setFilter(data.collectionFilter);
|
64
|
+
this._bindEvents();
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Replace the current underlying collection with a new one.
|
69
|
+
* note: only a reset event will be fired.
|
70
|
+
*/
|
71
|
+
,resetWith: function(newCollection) {
|
72
|
+
this.stopListening(this.collection);
|
73
|
+
this.collection = newCollection;
|
74
|
+
this.resetCollection();
|
75
|
+
this._bindEvents();
|
76
|
+
|
77
|
+
return this;
|
78
|
+
}
|
51
79
|
|
52
|
-
|
53
|
-
this.collection
|
54
|
-
this.collection
|
55
|
-
this.collection
|
56
|
-
this.collection
|
57
|
-
this.collection
|
80
|
+
,_bindEvents: function () {
|
81
|
+
this.listenTo(this.collection, "add", this.addModel);
|
82
|
+
this.listenTo(this.collection, "remove", this.removeModel);
|
83
|
+
this.listenTo(this.collection, "reset", this.resetCollection);
|
84
|
+
this.listenTo(this.collection, "sort", this.resortCollection);
|
85
|
+
this.listenTo(this.collection, "change", this._modelChanged);
|
86
|
+
this.listenTo(this.collection, "filter-complete", this._filterComplete);
|
58
87
|
}
|
59
88
|
|
60
89
|
,_reset: function(options) {
|
@@ -85,6 +114,11 @@ SOFTWARE.
|
|
85
114
|
var index = this.collection.indexOf(model);
|
86
115
|
this._forceAddModel(model, {index: index});
|
87
116
|
}
|
117
|
+
// the model passes the filter and is already in the collection
|
118
|
+
// therefore we want to indicate that the model has changed
|
119
|
+
else {
|
120
|
+
this.trigger("change", model, this);
|
121
|
+
}
|
88
122
|
} else {
|
89
123
|
// Model did not pass filter
|
90
124
|
if (ownIndexOfModel > -1){
|
@@ -97,11 +131,22 @@ SOFTWARE.
|
|
97
131
|
}
|
98
132
|
|
99
133
|
,resortCollection: function() {
|
100
|
-
|
101
|
-
|
102
|
-
|
134
|
+
// note: we don't need to do any filter work since sort
|
135
|
+
// implies nothing changed, only order
|
136
|
+
var newModels = [];
|
137
|
+
var newMapping = [];
|
138
|
+
var models = this.models;
|
139
|
+
_.each(this.collection.models, function(model, index) {
|
140
|
+
if (models.indexOf(model) >= 0) {
|
141
|
+
newModels.push(model);
|
142
|
+
newMapping.push(index);
|
143
|
+
}
|
144
|
+
});
|
145
|
+
this.models = newModels;
|
146
|
+
this._mapping = newMapping;
|
103
147
|
this.trigger("sort", this);
|
104
148
|
}
|
149
|
+
|
105
150
|
,resetCollection: function() {
|
106
151
|
this._mapping = [];
|
107
152
|
this._reset();
|
metadata
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backbone-filtered-collection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Dmitriy Likhten
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-10-10 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: railties
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '3.0'
|
22
20
|
- - <
|
@@ -25,9 +23,8 @@ dependencies:
|
|
25
23
|
type: :runtime
|
26
24
|
prerelease: false
|
27
25
|
version_requirements: !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
26
|
requirements:
|
30
|
-
- -
|
27
|
+
- - '>='
|
31
28
|
- !ruby/object:Gem::Version
|
32
29
|
version: '3.0'
|
33
30
|
- - <
|
@@ -36,33 +33,29 @@ dependencies:
|
|
36
33
|
- !ruby/object:Gem::Dependency
|
37
34
|
name: rake
|
38
35
|
requirement: !ruby/object:Gem::Requirement
|
39
|
-
none: false
|
40
36
|
requirements:
|
41
|
-
- -
|
37
|
+
- - '>='
|
42
38
|
- !ruby/object:Gem::Version
|
43
39
|
version: '0'
|
44
40
|
type: :development
|
45
41
|
prerelease: false
|
46
42
|
version_requirements: !ruby/object:Gem::Requirement
|
47
|
-
none: false
|
48
43
|
requirements:
|
49
|
-
- -
|
44
|
+
- - '>='
|
50
45
|
- !ruby/object:Gem::Version
|
51
46
|
version: '0'
|
52
47
|
- !ruby/object:Gem::Dependency
|
53
48
|
name: jasmine
|
54
49
|
requirement: !ruby/object:Gem::Requirement
|
55
|
-
none: false
|
56
50
|
requirements:
|
57
|
-
- -
|
51
|
+
- - '>='
|
58
52
|
- !ruby/object:Gem::Version
|
59
53
|
version: '0'
|
60
54
|
type: :development
|
61
55
|
prerelease: false
|
62
56
|
version_requirements: !ruby/object:Gem::Requirement
|
63
|
-
none: false
|
64
57
|
requirements:
|
65
|
-
- -
|
58
|
+
- - '>='
|
66
59
|
- !ruby/object:Gem::Version
|
67
60
|
version: '0'
|
68
61
|
description: A filtered collection for backbone.js
|
@@ -74,6 +67,7 @@ extra_rdoc_files: []
|
|
74
67
|
files:
|
75
68
|
- .gitignore
|
76
69
|
- .rvmrc
|
70
|
+
- .travis.yml
|
77
71
|
- Gemfile
|
78
72
|
- LICENSE
|
79
73
|
- LICENSE.txt
|
@@ -93,27 +87,26 @@ files:
|
|
93
87
|
homepage: http://github.com/dlikhten/filtered-collection
|
94
88
|
licenses:
|
95
89
|
- MIT
|
90
|
+
metadata: {}
|
96
91
|
post_install_message:
|
97
92
|
rdoc_options: []
|
98
93
|
require_paths:
|
99
94
|
- lib
|
100
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
-
none: false
|
102
96
|
requirements:
|
103
|
-
- -
|
97
|
+
- - '>='
|
104
98
|
- !ruby/object:Gem::Version
|
105
99
|
version: '0'
|
106
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
-
none: false
|
108
101
|
requirements:
|
109
|
-
- -
|
102
|
+
- - '>='
|
110
103
|
- !ruby/object:Gem::Version
|
111
104
|
version: '0'
|
112
105
|
requirements: []
|
113
106
|
rubyforge_project:
|
114
|
-
rubygems_version:
|
107
|
+
rubygems_version: 2.0.6
|
115
108
|
signing_key:
|
116
|
-
specification_version:
|
109
|
+
specification_version: 4
|
117
110
|
summary: Allowing implementation of a chain-of-responsibility pattern in backbone's
|
118
111
|
collection filtering
|
119
112
|
test_files:
|