helios 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/Gemfile.lock +82 -37
  2. data/README.md +16 -0
  3. data/bin/helios +4 -0
  4. data/example/Gemfile +7 -0
  5. data/example/Gemfile.lock +149 -0
  6. data/example/Procfile +1 -0
  7. data/example/Sample.xcdatamodeld/Sample.xcdatamodel/contents +23 -0
  8. data/example/config.ru +6 -0
  9. data/helios.gemspec +17 -7
  10. data/lib/helios.rb +15 -1
  11. data/lib/helios/backend.rb +46 -0
  12. data/lib/helios/backend/data.rb +36 -0
  13. data/lib/helios/backend/in-app-purchase.rb +33 -0
  14. data/lib/helios/backend/passbook.rb +25 -0
  15. data/lib/helios/backend/push-notification.rb +90 -0
  16. data/lib/helios/commands.rb +1 -0
  17. data/lib/helios/commands/new.rb +27 -0
  18. data/lib/helios/frontend.rb +56 -0
  19. data/lib/helios/frontend/images/data.png +0 -0
  20. data/lib/helios/frontend/images/data.svg +1 -0
  21. data/lib/helios/frontend/images/in-app-purchase.png +0 -0
  22. data/lib/helios/frontend/images/in-app-purchase.svg +1 -0
  23. data/lib/helios/frontend/images/passbook.png +0 -0
  24. data/lib/helios/frontend/images/passbook.svg +1 -0
  25. data/lib/helios/frontend/images/push-notification.png +0 -0
  26. data/lib/helios/frontend/images/push-notification.svg +1 -0
  27. data/lib/helios/frontend/javascripts/foundation/foundation.alerts.js +50 -0
  28. data/lib/helios/frontend/javascripts/foundation/foundation.clearing.js +478 -0
  29. data/lib/helios/frontend/javascripts/foundation/foundation.cookie.js +74 -0
  30. data/lib/helios/frontend/javascripts/foundation/foundation.dropdown.js +122 -0
  31. data/lib/helios/frontend/javascripts/foundation/foundation.forms.js +403 -0
  32. data/lib/helios/frontend/javascripts/foundation/foundation.joyride.js +613 -0
  33. data/lib/helios/frontend/javascripts/foundation/foundation.js +331 -0
  34. data/lib/helios/frontend/javascripts/foundation/foundation.magellan.js +130 -0
  35. data/lib/helios/frontend/javascripts/foundation/foundation.orbit.js +355 -0
  36. data/lib/helios/frontend/javascripts/foundation/foundation.placeholder.js +159 -0
  37. data/lib/helios/frontend/javascripts/foundation/foundation.reveal.js +272 -0
  38. data/lib/helios/frontend/javascripts/foundation/foundation.section.js +183 -0
  39. data/lib/helios/frontend/javascripts/foundation/foundation.tooltips.js +195 -0
  40. data/lib/helios/frontend/javascripts/foundation/foundation.topbar.js +208 -0
  41. data/lib/helios/frontend/javascripts/helios.coffee +48 -0
  42. data/lib/helios/frontend/javascripts/helios/collections.coffee +30 -0
  43. data/lib/helios/frontend/javascripts/helios/models.coffee +23 -0
  44. data/lib/helios/frontend/javascripts/helios/router.coffee +52 -0
  45. data/lib/helios/frontend/javascripts/helios/views.coffee +92 -0
  46. data/lib/helios/frontend/javascripts/vendor/backbone.datagrid.js +662 -0
  47. data/lib/helios/frontend/javascripts/vendor/backbone.js +1487 -0
  48. data/lib/helios/frontend/javascripts/vendor/jquery.js +9597 -0
  49. data/lib/helios/frontend/javascripts/vendor/underscore.js +1227 -0
  50. data/lib/helios/frontend/stylesheets/_settings.scss +988 -0
  51. data/lib/helios/frontend/stylesheets/screen.sass +98 -0
  52. data/lib/helios/frontend/templates/data/entities.hbs +7 -0
  53. data/lib/helios/frontend/templates/data/entity.hbs +17 -0
  54. data/lib/helios/frontend/templates/in-app-purchase/receipt.hbs +53 -0
  55. data/lib/helios/frontend/templates/in-app-purchase/receipts.hbs +39 -0
  56. data/lib/helios/frontend/templates/passbook/passes.hbs +25 -0
  57. data/lib/helios/frontend/templates/push-notification/device.hbs +35 -0
  58. data/lib/helios/frontend/templates/push-notification/devices.hbs +37 -0
  59. data/lib/helios/frontend/views/devices.jst.tpl +3 -0
  60. data/lib/helios/frontend/views/entities.jst.tpl +9 -0
  61. data/lib/helios/frontend/views/index.haml +30 -0
  62. data/lib/helios/frontend/views/passes.jst.tpl +3 -0
  63. data/lib/helios/frontend/views/receipts.jst.tpl +3 -0
  64. data/lib/helios/templates/Gemfile.erb +7 -0
  65. data/lib/helios/templates/Procfile.erb +1 -0
  66. data/lib/helios/templates/README.md.erb +9 -0
  67. data/lib/helios/templates/config.ru.erb +6 -0
  68. data/lib/helios/version.rb +3 -0
  69. metadata +199 -26
@@ -0,0 +1,30 @@
1
+ class Helios.Collection extends Backbone.Collection
2
+ parse: (response, options) ->
3
+ if _.isArray(response)
4
+ response
5
+ else
6
+ if response.page?
7
+ @total = response.total
8
+ @page = response.page
9
+
10
+ _.detect response, (value, key) ->
11
+ _.isArray(value)
12
+
13
+ class Helios.Collections.Entities extends Helios.Collection
14
+ model: Helios.Models.Entity
15
+ url: '/'
16
+
17
+ class Helios.Collections.Resources extends Helios.Collection
18
+ model: Helios.Models.Resource
19
+
20
+ class Helios.Collections.Devices extends Helios.Collection
21
+ model: Helios.Models.Device
22
+ url: '/devices'
23
+
24
+ class Helios.Collections.Receipts extends Helios.Collection
25
+ model: Helios.Models.Receipt
26
+ url: '/receipts'
27
+
28
+ class Helios.Collections.Passes extends Helios.Collection
29
+ model: Helios.Models.Pass
30
+ url: '/passes'
@@ -0,0 +1,23 @@
1
+ class Helios.Models.Entity extends Backbone.Model
2
+ idAttribute: "name"
3
+ url: ->
4
+ @get('resources').url.replace(/^\//, 'data/')
5
+
6
+ parse: (response) ->
7
+ response.resources = new Helios.Collections.Resources()
8
+ response.resources.url = response.url
9
+ response
10
+
11
+ class Helios.Models.Resource extends Backbone.Model
12
+ idAttribute: "url"
13
+
14
+ class Helios.Models.Device extends Backbone.Model
15
+ idAttribute: "token"
16
+ fields: ['token', 'alias', 'badge', 'locale', 'language', 'timezone', 'ip_address', 'lat', 'lng']
17
+
18
+ class Helios.Models.Receipt extends Backbone.Model
19
+ idAttribute: "transaction_id"
20
+ fields: ['transaction_id', 'product_id', 'purchase_date', 'original_transaction_id', 'original_purchase_date', 'app_item_id', 'version_external_identifier', 'bid', 'bvrs', 'ip_address']
21
+
22
+ class Helios.Models.Pass extends Backbone.Model
23
+ fields: ['pass_type_identifier', 'serial_number', 'authentication_token']
@@ -0,0 +1,52 @@
1
+ class Helios.Routers.Root extends Backbone.Router
2
+ el:
3
+ "div[role='main']"
4
+
5
+ initialize: (options) ->
6
+ @views = {}
7
+ super
8
+
9
+ routes:
10
+ '': 'index'
11
+ 'data': 'data'
12
+ 'push-notification': 'push_notification'
13
+ 'in-app-purchase': 'in_app_purchase'
14
+ 'passbook': 'passbook'
15
+
16
+ index: ->
17
+ console.log("index")
18
+ # @_activateNavbarLink("devices")
19
+
20
+ Helios.entities.fetch(type: 'OPTIONS')
21
+ # @views.devices ?= new RPN.Views.Devices(collection: RPN.devices)
22
+ # @views.devices.render()
23
+
24
+ # _activateNavbarLink: (className) ->
25
+ # $li = $("header nav li")
26
+ # $li.removeClass("active")
27
+ # $li.filter("." + className).addClass("active")
28
+
29
+ data: ->
30
+ console.log("data")
31
+ Helios.entities.fetch(type: 'OPTIONS')
32
+ @views.entities.render()
33
+
34
+ push_notification: ->
35
+ console.log("push")
36
+ @devices ?= new Helios.Collections.Devices
37
+ @views.devices ?= new Helios.Views.Devices(collection: @devices)
38
+ @views.devices.render()
39
+
40
+ in_app_purchase: ->
41
+ console.log("IAP")
42
+ @receipts ?= new Helios.Collections.Receipts
43
+ @views.receipts ?= new Helios.Views.Receipts(collection: @receipts)
44
+ @views.receipts.render()
45
+
46
+ passbook: ->
47
+ console.log("passbook")
48
+ @passes ?= new Helios.Collections.Passes
49
+ @views.passes ?= new Helios.Views.Passes(collection: @passes)
50
+ @views.passes.render()
51
+
52
+
@@ -0,0 +1,92 @@
1
+ class Helios.Views.Entities extends Backbone.View
2
+ template: JST['entities']
3
+ el: "[role='main']"
4
+
5
+ events:
6
+ 'change #entities': ->
7
+ window.app.navigate(@$el.find("#entities").val(), {trigger: true})
8
+
9
+ initialize: ->
10
+ @collection.on 'reset', @render
11
+
12
+ render: =>
13
+ @$el.html(@template(entities: @collection))
14
+
15
+ @
16
+
17
+ class Helios.Views.Entity extends Backbone.View
18
+ el: "[role='main']"
19
+
20
+ initialize: ->
21
+ @model.on 'reset', @render
22
+
23
+ @collection = @model.get('resources')
24
+ @collection.fetch({success: @render})
25
+
26
+ render: =>
27
+ if @collection
28
+ @datagrid = new Backbone.Datagrid({
29
+ collection: @collection,
30
+ columns: @collection.first().attributes.keys,
31
+ paginated: true,
32
+ perPage: 20
33
+ })
34
+ @$el.find("#datagrid").html(@datagrid.el)
35
+
36
+ @
37
+
38
+ class Helios.Views.Devices extends Backbone.View
39
+ template: JST['devices']
40
+ el: "[role='main']"
41
+
42
+ render: =>
43
+ @$el.html(@template())
44
+
45
+ if @collection
46
+ @datagrid = new Backbone.Datagrid({
47
+ collection: @collection,
48
+ columns: Helios.Models.Device.fields,
49
+ paginated: true,
50
+ perPage: 20
51
+ })
52
+ @$el.find("#datagrid").html(@datagrid.el)
53
+
54
+ @
55
+
56
+ class Helios.Views.Receipts extends Backbone.View
57
+ template: JST['receipts']
58
+ el: "[role='main']"
59
+
60
+ render: =>
61
+ @$el.html(@template())
62
+
63
+ if @collection
64
+ console.log("receipts")
65
+ @datagrid = new Backbone.Datagrid({
66
+ collection: @collection,
67
+ columns: Helios.Models.Receipt.fields,
68
+ paginated: true,
69
+ perPage: 20
70
+ })
71
+ @$el.find("#datagrid").html(@datagrid.el)
72
+
73
+ @
74
+
75
+ class Helios.Views.Passes extends Backbone.View
76
+ template: JST['passes']
77
+ el: "[role='main']"
78
+
79
+ render: =>
80
+ @$el.html(@template())
81
+
82
+ if @collection
83
+ console.log("passes")
84
+ @datagrid = new Backbone.Datagrid({
85
+ collection: @collection,
86
+ columns: Helios.Models.Pass.fields,
87
+ paginated: true,
88
+ perPage: 20
89
+ })
90
+ @$el.find("#datagrid").html(@datagrid.el)
91
+
92
+ @
@@ -0,0 +1,662 @@
1
+ // backbone.datagrid v0.3.2
2
+ //
3
+ // Copyright (c) 2012 Loïc Frering <loic.frering@gmail.com>
4
+ // Distributed under the MIT license
5
+
6
+ (function() {
7
+
8
+ var Datagrid = Backbone.View.extend({
9
+ initialize: function() {
10
+ this.columns = this.options.columns;
11
+ this.options = _.defaults(this.options, {
12
+ paginated: false,
13
+ page: 1,
14
+ perPage: 10,
15
+ tableClassName: 'table',
16
+ emptyMessage: '<p>No results found.</p>'
17
+ });
18
+
19
+ this.collection.on('reset', this.render, this);
20
+ this._prepare();
21
+ },
22
+
23
+ render: function() {
24
+ this.$el.empty();
25
+ this.renderTable();
26
+ if (this.options.paginated) {
27
+ this.renderPagination();
28
+ }
29
+
30
+ return this;
31
+ },
32
+
33
+ renderTable: function() {
34
+ var $table = $('<table></table>', {'class': this.options.tableClassName});
35
+ this.$el.append($table);
36
+
37
+ var header = new Header({columns: this.columns, sorter: this.sorter});
38
+ $table.append(header.render().el);
39
+
40
+ $table.append('<tbody></tbody>');
41
+
42
+ if (this.collection.isEmpty()) {
43
+ this.$el.append(this.options.emptyMessage);
44
+ } else {
45
+ this.collection.forEach(this.renderRow, this);
46
+ }
47
+ },
48
+
49
+ renderPagination: function() {
50
+ var pagination = new Pagination({pager: this.pager});
51
+ this.$el.append(pagination.render().el);
52
+ },
53
+
54
+ renderRow: function(model) {
55
+ var options = {
56
+ model: model,
57
+ columns: this.columns
58
+ };
59
+ var rowClassName = this.options.rowClassName;
60
+ if (_.isFunction(rowClassName)) {
61
+ rowClassName = rowClassName(model);
62
+ }
63
+ options.className = rowClassName;
64
+
65
+ var row = new Row(options);
66
+ this.$('tbody').append(row.render(this.columns).el);
67
+ },
68
+
69
+ refresh: function(options) {
70
+ if (this.options.paginated) {
71
+ this._page(options);
72
+ } else {
73
+ if (this.options.inMemory) {
74
+ this.collection.trigger('reset', this.collection);
75
+ if (options && options.success) {
76
+ options.success();
77
+ }
78
+ } else {
79
+ this._request(options);
80
+ }
81
+ }
82
+ },
83
+
84
+ sort: function(column, order) {
85
+ this.sorter.sort(column, order);
86
+ },
87
+
88
+ page: function(page) {
89
+ this.pager.page(page);
90
+ },
91
+
92
+ perPage: function(perPage) {
93
+ this.pager.set('perPage', perPage);
94
+ },
95
+
96
+ _sort: function() {
97
+ if (this.options.inMemory) {
98
+ this._sortInMemory();
99
+ } else {
100
+ this._sortRequest();
101
+ }
102
+ },
103
+
104
+ _sortInMemory: function() {
105
+ if (this.options.paginated) {
106
+ this._originalCollection.comparator = _.bind(this._comparator, this);
107
+ this._originalCollection.sort();
108
+ this.page(1);
109
+ } else {
110
+ this.collection.comparator = _.bind(this._comparator, this);
111
+ this.collection.sort();
112
+ }
113
+ },
114
+
115
+ _comparator: function(model1, model2) {
116
+ var columnComparator = this._comparatorForColumn(this.sorter.get('column'));
117
+ var order = columnComparator(model1, model2);
118
+ return this.sorter.sortedASC() ? order : -order;
119
+ },
120
+
121
+ _comparatorForColumn: function(column) {
122
+ var c = _.find(this.columns, function(c) {
123
+ return c.property === column || c.index === column;
124
+ });
125
+ return c ? c.comparator : undefined;
126
+ },
127
+
128
+ _sortRequest: function() {
129
+ this._request();
130
+ },
131
+
132
+ _page: function(options) {
133
+ if (this.options.inMemory) {
134
+ this._pageInMemory(options);
135
+ } else {
136
+ this._pageRequest(options);
137
+ }
138
+ },
139
+
140
+ _pageRequest: function(options) {
141
+ this._request(options);
142
+ },
143
+
144
+ _request: function(options) {
145
+ options = options || {};
146
+ var success = options.success;
147
+ var silent = options.silent;
148
+
149
+ options.data = this._getRequestData();
150
+ options.success = _.bind(function(collection) {
151
+ if (!this.columns || _.isEmpty(this.columns)) {
152
+ this._prepareColumns();
153
+ }
154
+ if (success) {
155
+ success();
156
+ }
157
+ if (this.options.paginated) {
158
+ this.pager.update(collection);
159
+ }
160
+ if (!silent) {
161
+ collection.trigger('reset', collection);
162
+ }
163
+ }, this);
164
+ options.silent = true;
165
+
166
+ this.collection.fetch(options);
167
+ },
168
+
169
+ _getRequestData: function() {
170
+ if (this.collection.data && _.isFunction(this.collection.data)) {
171
+ return this.collection.data(this.pager, this.sorter);
172
+ } else if (this.collection.data && typeof this.collection.data === 'object') {
173
+ var data = {};
174
+ _.each(this.collection.data, function(value, param) {
175
+ if (_.isFunction(value)) {
176
+ value = value(this.pager, this.sorter);
177
+ }
178
+ data[param] = value;
179
+ }, this);
180
+ return data;
181
+ } else if (this.options.paginated) {
182
+ return {
183
+ page: this.pager.get('currentPage'),
184
+ per_page: this.pager.get('perPage')
185
+ };
186
+ }
187
+
188
+ return {};
189
+ },
190
+
191
+ _pageInMemory: function(options) {
192
+ if (!this._originalCollection) {
193
+ this._originalCollection = this.collection.clone();
194
+ }
195
+
196
+ var page = this.pager.get('currentPage');
197
+ var perPage = this.pager.get('perPage');
198
+
199
+ var begin = (page - 1) * perPage;
200
+ var end = begin + perPage;
201
+
202
+ if (options && options.success) {
203
+ options.success();
204
+ }
205
+ this.pager.set('total', this._originalCollection.size());
206
+
207
+ this.collection.reset(this._originalCollection.slice(begin, end), options);
208
+ },
209
+
210
+ _prepare: function() {
211
+ this._prepareSorter();
212
+ this._preparePager();
213
+ this._prepareColumns();
214
+ this.refresh();
215
+ },
216
+
217
+ _prepareSorter: function() {
218
+ this.sorter = new Sorter();
219
+ this.sorter.on('change', function() {
220
+ this._sort(this.sorter.get('column'), this.sorter.get('order'));
221
+ }, this);
222
+ },
223
+
224
+ _preparePager: function() {
225
+ this.pager = new Pager({
226
+ currentPage: this.options.page,
227
+ perPage: this.options.perPage
228
+ });
229
+
230
+ this.pager.on('change:currentPage', function () {
231
+ this._page();
232
+ }, this);
233
+ this.pager.on('change:perPage', function() {
234
+ this.page(1);
235
+ }, this);
236
+ },
237
+
238
+ _prepareColumns: function() {
239
+ if (!this.columns || _.isEmpty(this.columns)) {
240
+ this._defaultColumns();
241
+ } else {
242
+ _.each(this.columns, function(column, i) {
243
+ this.columns[i] = this._prepareColumn(column, i);
244
+ }, this);
245
+ }
246
+ },
247
+
248
+ _prepareColumn: function(column, index) {
249
+ if (_.isString(column)) {
250
+ column = { property: column };
251
+ }
252
+ if (_.isObject(column)) {
253
+ column.index = index;
254
+ if (column.property) {
255
+ column.title = column.title || this._formatTitle(column.property);
256
+ } else if (!column.property && !column.view) {
257
+ throw new Error('Column \'' + column.title + '\' has no property and must accordingly define a custom cell view.');
258
+ }
259
+ if (this.options.inMemory && column.sortable) {
260
+ if (!column.comparator && !column.property && !column.sortedProperty) {
261
+ throw new Error('Invalid column definition: a sortable column must have a comparator, property or sortedProperty defined.');
262
+ }
263
+ column.comparator = column.comparator || this._defaultComparator(column.sortedProperty || column.property);
264
+ }
265
+ }
266
+ return column;
267
+ },
268
+
269
+ _formatTitle: function(title) {
270
+ return _.map(title.split(/\s|_/), function(word) {
271
+ return word.charAt(0).toUpperCase() + word.substr(1);
272
+ }).join(' ');
273
+ },
274
+
275
+ _defaultColumns: function() {
276
+ this.columns = [];
277
+ var model = this.collection.first(), i = 0;
278
+ if (model) {
279
+ for (var p in model.toJSON()) {
280
+ this.columns.push(this._prepareColumn(p, i++));
281
+ }
282
+ }
283
+ },
284
+
285
+ _defaultComparator: function(column) {
286
+ return function(model1, model2) {
287
+ var val1 = model1.has(column) ? model1.get(column) : '';
288
+ var val2 = model2.has(column) ? model2.get(column) : '';
289
+ return val1.localeCompare(val2);
290
+ };
291
+ }
292
+ });
293
+
294
+ var Header = Datagrid.Header = Backbone.View.extend({
295
+ tagName: 'thead',
296
+
297
+ initialize: function() {
298
+ this.columns = this.options.columns;
299
+ this.sorter = this.options.sorter;
300
+ },
301
+
302
+ render: function() {
303
+ var model = new Backbone.Model();
304
+ var headerColumn, columns = [];
305
+ _.each(this.columns, function(column, i) {
306
+ headerColumn = _.clone(column);
307
+ headerColumn.property = column.property || column.index;
308
+ headerColumn.view = column.headerView || {
309
+ type: HeaderCell,
310
+ sorter: this.sorter
311
+ };
312
+
313
+ model.set(headerColumn.property, column.title);
314
+ columns.push(headerColumn);
315
+ }, this);
316
+
317
+ var row = new Row({model: model, columns: columns, header: true});
318
+ this.$el.html(row.render().el);
319
+
320
+ return this;
321
+ }
322
+ });
323
+
324
+ var Row = Datagrid.Row = Backbone.View.extend({
325
+ tagName: 'tr',
326
+
327
+ initialize: function() {
328
+ this.columns = this.options.columns;
329
+ this.model.on('change', this.render, this);
330
+ },
331
+
332
+ render: function() {
333
+ this.$el.empty();
334
+ _.each(this.columns, this.renderCell, this);
335
+ return this;
336
+ },
337
+
338
+ renderCell: function(column) {
339
+ var cellView = this._resolveCellView(column);
340
+ this.$el.append(cellView.render().el);
341
+ },
342
+
343
+ _resolveCellView: function(column) {
344
+ var options = {
345
+ model: this.model,
346
+ column: column
347
+ };
348
+ if (this.options.header || column.header) {
349
+ options.tagName = 'th';
350
+ }
351
+ var cellClassName = column.cellClassName;
352
+ if (_.isFunction(cellClassName)) {
353
+ cellClassName = cellClassName(this.model);
354
+ }
355
+ options.className = cellClassName;
356
+
357
+
358
+ var view = column.view || Cell;
359
+
360
+ // Resolve view from string or function
361
+ if (typeof view !== 'object' && !(view.prototype && view.prototype.render)) {
362
+ if (_.isString(view)) {
363
+ options.callback = _.template(view);
364
+ view = CallbackCell;
365
+ } else if (_.isFunction(view) && !view.prototype.render) {
366
+ options.callback = view;
367
+ view = CallbackCell;
368
+ } else {
369
+ throw new TypeError('Invalid view passed to column "' + column.title + '".');
370
+ }
371
+ }
372
+
373
+ // Resolve view from options
374
+ else if (typeof view === 'object') {
375
+ _.extend(options, view);
376
+ view = view.type;
377
+ if (!view || !view.prototype || !view.prototype.render) {
378
+ throw new TypeError('Invalid view passed to column "' + column.title + '".');
379
+ }
380
+ }
381
+
382
+ return new view(options);
383
+ }
384
+ });
385
+
386
+ var Pagination = Datagrid.Pagination = Backbone.View.extend({
387
+ className: 'pagination pagination-centered',
388
+
389
+ events: {
390
+ 'click li:not(.disabled) a': 'page',
391
+ 'click li.disabled a': function(e) { e.preventDefault(); }
392
+ },
393
+
394
+ initialize: function() {
395
+ this.pager = this.options.pager;
396
+ },
397
+
398
+ render: function() {
399
+ var $ul = $('<ul></ul>'), $li;
400
+
401
+ $li = $('<li class="prev"><a href="#">«</a></li>');
402
+ if (!this.pager.hasPrev()) {
403
+ $li.addClass('disabled');
404
+ }
405
+ $ul.append($li);
406
+
407
+ if (this.pager.hasTotal()) {
408
+ for (var i = 1; i <= this.pager.get('totalPages'); i++) {
409
+ $li = $('<li></li>');
410
+ if (i === this.pager.get('currentPage')) {
411
+ $li.addClass('active');
412
+ }
413
+ $li.append('<a href="#">' + i + '</a>');
414
+ $ul.append($li);
415
+ }
416
+ }
417
+
418
+ $li = $('<li class="next"><a href="#">»</a></li>');
419
+ if (!this.pager.hasNext()) {
420
+ $li.addClass('disabled');
421
+ }
422
+ $ul.append($li);
423
+
424
+ this.$el.append($ul);
425
+ return this;
426
+ },
427
+
428
+ page: function(event) {
429
+ var $target = $(event.target), page;
430
+ if ($target.parent().hasClass('prev')) {
431
+ this.pager.prev();
432
+ } else if ($target.parent().hasClass('next')) {
433
+ this.pager.next();
434
+ }
435
+ else {
436
+ this.pager.page(parseInt($(event.target).html(), 10));
437
+ }
438
+ return false;
439
+ }
440
+ });
441
+
442
+ var Cell = Datagrid.Cell = Backbone.View.extend({
443
+ tagName: 'td',
444
+
445
+ initialize: function() {
446
+ this.column = this.options.column;
447
+ },
448
+
449
+ render: function() {
450
+ this._prepareValue();
451
+ this.$el.html(this.value);
452
+ return this;
453
+ },
454
+
455
+ _prepareValue: function() {
456
+ this.value = this.model.get(this.column.property);
457
+
458
+ if (this.value && this.value.length > 32) {
459
+ this.value = "<div>" + this.value + "</div>";
460
+ }
461
+ }
462
+ });
463
+
464
+ var CallbackCell = Datagrid.CallbackCell = Cell.extend({
465
+ initialize: function() {
466
+ CallbackCell.__super__.initialize.call(this);
467
+ this.callback = this.options.callback;
468
+ },
469
+
470
+ _prepareValue: function() {
471
+ this.value = this.callback(this.model.toJSON());
472
+ }
473
+ });
474
+
475
+ var ActionCell = Datagrid.ActionCell = Cell.extend({
476
+ initialize: function() {
477
+ ActionCell.__super__.initialize.call(this);
478
+ },
479
+
480
+ action: function() {
481
+ return this.options.action(this.model);
482
+ },
483
+
484
+ _prepareValue: function() {
485
+ var a = $('<a></a>');
486
+
487
+ a.html(this.options.label);
488
+ a.attr('href', this.options.href || '#');
489
+ if (this.options.actionClassName) {
490
+ a.addClass(this.options.actionClassName);
491
+ }
492
+ if (this.options.action) {
493
+ this.delegateEvents({
494
+ 'click a': this.action
495
+ });
496
+ }
497
+
498
+ this.value = a;
499
+ }
500
+ });
501
+
502
+ var HeaderCell = Datagrid.HeaderCell = Cell.extend({
503
+ initialize: function() {
504
+ HeaderCell.__super__.initialize.call(this);
505
+
506
+ this.sorter = this.options.sorter;
507
+
508
+ if (this.column.sortable) {
509
+ this.delegateEvents({click: 'sort'});
510
+ }
511
+ },
512
+
513
+ render: function() {
514
+ this._prepareValue();
515
+ var html = this.value, icon;
516
+
517
+ if (this.column.sortable) {
518
+ this.$el.addClass('sortable');
519
+ if (this.sorter.sortedBy(this.column.sortedProperty || this.column.property) || this.sorter.sortedBy(this.column.index)) {
520
+ if (this.sorter.sortedASC()) {
521
+ icon = 'icon-chevron-up';
522
+ } else {
523
+ icon = 'icon-chevron-down';
524
+ }
525
+ } else {
526
+ icon = 'icon-minus';
527
+ }
528
+
529
+ html += ' <i class="' + icon + ' pull-right"></i>';
530
+ }
531
+
532
+ this.$el.html(html);
533
+ return this;
534
+ },
535
+
536
+ sort: function() {
537
+ this.sorter.sort(this.column.sortedProperty || this.column.property);
538
+ }
539
+ });
540
+
541
+ var Pager = Datagrid.Pager = Backbone.Model.extend({
542
+ initialize: function() {
543
+ this.on('change:perPage change:total', function() {
544
+ this.totalPages(this.get('total'));
545
+ }, this);
546
+ if (this.has('total')) {
547
+ this.totalPages(this.get('total'));
548
+ }
549
+ },
550
+
551
+ update: function(options) {
552
+ _.each(['hasNext', 'hasPrev', 'total', 'totalPages', 'lastPage'], function(p) {
553
+ if (!_.isUndefined(options[p])) {
554
+ this.set(p, options[p]);
555
+ }
556
+ }, this);
557
+ },
558
+
559
+ totalPages: function(total) {
560
+ if (_.isNumber(total)) {
561
+ this.set('totalPages', Math.ceil(total/this.get('perPage')));
562
+ } else {
563
+ this.set('totalPages', undefined);
564
+ }
565
+ },
566
+
567
+ page: function(page) {
568
+ if (this.inBounds(page)) {
569
+ if (page === this.get('currentPage')) {
570
+ this.trigger('change:currentPage');
571
+ } else {
572
+ this.set('currentPage', page);
573
+ }
574
+ }
575
+ },
576
+
577
+ next: function() {
578
+ this.page(this.get('currentPage') + 1);
579
+ },
580
+
581
+ prev: function() {
582
+ this.page(this.get('currentPage') - 1);
583
+ },
584
+
585
+ hasTotal: function() {
586
+ return this.has('totalPages');
587
+ },
588
+
589
+ hasNext: function() {
590
+ if (this.hasTotal()) {
591
+ return this.get('currentPage') < this.get('totalPages');
592
+ } else {
593
+ return this.get('hasNext');
594
+ }
595
+ },
596
+
597
+ hasPrev: function() {
598
+ if (this.has('hasPrev')) {
599
+ return this.get('hasPrev');
600
+ } else {
601
+ return this.get('currentPage') > 1;
602
+ }
603
+ },
604
+
605
+ inBounds: function(page) {
606
+ return !this.hasTotal() || page > 0 && page <= this.get('totalPages');
607
+ },
608
+
609
+ set: function() {
610
+ var args = [];
611
+ for (var i = 0; i < arguments.length; i++) {
612
+ args.push(arguments[i]);
613
+ }
614
+ args[2] = _.extend({}, args[2], {validate: true});
615
+ Backbone.Model.prototype.set.apply(this, args);
616
+ },
617
+
618
+ validate: function(attrs) {
619
+ if (attrs.perPage < 1) {
620
+ throw new Error('perPage must be greater than zero.');
621
+ }
622
+ }
623
+ });
624
+
625
+ var Sorter = Datagrid.Sorter = Backbone.Model.extend({
626
+ sort: function(column, order) {
627
+ if (!order && this.get('column') === column) {
628
+ this.toggleOrder();
629
+ } else {
630
+ this.set({
631
+ column: column,
632
+ order: order || Sorter.ASC
633
+ });
634
+ }
635
+ },
636
+
637
+ sortedBy: function(column) {
638
+ return this.get('column') === column;
639
+ },
640
+
641
+ sortedASC: function() {
642
+ return this.get('order') === Sorter.ASC;
643
+ },
644
+
645
+ sortedDESC: function() {
646
+ return this.get('order') === Sorter.DESC;
647
+ },
648
+
649
+ toggleOrder: function() {
650
+ if (this.get('order') === Sorter.ASC) {
651
+ this.set('order', Sorter.DESC);
652
+ } else {
653
+ this.set('order', Sorter.ASC);
654
+ }
655
+ }
656
+ });
657
+
658
+ Sorter.ASC = 'asc';
659
+ Sorter.DESC = 'desc';
660
+
661
+ Backbone.Datagrid = Datagrid;
662
+ })();