* 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
/**
* '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
/**
* @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
self.unwait(); fn ...
+
+ ###
+
getClassName: ->
"#{@..name or @..displayName}"
@__superclass__ = @..__super__.constructor
@waitingOn = 0
Backbone.Model ...
- @trigger 'create', this
+ # @..trigger 'create', this
#
+ ### 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
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)"
# }}}
.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) ->
} = 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'
datasources : null
-
+ /**
+ * @constructor
+ */
constructor: function DataView
BaseView ...
@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
.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.
*/
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
@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
*/
DataSet = exports.DataSet = BaseModel.extend do # {{{
urlRoot : '/datasets'
- ready : false
/**
* @type DataSourceList
initialize : ->
BaseModel::initialize ...
- @sources = new DataSourceList
@metrics = new MetricList @attributes.metrics
@on 'change:metrics', @onMetricChange, this
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
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
{@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
metric.view
addAllMetrics: ->
+ console.log "#this.addAllMetrics! --> #{@model.metrics}"
@removeAllSubviews()
@model.metrics.each @addMetric, this
this
} = require 'kraken/util'
{ TimeSeriesData, CSVData,
} = require 'kraken/timeseries'
-{ BaseModel, BaseList, BaseView,
+{ BaseModel, BaseList, ModelCache,
} = require 'kraken/base'
{ Metric, MetricList,
} = require 'kraken/dataset/metric-model'
* @class
*/
DataSource = exports.DataSource = BaseModel.extend do # {{{
- __bind__ : <[ onLoadSuccess onLoadError ]>
+ __bind__ : <[ onLoadDataSuccess onLoadDataError ]>
urlRoot : '/datasources'
ready : false
@constructor.register this
@metrics = new MetricList @attributes.metrics
@on 'change:metrics', @onMetricChange, this
- # @load()
canonicalize: (ds) ->
{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
+
+ loadData: ->
+ @wait()
+ @trigger 'load-data', this
+ return @onLoadDataSuccess @data if @data
switch @get 'format'
- case 'json'
- @loadJSON url
- case 'csv'
- @loadCSV url
+ case 'json' then @loadJSON()
+ case 'csv' then @loadCSV()
default
console.error "#this.load() Unknown Data Format!"
- @onLoadError null, 'Unknown Data Format!', new Error 'Unknown Data Format!'
+ @onLoadDataError null, 'Unknown Data Format!', new Error 'Unknown Data Format!'
this
- loadJSON: (url) ->
+ loadJSON: ->
$.ajax do
- url : url
+ url : @get 'url'
dataType : 'json'
- success : (data) ~> @onLoadSuccess new TimeSeriesData data
- error : @onLoadError
+ success : (data) ~> @onLoadDataSuccess new TimeSeriesData data
+ error : @onLoadDataError
this
- loadCSV: (url) ->
+ loadCSV: ->
$.ajax do
- url : url
+ url : @get 'url'
dataType : 'text'
- success : (data) ~> @onLoadSuccess new CSVData data
- error : @onLoadError
+ success : (data) ~> @onLoadDataSuccess new CSVData data
+ error : @onLoadDataError
this
- onLoadSuccess: (@data) ->
- console.log "#this.onLoadSuccess #{@data}"
- @trigger 'load-success', this
- @triggerReady()
+ onLoadDataSuccess: (@data) ->
+ console.log "#this.onLoadDataSuccess #{@data}"
+ @unwait()
+ @trigger 'load-data-success', this
- onLoadError: (jqXHR, txtStatus, err) ->
- @_errorLoading = true
+ onLoadDataError: (jqXHR, txtStatus, err) ->
console.error "#this Error loading data! -- #msg: #{err or ''}"
- @trigger 'load-error', this, txtStatus, err
+ @unwait()
+ @_errorLoading = true
+ @trigger 'load-data-error', this, txtStatus, err
getDateColumn: ->
onMetricChange: ->
@metrics.reset @get 'metrics'
-
+
# }}}
+### DataSource Cache
-/* * * * DataSource Cache * * * */
-
-DataSource import do
- CACHE : new DataSourceList
- ready : false
-
- register: (model) ->
- return model unless model
- # console.log "#{@CACHE}.register(#{model.id or model.get('id')})", model
- if @CACHE.contains model
- @CACHE.remove model, {+silent}
- @CACHE.add model
- model
-
- get: (id) ->
- @CACHE.get id
-
- lookup: (id, cb, cxt=this) ->
- # console.log "#{@CACHE}.lookup(#id, #{typeof cb})"
- unless @ready
- @on 'cache-ready', ~>
- @off 'cache-ready', arguments.callee
- @lookup id, cb, cxt
- return
-
- if @CACHE.get id
- cb.call cxt, null, that
- else
- Cls = this
- @register new Cls {id}
- .on 'ready', -> cb.call cxt, null, it
- .load()
-
-
-_.bindAll DataSource, 'register', 'get', 'lookup'
-
+ALL_SOURCES = new DataSourceList
+sourceCache = new ModelCache DataSource, {-ready, cache:ALL_SOURCES}
# Fetch all DataSources
$.getJSON '/datasources/all', (data) ->
- DataSource.CACHE.reset _.map data, -> it
- DataSource.ready = true
- DataSource.trigger 'cache-ready', DataSource
+ ALL_SOURCES.reset _.map data, op.I
+ sourceCache.triggerReady()
+DataSource.getAllSources = ->
+ ALL_SOURCES
/**
* @class
+ * Model is a Metric.
*/
DataSourceUIView = exports.DataSourceUIView = BaseView.extend do # {{{
__bind__ : <[ ]>
@$el.toggleClass 'in'
onSelectMetric: (evt) ->
- tr = evt.currentTarget
- idx = @$ '.source-metrics .datasource-source-metric' .toArray().indexOf tr
- return unless idx is not -1
+ # tr = evt.currentTarget
+ # idx = @$ '.source-metrics .datasource-source-metric' .toArray().indexOf tr
+ # return unless idx is not -1
+ el = $ evt.currentTarget
+ {source_id, source_col} = el.data()
+ source_col = parseInt source_col
+ return if not source_id or isNaN source_col
+ @$ '.source-metrics .datasource-source-metric' .removeClass 'active'
+ el.addClass 'active'
+ @model.set {source_col, source_id}
+ # @trigger 'edit-metric', @model
# }}}
/**
* @class
+ * Model is a Metric.
*/
MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
tagName : 'section'
className : 'metric-edit-ui'
template : require 'kraken/template/metric-edit'
- callOnReturnKeypress : 'onChanged'
+ callOnReturnKeypress : 'onChange'
events:
'keydown .metric-label' : 'onReturnKeypress'
@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
+ # @$ '.metric-datasource' .append @datasource_ui_view.render().el
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
+ # build: ->
+ # BaseView::build ...
+ # if @datasource_ui_view
+ # @$ '.metric-datasource' .append @datasource_ui_view.render().el
+ # this
update: ->
color = @model.get 'color'
this
- onChanged: ->
+ onChange: ->
attrs = @$ 'form.metric-edit-form' .formData()
@model.set attrs, {+silent}
@trigger 'update', this
- editMetric: (metric) ->
- console.log "#this.editMetric!", metric
- @datasource_ui_view.setModel @setModel metric
- @render()
- @show()
+ onMetricChange: (metric) ->
+ console.log "#this.onMetricChange!", metric
this
# }}}
*/
Metric = exports.Metric = BaseModel.extend do # {{{
urlRoot : '/metrics'
- ready : false
/**
* Data source of the Metric.
initialize : ->
BaseModel::initialize ...
- @on 'change:source_id', @lookupSource, this
- @on 'change:source_col', @onUpdateSourceCol, this
- @lookupSource()
+ @on 'change:source_id', @load, this
+ @on 'change:source_col', @updateId, this
+ @load()
getDateColumn: ->
getData: ->
@source.getColumn @get 'source_col'
- lookupSource: ->
- if source_id = @get 'source_id'
- @wait()
- @updateId()
- DataSource.lookup source_id, @onSourceReady, this
- this
-
- onSourceReady: (err, source) ->
- # console.log "#this.onSourceReady", arguments
- @unwait()
- if err
- console.error "#this Error loading DataSource! #err"
- else
- @source = source
- @updateId()
- @triggerReady()
- this
-
- onUpdateSourceCol: ->
+ load: (opts={}) ->
+ 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
+
+ console.log "#this.load()..."
@updateId()
+ @loading = true
+ @wait()
+ @trigger 'load', this
+
+ DataSource.lookup source_id, (err, source) ~>
+ # console.log "#this.onSourceReady", arguments
+ @loading = false
+ @unwait() # terminates the `load` wait
+ if err
+ console.error "#{this} Error loading DataSource! #err"
+ else
+ console.log "#{this}.load() complete!"
+ @source = source
+ @updateId()
+ @triggerReady()
this
+
updateId: ->
if (source_id = @get('source_id')) and (source_col = @get('source_col'))
@id = "#source_id[#source_col]"
.on 'change:dataset', @onModelChange, this
.on 'change:options', @onModelChange, this
.on 'error', @onModelError, this
- .on 'ready', @onReady, this
+ # .on 'ready', @onReady, this
### Chart Options Tab, Scaffold
@scaffold = @addSubview new ChartOptionScaffold
- # @$ '.graph-options-pane' .append @scaffold.el
@scaffold.collection.reset that if o.graph_spec
-
@scaffold.on 'change', @onScaffoldChange
@chartOptions @model.getOptions(), {+silent}
- # Rerender once the tab is visible
- # Can't use @events because we need to bind before registering
- @$el.on 'click', '.graph-data-tab', @onFirstClickRenderDataTab
- @$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
-
### Graph Data UI
@data_view = @addSubview new DataView { model:@model.get('data'), graph_id:@id }
- # @$ '.graph-data-pane' .append @data_view.render().el
@data_view
- .on 'change', @onDataChange, this
.on 'start-waiting', @wait, this
.on 'stop-waiting', @unwait, this
+ .on 'change', @onDataChange, this
+
+
+ # Rerender once the tab is visible
+ # Can't use @events because we need to bind before registering
+ @$el.on 'click', '.graph-data-tab', @onFirstClickRenderDataTab
+ @$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
- @checkWaiting()
### Chart Viewport
@resizeViewport()
# Resize chart on window resize
# Note: can't debounce the method itself, as the debounce wrapper returns undefined
$ root .on 'resize', _.debounce @resizeViewport, DEBOUNCE_RENDER
+
+ # Kick off model load chain
+ @checkWaiting()
+ Seq()
+ .seq_ (next) ~>
+ @model.on 'ready', next.ok .load()
+ .seq_ (next) ~>
+ @model.on 'data-ready', next.ok .loadData()
+ .seq ~> @onReady()
+
+ onReady: ->
+ return if @ready
+ console.log "(#this via GraphEditView).ready!"
+ @unwait() # clears `wait()` from `initialize`
+ # @ready = @scaffold.ready = true
+ @triggerReady()
+ @scaffold.triggerReady()
+ @chartOptions @model.getOptions(), {+silent}
+ @render()
+
+ # fix up the spinner element once the DOM is settled
+ _.delay @checkWaiting, 50
BaseView::attachSubviews ...
@checkWaiting()
- # render: ->
- # return this unless @ready and not @_rendering
- # @_rendering = true
- # @wait()
- # @checkWaiting()
- # @renderDetails()
- # @attachSubviews()
- # # _.invoke @subviews, 'render'
- # @renderChart()
- # @updateURL()
- # @trigger 'render', this
- # @unwait()
- # @_rendering = false
- # this
+ render: ->
+ return this unless @ready and not @_rendering
+ @_rendering = true
+ @wait()
+ @checkWaiting()
+ # @renderDetails()
+ # @attachSubviews()
+ # _.invoke @subviews, 'render'
+ BaseView::render ...
+ @renderChart()
+ # @updateURL()
+ @trigger 'render', this
+ @unwait()
+ @_rendering = false
+ this
/**
### }}}
### Event Handlers {{{
- onReady: ->
- return if @ready
- console.log "(#this via GraphEditView).ready!"
- @ready = @scaffold.ready = true
- @unwait() # clears `wait()` from `initialize`
- @onSync()
-
- # fix up the spinner element once the DOM is settled
- _.delay @checkWaiting, 50
-
onSync: ->
return unless @ready
console.info "#this.sync() --> success!"
ready : false
/**
+ * Whether this Graph has loaded the actual data needed to draw the chart.
+ * @type Boolean
+ */
+ dataReady : false
+
+ /**
* The chart type backing this graph.
* @type ChartType
*/
BaseModel.call this, attributes, opts
- initialize: (attributes, opts) ->
+ initialize: (attributes) ->
BaseModel::initialize ...
- opts = {+autoload} import (opts or {})
@constructor.register this
@parents = new GraphList
# TODO: Load on-demand
- @chartType = ChartType.lookup @get('chartType')
+ @chartType = ChartType.lookup @get 'chartType'
# Insert submodels in place of JSON
- # data = @get('data') or @get('dataset')
- @set 'data', @dataset = new DataSet(@get('data')), {+silent}
+ @dataset = new DataSet {id:@id, ...@get 'data'}
+ @set 'data', @dataset, {+silent}
@trigger 'init', this
- @load() if opts.autoload
load: (opts={}) ->
return this if (@loading or @ready) and not opts.force
+ console.log "#this.load()..."
@loading = true
@wait()
@trigger 'load', this
Seq()
+ # Fetch model if
.seq_ (next) ~>
- if @isNew()
- next.ok()
- else
- console.log "#{this}.fetch()..."
- @wait()
- @fetch do
- error : @unwaitAnd (err) ~>
- console.error "#{this}.fetch() --> error! #arguments"
- next.ok()
- success : @unwaitAnd (model, res) ~>
- # console.log "#{this}.fetch() --> success!", res
- @dataset.set @get('data')
- @trigger 'change:data', this, @dataset, 'data'
- @trigger 'change', this, @dataset, 'data'
- next.ok res
+ return next.ok() if @isNew()
+ console.log "#{this}.fetch()..."
+ @wait()
+ @fetch do
+ error : @unwaitAnd (err) ~>
+ console.error "#{this}.fetch() --> error! #arguments"
+ next.ok()
+ success : @unwaitAnd (model, res) ~>
+ # console.log "#{this}.fetch() --> success!", res
+ @dataset.set @get 'data'
+ @trigger 'change:data', this, @dataset, 'data'
+ @trigger 'change', this, @dataset, 'data'
+ next.ok res
+
+ # Load Parents...
.seq_ (next) ~>
- next.ok @get('parents')
+ next.ok @get 'parents'
.flatten()
.seqMap_ (next, parent_id) ~>
@wait()
@optionCascade.addLookup parent.get('options')
@unwait()
next.ok()
+
+ # Load DataSet...
+ .seq_ (next) ~>
+ @dataset.once 'ready', next.ok .load()
+
+ # Done!
.seq ~>
+ console.log "#{this}.load() complete!"
@loading = false
@unwait() # terminates the `load` wait
@triggerReady()
this
+ loadData: (opts={}) ->
+ @resetReady 'dataReady', 'data-ready' if opts.force
+ return this if @loading or @dataReady
+
+ unless @dataset.metrics.length
+ return @triggerReady 'dataReady', 'data-ready'
+
+ console.log "#this.loadData()..."
+ @wait()
+ @loading = true
+ @trigger 'load-data', this
+ 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()
+ .seq ~>
+ console.log "#{this}.loadData() complete!"
+ @loading = false
+ @unwait() # terminates the `load` wait
+ @triggerReady 'dataReady', 'data-ready'
+ this
+
+
### Accessors
toPermalink: ->
"#{root.location.protocol}//#{window.location.host}#{@toLink()}"
-
+
+### Graph Cache for parent-lookup
+new ModelCache Graph
+
# }}}
constructor : function GraphList then BaseList ...
initialize : -> BaseList::initialize ...
-
- toString: ->
- modelIds = @models
- .map -> "\"#{it.id ? it.cid}\""
- .join ', '
- "#{@getClassName()}(#modelIds)"
+ toString: -> @toStringWithIds()
# }}}
-### Graph Cache for parent-lookup
-new ModelCache Graph
{ _, op,
} = require 'kraken/util'
-{ TimeSeriesData, CSVData,
-} = require 'kraken/timeseries'
{ BaseView, BaseModel, BaseList,
} = require 'kraken/base'
-{ Field, FieldList, FieldView, Scaffold,
-} = require 'kraken/scaffold'
{ ChartType, DygraphsChartType,
- ChartOption, ChartOptionList, TagSet,
- ChartOptionView, ChartOptionScaffold,
} = require 'kraken/chart'
{ DataSource, DataSourceList,
} = require 'kraken/dataset'
next.ok()
error : (err) -> console.error err
.seq ->
- console.log 'All data loaded!'
jQuery main
-section.datasource-ui
+section.datasource-ui(class="datasource-ui-#{source_id}")
- section.datasource-summary(data-toggle="collapse", data-target="##{graph_id} .datasource-ui .datasource-selector")
+ section.datasource-summary(data-toggle="collapse", data-target="##{graph_id} .datasource-ui.datasource-ui-#{source_id} .datasource-selector")
i.expand-datasource-ui-button.icon-chevron-down
i.collapse-datasource-ui-button.icon-chevron-up
tbody.source-metrics
for m, idx in ds.metrics.slice(1)
- var activeColClass = (activeClass && source_col === m.idx) ? 'active' : ''
- tr.datasource-source-metric(class=activeColClass)
+ tr.datasource-source-metric(class=activeColClass, data-source_id=ds.id, data-source_col=m.idx)
td.source-metric-idx #{m.idx}
td.source-metric-label #{m.label}
td.source-metric-type #{m.type}
_bb_events =
+
+ /**
+ * Registers an event listener on the given event(s) to be fired only once.
+ *
+ * @param {String} events Space delimited list of event names.
+ * @param {Function} callback Event listener function.
+ * @param {Object} [context=this] Object to be supplied as the context for the listener.
+ * @returns {this}
+ */
once: (events, callback, context) ->
fn = ~>
@off events, arguments.callee, this
callback.apply (context or this), arguments
@on events, fn, this
- fn
+ this
+
+ /**
+ * Compatibility with Node's `EventEmitter`.
+ */
+ emit: Backbone.Events.trigger
{EventEmitter} = require 'events'
+EventEmitter::trigger = EventEmitter::emit
/**
{EventEmitter} = require 'events'
+EventEmitter::trigger = EventEmitter::emit
+
/**