ctorName : 'BaseModel'
initialize: ->
- _.bindAll this, ...@__bind__ if @__bind__.length
@__super__ = @constructor.__super__
+ _.bindAll this, ...@__bind__ if @__bind__.length
+
+ serialize: (v) ->
+ if v!?
+ v = ''
+ else if _.isBoolean v
+ v = Number v
+ else if _.isObject v
+ v = JSON.stringify v
+ String v
/**
* Like `.toJSON()` in that it should return a plain object with no functions,
* @returns {Object}
*/
toKVObject: ->
- _.collapseObject @toJSON()
+ kvo = _.collapseObject @toJSON()
+ for k, v in kvo
+ kvo[k] = @serialize v
+ kvo
/**
* Serialize the model into a `www-form-encoded` string suitable for use as
_.bindAll this, ...@__bind__ if @__bind__.length
@__super__ = @constructor.__super__
+ @build()
@model.view = this
@$el.data { @model, view:this }
@model.on 'change', @render, this
{ Field, FieldList, FieldView, Scaffold,
} = require 'kraken/scaffold'
+IGNORED_TAGS = exports.IGNORED_TAGS = <[ callback deprecated debugging ]>
+
class exports.TagSet extends Array
# Notify Tag indexer of category when created, to ensure all category-tags
# get indices with colors :P
KNOWN_TAGS.update @getCategory()
+
+ # 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
+ @set 'ignore', true
# Wrapper to ensure @set('tags') is called, as tags.push()
ctorName : 'GraphOptionsScaffold'
tagName : 'form'
className : 'options scaffold'
+ template : require 'kraken/template/graph-scaffold'
collectionType : GraphOptionList
subviewType : GraphOptionView
+ $fields : '.fields'
# GraphView will set this
ready : false
# console.log "#this.render() -> .isotope()"
@__super__.render ...
return this unless @ready
- @$el.isotope do
+ @$el.find '.options.control-group' .isotope do
itemSelector : '.field.option'
layoutMode : 'masonry'
masonry : columnWidth : 10
+Seq = require 'seq'
{ _, op,
} = require 'kraken/util'
{ BaseView, BaseModel, BaseList,
{ VisView, VisModel,
} = require 'kraken/vis'
-root = do -> this
+root = this
+CHART_OPTIONS_SPEC = []
+ROOT_VIS_DATA = {}
+ROOT_VIS_OPTIONS = {}
+
# Create the Graph Scaffold
main = ->
+ # 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!', String(root.location)
- graph = root.graph = new VisView do
- graph_spec : CHART_OPTIONS_SPEC
# If we got querystring args, apply them to the graph
- if root.location?.search.slice 1
- g = _.uncollapseObject _.fromKVPairs that
- # yarr, have to do options separately or everything goes to shit
- options = delete g.options
- graph.model.set g, {+silent}
- graph.chartOptions options, {+silent}
+ data = {}
+ if String(root.location).split '?' .1
+ data = _.uncollapseObject _.fromKVPairs that.replace('#', '%23')
- # Flush all changes once the DOM settles
- _.defer do
- -> graph.scaffold.invoke 'change'
- 50
+ # # yarr, have to do options separately or everything goes to shit
+ # options = delete data.options
+ # graph.model.set data, {+silent}
+ # graph.chartOptions options, {+silent}
+ # # Flush all changes once the DOM settles
+ # _.delay do
+ # ->
+ # graph.change()
+ # # graph.scaffold.invoke 'change'
+ # 50
+
+
+ vis = root.vis = new VisModel data
+ graph = root.graph = new VisView do
+ graph_spec : root.CHART_OPTIONS_SPEC
+ model : vis
$ '#content .inner' .append graph.el
-jQuery.ajax do
- url : '/graph/dygraph-options.json',
- dataType : 'json'
- success : (data) ->
- root.CHART_OPTIONS_SPEC = data
- jQuery main
- error : (err) -> console.error err
+
+# Load data files
+Seq([ <[ CHART_OPTIONS_SPEC /graph/dygraph-options.json ]>,
+ <[ ROOT_VIS_DATA /graph/root ]>
+])
+.parEach_ (next, [key, url]) ->
+ jQuery.ajax do
+ url : url,
+ dataType : 'json'
+ success : (data) ->
+ root[key] = data
+ next.ok()
+ error : (err) -> console.error err
+.seq ->
+ console.log 'All data loaded!'
+ jQuery main
initialize: ->
+ _.bindAll this, ...(_.functions this .filter -> _.startsWith(it, 'parse'))
@set 'value', @get('default'), {+silent} if not @has 'value'
# console.log "#this.initialize!"
# @on 'all', (evt) ~> console.log "#this.trigger(#evt)"
type = _ (type or @get 'type').toLowerCase()
for t of <[ Integer Float Boolean Object Array Function ]>
if type.startsWith t.toLowerCase()
- # console.log "parse#t ->", @["parse#t"] unless @["parse#t"]
return @["parse#t"]
@parseString
parseFunction: (fn) ->
if fn and _.startswith String(fn), 'function'
- try eval "(#fn)" catch err
+ try eval "(#fn)" catch err then null
else
null
/* * * Serializers * * */
serializeValue: ->
- v = @getValue()
- if v!?
- v = ''
- else if _.isBoolean v
- v = Number v
- else if _.isArray(v) or _.isObject(v)
- v = JSON.stringify v
- String v
+ @serialize @getValue()
toJSON: ->
{id:@id} import do
_.clone(@attributes) import { value:@getValue(), def:@get('default') }
toKVObject: ->
- { "#{@id}": @serializeValue() }
+ { "#{@id}":@serializeValue() }
toString: -> "(#{@id}: #{@serializeValue()})"
# }}}
* Collects a map of fields to their values, excluding those set to `null` or their default.
* @returns {Object}
*/
- values: (keepDefaults=false) ->
+ values: (opts={}) ->
+ opts = {-keepDefaults, -serialize} import opts
_.synthesize do
- if keepDefaults then @models else @models.filter -> not it.isDefault()
- -> [ it.get('name'), it.getValue() ]
+ 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()
toKVObject: ->
- _.collapseObject @toJSON()
+ _.collapseObject @values false, true
toKVPairs: (item_delim='&', kv_delim='=') ->
_.toKVPairs @toKVObject(), item_delim, kv_delim
@trigger 'update', this
render: ->
- return @remove() if @model.get 'hidden', false
+ return @remove() if @model.get 'ignore', false
return BaseView::render ... if @template
name = @model.get 'name'
addOne: (field) ->
# console.log "[S] #this.addOne!", @__super__
+ fields = if @$fields then @$el.find that else @$el
_.remove @subviews, field.view if field.view
# avoid duplicating event propagation
SubviewType = @subviewType
view = new SubviewType model:field
@subviews.push view
- @$el.append view.render().el unless field.get 'hidden'
+ fields.append view.render().el unless field.get 'ignore'
view.on 'update', @change.bind(this, field)
@render()
fs = require 'fs'
path = require 'path'
+{existsSync:exists} = path
{exec, spawn} = require 'child_process'
_ = require 'underscore'
# Allow "spoofing" HTTP methods that IE doesn't support
app.use express.methodOverride()
+ # Route to the web services
+ app.use app.router
+
# Transparently recompile modules that have changed
app.use compiler do
enabled : <[ coco jade-browser stylus yaml ]>
app.use express.static VAR
app.use express.static STATIC
- # Route to the web services
- app.use app.router
-
# Serve directory listings
app.use express.directory WWW
app.use express.directory VAR
app.get '/', (req, res) ->
res.render 'dashboard'
+app.get '/graph/:id', (req, res, next) ->
+ {id} = req.params
+ console.log req.url
+ if exists("#WWW/graph/#id.yaml") or exists("#WWW/graph/#id.json")
+ req.url += '.json'
+ next()
+
YAML_EXT_PAT = /\.ya?ml$/i
console.error that if err.stack
res.send { error:String(err), partial_data:data }
+
app.get '/:type/:action', (req, res, next) ->
{type, action} = req.params
if path.existsSync "#WWW/#type/#action.jade"
else
next()
+
+
+
+
/**
* Handle webhook notification to pull from origin.
*/
- var id = model.id || model.cid, graph_id = _.domize('graph', id)
section.graph(id=graph_id)
- .graph-label
- .viewport
-
form.details.form-horizontal
.name-row.row-fluid.control-group
//- label.name.control-label(for="#{id}_name"): h3 Graph Name
- .controls: input.span6.name(type='text', id="#{id}_name", placeholder='Graph Name', value=name)
+ input.span6.name(type='text', id="#{id}_name", name="name", placeholder='Graph Name', value=name)
+
+ .viewport
+ .graph-label
.row-fluid
.half.control-group
label.slug.control-label(for='slug') Slug
.controls
- input.span3.slug(type='text', id='slug', placeholder='graph_slug', value=slug)
+ 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', placeholder='URL to dataset file', value=dataset)
+ 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.width(type='text', id='width', value=width)
+ input.span1.width(type='text', id='width', name='width', value=width)
p.help-block Choosing 'auto' will size the graph to the viewport bounds.
.half.control-group
label.height.control-label(for='height') Height
- .controls: input.span1.height(type='text', id='height', value=height)
+ .controls: input.span1.height(type='text', id='height', name='height', value=height)
- fieldset.options
- legend Graph Options
-
+
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' then JSON.parse(v) else v
# comparison
cmp : (x,y) -> if x < y then -1 else (if x > y then 1 else 0)
{ BaseModel, BaseView,
} = require 'kraken/base'
+root = do -> this
/**
*/
VisModel = exports.VisModel = BaseModel.extend do # {{{
ctorName : 'VisModel'
- urlRoot : '/graphs'
+ urlRoot : '/graph'
idAttribute : 'slug'
initialize : ->
+ BaseModel::initialize ...
name = @get 'name'
- if name and not (@id or @get('slug'))
+ if name and not @get 'slug', @id
@set 'slug', _.underscored name
+
defaults: ->
{
slug : ''
name : ''
- dataset : '/data/pageviews_by.timestamp.language.csv'
desc : ''
+ dataset : '/data/pageviews_by.timestamp.language.csv'
+ # presets : []
width : 'auto'
height : 320
- options : {}
- }
+ options : {} import root.ROOT_VIS_OPTIONS
+ } import root.ROOT_VIS_DATA
+
+ parse: (data) ->
+ data = JSON.parse data if typeof data is 'string'
+ for k, v in data
+ data[k] = Number v if _.contains(<[ width height ]>, k) and v is not 'auto'
+ data
+
+ set: (values, opts) ->
+ if arguments.length > 1 and typeof values is 'string'
+ [k, v, opts] = arguments
+ values = { "#k": v }
+ BaseModel::set.call this, @parse(values), opts
+
+
+ ### Chart Option Accessors ###
hasOption: (key) ->
options = @get 'options', {}
@trigger "change:options:#key", this, value, key, opts unless opts.silent
+
toString: -> "#{@ctorName}(id=#{@id}, name=#{@get 'name'}, dataset=#{@get 'dataset'})"
# }}}
template : require 'kraken/template/graph'
events:
- 'keypress form.options .value' : 'onKeypress'
- 'submit form.options' : 'onSubmit'
+ 'keypress form.details input[type="text"]' : 'onKeypress'
+ 'keypress form.options .value' : 'onKeypress'
+ 'submit form.details' : 'onDetailsSubmit'
+ 'submit form.options' : 'onOptionsSubmit'
ready: false
# console.log 'Model.changed(options) ->', changes
@chartOptions changes, {+silent}
- @build()
@viewport = @$el.find '.viewport'
@scaffold = new GraphOptionsScaffold
- @$el.find 'fieldset' .append @scaffold.el
+ @$el.append @scaffold.el
@scaffold.collection.reset that if o.graph_spec
@scaffold.on 'change', (scaffold, value, key, field) ~>
options = @model.get 'options', {}
@chartOptions options, {+silent}
+ @resizeViewport()
_.delay @onReady, DEBOUNCE_RENDER
onReady: ->
+ console.log 'VisView.ready!'