From: dsc Date: Thu, 29 Mar 2012 13:13:15 +0000 (-0700) Subject: Renames Vis* to Graph*. Adds support for loading graphs off the server. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=e4074e9812909bbdec8f2aa75b2ffda99d414ccb;p=kraken-ui.git Renames Vis* to Graph*. Adds support for loading graphs off the server. --- diff --git a/lib/graph/graph-display-view.co b/lib/graph/graph-display-view.co new file mode 100644 index 0000000..8d24a78 --- /dev/null +++ b/lib/graph/graph-display-view.co @@ -0,0 +1,30 @@ +root = do -> this + +_ = require 'kraken/util/underscore' +{ BaseView, +} = require 'kraken/base' +{ ChartType, DEBOUNCE_RENDER, +} = require 'kraken/chart' +{ Graph, +} = require 'kraken/graph/graph-model' + + + +GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{ + ctorName : 'GraphDisplayView' + tagName : 'section' + className : 'graph' + template : require 'kraken/template/graph-display' + + # events : + # 'blur .value' : 'update' + # 'submit .value' : 'update' + + + initialize: -> + @model or= new Graph + BaseView::initialize ... + + + toString: -> "#{@ctorName}(#{@model})" +# }}} diff --git a/lib/vis/vis-view.co b/lib/graph/graph-edit-view.co similarity index 74% rename from lib/vis/vis-view.co rename to lib/graph/graph-edit-view.co index 8275daa..0050f50 100644 --- a/lib/vis/vis-view.co +++ b/lib/graph/graph-edit-view.co @@ -3,20 +3,21 @@ root = do -> this _ = 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 @@ -25,18 +26,19 @@ VisView = exports.VisView = BaseView.extend do # {{{ __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' @@ -48,7 +50,7 @@ VisView = exports.VisView = BaseView.extend do # {{{ initialize : (o={}) -> - @model or= new VisModel + @model or= new Graph BaseView::initialize ... # console.log "#this.initialize!" @@ -59,12 +61,18 @@ VisView = exports.VisView = BaseView.extend do # {{{ # 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 @@ -74,33 +82,30 @@ VisView = exports.VisView = BaseView.extend do # {{{ @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' @@ -120,6 +125,13 @@ VisView = exports.VisView = BaseView.extend do # {{{ 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 } @@ -149,9 +161,26 @@ VisView = exports.VisView = BaseView.extend do # {{{ 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() @@ -183,6 +212,7 @@ VisView = exports.VisView = BaseView.extend do # {{{ # @chart.resize size @updateURL() + @trigger 'render', this this renderAll: -> @@ -225,17 +255,25 @@ VisView = exports.VisView = BaseView.extend do # {{{ ### 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) diff --git a/lib/vis/vis-model.co b/lib/graph/graph-model.co similarity index 72% rename from lib/vis/vis-model.co rename to lib/graph/graph-model.co index bbd2e7a..d60c6cb 100644 --- a/lib/vis/vis-model.co +++ b/lib/graph/graph-model.co @@ -15,12 +15,11 @@ root = do -> this * 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 /** @@ -31,7 +30,7 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ /** * List of graph parents. - * @type VisList + * @type GraphList */ parents : null @@ -66,34 +65,51 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ - 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') @@ -101,6 +117,8 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ .seq ~> @ready = true @trigger 'ready', this + + this ### Accessors @@ -120,11 +138,23 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ 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 @@ -200,34 +230,29 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ @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 # use jQuery's deep-copy implementation - kvo = $.extend true, {}, @attributes + kvo = @toJSON opts kvo.parents = JSON.stringify kvo.parents delete kvo.slug unless opts.keepSlug # console.group 'toKVPairs' # console.log '[IN]', JSON.stringify kvo - kvo.options = @getOptions opts for k, v in kvo.options kvo.options[k] = @serialize v # console.log '[OUT]', JSON.stringify kvo # console.groupEnd() + _.collapseObject kvo toKV: (opts) -> @@ -246,10 +271,10 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ # }}} -VisList = exports.VisList = BaseList.extend do # {{{ - ctorName : 'VisList' +GraphList = exports.GraphList = BaseList.extend do # {{{ + ctorName : 'GraphList' urlRoot : '/graph' - model : VisModel + model : Graph initialize : -> BaseList::initialize ... @@ -264,15 +289,16 @@ VisList = exports.VisList = BaseList.extend do # {{{ /* * * * Visualization Cache for parent-lookup * * * {{{ */ -VIS_CACHE = exports.VIS_CACHE = new VisList +GRAPH_CACHE = exports.GRAPH_CACHE = new GraphList -VisModel import do - CACHE : VIS_CACHE +Graph import do + CACHE : GRAPH_CACHE register: (model) -> - # console.log "#{@CACHE}.register(#{model.id or model.get('id')})", model - unless @CACHE.contains model - @CACHE.add 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) -> @@ -284,9 +310,8 @@ VisModel import do cb null, that else Cls = this - new Cls { id, slug:id } .fetch do - success : -> cb null, it - error : cb + @register new Cls { id, slug:id } + .on 'ready', -> cb null, it diff --git a/lib/graph/index.co b/lib/graph/index.co index 5437dcc..41c4e11 100644 --- a/lib/graph/index.co +++ b/lib/graph/index.co @@ -1,3 +1,4 @@ -# models = require 'kraken/graph/graph-model' -# views = require 'kraken/graph/graph-view' -# exports import models import views +models = require 'kraken/graph/graph-model' +display_views = require 'kraken/graph/graph-display-view' +edit_views = require 'kraken/graph/graph-edit-view' +exports import models import display_views import edit_views diff --git a/lib/main.co b/lib/main.co index 2134b50..dffeb9e 100644 --- a/lib/main.co +++ b/lib/main.co @@ -11,38 +11,30 @@ Backbone = require 'backbone' ChartOption, ChartOptionList, TagSet, ChartOptionView, ChartOptionScaffold, } = require 'kraken/chart' -{ VisView, VisModel, VisList, -} = require 'kraken/vis' +{ Graph, GraphList, GraphEditView, +} = require 'kraken/graph' root = this CHART_OPTIONS_SPEC = [] CHART_DEFAULT_OPTIONS = {} -ROOT_VIS_DATA = {} -ROOT_VIS_OPTIONS = {} # Create the Graph Scaffold main = -> - # opts = root.CHART_DEFAULT_OPTIONS = {} - # for opt of root.CHART_OPTIONS_SPEC - # opts[opt.name] = opt.default - + # Set up Dygraph chart type spec + # TODO: load this on-demand dyglib = new DygraphsChartType CHART_OPTIONS_SPEC - # TODO: create a preset manager - # Remove chart options from data so we don't have to deepcopy - ROOT_VIS_OPTIONS := delete root.ROOT_VIS_DATA.options - # Bind to URL changes History.Adapter.bind window, 'statechange', -> console.log 'StateChange!\n\n', String(root.location), '\n\n' - data = {} # Process URL loc = String root.location + data = {} # If we got querystring args, apply them to the graph if loc.split '?' .1 @@ -52,23 +44,24 @@ main = -> data.options or {} (v, k) -> [ k, dyglib.parseOption(k,v) ] - # Extract slug from URL + # Extract id from URL if match = /\/graph\/(?!view)([^\/?]+)/i.exec loc - data.slug = match[1] + data.id = data.slug = match[1] # _.dump _.clone(data.options), 'data.options' - vis = root.vis = new VisModel data, {+parse} - graph = root.graph = new VisView do - graph_spec : root.CHART_OPTIONS_SPEC - model : vis - $ '#content .inner' .append graph.el + # Instantiate model & view + graph = root.graph = new Graph data, {+parse} + view = root.view = new GraphEditView do + graph_spec : root.CHART_OPTIONS_SPEC # FIXME: necessary? + model : graph + + $ '#content .inner' .append view.el # Load data files -Seq([ <[ CHART_OPTIONS_SPEC /schema/dygraph.json ]>, - <[ ROOT_VIS_DATA /presets/root.json ]> +Seq([ <[ CHART_OPTIONS_SPEC /schema/dygraph.json ]> ]) .parEach_ (next, [key, url]) -> jQuery.ajax do diff --git a/lib/template/graph.jade b/lib/template/graph-display.jade similarity index 100% copy from lib/template/graph.jade copy to lib/template/graph-display.jade diff --git a/lib/template/graph.jade b/lib/template/graph-edit.jade similarity index 83% rename from lib/template/graph.jade rename to lib/template/graph-edit.jade index 74e403e..7601f63 100644 --- a/lib/template/graph.jade +++ b/lib/template/graph-edit.jade @@ -1,6 +1,6 @@ - var id = model.id || model.cid - var graph_id = view.id -section.graph(id=graph_id) +section.graph.graph-edit(id=graph_id) form.details.form-horizontal .name-row.row-fluid.control-group @@ -30,29 +30,29 @@ section.graph(id=graph_id) .row-fluid .half.control-group .control-group - label.slug.control-label(for='slug') Slug + label.slug.control-label(for="#{id}_slug") Slug .controls - input.span3.slug(type='text', id='slug', name='slug', placeholder='graph_slug', value=slug) + input.span3.slug(type='text', id="#{id}_slug", name='slug', placeholder='graph_slug', value=slug) p.help-block The slug uniquely identifies this graph and will be displayed in the URL. .control-group - label.width.control-label(for='width') Size + label.width.control-label(for="#{id}_width") Size .controls - input.span1.width(type='text', id='width', name='width', value=width) + input.span1.width(type='text', id="#{id}_width", name='width', value=width) | × - input.span1.height(type='text', id='height', name='height', value=height) + input.span1.height(type='text', id="#{id}_height", name='height', value=height) p.help-block Choosing 'auto' will size the graph to the viewport bounds. .half.control-group - label.desc.control-label(for='desc') Description + label.desc.control-label(for="#{id}_desc") Description .controls //- textarea.span3.desc(id='desc', name='desc', placeholder='Graph description.') #{desc} - + p.help-block A description of the graph. .graph-data-pane.tab-pane(id="graph-#{graph_id}-data") .row-fluid - label.dataset.control-label(for='dataset') Data Set + label.dataset.control-label(for="#{id}_dataset") Data Set .controls - input.span3.dataset(type='text', id='dataset', name='dataset', placeholder='URL to dataset file', value=dataset) + input.span3.dataset(type='text', id="#{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-#{graph_id}-options") diff --git a/lib/util/cascade.co b/lib/util/cascade.co index 38847ff..fb1cdb7 100644 --- a/lib/util/cascade.co +++ b/lib/util/cascade.co @@ -188,7 +188,7 @@ class Cascade for o of arguments then @set o this - toObject: -> + toJSON: -> _.extend {}, ...@_lookups.slice().reverse() # XXX: should unique? but then won't map 1:1 to @values()... @@ -227,5 +227,11 @@ class Cascade "#{Cls.displayName or Cls.name}()" +ALIASES = + toJSON : 'toObject' + each : 'forEach' + +for src, dest in ALIASES + Cascade::[dest] = Cascade::[src] module.exports = exports = Cascade diff --git a/lib/vis/index.co b/lib/vis/index.co deleted file mode 100644 index 9975640..0000000 --- a/lib/vis/index.co +++ /dev/null @@ -1,3 +0,0 @@ -models = require 'kraken/vis/vis-model' -views = require 'kraken/vis/vis-view' -exports import models import views diff --git a/www/misc/test.co b/www/misc/test.co index 5c3033a..a50dccf 100644 --- a/www/misc/test.co +++ b/www/misc/test.co @@ -11,29 +11,19 @@ Backbone = require 'backbone' ChartOption, ChartOptionList, TagSet, ChartOptionView, ChartOptionScaffold, } = require 'kraken/chart' -{ VisView, VisModel, VisList, -} = require 'kraken/vis' +{ GraphEditView, Graph, GraphList, +} = require 'kraken/graph' root = this CHART_OPTIONS_SPEC = [] CHART_DEFAULT_OPTIONS = {} -ROOT_VIS_DATA = {} -ROOT_VIS_OPTIONS = {} # Create the Graph Scaffold main = -> - # opts = root.CHART_DEFAULT_OPTIONS = {} - # for opt of root.CHART_OPTIONS_SPEC - # opts[opt.name] = opt.default - dyglib = new DygraphsChartType CHART_OPTIONS_SPEC - # TODO: create a preset manager - # Remove chart options from data so we don't have to deepcopy - ROOT_VIS_OPTIONS := delete root.ROOT_VIS_DATA.options - # Bind to URL changes History.Adapter.bind window, 'statechange', -> console.log 'StateChange!\n\n', String(root.location), '\n\n' @@ -56,8 +46,8 @@ main = -> if match = /\/graph\/(?!view)([^\/?]+)/i.exec loc data.slug = match[1] - vis = root.vis = new VisModel data, {+parse} - # graph = root.graph = new VisView do + vis = root.vis = new Graph data, {+parse} + # graph = root.graph = new GraphEditView do # graph_spec : root.CHART_OPTIONS_SPEC # model : vis # $ '#content .inner' .append graph.el @@ -66,7 +56,6 @@ main = -> # Load data files Seq([ <[ CHART_OPTIONS_SPEC /schema/dygraph.json ]>, - <[ ROOT_VIS_DATA /presets/root.json ]> ]) .parEach_ (next, [key, url]) -> jQuery.ajax do diff --git a/www/modules.yaml b/www/modules.yaml index d1c2c45..6172dfc 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -70,14 +70,12 @@ dev: - template: - chart-option.jade - chart-scaffold.jade - - graph.jade - # - graph: - # - graph-model - # - graph-view - # - index - - vis: - - vis-model - - vis-view + - graph-edit.jade + - graph-display.jade + - graph: + - graph-model + - graph-edit-view + - graph-display-view - index # - suffix: .js