helios_aim 0.2.2
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 +15 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +173 -0
- data/LICENSE +19 -0
- data/README.md +363 -0
- data/Rakefile +10 -0
- data/bin/helios +20 -0
- data/helios_aim.gemspec +47 -0
- data/lib/helios.rb +27 -0
- data/lib/helios/backend.rb +64 -0
- data/lib/helios/backend/data.rb +40 -0
- data/lib/helios/backend/in-app-purchase.rb +43 -0
- data/lib/helios/backend/newsstand.rb +97 -0
- data/lib/helios/backend/passbook.rb +41 -0
- data/lib/helios/backend/push-notification.rb +110 -0
- data/lib/helios/commands.rb +4 -0
- data/lib/helios/commands/console.rb +21 -0
- data/lib/helios/commands/link.rb +19 -0
- data/lib/helios/commands/new.rb +76 -0
- data/lib/helios/commands/server.rb +52 -0
- data/lib/helios/frontend.rb +66 -0
- data/lib/helios/frontend/fonts/icons.eot +0 -0
- data/lib/helios/frontend/fonts/icons.ttf +0 -0
- data/lib/helios/frontend/fonts/icons.woff +0 -0
- data/lib/helios/frontend/images/bg.jpg +0 -0
- data/lib/helios/frontend/images/helios.svg +33 -0
- data/lib/helios/frontend/javascripts/helios.coffee +72 -0
- data/lib/helios/frontend/javascripts/helios/collections.coffee +99 -0
- data/lib/helios/frontend/javascripts/helios/models.coffee +23 -0
- data/lib/helios/frontend/javascripts/helios/router.coffee +50 -0
- data/lib/helios/frontend/javascripts/helios/views.coffee +307 -0
- data/lib/helios/frontend/javascripts/vendor/backbone.datagrid.js +662 -0
- data/lib/helios/frontend/javascripts/vendor/backbone.js +1487 -0
- data/lib/helios/frontend/javascripts/vendor/backbone.paginator.js +1046 -0
- data/lib/helios/frontend/javascripts/vendor/codemirror.javascript.js +411 -0
- data/lib/helios/frontend/javascripts/vendor/codemirror.js +3047 -0
- data/lib/helios/frontend/javascripts/vendor/date.js +104 -0
- data/lib/helios/frontend/javascripts/vendor/foundation.js +331 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.alerts.js +50 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.clearing.js +478 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.cookie.js +74 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.dropdown.js +122 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.forms.js +403 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.joyride.js +613 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.magellan.js +130 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.orbit.js +355 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.placeholder.js +159 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.reveal.js +272 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.section.js +183 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.tooltips.js +195 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.topbar.js +208 -0
- data/lib/helios/frontend/javascripts/vendor/jquery.js +9597 -0
- data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload-ui.js +807 -0
- data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload.js +1201 -0
- data/lib/helios/frontend/javascripts/vendor/jquery/jquery.ui.widget.js +530 -0
- data/lib/helios/frontend/javascripts/vendor/linkheaders.js +117 -0
- data/lib/helios/frontend/javascripts/vendor/underscore.js +1227 -0
- data/lib/helios/frontend/stylesheets/_codemirror.sass +219 -0
- data/lib/helios/frontend/stylesheets/_fonts.sass +80 -0
- data/lib/helios/frontend/stylesheets/_iphone.sass +141 -0
- data/lib/helios/frontend/stylesheets/_settings.scss +989 -0
- data/lib/helios/frontend/stylesheets/screen.sass +187 -0
- data/lib/helios/frontend/templates/data/entities.jst.tpl +11 -0
- data/lib/helios/frontend/templates/in-app-purchase/receipts.jst.tpl +11 -0
- data/lib/helios/frontend/templates/navigation.jst.tpl +31 -0
- data/lib/helios/frontend/templates/newsstand/issues.jst.tpl +16 -0
- data/lib/helios/frontend/templates/newsstand/new.jst.tpl +28 -0
- data/lib/helios/frontend/templates/passbook/passes.jst.tpl +11 -0
- data/lib/helios/frontend/templates/push-notification/compose.jst.tpl +70 -0
- data/lib/helios/frontend/templates/push-notification/devices.jst.tpl +17 -0
- data/lib/helios/frontend/views/index.haml +22 -0
- data/lib/helios/templates/.env.erb +1 -0
- data/lib/helios/templates/.gitignore +3 -0
- data/lib/helios/templates/Gemfile.erb +10 -0
- data/lib/helios/templates/Procfile.erb +1 -0
- data/lib/helios/templates/README.md.erb +4 -0
- data/lib/helios/templates/config.ru.erb +11 -0
- data/lib/helios/version.rb +3 -0
- metadata +475 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
/*! backbone.paginator - v0.5.1-dev - 3/19/2013
|
|
2
|
+
* http://github.com/addyosmani/backbone.paginator
|
|
3
|
+
* Copyright (c) 2013 Addy Osmani; Licensed MIT */
|
|
4
|
+
|
|
5
|
+
Backbone.Paginator = (function ( Backbone, _, $ ) {
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
var Paginator = {};
|
|
9
|
+
Paginator.version = "0.6.0";
|
|
10
|
+
|
|
11
|
+
// @name: clientPager
|
|
12
|
+
//
|
|
13
|
+
// @tagline: Paginator for client-side data
|
|
14
|
+
//
|
|
15
|
+
// @description:
|
|
16
|
+
// This paginator is responsible for providing pagination
|
|
17
|
+
// and sort capabilities for a single payload of data
|
|
18
|
+
// we wish to paginate by the UI for easier browsering.
|
|
19
|
+
//
|
|
20
|
+
Paginator.clientPager = Backbone.Collection.extend({
|
|
21
|
+
|
|
22
|
+
// DEFAULTS FOR SORTING & FILTERING
|
|
23
|
+
useDiacriticsPlugin: true, // use diacritics plugin if available
|
|
24
|
+
useLevenshteinPlugin: true, // use levenshtein plugin if available
|
|
25
|
+
sortColumn: "",
|
|
26
|
+
sortDirection: "desc",
|
|
27
|
+
lastSortColumn: "",
|
|
28
|
+
fieldFilterRules: [],
|
|
29
|
+
lastFieldFilterRules: [],
|
|
30
|
+
filterFields: "",
|
|
31
|
+
filterExpression: "",
|
|
32
|
+
lastFilterExpression: "",
|
|
33
|
+
|
|
34
|
+
//DEFAULT PAGINATOR UI VALUES
|
|
35
|
+
defaults_ui: {
|
|
36
|
+
firstPage: 0,
|
|
37
|
+
currentPage: 1,
|
|
38
|
+
perPage: 5,
|
|
39
|
+
totalPages: 10,
|
|
40
|
+
pagesInRange: 4
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Default values used when sorting and/or filtering.
|
|
44
|
+
initialize: function(){
|
|
45
|
+
//LISTEN FOR ADD & REMOVE EVENTS THEN REMOVE MODELS FROM ORGINAL MODELS
|
|
46
|
+
this.on('add', this.addModel, this);
|
|
47
|
+
this.on('remove', this.removeModel, this);
|
|
48
|
+
|
|
49
|
+
// SET DEFAULT VALUES (ALLOWS YOU TO POPULATE PAGINATOR MAUNALLY)
|
|
50
|
+
this.setDefaults();
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
setDefaults: function() {
|
|
55
|
+
// SET DEFAULT UI SETTINGS
|
|
56
|
+
var options = _.defaults(this.paginator_ui, this.defaults_ui);
|
|
57
|
+
|
|
58
|
+
//UPDATE GLOBAL UI SETTINGS
|
|
59
|
+
_.defaults(this, options);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
addModel: function(model) {
|
|
63
|
+
this.origModels.push(model);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
removeModel: function(model) {
|
|
67
|
+
var index = _.indexOf(this.origModels, model);
|
|
68
|
+
|
|
69
|
+
this.origModels.splice(index, 1);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
sync: function ( method, model, options ) {
|
|
73
|
+
var self = this;
|
|
74
|
+
|
|
75
|
+
// SET DEFAULT VALUES
|
|
76
|
+
this.setDefaults();
|
|
77
|
+
|
|
78
|
+
// Some values could be functions, let's make sure
|
|
79
|
+
// to change their scope too and run them
|
|
80
|
+
var queryAttributes = {};
|
|
81
|
+
_.each(_.result(self, "server_api"), function(value, key){
|
|
82
|
+
if( _.isFunction(value) ) {
|
|
83
|
+
value = _.bind(value, self);
|
|
84
|
+
value = value();
|
|
85
|
+
}
|
|
86
|
+
queryAttributes[key] = value;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
var queryOptions = _.clone(self.paginator_core);
|
|
90
|
+
_.each(queryOptions, function(value, key){
|
|
91
|
+
if( _.isFunction(value) ) {
|
|
92
|
+
value = _.bind(value, self);
|
|
93
|
+
value = value();
|
|
94
|
+
}
|
|
95
|
+
queryOptions[key] = value;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Create default values if no others are specified
|
|
99
|
+
queryOptions = _.defaults(queryOptions, {
|
|
100
|
+
timeout: 25000,
|
|
101
|
+
cache: false,
|
|
102
|
+
type: 'GET',
|
|
103
|
+
dataType: 'jsonp'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
queryOptions = _.extend(queryOptions, {
|
|
107
|
+
data: decodeURIComponent($.param(queryAttributes)),
|
|
108
|
+
processData: false,
|
|
109
|
+
url: _.result(queryOptions, 'url')
|
|
110
|
+
}, options);
|
|
111
|
+
|
|
112
|
+
var bbVer = Backbone.VERSION.split('.');
|
|
113
|
+
var oldSuccessFormat = (parseInt(bbVer[0], 10) === 0 &&
|
|
114
|
+
parseInt(bbVer[1], 10) === 9 &&
|
|
115
|
+
parseInt(bbVer[2], 10) <= 9);
|
|
116
|
+
|
|
117
|
+
var success = queryOptions.success;
|
|
118
|
+
queryOptions.success = function ( resp, status, xhr ) {
|
|
119
|
+
if ( success ) {
|
|
120
|
+
// This is to keep compatibility with Backbone older than 0.9.10
|
|
121
|
+
if (oldSuccessFormat) {
|
|
122
|
+
success( resp, status, xhr );
|
|
123
|
+
} else {
|
|
124
|
+
success( model, resp, queryOptions );
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if ( model && model.trigger ) {
|
|
128
|
+
model.trigger( 'sync', model, resp, queryOptions );
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
var error = queryOptions.error;
|
|
133
|
+
queryOptions.error = function ( xhr ) {
|
|
134
|
+
if ( error ) {
|
|
135
|
+
error( model, xhr, queryOptions );
|
|
136
|
+
}
|
|
137
|
+
if ( model && model.trigger ) {
|
|
138
|
+
model.trigger( 'error', model, xhr, queryOptions );
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
var xhr = queryOptions.xhr = $.ajax( queryOptions );
|
|
143
|
+
if ( model && model.trigger ) {
|
|
144
|
+
model.trigger('request', model, xhr, queryOptions);
|
|
145
|
+
}
|
|
146
|
+
return xhr;
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
nextPage: function (options) {
|
|
150
|
+
if(this.currentPage < this.information.totalPages) {
|
|
151
|
+
this.currentPage = ++this.currentPage;
|
|
152
|
+
this.pager(options);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
previousPage: function (options) {
|
|
157
|
+
if(this.currentPage > 1) {
|
|
158
|
+
this.currentPage = --this.currentPage;
|
|
159
|
+
this.pager(options);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
goTo: function ( page, options ) {
|
|
164
|
+
if(page !== undefined){
|
|
165
|
+
this.currentPage = parseInt(page, 10);
|
|
166
|
+
this.pager(options);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
howManyPer: function ( perPage ) {
|
|
171
|
+
if(perPage !== undefined){
|
|
172
|
+
var lastPerPage = this.perPage;
|
|
173
|
+
this.perPage = parseInt(perPage, 10);
|
|
174
|
+
this.currentPage = Math.ceil( ( lastPerPage * ( this.currentPage - 1 ) + 1 ) / perPage);
|
|
175
|
+
this.pager();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// setSort is used to sort the current model. After
|
|
181
|
+
// passing 'column', which is the model's field you want
|
|
182
|
+
// to filter and 'direction', which is the direction
|
|
183
|
+
// desired for the ordering ('asc' or 'desc'), pager()
|
|
184
|
+
// and info() will be called automatically.
|
|
185
|
+
setSort: function ( column, direction ) {
|
|
186
|
+
if(column !== undefined && direction !== undefined){
|
|
187
|
+
this.lastSortColumn = this.sortColumn;
|
|
188
|
+
this.sortColumn = column;
|
|
189
|
+
this.sortDirection = direction;
|
|
190
|
+
this.pager();
|
|
191
|
+
this.info();
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// setFieldFilter is used to filter each value of each model
|
|
196
|
+
// according to `rules` that you pass as argument.
|
|
197
|
+
// Example: You have a collection of books with 'release year' and 'author'.
|
|
198
|
+
// You can filter only the books that were released between 1999 and 2003
|
|
199
|
+
// And then you can add another `rule` that will filter those books only to
|
|
200
|
+
// authors who's name start with 'A'.
|
|
201
|
+
setFieldFilter: function ( fieldFilterRules ) {
|
|
202
|
+
if( !_.isEmpty( fieldFilterRules ) ) {
|
|
203
|
+
this.lastFieldFilterRules = this.fieldFilterRules;
|
|
204
|
+
this.fieldFilterRules = fieldFilterRules;
|
|
205
|
+
this.pager();
|
|
206
|
+
this.info();
|
|
207
|
+
// if all the filters are removed, we should save the last filter
|
|
208
|
+
// and then let the list reset to it's original state.
|
|
209
|
+
} else {
|
|
210
|
+
this.lastFieldFilterRules = this.fieldFilterRules;
|
|
211
|
+
this.fieldFilterRules = '';
|
|
212
|
+
this.pager();
|
|
213
|
+
this.info();
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// doFakeFieldFilter can be used to get the number of models that will remain
|
|
218
|
+
// after calling setFieldFilter with a filter rule(s)
|
|
219
|
+
doFakeFieldFilter: function ( rules ) {
|
|
220
|
+
if( !_.isEmpty( rules ) ) {
|
|
221
|
+
var testModels = this.origModels;
|
|
222
|
+
if (testModels === undefined) {
|
|
223
|
+
testModels = this.models;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
testModels = this._fieldFilter(testModels, rules);
|
|
227
|
+
|
|
228
|
+
// To comply with current behavior, also filter by any previously defined setFilter rules.
|
|
229
|
+
if ( this.filterExpression !== "" ) {
|
|
230
|
+
testModels = this._filter(testModels, this.filterFields, this.filterExpression);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Return size
|
|
234
|
+
return testModels.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// setFilter is used to filter the current model. After
|
|
240
|
+
// passing 'fields', which can be a string referring to
|
|
241
|
+
// the model's field, an array of strings representing
|
|
242
|
+
// each of the model's fields or an object with the name
|
|
243
|
+
// of the model's field(s) and comparing options (see docs)
|
|
244
|
+
// you wish to filter by and
|
|
245
|
+
// 'filter', which is the word or words you wish to
|
|
246
|
+
// filter by, pager() and info() will be called automatically.
|
|
247
|
+
setFilter: function ( fields, filter ) {
|
|
248
|
+
if( fields !== undefined && filter !== undefined ){
|
|
249
|
+
this.filterFields = fields;
|
|
250
|
+
this.lastFilterExpression = this.filterExpression;
|
|
251
|
+
this.filterExpression = filter;
|
|
252
|
+
this.pager();
|
|
253
|
+
this.info();
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// doFakeFilter can be used to get the number of models that will
|
|
258
|
+
// remain after calling setFilter with a `fields` and `filter` args.
|
|
259
|
+
doFakeFilter: function ( fields, filter ) {
|
|
260
|
+
if( fields !== undefined && filter !== undefined ){
|
|
261
|
+
var testModels = this.origModels;
|
|
262
|
+
if (testModels === undefined) {
|
|
263
|
+
testModels = this.models;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// To comply with current behavior, first filter by any previously defined setFieldFilter rules.
|
|
267
|
+
if ( !_.isEmpty( this.fieldFilterRules ) ) {
|
|
268
|
+
testModels = this._fieldFilter(testModels, this.fieldFilterRules);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
testModels = this._filter(testModels, fields, filter);
|
|
272
|
+
|
|
273
|
+
// Return size
|
|
274
|
+
return testModels.length;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
// pager is used to sort, filter and show the data
|
|
280
|
+
// you expect the library to display.
|
|
281
|
+
pager: function (options) {
|
|
282
|
+
var self = this,
|
|
283
|
+
disp = this.perPage,
|
|
284
|
+
start = (self.currentPage - 1) * disp,
|
|
285
|
+
stop = start + disp;
|
|
286
|
+
// Saving the original models collection is important
|
|
287
|
+
// as we could need to sort or filter, and we don't want
|
|
288
|
+
// to loose the data we fetched from the server.
|
|
289
|
+
if (self.origModels === undefined) {
|
|
290
|
+
self.origModels = self.models;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
self.models = self.origModels.slice();
|
|
294
|
+
|
|
295
|
+
// Check if sorting was set using setSort.
|
|
296
|
+
if ( this.sortColumn !== "" ) {
|
|
297
|
+
self.models = self._sort(self.models, this.sortColumn, this.sortDirection);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check if field-filtering was set using setFieldFilter
|
|
301
|
+
if ( !_.isEmpty( this.fieldFilterRules ) ) {
|
|
302
|
+
self.models = self._fieldFilter(self.models, this.fieldFilterRules);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check if filtering was set using setFilter.
|
|
306
|
+
if ( this.filterExpression !== "" ) {
|
|
307
|
+
self.models = self._filter(self.models, this.filterFields, this.filterExpression);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// If the sorting or the filtering was changed go to the first page
|
|
311
|
+
if ( this.lastSortColumn !== this.sortColumn || this.lastFilterExpression !== this.filterExpression || !_.isEqual(this.fieldFilterRules, this.lastFieldFilterRules) ) {
|
|
312
|
+
start = 0;
|
|
313
|
+
stop = start + disp;
|
|
314
|
+
self.currentPage = 1;
|
|
315
|
+
|
|
316
|
+
this.lastSortColumn = this.sortColumn;
|
|
317
|
+
this.lastFieldFilterRules = this.fieldFilterRules;
|
|
318
|
+
this.lastFilterExpression = this.filterExpression;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// We need to save the sorted and filtered models collection
|
|
322
|
+
// because we'll use that sorted and filtered collection in info().
|
|
323
|
+
self.sortedAndFilteredModels = self.models.slice();
|
|
324
|
+
self.info();
|
|
325
|
+
self.reset(self.models.slice(start, stop));
|
|
326
|
+
|
|
327
|
+
// This is somewhat of a hack to get all the nextPage, prevPage, and goTo methods
|
|
328
|
+
// to work with a success callback (as in the requestPager). Realistically there is no failure case here,
|
|
329
|
+
// but maybe we could catch exception and trigger a failure callback?
|
|
330
|
+
_.result(options, 'success');
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
// The actual place where the collection is sorted.
|
|
334
|
+
// Check setSort for arguments explicacion.
|
|
335
|
+
_sort: function ( models, sort, direction ) {
|
|
336
|
+
models = models.sort(function (a, b) {
|
|
337
|
+
var ac = a.get(sort),
|
|
338
|
+
bc = b.get(sort);
|
|
339
|
+
|
|
340
|
+
if ( _.isUndefined(ac) || _.isUndefined(bc) || ac === null || bc === null ) {
|
|
341
|
+
return 0;
|
|
342
|
+
} else {
|
|
343
|
+
/* Make sure that both ac and bc are lowercase strings.
|
|
344
|
+
* .toString() first so we don't have to worry if ac or bc
|
|
345
|
+
* have other String-only methods.
|
|
346
|
+
*/
|
|
347
|
+
ac = ac.toString().toLowerCase();
|
|
348
|
+
bc = bc.toString().toLowerCase();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (direction === 'desc') {
|
|
352
|
+
|
|
353
|
+
// We need to know if there aren't any non-number characters
|
|
354
|
+
// and that there are numbers-only characters and maybe a dot
|
|
355
|
+
// if we have a float.
|
|
356
|
+
// Oh, also a '-' for negative numbers!
|
|
357
|
+
if((!ac.match(/[^\-\d\.]/) && ac.match(/-?[\d\.]+/)) &&
|
|
358
|
+
(!bc.match(/[^\-\d\.]/) && bc.match(/-?[\d\.]+/))
|
|
359
|
+
){
|
|
360
|
+
|
|
361
|
+
if( (ac - 0) < (bc - 0) ) {
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
|
364
|
+
if( (ac - 0) > (bc - 0) ) {
|
|
365
|
+
return -1;
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
if (ac < bc) {
|
|
369
|
+
return 1;
|
|
370
|
+
}
|
|
371
|
+
if (ac > bc) {
|
|
372
|
+
return -1;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
} else {
|
|
377
|
+
|
|
378
|
+
//Same as the regexp check in the 'if' part.
|
|
379
|
+
if((!ac.match(/[^\-\d\.]/) && ac.match(/-?[\d\.]+/)) &&
|
|
380
|
+
(!bc.match(/[^\-\d\.]/) && bc.match(/-?[\d\.]+/))
|
|
381
|
+
){
|
|
382
|
+
if( (ac - 0) < (bc - 0) ) {
|
|
383
|
+
return -1;
|
|
384
|
+
}
|
|
385
|
+
if( (ac - 0) > (bc - 0) ) {
|
|
386
|
+
return 1;
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
if (ac < bc) {
|
|
390
|
+
return -1;
|
|
391
|
+
}
|
|
392
|
+
if (ac > bc) {
|
|
393
|
+
return 1;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (a.cid && b.cid){
|
|
400
|
+
var aId = a.cid,
|
|
401
|
+
bId = b.cid;
|
|
402
|
+
|
|
403
|
+
if (aId < bId) {
|
|
404
|
+
return -1;
|
|
405
|
+
}
|
|
406
|
+
if (aId > bId) {
|
|
407
|
+
return 1;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return 0;
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
return models;
|
|
415
|
+
},
|
|
416
|
+
|
|
417
|
+
// The actual place where the collection is field-filtered.
|
|
418
|
+
// Check setFieldFilter for arguments explicacion.
|
|
419
|
+
_fieldFilter: function( models, rules ) {
|
|
420
|
+
|
|
421
|
+
// Check if there are any rules
|
|
422
|
+
if ( _.isEmpty(rules) ) {
|
|
423
|
+
return models;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
var filteredModels = [];
|
|
427
|
+
|
|
428
|
+
// Iterate over each rule
|
|
429
|
+
_.each(models, function(model){
|
|
430
|
+
|
|
431
|
+
var should_push = true;
|
|
432
|
+
|
|
433
|
+
// Apply each rule to each model in the collection
|
|
434
|
+
_.each(rules, function(rule){
|
|
435
|
+
|
|
436
|
+
// Don't go inside the switch if we're already sure that the model won't be included in the results
|
|
437
|
+
if( !should_push ){
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
should_push = false;
|
|
442
|
+
|
|
443
|
+
// The field's value will be passed to a custom function, which should
|
|
444
|
+
// return true (if model should be included) or false (model should be ignored)
|
|
445
|
+
if(rule.type === "function"){
|
|
446
|
+
var f = _.wrap(rule.value, function(func){
|
|
447
|
+
return func( model.get(rule.field) );
|
|
448
|
+
});
|
|
449
|
+
if( f() ){
|
|
450
|
+
should_push = true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// The field's value is required to be non-empty
|
|
454
|
+
}else if(rule.type === "required"){
|
|
455
|
+
if( !_.isEmpty( model.get(rule.field).toString() ) ) {
|
|
456
|
+
should_push = true;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// The field's value is required to be greater tan N (numbers only)
|
|
460
|
+
}else if(rule.type === "min"){
|
|
461
|
+
if( !_.isNaN( Number( model.get(rule.field) ) ) &&
|
|
462
|
+
!_.isNaN( Number( rule.value ) ) &&
|
|
463
|
+
Number( model.get(rule.field) ) >= Number( rule.value ) ) {
|
|
464
|
+
should_push = true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// The field's value is required to be smaller tan N (numbers only)
|
|
468
|
+
}else if(rule.type === "max"){
|
|
469
|
+
if( !_.isNaN( Number( model.get(rule.field) ) ) &&
|
|
470
|
+
!_.isNaN( Number( rule.value ) ) &&
|
|
471
|
+
Number( model.get(rule.field) ) <= Number( rule.value ) ) {
|
|
472
|
+
should_push = true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// The field's value is required to be between N and M (numbers only)
|
|
476
|
+
}else if(rule.type === "range"){
|
|
477
|
+
if( !_.isNaN( Number( model.get(rule.field) ) ) &&
|
|
478
|
+
_.isObject( rule.value ) &&
|
|
479
|
+
!_.isNaN( Number( rule.value.min ) ) &&
|
|
480
|
+
!_.isNaN( Number( rule.value.max ) ) &&
|
|
481
|
+
Number( model.get(rule.field) ) >= Number( rule.value.min ) &&
|
|
482
|
+
Number( model.get(rule.field) ) <= Number( rule.value.max ) ) {
|
|
483
|
+
should_push = true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// The field's value is required to be more than N chars long
|
|
487
|
+
}else if(rule.type === "minLength"){
|
|
488
|
+
if( model.get(rule.field).toString().length >= rule.value ) {
|
|
489
|
+
should_push = true;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// The field's value is required to be no more than N chars long
|
|
493
|
+
}else if(rule.type === "maxLength"){
|
|
494
|
+
if( model.get(rule.field).toString().length <= rule.value ) {
|
|
495
|
+
should_push = true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// The field's value is required to be more than N chars long and no more than M chars long
|
|
499
|
+
}else if(rule.type === "rangeLength"){
|
|
500
|
+
if( _.isObject( rule.value ) &&
|
|
501
|
+
!_.isNaN( Number( rule.value.min ) ) &&
|
|
502
|
+
!_.isNaN( Number( rule.value.max ) ) &&
|
|
503
|
+
model.get(rule.field).toString().length >= rule.value.min &&
|
|
504
|
+
model.get(rule.field).toString().length <= rule.value.max ) {
|
|
505
|
+
should_push = true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// The field's value is required to be equal to one of the values in rules.value
|
|
509
|
+
}else if(rule.type === "oneOf"){
|
|
510
|
+
if( _.isArray( rule.value ) &&
|
|
511
|
+
_.include( rule.value, model.get(rule.field) ) ) {
|
|
512
|
+
should_push = true;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// The field's value is required to be equal to the value in rules.value
|
|
516
|
+
}else if(rule.type === "equalTo"){
|
|
517
|
+
if( rule.value === model.get(rule.field) ) {
|
|
518
|
+
should_push = true;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
}else if(rule.type === "containsAllOf"){
|
|
522
|
+
if( _.isArray( rule.value ) &&
|
|
523
|
+
_.isArray(model.get(rule.field)) &&
|
|
524
|
+
_.intersection( rule.value, model.get(rule.field)).length === rule.value.length
|
|
525
|
+
) {
|
|
526
|
+
should_push = true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// The field's value is required to match the regular expression
|
|
530
|
+
}else if(rule.type === "pattern"){
|
|
531
|
+
if( model.get(rule.field).toString().match(rule.value) ) {
|
|
532
|
+
should_push = true;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
//Unknown type
|
|
536
|
+
}else{
|
|
537
|
+
should_push = false;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
if( should_push ){
|
|
543
|
+
filteredModels.push(model);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
return filteredModels;
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
// The actual place where the collection is filtered.
|
|
552
|
+
// Check setFilter for arguments explicacion.
|
|
553
|
+
_filter: function ( models, fields, filter ) {
|
|
554
|
+
|
|
555
|
+
// For example, if you had a data model containing cars like { color: '', description: '', hp: '' },
|
|
556
|
+
// your fields was set to ['color', 'description', 'hp'] and your filter was set
|
|
557
|
+
// to "Black Mustang 300", the word "Black" will match all the cars that have black color, then
|
|
558
|
+
// "Mustang" in the description and then the HP in the 'hp' field.
|
|
559
|
+
// NOTE: "Black Musta 300" will return the same as "Black Mustang 300"
|
|
560
|
+
|
|
561
|
+
// We accept fields to be a string, an array or an object
|
|
562
|
+
// but if string or array is passed we need to convert it
|
|
563
|
+
// to an object.
|
|
564
|
+
|
|
565
|
+
var self = this;
|
|
566
|
+
|
|
567
|
+
var obj_fields = {};
|
|
568
|
+
|
|
569
|
+
if( _.isString( fields ) ) {
|
|
570
|
+
obj_fields[fields] = {cmp_method: 'regexp'};
|
|
571
|
+
}else if( _.isArray( fields ) ) {
|
|
572
|
+
_.each(fields, function(field){
|
|
573
|
+
obj_fields[field] = {cmp_method: 'regexp'};
|
|
574
|
+
});
|
|
575
|
+
}else{
|
|
576
|
+
_.each(fields, function( cmp_opts, field ) {
|
|
577
|
+
obj_fields[field] = _.defaults(cmp_opts, { cmp_method: 'regexp' });
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
fields = obj_fields;
|
|
582
|
+
|
|
583
|
+
//Remove diacritic characters if diacritic plugin is loaded
|
|
584
|
+
if( _.has(Backbone.Paginator, 'removeDiacritics') && self.useDiacriticsPlugin ){
|
|
585
|
+
filter = Backbone.Paginator.removeDiacritics(filter);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// 'filter' can be only a string.
|
|
589
|
+
// If 'filter' is string we need to convert it to
|
|
590
|
+
// a regular expression.
|
|
591
|
+
// For example, if 'filter' is 'black dog' we need
|
|
592
|
+
// to find every single word, remove duplicated ones (if any)
|
|
593
|
+
// and transform the result to '(black|dog)'
|
|
594
|
+
if( filter === '' || !_.isString(filter) ) {
|
|
595
|
+
return models;
|
|
596
|
+
} else {
|
|
597
|
+
var words = _.map(filter.match(/\w+/ig), function(element) { return element.toLowerCase(); });
|
|
598
|
+
var pattern = "(" + _.uniq(words).join("|") + ")";
|
|
599
|
+
var regexp = new RegExp(pattern, "igm");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
var filteredModels = [];
|
|
603
|
+
|
|
604
|
+
// We need to iterate over each model
|
|
605
|
+
_.each( models, function( model ) {
|
|
606
|
+
|
|
607
|
+
var matchesPerModel = [];
|
|
608
|
+
|
|
609
|
+
// and over each field of each model
|
|
610
|
+
_.each( fields, function( cmp_opts, field ) {
|
|
611
|
+
|
|
612
|
+
var value = model.get( field );
|
|
613
|
+
|
|
614
|
+
if( value ) {
|
|
615
|
+
|
|
616
|
+
// The regular expression we created earlier let's us detect if a
|
|
617
|
+
// given string contains each and all of the words in the regular expression
|
|
618
|
+
// or not, but in both cases match() will return an array containing all
|
|
619
|
+
// the words it matched.
|
|
620
|
+
var matchesPerField = [];
|
|
621
|
+
|
|
622
|
+
if( _.has(Backbone.Paginator, 'removeDiacritics') && self.useDiacriticsPlugin ){
|
|
623
|
+
value = Backbone.Paginator.removeDiacritics(value.toString());
|
|
624
|
+
}else{
|
|
625
|
+
value = value.toString();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Levenshtein cmp
|
|
629
|
+
if( cmp_opts.cmp_method === 'levenshtein' && _.has(Backbone.Paginator, 'levenshtein') && self.useLevenshteinPlugin ) {
|
|
630
|
+
var distance = Backbone.Paginator.levenshtein(value, filter);
|
|
631
|
+
|
|
632
|
+
_.defaults(cmp_opts, { max_distance: 0 });
|
|
633
|
+
|
|
634
|
+
if( distance <= cmp_opts.max_distance ) {
|
|
635
|
+
matchesPerField = _.uniq(words);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Default (RegExp) cmp
|
|
639
|
+
}else{
|
|
640
|
+
matchesPerField = value.match( regexp );
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
matchesPerField = _.map(matchesPerField, function(match) {
|
|
644
|
+
return match.toString().toLowerCase();
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
_.each(matchesPerField, function(match){
|
|
648
|
+
matchesPerModel.push(match);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// We just need to check if the returned array contains all the words in our
|
|
656
|
+
// regex, and if it does, it means that we have a match, so we should save it.
|
|
657
|
+
matchesPerModel = _.uniq( _.without(matchesPerModel, "") );
|
|
658
|
+
|
|
659
|
+
if( _.isEmpty( _.difference(words, matchesPerModel) ) ) {
|
|
660
|
+
filteredModels.push(model);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
return filteredModels;
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
// You shouldn't need to call info() as this method is used to
|
|
669
|
+
// calculate internal data as first/prev/next/last page...
|
|
670
|
+
info: function () {
|
|
671
|
+
var self = this,
|
|
672
|
+
info = {},
|
|
673
|
+
totalRecords = (self.sortedAndFilteredModels) ? self.sortedAndFilteredModels.length : self.length,
|
|
674
|
+
totalPages = Math.ceil(totalRecords / self.perPage);
|
|
675
|
+
|
|
676
|
+
info = {
|
|
677
|
+
totalUnfilteredRecords: self.origModels.length,
|
|
678
|
+
totalRecords: totalRecords,
|
|
679
|
+
currentPage: self.currentPage,
|
|
680
|
+
perPage: this.perPage,
|
|
681
|
+
totalPages: totalPages,
|
|
682
|
+
lastPage: totalPages,
|
|
683
|
+
previous: false,
|
|
684
|
+
next: false,
|
|
685
|
+
startRecord: totalRecords === 0 ? 0 : (self.currentPage - 1) * this.perPage + 1,
|
|
686
|
+
endRecord: Math.min(totalRecords, self.currentPage * this.perPage)
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
if (self.currentPage > 1) {
|
|
690
|
+
info.previous = self.currentPage - 1;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (self.currentPage < info.totalPages) {
|
|
694
|
+
info.next = self.currentPage + 1;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
info.pageSet = self.setPagination(info);
|
|
698
|
+
|
|
699
|
+
self.information = info;
|
|
700
|
+
return info;
|
|
701
|
+
},
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
// setPagination also is an internal function that shouldn't be called directly.
|
|
705
|
+
// It will create an array containing the pages right before and right after the
|
|
706
|
+
// actual page.
|
|
707
|
+
setPagination: function ( info ) {
|
|
708
|
+
|
|
709
|
+
var pages = [], i = 0, l = 0;
|
|
710
|
+
|
|
711
|
+
// How many adjacent pages should be shown on each side?
|
|
712
|
+
var ADJACENTx2 = this.pagesInRange * 2,
|
|
713
|
+
LASTPAGE = Math.ceil(info.totalRecords / info.perPage);
|
|
714
|
+
|
|
715
|
+
if (LASTPAGE > 1) {
|
|
716
|
+
|
|
717
|
+
// not enough pages to bother breaking it up
|
|
718
|
+
if (LASTPAGE <= (1 + ADJACENTx2)) {
|
|
719
|
+
for (i = 1, l = LASTPAGE; i <= l; i++) {
|
|
720
|
+
pages.push(i);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// enough pages to hide some
|
|
725
|
+
else {
|
|
726
|
+
|
|
727
|
+
//close to beginning; only hide later pages
|
|
728
|
+
if (info.currentPage <= (this.pagesInRange + 1)) {
|
|
729
|
+
for (i = 1, l = 2 + ADJACENTx2; i < l; i++) {
|
|
730
|
+
pages.push(i);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// in middle; hide some front and some back
|
|
735
|
+
else if (LASTPAGE - this.pagesInRange > info.currentPage && info.currentPage > this.pagesInRange) {
|
|
736
|
+
for (i = info.currentPage - this.pagesInRange; i <= info.currentPage + this.pagesInRange; i++) {
|
|
737
|
+
pages.push(i);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// close to end; only hide early pages
|
|
742
|
+
else {
|
|
743
|
+
for (i = LASTPAGE - ADJACENTx2; i <= LASTPAGE; i++) {
|
|
744
|
+
pages.push(i);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return pages;
|
|
752
|
+
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
bootstrap: function(options) {
|
|
756
|
+
_.extend(this, options);
|
|
757
|
+
this.goTo(1);
|
|
758
|
+
this.info();
|
|
759
|
+
return this;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// function aliasing
|
|
765
|
+
Paginator.clientPager.prototype.prevPage = Paginator.clientPager.prototype.previousPage;
|
|
766
|
+
|
|
767
|
+
// @name: requestPager
|
|
768
|
+
//
|
|
769
|
+
// Paginator for server-side data being requested from a backend/API
|
|
770
|
+
//
|
|
771
|
+
// @description:
|
|
772
|
+
// This paginator is responsible for providing pagination
|
|
773
|
+
// and sort capabilities for requests to a server-side
|
|
774
|
+
// data service (e.g an API)
|
|
775
|
+
//
|
|
776
|
+
Paginator.requestPager = Backbone.Collection.extend({
|
|
777
|
+
|
|
778
|
+
sync: function ( method, model, options ) {
|
|
779
|
+
|
|
780
|
+
var self = this;
|
|
781
|
+
|
|
782
|
+
self.setDefaults();
|
|
783
|
+
|
|
784
|
+
// Some values could be functions, let's make sure
|
|
785
|
+
// to change their scope too and run them
|
|
786
|
+
var queryAttributes = {};
|
|
787
|
+
_.each(_.result(self, "server_api"), function(value, key){
|
|
788
|
+
if( _.isFunction(value) ) {
|
|
789
|
+
value = _.bind(value, self);
|
|
790
|
+
value = value();
|
|
791
|
+
}
|
|
792
|
+
queryAttributes[key] = value;
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
var queryOptions = _.clone(self.paginator_core);
|
|
796
|
+
_.each(queryOptions, function(value, key){
|
|
797
|
+
if( _.isFunction(value) ) {
|
|
798
|
+
value = _.bind(value, self);
|
|
799
|
+
value = value();
|
|
800
|
+
}
|
|
801
|
+
queryOptions[key] = value;
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// Create default values if no others are specified
|
|
805
|
+
queryOptions = _.defaults(queryOptions, {
|
|
806
|
+
timeout: 25000,
|
|
807
|
+
cache: false,
|
|
808
|
+
type: 'GET',
|
|
809
|
+
dataType: 'jsonp'
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Allows the passing in of {data: {foo: 'bar'}} at request time to overwrite server_api defaults
|
|
813
|
+
if( options.data ){
|
|
814
|
+
options.data = decodeURIComponent($.param(_.extend(queryAttributes,options.data)));
|
|
815
|
+
}else{
|
|
816
|
+
options.data = decodeURIComponent($.param(queryAttributes));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
queryOptions = _.extend(queryOptions, {
|
|
820
|
+
data: decodeURIComponent($.param(queryAttributes)),
|
|
821
|
+
processData: false,
|
|
822
|
+
url: _.result(queryOptions, 'url')
|
|
823
|
+
}, options);
|
|
824
|
+
|
|
825
|
+
var bbVer = Backbone.VERSION.split('.');
|
|
826
|
+
var oldSuccessFormat = (parseInt(bbVer[0], 10) === 0 &&
|
|
827
|
+
parseInt(bbVer[1], 10) === 9 &&
|
|
828
|
+
parseInt(bbVer[2], 10) <= 9);
|
|
829
|
+
|
|
830
|
+
var success = queryOptions.success;
|
|
831
|
+
queryOptions.success = function ( resp, status, xhr ) {
|
|
832
|
+
|
|
833
|
+
if ( success ) {
|
|
834
|
+
// This is to keep compatibility with Backbone older than 0.9.10
|
|
835
|
+
if (oldSuccessFormat) {
|
|
836
|
+
success( resp, status, xhr );
|
|
837
|
+
} else {
|
|
838
|
+
success( model, resp, queryOptions );
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if ( model && model.trigger ) {
|
|
842
|
+
model.trigger( 'sync', model, resp, queryOptions );
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
var error = queryOptions.error;
|
|
847
|
+
queryOptions.error = function ( xhr ) {
|
|
848
|
+
if ( error ) {
|
|
849
|
+
error( model, xhr, queryOptions );
|
|
850
|
+
}
|
|
851
|
+
if ( model && model.trigger ) {
|
|
852
|
+
model.trigger( 'error', model, xhr, queryOptions );
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
var xhr = queryOptions.xhr = $.ajax( queryOptions );
|
|
857
|
+
if ( model && model.trigger ) {
|
|
858
|
+
model.trigger('request', model, xhr, queryOptions);
|
|
859
|
+
}
|
|
860
|
+
return xhr;
|
|
861
|
+
},
|
|
862
|
+
|
|
863
|
+
setDefaults: function() {
|
|
864
|
+
var self = this;
|
|
865
|
+
|
|
866
|
+
// Create default values if no others are specified
|
|
867
|
+
_.defaults(self.paginator_ui, {
|
|
868
|
+
firstPage: 0,
|
|
869
|
+
currentPage: 1,
|
|
870
|
+
perPage: 5,
|
|
871
|
+
totalPages: 10,
|
|
872
|
+
pagesInRange: 4
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// Change scope of 'paginator_ui' object values
|
|
876
|
+
_.each(self.paginator_ui, function(value, key) {
|
|
877
|
+
if (_.isUndefined(self[key])) {
|
|
878
|
+
self[key] = self.paginator_ui[key];
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
},
|
|
882
|
+
|
|
883
|
+
requestNextPage: function ( options ) {
|
|
884
|
+
if ( this.currentPage !== undefined ) {
|
|
885
|
+
this.currentPage += 1;
|
|
886
|
+
return this.pager( options );
|
|
887
|
+
} else {
|
|
888
|
+
var response = new $.Deferred();
|
|
889
|
+
response.reject();
|
|
890
|
+
return response.promise();
|
|
891
|
+
}
|
|
892
|
+
},
|
|
893
|
+
|
|
894
|
+
requestPreviousPage: function ( options ) {
|
|
895
|
+
if ( this.currentPage !== undefined ) {
|
|
896
|
+
this.currentPage -= 1;
|
|
897
|
+
return this.pager( options );
|
|
898
|
+
} else {
|
|
899
|
+
var response = new $.Deferred();
|
|
900
|
+
response.reject();
|
|
901
|
+
return response.promise();
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
|
|
905
|
+
updateOrder: function ( column ) {
|
|
906
|
+
if (column !== undefined) {
|
|
907
|
+
this.sortField = column;
|
|
908
|
+
this.pager();
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
goTo: function ( page, options ) {
|
|
914
|
+
if ( page !== undefined ) {
|
|
915
|
+
this.currentPage = parseInt(page, 10);
|
|
916
|
+
return this.pager( options );
|
|
917
|
+
} else {
|
|
918
|
+
var response = new $.Deferred();
|
|
919
|
+
response.reject();
|
|
920
|
+
return response.promise();
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
|
|
924
|
+
howManyPer: function ( count ) {
|
|
925
|
+
if( count !== undefined ){
|
|
926
|
+
this.currentPage = this.firstPage;
|
|
927
|
+
this.perPage = count;
|
|
928
|
+
this.pager();
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
info: function () {
|
|
933
|
+
|
|
934
|
+
var info = {
|
|
935
|
+
// If parse() method is implemented and totalRecords is set to the length
|
|
936
|
+
// of the records returned, make it available. Else, default it to 0
|
|
937
|
+
totalRecords: this.totalRecords || 0,
|
|
938
|
+
|
|
939
|
+
currentPage: this.currentPage,
|
|
940
|
+
firstPage: this.firstPage,
|
|
941
|
+
totalPages: Math.ceil(this.totalRecords / this.perPage),
|
|
942
|
+
lastPage: this.totalPages, // should use totalPages in template
|
|
943
|
+
perPage: this.perPage,
|
|
944
|
+
previous:false,
|
|
945
|
+
next:false
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
if (this.currentPage > 1) {
|
|
949
|
+
info.previous = this.currentPage - 1;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (this.currentPage < info.totalPages) {
|
|
953
|
+
info.next = this.currentPage + 1;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// left around for backwards compatibility
|
|
957
|
+
info.hasNext = info.next;
|
|
958
|
+
info.hasPrevious = info.next;
|
|
959
|
+
|
|
960
|
+
info.pageSet = this.setPagination(info);
|
|
961
|
+
|
|
962
|
+
this.information = info;
|
|
963
|
+
return info;
|
|
964
|
+
},
|
|
965
|
+
|
|
966
|
+
setPagination: function ( info ) {
|
|
967
|
+
|
|
968
|
+
var pages = [], i = 0, l = 0;
|
|
969
|
+
|
|
970
|
+
// How many adjacent pages should be shown on each side?
|
|
971
|
+
var ADJACENTx2 = this.pagesInRange * 2,
|
|
972
|
+
LASTPAGE = Math.ceil(info.totalRecords / info.perPage);
|
|
973
|
+
|
|
974
|
+
if (LASTPAGE > 1) {
|
|
975
|
+
|
|
976
|
+
// not enough pages to bother breaking it up
|
|
977
|
+
if (LASTPAGE <= (1 + ADJACENTx2)) {
|
|
978
|
+
for (i = 1, l = LASTPAGE; i <= l; i++) {
|
|
979
|
+
pages.push(i);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// enough pages to hide some
|
|
984
|
+
else {
|
|
985
|
+
|
|
986
|
+
//close to beginning; only hide later pages
|
|
987
|
+
if (info.currentPage <= (this.pagesInRange + 1)) {
|
|
988
|
+
for (i = 1, l = 2 + ADJACENTx2; i < l; i++) {
|
|
989
|
+
pages.push(i);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// in middle; hide some front and some back
|
|
994
|
+
else if (LASTPAGE - this.pagesInRange > info.currentPage && info.currentPage > this.pagesInRange) {
|
|
995
|
+
for (i = info.currentPage - this.pagesInRange; i <= info.currentPage + this.pagesInRange; i++) {
|
|
996
|
+
pages.push(i);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// close to end; only hide early pages
|
|
1001
|
+
else {
|
|
1002
|
+
for (i = LASTPAGE - ADJACENTx2; i <= LASTPAGE; i++) {
|
|
1003
|
+
pages.push(i);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
return pages;
|
|
1011
|
+
|
|
1012
|
+
},
|
|
1013
|
+
|
|
1014
|
+
// fetches the latest results from the server
|
|
1015
|
+
pager: function ( options ) {
|
|
1016
|
+
if ( !_.isObject(options) ) {
|
|
1017
|
+
options = {};
|
|
1018
|
+
}
|
|
1019
|
+
return this.fetch( options );
|
|
1020
|
+
},
|
|
1021
|
+
|
|
1022
|
+
url: function(){
|
|
1023
|
+
// Expose url parameter enclosed in this.paginator_core.url to properly
|
|
1024
|
+
// extend Collection and allow Collection CRUD
|
|
1025
|
+
if(this.paginator_core !== undefined && this.paginator_core.url !== undefined){
|
|
1026
|
+
return this.paginator_core.url;
|
|
1027
|
+
} else {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
bootstrap: function(options) {
|
|
1033
|
+
_.extend(this, options);
|
|
1034
|
+
this.setDefaults();
|
|
1035
|
+
this.info();
|
|
1036
|
+
return this;
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// function aliasing
|
|
1041
|
+
Paginator.requestPager.prototype.nextPage = Paginator.requestPager.prototype.requestNextPage;
|
|
1042
|
+
Paginator.requestPager.prototype.prevPage = Paginator.requestPager.prototype.requestPreviousPage;
|
|
1043
|
+
|
|
1044
|
+
return Paginator;
|
|
1045
|
+
|
|
1046
|
+
}( Backbone, _, jQuery ));
|