_.bindAll this, ...@__bind__ if @__bind__.length
serialize: (v) ->
- if v!?
- v = ''
- else if _.isBoolean v
+ # if v!?
+ # v = ''
+ if _.isBoolean v
v = Number v
else if _.isObject v
v = JSON.stringify v
/**
* Like `.toJSON()` in that it should return a plain object with no functions,
- * but for the purpose of `.toKVPairs()`, allowing you to customize the values
+ * but for the purpose of `.toKV()`, allowing you to customize the values
* included and keys used.
* @returns {Object}
*/
- toKVObject: ->
+ toKVPairs: ->
kvo = _.collapseObject @toJSON()
for k, v in kvo
kvo[k] = @serialize v
* a query string or a POST body.
* @returns {String}
*/
- toKVPairs: (item_delim='&', kv_delim='=') ->
- _.toKVPairs @toKVObject(), item_delim, kv_delim
+ toKV: (item_delim='&', kv_delim='=') ->
+ _.toKV @toKVPairs(), item_delim, kv_delim
+
+ /**
+ * @returns {String} URL identifying this model.
+ */
+ toURL: (item_delim='&', kv_delim='=') ->
+ "?#{@toKV()}"
toString: -> "#{@ctorName}(id=#{@id})"
* @param {String|Object} o Serialized KV-pairs (or a plain object).
* @returns {BaseModel} An instance of this model.
*/
- fromKVPairs: (o, item_delim='&', kv_delim='=') ->
- o = _.fromKVPairs o, item_delim, kv_delim if typeof o is 'string'
+ fromKV: (o, item_delim='&', kv_delim='=') ->
+ o = _.fromKV o, item_delim, kv_delim if typeof o is 'string'
Cls = if typeof this is 'function' then this else this.constructor
new Cls _.uncollapseObject o
BaseList = exports.BaseList = Backbone.Collection.extend do # {{{
ctorName : 'BaseList'
- toKVObject: ->
+ toKVPairs: ->
_.collapseObject @toJSON()
- toKVPairs: (item_delim='&', kv_delim='=') ->
- _.toKVPairs @toKVObject(), item_delim, kv_delim
+ toKV: (item_delim='&', kv_delim='=') ->
+ _.toKV @toKVPairs(), item_delim, kv_delim
+
+ toURL: (item_delim='&', kv_delim='=') ->
+ "?#{@toKV ...}"
toString: -> "#{@ctorName}(length=#{@length})"
# }}}
# Ignore functions/callbacks and, ahem, hidden tags.
type = @get 'type', '' .toLowerCase()
tags = @get 'tags', []
- if _.str.include(type, 'function') or _.intersection tags, IGNORED_TAGS .length
+ if _.str.include(type, 'function') or _.intersection(tags, IGNORED_TAGS).length
@set 'ignore', true
GraphOptionList = exports.GraphOptionList = FieldList.extend do # {{{
ctorName : 'GraphOptionList'
model : GraphOption
+
+ /**
+ * Override to omit defaults from URL.
+ */
+ toKVPairs: ->
+ _.collapseObject @values {-keepDefaults, +serialize}
+
# }}}
view.on 'change:collapse render', @render
view
- toKVPairs: ->
- @collection.toKVPairs ...
+ toKV: ->
+ @collection.toKV ...
# }}}
} = require 'kraken/vis'
root = this
-CHART_OPTIONS_SPEC = []
-ROOT_VIS_DATA = {}
-ROOT_VIS_OPTIONS = {}
+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
+
# 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!', String(root.location)
+ console.log 'StateChange!\n\n', String(root.location), '\n\n'
# If we got querystring args, apply them to the graph
data = {}
if String(root.location).split '?' .1
- data = _.uncollapseObject _.fromKVPairs that.replace('#', '%23')
+ data = _.uncollapseObject _.fromKV that.replace('#', '%23')
# # yarr, have to do options separately or everything goes to shit
# options = delete data.options
{id:@id} import do
_.clone(@attributes) import { value:@getValue(), def:@get('default') }
- toKVObject: ->
+ toKVPairs: ->
{ "#{@id}":@serializeValue() }
toString: -> "(#{@id}: #{@serializeValue()})"
* @returns {Object}
*/
values: (opts={}) ->
- opts = {-keepDefaults, -serialize} import opts
+ opts = {+keepDefaults, -serialize} import opts
_.synthesize do
if opts.keepDefaults then @models else @models.filter -> not it.isDefault()
-> [ it.get('name'), if opts.serialize then it.serializeValue() else it.getValue() ]
toJSON: ->
- @values()
+ @values {+keepDefaults, -serialize}
- toKVObject: ->
- _.collapseObject @values false, true
+ toKVPairs: ->
+ _.collapseObject @values {+keepDefaults, +serialize}
- toKVPairs: (item_delim='&', kv_delim='=') ->
- _.toKVPairs @toKVObject(), item_delim, kv_delim
+ toKV: (item_delim='&', kv_delim='=') ->
+ _.toKV @toKVPairs(), item_delim, kv_delim
+
+ toURL: (item_delim='&', kv_delim='=') ->
+ "?#{@toKV ...}"
toString: -> "#{@ctorName}(length=#{@length})"
# }}}
tagName : 'div'
className : 'field'
+ type : 'string'
+
events :
'blur .value' : 'update'
'submit .value' : 'update'
- # initialize: ->
- # # console.log "#this.initialize!"
- # BaseView::initialize ...
+ initialize: ->
+ # console.log "#this.initialize!"
+ BaseView::initialize ...
+ @type = @model.get('type', 'string').toLowerCase()
update: ->
- val = @model.getParser() @$el.find('.value').val()
+ if @type is 'boolean'
+ val = !! @$el.find('.value').attr('checked')
+ else
+ val = @model.getParser() @$el.find('.value').val()
+
current = @model.getValue()
return if _.isEqual val, current
console.log "#this.update( #current -> #val )"
# Proxy collection methods
-<[ get at pluck invoke values toJSON toKVObject toKVPairs ]>
+<[ get at pluck invoke values toJSON toKVPairs toKV toURL ]>
.forEach (methodname) ->
Scaffold::[methodname] = -> @collection[methodname].apply @collection, arguments
input.span3.slug(type='text', 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.
.half.control-group
- label.dataset.control-label(for='dataset') Data Set
- .controls
- input.span3.dataset(type='text', 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.
.row-fluid
.half.control-group
label.width.control-label(for='width') Width
.controls: input.span1.height(type='text', id='height', name='height', value=height)
.graph-data-pane.tab-pane(id="graph-#{graph_id}-data")
+ .row-fluid
+ label.dataset.control-label(for='dataset') Data Set
+ .controls
+ input.span3.dataset(type='text', 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")
- toKVPairs: (o, item_delim='&', kv_delim='=') ->
+ toKV: (o, item_delim='&', kv_delim='=') ->
_.reduce do
o
(acc, v, k) ->
[]
.join item_delim
- fromKVPairs: (qs, item_delim='&', kv_delim='=') ->
+ fromKV: (qs, item_delim='&', kv_delim='=') ->
_.reduce do
qs.split item_delim
(acc, pair) ->
# {crc32} = require 'kraken/util/crc'
+## Debug
+_.dump = (o, label='dump') ->
+ if not _.isArray(o) and _.isObject(o)
+ console.group label
+ for k, v in o
+ console.log "#k:", v
+ console.groupEnd()
+ else
+ console.log label, o
+ o
+
+
exports import { root, _, op, backbone }
# exports import { root, _, op, HashSet, BitString, crc32, }
encode : -> it and $ "<div>#it</div>" .html().replace /"/g, '"'
decode : -> it and $ "<div>#it</div>" .text()
-
* other settings for both its content and presentation.
*/
VisModel = exports.VisModel = BaseModel.extend do # {{{
+ IGNORE_OPTIONS : <[ width height timingName ]>
ctorName : 'VisModel'
urlRoot : '/graph'
idAttribute : 'slug'
+
initialize : ->
BaseModel::initialize ...
name = @get 'name'
defaults: ->
- {
+ ({
slug : ''
name : ''
desc : ''
# presets : []
width : 'auto'
height : 320
- options : {} import root.ROOT_VIS_OPTIONS
- } import root.ROOT_VIS_DATA
+ } import root.ROOT_VIS_DATA) import { options:_.clone root.ROOT_VIS_OPTIONS }
parse: (data) ->
setOption: (key, value, opts={}) ->
options = @get 'options', {}
- options[key] = value
- @set 'options', options, opts
- @trigger "change:options:#key", this, value, key, opts unless opts.silent
+ unless _.contains @IGNORE_OPTIONS, key
+ options[key] = value
+ @set 'options', options, opts
+ @trigger "change:options:#key", this, value, key, opts unless opts.silent
+ this
unsetOption: (key, opts={}) ->
options = @get 'options', {}
delete options[key]
@set 'options', options, opts
@trigger "change:options:#key", this, value, key, opts unless opts.silent
-
-
-
- toString: -> "#{@ctorName}(id=#{@id}, name=#{@get 'name'}, dataset=#{@get 'dataset'})"
+ this
+
+
+ ### URL Serialization
+
+ toJSON: (options={}) ->
+ options = {+keepDefaults} import options
+
+ json = _.clone(@attributes) import { options:_.clone(@attributes.options) }
+ return json if options.keepDefaults
+
+ defs = root.CHART_DEFAULT_OPTIONS
+ opts = json.options
+ for k, v in opts
+ delete opts[k] if v is defs[k]
+ json
+
+
+ toKVPairs: ->
+ kvo = @toJSON()
+ # console.group 'toKVPairs'
+ # console.log '[IN]', JSON.stringify kvo
+ opts = kvo.options = _.clone kvo.options
+ for k, rootVal in root.ROOT_VIS_OPTIONS
+ v = opts[k]
+ # console.log " [#k] rootVal:", rootVal, "===", v, "?", _.isEqual(rootVal, v) unless _.isEqual(rootVal, v)
+ if _.isEqual rootVal, v
+ delete opts[k]
+ else
+ opts[k] = @serialize v
+ # console.log '[OUT]', JSON.stringify kvo
+ # console.groupEnd()
+ _.collapseObject kvo
+
+
+ toString: -> "#{@ctorName}(id=#{@id})"
# }}}
* - Graph metadata, such as name, description, slug
*/
VisView = exports.VisView = BaseView.extend do # {{{
+ FILTER_CHART_OPTIONS : <[ file labels visibility colors dateWindow ticker timingName axisLabelFormatter valueFormatter xAxisLabelFormatter yAxisLabelFormatter xValueFormatter yValueFormatter xValueParser ]>
__bind__ : <[ resizeViewport render renderAll onReady formatter axisFormatter ]>
ctorName : 'VisView'
tagName : 'section'
BaseView::initialize ...
# console.log "#this.initialize!"
- for name of <[ resizeViewport render renderAll ]>
+ for name of <[ render renderAll ]>
@[name] = _.debounce @[name], DEBOUNCE_RENDER
# Resize graph on window resize
- $ root .on 'resize', @resizeViewport
+ $ root .on 'resize', _.debounce(@resizeViewport, DEBOUNCE_RENDER)
@id = _.domize 'graph', (@model.id or @model.cid)
@model.on 'change', @render, this
@model.on 'change:dataset', ~>
changes = @model.changedAttributes()
- console.log 'VisModel.changed(dataset) ->', JSON.stringify changes
+ console.log 'VisModel.changed( dataset ) ->', JSON.stringify changes
@chart.updateOptions file:that if changes?.dataset
@model.on 'change:options', ~>
changes = @model.changedAttributes()
- console.log 'VisModel.changed(options) ->', JSON.stringify changes
+ console.log 'VisModel.changed( options ) ->', JSON.stringify changes
@chartOptions that, {+silent} if changes?.options
@viewport = @$el.find '.viewport'
@scaffold.collection.reset that if o.graph_spec
@scaffold.on 'change', (scaffold, value, key, field) ~>
- # console.log "scaffold.change!", value, key, field
- @model.setOption key, value, {+silent} unless field.isDefault()
+ console.log "scaffold.change!", key, value, field
+ @model.setOption key, value, {+silent} #unless field.isDefault()
options = @model.get 'options', {}
@chartOptions options, {+silent}
options.get(k)?.setValue v, opts
this
else
- options.values() # TODO: pull this from model (must clone), sort out events
+ opts = @model.toJSON({ -keepDefaults })?.options or {}
+ for k of @FILTER_CHART_OPTIONS
+ # console.log "filter #k?", not opts[k]
+ if k in opts and not opts[k]
+ delete opts[k]
+ opts
/**
* @return { width, height }
*/
resizeViewport: ->
- return this unless @ready
+ modelW = width = @model.get 'width'
+ modelH = height = @model.get 'height'
+ return { width, height } unless @ready
# Remove old style, as it confuses dygraph after options update
@viewport.attr 'style', ''
label = @$el.find '.graph-label'
- if (width = @model.get 'width') is 'auto'
+ if width is 'auto'
vpWidth = @viewport.innerWidth()
labelW = label.outerWidth()
width = vpWidth - labelW - 10 - (vpWidth - label.position().left - labelW)
- if (height = @model.get 'height') is 'auto'
+ width ?= modelW
+ if height is 'auto'
height = @viewport.innerHeight()
+ height ?= modelH
+
size = { width, height }
@viewport.css size
# console.log 'resizeViewport!', JSON.stringify(size), @viewport
render: ->
return this unless @ready
- console.log "#this.render!"
size = @resizeViewport()
- options = @chartOptions() import size
+ options = @chartOptions() #import size
options import do
labelsDiv : @$el.find '.graph-label' .0
# axisLabelFormatter : @axisFormatter
# valueFormatter : @formatter
-
- # @chart?.destroy()
- unless @chart
- @chart = new Dygraph do
- @viewport.0
- @model.get 'dataset'
- options
- else
- @chart.updateOptions options
- @chart.resize size
+ dataset = @model.get 'dataset'
+
+ console.log "#this.render!", dataset
+ _.dump options, 'options'
+
+ @chart?.destroy()
+ @chart = new Dygraph do
+ @viewport.0
+ dataset
+ options
+
+ # unless @chart
+ # @chart = new Dygraph do
+ # @viewport.0
+ # dataset
+ # options
+ # else
+ # @chart.updateOptions options
+ # @chart.resize size
# path = String(root.location?.path or '/')
data = @toJSON()
title = @model.get('name', root.document?.title or '')
- url = "?"+@toKVPairs()
+ url = @toURL()
# console.log 'History.pushState', JSON.stringify(data), title, url
History.pushState data, title, url
toJSON: ->
@model.toJSON()
- toKVPairs: ->
- @model.toKVPairs.apply @model, arguments
+ toKV: ->
+ @model.toKV.apply @model, arguments
+
+ toURL: ->
+ @model.toURL()
toString: -> "#{@ctorName}(#{@model})"
# }}}