Implements colorpicker for metrics.
authordsc <dsc@wikimedia.org>
Mon, 7 May 2012 14:14:22 +0000 (07:14 -0700)
committerdsc <dsc@wikimedia.org>
Mon, 7 May 2012 14:14:22 +0000 (07:14 -0700)
13 files changed:
lib/dataset/data-view.co
lib/dataset/dataset-model.co
lib/dataset/dataset-view.co
lib/dataset/datasource-ui-view.co
lib/dataset/metric-edit-view.co
lib/dataset/metric-model.co
lib/graph/graph-edit-view.co
lib/template/dataset-metric.jade
lib/template/metric-edit.jade
www/css/data.styl
www/graph/edit.jade
www/layout.jade
www/modules.yaml

index 6c8d115..d36cc61 100644 (file)
@@ -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()
+    
+    
 # }}}
index bec6098..5d0fa33 100644 (file)
@@ -86,7 +86,7 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
      * @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
     
@@ -94,16 +94,18 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
      * @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
@@ -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
index ecdf08b..e19efcb 100644 (file)
@@ -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
                     '&mdash;'
-        
-        # XXX: Icons/classes for visible/disabled?
-        { $, _, op, @model, view:this } import m
+    
+    onUpdate: ->
+        @$ '.col-color' .css 'color', @model.get 'color'
     
 # }}}
 
index 29df057..0a69ba1 100644 (file)
@@ -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
     
 # }}}
index 4cbdcf3..f0d787c 100644 (file)
@@ -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
     
 # }}}
index 0810370..e3b6ce6 100644 (file)
@@ -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 ###
index 2dd3465..54656cf 100644 (file)
@@ -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
+    
+    # }}}
     
 # }}}
 
index e15dac5..6209be4 100644 (file)
@@ -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="#") &times;
         .activity-arrow: div.inner
index d8c37cc..8b2a9d3 100644 (file)
@@ -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")
         
index cffa1a8..dac1e20 100644 (file)
@@ -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
index d51b469..e31c845 100644 (file)
@@ -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')
index 4b3591c..af3c4b9 100644 (file)
@@ -1,5 +1,4 @@
 include mixins/helpers
-include mixins/forms
 
 !!! html
 html(lang="en", dir="ltr")
index 37f1b82..5944884 100644 (file)
@@ -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)