helios_aim 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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 ));