From e5f2d3455b13a87e0d530c9e33d6ea600c52fe50 Mon Sep 17 00:00:00 2001 From: dsc Date: Mon, 7 May 2012 07:14:22 -0700 Subject: [PATCH] Implements colorpicker for metrics. --- lib/dataset/data-view.co | 10 +++- lib/dataset/dataset-model.co | 11 +++-- lib/dataset/dataset-view.co | 14 +++-- lib/dataset/datasource-ui-view.co | 2 +- lib/dataset/metric-edit-view.co | 58 +++++++++++++------- lib/dataset/metric-model.co | 77 +++++++++++++++++++-------- lib/graph/graph-edit-view.co | 107 ++++++++++++++++++++----------------- lib/template/dataset-metric.jade | 6 +- lib/template/metric-edit.jade | 11 +++- www/css/data.styl | 16 ++++-- www/graph/edit.jade | 2 + www/layout.jade | 1 - www/modules.yaml | 3 + 13 files changed, 203 insertions(+), 115 deletions(-) diff --git a/lib/dataset/data-view.co b/lib/dataset/data-view.co index 6c8d115..d36cc61 100644 --- a/lib/dataset/data-view.co +++ b/lib/dataset/data-view.co @@ -33,6 +33,7 @@ DataView = exports.DataView = BaseView.extend do # {{{ BaseView::initialize ... @metric_views = new ViewList @datasources = DataSource.getAllSources() + # @on 'update', @onUpdate, this @model.metrics .on 'add', @addMetric, this .on 'remove', @removeMetric, this @@ -83,7 +84,8 @@ DataView = exports.DataView = BaseView.extend do # {{{ 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 @@ -110,5 +112,9 @@ DataView = exports.DataView = BaseView.extend do # {{{ @$el.css 'min-height', newMinHeight onUpdateMetric: -> - @renderSubviews() + console.log "#this.onUpdateMetric!" + @trigger 'metric-change', @model, this + @render() + + # }}} diff --git a/lib/dataset/dataset-model.co b/lib/dataset/dataset-model.co index bec6098..5d0fa33 100644 --- a/lib/dataset/dataset-model.co +++ b/lib/dataset/dataset-model.co @@ -86,7 +86,7 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{ * @returns {Array} The date column. */ getDateColumn: -> - dates = @metrics.invoke 'getDateColumn' + dates = @metrics.onlyOk().invoke 'getDateColumn' maxLen = _.max _.pluck dates, 'length' _.find dates, -> it.length is maxLen @@ -94,16 +94,18 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{ * @returns {Array} List of all columns except the date column. */ getDataColumns: -> - @metrics.invoke 'getData' + @metrics.onlyOk().invoke 'getData' /** * @returns {Array} 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 @@ -111,8 +113,9 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{ 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 diff --git a/lib/dataset/dataset-view.co b/lib/dataset/dataset-view.co index ecdf08b..e19efcb 100644 --- a/lib/dataset/dataset-view.co +++ b/lib/dataset/dataset-view.co @@ -83,8 +83,6 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{ @trigger 'edit-metric', metric, view, this this - render: -> - this # }}} @@ -106,12 +104,16 @@ DataSetMetricView = exports.DataSetMetricView = BaseView.extend do # {{{ 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', @@ -127,9 +129,9 @@ DataSetMetricView = exports.DataSetMetricView = BaseView.extend do # {{{ "#{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' # }}} diff --git a/lib/dataset/datasource-ui-view.co b/lib/dataset/datasource-ui-view.co index 29df057..0a69ba1 100644 --- a/lib/dataset/datasource-ui-view.co +++ b/lib/dataset/datasource-ui-view.co @@ -64,6 +64,6 @@ DataSourceUIView = exports.DataSourceUIView = BaseView.extend do # {{{ @$ '.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 # }}} diff --git a/lib/dataset/metric-edit-view.co b/lib/dataset/metric-edit-view.co index 4cbdcf3..f0d787c 100644 --- a/lib/dataset/metric-edit-view.co +++ b/lib/dataset/metric-edit-view.co @@ -14,6 +14,7 @@ * Model is a Metric. */ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{ + __bind__ : <[ onChange ]> tagName : 'section' className : 'metric-edit-ui' template : require 'kraken/template/metric-edit' @@ -35,37 +36,56 @@ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{ 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 # }}} diff --git a/lib/dataset/metric-model.co b/lib/dataset/metric-model.co index 0810370..e3b6ce6 100644 --- a/lib/dataset/metric-model.co +++ b/lib/dataset/metric-model.co @@ -8,6 +8,7 @@ DataSource = DataSourceList = null * @class */ Metric = exports.Metric = BaseModel.extend do # {{{ + NEW_METRIC_LABEL : 'New Metric' urlRoot : '/metrics' /** @@ -16,25 +17,27 @@ Metric = exports.Metric = BaseModel.extend do # {{{ */ 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 @@ -43,8 +46,10 @@ Metric = exports.Metric = BaseModel.extend do # {{{ 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() @@ -54,6 +59,18 @@ Metric = exports.Metric = BaseModel.extend do # {{{ 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' @@ -75,14 +92,32 @@ Metric = exports.Metric = BaseModel.extend do # {{{ 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 @@ -91,12 +126,7 @@ Metric = exports.Metric = BaseModel.extend do # {{{ * 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 # }}} @@ -114,6 +144,9 @@ MetricList = exports.MetricList = BaseList.extend do # {{{ comparator: (metric) -> metric.get('index') ? Infinity + onlyOk: -> + new MetricList @filter -> it.isOk() + # }}} ### FIXME: LOLHACKS ### diff --git a/lib/graph/graph-edit-view.co b/lib/graph/graph-edit-view.co index 2dd3465..54656cf 100644 --- a/lib/graph/graph-edit-view.co +++ b/lib/graph/graph-edit-view.co @@ -108,7 +108,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ @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 @@ -128,9 +128,9 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ @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: -> @@ -262,6 +262,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ # 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 @@ -327,50 +328,6 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ 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 {{{ @@ -420,10 +377,12 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ @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 @@ -459,7 +418,9 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ @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 @@ -487,6 +448,52 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ 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 + + # }}} # }}} diff --git a/lib/template/dataset-metric.jade b/lib/template/dataset-metric.jade index e15dac5..6209be4 100644 --- a/lib/template/dataset-metric.jade +++ b/lib/template/dataset-metric.jade @@ -1,7 +1,7 @@ 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 diff --git a/lib/template/metric-edit.jade b/lib/template/metric-edit.jade index d8c37cc..8b2a9d3 100644 --- a/lib/template/metric-edit.jade +++ b/lib/template/metric-edit.jade @@ -2,9 +2,14 @@ section.metric-edit-ui .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") diff --git a/www/css/data.styl b/www/css/data.styl index cffa1a8..dac1e20 100644 --- a/www/css/data.styl +++ b/www/css/data.styl @@ -128,10 +128,18 @@ section.graph section.data-ui 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 diff --git a/www/graph/edit.jade b/www/graph/edit.jade index d51b469..e31c845 100644 --- a/www/graph/edit.jade +++ b/www/graph/edit.jade @@ -4,6 +4,8 @@ block title 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') diff --git a/www/layout.jade b/www/layout.jade index 4b3591c..af3c4b9 100644 --- a/www/layout.jade +++ b/www/layout.jade @@ -1,5 +1,4 @@ include mixins/helpers -include mixins/forms !!! html html(lang="en", dir="ltr") diff --git a/www/modules.yaml b/www/modules.yaml index 37f1b82..5944884 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -19,7 +19,10 @@ dev: - 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) -- 1.7.0.4