_ = require 'kraken/util/underscore'
{ BaseView,
} = require 'kraken/base'
-{ ChartOptionScaffold, ChartOption, ChartOptionList, DEBOUNCE_RENDER,
+{ ChartOptionScaffold, DEBOUNCE_RENDER,
} = require 'kraken/chart'
-{ VisModel,
-} = require 'kraken/vis/vis-model'
+{ Graph,
+} = require 'kraken/graph/graph-model'
/**
- * View for a graph visualization encapsulating the UI for:
+ * @class View for a graph visualization encapsulating the editing UI for:
* - Graph metadata, such as name, description, slug
+ * - Chart options, using ChartOptionScaffold
*/
-VisView = exports.VisView = BaseView.extend do # {{{
+GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
FILTER_CHART_OPTIONS : <[
file labels visibility colors dateWindow ticker timingName xValueParser
axisLabelFormatter xAxisLabelFormatter yAxisLabelFormatter
__bind__ : <[
render renderAll resizeViewport
formatter axisFormatter
- onReady onModelChange onScaffoldChange onFirstClickRenderOptionsTab
+ onReady onSync
+ onModelChange onScaffoldChange onFirstClickRenderOptionsTab
]>
__debounce__: <[ render renderAll ]>
- ctorName : 'VisView'
+ ctorName : 'GraphEditView'
tagName : 'section'
- className : 'graph'
- template : require 'kraken/template/graph'
+ className : 'graph graph-edit'
+ template : require 'kraken/template/graph-edit'
events:
'click .redraw-button' : 'render'
'click .save-button' : 'save'
- # 'click .load-button' : 'load'
+ 'click .load-button' : 'load'
'keypress form.details input[type="text"]' : 'onKeypress'
'keypress form.options .value' : 'onKeypress'
'submit form.details' : 'onDetailsSubmit'
initialize : (o={}) ->
- @model or= new VisModel
+ @model or= new Graph
BaseView::initialize ...
# console.log "#this.initialize!"
# Note: can't debounce the method itself, as the debounce wrapper returns undefined
$ root .on 'resize', _.debounce(@resizeViewport, DEBOUNCE_RENDER)
- @id = _.domize 'graph', (@model.get('slug', @model.id or @model.cid))
+ @id = _.domize 'graph', (@model.get('slug') or @model.id or @model.cid)
- @model.on 'destroy', @remove, this
- @model.on 'change', @render, this
- @model.on 'change:dataset', @onModelChange
- @model.on 'change:options', @onModelChange
+ @model
+ .on 'ready', @onReady
+ .on 'sync', @onSync
+ .on 'destroy', @remove, this
+ .on 'change', @render, this
+ .on 'change:dataset', @onModelChange
+ .on 'change:options', @onModelChange
+ .on 'error', ~>
+ console.error "#this.error!", arguments
+ # TODO: UI alert
# Rerender the options boxes once the tab is visible
@$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
@scaffold = new ChartOptionScaffold
@$el.find '.graph-options-pane' .append @scaffold.el
@scaffold.collection.reset that if o.graph_spec
-
@scaffold.on 'change', @onScaffoldChange
- options = @model.getOptions()
- @chartOptions options, {+silent}
-
+ @chartOptions @model.getOptions(), {+silent}
@resizeViewport()
- _.delay @onReady, DEBOUNCE_RENDER
+ # _.delay @onReady, DEBOUNCE_RENDER
- change: ->
- @model.change()
- @scaffold.invoke 'change'
- this
+
+
+ load: ->
+ console.log "#this.load!"
+ @model.fetch()
save: ->
console.log "#this.save!"
- $.ajax do
- url : '/graph/save'
- type : 'POST'
- data : @toJSON()
- success : (response) ->
- console.log 'saved!'
- error : (err) ->
- console.error "error!", arguments
+ id = @model.id or @model.get('slug')
+ @model.save {id}, {+wait}
+ change: ->
+ @model.change()
+ @scaffold.invoke 'change'
+ this
+
chartOptions: (values, opts) ->
# Handle @chartOptions(k, v, opts)
if arguments.length > 1 and typeof values is 'string'
delete options[k]
options
+
+ toTemplateLocals: ->
+ attrs = _.clone @model.attributes
+ delete attrs.options
+ { $, _, op, @model, view:this } import attrs
+
+
/**
* Resizes chart according to the model's width and height.
* @return { width, height }
size
+ # Repopulate UI from Model
+ renderDetails: ->
+ form = @$el.find 'form.details'
+ for k, v in @model.attributes
+ continue if k is 'options'
+ txt = @model.serialize v
+
+ el = form.find "input[name=#k]"
+ if el.attr('type') is 'checkbox'
+ el.attr 'checked', if v then 'checked' else ''
+ else
+ el.val txt
+
+ form.find "textarea[name=#k]" .text txt
+ this
+
render: ->
return this unless @ready
+ @renderDetails()
dataset = @model.get 'dataset'
size = @resizeViewport()
# @chart.resize size
@updateURL()
+ @trigger 'render', this
this
renderAll: ->
### Event Handlers {{{
onReady: ->
- # console.log "(#this via VisView).ready!"
+ console.log "(#this via GraphEditView).ready!"
@ready = @scaffold.ready = true
+ @onSync()
+
+ onSync: ->
+ return unless @ready
+ console.info "#this.sync() --> success!"
+ # TODO: UI alert
# @change()
- @model.change()
+ # @model.change()
+ @chartOptions @model.getOptions(), {+silent}
@renderAll()
onModelChange: ->
changes = @model.changedAttributes()
- # console.log 'VisModel.changed( options ) ->', JSON.stringify changes
+ options = @model.getOptions()
+ # console.log "Graph.changed( options ) ->\n\tchanges: #{JSON.stringify changes}\n\toptions: #{JSON.stringify options}" #"\n\t^opts: #{JSON.stringify _.intersection _.keys(changes), _.keys(options)}"
@chart.updateOptions file:that if changes?.dataset
- @chartOptions that, {+silent} if changes?.options
+ @chartOptions options, {+silent} if changes?.options
onScaffoldChange: (scaffold, value, key, field) ->
current = @model.getOption(key)
* Represents a Graph, including its charting options, dataset, annotations, and all
* other settings for both its content and presentation.
*/
-VisModel = exports.VisModel = BaseModel.extend do # {{{
- ctorName : 'VisModel'
+Graph = exports.Graph = BaseModel.extend do # {{{
+ ctorName : 'Graph'
IGNORE_OPTIONS : <[ width height timingName ]>
urlRoot : '/graph'
- idAttribute : 'slug'
ready : false
/**
/**
* List of graph parents.
- * @type VisList
+ * @type GraphList
*/
parents : null
- constructor : (attributes={}, options) ->
- # @on 'ready', ~> console.log "(#this via VisModel).ready!"
+ constructor : (attributes={}, opts) ->
+ @on 'ready', ~> console.log "(#this via Graph).ready!"
attributes.options or= {}
@optionCascade = new Cascade attributes.options
- BaseModel.call this, attributes, options
+ BaseModel.call this, attributes, opts
- initialize : ->
+ initialize : (attributes, opts) ->
@__super__.initialize ...
+ opts = {+autoLoad} import (opts or {})
- @parents = new VisList
+ @constructor.register this
+ @parents = new GraphList
# TODO: Load on-demand
@chartType = ChartType.lookup @get('chartType')
# unless @id or @get('id') or @get('slug')
# @set 'slug', "unsaved_graph_#{@cid}"
- @constructor.register this
@trigger 'init', this
- @load()
+ @load() if opts.autoLoad
load: (opts={}) ->
return this if @ready and not opts.force
+ self = this
@trigger 'load', this
- Seq @get('parents')
- .seqMap -> VisModel.lookup it, this
+ Seq()
+ .seq_ (next) ~>
+ if @isNew()
+ next.ok()
+ else
+ console.log "#{this}.fetch()..."
+ @fetch do
+ error : (err) ~>
+ console.error "#{this}.fetch() --> error! #arguments"
+ next.ok()
+ success : (model, res) ~>
+ console.log "#{this}.fetch() --> success!", res
+ next.ok res
+ .seq_ (next) ~>
+ next.ok @get('parents')
+ .flatten()
+ .seqMap -> Graph.lookup it, this
.seqEach_ (next, parent) ~>
@parents.add parent
@optionCascade.addLookup parent.get('options')
.seq ~>
@ready = true
@trigger 'ready', this
+
+ this
### Accessors
values = { "#key": value }
values = @parse values
- if @ready and values.options
+ setter = (@__super__ or BaseModel::).set
+
+ # Merge options in, firing granulated change events
+ if values.options
+ # Remove from values to prevent the super call to `set()` from
+ # replacing the object wholesale.
options = delete values.options
+
+ # ...Unless we don't have one yet.
+ if not @attributes.options
+ setter.call this, {options}, {+silent}
+
+ # Now delegate `setOption()` to do the nested merging.
@setOption options, opts
- (@__super__ or BaseModel::).set.call this, values, opts
+ # Deal with everything else
+ setter.call this, values, opts
@optionCascade.isChangedValue k
and not @isDefaultOption k
+
toJSON: (opts={}) ->
- opts = {+keepDefaults} import opts
-
- # use jQuery's deep-copy implementation
- json = $.extend true, {}, @attributes
- # json = _.clone(@attributes) import { options:_.clone(@attributes.options) }
- return json if opts.keepDefaults
-
- for k, v in json.options
- delete json.options[k] if v is void or @isDefaultOption k
- json
+ 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) }
toKVPairs: (opts={}) ->
opts = {-keepSlug, -keepDefaults, -keepUnchanged} import opts
&nb