backgridjs-rails 0.2.6 → 0.3.5

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.
@@ -5,23 +5,214 @@
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) {
8
+ (function (root, factory) {
9
+
10
+ // CommonJS
11
+ if (typeof exports == "object") {
12
+ module.exports = factory(require("underscore"),
13
+ require("backbone"),
14
+ require("backgrid"),
15
+ require("backbone-pageable"));
16
+ }
17
+ // Browser
18
+ else {
19
+ factory(root._, root.Backbone, root.Backgrid);
20
+ }
21
+
22
+ }(this, function (_, Backbone, Backgrid) {
10
23
 
11
24
  "use strict";
12
25
 
26
+ /**
27
+ PageHandle is a class that renders the actual page handles and reacts to
28
+ click events for pagination.
29
+
30
+ This class acts in two modes - control or discrete page handle modes. If
31
+ one of the `is*` flags is `true`, an instance of this class is under
32
+ control page handle mode. Setting a `pageIndex` to an instance of this
33
+ class under control mode has no effect and the correct page index will
34
+ always be inferred from the `is*` flag. Only one of the `is*` flags should
35
+ be set to `true` at a time. For example, an instance of this class cannot
36
+ simultaneously be a rewind control and a fast forward control. A `label`
37
+ and a `title` template or a string are required to be passed to the
38
+ constuctor under this mode. If a `title` template is provided, it __MUST__
39
+ accept a parameter `label`. When the `label` is provided to the `title`
40
+ template function, its result will be used to render the generated anchor's
41
+ title attribute.
42
+
43
+ If all of the `is*` flags is set to `false`, which is the default, an
44
+ instance of this class will be in discrete page handle mode. An instance
45
+ under this mode requires the `pageIndex` to be passed from the constructor
46
+ as an option and it __MUST__ be a 0-based index of the list of page numbers
47
+ to render. The constuctor will normalize the base to the same base the
48
+ underlying PageableCollection collection instance uses. A `label` is not
49
+ required under this mode, which will default to the equivalent 1-based page
50
+ index calculated from `pageIndex` and the underlying PageableCollection
51
+ instance. A provided `label` will still be honored however. The `title`
52
+ parameter is also not required under this mode, in which case the default
53
+ `title` template will be used. You are encouraged to provide your own
54
+ `title` template however if you wish to localize the title strings.
55
+
56
+ If this page handle represents the current page, an `active` class will be
57
+ placed on the root list element.
58
+
59
+ If this page handle is at the border of the list of pages, a `disabled`
60
+ class will be placed on the root list element.
61
+
62
+ Only page handles that are neither `active` nor `disabled` will respond to
63
+ click events and triggers pagination.
64
+
65
+ @class Backgrid.Extension.PageHandle
66
+ */
67
+ var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
68
+
69
+ /** @property */
70
+ tagName: "li",
71
+
72
+ /** @property */
73
+ events: {
74
+ "click a": "changePage"
75
+ },
76
+
77
+ /**
78
+ @property {string|function(Object.<string, string>): string} title
79
+ The title to use for the `title` attribute of the generated page handle
80
+ anchor elements. It can be a string or an Underscore template function
81
+ that takes a mandatory `label` parameter.
82
+ */
83
+ title: _.template('Page <%- label %>', null, {variable: null}),
84
+
85
+ /**
86
+ @property {boolean} isRewind Whether this handle represents a rewind
87
+ control
88
+ */
89
+ isRewind: false,
90
+
91
+ /**
92
+ @property {boolean} isBack Whether this handle represents a back
93
+ control
94
+ */
95
+ isBack: false,
96
+
97
+ /**
98
+ @property {boolean} isForward Whether this handle represents a forward
99
+ control
100
+ */
101
+ isForward: false,
102
+
103
+ /**
104
+ @property {boolean} isFastForward Whether this handle represents a fast
105
+ forward control
106
+ */
107
+ isFastForward: false,
108
+
109
+ /**
110
+ Initializer.
111
+
112
+ @param {Object} options
113
+ @param {Backbone.Collection} options.collection
114
+ @param {number} pageIndex 0-based index of the page number this handle
115
+ handles. This parameter will be normalized to the base the underlying
116
+ PageableCollection uses.
117
+ @param {string} [options.label] If provided it is used to render the
118
+ anchor text, otherwise the normalized pageIndex will be used
119
+ instead. Required if any of the `is*` flags is set to `true`.
120
+ @param {string} [options.title]
121
+ @param {boolean} [options.isRewind=false]
122
+ @param {boolean} [options.isBack=false]
123
+ @param {boolean} [options.isForward=false]
124
+ @param {boolean} [options.isFastForward=false]
125
+ */
126
+ initialize: function (options) {
127
+ var collection = this.collection;
128
+ var state = collection.state;
129
+ var currentPage = state.currentPage;
130
+ var firstPage = state.firstPage;
131
+ var lastPage = state.lastPage;
132
+
133
+ _.extend(this, _.pick(options,
134
+ ["isRewind", "isBack", "isForward", "isFastForward"]));
135
+
136
+ var pageIndex;
137
+ if (this.isRewind) pageIndex = firstPage;
138
+ else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
139
+ else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
140
+ else if (this.isFastForward) pageIndex = lastPage;
141
+ else {
142
+ pageIndex = +options.pageIndex;
143
+ pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
144
+ }
145
+ this.pageIndex = pageIndex;
146
+
147
+ this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
148
+ var title = options.title || this.title;
149
+ this.title = _.isFunction(title) ? title({label: this.label}) : title;
150
+ },
151
+
152
+ /**
153
+ Renders a clickable anchor element under a list item.
154
+ */
155
+ render: function () {
156
+ this.$el.empty();
157
+ var anchor = document.createElement("a");
158
+ anchor.href = '#';
159
+ if (this.title) anchor.title = this.title;
160
+ anchor.innerHTML = this.label;
161
+ this.el.appendChild(anchor);
162
+
163
+ var collection = this.collection;
164
+ var state = collection.state;
165
+ var currentPage = state.currentPage;
166
+ var pageIndex = this.pageIndex;
167
+
168
+ if (this.isRewind && currentPage == state.firstPage ||
169
+ this.isBack && !collection.hasPreviousPage() ||
170
+ this.isForward && !collection.hasNextPage() ||
171
+ this.isFastForward && (currentPage == state.lastPage || state.totalPages < 1)) {
172
+ this.$el.addClass("disabled");
173
+ }
174
+ else if (!(this.isRewind ||
175
+ this.isBack ||
176
+ this.isForward ||
177
+ this.isFastForward) &&
178
+ state.currentPage == pageIndex) {
179
+ this.$el.addClass("active");
180
+ }
181
+
182
+ this.delegateEvents();
183
+ return this;
184
+ },
185
+
186
+ /**
187
+ jQuery click event handler. Goes to the page this PageHandle instance
188
+ represents. No-op if this page handle is currently active or disabled.
189
+ */
190
+ changePage: function (e) {
191
+ e.preventDefault();
192
+ var $el = this.$el, col = this.collection;
193
+ if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
194
+ if (this.isRewind) col.getFirstPage();
195
+ else if (this.isBack) col.getPreviousPage();
196
+ else if (this.isForward) col.getNextPage();
197
+ else if (this.isFastForward) col.getLastPage();
198
+ else col.getPage(this.pageIndex, {reset: true});
199
+ }
200
+ return this;
201
+ }
202
+
203
+ });
204
+
13
205
  /**
14
206
  Paginator is a Backgrid extension that renders a series of configurable
15
207
  pagination handles. This extension is best used for splitting a large data
16
208
  set across multiple pages. If the number of pages is larger then a
17
209
  threshold, which is set to 10 by default, the page handles are rendered
18
- within a sliding window, plus the fast forward, fast backward, previous and
19
- next page handles. The fast forward, fast backward, previous and next page
20
- handles can be turned off.
210
+ within a sliding window, plus the rewind, back, forward and fast forward
211
+ control handles. The individual control handles can be turned off.
21
212
 
22
213
  @class Backgrid.Extension.Paginator
23
214
  */
24
- Backgrid.Extension.Paginator = Backbone.View.extend({
215
+ var Paginator = Backgrid.Extension.Paginator = Backbone.View.extend({
25
216
 
26
217
  /** @property */
27
218
  className: "backgrid-paginator",
@@ -30,97 +221,122 @@
30
221
  windowSize: 10,
31
222
 
32
223
  /**
33
- @property {Object} fastForwardHandleLabels You can disable specific
34
- handles by setting its value to `null`.
224
+ @property {number} slideScale the number used by #slideHowMuch to scale
225
+ `windowSize` to yield the number of pages to slide. For example, the
226
+ default windowSize(10) * slideScale(0.5) yields 5, which means the window
227
+ will slide forward 5 pages as soon as you've reached page 6. The smaller
228
+ the scale factor the less pages to slide, and vice versa.
229
+
230
+ Also See:
231
+
232
+ - #slideMaybe
233
+ - #slideHowMuch
35
234
  */
36
- fastForwardHandleLabels: {
37
- first: "《",
38
- prev: "〈",
39
- next: "〉",
40
- last: "》"
235
+ slideScale: 0.5,
236
+
237
+ /**
238
+ @property {Object.<string, Object.<string, string>>} controls You can
239
+ disable specific control handles by setting the keys in question to
240
+ null. The defaults will be merged with your controls object, with your
241
+ changes taking precedent.
242
+ */
243
+ controls: {
244
+ rewind: {
245
+ label: "《",
246
+ title: "First"
247
+ },
248
+ back: {
249
+ label: "〈",
250
+ title: "Previous"
251
+ },
252
+ forward: {
253
+ label: "〉",
254
+ title: "Next"
255
+ },
256
+ fastForward: {
257
+ label: "》",
258
+ title: "Last"
259
+ }
41
260
  },
42
261
 
43
262
  /** @property */
44
- template: _.template('<ul><% _.each(handles, function (handle) { %><li <% if (handle.className) { %>class="<%= handle.className %>"<% } %>><a href="#" <% if (handle.title) {%> title="<%= handle.title %>"<% } %>><%= handle.label %></a></li><% }); %></ul>'),
263
+ renderIndexedPageHandles: true,
264
+
265
+ /**
266
+ @property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
267
+ class to use for rendering individual handles
268
+ */
269
+ pageHandle: PageHandle,
45
270
 
46
271
  /** @property */
47
- events: {
48
- "click a": "changePage"
49
- },
272
+ goBackFirstOnSort: true,
50
273
 
51
274
  /**
52
275
  Initializer.
53
276
 
54
277
  @param {Object} options
55
278
  @param {Backbone.Collection} options.collection
56
- @param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons.
279
+ @param {boolean} [options.controls]
280
+ @param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
281
+ @param {boolean} [options.goBackFirstOnSort=true]
57
282
  */
58
283
  initialize: function (options) {
59
- Backgrid.requireOptions(options, ["collection"]);
60
-
61
- var collection = this.collection;
62
- var fullCollection = collection.fullCollection;
63
- if (fullCollection) {
64
- this.listenTo(fullCollection, "add", this.render);
65
- this.listenTo(fullCollection, "remove", this.render);
66
- this.listenTo(fullCollection, "reset", this.render);
67
- }
68
- else {
69
- this.listenTo(collection, "add", this.render);
70
- this.listenTo(collection, "remove", this.render);
71
- this.listenTo(collection, "reset", this.render);
72
- }
284
+ var self = this;
285
+ self.controls = _.defaults(options.controls || {}, self.controls,
286
+ Paginator.prototype.controls);
287
+
288
+ _.extend(self, _.pick(options || {}, "windowSize", "pageHandle",
289
+ "slideScale", "goBackFirstOnSort",
290
+ "renderIndexedPageHandles"));
291
+
292
+ var col = self.collection;
293
+ self.listenTo(col, "add", self.render);
294
+ self.listenTo(col, "remove", self.render);
295
+ self.listenTo(col, "reset", self.render);
296
+ self.listenTo(col, "backgrid:sorted", function () {
297
+ if (self.goBackFirstOnSort) col.getFirstPage({reset: true});
298
+ });
73
299
  },
74
300
 
75
301
  /**
76
- jQuery event handler for the page handlers. Goes to the right page upon
77
- clicking.
302
+ Decides whether the window should slide. This method should return 1 if
303
+ sliding should occur and 0 otherwise. The default is sliding should occur
304
+ if half of the pages in a window has been reached.
78
305
 
79
- @param {Event} e
80
- */
81
- changePage: function (e) {
82
- e.preventDefault();
306
+ __Note__: All the parameters have been normalized to be 0-based.
83
307
 
84
- var $li = $(e.target).parent();
85
- if (!$li.hasClass("active") && !$li.hasClass("disabled")) {
86
-
87
- var label = $(e.target).text();
88
- var ffLabels = this.fastForwardHandleLabels;
89
-
90
- var collection = this.collection;
91
-
92
- if (ffLabels) {
93
- switch (label) {
94
- case ffLabels.first:
95
- collection.getFirstPage();
96
- return;
97
- case ffLabels.prev:
98
- collection.getPreviousPage();
99
- return;
100
- case ffLabels.next:
101
- collection.getNextPage();
102
- return;
103
- case ffLabels.last:
104
- collection.getLastPage();
105
- return;
106
- }
107
- }
308
+ @param {number} firstPage
309
+ @param {number} lastPage
310
+ @param {number} currentPage
311
+ @param {number} windowSize
312
+ @param {number} slideScale
108
313
 
109
- var state = collection.state;
110
- var pageIndex = +label;
111
- collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex);
112
- }
314
+ @return {0|1}
315
+ */
316
+ slideMaybe: function (firstPage, lastPage, currentPage, windowSize, slideScale) {
317
+ return Math.round(currentPage % windowSize / windowSize);
113
318
  },
114
319
 
115
320
  /**
116
- Internal method to create a list of page handle objects for the template
117
- to render them.
321
+ Decides how many pages to slide when sliding should occur. The default
322
+ simply scales the `windowSize` to arrive at a fraction of the `windowSize`
323
+ to increment.
118
324
 
119
- @return {Array.<Object>} an array of page handle objects hashes
325
+ __Note__: All the parameters have been normalized to be 0-based.
326
+
327
+ @param {number} firstPage
328
+ @param {number} lastPage
329
+ @param {number} currentPage
330
+ @param {number} windowSize
331
+ @param {number} slideScale
332
+
333
+ @return {number}
120
334
  */
121
- makeHandles: function () {
335
+ slideThisMuch: function (firstPage, lastPage, currentPage, windowSize, slideScale) {
336
+ return ~~(windowSize * slideScale);
337
+ },
122
338
 
123
- var handles = [];
339
+ _calculateWindow: function () {
124
340
  var collection = this.collection;
125
341
  var state = collection.state;
126
342
 
@@ -130,51 +346,55 @@
130
346
  lastPage = Math.max(0, firstPage ? lastPage - 1 : lastPage);
131
347
  var currentPage = Math.max(state.currentPage, state.firstPage);
132
348
  currentPage = firstPage ? currentPage - 1 : currentPage;
133
- var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
134
- var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
135
-
136
- if (collection.mode !== "infinite") {
137
- for (var i = windowStart; i < windowEnd; i++) {
138
- handles.push({
139
- label: i + 1,
140
- title: "No. " + (i + 1),
141
- className: currentPage === i ? "active" : undefined
142
- });
143
- }
349
+ var windowSize = this.windowSize;
350
+ var slideScale = this.slideScale;
351
+ var windowStart = Math.floor(currentPage / windowSize) * windowSize;
352
+ if (currentPage <= lastPage - this.slideThisMuch()) {
353
+ windowStart += (this.slideMaybe(firstPage, lastPage, currentPage, windowSize, slideScale) *
354
+ this.slideThisMuch(firstPage, lastPage, currentPage, windowSize, slideScale));
144
355
  }
356
+ var windowEnd = Math.min(lastPage + 1, windowStart + windowSize);
357
+ return [windowStart, windowEnd];
358
+ },
145
359
 
146
- var ffLabels = this.fastForwardHandleLabels;
147
- if (ffLabels) {
360
+ /**
361
+ Creates a list of page handle objects for rendering.
148
362
 
149
- if (ffLabels.prev) {
150
- handles.unshift({
151
- label: ffLabels.prev,
152
- className: collection.hasPrevious() ? void 0 : "disabled"
153
- });
154
- }
363
+ @return {Array.<Object>} an array of page handle objects hashes
364
+ */
365
+ makeHandles: function () {
155
366
 
156
- if (ffLabels.first) {
157
- handles.unshift({
158
- label: ffLabels.first,
159
- className: collection.hasPrevious() ? void 0 : "disabled"
160
- });
161
- }
367
+ var handles = [];
368
+ var collection = this.collection;
162
369
 
163
- if (ffLabels.next) {
164
- handles.push({
165
- label: ffLabels.next,
166
- className: collection.hasNext() ? void 0 : "disabled"
167
- });
168
- }
370
+ var window = this._calculateWindow();
371
+ var winStart = window[0], winEnd = window[1];
169
372
 
170
- if (ffLabels.last) {
171
- handles.push({
172
- label: ffLabels.last,
173
- className: collection.hasNext() ? void 0 : "disabled"
174
- });
373
+ if (this.renderIndexedPageHandles) {
374
+ for (var i = winStart; i < winEnd; i++) {
375
+ handles.push(new this.pageHandle({
376
+ collection: collection,
377
+ pageIndex: i
378
+ }));
175
379
  }
176
380
  }
177
381
 
382
+ var controls = this.controls;
383
+ _.each(["back", "rewind", "forward", "fastForward"], function (key) {
384
+ var value = controls[key];
385
+ if (value) {
386
+ var handleCtorOpts = {
387
+ collection: collection,
388
+ title: value.title,
389
+ label: value.label
390
+ };
391
+ handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
392
+ var handle = new this.pageHandle(handleCtorOpts);
393
+ if (key == "rewind" || key == "back") handles.unshift(handle);
394
+ else handles.push(handle);
395
+ }
396
+ }, this);
397
+
178
398
  return handles;
179
399
  },
180
400
 
@@ -184,15 +404,24 @@
184
404
  render: function () {
185
405
  this.$el.empty();
186
406
 
187
- this.$el.append(this.template({
188
- handles: this.makeHandles()
189
- }));
407
+ if (this.handles) {
408
+ for (var i = 0, l = this.handles.length; i < l; i++) {
409
+ this.handles[i].remove();
410
+ }
411
+ }
190
412
 
191
- this.delegateEvents();
413
+ var handles = this.handles = this.makeHandles();
414
+
415
+ var ul = document.createElement("ul");
416
+ for (var i = 0; i < handles.length; i++) {
417
+ ul.appendChild(handles[i].render().el);
418
+ }
419
+
420
+ this.el.appendChild(ul);
192
421
 
193
422
  return this;
194
423
  }
195
424
 
196
425
  });
197
426
 
198
- }(jQuery, _, Backbone, Backgrid));
427
+ }));