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.
Files changed (79) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +173 -0
  4. data/LICENSE +19 -0
  5. data/README.md +363 -0
  6. data/Rakefile +10 -0
  7. data/bin/helios +20 -0
  8. data/helios_aim.gemspec +47 -0
  9. data/lib/helios.rb +27 -0
  10. data/lib/helios/backend.rb +64 -0
  11. data/lib/helios/backend/data.rb +40 -0
  12. data/lib/helios/backend/in-app-purchase.rb +43 -0
  13. data/lib/helios/backend/newsstand.rb +97 -0
  14. data/lib/helios/backend/passbook.rb +41 -0
  15. data/lib/helios/backend/push-notification.rb +110 -0
  16. data/lib/helios/commands.rb +4 -0
  17. data/lib/helios/commands/console.rb +21 -0
  18. data/lib/helios/commands/link.rb +19 -0
  19. data/lib/helios/commands/new.rb +76 -0
  20. data/lib/helios/commands/server.rb +52 -0
  21. data/lib/helios/frontend.rb +66 -0
  22. data/lib/helios/frontend/fonts/icons.eot +0 -0
  23. data/lib/helios/frontend/fonts/icons.ttf +0 -0
  24. data/lib/helios/frontend/fonts/icons.woff +0 -0
  25. data/lib/helios/frontend/images/bg.jpg +0 -0
  26. data/lib/helios/frontend/images/helios.svg +33 -0
  27. data/lib/helios/frontend/javascripts/helios.coffee +72 -0
  28. data/lib/helios/frontend/javascripts/helios/collections.coffee +99 -0
  29. data/lib/helios/frontend/javascripts/helios/models.coffee +23 -0
  30. data/lib/helios/frontend/javascripts/helios/router.coffee +50 -0
  31. data/lib/helios/frontend/javascripts/helios/views.coffee +307 -0
  32. data/lib/helios/frontend/javascripts/vendor/backbone.datagrid.js +662 -0
  33. data/lib/helios/frontend/javascripts/vendor/backbone.js +1487 -0
  34. data/lib/helios/frontend/javascripts/vendor/backbone.paginator.js +1046 -0
  35. data/lib/helios/frontend/javascripts/vendor/codemirror.javascript.js +411 -0
  36. data/lib/helios/frontend/javascripts/vendor/codemirror.js +3047 -0
  37. data/lib/helios/frontend/javascripts/vendor/date.js +104 -0
  38. data/lib/helios/frontend/javascripts/vendor/foundation.js +331 -0
  39. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.alerts.js +50 -0
  40. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.clearing.js +478 -0
  41. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.cookie.js +74 -0
  42. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.dropdown.js +122 -0
  43. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.forms.js +403 -0
  44. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.joyride.js +613 -0
  45. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.magellan.js +130 -0
  46. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.orbit.js +355 -0
  47. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.placeholder.js +159 -0
  48. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.reveal.js +272 -0
  49. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.section.js +183 -0
  50. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.tooltips.js +195 -0
  51. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.topbar.js +208 -0
  52. data/lib/helios/frontend/javascripts/vendor/jquery.js +9597 -0
  53. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload-ui.js +807 -0
  54. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload.js +1201 -0
  55. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.ui.widget.js +530 -0
  56. data/lib/helios/frontend/javascripts/vendor/linkheaders.js +117 -0
  57. data/lib/helios/frontend/javascripts/vendor/underscore.js +1227 -0
  58. data/lib/helios/frontend/stylesheets/_codemirror.sass +219 -0
  59. data/lib/helios/frontend/stylesheets/_fonts.sass +80 -0
  60. data/lib/helios/frontend/stylesheets/_iphone.sass +141 -0
  61. data/lib/helios/frontend/stylesheets/_settings.scss +989 -0
  62. data/lib/helios/frontend/stylesheets/screen.sass +187 -0
  63. data/lib/helios/frontend/templates/data/entities.jst.tpl +11 -0
  64. data/lib/helios/frontend/templates/in-app-purchase/receipts.jst.tpl +11 -0
  65. data/lib/helios/frontend/templates/navigation.jst.tpl +31 -0
  66. data/lib/helios/frontend/templates/newsstand/issues.jst.tpl +16 -0
  67. data/lib/helios/frontend/templates/newsstand/new.jst.tpl +28 -0
  68. data/lib/helios/frontend/templates/passbook/passes.jst.tpl +11 -0
  69. data/lib/helios/frontend/templates/push-notification/compose.jst.tpl +70 -0
  70. data/lib/helios/frontend/templates/push-notification/devices.jst.tpl +17 -0
  71. data/lib/helios/frontend/views/index.haml +22 -0
  72. data/lib/helios/templates/.env.erb +1 -0
  73. data/lib/helios/templates/.gitignore +3 -0
  74. data/lib/helios/templates/Gemfile.erb +10 -0
  75. data/lib/helios/templates/Procfile.erb +1 -0
  76. data/lib/helios/templates/README.md.erb +4 -0
  77. data/lib/helios/templates/config.ru.erb +11 -0
  78. data/lib/helios/version.rb +3 -0
  79. 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 ));