js_stack 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 859a7e59f443025858bedae178446651c40ae4fb
4
- data.tar.gz: 93d28d6952ce9eba50d4e3f475bc13a75a3ff395
3
+ metadata.gz: 2b2232ea8922ab38c10202bc52364394fc45b40b
4
+ data.tar.gz: 0d16916c11ddae1d744622e4102088dd46b4adc5
5
5
  SHA512:
6
- metadata.gz: b03c12e5e0e285a106d6d317d881f62f5f6b0d896f5e08adfaebe00eef944eb7f5bf5cdeb36ec2d609a68748908fa5a8045847cc3777ded290c0aa9a87f012f1
7
- data.tar.gz: 793a36439f627f0eaf7571d531b78df3d7c2d04391408d78864acbc7c753b95e3cbd7a143a2b2c1afa3bf35d87d2face11176a4ed2850b85986eb4a1c64f2884
6
+ metadata.gz: cdb099cf43d534d149b3141691777ffdaab8e869d3ed99c0d502b2dc39581dca0ca5f92dcef9e85145259e04c254d270147210ffb1cecd59ac26bf81499e6e44
7
+ data.tar.gz: 3043017d6f72ba49b305fc2ea61b4b0c69df1c7b86b4a357da126a4359f339914408f6727805bf20f7041de80e771a9701eb72f009e8a20e9fc977033645c8f3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ # 0.6.4
4
+
5
+ * add backbone.paginator 2.0.0 [@gvl]
6
+ - backbone.pageable is now deprecated- see: https://github.com/backbone-paginator/backbone-pageable#deprecated
7
+
3
8
  # 0.6.3
4
9
 
5
10
  * update backbone.routefilter 0.2.0 -> 0.2.1 [@gvl]
data/README.md CHANGED
@@ -66,6 +66,7 @@ Examples:
66
66
  | backbone deepmodel | **0.10.4** | [changelog](https://github.com/powmedia/backbone-deep-model#changelog) | [homepage](https://github.com/powmedia/backbone-deep-model) | Yes
67
67
  | backbone mutators | **0.4.2** | [changelog](https://github.com/asciidisco/Backbone.Mutators#changelog) | [homepage](https://github.com/asciidisco/Backbone.Mutators) | No
68
68
  | backbone pageable | **1.4.8**, 1.3.2 | [changelog](https://github.com/backbone-paginator/backbone-pageable#change-log) | [homepage](https://github.com/wyuenho/backbone-pageable) | Yes
69
+ | backbone paginator | **2.0.0** | [changelog](https://github.com/backbone-paginator/backbone.paginator/wiki/Changelog) | [homepage](https://github.com/backbone-paginator/backbone.paginator) | No
69
70
  | backbone routefilter | **0.2.1** | [changelog](https://github.com/boazsender/backbone.routefilter#release-history) | [homepage](https://github.com/boazsender/backbone.routefilter) | No
70
71
  | backbone stickit | **0.8.0**, 0.7.0, 0.6.3 | [changelog](http://nytimes.github.io/backbone.stickit/#change-log) | [homepage](http://nytimes.github.io/backbone.stickit/) | Yes
71
72
  | backbone validation | **0.9.1**, 0.8.1 | [changelog](https://github.com/thedersen/backbone.validation#release-notes) | [homepage](https://github.com/thedersen/backbone.validation) | Yes
@@ -1,3 +1,3 @@
1
1
  module JsStack
2
- VERSION = '0.6.3'
2
+ VERSION = '0.6.4'
3
3
  end
@@ -0,0 +1,1314 @@
1
+ /*
2
+ backbone.paginator 2.0.0
3
+ http://github.com/backbone-paginator/backbone.paginator
4
+
5
+ Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
6
+ Licensed under the MIT @license.
7
+ */
8
+
9
+ (function (factory) {
10
+
11
+ // CommonJS
12
+ if (typeof exports == "object") {
13
+ module.exports = factory(require("underscore"), require("backbone"));
14
+ }
15
+ // AMD
16
+ else if (typeof define == "function" && define.amd) {
17
+ define(["underscore", "backbone"], factory);
18
+ }
19
+ // Browser
20
+ else if (typeof _ !== "undefined" && typeof Backbone !== "undefined") {
21
+ var oldPageableCollection = Backbone.PageableCollection;
22
+ var PageableCollection = factory(_, Backbone);
23
+
24
+ /**
25
+ __BROWSER ONLY__
26
+
27
+ If you already have an object named `PageableCollection` attached to the
28
+ `Backbone` module, you can use this to return a local reference to this
29
+ Backbone.PageableCollection class and reset the name
30
+ Backbone.PageableCollection to its previous definition.
31
+
32
+ // The left hand side gives you a reference to this
33
+ // Backbone.PageableCollection implementation, the right hand side
34
+ // resets Backbone.PageableCollection to your other
35
+ // Backbone.PageableCollection.
36
+ var PageableCollection = Backbone.PageableCollection.noConflict();
37
+
38
+ @static
39
+ @member Backbone.PageableCollection
40
+ @return {Backbone.PageableCollection}
41
+ */
42
+ Backbone.PageableCollection.noConflict = function () {
43
+ Backbone.PageableCollection = oldPageableCollection;
44
+ return PageableCollection;
45
+ };
46
+ }
47
+
48
+ }(function (_, Backbone) {
49
+
50
+ "use strict";
51
+
52
+ var _extend = _.extend;
53
+ var _omit = _.omit;
54
+ var _clone = _.clone;
55
+ var _each = _.each;
56
+ var _pick = _.pick;
57
+ var _contains = _.contains;
58
+ var _isEmpty = _.isEmpty;
59
+ var _pairs = _.pairs;
60
+ var _invert = _.invert;
61
+ var _isArray = _.isArray;
62
+ var _isFunction = _.isFunction;
63
+ var _isObject = _.isObject;
64
+ var _keys = _.keys;
65
+ var _isUndefined = _.isUndefined;
66
+ var _result = _.result;
67
+ var ceil = Math.ceil;
68
+ var floor = Math.floor;
69
+ var max = Math.max;
70
+
71
+ var BBColProto = Backbone.Collection.prototype;
72
+
73
+ function finiteInt (val, name) {
74
+ if (!_.isNumber(val) || _.isNaN(val) || !_.isFinite(val) || ~~val !== val) {
75
+ throw new TypeError("`" + name + "` must be a finite integer");
76
+ }
77
+ return val;
78
+ }
79
+
80
+ function queryStringToParams (qs) {
81
+ var kvp, k, v, ls, params = {}, decode = decodeURIComponent;
82
+ var kvps = qs.split('&');
83
+ for (var i = 0, l = kvps.length; i < l; i++) {
84
+ var param = kvps[i];
85
+ kvp = param.split('='), k = kvp[0], v = kvp[1] || true;
86
+ k = decode(k), v = decode(v), ls = params[k];
87
+ if (_isArray(ls)) ls.push(v);
88
+ else if (ls) params[k] = [ls, v];
89
+ else params[k] = v;
90
+ }
91
+ return params;
92
+ }
93
+
94
+ // hack to make sure the whatever event handlers for this event is run
95
+ // before func is, and the event handlers that func will trigger.
96
+ function runOnceAtLastHandler (col, event, func) {
97
+ var eventHandlers = col._events[event];
98
+ if (eventHandlers && eventHandlers.length) {
99
+ var lastHandler = eventHandlers[eventHandlers.length - 1];
100
+ var oldCallback = lastHandler.callback;
101
+ lastHandler.callback = function () {
102
+ try {
103
+ oldCallback.apply(this, arguments);
104
+ func();
105
+ }
106
+ catch (e) {
107
+ throw e;
108
+ }
109
+ finally {
110
+ lastHandler.callback = oldCallback;
111
+ }
112
+ };
113
+ }
114
+ else func();
115
+ }
116
+
117
+ var PARAM_TRIM_RE = /[\s'"]/g;
118
+ var URL_TRIM_RE = /[<>\s'"]/g;
119
+
120
+ /**
121
+ Drop-in replacement for Backbone.Collection. Supports server-side and
122
+ client-side pagination and sorting. Client-side mode also support fully
123
+ multi-directional synchronization of changes between pages.
124
+
125
+ @class Backbone.PageableCollection
126
+ @extends Backbone.Collection
127
+ */
128
+ var PageableCollection = Backbone.PageableCollection = Backbone.Collection.extend({
129
+
130
+ /**
131
+ The container object to store all pagination states.
132
+
133
+ You can override the default state by extending this class or specifying
134
+ them in an `options` hash to the constructor.
135
+
136
+ @property {Object} state
137
+
138
+ @property {0|1} [state.firstPage=1] The first page index. Set to 0 if
139
+ your server API uses 0-based indices. You should only override this value
140
+ during extension, initialization or reset by the server after
141
+ fetching. This value should be read only at other times.
142
+
143
+ @property {number} [state.lastPage=null] The last page index. This value
144
+ is __read only__ and it's calculated based on whether `firstPage` is 0 or
145
+ 1, during bootstrapping, fetching and resetting. Please don't change this
146
+ value under any circumstances.
147
+
148
+ @property {number} [state.currentPage=null] The current page index. You
149
+ should only override this value during extension, initialization or reset
150
+ by the server after fetching. This value should be read only at other
151
+ times. Can be a 0-based or 1-based index, depending on whether
152
+ `firstPage` is 0 or 1. If left as default, it will be set to `firstPage`
153
+ on initialization.
154
+
155
+ @property {number} [state.pageSize=25] How many records to show per
156
+ page. This value is __read only__ after initialization, if you want to
157
+ change the page size after initialization, you must call #setPageSize.
158
+
159
+ @property {number} [state.totalPages=null] How many pages there are. This
160
+ value is __read only__ and it is calculated from `totalRecords`.
161
+
162
+ @property {number} [state.totalRecords=null] How many records there
163
+ are. This value is __required__ under server mode. This value is optional
164
+ for client mode as the number will be the same as the number of models
165
+ during bootstrapping and during fetching, either supplied by the server
166
+ in the metadata, or calculated from the size of the response.
167
+
168
+ @property {string} [state.sortKey=null] The model attribute to use for
169
+ sorting.
170
+
171
+ @property {-1|0|1} [state.order=-1] The order to use for sorting. Specify
172
+ -1 for ascending order or 1 for descending order. If 0, no client side
173
+ sorting will be done and the order query parameter will not be sent to
174
+ the server during a fetch.
175
+ */
176
+ state: {
177
+ firstPage: 1,
178
+ lastPage: null,
179
+ currentPage: null,
180
+ pageSize: 25,
181
+ totalPages: null,
182
+ totalRecords: null,
183
+ sortKey: null,
184
+ order: -1
185
+ },
186
+
187
+ /**
188
+ @property {"server"|"client"|"infinite"} [mode="server"] The mode of
189
+ operations for this collection. `"server"` paginates on the server-side,
190
+ `"client"` paginates on the client-side and `"infinite"` paginates on the
191
+ server-side for APIs that do not support `totalRecords`.
192
+ */
193
+ mode: "server",
194
+
195
+ /**
196
+ A translation map to convert Backbone.PageableCollection state attributes
197
+ to the query parameters accepted by your server API.
198
+
199
+ You can override the default state by extending this class or specifying
200
+ them in `options.queryParams` object hash to the constructor.
201
+
202
+ @property {Object} queryParams
203
+ @property {string} [queryParams.currentPage="page"]
204
+ @property {string} [queryParams.pageSize="per_page"]
205
+ @property {string} [queryParams.totalPages="total_pages"]
206
+ @property {string} [queryParams.totalRecords="total_entries"]
207
+ @property {string} [queryParams.sortKey="sort_by"]
208
+ @property {string} [queryParams.order="order"]
209
+ @property {string} [queryParams.directions={"-1": "asc", "1": "desc"}] A
210
+ map for translating a Backbone.PageableCollection#state.order constant to
211
+ the ones your server API accepts.
212
+ */
213
+ queryParams: {
214
+ currentPage: "page",
215
+ pageSize: "per_page",
216
+ totalPages: "total_pages",
217
+ totalRecords: "total_entries",
218
+ sortKey: "sort_by",
219
+ order: "order",
220
+ directions: {
221
+ "-1": "asc",
222
+ "1": "desc"
223
+ }
224
+ },
225
+
226
+ /**
227
+ __CLIENT MODE ONLY__
228
+
229
+ This collection is the internal storage for the bootstrapped or fetched
230
+ models. You can use this if you want to operate on all the pages.
231
+
232
+ @property {Backbone.Collection} fullCollection
233
+ */
234
+
235
+ /**
236
+ Given a list of models or model attributues, bootstraps the full
237
+ collection in client mode or infinite mode, or just the page you want in
238
+ server mode.
239
+
240
+ If you want to initialize a collection to a different state than the
241
+ default, you can specify them in `options.state`. Any state parameters
242
+ supplied will be merged with the default. If you want to change the
243
+ default mapping from #state keys to your server API's query parameter
244
+ names, you can specifiy an object hash in `option.queryParams`. Likewise,
245
+ any mapping provided will be merged with the default. Lastly, all
246
+ Backbone.Collection constructor options are also accepted.
247
+
248
+ See:
249
+
250
+ - Backbone.PageableCollection#state
251
+ - Backbone.PageableCollection#queryParams
252
+ - [Backbone.Collection#initialize](http://backbonejs.org/#Collection-constructor)
253
+
254
+ @param {Array.<Object>} [models]
255
+
256
+ @param {Object} [options]
257
+
258
+ @param {function(*, *): number} [options.comparator] If specified, this
259
+ comparator is set to the current page under server mode, or the #fullCollection
260
+ otherwise.
261
+
262
+ @param {boolean} [options.full] If `false` and either a
263
+ `options.comparator` or `sortKey` is defined, the comparator is attached
264
+ to the current page. Default is `true` under client or infinite mode and
265
+ the comparator will be attached to the #fullCollection.
266
+
267
+ @param {Object} [options.state] The state attributes overriding the defaults.
268
+
269
+ @param {string} [options.state.sortKey] The model attribute to use for
270
+ sorting. If specified instead of `options.comparator`, a comparator will
271
+ be automatically created using this value, and optionally a sorting order
272
+ specified in `options.state.order`. The comparator is then attached to
273
+ the new collection instance.
274
+
275
+ @param {-1|1} [options.state.order] The order to use for sorting. Specify
276
+ -1 for ascending order and 1 for descending order.
277
+
278
+ @param {Object} [options.queryParam]
279
+ */
280
+ constructor: function (models, options) {
281
+
282
+ BBColProto.constructor.apply(this, arguments);
283
+
284
+ options = options || {};
285
+
286
+ var mode = this.mode = options.mode || this.mode || PageableProto.mode;
287
+
288
+ var queryParams = _extend({}, PageableProto.queryParams, this.queryParams,
289
+ options.queryParams || {});
290
+
291
+ queryParams.directions = _extend({},
292
+ PageableProto.queryParams.directions,
293
+ this.queryParams.directions,
294
+ queryParams.directions || {});
295
+
296
+ this.queryParams = queryParams;
297
+
298
+ var state = this.state = _extend({}, PageableProto.state, this.state,
299
+ options.state || {});
300
+
301
+ state.currentPage = state.currentPage == null ?
302
+ state.firstPage :
303
+ state.currentPage;
304
+
305
+ if (!_isArray(models)) models = models ? [models] : [];
306
+ models = models.slice();
307
+
308
+ if (mode != "server" && state.totalRecords == null && !_isEmpty(models)) {
309
+ state.totalRecords = models.length;
310
+ }
311
+
312
+ this.switchMode(mode, _extend({fetch: false,
313
+ resetState: false,
314
+ models: models}, options));
315
+
316
+ var comparator = options.comparator;
317
+
318
+ if (state.sortKey && !comparator) {
319
+ this.setSorting(state.sortKey, state.order, options);
320
+ }
321
+
322
+ if (mode != "server") {
323
+ var fullCollection = this.fullCollection;
324
+
325
+ if (comparator && options.full) {
326
+ this.comparator = null;
327
+ fullCollection.comparator = comparator;
328
+ }
329
+
330
+ if (options.full) fullCollection.sort();
331
+
332
+ // make sure the models in the current page and full collection have the
333
+ // same references
334
+ if (models && !_isEmpty(models)) {
335
+ this.reset(models, _extend({silent: true}, options));
336
+ this.getPage(state.currentPage);
337
+ models.splice.apply(models, [0, models.length].concat(this.models));
338
+ }
339
+ }
340
+
341
+ this._initState = _clone(this.state);
342
+ },
343
+
344
+ /**
345
+ Makes a Backbone.Collection that contains all the pages.
346
+
347
+ @private
348
+ @param {Array.<Object|Backbone.Model>} models
349
+ @param {Object} options Options for Backbone.Collection constructor.
350
+ @return {Backbone.Collection}
351
+ */
352
+ _makeFullCollection: function (models, options) {
353
+
354
+ var properties = ["url", "model", "sync", "comparator"];
355
+ var thisProto = this.constructor.prototype;
356
+ var i, length, prop;
357
+
358
+ var proto = {};
359
+ for (i = 0, length = properties.length; i < length; i++) {
360
+ prop = properties[i];
361
+ if (!_isUndefined(thisProto[prop])) {
362
+ proto[prop] = thisProto[prop];
363
+ }
364
+ }
365
+
366
+ var fullCollection = new (Backbone.Collection.extend(proto))(models, options);
367
+
368
+ for (i = 0, length = properties.length; i < length; i++) {
369
+ prop = properties[i];
370
+ if (this[prop] !== thisProto[prop]) {
371
+ fullCollection[prop] = this[prop];
372
+ }
373
+ }
374
+
375
+ return fullCollection;
376
+ },
377
+
378
+ /**
379
+ Factory method that returns a Backbone event handler that responses to
380
+ the `add`, `remove`, `reset`, and the `sort` events. The returned event
381
+ handler will synchronize the current page collection and the full
382
+ collection's models.
383
+
384
+ @private
385
+
386
+ @param {Backbone.PageableCollection} pageCol
387
+ @param {Backbone.Collection} fullCol
388
+
389
+ @return {function(string, Backbone.Model, Backbone.Collection, Object)}
390
+ Collection event handler
391
+ */
392
+ _makeCollectionEventHandler: function (pageCol, fullCol) {
393
+
394
+ return function collectionEventHandler (event, model, collection, options) {
395
+
396
+ var handlers = pageCol._handlers;
397
+ _each(_keys(handlers), function (event) {
398
+ var handler = handlers[event];
399
+ pageCol.off(event, handler);
400
+ fullCol.off(event, handler);
401
+ });
402
+
403
+ var state = _clone(pageCol.state);
404
+ var firstPage = state.firstPage;
405
+ var currentPage = firstPage === 0 ?
406
+ state.currentPage :
407
+ state.currentPage - 1;
408
+ var pageSize = state.pageSize;
409
+ var pageStart = currentPage * pageSize, pageEnd = pageStart + pageSize;
410
+
411
+ if (event == "add") {
412
+ var pageIndex, fullIndex, addAt, colToAdd, options = options || {};
413
+ if (collection == fullCol) {
414
+ fullIndex = fullCol.indexOf(model);
415
+ if (fullIndex >= pageStart && fullIndex < pageEnd) {
416
+ colToAdd = pageCol;
417
+ pageIndex = addAt = fullIndex - pageStart;
418
+ }
419
+ }
420
+ else {
421
+ pageIndex = pageCol.indexOf(model);
422
+ fullIndex = pageStart + pageIndex;
423
+ colToAdd = fullCol;
424
+ var addAt = !_isUndefined(options.at) ?
425
+ options.at + pageStart :
426
+ fullIndex;
427
+ }
428
+
429
+ if (!options.onRemove) {
430
+ ++state.totalRecords;
431
+ delete options.onRemove;
432
+ }
433
+
434
+ pageCol.state = pageCol._checkState(state);
435
+
436
+ if (colToAdd) {
437
+ colToAdd.add(model, _extend({}, options || {}, {at: addAt}));
438
+ var modelToRemove = pageIndex >= pageSize ?
439
+ model :
440
+ !_isUndefined(options.at) && addAt < pageEnd && pageCol.length > pageSize ?
441
+ pageCol.at(pageSize) :
442
+ null;
443
+ if (modelToRemove) {
444
+ runOnceAtLastHandler(collection, event, function () {
445
+ pageCol.remove(modelToRemove, {onAdd: true});
446
+ });
447
+ }
448
+ }
449
+ }
450
+
451
+ // remove the model from the other collection as well
452
+ if (event == "remove") {
453
+ if (!options.onAdd) {
454
+ // decrement totalRecords and update totalPages and lastPage
455
+ if (!--state.totalRecords) {
456
+ state.totalRecords = null;
457
+ state.totalPages = null;
458
+ }
459
+ else {
460
+ var totalPages = state.totalPages = ceil(state.totalRecords / pageSize);
461
+ state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages || firstPage;
462
+ if (state.currentPage > totalPages) state.currentPage = state.lastPage;
463
+ }
464
+ pageCol.state = pageCol._checkState(state);
465
+
466
+ var nextModel, removedIndex = options.index;
467
+ if (collection == pageCol) {
468
+ if (nextModel = fullCol.at(pageEnd)) {
469
+ runOnceAtLastHandler(pageCol, event, function () {
470
+ pageCol.push(nextModel, {onRemove: true});
471
+ });
472
+ }
473
+ fullCol.remove(model);
474
+ }
475
+ else if (removedIndex >= pageStart && removedIndex < pageEnd) {
476
+ if (nextModel = fullCol.at(pageEnd - 1)) {
477
+ runOnceAtLastHandler(pageCol, event, function() {
478
+ pageCol.push(nextModel, {onRemove: true});
479
+ });
480
+ }
481
+ pageCol.remove(model);
482
+ }
483
+ }
484
+ else delete options.onAdd;
485
+ }
486
+
487
+ if (event == "reset") {
488
+ options = collection;
489
+ collection = model;
490
+
491
+ // Reset that's not a result of getPage
492
+ if (collection == pageCol && options.from == null &&
493
+ options.to == null) {
494
+ var head = fullCol.models.slice(0, pageStart);
495
+ var tail = fullCol.models.slice(pageStart + pageCol.models.length);
496
+ fullCol.reset(head.concat(pageCol.models).concat(tail), options);
497
+ }
498
+ else if (collection == fullCol) {
499
+ if (!(state.totalRecords = fullCol.models.length)) {
500
+ state.totalRecords = null;
501
+ state.totalPages = null;
502
+ }
503
+ if (pageCol.mode == "client") {
504
+ state.lastPage = state.currentPage = state.firstPage;
505
+ }
506
+ pageCol.state = pageCol._checkState(state);
507
+ pageCol.reset(fullCol.models.slice(pageStart, pageEnd),
508
+ _extend({}, options, {parse: false}));
509
+ }
510
+ }
511
+
512
+ if (event == "sort") {
513
+ options = collection;
514
+ collection = model;
515
+ if (collection === fullCol) {
516
+ pageCol.reset(fullCol.models.slice(pageStart, pageEnd),
517
+ _extend({}, options, {parse: false}));
518
+ }
519
+ }
520
+
521
+ _each(_keys(handlers), function (event) {
522
+ var handler = handlers[event];
523
+ _each([pageCol, fullCol], function (col) {
524
+ col.on(event, handler);
525
+ var callbacks = col._events[event] || [];
526
+ callbacks.unshift(callbacks.pop());
527
+ });
528
+ });
529
+ };
530
+ },
531
+
532
+ /**
533
+ Sanity check this collection's pagination states. Only perform checks
534
+ when all the required pagination state values are defined and not null.
535
+ If `totalPages` is undefined or null, it is set to `totalRecords` /
536
+ `pageSize`. `lastPage` is set according to whether `firstPage` is 0 or 1
537
+ when no error occurs.
538
+
539
+ @private
540
+
541
+ @throws {TypeError} If `totalRecords`, `pageSize`, `currentPage` or
542
+ `firstPage` is not a finite integer.
543
+
544
+ @throws {RangeError} If `pageSize`, `currentPage` or `firstPage` is out
545
+ of bounds.
546
+
547
+ @return {Object} Returns the `state` object if no error was found.
548
+ */
549
+ _checkState: function (state) {
550
+
551
+ var mode = this.mode;
552
+ var links = this.links;
553
+ var totalRecords = state.totalRecords;
554
+ var pageSize = state.pageSize;
555
+ var currentPage = state.currentPage;
556
+ var firstPage = state.firstPage;
557
+ var totalPages = state.totalPages;
558
+
559
+ if (totalRecords != null && pageSize != null && currentPage != null &&
560
+ firstPage != null && (mode == "infinite" ? links : true)) {
561
+
562
+ totalRecords = finiteInt(totalRecords, "totalRecords");
563
+ pageSize = finiteInt(pageSize, "pageSize");
564
+ currentPage = finiteInt(currentPage, "currentPage");
565
+ firstPage = finiteInt(firstPage, "firstPage");
566
+
567
+ if (pageSize < 1) {
568
+ throw new RangeError("`pageSize` must be >= 1");
569
+ }
570
+
571
+ totalPages = state.totalPages = ceil(totalRecords / pageSize);
572
+
573
+ if (firstPage < 0 || firstPage > 1) {
574
+ throw new RangeError("`firstPage must be 0 or 1`");
575
+ }
576
+
577
+ state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
578
+
579
+ if (mode == "infinite") {
580
+ if (!links[currentPage + '']) {
581
+ throw new RangeError("No link found for page " + currentPage);
582
+ }
583
+ }
584
+ else if (currentPage < firstPage ||
585
+ (totalPages > 0 &&
586
+ (firstPage ? currentPage > totalPages : currentPage >= totalPages))) {
587
+ throw new RangeError("`currentPage` must be firstPage <= currentPage " +
588
+ (firstPage ? ">" : ">=") +
589
+ " totalPages if " + firstPage + "-based. Got " +
590
+ currentPage + '.');
591
+ }
592
+ }
593
+
594
+ return state;
595
+ },
596
+
597
+ /**
598
+ Change the page size of this collection.
599
+
600
+ Under most if not all circumstances, you should call this method to
601
+ change the page size of a pageable collection because it will keep the
602
+ pagination state sane. By default, the method will recalculate the
603
+ current page number to one that will retain the current page's models
604
+ when increasing the page size. When decreasing the page size, this method
605
+ will retain the last models to the current page that will fit into the
606
+ smaller page size.
607
+
608
+ If `options.first` is true, changing the page size will also reset the
609
+ current page back to the first page instead of trying to be smart.
610
+
611
+ For server mode operations, changing the page size will trigger a #fetch
612
+ and subsequently a `reset` event.
613
+
614
+ For client mode operations, changing the page size will `reset` the
615
+ current page by recalculating the current page boundary on the client
616
+ side.
617
+
618
+ If `options.fetch` is true, a fetch can be forced if the collection is in
619
+ client mode.
620
+
621
+ @param {number} pageSize The new page size to set to #state.
622
+ @param {Object} [options] {@link #fetch} options.
623
+ @param {boolean} [options.first=false] Reset the current page number to
624
+ the first page if `true`.
625
+ @param {boolean} [options.fetch] If `true`, force a fetch in client mode.
626
+
627
+ @throws {TypeError} If `pageSize` is not a finite integer.
628
+ @throws {RangeError} If `pageSize` is less than 1.
629
+
630
+ @chainable
631
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
632
+ from fetch or this.
633
+ */
634
+ setPageSize: function (pageSize, options) {
635
+ pageSize = finiteInt(pageSize, "pageSize");
636
+
637
+ options = options || {first: false};
638
+
639
+ var state = this.state;
640
+ var totalPages = ceil(state.totalRecords / pageSize);
641
+ var currentPage = totalPages ?
642
+ max(state.firstPage, floor(totalPages * state.currentPage / state.totalPages)) :
643
+ state.firstPage;
644
+
645
+ state = this.state = this._checkState(_extend({}, state, {
646
+ pageSize: pageSize,
647
+ currentPage: options.first ? state.firstPage : currentPage,
648
+ totalPages: totalPages
649
+ }));
650
+
651
+ return this.getPage(state.currentPage, _omit(options, ["first"]));
652
+ },
653
+
654
+ /**
655
+ Switching between client, server and infinite mode.
656
+
657
+ If switching from client to server mode, the #fullCollection is emptied
658
+ first and then deleted and a fetch is immediately issued for the current
659
+ page from the server. Pass `false` to `options.fetch` to skip fetching.
660
+
661
+ If switching to infinite mode, and if `options.models` is given for an
662
+ array of models, #links will be populated with a URL per page, using the
663
+ default URL for this collection.
664
+
665
+ If switching from server to client mode, all of the pages are immediately
666
+ refetched. If you have too many pages, you can pass `false` to
667
+ `options.fetch` to skip fetching.
668
+
669
+ If switching to any mode from infinite mode, the #links will be deleted.
670
+
671
+ @param {"server"|"client"|"infinite"} [mode] The mode to switch to.
672
+
673
+ @param {Object} [options]
674
+
675
+ @param {boolean} [options.fetch=true] If `false`, no fetching is done.
676
+
677
+ @param {boolean} [options.resetState=true] If 'false', the state is not
678
+ reset, but checked for sanity instead.
679
+
680
+ @chainable
681
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
682
+ from fetch or this if `options.fetch` is `false`.
683
+ */
684
+ switchMode: function (mode, options) {
685
+
686
+ if (!_contains(["server", "client", "infinite"], mode)) {
687
+ throw new TypeError('`mode` must be one of "server", "client" or "infinite"');
688
+ }
689
+
690
+ options = options || {fetch: true, resetState: true};
691
+
692
+ var state = this.state = options.resetState ?
693
+ _clone(this._initState) :
694
+ this._checkState(_extend({}, this.state));
695
+
696
+ this.mode = mode;
697
+
698
+ var self = this;
699
+ var fullCollection = this.fullCollection;
700
+ var handlers = this._handlers = this._handlers || {}, handler;
701
+ if (mode != "server" && !fullCollection) {
702
+ fullCollection = this._makeFullCollection(options.models || [], options);
703
+ fullCollection.pageableCollection = this;
704
+ this.fullCollection = fullCollection;
705
+ var allHandler = this._makeCollectionEventHandler(this, fullCollection);
706
+ _each(["add", "remove", "reset", "sort"], function (event) {
707
+ handlers[event] = handler = _.bind(allHandler, {}, event);
708
+ self.on(event, handler);
709
+ fullCollection.on(event, handler);
710
+ });
711
+ fullCollection.comparator = this._fullComparator;
712
+ }
713
+ else if (mode == "server" && fullCollection) {
714
+ _each(_keys(handlers), function (event) {
715
+ handler = handlers[event];
716
+ self.off(event, handler);
717
+ fullCollection.off(event, handler);
718
+ });
719
+ delete this._handlers;
720
+ this._fullComparator = fullCollection.comparator;
721
+ delete this.fullCollection;
722
+ }
723
+
724
+ if (mode == "infinite") {
725
+ var links = this.links = {};
726
+ var firstPage = state.firstPage;
727
+ var totalPages = ceil(state.totalRecords / state.pageSize);
728
+ var lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
729
+ for (var i = state.firstPage; i <= lastPage; i++) {
730
+ links[i] = this.url;
731
+ }
732
+ }
733
+ else if (this.links) delete this.links;
734
+
735
+ return options.fetch ?
736
+ this.fetch(_omit(options, "fetch", "resetState")) :
737
+ this;
738
+ },
739
+
740
+ /**
741
+ @return {boolean} `true` if this collection can page backward, `false`
742
+ otherwise.
743
+ */
744
+ hasPreviousPage: function () {
745
+ var state = this.state;
746
+ var currentPage = state.currentPage;
747
+ if (this.mode != "infinite") return currentPage > state.firstPage;
748
+ return !!this.links[currentPage - 1];
749
+ },
750
+
751
+ /**
752
+ @return {boolean} `true` if this collection can page forward, `false`
753
+ otherwise.
754
+ */
755
+ hasNextPage: function () {
756
+ var state = this.state;
757
+ var currentPage = this.state.currentPage;
758
+ if (this.mode != "infinite") return currentPage < state.lastPage;
759
+ return !!this.links[currentPage + 1];
760
+ },
761
+
762
+ /**
763
+ Fetch the first page in server mode, or reset the current page of this
764
+ collection to the first page in client or infinite mode.
765
+
766
+ @param {Object} options {@link #getPage} options.
767
+
768
+ @chainable
769
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
770
+ from fetch or this.
771
+ */
772
+ getFirstPage: function (options) {
773
+ return this.getPage("first", options);
774
+ },
775
+
776
+ /**
777
+ Fetch the previous page in server mode, or reset the current page of this
778
+ collection to the previous page in client or infinite mode.
779
+
780
+ @param {Object} options {@link #getPage} options.
781
+
782
+ @chainable
783
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
784
+ from fetch or this.
785
+ */
786
+ getPreviousPage: function (options) {
787
+ return this.getPage("prev", options);
788
+ },
789
+
790
+ /**
791
+ Fetch the next page in server mode, or reset the current page of this
792
+ collection to the next page in client mode.
793
+
794
+ @param {Object} options {@link #getPage} options.
795
+
796
+ @chainable
797
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
798
+ from fetch or this.
799
+ */
800
+ getNextPage: function (options) {
801
+ return this.getPage("next", options);
802
+ },
803
+
804
+ /**
805
+ Fetch the last page in server mode, or reset the current page of this
806
+ collection to the last page in client mode.
807
+
808
+ @param {Object} options {@link #getPage} options.
809
+
810
+ @chainable
811
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
812
+ from fetch or this.
813
+ */
814
+ getLastPage: function (options) {
815
+ return this.getPage("last", options);
816
+ },
817
+
818
+ /**
819
+ Given a page index, set #state.currentPage to that index. If this
820
+ collection is in server mode, fetch the page using the updated state,
821
+ otherwise, reset the current page of this collection to the page
822
+ specified by `index` in client mode. If `options.fetch` is true, a fetch
823
+ can be forced in client mode before resetting the current page. Under
824
+ infinite mode, if the index is less than the current page, a reset is
825
+ done as in client mode. If the index is greater than the current page
826
+ number, a fetch is made with the results **appended** to #fullCollection.
827
+ The current page will then be reset after fetching.
828
+
829
+ @param {number|string} index The page index to go to, or the page name to
830
+ look up from #links in infinite mode.
831
+ @param {Object} [options] {@link #fetch} options or
832
+ [reset](http://backbonejs.org/#Collection-reset) options for client mode
833
+ when `options.fetch` is `false`.
834
+ @param {boolean} [options.fetch=false] If true, force a {@link #fetch} in
835
+ client mode.
836
+
837
+ @throws {TypeError} If `index` is not a finite integer under server or
838
+ client mode, or does not yield a URL from #links under infinite mode.
839
+
840
+ @throws {RangeError} If `index` is out of bounds.
841
+
842
+ @chainable
843
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
844
+ from fetch or this.
845
+ */
846
+ getPage: function (index, options) {
847
+
848
+ var mode = this.mode, fullCollection = this.fullCollection;
849
+
850
+ options = options || {fetch: false};
851
+
852
+ var state = this.state,
853
+ firstPage = state.firstPage,
854
+ currentPage = state.currentPage,
855
+ lastPage = state.lastPage,
856
+ pageSize = state.pageSize;
857
+
858
+ var pageNum = index;
859
+ switch (index) {
860
+ case "first": pageNum = firstPage; break;
861
+ case "prev": pageNum = currentPage - 1; break;
862
+ case "next": pageNum = currentPage + 1; break;
863
+ case "last": pageNum = lastPage; break;
864
+ default: pageNum = finiteInt(index, "index");
865
+ }
866
+
867
+ this.state = this._checkState(_extend({}, state, {currentPage: pageNum}));
868
+
869
+ options.from = currentPage, options.to = pageNum;
870
+
871
+ var pageStart = (firstPage === 0 ? pageNum : pageNum - 1) * pageSize;
872
+ var pageModels = fullCollection && fullCollection.length ?
873
+ fullCollection.models.slice(pageStart, pageStart + pageSize) :
874
+ [];
875
+ if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) &&
876
+ !options.fetch) {
877
+ this.reset(pageModels, _omit(options, "fetch"));
878
+ return this;
879
+ }
880
+
881
+ if (mode == "infinite") options.url = this.links[pageNum];
882
+
883
+ return this.fetch(_omit(options, "fetch"));
884
+ },
885
+
886
+ /**
887
+ Fetch the page for the provided item offset in server mode, or reset the current page of this
888
+ collection to the page for the provided item offset in client mode.
889
+
890
+ @param {Object} options {@link #getPage} options.
891
+
892
+ @chainable
893
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
894
+ from fetch or this.
895
+ */
896
+ getPageByOffset: function (offset, options) {
897
+ if (offset < 0) {
898
+ throw new RangeError("`offset must be > 0`");
899
+ }
900
+ offset = finiteInt(offset);
901
+
902
+ var page = floor(offset / this.state.pageSize);
903
+ if (this.state.firstPage !== 0) page++;
904
+ if (page > this.state.lastPage) page = this.state.lastPage;
905
+ return this.getPage(page, options);
906
+ },
907
+
908
+ /**
909
+ Overidden to make `getPage` compatible with Zepto.
910
+
911
+ @param {string} method
912
+ @param {Backbone.Model|Backbone.Collection} model
913
+ @param {Object} [options]
914
+
915
+ @return {XMLHttpRequest}
916
+ */
917
+ sync: function (method, model, options) {
918
+ var self = this;
919
+ if (self.mode == "infinite") {
920
+ var success = options.success;
921
+ var currentPage = self.state.currentPage;
922
+ options.success = function (resp, status, xhr) {
923
+ var links = self.links;
924
+ var newLinks = self.parseLinks(resp, _extend({xhr: xhr}, options));
925
+ if (newLinks.first) links[self.state.firstPage] = newLinks.first;
926
+ if (newLinks.prev) links[currentPage - 1] = newLinks.prev;
927
+ if (newLinks.next) links[currentPage + 1] = newLinks.next;
928
+ if (success) success(resp, status, xhr);
929
+ };
930
+ }
931
+
932
+ return (BBColProto.sync || Backbone.sync).call(self, method, model, options);
933
+ },
934
+
935
+ /**
936
+ Parse pagination links from the server response. Only valid under
937
+ infinite mode.
938
+
939
+ Given a response body and a XMLHttpRequest object, extract pagination
940
+ links from them for infinite paging.
941
+
942
+ This default implementation parses the RFC 5988 `Link` header and extract
943
+ 3 links from it - `first`, `prev`, `next`. Any subclasses overriding this
944
+ method __must__ return an object hash having only the keys
945
+ above. However, simply returning a `next` link or an empty hash if there
946
+ are no more links should be enough for most implementations.
947
+
948
+ @param {*} resp The deserialized response body.
949
+ @param {Object} [options]
950
+ @param {XMLHttpRequest} [options.xhr] The XMLHttpRequest object for this
951
+ response.
952
+ @return {Object}
953
+ */
954
+ parseLinks: function (resp, options) {
955
+ var links = {};
956
+ var linkHeader = options.xhr.getResponseHeader("Link");
957
+ if (linkHeader) {
958
+ var relations = ["first", "prev", "next"];
959
+ _each(linkHeader.split(","), function (linkValue) {
960
+ var linkParts = linkValue.split(";");
961
+ var url = linkParts[0].replace(URL_TRIM_RE, '');
962
+ var params = linkParts.slice(1);
963
+ _each(params, function (param) {
964
+ var paramParts = param.split("=");
965
+ var key = paramParts[0].replace(PARAM_TRIM_RE, '');
966
+ var value = paramParts[1].replace(PARAM_TRIM_RE, '');
967
+ if (key == "rel" && _contains(relations, value)) links[value] = url;
968
+ });
969
+ });
970
+ }
971
+
972
+ return links;
973
+ },
974
+
975
+ /**
976
+ Parse server response data.
977
+
978
+ This default implementation assumes the response data is in one of two
979
+ structures:
980
+
981
+ [
982
+ {}, // Your new pagination state
983
+ [{}, ...] // An array of JSON objects
984
+ ]
985
+
986
+ Or,
987
+
988
+ [{}] // An array of JSON objects
989
+
990
+ The first structure is the preferred form because the pagination states
991
+ may have been updated on the server side, sending them down again allows
992
+ this collection to update its states. If the response has a pagination
993
+ state object, it is checked for errors.
994
+
995
+ The second structure is the
996
+ [Backbone.Collection#parse](http://backbonejs.org/#Collection-parse)
997
+ default.
998
+
999
+ **Note:** this method has been further simplified since 1.1.7. While
1000
+ existing #parse implementations will continue to work, new code is
1001
+ encouraged to override #parseState and #parseRecords instead.
1002
+
1003
+ @param {Object} resp The deserialized response data from the server.
1004
+ @param {Object} the options for the ajax request
1005
+
1006
+ @return {Array.<Object>} An array of model objects
1007
+ */
1008
+ parse: function (resp, options) {
1009
+ var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state), options);
1010
+ if (newState) this.state = this._checkState(_extend({}, this.state, newState));
1011
+ return this.parseRecords(resp, options);
1012
+ },
1013
+
1014
+ /**
1015
+ Parse server response for server pagination state updates. Not applicable
1016
+ under infinite mode.
1017
+
1018
+ This default implementation first checks whether the response has any
1019
+ state object as documented in #parse. If it exists, a state object is
1020
+ returned by mapping the server state keys to this pageable collection
1021
+ instance's query parameter keys using `queryParams`.
1022
+
1023
+ It is __NOT__ neccessary to return a full state object complete with all
1024
+ the mappings defined in #queryParams. Any state object resulted is merged
1025
+ with a copy of the current pageable collection state and checked for
1026
+ sanity before actually updating. Most of the time, simply providing a new
1027
+ `totalRecords` value is enough to trigger a full pagination state
1028
+ recalculation.
1029
+
1030
+ parseState: function (resp, queryParams, state, options) {
1031
+ return {totalRecords: resp.total_entries};
1032
+ }
1033
+
1034
+ If you want to use header fields use:
1035
+
1036
+ parseState: function (resp, queryParams, state, options) {
1037
+ return {totalRecords: options.xhr.getResponseHeader("X-total")};
1038
+ }
1039
+
1040
+ This method __MUST__ return a new state object instead of directly
1041
+ modifying the #state object. The behavior of directly modifying #state is
1042
+ undefined.
1043
+
1044
+ @param {Object} resp The deserialized response data from the server.
1045
+ @param {Object} queryParams A copy of #queryParams.
1046
+ @param {Object} state A copy of #state.
1047
+ @param {Object} [options] The options passed through from
1048
+ `parse`. (backbone >= 0.9.10 only)
1049
+
1050
+ @return {Object} A new (partial) state object.
1051
+ */
1052
+ parseState: function (resp, queryParams, state, options) {
1053
+ if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
1054
+
1055
+ var newState = _clone(state);
1056
+ var serverState = resp[0];
1057
+
1058
+ _each(_pairs(_omit(queryParams, "directions")), function (kvp) {
1059
+ var k = kvp[0], v = kvp[1];
1060
+ var serverVal = serverState[v];
1061
+ if (!_isUndefined(serverVal) && !_.isNull(serverVal)) newState[k] = serverState[v];
1062
+ });
1063
+
1064
+ if (serverState.order) {
1065
+ newState.order = _invert(queryParams.directions)[serverState.order] * 1;
1066
+ }
1067
+
1068
+ return newState;
1069
+ }
1070
+ },
1071
+
1072
+ /**
1073
+ Parse server response for an array of model objects.
1074
+
1075
+ This default implementation first checks whether the response has any
1076
+ state object as documented in #parse. If it exists, the array of model
1077
+ objects is assumed to be the second element, otherwise the entire
1078
+ response is returned directly.
1079
+
1080
+ @param {Object} resp The deserialized response data from the server.
1081
+ @param {Object} [options] The options passed through from the
1082
+ `parse`. (backbone >= 0.9.10 only)
1083
+
1084
+ @return {Array.<Object>} An array of model objects
1085
+ */
1086
+ parseRecords: function (resp, options) {
1087
+ if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
1088
+ return resp[1];
1089
+ }
1090
+
1091
+ return resp;
1092
+ },
1093
+
1094
+ /**
1095
+ Fetch a page from the server in server mode, or all the pages in client
1096
+ mode. Under infinite mode, the current page is refetched by default and
1097
+ then reset.
1098
+
1099
+ The query string is constructed by translating the current pagination
1100
+ state to your server API query parameter using #queryParams. The current
1101
+ page will reset after fetch.
1102
+
1103
+ @param {Object} [options] Accepts all
1104
+ [Backbone.Collection#fetch](http://backbonejs.org/#Collection-fetch)
1105
+ options.
1106
+
1107
+ @return {XMLHttpRequest}
1108
+ */
1109
+ fetch: function (options) {
1110
+
1111
+ options = options || {};
1112
+
1113
+ var state = this._checkState(this.state);
1114
+
1115
+ var mode = this.mode;
1116
+
1117
+ if (mode == "infinite" && !options.url) {
1118
+ options.url = this.links[state.currentPage];
1119
+ }
1120
+
1121
+ var data = options.data || {};
1122
+
1123
+ // dedup query params
1124
+ var url = _result(options, "url") || _result(this, "url") || '';
1125
+ var qsi = url.indexOf('?');
1126
+ if (qsi != -1) {
1127
+ _extend(data, queryStringToParams(url.slice(qsi + 1)));
1128
+ url = url.slice(0, qsi);
1129
+ }
1130
+
1131
+ options.url = url;
1132
+ options.data = data;
1133
+
1134
+ // map params except directions
1135
+ var queryParams = this.mode == "client" ?
1136
+ _pick(this.queryParams, "sortKey", "order") :
1137
+ _omit(_pick(this.queryParams, _keys(PageableProto.queryParams)),
1138
+ "directions");
1139
+
1140
+ var i, kvp, k, v, kvps = _pairs(queryParams), thisCopy = _clone(this);
1141
+ for (i = 0; i < kvps.length; i++) {
1142
+ kvp = kvps[i], k = kvp[0], v = kvp[1];
1143
+ v = _isFunction(v) ? v.call(thisCopy) : v;
1144
+ if (state[k] != null && v != null) {
1145
+ data[v] = state[k];
1146
+ }
1147
+ }
1148
+
1149
+ // fix up sorting parameters
1150
+ if (state.sortKey && state.order) {
1151
+ data[queryParams.order] = this.queryParams.directions[state.order + ""];
1152
+ }
1153
+ else if (!state.sortKey) delete data[queryParams.order];
1154
+
1155
+ // map extra query parameters
1156
+ var extraKvps = _pairs(_omit(this.queryParams,
1157
+ _keys(PageableProto.queryParams)));
1158
+ for (i = 0; i < extraKvps.length; i++) {
1159
+ kvp = extraKvps[i];
1160
+ v = kvp[1];
1161
+ v = _isFunction(v) ? v.call(thisCopy) : v;
1162
+ if (v != null) data[kvp[0]] = v;
1163
+ }
1164
+
1165
+ if (mode != "server") {
1166
+ var self = this, fullCol = this.fullCollection;
1167
+ var success = options.success;
1168
+ options.success = function (col, resp, opts) {
1169
+
1170
+ // make sure the caller's intent is obeyed
1171
+ opts = opts || {};
1172
+ if (_isUndefined(options.silent)) delete opts.silent;
1173
+ else opts.silent = options.silent;
1174
+
1175
+ var models = col.models;
1176
+ if (mode == "client") fullCol.reset(models, opts);
1177
+ else {
1178
+ fullCol.add(models, _extend({at: fullCol.length},
1179
+ _extend(opts, {parse: false})));
1180
+ self.trigger("reset", self, opts);
1181
+ }
1182
+
1183
+ if (success) success(col, resp, opts);
1184
+ };
1185
+
1186
+ // silent the first reset from backbone
1187
+ return BBColProto.fetch.call(this, _extend({}, options, {silent: true}));
1188
+ }
1189
+
1190
+ return BBColProto.fetch.call(this, options);
1191
+ },
1192
+
1193
+ /**
1194
+ Convenient method for making a `comparator` sorted by a model attribute
1195
+ identified by `sortKey` and ordered by `order`.
1196
+
1197
+ Like a Backbone.Collection, a Backbone.PageableCollection will maintain
1198
+ the __current page__ in sorted order on the client side if a `comparator`
1199
+ is attached to it. If the collection is in client mode, you can attach a
1200
+ comparator to #fullCollection to have all the pages reflect the global
1201
+ sorting order by specifying an option `full` to `true`. You __must__ call
1202
+ `sort` manually or #fullCollection.sort after calling this method to
1203
+ force a resort.
1204
+
1205
+ While you can use this method to sort the current page in server mode,
1206
+ the sorting order may not reflect the global sorting order due to the
1207
+ additions or removals of the records on the server since the last
1208
+ fetch. If you want the most updated page in a global sorting order, it is
1209
+ recommended that you set #state.sortKey and optionally #state.order, and
1210
+ then call #fetch.
1211
+
1212
+ @protected
1213
+
1214
+ @param {string} [sortKey=this.state.sortKey] See `state.sortKey`.
1215
+ @param {number} [order=this.state.order] See `state.order`.
1216
+ @param {(function(Backbone.Model, string): Object) | string} [sortValue] See #setSorting.
1217
+
1218
+ See [Backbone.Collection.comparator](http://backbonejs.org/#Collection-comparator).
1219
+ */
1220
+ _makeComparator: function (sortKey, order, sortValue) {
1221
+ var state = this.state;
1222
+
1223
+ sortKey = sortKey || state.sortKey;
1224
+ order = order || state.order;
1225
+
1226
+ if (!sortKey || !order) return;
1227
+
1228
+ if (!sortValue) sortValue = function (model, attr) {
1229
+ return model.get(attr);
1230
+ };
1231
+
1232
+ return function (left, right) {
1233
+ var l = sortValue(left, sortKey), r = sortValue(right, sortKey), t;
1234
+ if (order === 1) t = l, l = r, r = t;
1235
+ if (l === r) return 0;
1236
+ else if (l < r) return -1;
1237
+ return 1;
1238
+ };
1239
+ },
1240
+
1241
+ /**
1242
+ Adjusts the sorting for this pageable collection.
1243
+
1244
+ Given a `sortKey` and an `order`, sets `state.sortKey` and
1245
+ `state.order`. A comparator can be applied on the client side to sort in
1246
+ the order defined if `options.side` is `"client"`. By default the
1247
+ comparator is applied to the #fullCollection. Set `options.full` to
1248
+ `false` to apply a comparator to the current page under any mode. Setting
1249
+ `sortKey` to `null` removes the comparator from both the current page and
1250
+ the full collection.
1251
+
1252
+ If a `sortValue` function is given, it will be passed the `(model,
1253
+ sortKey)` arguments and is used to extract a value from the model during
1254
+ comparison sorts. If `sortValue` is not given, `model.get(sortKey)` is
1255
+ used for sorting.
1256
+
1257
+ @chainable
1258
+
1259
+ @param {string} sortKey See `state.sortKey`.
1260
+ @param {number} [order=this.state.order] See `state.order`.
1261
+ @param {Object} [options]
1262
+ @param {"server"|"client"} [options.side] By default, `"client"` if
1263
+ `mode` is `"client"`, `"server"` otherwise.
1264
+ @param {boolean} [options.full=true]
1265
+ @param {(function(Backbone.Model, string): Object) | string} [options.sortValue]
1266
+ */
1267
+ setSorting: function (sortKey, order, options) {
1268
+
1269
+ var state = this.state;
1270
+
1271
+ state.sortKey = sortKey;
1272
+ state.order = order = order || state.order;
1273
+
1274
+ var fullCollection = this.fullCollection;
1275
+
1276
+ var delComp = false, delFullComp = false;
1277
+
1278
+ if (!sortKey) delComp = delFullComp = true;
1279
+
1280
+ var mode = this.mode;
1281
+ options = _extend({side: mode == "client" ? mode : "server", full: true},
1282
+ options);
1283
+
1284
+ var comparator = this._makeComparator(sortKey, order, options.sortValue);
1285
+
1286
+ var full = options.full, side = options.side;
1287
+
1288
+ if (side == "client") {
1289
+ if (full) {
1290
+ if (fullCollection) fullCollection.comparator = comparator;
1291
+ delComp = true;
1292
+ }
1293
+ else {
1294
+ this.comparator = comparator;
1295
+ delFullComp = true;
1296
+ }
1297
+ }
1298
+ else if (side == "server" && !full) {
1299
+ this.comparator = comparator;
1300
+ }
1301
+
1302
+ if (delComp) this.comparator = null;
1303
+ if (delFullComp && fullCollection) fullCollection.comparator = null;
1304
+
1305
+ return this;
1306
+ }
1307
+
1308
+ });
1309
+
1310
+ var PageableProto = PageableCollection.prototype;
1311
+
1312
+ return PageableCollection;
1313
+
1314
+ }));
@@ -0,0 +1 @@
1
+ //= require js_stack/plugins/backbone/paginator/2.0.0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: js_stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomasz Pewiński
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-09 00:00:00.000000000 Z
12
+ date: 2014-05-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: haml_coffee_assets
@@ -121,6 +121,7 @@ files:
121
121
  - vendor/assets/javascripts/js_stack/plugins/backbone.deepmodel.js
122
122
  - vendor/assets/javascripts/js_stack/plugins/backbone.mutators.js
123
123
  - vendor/assets/javascripts/js_stack/plugins/backbone.pageable.js
124
+ - vendor/assets/javascripts/js_stack/plugins/backbone.paginator.js
124
125
  - vendor/assets/javascripts/js_stack/plugins/backbone.routefilter.js
125
126
  - vendor/assets/javascripts/js_stack/plugins/backbone.stickit.js
126
127
  - vendor/assets/javascripts/js_stack/plugins/backbone.validation.js
@@ -131,6 +132,7 @@ files:
131
132
  - vendor/assets/javascripts/js_stack/plugins/backbone/mutators/0.4.2.js
132
133
  - vendor/assets/javascripts/js_stack/plugins/backbone/pageable/1.3.2.js
133
134
  - vendor/assets/javascripts/js_stack/plugins/backbone/pageable/1.4.8.js
135
+ - vendor/assets/javascripts/js_stack/plugins/backbone/paginator/2.0.0.js
134
136
  - vendor/assets/javascripts/js_stack/plugins/backbone/routefilter/0.2.1.js
135
137
  - vendor/assets/javascripts/js_stack/plugins/backbone/stickit/0.6.3.js
136
138
  - vendor/assets/javascripts/js_stack/plugins/backbone/stickit/0.7.0.js