helios 0.0.1 → 0.0.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 (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
+ })();