} = require 'kraken/util'
{ BaseBackboneMixin, mixinBase,
} = require 'kraken/base/base-mixin'
+{ DataBinding,
+} = require 'kraken/base/data-binding'
*/
BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
tagName : 'section'
+ model : BaseModel
/**
- * The identifier for this view class.
- * By default, the class name is converted to underscore-case, and a
- * trailing '_view' suffix is dropped.
- * Example: "CamelCaseView" becomes "camel_case"
+ * Method-name called by `onReturnKeypress` when used as an event-handler.
* @type String
*/
- __view_type_id__: null
+ callOnReturnKeypress: null
+
+
+ /**
+ * Parent view of this view.
+ * @type BaseView
+ */
+ parent : null
/**
* Array of [view, selector]-pairs.
* Whether this view has been added to the DOM.
* @type Boolean
*/
- _parented: false
+ isAttached: false
@__superclass__ = @..__super__.constructor
@__view_type_id__ or= _.str.underscored @getClassName() .replace /_view$/, ''
@waitingOn = 0
- @subviews = []
+ @subviews = new ViewList
+ @onReturnKeypress = _.debounce @onReturnKeypress.bind(this), 50
Backbone.View ...
@trigger 'create', this
@$el.data { @model, view:this }
@model.on 'change', @render, this
@model.on 'destroy', @remove, this
- @model
+ @trigger 'change:model', this, model
+ model
### Subviews
- addSubview: (selector, view) ->
- [view, selector] = [selector, null] unless view
- @subviews.push [view, selector]
+ setParent: (parent) ->
+ [old_parent, @parent] = [@parent, parent]
+ @trigger 'parent', this, parent, old_parent
+ this
+
+ unsetParent: ->
+ [old_parent, @parent] = [@parent, null]
+ @trigger 'unparent', this, old_parent
+ this
+
+
+ addSubview: (view) ->
+ @removeSubview view
+ @subviews.push view
+ view.setParent this
view
removeSubview: (view) ->
- for [v, sel], idx of @subviews
- if v is view
- @subviews.splice(idx, 1)
- return [v, sel]
- null
+ if @hasSubview view
+ view.remove()
+ @subviews.remove view
+ view.unsetParent()
+ view
hasSubview: (view) ->
- _.any @subviews, ([v]) -> v is view
+ @subviews.contains view
invokeSubviews: ->
- _ _.pluck(@subviews, 0) .invoke ...arguments
+ @subviews.invoke ...arguments
- attachSubviews: ->
- for [view, selector] of @subviews
- return unless view
- view.undelegateEvents()
- return unless el = view.render()?.el
- if selector
- @$el.find selector .append el
- else
- @$el.append el
- view.delegateEvents()
+ removeAllSubviews: ->
+ @subviews.forEach @removeSubview, this
+ @subviews = new ViewList
this
- removeAllSubviews: ->
- @invokeSubviews 'remove'
- _.pluck @subviews, 0 .forEach @removeSubview, this
+
+
+ ### UI Utilities
+
+ attach: (el) ->
+ # @undelegateEvents()
+ @$el.appendTo el
+ # only trigger the event the first time
+ return this if @isAttached
+ @isAttached = true
+ _.delay do
+ ~> # have to let DOM settle to ensure elements can be found
+ @delegateEvents()
+ @trigger 'attach', this
+ 50
this
- bubbleEvent: (evt) ->
- @invokeSubviews 'trigger', ...arguments
+ remove : ->
+ # @undelegateEvents()
+ @$el.remove()
+ return this unless @isAttached
+ @isAttached = false
+ @trigger 'unattach', this
+ this
+
+ clear : ->
+ @remove()
+ @model.destroy()
+ @trigger 'clear', this
+ this
+
+ hide : -> @$el.hide(); @trigger('hide', this); this
+ show : -> @$el.show(); @trigger('show', this); this
+
+ /**
+ * Attach each subview to its bind-point.
+ * @returns {this}
+ */
+ attachSubviews: ->
+ bps = @getOwnSubviewBindPoints()
+ if @subviews.length and not bps.length
+ console.warn "#this.attachSubviews(): no subview bind-points found!"
+ return this
+ for view of @subviews
+ if bp = @findSubviewBindPoint view, bps
+ view.attach bp
+ else
+ console.warn "#this.attachSubviews(): Unable to find bind-point for #view!"
this
+ /**
+ * Finds all subview bind-points under this view's element, but not under
+ * the view element of any subview.
+ * @returns {jQuery|undefined}
+ */
+ getOwnSubviewBindPoints: ->
+ @$ '[data-subview]' .not @$ '[data-subview] [data-subview]'
+
+ /**
+ * Find the matching subview bind-point for the given view.
+ */
+ findSubviewBindPoint: (view, bind_points) ->
+ bind_points or= @getOwnSubviewBindPoints()
+
+ # check if any bindpoint specifies this subview by id
+ if view.id
+ bp = bind_points.filter "[data-subview$=':#{view.id}']"
+ return bp.eq 0 if bp.length
+
+ # Find all elements that specify this type as the subview type
+ bp = bind_points.filter "[data-subview='#{view.getClassName()}']"
+ return bp.eq 0 if bp.length
+
+
### Rendering Chain
toTemplateLocals: ->
- json = {value:v} = @model.toJSON()
- if _.isArray(v) or _.isObject(v)
- json.value = JSON.stringify v
- json
+ @model.toJSON()
- $template: (locals={}) ->
- $ @template do
- { $, _, op, @model, view:this } import @toTemplateLocals() import locals
+ $template: ->
+ $ @template { _, op, @model, view:this, ...@toTemplateLocals() }
build: ->
return this unless @template
@$el.html outer.html()
.attr do
id : outer.attr 'id'
- class : outer.attr('class')
+ class : outer.attr 'class'
@attachSubviews()
+ @isBuilt = true
this
render: ->
- @build()
+ if @isBuilt
+ @update()
+ else
+ @build()
+ @renderSubviews()
@trigger 'render', this
this
renderSubviews: ->
- _.invoke _.pluck(@subviews, 0), 'render'
+ @attachSubviews()
+ @subviews.invoke 'render'
this
-
- attach: (el) ->
- @$el.appendTo el
- # only trigger the event the first time
- return this if @_parented
- @_parented = true
- _.delay do
- ~> # have to let DOM settle to ensure elements can be found
- @delegateEvents()
- @trigger 'parent', this
- 50
+ update: ->
+ new DataBinding this .update @toTemplateLocals()
+ @trigger 'update', this
this
- remove : ->
- @undelegateEvents()
- @$el.remove()
- return this unless @_parented
- @_parented = false
- @trigger 'unparent', this
- this
+ /* * * * Events * * * */
- ### UI Utilities
+ bubbleEvent: (evt) ->
+ @invokeSubviews 'trigger', ...arguments
+ this
- hide : -> @$el.hide(); @trigger('hide', this); this
- show : -> @$el.show(); @trigger('show', this); this
- clear : -> @model.destroy(); @trigger('clear', this); @remove()
+ redispatch: (evt) ->
+ @trigger ...arguments
+ this
+ onlyOnReturn: (fn, ...args) ->
+ fn = _.debounce fn.bind(this), 50
+ (evt) ~> fn.apply this, args if evt.keyCode is 13
- # remove : ->
- # if (p = @$el.parent()).length
- # @$parent or= p
- # # @parent_index = p.children().indexOf @$el
- # @$el.remove()
- # this
- #
- # reparent : (parent=@$parent) ->
- # parent = $ parent
- # @$el.appendTo parent if parent?.length
- # this
+ /**
+ * Call a delegate on keypress == the return key.
+ * @returns {Function} Keypress event handler.
+ */
+ onReturnKeypress: (evt) ->
+ fn = this[@callOnReturnKeypress] if @callOnReturnKeypress
+ fn.call this if fn and evt.keyCode is 13
toString : ->
"#{@getClassName()}(model=#{@model})"
# }}}
+
+
+class exports.ViewList extends Array
+
+ (views=[]) ->
+ super ...
+
+ extend: (views) ->
+ _.each views, ~> @push it
+ this
+
+ findByModel: (model) ->
+ @find -> it.model is model
+
+ toString: ->
+ contents = if @length then "\"#{@join '","'}\"" else ''
+ "ViewList[#{@length}](#contents)"
+
+
+<[ each contains invoke pluck find remove compact flatten without union intersection difference unique uniq ]>
+ .forEach (methodname) ->
+ ViewList::[methodname] = -> _[methodname].call _, this, ...arguments
+
+
-mixins = require 'kraken/base/base-mixin'
-models = require 'kraken/base/base-model'
-views = require 'kraken/base/base-view'
-cache = require 'kraken/base/model-cache'
-cascading = require 'kraken/base/cascading-model'
-exports import mixins import models import views import cache import cascading
+mixins = require 'kraken/base/base-mixin'
+models = require 'kraken/base/base-model'
+views = require 'kraken/base/base-view'
+cache = require 'kraken/base/model-cache'
+cascading = require 'kraken/base/cascading-model'
+data_binding = require 'kraken/base/data-binding'
+exports import mixins import models import views import cache import cascading import data_binding
KNOWN_TAGS.update @getCategory()
# Ignore functions/callbacks and, ahem, hidden tags.
- type = @get 'type', '' .toLowerCase()
- tags = @get 'tags', []
+ type = @get 'type' .toLowerCase() or ''
+ tags = @get('tags') or []
if _.str.include(type, 'function') or _.intersection(tags, @IGNORED_TAGS).length
@set 'ignore', true
# will not trigger the 'changed:tags' event.
addTag: (tag) ->
return this unless tag
- tags = @get('tags', [])
+ tags = @get('tags') or []
tags.push tag
@set 'tags', tags
this
# will not trigger the 'changed:tags' event.
removeTag: (tag) ->
return this unless tag
- tags = @get('tags', [])
+ tags = @get('tags') or []
_.remove tags, tag
@set 'tags', tags
this
# A field's category is its first tag.
getCategory: ->
- @get('tags', [])[0]
+ tags = (@get('tags') or [])[0]
getCategoryIndex: ->
@getTagIndex @getCategory()
* @class List of ChartOption fields.
*/
ChartOptionList = exports.ChartOptionList = FieldList.extend do # {{{
- model : ChartOption
+ model : ChartOption
constructor: function ChartOptionList
isCollapsed : true
events :
- 'blur .value' : 'update'
- 'click input[type="checkbox"].value' : 'update'
- 'submit .value' : 'update'
+ 'blur .value' : 'change'
+ 'click input[type="checkbox"].value' : 'change'
+ 'submit .value' : 'change'
'click .close' : 'toggleCollapsed'
'click h3' : 'toggleCollapsed'
'click .collapsed' : 'onClick'
render: ->
FieldView::render ...
+ @$el.addClass 'ignore' if @get 'ignore'
@$el.addClass 'collapsed' if @isCollapsed
this
render: ->
console.log "#this.render(ready=#{@ready}) -> .isotope()"
- # Scaffold::render ...
+ Scaffold::render ...
return this unless @ready
- container = if @fields then @$el.find @fields else @$el
+
+ container = if @fields then @$ @fields else @$el
container
.addClass 'isotope'
.find '.chart-option.field' .addClass 'isotope-item'
this
getOptionsFilter: ->
- data = @$el.find '.options-filter-button.active' .toArray().map -> $ it .data()
+ data = @$ '.options-filter-button.active' .toArray().map -> $ it .data()
sel = data.reduce do
(sel, d) ->
sel += that if d.filter
sel
- ''
+ ':not(.ignore)'
sel
collapseAll: ->
- _.invoke @_subviews, 'collapse', true
+ _.invoke @subviews, 'collapse', true
# @renderSubviews()
false
expandAll: ->
- _.invoke @_subviews, 'collapse', false
+ _.invoke @subviews, 'collapse', false
# @renderSubviews()
false
* Add a ChartOption to this scaffold, rerendering the isotope
* layout after collapse events.
*/
- addOne: (field) ->
- view = Scaffold::addOne ...
- view.on 'change:collapse render', @render, this
+ addField: (field) ->
+ view = Scaffold::addField ...
+ # view.on 'change:collapse render', @render, this
+ view.on 'change:collapse', @render, this
view
toKV: ->
@trigger 'ready', this
attachGraphs: ->
- graphs_el = @$el.find '#graphs'
+ graphs_el = @$ '#graphs'
for id of @graph_ids
break unless graph = @graphs.get id
continue if graph.view.isAttached
Seq = require 'seq'
{ _, op,
} = require 'kraken/util'
-{ BaseView,
+{ BaseView, ViewList,
} = require 'kraken/base'
{ DataSetView,
} = require 'kraken/dataset/dataset-view'
className : 'data-ui'
template : require 'kraken/template/data'
- data : {}
datasources : null
initialize: ->
@graph_id = @options.graph_id
BaseView::initialize ...
+ @metric_views = new ViewList
@datasources = @model.sources
+ @model.metrics
+ .on 'add', @addMetric, this
+ .on 'remove', @removeMetric, this
@on 'ready', @onReady, this
@load()
onReady: ->
dataset = @model
- @metric_edit_view = @addSubview new MetricEditView {@graph_id, dataset, @datasources}
- @metric_edit_view
- .on 'update', @onUpdateMetric, this
+ @model.metrics.each @addMetric, this
+ # @metric_edit_view = @addSubview new MetricEditView {@graph_id, dataset, @datasources}
+ # @metric_edit_view
+ # .on 'update', @onUpdateMetric, this
- @dataset_view = @addSubview new DataSetView {@model, @graph_id, dataset, @datasources}
- @dataset_view
+ @dataset_view = new DataSetView {@model, @graph_id, dataset, @datasources}
+ @addSubview @dataset_view
.on 'add-metric', @onMetricsChanged, this
.on 'remove-metric', @onMetricsChanged, this
.on 'edit-metric', @editMetric, this
- @attachSubviews()
+ @render()
this
load: ->
@wait()
- $.getJSON '/datasources/all', (@data) ~>
- _.each @data, @canonicalizeDataSource, this
- @model.sources.reset _.map @data, -> it
- @ready = true
- @unwait()
- @render()
- @trigger 'ready', this
+ # $.getJSON '/datasources/all', (@data) ~>
+ # _.each @data, @canonicalizeDataSource, this
+ # @model.sources.reset _.map @data, -> it
+ @ready = true
+ @unwait()
+ # @render()
+ @trigger 'ready', this
/**
* Transform the `columns` field to ensure an Array of {label, type} objects.
toTemplateLocals: ->
attrs = _.clone @model.attributes
- { $, _, op, @model, view:this, @graph_id, @datasources } import attrs
+ { @graph_id, @datasources } import attrs
- # attachSubviews: ->
- # @$el.empty()
- # BaseView::attachSubviews ...
- # @$el.append '<div class="clearer"/>'
+ # Don't rebuild HTML, simply notify subviews
+ # render: ->
+ # @renderSubviews()
+ # @trigger 'render', this
# this
- # 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}
+ @metric_views.push @addSubview view
+ metric
+
+ removeMetric: (metric) ->
+ console.log "#this.removeMetric!", metric
+ return unless view = @metric_views.findByModel metric
+ @metric_views.remove view
+ @removeSubview view
+ metric
editMetric: (metric) ->
- @metric_edit_view.editMetric metric
+ console.log "#this.editMetric!", metric
+ @metric_views.invoke 'hide'
+ @metric_edit_view = @metric_views.findByModel metric
+ @metric_edit_view?.show()
@onMetricsChanged()
onMetricsChanged: ->
newMinHeight = Math.max do
oldMinHeight
@dataset_view.$el.height()
- @metric_edit_view.$el.height()
+ @metric_edit_view?.$el.height()
# console.log 'onMetricsChanged!', oldMinHeight, '-->', newMinHeight
@$el.css 'min-height', newMinHeight
view = @addSubview new DataSetMetricView {model:metric, @graph_id}
@views_by_cid[metric.cid] = view
- @$el.find '.metrics' .append view.render().el
+ @$ '.metrics' .append view.render().el
# @render()
@trigger 'add-metric', metric, view, this
this
editMetric: (metric) ->
- console.log "#this.editMetric!", metric
+ # console.log "#this.editMetric!", metric
if metric instanceof [jQuery.Event, Event]
metric = $ metric.currentTarget .data 'model'
view = @active_view = @views_by_cid[metric.cid]
- console.log ' --> metric:', metric, 'view:', view
+ console.log "#this.editMetric!", metric
- @$el.find '.metrics .dataset-metric' .removeClass 'metric-active'
+ @$ '.metrics .dataset-metric' .removeClass 'metric-active'
view.$el.addClass 'metric-active'
view.$el.find '.activity-arrow' .css 'font-size', 2+view.$el.height()
-{ _, op, CSVData,
+{ _, op,
} = require 'kraken/util'
+{ TimeSeriesData, CSVData,
+} = require 'kraken/timeseries'
{ BaseModel, BaseList, BaseView,
} = require 'kraken/base'
{ Metric, MetricList,
@constructor.register this
@metrics = new MetricList @attributes.metrics
@on 'change:metrics', @onMetricChange, this
- @load()
+ # @load()
canonicalize: (ds) ->
$.ajax do
url : url
dataType : 'json'
- success : @onLoadSuccess
+ success : (data) ~> @onLoadSuccess new TimeSeriesData data
error : @onLoadError
this
Cls = this
@register new Cls {id}
.on 'ready', -> cb.call cxt, null, it
+ .load()
_.bindAll DataSource, 'register', 'get', 'lookup'
template : require 'kraken/template/datasource-ui'
events :
- 'click .datasource-summary': 'onHeaderClick'
+ 'click .datasource-summary' : 'onHeaderClick'
+ 'click .datasource-source-metric' : 'onSelectMetric'
graph_id : null
dataset : null
onHeaderClick: ->
@$el.toggleClass 'in'
+ onSelectMetric: (evt) ->
+ tr = evt.currentTarget
+ idx = @$ '.source-metrics .datasource-source-metric' .toArray().indexOf tr
+ return unless idx is not -1
+
+
# }}}
className : 'metric-edit-ui'
template : require 'kraken/template/metric-edit'
+ callOnReturnKeypress : 'onChanged'
+ events:
+ 'keydown .metric-label' : 'onReturnKeypress'
+
graph_id : null
dataset : null
datasources : null
this import @options.{graph_id, dataset, datasources}
@model or= new Metric
BaseView::initialize ...
- @datasource_ui_view = @addSubview '.metric-datasource', new DataSourceUIView {@model, @graph_id, @dataset, @datasources}
- @$el.find '.metric-datasource' .append @datasource_ui_view.render().el
+ @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
toTemplateLocals: ->
build: ->
BaseView::build ...
if @datasource_ui_view
- @$el.find '.metric-datasource' .append @datasource_ui_view.render().el
+ @$ '.metric-datasource' .append @datasource_ui_view.render().el
+ this
+
+ update: ->
+ color = @model.get 'color'
+ @$ '.color-swatch' .css 'background-color', color
+ @$ '.metric-color' .val color
+ @$ '.metric-label' .val @model.get 'label'
+
this
+ onChanged: ->
+ attrs = @$ 'form.metric-edit-form' .formData()
+ @model.set attrs, {+silent}
+ @trigger 'update', this
+
editMetric: (metric) ->
console.log "#this.editMetric!", metric
- @datasource_ui_view.model = @model = metric
+ @datasource_ui_view.setModel @setModel metric
@render()
@show()
this
@lookupSource()
- getDates: ->
- @source.getDates()
+ getDateColumn: ->
+ @source.getDateColumn()
getData: ->
@source.getColumn @get 'source_col'
return { width, height } unless @ready
# Remove old style, as it confuses dygraph after options update
- viewport = @$el.find '.viewport'
+ viewport = @$ '.viewport'
viewport.attr 'style', ''
- label = @$el.find '.graph-legend'
+ label = @$ '.graph-legend'
if width is 'auto'
vpWidth = viewport.innerWidth()
renderChart: ->
data = @model.get 'dataset' #.getData()
size = @resizeViewport()
- viewport = @$el.find '.viewport'
+ viewport = @$ '.viewport'
# XXX: use @model.changedAttributes() to calculate what to update
options = @chartOptions() #import size
options import do
- labelsDiv : @$el.find '.graph-legend' .0
+ labelsDiv : @$ '.graph-legend' .0
valueFormatter : @numberFormatterHTML
axes:
x:
update: ->
locals = @toTemplateLocals()
- @$el.find '.graph-name a' .text(locals.name or '')
- @$el.find '.graph-desc' .html jade.filters.markdown locals.desc or ''
+ @$ '.graph-name a' .text(locals.name or '')
+ @$ '.graph-desc' .html jade.filters.markdown locals.desc or ''
this
render: ->
# last action that happens. If we don't
# defer, the focusing click will
# unselect the text.
- _.defer( ~> @$el.find '.graph-permalink input' .select() )
+ _.defer( ~> @$ '.graph-permalink input' .select() )
# Needed because (sigh) _.debounce returns undefined
stopAndRender: ->
valueFormatter xValueFormatter yValueFormatter
]>
__bind__ : <[
- render renderAll stopAndRender stopAndRenderAll resizeViewport wait unwait checkWaiting
+ render stopAndRender resizeViewport wait unwait checkWaiting
numberFormatter numberFormatterHTML
onReady onSync onModelChange onScaffoldChange
onFirstClickRenderOptionsTab onFirstClickRenderDataTab
]>
- __debounce__: <[ render renderAll ]>
+ __debounce__: <[ render ]>
tagName : 'section'
className : 'graph-edit graph'
template : require 'kraken/template/graph-edit'
BaseView ...
initialize : (o={}) ->
- # @data = {}
@model or= new Graph
@id = @graph_id = _.domize 'graph', (@model.id or @model.get('slug') or @model.cid)
BaseView::initialize ...
.on 'ready', @onReady, this
### Chart Options Tab, Scaffold
- @scaffold = @addSubview '.graph-options-pane', new ChartOptionScaffold
- @$el.find '.graph-options-pane' .append @scaffold.el
+ @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}
- @$el.on 'click', '.graph-data-tab', @onFirstClickRenderDataTab
- # Rerender the options boxes once the tab is visible
+ # Rerender once the tab is visible
# Can't use @events because we need to bind before registering
- @$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
+ @$el.on 'click', '.graph-data-tab', @onFirstClickRenderDataTab
+ @$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
### Graph Data UI
- @data = @addSubview '.graph-data-pane', new DataView { model:@model.get('data'), graph_id:@id }
- @$el.find '.graph-data-pane' .append @data.render().el
- @data
+ @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
toTemplateLocals: ->
attrs = _.clone @model.attributes
delete attrs.options
- # delete attrs.dataset
- # attrs.data = @data
- { $, _, op, @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs
+ { @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs
/**
modelH = height = @model.get 'height'
return { width, height } unless @ready
- viewport = @$el.find '.viewport'
+ viewport = @$ '.viewport'
# Remove old style, as it confuses dygraph after options update
viewport.attr 'style', ''
- label = @$el.find '.graph-label'
+ label = @$ '.graph-label'
if width is 'auto'
vpWidth = viewport.innerWidth()
# Repopulate UI from Model
renderDetails: ->
- form = @$el.find 'form.graph-details'
+ form = @$ 'form.graph-details'
for k, v in @model.attributes
continue if k is 'options'
txt = @model.serialize v
form.find "textarea[name=#k]" .text txt
# Graph Name field is not part of the form due to the layout.
- @$el.find "input.graph-name[name='name']" .val @get 'name'
+ @$ "input.graph-name[name='name']" .val @get 'name'
this
# Redraw chart inside viewport.
options = @chartOptions() #import size
options import do
labels : dataset.getLabels()
- labelsDiv : @$el.find '.graph-label' .0
+ labelsDiv : @$ '.graph-label' .0
valueFormatter : @numberFormatterHTML
axes:
x:
# dygraphs to reset the current option state.
@chart?.destroy()
@chart = new Dygraph do
- @$el.find '.viewport' .0
+ @$ '.viewport' .0
data
options
# unless @chart
# @chart = new Dygraph do
- # @$el.find '.viewport' .0
+ # @$ '.viewport' .0
# data
# options
# else
this
attachSubviews: ->
- @$el.find '.graph-options-pane' .append @scaffold.el if @scaffold
- @$el.find '.graph-data-pane' .append @data.render().el if @data
+ 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
-
- renderAll: ->
- return this unless @ready
- # console.log "#this.renderAll!"
- @wait()
- _.invoke @scaffold.subviews, 'render'
- @scaffold.render()
- @render()
- @unwait()
+ # 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
/**
* Retrieve or construct the spinner.
*/
spinner: ->
- el = @$el.find '.graph-spinner'
+ el = @$ '.graph-spinner'
unless el.data 'spinner'
### Spin.js Options ###
opts =
onReady: ->
return if @ready
- # $.getJSON '/datasources/all', (@data) ~>
console.log "(#this via GraphEditView).ready!"
@ready = @scaffold.ready = true
@unwait() # clears `wait()` from `initialize`
return unless @ready
console.info "#this.sync() --> success!"
# TODO: UI alert
- # @change()
- # @model.change()
@chartOptions @model.getOptions(), {+silent}
- @renderAll()
+ @render()
onStartWaiting: ->
console.log "#this.onStartWaiting!", @checkWaiting()
@scaffold.render()
onFirstClickRenderDataTab: ->
- # @$el.off 'click', '.graph-data-tab', @onFirstClickRenderDataTab
- _.defer ~> @data.onMetricsChanged()
+ @$el.off 'click', '.graph-data-tab', @onFirstClickRenderDataTab
+ _.defer ~> @data_view.onMetricsChanged()
onKeypress: (evt) ->
$(evt.target).submit() if evt.keyCode is 13
onDetailsSubmit: ->
console.log "#this.onDetailsSubmit!"
- data = _.synthesize do
- @$el.find('form.graph-details').serializeArray()
- -> [it.name, it.value]
+ data = @$ 'form.graph-details' .formData()
@model.set data
false
# Needed because (sigh) _.debounce returns undefined, and we need to preventDefault()
stopAndRender : -> @render ... ; false
- stopAndRenderAll : -> @renderAll ... ; false
# }}}
{ _, op,
} = require 'kraken/util'
+{ TimeSeriesData, CSVData,
+} = require 'kraken/timeseries'
{ BaseView, BaseModel, BaseList,
} = require 'kraken/base'
{ Field, FieldList, FieldView, Scaffold,
type : 'string'
events :
- 'blur .value' : 'update'
- 'submit .value' : 'update'
+ 'blur .value' : 'change'
+ 'submit .value' : 'change'
constructor: function FieldView
initialize: ->
# console.log "#this.initialize!"
BaseView::initialize ...
- @type = @model.get('type', 'string').toLowerCase()
+ @type = @model.get('type').toLowerCase() or 'string'
- update: ->
+ toTemplateLocals: ->
+ json = {value:v} = @model.toJSON()
+ if _.isArray(v) or _.isPlainObject(v)
+ json.value = JSON.stringify v
+ json
+
+ change: ->
if @type is 'boolean'
- val = !! @$el.find('.value').attr('checked')
+ val = !! @$('.value').attr('checked')
else
- val = @model.getParser() @$el.find('.value').val()
+ val = @model.getParser() @$('.value').val()
current = @model.getValue()
return if _.isEqual val, current
- console.log "#this.update( #current -> #val )"
+ console.log "#this.change( #current -> #val )"
@model.setValue val, {+silent}
- @trigger 'update', this
+ @trigger 'change', this
render: ->
return @remove() if @model.get 'ignore', false
# model, collection, el, id, className, tagName, attributes
Scaffold = exports.Scaffold = BaseView.extend do # {{{
- __bind__ : <[ addOne addAll ]>
+ __bind__ : <[ addField resetFields ]>
tagName : 'form'
className : 'scaffold'
@model = (@collection or= new CollectionType)
BaseView::initialize ...
- @collection.on 'add', @addOne
- @collection.on 'reset', @addAll
+ @collection.on 'add', @addField, this
+ @collection.on 'reset', @resetFields, this
@$el.data { model:@collection, view:this } .addClass @className
- renderSubviews: ->
- _.invoke @_subviews, 'render'
- this
-
- addOne: (field) ->
- # console.log "[S] #this.addOne!", @..__super__
- _.remove @_subviews, field.view if field.view
+ addField: (field) ->
+ @removeSubview field.view if field.view
# avoid duplicating event propagation
field.off 'change:value', @change, this
field.on 'change:value', @change, this
SubviewType = @subviewType
- view = new SubviewType model:field
- @_subviews.push view
- container = if @fields then @$el.find @fields else @$el
- container.append view.render().el unless field.get 'ignore'
- view.on 'update', @change.bind(this, field)
+ view = @addSubview new SubviewType model:field
+ view.on 'change', @change.bind(this, field)
@render()
view
- addAll: ->
- _.invoke @_subviews, 'remove'
- @_subviews = []
- @collection.each @addOne
+ resetFields: ->
+ @removeAllSubviews()
+ @collection.each @addField
this
change: (field) ->
a.advanced-filter-button.options-filter-button.btn(href="#", data-filter="") Advanced
- .fields
- //- .fields.control-group
+ .fields(data-subview="ChartOptionView")
section.data-ui
- //-
- .row-fluid
- //-
- .dataset-controls.dropdown
- select(name="dataset")
- for datum, k in datasources
- - var selected = (datum.url === dataset ? 'selected' : null);
- option(value=datum.url, name=datum.id, selected=selected) #{datum.name}
-
- //-
- a.span3.dataset.dropdown-toggle(data-toggle="dropdown", data-target="#{graph_id} .dataset-dropdown", href="#dataset-dropdown") Select...
- ul.dropdown-menu
- for datum, k in datasources
- li: a(href="#", data-dataset="#{datum.id}") #{datum.name}
+ .metric_edit_view_pane(data-subview="MetricEditView")
+ .data_set_view_pane(data-subview="DataSetView")
th.col-source Source
th.col-times Timespan
th.col-actions Actions
- tbody.metrics
+ tbody.metrics(data-subview="DataSetMetricView")
//- DataSetMetricViews attach here
li(class=activeClass): a(href="#datasource-selector_datasource-source-#{ds.id}", data-toggle="tab", data-target=ds_target) #{ds.shortName}
.datasource-sources-info.tab-content
+ //- DataSourceViews attach here
for source, k in datasources.models
- var ds = source.attributes
- var activeClass = (source_id === ds.id ? 'active' : '')
+- var ds = source.attributes
+- var activeClass = (source_id === ds.id ? 'active' : '')
+.datasource-source.tab-pane(class="datasource-source-#{ds.id} #{activeClass}")
+ .datasource-source-details.well
+ .source-name #{ds.name}
+ .source-id #{ds.id}
+ .source-format #{ds.format}
+ .source-charttype #{ds.chart.chartType}
+ input.source-url(type="text", name="source-url", value=ds.url)
+ .datasource-source-time
+ .source-time-start #{ds.timespan.start}
+ .source-time-end #{ds.timespan.end}
+ .source-time-step #{ds.timespan.step}
+ .datasource-source-metrics
+ table.table.table-striped
+ thead
+ tr
+ th.source-metric-idx #
+ th.source-metric-label Label
+ th.source-metric-type Type
+ 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)
+ td.source-metric-idx #{m.idx}
+ td.source-metric-label #{m.label}
+ td.source-metric-type #{m.type}
+
- var graph_id = view.id || model.id || model.cid
section.graph-edit.graph(id=graph_id)
- //- Graph Name field is not part of the form due to the layout.
.graph-name-row.graph-details.row-fluid.control-group
- //- label.name.control-label(for="#{id}_name"): h3 Graph Name
input.span6.graph-name(type='text', id="#{graph_id}_name", name="name", placeholder='Graph Name', value=name)
.graph-viewport-row.row-fluid
<textarea class="span3 desc" id="#{graph_id}_desc" name="desc" placeholder="Graph description.">#{desc}</textarea>
p.help-block A description of the graph.
- .graph-data-pane.tab-pane(id="#{graph_id}-tab-data")
+ .graph-data-pane.tab-pane(id="#{graph_id}-tab-data", data-subview="DataView")
//-
.row-fluid
label.dataset.control-label(for="#{graph_id}_dataset") Data Set
input.span3.dataset(type='text', id="#{graph_id}_dataset", name='dataset', placeholder='URL to dataset file', value=dataset)
p.help-block This dataset filename will soon be replaced by a friendly UI.
- .graph-options-pane.tab-pane(id="#{graph_id}-tab-options")
+ .graph-options-pane.tab-pane(id="#{graph_id}-tab-options", data-subview="ChartOptionScaffold")
section.metric-edit-ui
- .inner: form.form-horizontal
+ .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)
- .metric-datasource.control-group
+ .metric-datasource.control-group(data-subview="DataSourceUIView")
.metric-actions.control-group
a.delete-button.btn.btn-danger(href="#")
_ = require 'kraken/util/underscore'
-op = require 'kraken/util/op'
-
-TimeSeriesData = require 'kraken/util/timeseries/timeseries'
+TimeSeriesData = require 'kraken/timeseries/timeseries'
DASH_PATTERN = /-/g
* @returns {this}
*/
parse: (@rawData) ->
- if typeof rawData is not 'string'
-
+ return this if typeof rawData is not 'string'
o = @options
lines = rawData.split o.rowSep
_ = require 'kraken/util/underscore'
-op = require 'kraken/util/op'
-_ = require 'kraken/util/underscore'
+_ = exports._ = require 'kraken/util/underscore'
+op = exports.op = require 'kraken/util/op'
-root = do -> this
-root.console or= _ <[ log info warn error dir table group groupCollapsed groupEnd ]> .synthesize -> [it, nop]
+# Root object -- `window` in the browser, `global` in Node.
+root = exports.root = do -> this
+# Stub out console with empty methods
+root.console or= _ <[ log info warn error dir table group groupCollapsed groupEnd ]> .synthesize -> [it, op.nop]
+
+### Extend jQuery with useful functions
+
+/**
+ * @returns {Object} Object of the data from the form, via `.serializeArray()`.
+ */
+root.jQuery?.fn.formData = ->
+ _.synthesize do
+ this.serializeArray()
+ -> [it.name, it.value]
+
+/**
+ * Invokes a jQuery method on each element, returning the array of the result.
+ * @returns {Array} Results.
+ */
root.jQuery?.fn.invoke = (method, ...args) ->
for el, idx of this
- el = jQuery(el)
- el[method] ...args
-
-op = require 'kraken/util/op'
-backbone = require 'kraken/util/backbone'
-parser = require 'kraken/util/parser'
-Cascade = require 'kraken/util/cascade'
-CSVData = require 'kraken/util/csv'
-exports import { root, _, op, backbone, parser, Cascade, CSVData, }
-
-# HashSet = require 'kraken/util/hashset'
-# BitString = require 'kraken/util/bitstring'
-# {crc32} = require 'kraken/util/crc'
-# exports import { HashSet, BitString, crc32, }
+ jQuery(el)[method] ...args
+
+
+backbone = exports.backbone = require 'kraken/util/backbone'
+parser = exports.parser = require 'kraken/util/parser'
+Cascade = exports.Cascade = require 'kraken/util/cascade'
+
+# HashSet = exports.HashSet = require 'kraken/util/hashset'
+# BitString = exports.BitString = require 'kraken/util/bitstring'
+# {crc32} = exports.{crc32} = require 'kraken/util/crc'
+
+STRIP_PAT = /(^\s*|\s*$)/g
+strip = (s) ->
+ if s then s.replace STRIP_PAT, '' else s
FALSEY = /^\s*(?:no|off|false)\s*$/i
parseBool = (s) ->
i = parseInt(s or 0)
!! if isNaN(i) then not FALSEY.test(s) else i
+
+
module.exports = op =
-
I : (x) -> x
K : (k) -> -> k
nop : ->
kObject : -> {}
kArray : -> []
- # values
+ ### values
val : (def,o) -> o ? def
ok : (o) -> o?
notOk : (o) -> o!?
arguments[1] = a
fn.apply this, arguments
- # reduce-ordered values & accessors
+ ### reduce-ordered values & accessors
khas : (k,o) -> k in o
kget : (k,o) -> o[k]
defkget : (def,k,o) -> if k in o then o[k] else def
thisget : (k) -> this[k]
vkset : (o,v,k) -> o[k] = v if o and k?; o
- # curry-ordered values & accessors
+ ### curry-ordered values & accessors
has : (o,k) -> k in o
get : (o,k) -> o[k]
getdef : (o,k,def) -> if k in o then o[k] else def
obj[name] ...args.concat(_args) if obj?[name]
isK : (k) -> (v) -> v is k
- # type coercion (w/ limited parameters for mapping)
+ ### type coercion (w/ limited parameters for mapping)
parseBool : parseBool
toBool : parseBool
toInt : (v) -> parseInt v
toFloat : (v) -> parseFloat v
toStr : (v) -> String v
- toObject : (v) -> if typeof v is 'string' then JSON.parse(v) else v
+ toObject : (v) ->
+ if typeof v is 'string' and strip(v)
+ JSON.parse v
+ else
+ v
- # comparison
+ ### comparison
cmp : (x,y) -> if x < y then -1 else (if x > y then 1 else 0)
eq : (x,y) -> x == y
ne : (x,y) -> x != y
lt : (x,y) -> x < y
le : (x,y) -> x <= y
- # math
+ ### math
add : (x,y) -> x + y
sub : (x,y) -> x - y
mul : (x,y) -> x * y
log2 : (n) -> Math.log n / Math.LN2
- # logic
+ ### logic
is : (x,y) -> x is y
isnt : (x,y) -> x is not y
and : (x,y) -> x and y
or : (x,y) -> x or y
not : (x) -> not x
- # bitwise
+ ### bitwise
bitnot : (x) -> ~x
bitand : (x,y) -> x & y
bitor : (x,y) -> x | y
rshift : (x,y) -> x >> y
# zrshift : (x,y) -> x >>> y
- # binary
+ ### binary
# Binary representation of the number.
bin : (n) ->
ord : -> String(it).charCodeAt 0
encode : -> it and $ "<div>#it</div>" .html().replace /"/g, '"'
decode : -> it and $ "<div>#it</div>" .text()
-
+ strip : strip