DataSet UI now wired up properly, woo.
authordsc <dsc@wikimedia.org>
Mon, 7 May 2012 19:52:35 +0000 (12:52 -0700)
committerdsc <dsc@wikimedia.org>
Mon, 7 May 2012 19:52:35 +0000 (12:52 -0700)
lib/dataset/data-view.co
lib/dataset/dataset-model.co
lib/dataset/dataset-view.co
lib/dataset/metric-edit-view.co
lib/dataset/metric-model.co
lib/graph/graph-edit-view.co
lib/graph/graph-model.co
lib/template/metric-edit.jade

index d36cc61..b947e9a 100644 (file)
@@ -47,7 +47,7 @@ DataView = exports.DataView = BaseView.extend do # {{{
         @addSubview @dataset_view
             .on 'add-metric',       @onMetricsChanged,  this
             .on 'remove-metric',    @onMetricsChanged,  this
-            .on 'edit-metric',      @editMetric,        this
+            .on 'select-metric',    @selectMetric,      this
         
         @render()
         @triggerReady()
@@ -87,6 +87,7 @@ DataView = exports.DataView = BaseView.extend do # {{{
             .on 'metric-update', @onUpdateMetric, this
             .on 'metric-change', @onUpdateMetric, this
         @metric_views.push @addSubview view
+        @renderSubviews()
         metric
     
     removeMetric: (metric) ->
@@ -96,14 +97,15 @@ DataView = exports.DataView = BaseView.extend do # {{{
         @removeSubview view
         metric
     
-    editMetric: (metric) ->
-        console.log "#this.editMetric!", metric
+    selectMetric: (metric) ->
+        # console.log "#this.selectMetric!", metric
         @metric_views.invoke 'hide'
         @metric_edit_view = @metric_views.findByModel metric
         @metric_edit_view?.show()
         _.delay @onMetricsChanged, 10
     
     onMetricsChanged: ->
+        return unless @dataset_view
         oldMinHeight = parseInt @$el.css 'min-height'
         newMinHeight = Math.max do
             @dataset_view.$el.height()
index 5d0fa33..44e3a4c 100644 (file)
@@ -34,16 +34,16 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
         metrics : []
     
     
-    constructor: function DataSet
-        BaseModel ...
+    constructor: function DataSet (attributes={}, opts)
+        @metrics = new MetricList attributes.metrics
+        BaseModel.call this, attributes, opts
     
     initialize : ->
         BaseModel::initialize ...
-        @metrics = new MetricList @attributes.metrics
+        @set 'metrics', @metrics, {+silent}
         @on 'change:metrics', @onMetricChange, this
     
     
-    
     load: (opts={}) ->
         @resetReady() if opts.force
         return this if @loading or @ready
@@ -67,6 +67,34 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
                 @triggerReady()
         this
     
+    # refreshSubModels: ->
+    #     # @set 'metrics', @metrics.toJSON(), {+silent}
+    #     @set 'metrics', _.pluck(@metrics.models, 'attributes'), {+silent}
+    #     this
+    
+    /**
+     * Override to handle the case where one of our rich sub-objects is attempted
+     * to be overridden with a native object.
+     */
+    set: (key, value, opts) ->
+        # return DataSet.__super__.set ... unless @metrics
+        
+        if _.isObject(key) and key?
+            [values, opts] = [key, value]
+        else
+            values = { "#key": value }
+        opts or= {}
+        
+        for key, value in values
+            continue unless key is 'metrics' and _.isArray value
+            @metrics.reset value
+            delete values[key]
+            unless opts.silent
+                DataSet.__super__.set.call this, 'metrics', value, {+silent}
+                DataSet.__super__.set.call this, 'metrics', @metrics, opts
+        
+        DataSet.__super__.set.call this, values, opts
+    
     
     /* * * *  TimeSeriesData interface  * * * {{{ */
     
@@ -74,18 +102,21 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
      * @returns {Array<Array>} The reified dataset, materialized to a list of rows including timestamps.
      */
     getData: ->
+        return [] unless @ready
         _.zip ...@getColumns()
     
     /**
      * @returns {Array<Array>} List of all columns (including date column).
      */
     getColumns: ->
-        [ @getDateColumn() ].concat @getDataColumns()
+        return [] unless @ready
+        _.compact [ @getDateColumn() ].concat @getDataColumns()
     
     /**
      * @returns {Array<Date>} The date column.
      */
     getDateColumn: ->
+        return [] unless @ready
         dates = @metrics.onlyOk().invoke 'getDateColumn'
         maxLen = _.max _.pluck dates, 'length'
         _.find dates, -> it.length is maxLen
@@ -94,33 +125,45 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
      * @returns {Array<Array>} List of all columns except the date column.
      */
     getDataColumns: ->
+        return [] unless @ready
         @metrics.onlyOk().invoke 'getData'
     
     /**
      * @returns {Array<String>} List of column labels.
      */
     getLabels: ->
+        return [] unless @ready
         [ 'Date' ].concat @metrics.onlyOk().invoke 'getLabel'
     
-    # }}}
-    
     getColors: ->
+        return [] unless @ready
         @metrics.onlyOk().pluck 'color'
     
+    # }}}
+    
+    
     newMetric: ->
         index = @metrics.length
         @metrics.add m = new Metric { index, color:ColorBrewer.Spectral[11][index] }
+        # @get 'metrics' .push m.attributes
+        # @trigger 'change:metrics',  this, @metrics, 'metrics'
+        # @trigger 'change',          this, @metrics, 'metrics'
         m
     
     onMetricChange: ->
         console.log "#this.onMetricChange! ready=#{@ready}"
         @resetReady()
-        @metrics.reset @get 'metrics'
         @load()
     
-    # TODO: toJSON() must ensure columns in MetricList are ordered by index
+    
+    # XXX: toJSON() must ensure columns in MetricList are ordered by index
     #   ...in theory, MetricList.comparator now does this
     
+    # toJSON: ->
+    #     @refreshSubModels()
+    #     json = DataSet.__super__.toJSON ...
+    #     json.metrics = json.metrics.map -> it.toJSON?() or it
+    #     json
     
 # }}}
 
index e19efcb..34d7ff7 100644 (file)
@@ -14,7 +14,7 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
     
     events:
         'click .new-metric-button'       : 'newMetric'
-        'click .metrics .dataset-metric' : 'editMetric'
+        'click .metrics .dataset-metric' : 'selectMetric'
     
     views_by_cid : {}
     active_view : null
@@ -43,25 +43,24 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
     
     addMetric: (metric) ->
         console.log "#this.addMetric!", metric
-        if metric.view
-            @removeSubview metric.view.remove()
+        if @views_by_cid[metric.cid]
+            @removeSubview that
             delete @views_by_cid[metric.cid]
         
         view = @addSubview new DataSetMetricView {model:metric, @graph_id}
         @views_by_cid[metric.cid] = view
-        @$ '.metrics' .append view.render().el
-        
-        # @render()
+        # @$ '.metrics' .append view.render().el
         @trigger 'add-metric', metric, view, this
+        @render()
         view
     
     removeMetric: (metric) ->
         console.log "#this.removeMetric!", metric
-        if view = metric.view
-            @removeSubview view.remove()
+        if view = @views_by_cid[metric.cid]
+            @removeSubview view
             delete @views_by_cid[metric.cid]
             @trigger 'remove-metric', metric, view, this
-        metric.view
+        view
     
     addAllMetrics: ->
         console.log "#this.addAllMetrics! --> #{@model.metrics}"
@@ -69,18 +68,17 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
         @model.metrics.each @addMetric, this
         this
     
-    editMetric: (metric) ->
-        # console.log "#this.editMetric!", metric
+    selectMetric: (metric) ->
         if metric instanceof [jQuery.Event, Event]
             metric = $ metric.currentTarget .data 'model'
         view = @active_view = @views_by_cid[metric.cid]
-        console.log "#this.editMetric!", metric
+        # console.log "#this.selectMetric!", metric
         
         @$ '.metrics .dataset-metric' .removeClass 'metric-active'
         view.$el.addClass 'metric-active'
         view.$el.find '.activity-arrow' .css 'font-size', 2+view.$el.height()
         
-        @trigger 'edit-metric', metric, view, this
+        @trigger 'select-metric', metric, view, this
         this
     
     
@@ -120,8 +118,8 @@ DataSetMetricView = exports.DataSetMetricView = BaseView.extend do # {{{
                 'disabled' if m.disabled,
             ]).map( -> "metric-#it" ).join ' '
             source :
-                if m.source_id and m.source_col_name
-                    "#{m.source_id}.#{m.source_col_name}"
+                if m.source_id and m.source_col
+                    "#{m.source_id}[#{m.source_col}]"
                 else
                     'No source'
             timespan :
index f0d787c..4b5be60 100644 (file)
@@ -52,6 +52,7 @@ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
     
     update: ->
         MetricEditView.__super__.update ...
+        @$ '.metric-label' .attr 'placeholder', @model.getPlaceholderLabel()
         
         # Update the color picker
         @$ '.color-swatch'
@@ -85,6 +86,7 @@ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
     
     onSourceMetricChange: (metric) ->
         console.log "#this.onSourceMetricChange!", metric
+        @$ '.metric-label' .attr 'placeholder', @model.getPlaceholderLabel()
         @trigger 'metric-change', @model, this
         this
     
index e3b6ce6..7083738 100644 (file)
@@ -64,7 +64,7 @@ Metric = exports.Metric = BaseModel.extend do # {{{
     
     getPlaceholderLabel: ->
         col  = @get 'source_col'
-        name = "#{@source.get 'shortName'}, #{@source.getColumnName col}" if @source and col > 0
+        name = "#{@source.get 'shortName'}, #{@source.getColumnName col}" if @source and col >= 0
         name or @NEW_METRIC_LABEL
     
     getSourceColumnName: ->
@@ -73,9 +73,12 @@ Metric = exports.Metric = BaseModel.extend do # {{{
     
     
     load: (opts={}) ->
-        source_id = @get 'source_id'
+        source_id  = @get 'source_id'
         @resetReady() if opts.force or @source?.id is not source_id
-        return this if not source_id or @loading or @ready
+        return this if @loading or @ready
+        
+        unless source_id and @get('source_col') >= 0
+            return @triggerReady()
         
         console.log "#this.load()..."
         @updateId()
index 54656cf..eec0146 100644 (file)
@@ -104,7 +104,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         
         
         ### Graph Data UI
-        @data_view = @addSubview new DataView { model:@model.get('data'), graph_id:@id }
+        @data_view = @addSubview new DataView { model:@model.dataset, graph_id:@id }
         @data_view
             .on 'start-waiting', @wait,         this
             .on 'stop-waiting',  @unwait,       this
@@ -275,7 +275,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
                     valueFormatter     : @numberFormatterHTML
         
         # console.log "#this.render!", dataset
-        _.dump options, 'options'
+        # _.dump options, 'options'
         
         # Always rerender the chart to sidestep the case where we need to push defaults into
         # dygraphs to reset the current option state.
@@ -308,7 +308,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         # @renderDetails()
         # @attachSubviews()
         # _.invoke @subviews, 'render'
-        BaseView::render ...
+        GraphEditView.__super__.render ...
         @renderChart()
         # @updateURL()
         @trigger 'render', this
@@ -321,11 +321,11 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
      * Update the page URL using HTML5 History API
      */
     updateURL: ->
-        data  = @toJSON()
+        json  = @toJSON()
         title = "#{@model.get('name') or 'New Graph'} | Edit Graph | GraphKit"
         url   = @toURL('edit')
-        # console.log 'History.pushState', JSON.stringify(data), title, url
-        History.pushState data, title, url
+        # console.log 'History.pushState', JSON.stringify(json), title, url
+        History.pushState json, title, url
     
     
     ### }}}
@@ -397,7 +397,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         #     \toptions: #{JSON.stringify options}
         #     \t^opts: #{JSON.stringify _.intersection _.keys(changes), _.keys(options)}
         # """
-        @chart?.updateOptions file:that if changes?.dataset
+        # @chart?.updateOptions file:that if changes?.dataset
         @chartOptions options, {+silent} if changes?.options
     
     onScaffoldChange: (scaffold, value, key, field) ->
@@ -435,8 +435,8 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
     
     onDetailsSubmit: ->
         console.log "#this.onDetailsSubmit!"
-        data = @$ 'form.graph-details' .formData()
-        @model.set data
+        details = @$ 'form.graph-details' .formData()
+        @model.set details
         false
     
     onOptionsSubmit: ->
index 8790325..2d231f5 100644 (file)
@@ -98,6 +98,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{
         
         # Insert submodels in place of JSON
         @dataset = new DataSet {id:@id, ...@get 'data'}
+            # .on 'change', @onDataSetChange, this
         @set 'data', @dataset, {+silent}
         
         @trigger 'init', this
@@ -121,9 +122,9 @@ Graph = exports.Graph = BaseModel.extend do # {{{
                         next.ok()
                     success : @unwaitAnd (model, res) ~>
                         # console.log "#{this}.fetch() --> success!", res
+                        # Update the DataSet model with the new values
                         @dataset.set @get 'data'
-                        @trigger 'change:data', this, @dataset, 'data'
-                        @trigger 'change',      this, @dataset, 'data'
+                        @set 'data', @dataset, {+silent}
                         next.ok res
             
             # Load Parents...
@@ -166,9 +167,11 @@ Graph = exports.Graph = BaseModel.extend do # {{{
         Seq @dataset.metrics.models
             .parEach_ (next, metric) ->
                 metric.once 'ready', next.ok .load()
-            .parEach_ (next, metric) ->
-                metric.source
-                    .on 'load-data-success', next.ok .loadData()
+            .parEach_ (next, metric) ~>
+                unless metric.source
+                    console.warn "#{this}.loadData() -- Skipping metric #metric with invalid source!", metric
+                    return next.ok()
+                metric.source.on 'load-data-success', next.ok .loadData()
             .seq ~>
                 console.log "#{this}.loadData() complete!"
                 @loading = false
@@ -176,6 +179,9 @@ Graph = exports.Graph = BaseModel.extend do # {{{
                 @triggerReady 'dataReady', 'data-ready'
         this
     
+    onDataSetChange: ->
+        console.log "#this.onDataSetChange!"
+        @set 'data', @dataset, {+silent}
     
     
     ### Accessors
@@ -184,7 +190,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{
         if _.startsWith key, 'options.'
             @getOption key.slice(8)
         else
-            (@..__super__ or BaseModel::).get.call this, key
+            Graph.__super__.get.call this, key
     
     
     set: (key, value, opts) ->
@@ -195,7 +201,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{
             values = { "#key": value }
         values = @parse values
         
-        setter = (@..__super__ or BaseModel::).set
+        setter = Graph.__super__.set
         
         # Merge options in, firing granulated change events
         if values.options
@@ -215,7 +221,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{
     
     
     
-    ### Chart Option Accessors ###
+    ### Chart Option Accessors {{{
     
     hasOption: (key) ->
         @getOption(key) is void
@@ -266,8 +272,8 @@ Graph = exports.Graph = BaseModel.extend do # {{{
         options
     
     
-    
-    ### Serialization
+    # }}}
+    ### Serialization {{{
     
     parse: (data) ->
         data = JSON.parse data if typeof data is 'string'
@@ -300,8 +306,8 @@ Graph = exports.Graph = BaseModel.extend do # {{{
     toJSON: (opts={}) ->
         opts = {+keepDefaults, +keepUnchanged} import opts
         # use jQuery's deep-copy implementation -- XXX: Deep-copy no longer necessary thanks to @getOptions()
-        # json = $.extend true, {}, @attributes
         json = _.clone(@attributes) import { options:@getOptions(opts) }
+        # { data: ...json }
     
     
     toKVPairs: (opts={}) ->
@@ -346,6 +352,8 @@ Graph = exports.Graph = BaseModel.extend do # {{{
     toPermalink: ->
         "#{root.location.protocol}//#{window.location.host}#{@toLink()}"
     
+    # }}}
+    
 
 ### Graph Cache for parent-lookup
 new ModelCache Graph
index 8b2a9d3..a95e2a7 100644 (file)
@@ -7,7 +7,7 @@ section.metric-edit-ui
                 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};")
+                span.add-on: i(style="background-color: #{color};", title="#{color}")
             
             input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='#{placeholder_label}', value=label)