pulse-meter 0.2.11 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.rbenv-version +1 -1
  2. data/README.md +32 -16
  3. data/Rakefile +29 -10
  4. data/examples/basic.ru +1 -1
  5. data/examples/full/server.ru +1 -1
  6. data/examples/minimal/server.ru +1 -1
  7. data/lib/pulse-meter.rb +1 -0
  8. data/lib/pulse-meter/observer.rb +117 -0
  9. data/lib/pulse-meter/sensor.rb +1 -0
  10. data/lib/pulse-meter/sensor/timeline.rb +3 -2
  11. data/lib/pulse-meter/sensor/timelined/multi_percentile.rb +43 -0
  12. data/lib/pulse-meter/version.rb +1 -1
  13. data/lib/pulse-meter/visualize/app.rb +28 -12
  14. data/lib/pulse-meter/visualize/coffee/application.coffee +40 -0
  15. data/lib/pulse-meter/visualize/coffee/collections/page_info_list.coffee +17 -0
  16. data/lib/pulse-meter/visualize/coffee/collections/sensor_info_list.coffee +4 -0
  17. data/lib/pulse-meter/visualize/coffee/collections/widget_list.coffee +14 -0
  18. data/lib/pulse-meter/visualize/coffee/extensions.coffee +26 -0
  19. data/lib/pulse-meter/visualize/coffee/models/dinamic_widget.coffee +34 -0
  20. data/lib/pulse-meter/visualize/coffee/models/page_info.coffee +2 -0
  21. data/lib/pulse-meter/visualize/coffee/models/sensor_info.coffee +2 -0
  22. data/lib/pulse-meter/visualize/coffee/models/widget.coffee +54 -0
  23. data/lib/pulse-meter/visualize/coffee/presenters/area.coffee +2 -0
  24. data/lib/pulse-meter/visualize/coffee/presenters/gauge.coffee +11 -0
  25. data/lib/pulse-meter/visualize/coffee/presenters/line.coffee +2 -0
  26. data/lib/pulse-meter/visualize/coffee/presenters/pie.coffee +20 -0
  27. data/lib/pulse-meter/visualize/coffee/presenters/series.coffee +44 -0
  28. data/lib/pulse-meter/visualize/coffee/presenters/table.coffee +10 -0
  29. data/lib/pulse-meter/visualize/coffee/presenters/timeline.coffee +13 -0
  30. data/lib/pulse-meter/visualize/coffee/presenters/widget.coffee +65 -0
  31. data/lib/pulse-meter/visualize/coffee/router.coffee +21 -0
  32. data/lib/pulse-meter/visualize/coffee/views/dynamic_chart.coffee +91 -0
  33. data/lib/pulse-meter/visualize/coffee/views/dynamic_widget.coffee +58 -0
  34. data/lib/pulse-meter/visualize/coffee/views/page_title.coffee +17 -0
  35. data/lib/pulse-meter/visualize/coffee/views/page_titles.coffee +15 -0
  36. data/lib/pulse-meter/visualize/coffee/views/sensor_info_list.coffee +19 -0
  37. data/lib/pulse-meter/visualize/coffee/views/widget.coffee +99 -0
  38. data/lib/pulse-meter/visualize/coffee/views/widget_chart.coffee +13 -0
  39. data/lib/pulse-meter/visualize/coffee/views/widget_list.coffee +15 -0
  40. data/lib/pulse-meter/visualize/layout.rb +4 -4
  41. data/lib/pulse-meter/visualize/public/css/application.css +13 -4
  42. data/lib/pulse-meter/visualize/public/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  43. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  44. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  45. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  46. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  47. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_ffffff_1x400.png +0 -0
  48. data/lib/pulse-meter/visualize/public/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  49. data/lib/pulse-meter/visualize/public/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png +0 -0
  50. data/lib/pulse-meter/visualize/public/css/images/ui-icons_222222_256x240.png +0 -0
  51. data/lib/pulse-meter/visualize/public/css/images/ui-icons_2e83ff_256x240.png +0 -0
  52. data/lib/pulse-meter/visualize/public/css/images/ui-icons_454545_256x240.png +0 -0
  53. data/lib/pulse-meter/visualize/public/css/images/ui-icons_888888_256x240.png +0 -0
  54. data/lib/pulse-meter/visualize/public/css/images/ui-icons_cd0a0a_256x240.png +0 -0
  55. data/lib/pulse-meter/visualize/public/css/images/ui-icons_f6cf3b_256x240.png +0 -0
  56. data/lib/pulse-meter/visualize/public/css/jquery-ui-1.8.16.bootstrap.css +1320 -0
  57. data/lib/pulse-meter/visualize/public/js/application.js +900 -691
  58. data/lib/pulse-meter/visualize/public/js/jquery-ui-1.8.16.bootstrap.min.js +791 -0
  59. data/lib/pulse-meter/visualize/public/js/jquery-ui-1.8.23.custom.min.js +21 -0
  60. data/lib/pulse-meter/visualize/public/js/jquery-ui-timepicker-addon.js +1687 -0
  61. data/lib/pulse-meter/visualize/sensor.rb +2 -2
  62. data/lib/pulse-meter/visualize/views/main.haml +2 -3
  63. data/lib/pulse-meter/visualize/views/sensors.haml +14 -1
  64. data/lib/pulse-meter/visualize/views/widgets/area.haml +46 -24
  65. data/lib/pulse-meter/visualize/views/widgets/line.haml +46 -23
  66. data/lib/pulse-meter/visualize/views/widgets/table.haml +37 -15
  67. data/lib/pulse-meter/visualize/widgets/timeline.rb +20 -5
  68. data/pulse-meter.gemspec +4 -0
  69. data/spec/pulse_meter/observer_spec.rb +252 -0
  70. data/spec/pulse_meter/sensor/timelined/multi_percentile_spec.rb +21 -0
  71. data/spec/pulse_meter/visualize/sensor_spec.rb +5 -5
  72. data/spec/pulse_meter/visualize/widgets/area_spec.rb +1 -74
  73. data/spec/pulse_meter/visualize/widgets/line_spec.rb +1 -73
  74. data/spec/pulse_meter/visualize/widgets/table_spec.rb +1 -73
  75. data/spec/shared_examples/timeline_sensor.rb +10 -0
  76. data/spec/shared_examples/widget.rb +97 -0
  77. data/spec/spec_helper.rb +1 -0
  78. metadata +120 -5
  79. data/lib/pulse-meter/visualize/public/js/application.coffee +0 -616
@@ -1,616 +0,0 @@
1
- document.startApp = ->
2
- globalOptions = gon.options
3
-
4
- String::capitalize = ->
5
- @charAt(0).toUpperCase() + @slice(1)
6
- String::strip = ->
7
- if String::trim? then @trim() else @replace /^\s+|\s+$/g, ""
8
-
9
- Number::humanize = ->
10
- interval = this
11
- res = ""
12
- s = interval % 60
13
- res = "#{s} s" if s > 0
14
- interval = (interval - s) / 60
15
- return res unless interval > 0
16
-
17
- m = interval % 60
18
- res = "#{m} m #{res}".strip() if m > 0
19
- interval = (interval - m) / 60
20
- return res unless interval > 0
21
-
22
- h = interval % 24
23
- res = "#{h} h #{res}".strip() if h > 0
24
- d = (interval - h) / 24
25
- if d > 0
26
- "#{d} d #{res}".strip()
27
- else
28
- res
29
-
30
- PageInfo = Backbone.Model.extend {
31
- }
32
-
33
- PageInfoList = Backbone.Collection.extend {
34
- model: PageInfo
35
- selected: ->
36
- @find (m) ->
37
- m.get 'selected'
38
-
39
- selectFirst: ->
40
- @at(0).set('selected', true) if @length > 0
41
-
42
- selectNone: ->
43
- @each (m) ->
44
- m.set 'selected', false
45
-
46
- selectPage: (id) ->
47
- @each (m) ->
48
- m.set 'selected', m.id == id
49
- }
50
-
51
- pageInfos = new PageInfoList
52
-
53
- PageTitleView = Backbone.View.extend {
54
- tagName: 'li'
55
-
56
- template: _.template('<a href="#/pages/<%= id %>"><%= title %></a>')
57
-
58
- initialize: ->
59
- @model.bind 'change', @render, this
60
- @model.bind 'destroy', @remove, this
61
-
62
- render: ->
63
- @$el.html @template(@model.toJSON())
64
- if @model.get('selected')
65
- @$el.addClass('active')
66
- else
67
- @$el.removeClass('active')
68
- }
69
-
70
- PageTitlesView = Backbone.View.extend {
71
- initialize: ->
72
- pageInfos.bind 'reset', @render, this
73
-
74
- addOne: (pageInfo) ->
75
- view = new PageTitleView {
76
- model: pageInfo
77
- }
78
- view.render()
79
- $('#page-titles').append(view.el)
80
-
81
- render: ->
82
- $('#page-titles').empty()
83
- pageInfos.each(@addOne)
84
- }
85
-
86
- pageTitlesApp = new PageTitlesView
87
- pageInfos.reset gon.pageInfos
88
-
89
-
90
- class WidgetPresenter
91
- constructor: (model, el) ->
92
- @model = model
93
- chartClass = @chartClass()
94
- @chart = new chartClass(el)
95
- @draw()
96
-
97
- get: (arg) -> @model.get(arg)
98
-
99
- dateOffset: ->
100
- if globalOptions.useUtc
101
- (new Date).getTimezoneOffset() * 60000
102
- else
103
- 0
104
-
105
- options: ->
106
- {
107
- title: @get('title')
108
- height: 300
109
- }
110
-
111
- mergedOptions: ->
112
- pageOptions = if pageInfos.selected()
113
- pageInfos.selected().get('gchartOptions')
114
- else
115
- {}
116
-
117
- $.extend(true,
118
- @options(),
119
- globalOptions.gchartOptions,
120
- pageOptions,
121
- @get('gchartOptions')
122
- )
123
-
124
- data: -> new google.visualization.DataTable
125
-
126
- chartClass: -> google.visualization[@visualization]
127
-
128
- cutoff: ->
129
-
130
- cutoffValue: (v, min, max) ->
131
- if v?
132
- if min? && v < min
133
- min
134
- else if max? && v > max
135
- max
136
- else
137
- v
138
- else
139
- 0
140
-
141
- draw: (min, max) ->
142
- @cutoff(min, max)
143
- @chart.draw(@data(), @mergedOptions())
144
-
145
- WidgetPresenter.create = (model, el) ->
146
- type = model.get('type')
147
- if type? && type.match(/^\w+$/)
148
- presenterClass = eval("#{type.capitalize()}Presenter")
149
- new presenterClass(model, el)
150
- else
151
- null
152
-
153
- class PiePresenter extends WidgetPresenter
154
- visualization: 'PieChart'
155
-
156
- cutoff: ->
157
-
158
- data: ->
159
- data = super()
160
- data.addColumn('string', 'Title')
161
- data.addColumn('number', @get('valuesTitle'))
162
- data.addRows(@get('series').data)
163
- data
164
-
165
- options: ->
166
- $.extend true, super(), {
167
- slices: @get('series').options
168
- legend: {
169
- position: 'bottom'
170
- }
171
- }
172
-
173
- class TimelinePresenter extends WidgetPresenter
174
- data: ->
175
- data = super()
176
- data.addColumn('datetime', 'Time')
177
- dateOffset = @dateOffset() + @get('interval') * 1000
178
- series = @get('series')
179
- _.each series.titles, (t) ->
180
- data.addColumn('number', t)
181
-
182
- _.each series.rows, (row) ->
183
- row[0] = new Date(row[0] + dateOffset)
184
- data.addRow(row)
185
- data
186
-
187
- class SeriesPresenter extends TimelinePresenter
188
- options: ->
189
- secondPart = if @get('interval') % 60 == 0 then '' else ':ss'
190
- format = if @model.timespan() > 24 * 60 * 60
191
- "yyyy.MM.dd HH:mm#{secondPart}"
192
- else
193
- "HH:mm#{secondPart}"
194
-
195
- $.extend true, super(), {
196
- lineWidth: 1
197
- chartArea: {
198
- width: '100%'
199
- }
200
- legend: {
201
- position: 'bottom'
202
- }
203
- vAxis: {
204
- title: @valuesTitle()
205
- textPosition: 'in'
206
- }
207
- hAxis: {
208
- format: format
209
- }
210
- series: @get('series').options
211
- axisTitlesPosition: 'in'
212
- }
213
-
214
- valuesTitle: ->
215
- if @get('valuesTitle')
216
- "#{@get('valuesTitle')} / #{@humanizedInterval()}"
217
- else
218
- @humanizedInterval()
219
-
220
-
221
- humanizedInterval: ->
222
- @get('interval').humanize()
223
-
224
- cutoff: (min, max) ->
225
- _.each(@get('series').rows, (row) ->
226
- for i in [1 .. row.length - 1]
227
- value = row[i]
228
- value = 0 unless value?
229
- row[i] = @cutoffValue(value, min, max)
230
- , this)
231
-
232
-
233
- class LinePresenter extends SeriesPresenter
234
- visualization: 'LineChart'
235
-
236
- class AreaPresenter extends SeriesPresenter
237
- visualization: 'AreaChart'
238
-
239
- class TablePresenter extends TimelinePresenter
240
- visualization: 'Table'
241
-
242
- cutoff: ->
243
-
244
- options: ->
245
- $.extend true, super(), {
246
- sortColumn: 0
247
- sortAscending: false
248
- }
249
-
250
- class GaugePresenter extends WidgetPresenter
251
- visualization: 'Gauge'
252
-
253
- cutoff: ->
254
-
255
- data: ->
256
- data = super()
257
- data.addColumn('string', 'Label')
258
- data.addColumn('number', @get('valuesTitle'))
259
- data.addRows(@get('series'))
260
- data
261
-
262
- Widget = Backbone.Model.extend {
263
- initialize: ->
264
- @needRefresh = true
265
- @setNextFetch()
266
- @timespanInc = 0
267
-
268
- increaseTimespan: (inc) ->
269
- @timespanInc = @timespanInc + inc
270
- @forceUpdate()
271
-
272
- resetTimespan: ->
273
- @timespanInc = 0
274
- @forceUpdate()
275
-
276
- timespan: -> @get('timespan') + @timespanInc
277
-
278
- url: ->
279
- timespan = @timespan()
280
- if _.isNaN(timespan)
281
- "#{@collection.url()}/#{@get('id')}"
282
- else
283
- "#{@collection.url()}/#{@get('id')}?timespan=#{timespan}"
284
-
285
- time: -> (new Date()).getTime()
286
-
287
- setNextFetch: ->
288
- @nextFetch = @time() + @get('redrawInterval') * 1000
289
-
290
- setRefresh: (needRefresh) ->
291
- @needRefresh = needRefresh
292
-
293
- needFetch: ->
294
- interval = @get('redrawInterval')
295
- @time() > @nextFetch && @needRefresh && interval? && interval > 0
296
-
297
- refetch: ->
298
- if @needFetch()
299
- @forceUpdate()
300
- @setNextFetch()
301
-
302
- forceUpdate: ->
303
- @fetch {
304
- success: (model, response) ->
305
- model.trigger('redraw')
306
- }
307
-
308
- }
309
-
310
- DynamicWidget = Backbone.Model.extend {
311
-
312
- increaseTimespan: (inc) ->
313
- @set('timespan', @timespan() + inc)
314
-
315
- resetTimespan: ->
316
- @set('timespan', null)
317
-
318
- timespan: -> @get('timespan')
319
-
320
- sensorArgs: ->
321
- _.map(@get('sensorIds'), (name) -> "sensor[]=#{name}").join('&')
322
-
323
- url: ->
324
- url = "#{ROOT}dynamic_widget?#{@sensorArgs()}&type=#{@get('type')}"
325
-
326
- timespan = @timespan()
327
- url = "#{url}&timespan=#{timespan}" if timespan? && !_.isNaN(timespan)
328
-
329
- url
330
-
331
- forceUpdate: ->
332
- @fetch {
333
- success: (model, response) ->
334
- model.trigger('redraw')
335
- }
336
- }
337
-
338
- WidgetList = Backbone.Collection.extend {
339
- model: Widget
340
- url: ->
341
- ROOT + 'pages/' + pageInfos.selected().id + '/widgets'
342
- }
343
-
344
- SensorInfo = Backbone.Model.extend {
345
-
346
- }
347
-
348
- SensorInfoList = Backbone.Collection.extend {
349
- model: SensorInfo
350
- url: ->
351
- ROOT + 'sensors'
352
- }
353
-
354
-
355
- SensorInfoListView = Backbone.View.extend {
356
- tagName: 'div'
357
-
358
- template: ->
359
- _.template($("#sensor-list").html())
360
-
361
- initialize: (sensorInfo) ->
362
- @sensorInfo = sensorInfo
363
- @sensorInfo.bind 'reset', @render, this
364
-
365
- render: ->
366
- @$el.html @template()({sensors: @sensorInfo.toJSON()})
367
-
368
- selectedSensors: ->
369
- checked = _.filter @$el.find('.sensor-box'), (el) -> $(el).is(':checked')
370
- ids = {}
371
- _.each checked, (box) -> ids[box.id] = true
372
- selected = @sensorInfo.filter (sensor) -> ids[sensor.id]
373
-
374
- }
375
-
376
- DynamicChartView = Backbone.View.extend {
377
- initialize: ->
378
- @sensors = []
379
- @type = 'Area'
380
- @widget = new DynamicWidget
381
-
382
- @widget.bind('destroy', @remove, this)
383
- @widget.bind('redraw', @redrawChart, this)
384
-
385
- tagName: 'div'
386
-
387
- events: {
388
- "click #refresh-chart": 'update'
389
- "click #extend-timespan": 'extendTimespan'
390
- "click #reset-timespan": 'resetTimespan'
391
- }
392
-
393
- template: -> _.template($("#dynamic-widget-plotarea").html())
394
-
395
- render: ->
396
- @$el.html(@template()())
397
-
398
- extendTimespan: ->
399
- select = @$el.find("#extend-timespan-val")
400
- val = select.first().val()
401
- @widget.increaseTimespan(parseInt(val))
402
- @update()
403
-
404
- resetTimespan: ->
405
- @widget.resetTimespan()
406
- @update()
407
-
408
- sensorIds: -> _.map(@sensors, (s) -> s.id)
409
-
410
- redrawChart: ->
411
- if @presenter
412
- @presenter.draw()
413
- else
414
- @presenter = WidgetPresenter.create(@widget, @chartContainer())
415
-
416
-
417
- chartContainer: ->
418
- @$el.find('#chart')[0]
419
-
420
- update: ->
421
- @widget.forceUpdate() if @sensors.length > 0
422
-
423
- draw: (sensors, type) ->
424
- @sensors = sensors
425
- @type = type
426
-
427
- @widget.set('sensorIds', @sensorIds())
428
- @widget.set('type', @type)
429
-
430
- @presenter = null
431
- $(@chartContainer()).empty()
432
- @widget.forceUpdate()
433
- }
434
-
435
- DynamicWidgetView = Backbone.View.extend {
436
- tagName: 'div'
437
-
438
- initialize: ->
439
- @sensorInfo = new SensorInfoList
440
-
441
- @sensorListView = new SensorInfoListView(@sensorInfo)
442
- @chartView = new DynamicChartView
443
-
444
- @$el.html(@template()())
445
-
446
- @$el.find('#sensor-list-area').append(@sensorListView.el)
447
-
448
- @chartView.render()
449
- @$el.find('#dynamic-plotarea').append(@chartView.el)
450
-
451
- events: {
452
- "click #sensor-controls #refresh": 'refresh'
453
- "click #sensor-controls #draw": 'drawChart'
454
- }
455
-
456
- template: ->
457
- _.template($("#dynamic-widget").html())
458
-
459
- errorTemplate: -> _.template($("#dynamic-widget-error").html())
460
-
461
- error: (error)->
462
- @$el.find('#errors').append(@errorTemplate()(error: error))
463
-
464
-
465
- refresh: ->
466
- @sensorInfo.fetch()
467
-
468
- intervalsEqual: (sensors) ->
469
- interval = sensors[0].get('interval')
470
- badIntervals = _.filter(sensors, (s) ->
471
- s.get('interval') != interval
472
- )
473
- badIntervals.length == 0
474
-
475
- drawChart: ->
476
- selectedSensors = @sensorListView.selectedSensors()
477
- return unless selectedSensors.length > 0
478
-
479
- unless @intervalsEqual(selectedSensors)
480
- @error('Selected sensors have different intervals')
481
- return
482
-
483
- type = @$el.find('#chart-type').val()
484
- @chartView.draw(selectedSensors, type)
485
-
486
- render: (container) ->
487
- container.empty()
488
- container.append(@$el)
489
- @sensorInfo.fetch()
490
- @chartView.update()
491
-
492
- }
493
-
494
-
495
- WidgetChartView = Backbone.View.extend {
496
- tagName: 'div'
497
-
498
- initialize: ->
499
- @model.bind 'destroy', @remove, this
500
-
501
- updateData: (min, max) ->
502
- @presenter.draw(min, max)
503
-
504
- render: ->
505
- @presenter = WidgetPresenter.create(@model, @el)
506
- }
507
-
508
- WidgetView = Backbone.View.extend {
509
- tagName: 'div'
510
-
511
- template: ->
512
- _.template($(".widget-template[data-widget-type=\"#{@model.get('type')}\"]").html())
513
-
514
- initialize: ->
515
- @model.bind('destroy', @remove, this)
516
- @model.bind('redraw', @updateChart, this)
517
-
518
- events: {
519
- "click #refresh": 'refresh'
520
- "click #need-refresh": 'setRefresh'
521
- "click #extend-timespan": 'extendTimespan'
522
- "click #reset-timespan": 'resetTimespan'
523
- }
524
-
525
- refresh: ->
526
- @model.forceUpdate()
527
-
528
- setRefresh: ->
529
- needRefresh = @$el.find('#need-refresh').is(":checked")
530
- @model.setRefresh(needRefresh)
531
- true
532
-
533
- extendTimespan: ->
534
- select = @$el.find("#extend-timespan-val")
535
- val = select.first().val()
536
- @model.increaseTimespan(parseInt(val))
537
-
538
- resetTimespan: ->
539
- @model.resetTimespan()
540
-
541
- renderChart: ->
542
- @chartView.render()
543
-
544
- updateChart: ->
545
- @chartView.updateData(@cutoffMin(), @cutoffMax())
546
-
547
- render: ->
548
- @$el.html @template(@model.toJSON())
549
- @chartView = new WidgetChartView {
550
- model: @model
551
- }
552
- @$el.find("#plotarea").append(@chartView.el)
553
- @$el.addClass("span#{@model.get('width')}")
554
-
555
- cutoffMin: ->
556
- val = parseFloat(@controlValue('#cutoff-min'))
557
- if _.isNaN(val) then null else val
558
-
559
- cutoffMax: ->
560
- val = parseFloat(@controlValue('#cutoff-max'))
561
- if _.isNaN(val) then null else val
562
-
563
- controlValue: (id) ->
564
- val = @$el.find(id).first().val()
565
-
566
-
567
- }
568
-
569
- widgetList = new WidgetList
570
- setInterval( ->
571
- if pageInfos.selected()
572
- widgetList.each (w) ->
573
- w.refetch()
574
- , 200)
575
-
576
- WidgetListView = Backbone.View.extend {
577
- initialize: ->
578
- widgetList.bind 'reset', @render, this
579
-
580
- render: ->
581
- container = $('#widgets')
582
- container.empty()
583
- widgetList.each (w) ->
584
- view = new WidgetView { model: w }
585
- view.render()
586
- container.append(view.el)
587
- view.renderChart()
588
- }
589
-
590
- widgetListApp = new WidgetListView
591
-
592
- AppRouter = Backbone.Router.extend {
593
- routes: {
594
- 'pages/:id': 'getPage'
595
- 'custom': 'custom'
596
- '*actions': 'defaultRoute'
597
- }
598
- getPage: (ids) ->
599
- id = parseInt(ids)
600
- pageInfos.selectPage(id)
601
- widgetList.fetch()
602
- custom: ->
603
- pageInfos.selectNone()
604
- dynamicWidget = new DynamicWidgetView
605
- dynamicWidget.render($('#widgets'))
606
- defaultRoute: (actions) ->
607
- if pageInfos.length > 0
608
- @navigate('//pages/1')
609
- else
610
- @navigate('//custom')
611
- }
612
-
613
- appRouter = new AppRouter
614
-
615
- Backbone.history.start()
616
-