Checkpoint: Events mostly flow upward.
authordsc <dsc@wikimedia.org>
Mon, 30 Apr 2012 18:59:23 +0000 (11:59 -0700)
committerdsc <dsc@wikimedia.org>
Mon, 30 Apr 2012 18:59:23 +0000 (11:59 -0700)
18 files changed:
data/graphs/ohai.json
lib/base/base-mixin.co
lib/base/base-model.co
lib/base/model-cache.co
lib/dataset/data-view.co
lib/dataset/dataset-model.co
lib/dataset/dataset-view.co
lib/dataset/datasource-model.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/graph/graph-model.co
lib/main-edit.co
lib/template/datasource-ui.jade
lib/util/backbone.co
lib/util/event/ready-emitter.co
lib/util/event/waiting-emitter.co

index 224d8d1..c15f14c 100644 (file)
@@ -1,4 +1,32 @@
 {
+    "id": "ohai",
+    "slug": "ohai",
+    "name": "ohai~",
+    "desc": "A graph for the testing of great justice.",
+    "notes": "",
+    "width": "auto",
+    "height": 250,
+    "parents": ["root"],
+    "dataset": "/data/datasources/rc/rc_page_requests.csv",
+    "data": {
+        "metrics": [{
+            "source_id": "rc_active_editors_count",
+            "source_col": 1,
+            "label": "Total Active Editors",
+            "color": "#E62F74"
+        }, {
+            "source_id": "rc_very_active_editors_count",
+            "source_col": 1,
+            "label": "Total Very Active Editors",
+            "color": "#244792"
+        }, {
+            "source_id": "rc_edits_count",
+            "source_col": 1,
+            "label": "Total Edits",
+            "color": "#FF6458"
+        }]
+    },
+    "chartType": "dygraphs",
     "options": {
         "animatedZooms": true,
         "avoidMinZero": false,
         "yLabelWidth": 18,
         "yValueFormatter": null,
         "ylabel": null
-    },
-    "slug": "ohai",
-    "name": "ohai~",
-    "desc": "A graph for the testing of great justice.",
-    "notes": "",
-    "width": "auto",
-    "height": 250,
-    "chartType": "dygraphs",
-    "parents": ["root"],
-    "id": "ohai",
-    "dataset": "/data/datasources/rc/rc_page_requests.csv",
-    "data": {
-        "metrics": [{
-            "source_id": "rc_page_requests",
-            "source_col": 1,
-            "label": "All Wikipedias (+Mobile)",
-            "color": "#E62F74"
-        }, {
-            "source_id": "rc_page_requests",
-            "source_col": 2,
-            "label": "English",
-            "color": "#244792"
-        }]
     }
 }
index 1dc1a2b..0b081a7 100644 (file)
@@ -44,10 +44,10 @@ BaseBackboneMixin = exports.BaseBackboneMixin =
      * Subsequent listeners added on this event will be auto-triggered.
      * @returns {this}
      */
-    triggerReady: ->
-        return this if @ready
-        @ready = true
-        @trigger 'ready', this
+    triggerReady: (lock='ready', event='ready') ->
+        return this if @[lock]
+        @[lock] = true
+        @trigger event, this
         this
     
     /**
@@ -55,10 +55,10 @@ BaseBackboneMixin = exports.BaseBackboneMixin =
      * 'ready-reset' event.
      * @returns {this}
      */
-    resetReady: ->
-        return this unless @ready
-        @ready = false
-        @trigger 'ready-reset', this
+    resetReady: (lock='ready', event='ready') ->
+        return this unless @[lock]
+        @[lock] = false
+        @trigger "#event-reset", this
         this
     
     /**
@@ -71,13 +71,18 @@ BaseBackboneMixin = exports.BaseBackboneMixin =
      * @param {Object} [context]
      * @returns {this}
      */
-    on: (events, callback, context) ->
+    on: (events, callback, context=this) ->
         return this if not callback
         Backbone.Events.on ...
         if @ready and _.contains events.split(/\s+/), 'ready'
-            callback.call context or this, this
+            callback.call context, this
         this
     
+    makeHandlersForCallback: (cb) ->
+        success : ~> cb.call this, [null].concat arguments
+        error   : ~> cb.call this, it
+    
+    
     
     
     ### Synchronization
@@ -127,6 +132,9 @@ BaseBackboneMixin = exports.BaseBackboneMixin =
             self.unwait(); fn ...
     
     
+    
+    ###
+    
     getClassName: ->
         "#{@..name or @..displayName}"
     
index 499fb7d..348ea82 100644 (file)
@@ -18,7 +18,7 @@ BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
         @__superclass__ = @..__super__.constructor
         @waitingOn      = 0
         Backbone.Model ...
-        @trigger 'create', this
+        # @..trigger 'create', this
     
     
     
@@ -52,7 +52,78 @@ BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
     #     
     
     
+    ### Data & Model Loading
+    
+    /**
+     * Override to customize what data or assets the model requires,
+     * and how they should be loaded.
+     * 
+     * By default, `load()` simply calls `loadModel()` via `loader()`.
+     * 
+     * @see BaseModel#loader
+     * @see BaseModel#loadModel
+     * @returns {this}
+     */
+    load: ->
+        console.log "#this.load()"
+        @loader do
+            start         : @loadModel
+            completeEvent : 'fetch-success'
+        this
+    
     
+    /**
+     * Wraps the loading workflow boilerplate:
+     *  - Squelches multiple loads from running at once
+     *  - Squelches loads post-ready, unless forced
+     *  - Triggers a start event
+     *  - Triggers "ready" when complete
+     *  - Wraps workflow with wait/unwait
+     *  - Cleans up "loading" state
+     * 
+     * @protected
+     * @param {Object} [opts={}] Options:
+     * @param {Function} opts.start Function that starts the loading process. Always called with `this` as the context.
+     * @param {String} [opts.startEvent='load'] Event to trigger before beginning the load.
+     * @param {String} [opts.completeEvent='load-success'] Event which signals loading has completed.
+     * @param {Boolean} [opts.force=false] If true, move forward with the load even if we're ready.
+     * @returns {this} 
+     */
+    loader: (opts={}) ->
+        opts = { -force, startEvent:'load', completeEvent:'load-success', ...opts }
+        @resetReady() if opts.force
+        return this if not opts.start or @loading or @ready
+        
+        @wait()
+        @loading = true
+        @trigger opts.startEvent, this
+        
+        # Register a handler for the post-load event that will run only once
+        @once opts.completeEvent, ~>
+            console.log "#{this}.onLoadComplete()"
+            @loading = false
+            @unwait() # terminates the `load` wait
+            @trigger 'load-success', this unless opts.completeEvent is 'load-success'
+            @triggerReady()
+        
+        # Finally, start the loading process
+        opts.start.call this
+        this
+    
+    /**
+     * Runs `.fetch()`, triggering a `fetch` event at start, and
+     * `fetch-success` / `fetch-error` on completion.
+     * 
+     * @protected
+     * @returns {this}
+     */
+    loadModel: ->
+        @wait()
+        @trigger 'fetch', this
+        @fetch do
+            success : ~> @unwait(); @trigger 'fetch-success', this
+            error   : ~> @unwait(); @trigger 'fetch-error',   this, ...arguments
+        this
     
     
     ### Serialization
@@ -149,11 +220,11 @@ BaseList = exports.BaseList = Backbone.Collection.extend mixinBase do # {{{
     toString: ->
         "#{@getClassName()}[#{@length}]"
     
-    # toString: ->
-    #     modelIds = @models
-    #         .map -> "\"#{it.id ? it.cid}\""
-    #         .join ', '
-    #     "#{@getClassName()}[#{@length}](#modelIds)"
+    toStringWithIds: ->
+        modelIds = @models
+            .map -> "\"#{it.id ? it.cid}\""
+            .join ', '
+        "#{@getClassName()}[#{@length}](#modelIds)"
 # }}}
 
 
index f3ef08c..f636a7d 100644 (file)
@@ -146,7 +146,7 @@ class exports.ModelCache extends ReadyEmitter
             .parMap_ (next, id) ~>
                 return next.ok(that) if @cache.get id
                 @register @createModel id
-                    .on 'ready', next
+                    .on 'ready', -> next.ok it
                     .load()
             .unflatten()
             .seq (models) ->
index 9fc9f3d..6c8d115 100644 (file)
@@ -7,13 +7,14 @@ Seq = require 'seq'
 } = require 'kraken/dataset/dataset-view'
 { MetricEditView,
 } = require 'kraken/dataset/metric-edit-view'
-
+{ DataSource, 
+} = require 'kraken/dataset/datasource-model'
 
 /**
- * @class
+ * @class DataSet selection and customization UI (root of the `data` tab).
  */
 DataView = exports.DataView = BaseView.extend do # {{{
-    __bind__       : <[ ]>
+    __bind__       : <[ onMetricsChanged ]>
     tagName        : 'section'
     className      : 'data-ui'
     template       : require 'kraken/template/data'
@@ -21,7 +22,9 @@ DataView = exports.DataView = BaseView.extend do # {{{
     datasources : null
     
     
-    
+    /**
+     * @constructor
+     */
     constructor: function DataView
         BaseView ...
     
@@ -29,20 +32,16 @@ DataView = exports.DataView = BaseView.extend do # {{{
         @graph_id = @options.graph_id
         BaseView::initialize ...
         @metric_views = new ViewList
-        @datasources = @model.sources
+        @datasources = DataSource.getAllSources()
         @model.metrics
-            .on 'add',      @addMetric,     this
-            .on 'remove',   @removeMetric,  this
-        @on 'ready', @onReady, this
-        @load()
+            .on 'add',          @addMetric,     this
+            .on 'remove',       @removeMetric,  this
+        @model.once 'ready',    @onReady,       this
     
     onReady: ->
+        console.log "#this.onReady! #{@model.metrics}"
         dataset = @model
         @model.metrics.each @addMetric, this
-        # @metric_edit_view = @addSubview new MetricEditView  {@graph_id, dataset, @datasources}
-        # @metric_edit_view
-        #     .on 'update',           @onUpdateMetric,    this
-        
         @dataset_view = new DataSetView {@model, @graph_id, dataset, @datasources}
         @addSubview @dataset_view
             .on 'add-metric',       @onMetricsChanged,  this
@@ -50,19 +49,10 @@ DataView = exports.DataView = BaseView.extend do # {{{
             .on 'edit-metric',      @editMetric,        this
         
         @render()
+        @triggerReady()
         this
     
     
-    
-    load: ->
-        @wait()
-        # $.getJSON '/datasources/all', (@data) ~>
-        #     _.each @data, @canonicalizeDataSource, this
-        #     @model.sources.reset _.map @data, -> it
-        @unwait()
-        # @render()
-        @triggerReady()
-    
     /**
      * Transform the `columns` field to ensure an Array of {label, type} objects.
      */
@@ -89,16 +79,11 @@ DataView = exports.DataView = BaseView.extend do # {{{
         attrs = _.clone @model.attributes
         { @graph_id, @datasources } import attrs
     
-    # Don't rebuild HTML, simply notify subviews
-    # render: ->
-    #     @renderSubviews()
-    #     @trigger 'render', this
-    #     this
-    
     addMetric: (metric) ->
         console.log "#this.addMetric!", metric
         return metric if @metric_views.findByModel metric
-        view = new MetricEditView {model:metric, @graph_id, dataset, @datasources}
+        view = new MetricEditView {model:metric, @graph_id, dataset:@model, @datasources}
+        view.on 'update', @onUpdateMetric, this
         @metric_views.push @addSubview view
         metric
     
@@ -114,12 +99,11 @@ DataView = exports.DataView = BaseView.extend do # {{{
         @metric_views.invoke 'hide'
         @metric_edit_view = @metric_views.findByModel metric
         @metric_edit_view?.show()
-        @onMetricsChanged()
+        _.delay @onMetricsChanged, 10
     
     onMetricsChanged: ->
         oldMinHeight = parseInt @$el.css 'min-height'
         newMinHeight = Math.max do
-            oldMinHeight
             @dataset_view.$el.height()
             @metric_edit_view?.$el.height()
         # console.log 'onMetricsChanged!', oldMinHeight, '-->', newMinHeight
index e0722b4..bec6098 100644 (file)
@@ -17,7 +17,6 @@ ColorBrewer = require 'colorbrewer'
  */
 DataSet = exports.DataSet = BaseModel.extend do # {{{
     urlRoot : '/datasets'
-    ready : false
     
     /**
      * @type DataSourceList
@@ -40,7 +39,6 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
     
     initialize : ->
         BaseModel::initialize ...
-        @sources = new DataSourceList
         @metrics = new MetricList @attributes.metrics
         @on 'change:metrics', @onMetricChange, this
     
@@ -48,16 +46,23 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
     
     load: (opts={}) ->
         @resetReady() if opts.force
-        return this if @ready
+        return this if @loading or @ready
+        
+        unless @metrics.length
+            return @triggerReady()
+        
+        console.log "#this.load()..."
         @wait()
+        @loading = true
         @trigger 'load', this
-        Seq _.unique @metrics.pluck 'source_id'
-            .parMap_ (next, source_id) ->
-                DataSource.lookup source_id, next
-            .seqEach_ (next, source) ~>
-                @sources.add source
-                next.ok source
+        Seq @metrics.models
+            .parEach_ (next, metric) ->
+                metric.once 'ready', next.ok .load()
+            # .parEach_ (next, metric) ->
+            #     metric.on 'load-data-success', next.ok .loadData()
             .seq ~>
+                console.log "#{this}.load() complete!"
+                @loading = false
                 @unwait() # terminates the `load` wait
                 @triggerReady()
         this
@@ -106,7 +111,9 @@ DataSet = exports.DataSet = BaseModel.extend do # {{{
         m
     
     onMetricChange: ->
-        @metrics.reset @get 'metrics'
+        @resetReady()
+        # @metrics.reset @get 'metrics'
+        @load()
     
     # TODO: toJSON() must ensure columns in MetricList are ordered by index
     #   ...in theory, MetricList.comparator now does this
index b201229..ecdf08b 100644 (file)
@@ -27,7 +27,8 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
         {@graph_id, @datasources, @dataset} = @options
         BaseView::initialize ...
         @views_by_cid = {}
-        @addAllMetrics()
+        @model
+            .on 'ready',  @addAllMetrics,     this
         @model.metrics
             .on 'add',    @addMetric,         this
             .on 'remove', @removeMetric,      this
@@ -63,6 +64,7 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
         metric.view
     
     addAllMetrics: ->
+        console.log "#this.addAllMetrics! --> #{@model.metrics}"
         @removeAllSubviews()
         @model.metrics.each @addMetric, this
         this
index 7b824eb..75bf5e0 100644 (file)
@@ -2,7 +2,7 @@
 } = require 'kraken/util'
 { TimeSeriesData, CSVData,
 } = require 'kraken/timeseries'
-{ BaseModel, BaseList, BaseView,
+{ BaseModel, BaseList, ModelCache,
 } = require 'kraken/base'
 { Metric, MetricList,
 } = require 'kraken/dataset/metric-model'
@@ -12,7 +12,7 @@
  * @class
  */
 DataSource = exports.DataSource = BaseModel.extend do # {{{
-    __bind__ : <[ onLoadSuccess onLoadError ]>
+    __bind__ : <[ onLoadDataSuccess onLoadDataError ]>
     urlRoot  : '/datasources'
     ready    : false
     
@@ -61,7 +61,6 @@ DataSource = exports.DataSource = BaseModel.extend do # {{{
         @constructor.register this
         @metrics = new MetricList @attributes.metrics
         @on 'change:metrics', @onMetricChange, this
-        # @load()
     
     
     canonicalize: (ds) ->
@@ -83,44 +82,59 @@ DataSource = exports.DataSource = BaseModel.extend do # {{{
                 {idx, label, type:cols.types[idx] or 'int'}
         ds
     
-    load: ->
-        @trigger 'load', this
-        url = @get 'url'
+    
+    
+    loadAll: ->
+        @loader start: ->
+            Seq()
+                .seq_ (next) ~>
+                    @once 'fetch-success', next.ok
+                    @loadModel()
+                .seq_ (next) ~>
+                    @once 'load-data-success', next.ok
+                    @loadData()
+                .seq ~>
+                    @trigger 'load-success', this
+        this