backgridjs-rails 0.2.6 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZjE2OTQ4Y2JjNTM2YzM2MWFkYmEzM2U0MjBhNGViMTQxN2JkZjBiZA==
5
- data.tar.gz: !binary |-
6
- ZTNiOGU0MGViYmI1ZTUzMTExNmUzMWYyYjNiNzVmNzU4ZWQ1OTliZA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MWY3MDhiNDhjNDkyOTQ1ZTBiNDM5MTQ0YWUwYWUwMDhmOWNkNjQ4YmFhNTdm
10
- NTljZGUyYzMxNzM4NDg0MGIyNDJjY2RkZmM4OGEzOThjNjc2OTcwYThlNTNj
11
- Y2FlYjc5ZDRhYzM2MDg3ODQ3ODg1MDc0ODA4ODFiYjAzMzMzMTU=
12
- data.tar.gz: !binary |-
13
- YzNlNjQ4MTg5YTg1N2Q5ZDE4YTkyYmJkOGE1N2ZmODEwMjg1NDJjMDc2MDc2
14
- MDk5MzY3ZTRjYTU1OWU0M2FhYzc2YjZiMmJhMmEzMWE5NWY5MGIxODQ2NzQy
15
- N2Y4ZDIwY2E3YWY1YWQ0Y2MyNTMxMjMzOWQ4MWQ0NTg2YjkwMDk=
2
+ SHA1:
3
+ metadata.gz: 1ab7aafbde55ffeca00bdf228fce029974a92330
4
+ data.tar.gz: b800be2fe7b2eba3a228ee2cd1b3a17991dc89c5
5
+ SHA512:
6
+ metadata.gz: c78c96f141e13784f227aac7c34d99e8a14a382c20304de71986a45f04b2f14cd52481fec128e8b2ccfa44e490dc9bd099a412ab1825e7076f9d6c48180c140a
7
+ data.tar.gz: 4f0f4a01bf087193b623468ca02634e6ca426bb09aecb37a9660c3f1a1f82028098a9031a8383fd7804a47e0045d10f0c80e4a199527ebf0b0061c90f7740642
@@ -1,3 +1,3 @@
1
1
  module BackgridjsRails
2
- VERSION = "0.2.6"
2
+ VERSION = "0.3.5"
3
3
  end
@@ -5,8 +5,25 @@
5
5
  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
6
6
  Licensed under the MIT @license.
7
7
  */
8
-
9
- (function ($, _, Backbone, Backgrid, lunr) {
8
+ (function (root, factory) {
9
+
10
+ // CommonJS
11
+ if (typeof exports == "object") {
12
+ (function () {
13
+ var lunr;
14
+ try { lunr = require("lunr"); } catch (e) {}
15
+ module.exports = factory(require("underscore"),
16
+ require("backbone"),
17
+ require("backgrid"),
18
+ lunr);
19
+ }());
20
+ }
21
+ // Browser
22
+ else {
23
+ factory(root._, root.Backbone, root.Backgrid, root.lunr);
24
+ }
25
+
26
+ }(this, function (_, Backbone, Backgrid, lunr) {
10
27
 
11
28
  "use strict";
12
29
 
@@ -25,62 +42,140 @@
25
42
  className: "backgrid-filter form-search",
26
43
 
27
44
  /** @property {function(Object, ?Object=): string} template */
28
- template: _.template('<div class="input-prepend input-append"><span class="add-on"><i class="icon-search"></i></span><input type="text" <% if (placeholder) { %> placeholder="<%- placeholder %>" <% } %> name="<%- name %>" /><span class="add-on"><a class="close" href="#">&times;</a></span></div>'),
45
+ template: _.template('<span class="search">&nbsp;</span><input type="search" <% if (placeholder) { %> placeholder="<%- placeholder %>" <% } %> name="<%- name %>" <% if (value) { %> value="<%- value %>" <% } %>/><a class="clear" data-backgrid-action="clear" href="#">&times;</a>', null, {variable: null}),
29
46
 
30
47
  /** @property */
31
48
  events: {
32
- "click .close": "clear",
49
+ "keyup input[type=search]": "showClearButtonMaybe",
50
+ "click a[data-backgrid-action=clear]": "clear",
33
51
  "submit": "search"
34
52
  },
35
53
 
36
54
  /** @property {string} [name='q'] Query key */
37
55
  name: "q",
38
56
 
39
- /** @property The HTML5 placeholder to appear beneath the search box. */
57
+ /** @property {string} [value] The search box value. */
58
+ value: null,
59
+
60
+ /**
61
+ @property {string} [placeholder] The HTML5 placeholder to appear beneath
62
+ the search box.
63
+ */
40
64
  placeholder: null,
41
65
 
42
66
  /**
43
67
  @param {Object} options
44
68
  @param {Backbone.Collection} options.collection
45
- @param {String} [options.name]
46
- @param {String} [options.placeholder]
69
+ @param {string} [options.name]
70
+ @param {string} [options.value]
71
+ @param {string} [options.placeholder]
72
+ @param {function(Object): string} [options.template]
47
73
  */
48
74
  initialize: function (options) {
49
- Backgrid.requireOptions(options, ["collection"]);
50
- Backbone.View.prototype.initialize.apply(this, arguments);
75
+ ServerSideFilter.__super__.initialize.apply(this, arguments);
51
76
  this.name = options.name || this.name;
77
+ this.value = options.value || this.value;
52
78
  this.placeholder = options.placeholder || this.placeholder;
79
+ this.template = options.template || this.template;
53
80
 
81
+ // Persist the query on pagination
54
82
  var collection = this.collection, self = this;
55
83
  if (Backbone.PageableCollection &&
56
84
  collection instanceof Backbone.PageableCollection &&
57
85
  collection.mode == "server") {
58
86
  collection.queryParams[this.name] = function () {
59
- return self.$el.find("input[type=text]").val();
87
+ return self.searchBox().val() || null;
60
88
  };
61
89
  }
62
90
  },
63
91
 
92
+ /**
93
+ Event handler. Clear the search box and reset the internal search value.
94
+ */
95
+ clearSearchBox: function() {
96
+ this.value = null;
97
+ this.searchBox().val(null);
98
+ this.showClearButtonMaybe();
99
+ },
100
+
101
+ /**
102
+ Event handler. Show the clear button when the search box has text, hide
103
+ it otherwise.
104
+ */
105
+ showClearButtonMaybe: function () {
106
+ var $clearButton = this.clearButton();
107
+ var searchTerms = this.searchBox().val();
108
+ if (searchTerms) $clearButton.show();
109
+ else $clearButton.hide();
110
+ },
111
+
112
+ /**
113
+ Returns the search input box.
114
+ */
115
+ searchBox: function () {
116
+ return this.$el.find("input[type=search]");
117
+ },
118
+
119
+ /**
120
+ Returns the clear button.
121
+ */
122
+ clearButton: function () {
123
+ return this.$el.find("a[data-backgrid-action=clear]");
124
+ },
125
+
126
+
127
+ /**
128
+ Returns the current search query.
129
+ */
130
+ query: function() {
131
+ this.value = this.searchBox().val();
132
+ return this.value;
133
+ },
134
+
64
135
  /**
65
136
  Upon search form submission, this event handler constructs a query
66
137
  parameter object and pass it to Collection#fetch for server-side
67
138
  filtering.
139
+
140
+ If the collection is a PageableCollection, searching will go back to the
141
+ first page.
68
142
  */
69
143
  search: function (e) {
70
144
  if (e) e.preventDefault();
145
+
71
146
  var data = {};
72
- data[this.name] = this.$el.find("input[type=text]").val();
73
- this.collection.fetch({data: data});
147
+ var query = this.query();
148
+ if (query) data[this.name] = query;
149
+
150
+ var collection = this.collection;
151
+
152
+ // go back to the first page on search
153
+ if (Backbone.PageableCollection &&
154
+ collection instanceof Backbone.PageableCollection) {
155
+ collection.getFirstPage({data: data, reset: true, fetch: true});
156
+ }
157
+ else collection.fetch({data: data, reset: true});
74
158
  },
75
159
 
76
160
  /**
77
- Event handler for the close button. Clears the search box and refetch the
161
+ Event handler for the clear button. Clears the search box and refetch the
78
162
  collection.
163
+
164
+ If the collection is a PageableCollection, clearing will go back to the
165
+ first page.
79
166
  */
80
167
  clear: function (e) {
81
168
  if (e) e.preventDefault();
82
- this.$("input[type=text]").val(null);
83
- this.collection.fetch();
169
+ this.clearSearchBox();
170
+
171
+ var collection = this.collection;
172
+
173
+ // go back to the first page on clear
174
+ if (Backbone.PageableCollection &&
175
+ collection instanceof Backbone.PageableCollection) {
176
+ collection.getFirstPage({reset: true, fetch: true});
177
+ }
178
+ else collection.fetch({reset: true});
84
179
  },
85
180
 
86
181
  /**
@@ -93,6 +188,7 @@
93
188
  placeholder: this.placeholder,
94
189
  value: this.value
95
190
  }));
191
+ this.showClearButtonMaybe();
96
192
  this.delegateEvents();
97
193
  return this;
98
194
  }
@@ -110,27 +206,26 @@
110
206
  var ClientSideFilter = Backgrid.Extension.ClientSideFilter = ServerSideFilter.extend({
111
207
 
112
208
  /** @property */
113
- events: {
114
- "click .close": function (e) {
209
+ events: _.extend({}, ServerSideFilter.prototype.events, {
210
+ "click a[data-backgrid-action=clear]": function (e) {
115
211
  e.preventDefault();
116
212
  this.clear();
117
213
  },
118
- "change input[type=text]": "search",
119
- "keyup input[type=text]": "search",
214
+ "keydown input[type=search]": "search",
120
215
  "submit": function (e) {
121
216
  e.preventDefault();
122
217
  this.search();
123
218
  }
124
- },
219
+ }),
125
220
 
126
221
  /**
127
- @property {?Array.<string>} A list of model field names to search
128
- for matches. If null, all of the fields will be searched.
222
+ @property {?Array.<string>} [fields] A list of model field names to
223
+ search for matches. If null, all of the fields will be searched.
129
224
  */
130
225
  fields: null,
131
226
 
132
227
  /**
133
- @property wait The time in milliseconds to wait since for since the last
228
+ @property [wait=149] The time in milliseconds to wait since the last
134
229
  change to the search box's value before searching. This value can be
135
230
  adjusted depending on how often the search box is used and how large the
136
231
  search index is.
@@ -143,23 +238,20 @@
143
238
 
144
239
  @param {Object} options
145
240
  @param {Backbone.Collection} options.collection
146
- @param {String} [options.placeholder]
147
- @param {String} [options.fields]
148
- @param {String} [options.wait=149]
241
+ @param {string} [options.placeholder]
242
+ @param {string} [options.fields]
243
+ @param {string} [options.wait=149]
149
244
  */
150
245
  initialize: function (options) {
151
- ServerSideFilter.prototype.initialize.apply(this, arguments);
246
+ ClientSideFilter.__super__.initialize.apply(this, arguments);
152
247
 
153
248
  this.fields = options.fields || this.fields;
154
249
  this.wait = options.wait || this.wait;
155
250
 
156
251
  this._debounceMethods(["search", "clear"]);
157
252
 
158
- var collection = this.collection;
253
+ var collection = this.collection = this.collection.fullCollection || this.collection;
159
254
  var shadowCollection = this.shadowCollection = collection.clone();
160
- shadowCollection.url = collection.url;
161
- shadowCollection.sync = collection.sync;
162
- shadowCollection.parse = collection.parse;
163
255
 
164
256
  this.listenTo(collection, "add", function (model, collection, options) {
165
257
  shadowCollection.add(model, options);
@@ -167,9 +259,14 @@
167
259
  this.listenTo(collection, "remove", function (model, collection, options) {
168
260
  shadowCollection.remove(model, options);
169
261
  });
170
- this.listenTo(collection, "sort reset", function (collection, options) {
262
+ this.listenTo(collection, "sort", function (col) {
263
+ if (!this.searchBox().val()) shadowCollection.reset(col.models);
264
+ });
265
+ this.listenTo(collection, "reset", function (col, options) {
171
266
  options = _.extend({reindex: true}, options || {});
172
- if (options.reindex) shadowCollection.reset(collection.models);
267
+ if (options.reindex && options.from == null && options.to == null) {
268
+ shadowCollection.reset(col.models);
269
+ }
173
270
  });
174
271
  },
175
272
 
@@ -187,22 +284,47 @@
187
284
  this.delegateEvents();
188
285
  },
189
286
 
287
+ /**
288
+ Constructs a Javascript regular expression object for #makeMatcher.
289
+
290
+ This default implementation takes a query string and returns a Javascript
291
+ RegExp object that matches any of the words contained in the query string
292
+ case-insensitively. Override this method to return a different regular
293
+ expression matcher if this behavior is not desired.
294
+
295
+ @param {string} query The search query in the search box.
296
+ @return {RegExp} A RegExp object to match against model #fields.
297
+ */
298
+ makeRegExp: function (query) {
299
+ return new RegExp(query.trim().split(/\s+/).join("|"), "i");
300
+ },
301
+
190
302
  /**
191
303
  This default implementation takes a query string and returns a matcher
192
304
  function that looks for matches in the model's #fields or all of its
193
305
  fields if #fields is null, for any of the words in the query
194
- case-insensitively.
306
+ case-insensitively using the regular expression object returned from
307
+ #makeRegExp.
308
+
309
+ Most of time, you'd want to override the regular expression used for
310
+ matching. If so, please refer to the #makeRegExp documentation,
311
+ otherwise, you can override this method to return a custom matching
312
+ function.
195
313
 
196
314
  Subclasses overriding this method must take care to conform to the
197
- signature of the matcher function. In addition, when the matcher function
198
- is called, its context will be bound to this ClientSideFilter object so
199
- it has access to the filter's attributes and methods.
315
+ signature of the matcher function. The matcher function is a function
316
+ that takes a model as paramter and returns true if the model matches a
317
+ search, or false otherwise.
318
+
319
+ In addition, when the matcher function is called, its context will be
320
+ bound to this ClientSideFilter object so it has access to the filter's
321
+ attributes and methods.
200
322
 
201
323
  @param {string} query The search query in the search box.
202
324
  @return {function(Backbone.Model):boolean} A matching function.
203
325
  */
204
326
  makeMatcher: function (query) {
205
- var regexp = new RegExp(query.trim().split(/\W/).join("|"), "i");
327
+ var regexp = this.makeRegExp(query);
206
328
  return function (model) {
207
329
  var keys = this.fields || model.keys();
208
330
  for (var i = 0, l = keys.length; i < l; i++) {
@@ -216,18 +338,28 @@
216
338
  Takes the query from the search box, constructs a matcher with it and
217
339
  loops through collection looking for matches. Reset the given collection
218
340
  when all the matches have been found.
341
+
342
+ If the collection is a PageableCollection, searching will go back to the
343
+ first page.
219
344
  */
220
345
  search: function () {
221
- var matcher = _.bind(this.makeMatcher(this.$("input[type=text]").val()), this);
222
- this.collection.reset(this.shadowCollection.filter(matcher), {reindex: false});
346
+ var matcher = _.bind(this.makeMatcher(this.query()), this);
347
+ var col = this.collection;
348
+ if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
349
+ col.reset(this.shadowCollection.filter(matcher), {reindex: false});
223
350
  },
224
351
 
225
352
  /**
226
353
  Clears the search box and reset the collection to its original.
354
+
355
+ If the collection is a PageableCollection, clearing will go back to the
356
+ first page.
227
357
  */
228
358
  clear: function () {
229
- this.$("input[type=text]").val(null);
230
- this.collection.reset(this.shadowCollection.models, {reindex: false});
359
+ this.clearSearchBox();
360
+ var col = this.collection;
361
+ if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
362
+ col.reset(this.shadowCollection.models, {reindex: false});
231
363
  }
232
364
 
233
365
  });
@@ -240,7 +372,7 @@
240
372
  @class Backgrid.Extension.LunrFilter
241
373
  @extends Backgrid.Extension.ClientSideFilter
242
374
  */
243
- Backgrid.Extension.LunrFilter = ClientSideFilter.extend({
375
+ var LunrFilter = Backgrid.Extension.LunrFilter = ClientSideFilter.extend({
244
376
 
245
377
  /**
246
378
  @property {string} [ref="id"]`lunrjs` document reference attribute name.
@@ -262,18 +394,18 @@
262
394
 
263
395
  @param {Object} options
264
396
  @param {Backbone.Collection} options.collection
265
- @param {String} [options.placeholder]
397
+ @param {string} [options.placeholder]
266
398
  @param {string} [options.ref] `lunrjs` document reference attribute name.
267
399
  @param {Object} [options.fields] A hash of `lunrjs` index field names and
268
400
  boost value.
269
401
  @param {number} [options.wait]
270
402
  */
271
403
  initialize: function (options) {
272
- ClientSideFilter.prototype.initialize.apply(this, arguments);
404
+ LunrFilter.__super__.initialize.apply(this, arguments);
273
405
 
274
406
  this.ref = options.ref || this.ref;
275
407
 
276
- var collection = this.collection;
408
+ var collection = this.collection = this.collection.fullCollection || this.collection;
277
409
  this.listenTo(collection, "add", this.addToIndex);
278
410
  this.listenTo(collection, "remove", this.removeFromIndex);
279
411
  this.listenTo(collection, "reset", this.resetIndex);
@@ -349,17 +481,28 @@
349
481
  the client-side. The search result is returned by resetting the
350
482
  underlying collection to the models after interrogating the index for the
351
483
  query answer.
484
+
485
+ If the collection is a PageableCollection, searching will go back to the
486
+ first page.
352
487
  */
353
488
  search: function () {
354
- var searchResults = this.index.search(this.$("input[type=text]").val());
489
+ var col = this.collection;
490
+ if (!this.query()) {
491
+ col.reset(this.shadowCollection.models, {reindex: false});
492
+ return;
493
+ }
494
+
495
+ var searchResults = this.index.search(this.query());
355
496
  var models = [];
356
497
  for (var i = 0; i < searchResults.length; i++) {
357
498
  var result = searchResults[i];
358
499
  models.push(this.shadowCollection.get(result.ref));
359
500
  }
360
- this.collection.reset(models, {reindex: false});
501
+
502
+ if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
503
+ col.reset(models, {reindex: false});
361
504
  }
362
505
 
363
506
  });
364
507
 
365
- }(jQuery, _, Backbone, Backgrid, lunr));
508
+ }));
@@ -5,7 +5,17 @@
5
5
  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
6
6
  Licensed under the MIT @license.
7
7
  */
8
- (function ($, _, Backbone, Backgrid, moment) {
8
+ (function (root, factory) {
9
+
10
+ // CommonJS
11
+ if (typeof exports == "object") {
12
+ module.exports = factory(require("underscore"), require("backgrid"),
13
+ require("moment"));
14
+ }
15
+ // Browser
16
+ else factory(root._, root.Backgrid, root.moment);
17
+
18
+ }(this, function (_, Backgrid, moment) {
9
19
 
10
20
  /**
11
21
  MomentFormatter converts bi-directionally any datetime values in any format
@@ -152,14 +162,14 @@
152
162
  */
153
163
  initialize: function (options) {
154
164
 
155
- Backgrid.Cell.prototype.initialize.apply(this, arguments);
165
+ MomentCell.__super__.initialize.apply(this, arguments);
156
166
 
157
167
  var formatterDefaults = MomentFormatter.prototype.defaults;
158
168
  var formatterDefaultKeys = _.keys(formatterDefaults);
159
- var classAttrs = _.pick(this, formatterDefaultKeys);
169
+ var instanceAttrs = _.pick(this, formatterDefaultKeys);
160
170
  var formatterOptions = _.pick(options, formatterDefaultKeys);
161
171
 
162
- this.formatter = new this.formatter(_.extend({}, formatterDefaults, classAttrs, formatterOptions));
172
+ _.extend(this.formatter, formatterDefaults, instanceAttrs, formatterOptions);
163
173
 
164
174
  this.editor = this.editor.extend({
165
175
  attributes: _.extend({}, this.editor.prototype.attributes || this.editor.attributes || {}, {
@@ -172,4 +182,4 @@
172
182
 
173
183
  _.extend(MomentCell.prototype, MomentFormatter.prototype.defaults);
174
184
 
175
- }(jQuery, _, Backbone, Backgrid, moment));
185
+ }));