BaseView::initialize ...
@metric_views = new ViewList
@datasources = DataSource.getAllSources()
+ # @on 'update', @onUpdate, this
@model.metrics
.on 'add', @addMetric, this
.on 'remove', @removeMetric, this
console.log "#this.addMetric!", metric
return metric if @metric_views.findByModel metric
view = new MetricEditView {model:metric, @graph_id, dataset:@model, @datasources}
- view.on 'update', @onUpdateMetric, this
+ .on 'metric-update', @onUpdateMetric, this
+ .on 'metric-change', @onUpdateMetric, this
@metric_views.push @addSubview view
metric
@$el.css 'min-height', newMinHeight
onUpdateMetric: ->
- @renderSubviews()
+ console.log "#this.onUpdateMetric!"
+ @trigger 'metric-change', @model, this
+ @render()
+
+
# }}}
* @returns {Array<Date>} The date column.
*/
getDateColumn: ->
- dates = @metrics.invoke 'getDateColumn'
+ dates = @metrics.onlyOk().invoke 'getDateColumn'
maxLen = _.max _.pluck dates, 'length'
_.find dates, -> it.length is maxLen
* @returns {Array<Array>} List of all columns except the date column.
*/
getDataColumns: ->
- @metrics.invoke 'getData'
+ @metrics.onlyOk().invoke 'getData'
/**
* @returns {Array<String>} List of column labels.
*/
getLabels: ->
- ['Date'].concat @metrics.pluck 'label'
+ [ 'Date' ].concat @metrics.onlyOk().invoke 'getLabel'
# }}}
+ getColors: ->
+ @metrics.onlyOk().pluck 'color'
newMetric: ->
index = @metrics.length
m
onMetricChange: ->
+ console.log "#this.onMetricChange! ready=#{@ready}"
@resetReady()
- # @metrics.reset @get 'metrics'
+ @metrics.reset @get 'metrics'
@load()
# TODO: toJSON() must ensure columns in MetricList are ordered by index
@trigger 'edit-metric', metric, view, this
this
- render: ->
- this
# }}}
initialize: ->
@graph_id = @options.graph_id
BaseView::initialize ...
+ @on 'update', @onUpdate, this
toTemplateLocals: ->
- m = @model.toJSON()
+ m = DataSetMetricView.__super__.toTemplateLocals ...
+
+ # XXX: Icons/classes for visible/disabled?
m import
graph_id : @graph_id
+ label : @model.getLabel()
viewClasses : _.compact([
if @model.isOk() then 'valid' else 'invalid',
if m.visible then 'visible' else 'hidden',
"#{ts.start} to #{ts.end} by #{ts.step}"
else
'—'
-
- # XXX: Icons/classes for visible/disabled?
- { $, _, op, @model, view:this } import m
+
+ onUpdate: ->
+ @$ '.col-color' .css 'color', @model.get 'color'
# }}}
@$ '.source-metrics .datasource-source-metric' .removeClass 'active'
el.addClass 'active'
@model.set {source_col, source_id}
- # @trigger 'edit-metric', @model
+ @trigger 'metric-change', @model, this
# }}}
* Model is a Metric.
*/
MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
+ __bind__ : <[ onChange ]>
tagName : 'section'
className : 'metric-edit-ui'
template : require 'kraken/template/metric-edit'
this import @options.{graph_id, dataset, datasources}
@model or= new Metric
BaseView::initialize ...
+ @on 'attach', @onAttach, this
@datasource_ui_view = new DataSourceUIView {@model, @graph_id, @dataset, @datasources}
@addSubview @datasource_ui_view
- .on 'update', ~> @trigger 'update', this
- # @$ '.metric-datasource' .append @datasource_ui_view.render().el
+ .on 'metric-update', ~> @trigger 'update', this
+ .on 'metric-change', @onSourceMetricChange, this
toTemplateLocals: ->
- locals = BaseView::toTemplateLocals ...
- locals import { @graph_id, @dataset, @datasources }
-
- # build: ->
- # BaseView::build ...
- # if @datasource_ui_view
- # @$ '.metric-datasource' .append @datasource_ui_view.render().el
- # this
+ locals = MetricEditView.__super__.toTemplateLocals ...
+ locals import {
+ @graph_id, @dataset, @datasources
+ placeholder_label: @model.getPlaceholderLabel()
+ }
update: ->
- color = @model.get 'color'
- @$ '.color-swatch' .css 'background-color', color
- @$ '.metric-color' .val color
- @$ '.metric-label' .val @model.get 'label'
+ MetricEditView.__super__.update ...
+
+ # Update the color picker
+ @$ '.color-swatch'
+ .data 'color', @model.get 'color'
+ .colorpicker 'update'
+ # @$ '.color-swatch' .css 'background-color', color
+ # @$ '.metric-color' .val color
this
- onChange: ->
+ onAttach: ->
+ console.log "#this.onAttach!"
+ @$ '.color-swatch'
+ .data 'color', @model.get 'color'
+ .colorpicker()
+ .on 'hide', @onChange
+
+ onChange: (evt) ->
attrs = @$ 'form.metric-edit-form' .formData()
- @model.set attrs, {+silent}
- @trigger 'update', this
+ # attrs.color = that if evt?.color
+ same = _.isEqual @model.attributes, attrs
+
+ console.log "#this.onChange! (same? #same)"
+ _.dump @model.attributes, 'old', not same
+ _.dump attrs, 'new', not same
+ # _.dump attrs, "#this.onChange! (same? #same)", not same
+
+ unless _.isEqual @model.attributes, attrs
+ @model.set attrs, {+silent}
+ @trigger 'metric-update', this
- onMetricChange: (metric) ->
- console.log "#this.onMetricChange!", metric
+ onSourceMetricChange: (metric) ->
+ console.log "#this.onSourceMetricChange!", metric
+ @trigger 'metric-change', @model, this
this
# }}}
* @class
*/
Metric = exports.Metric = BaseModel.extend do # {{{
+ NEW_METRIC_LABEL : 'New Metric'
urlRoot : '/metrics'
/**
*/
source : null
+ is_def_label : true
+
defaults : ->
- index : 0
- label : 'New Metric'
- type : 'int'
- timespan : { start:null, end:null, step:null }
- disabled : false
+ index : 0
+ label : ''
+ type : 'int'
+ timespan : { start:null, end:null, step:null }
+ disabled : false
# DataSource
- source_id : null
- source_col : -1
+ source_id : null
+ source_col : -1
# Chart Options
- color : null
- visible : true
- format_value : null
- format_axis : null
+ color : null
+ visible : true
+ format_value : null
+ format_axis : null
- transforms : []
- scale : 1.0
+ transforms : []
+ scale : 1.0
initialize : ->
BaseModel::initialize ...
- @on 'change:source_id', @load, this
- @on 'change:source_col', @updateId, this
+ @is_def_label = @isDefaultLabel()
+ @on 'change:source_id', @load, this
+ @on 'change:source_col', @updateId, this
+ @on 'change:label', @updateLabel, this
@load()
getData: ->
@source.getColumn @get 'source_col'
+ getLabel: ->
+ @get('label') or @getPlaceholderLabel()
+
+ getPlaceholderLabel: ->
+ col = @get 'source_col'
+ name = "#{@source.get 'shortName'}, #{@source.getColumnName col}" if @source and col > 0
+ name or @NEW_METRIC_LABEL
+
+ getSourceColumnName: ->
+ col = @get 'source_col'
+ @source.getColumnName col if @source and col > 0
+
load: (opts={}) ->
source_id = @get 'source_id'
else
console.log "#{this}.load() complete!"
@source = source
+ @is_def_label = @isDefaultLabel()
@updateId()
@triggerReady()
this
+ isDefaultLabel: ->
+ label = @get 'label'
+ not label or label is @getSourceColumnName() or label is @NEW_METRIC_LABEL
+
+ updateLabel: ->
+ return this unless @source
+ label = @get 'label'
+ if not label or @is_def_label
+ @set 'label', ''
+ @is_def_label = true
+ else
+ @is_def_label = @isDefaultLabel()
+ this
+
updateId: ->
- if (source_id = @get('source_id')) and (source_col = @get('source_col'))
+ source_id = @get 'source_id'
+ source_col = @get 'source_col'
+ if source_id and source_col?
@id = "#source_id[#source_col]"
+ @updateLabel()
this
* attempt to graph unconfigured crap.
*/
isOk: ->
- (label = @get('label')) and label is not 'New Metric'
- and @get('source_id') and @get('source_col') >= 0
- and _.every @get('timespan'), op.ok
-
- getSourceColumnName: ->
- @source?.getColumnName @get 'source_col'
+ @source # and _.every @get('timespan'), op.ok
# }}}
comparator: (metric) ->
metric.get('index') ? Infinity
+ onlyOk: ->
+ new MetricList @filter -> it.isOk()
+
# }}}
### FIXME: LOLHACKS ###
@data_view
.on 'start-waiting', @wait, this
.on 'stop-waiting', @unwait, this
- .on 'change', @onDataChange, this
+ .on 'metric-change', @onDataChange, this
# Rerender once the tab is visible
@checkWaiting()
Seq()
.seq_ (next) ~>
- @model.on 'ready', next.ok .load()
+ @model.once 'ready', next.ok .load()
.seq_ (next) ~>
- @model.on 'data-ready', next.ok .loadData()
+ @model.once 'data-ready', next.ok .loadData()
.seq ~> @onReady()
onReady: ->
# XXX: use @model.changedAttributes() to calculate what to update
options = @chartOptions() #import size
options import do
+ colors : dataset.getColors()
labels : dataset.getLabels()
labelsDiv : @$ '.graph-label' .0
valueFormatter : @numberFormatterHTML
History.pushState data, title, url
- /**
- * Retrieve or construct the spinner.
- */
- spinner: ->
- el = @$ '.graph-spinner'
- unless el.data 'spinner'
- ### Spin.js Options ###
- opts =
- lines : 9 # [12] The number of lines to draw
- length : 2 # [7] The length of each line
- width : 1 # [5] The line thickness
- radius : 7 # [10] The radius of the inner circle
- rotate : -10.5 # [0] rotation offset
- trail : 50 # [100] Afterglow percentage
- opacity : 1/4 # [1/4] Opacity of the lines
- shadow : false # [false] Whether to render a shadow
- speed : 1 # [1] Spins per second
- zIndex : 2e9 # [2e9] zIndex; uses a very high z-index by default
- color : '#000' # ['#000'] Line color; '#rgb' or '#rrggbb'.
- top : 'auto' # ['auto'] Top position relative to parent in px; 'auto' = center vertically.
- left : 'auto' # ['auto'] Left position relative to parent in px; 'auto' = center horizontally.
- className : 'spinner' # ['spinner'] CSS class to assign to the element
- fps : 20 # [20] Frames per second when falling back to `setTimeout()`.
- hwaccel : Modernizr.csstransforms3d # [false] Whether to use hardware acceleration.
-
- isHidden = el.css('display') is 'none'
- el.show().spin opts
- el.hide() if isHidden
- el
-
- checkWaiting: ->
- spinner = @spinner()
- if isWaiting = (@waitingOn > 0)
- spinner.show()
- if spinner.find('.spinner').css('top') is '0px'
- # delete spinner
- spinner.spin(false)
- # re-add to DOM with correct parent sizing
- @spinner()
- else
- spinner.hide()
- isWaiting
-
-
### }}}
### Formatters {{{
@render()
onStartWaiting: ->
- console.log "#this.onStartWaiting!", @checkWaiting()
+ status = @checkWaiting()
+ # console.log "#this.onStartWaiting!", status
onStopWaiting: ->
- console.log "#this.onStopWaiting!", @checkWaiting()
+ status = @checkWaiting()
+ # console.log "#this.onStopWaiting!", status
onModelError: ->
console.error "#this.error!", arguments
@model.setOption(key, value, {+silent})
onDataChange: ->
- console.log 'onDataChange!'
+ console.log "#this.onDataChange!"
+ @model.once 'data-ready', @render, this
+ .loadData {+force}
onFirstClickRenderOptionsTab: ->
@$el.off 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
stopAndRender : -> @render ... ; false
# }}}
+ ### Spinner {{{
+
+ /**
+ * Retrieve or construct the spinner.
+ */
+ spinner: ->
+ el = @$ '.graph-spinner'
+ unless el.data 'spinner'
+ ### Spin.js Options ###
+ opts =
+ lines : 9 # [12] The number of lines to draw
+ length : 2 # [7] The length of each line
+ width : 1 # [5] The line thickness
+ radius : 7 # [10] The radius of the inner circle
+ rotate : -10.5 # [0] rotation offset
+ trail : 50 # [100] Afterglow percentage
+ opacity : 1/4 # [1/4] Opacity of the lines
+ shadow : false # [false] Whether to render a shadow
+ speed : 1 # [1] Spins per second
+ zIndex : 2e9 # [2e9] zIndex; uses a very high z-index by default
+ color : '#000' # ['#000'] Line color; '#rgb' or '#rrggbb'.
+ top : 'auto' # ['auto'] Top position relative to parent in px; 'auto' = center vertically.
+ left : 'auto' # ['auto'] Left position relative to parent in px; 'auto' = center horizontally.
+ className : 'spinner' # ['spinner'] CSS class to assign to the element
+ fps : 20 # [20] Frames per second when falling back to `setTimeout()`.
+ hwaccel : Modernizr.csstransforms3d # [false] Whether to use hardware acceleration.
+
+ isHidden = el.css('display') is 'none'
+ el.show().spin opts
+ el.hide() if isHidden
+ el
+
+ checkWaiting: ->
+ spinner = @spinner()
+ if isWaiting = (@waitingOn > 0)
+ spinner.show()
+ if spinner.find('.spinner').css('top') is '0px'
+ # delete spinner
+ spinner.spin(false)
+ # re-add to DOM with correct parent sizing
+ @spinner()
+ else
+ spinner.hide()
+ isWaiting
+
+ # }}}
# }}}
tr.dataset-metric(class=viewClasses)
- td.col-label(style="color: #{color};") #{label}
- td.col-source #{source}
- td.col-time #{timespan}
+ td.col-label.col-color(style="color: #{color};", data-bind="label") #{label}
+ td.col-source(data-bind="source") #{source}
+ td.col-time(data-bind="timespan", data-bind-escape="false") #{timespan}
td.col-actions
a.delete-metric-button.close(href="#") ×
.activity-arrow: div.inner
.inner: form.metric-edit-form.form-horizontal
.metric-header.control-group
- .color-swatch(style="background-color: #{color};")
- input.metric-color(type='hidden', id="#{graph_id}_metric", name="color", value=color)
- input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='Metric Label', value=label)
+ //-
+ .color-swatch(style="background-color: #{color};")
+ input.metric-color(type='hidden', id="#{graph_id}_metric", name="color", value=color)
+ .color-swatch.input-append.color(data-color="#{color}", data-color-format="hex")
+ input.metric-color(type='hidden', id="#{graph_id}_metric_color", name="color", value=color)
+ span.add-on: i(style="background-color: #{color};")
+
+ input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='#{placeholder_label}', value=label)
.metric-datasource.control-group(data-subview="DataSourceUIView")
absolute top 3px right 0
width 30px
height 30px
- border 1px solid #333
- border-color #ddd #333 #333 #ddd
- border-radius 3px
- cursor pointer
+ .add-on, i
+ display block
+ width 100%
+ height 100%
+ border 0
+ padding 0
+ outline 0
+ i
+ border 1px solid #333
+ border-color #ddd #333 #333 #ddd
+ border-radius 3px
+ // cursor pointer
input
display block
title Edit Graph | GraphKit
append styles
+ mixin css('/vendor/bootstrap-colorpicker/css/colorpicker.css')
+ mixin css('/vendor/bootstrap-datepicker/css/datepicker.css')
mixin css('graph.css')
mixin css('data.css')
mixin css('isotope.css')
include mixins/helpers
-include mixins/forms
!!! html
html(lang="en", dir="ltr")
- jquery.hotkeys.min
- jquery.isotope.min
- jquery.spin.min
+
- bootstrap.min
+ - bootstrap-colorpicker
+ - bootstrap-datepicker
### CommonJS Support Starts Here
### (this means Browserify must come before any .mod files)