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)
17 files changed:
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 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