From afc25b14c4365fb82d48c3072e39d127ffe9e4b3 Mon Sep 17 00:00:00 2001 From: dsc Date: Wed, 29 Feb 2012 09:03:40 -0800 Subject: [PATCH] Updates todo and adds notes from various planning meetings. --- lib/base.co | 17 +++++++++- lib/graph/graph-model.co | 8 +++++ lib/graph/graph-view.co | 4 ++- lib/main.co | 68 +++++++++++++++++++++++++++------------ lib/scaffold/scaffold-model.co | 24 +++++--------- lib/scaffold/scaffold-view.co | 5 ++- lib/server/server.co | 19 +++++++++-- lib/template/graph.jade | 20 +++++------ lib/util/op.co | 2 +- lib/vis/vis-model.co | 30 ++++++++++++++--- lib/vis/vis-view.co | 64 ++++++++++++++++++++++++------------- www/css/colors.styl | 4 +- www/css/graph.styl | 31 +++++++++++++----- www/graph/test.jade | 1 - www/layout.jade | 17 +++++----- www/modules.yaml | 1 + 16 files changed, 213 insertions(+), 102 deletions(-) diff --git a/lib/base.co b/lib/base.co index f3766f4..3f96421 100644 --- a/lib/base.co +++ b/lib/base.co @@ -12,8 +12,17 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{ 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, @@ -22,7 +31,10 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{ * @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 @@ -85,6 +97,7 @@ BaseView = exports.BaseView = Backbone.View.extend do # {{{ _.bindAll this, ...@__bind__ if @__bind__.length @__super__ = @constructor.__super__ + @build() @model.view = this @$el.data { @model, view:this } @model.on 'change', @render, this diff --git a/lib/graph/graph-model.co b/lib/graph/graph-model.co index 2728efa..372fb66 100644 --- a/lib/graph/graph-model.co +++ b/lib/graph/graph-model.co @@ -4,6 +4,8 @@ _ = require 'kraken/underscore' { Field, FieldList, FieldView, Scaffold, } = require 'kraken/scaffold' +IGNORED_TAGS = exports.IGNORED_TAGS = <[ callback deprecated debugging ]> + class exports.TagSet extends Array @@ -48,6 +50,12 @@ GraphOption = exports.GraphOption = Field.extend do # {{{ # 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() diff --git a/lib/graph/graph-view.co b/lib/graph/graph-view.co index 4087cf5..3d87cb0 100644 --- a/lib/graph/graph-view.co +++ b/lib/graph/graph-view.co @@ -58,8 +58,10 @@ GraphOptionsScaffold = exports.GraphOptionsScaffold = Scaffold.extend do # {{{ 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 @@ -74,7 +76,7 @@ GraphOptionsScaffold = exports.GraphOptionsScaffold = Scaffold.extend do # {{{ # 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 diff --git a/lib/main.co b/lib/main.co index 43962e4..a90bd2a 100644 --- a/lib/main.co +++ b/lib/main.co @@ -1,3 +1,4 @@ +Seq = require 'seq' { _, op, } = require 'kraken/util' { BaseView, BaseModel, BaseList, @@ -10,37 +11,62 @@ { 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 diff --git a/lib/scaffold/scaffold-model.co b/lib/scaffold/scaffold-model.co index 8142295..65814ff 100644 --- a/lib/scaffold/scaffold-model.co +++ b/lib/scaffold/scaffold-model.co @@ -14,6 +14,7 @@ Field = exports.Field = BaseModel.extend do # {{{ 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)" @@ -38,7 +39,6 @@ Field = exports.Field = BaseModel.extend do # {{{ 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 @@ -64,7 +64,7 @@ Field = exports.Field = BaseModel.extend do # {{{ parseFunction: (fn) -> if fn and _.startswith String(fn), 'function' - try eval "(#fn)" catch err + try eval "(#fn)" catch err then null else null @@ -90,21 +90,14 @@ Field = exports.Field = BaseModel.extend do # {{{ /* * * 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()})" # }}} @@ -118,16 +111,17 @@ FieldList = exports.FieldList = BaseList.extend do # {{{ * 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 diff --git a/lib/scaffold/scaffold-view.co b/lib/scaffold/scaffold-view.co index 8f2c770..5e4cbfb 100644 --- a/lib/scaffold/scaffold-view.co +++ b/lib/scaffold/scaffold-view.co @@ -28,7 +28,7 @@ FieldView = exports.FieldView = BaseView.extend do # {{{ @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' @@ -74,6 +74,7 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{ 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 @@ -85,7 +86,7 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{ 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() diff --git a/lib/server/server.co b/lib/server/server.co index db5df45..3281595 100755 --- a/lib/server/server.co +++ b/lib/server/server.co @@ -2,6 +2,7 @@ fs = require 'fs' path = require 'path' +{existsSync:exists} = path {exec, spawn} = require 'child_process' _ = require 'underscore' @@ -65,6 +66,9 @@ app.configure -> # 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 ]> @@ -95,9 +99,6 @@ app.configure -> 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 @@ -113,6 +114,13 @@ app.configure -> 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 @@ -167,6 +175,7 @@ app.get '/data/all', (req, res, next) -> 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" @@ -174,6 +183,10 @@ app.get '/:type/:action', (req, res, next) -> else next() + + + + /** * Handle webhook notification to pull from origin. */ diff --git a/lib/template/graph.jade b/lib/template/graph.jade index 962f9e7..0216850 100644 --- a/lib/template/graph.jade +++ b/lib/template/graph.jade @@ -1,37 +1,35 @@ - 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 - + diff --git a/lib/util/op.co b/lib/util/op.co index a47465b..ad42271 100644 --- a/lib/util/op.co +++ b/lib/util/op.co @@ -57,7 +57,7 @@ module.exports = op = 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) diff --git a/lib/vis/vis-model.co b/lib/vis/vis-model.co index 5bd89c6..99cd073 100644 --- a/lib/vis/vis-model.co +++ b/lib/vis/vis-model.co @@ -2,6 +2,7 @@ _ = require 'kraken/underscore' { BaseModel, BaseView, } = require 'kraken/base' +root = do -> this /** @@ -10,26 +11,44 @@ _ = require 'kraken/underscore' */ 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', {} @@ -51,6 +70,7 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{ @trigger "change:options:#key", this, value, key, opts unless opts.silent + toString: -> "#{@ctorName}(id=#{@id}, name=#{@get 'name'}, dataset=#{@get 'dataset'})" # }}} diff --git a/lib/vis/vis-view.co b/lib/vis/vis-view.co index b707d58..71ff152 100644 --- a/lib/vis/vis-view.co +++ b/lib/vis/vis-view.co @@ -26,8 +26,10 @@ VisView = exports.VisView = BaseView.extend do # {{{ 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 @@ -51,11 +53,10 @@ VisView = exports.VisView = BaseView.extend do # {{{ # 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) ~> @@ -65,13 +66,21 @@ VisView = exports.VisView = BaseView.extend do # {{{ options = @model.get 'options', {} @chartOptions options, {+silent} + @resizeViewport() _.delay @onReady, DEBOUNCE_RENDER onReady: -> + console.log 'VisView.ready!' @ready = @scaffold.ready = true + @change() @renderAll() + change: -> + @model.change() + @scaffold.invoke 'change' + this + chartOptions: (values, opts) -> # Handle @chartOptions(k, v, opts) @@ -85,7 +94,7 @@ VisView = exports.VisView = BaseView.extend do # {{{ options.get(k)?.setValue v, opts this else - options.values() + options.values() # TODO: pull this from model (must clone), sort out events /** @@ -93,7 +102,7 @@ VisView = exports.VisView = BaseView.extend do # {{{ * @return { width, height } */ resizeViewport: -> - return this unless @ready and @chart + return this unless @ready # Remove old style, as it confuses dygraph after options update @viewport.attr 'style', '' @@ -104,23 +113,19 @@ VisView = exports.VisView = BaseView.extend do # {{{ height = @viewport.height() size = { width, height } @viewport.css size - console.log 'resizeViewport!', JSON.stringify(size), @viewport + # console.log 'resizeViewport!', JSON.stringify(size), @viewport # @chart.resize size if forceRedraw size + render: -> return this unless @ready - - # console.log "#this" - # console.log do - # " .viewport.{ width=%s, height=%s, style=%s }" - # @viewport.css('width') - # @viewport.css('height') - # @viewport.attr 'style' - # console.log ' .options:', JSON.stringify options + console.log 'VisView.render!' size = @resizeViewport() - options = @chartOptions() + options = @chartOptions() import size + options.labelsDiv = @$el.find '.graph-label' .0 + # @chart?.destroy() unless @chart @chart = new Dygraph do @@ -131,15 +136,18 @@ VisView = exports.VisView = BaseView.extend do # {{{ @chart.updateOptions options @chart.resize size - path = root.location?.path or '/' - url = "#path?#{@toKVPairs()}" - # console.log 'History.pushState', url - History.pushState url, @model.get('name', root.document?.title or ''), url + # path = String(root.location?.path or '/') + data = @toJSON() + title = @model.get('name', root.document?.title or '') + url = "?"+@toKVPairs() + # console.log 'History.pushState', JSON.stringify(data), title, url + History.pushState data, title, url this renderAll: -> return this unless @ready + console.log 'VisView.renderAll!' _.invoke @scaffold.subviews, 'render' @scaffold.render() @render() @@ -148,11 +156,23 @@ VisView = exports.VisView = BaseView.extend do # {{{ onKeypress: (evt) -> $(evt.target).submit() if evt.keyCode is 13 - onSubmit: -> - # console.log "#this.onSubmit!" + onDetailsSubmit: -> + console.log "#this.onDetailsSubmit!" + data = _.synthesize do + @$el.find('form.details').serializeArray() + -> [it.name, it.value] + console.log @$el, JSON.stringify data + @model.set data + false + + onOptionsSubmit: -> + console.log "#this.onOptionsSubmit!" @render() false + toJSON: -> + @model.toJSON() + toKVPairs: -> @model.toKVPairs.apply @model, arguments diff --git a/www/css/colors.styl b/www/css/colors.styl index 4c836b6..a486891 100644 --- a/www/css/colors.styl +++ b/www/css/colors.styl @@ -41,8 +41,8 @@ $hilite_favs = ($hilite_dkblue $hilite_ltblue $hilite_cyan $hilite_lime $hilite_ /* background colors */ -$main_bgcolor = white -$page_bgcolor = $light +$main_bgcolor = $light +$page_bgcolor = white $foot_bgcolor = #3b3b3b /* text & link colors */ diff --git a/www/css/graph.styl b/www/css/graph.styl index 4f9b09f..78ce592 100644 --- a/www/css/graph.styl +++ b/www/css/graph.styl @@ -8,7 +8,19 @@ section.graph position relative .graph-label - position relative + position absolute + z-index 100 + top 1em + right 1em + max-width 300px + + padding 1em + border-radius 5px + background-color rgba(255,255,255, 0.75) + font 12px/1.3 "helvetica neue", helvetica, arial, sans-serif + + .viewport:hover + .graph-label + border 1px solid $light .viewport position relative @@ -17,13 +29,19 @@ section.graph margin-bottom 1.5em overflow hidden + form.details position relative .name-row + font-size 120% line-height 2em - // input.name - // width 50% + input.name + font-size 120% + line-height 1.2 + height 1.2em + border-color $light + // width 50% .row-fluid .half.control-group width 50% @@ -38,11 +56,8 @@ section.graph font-size 11px line-height 1.3 - fieldset.options - border-radius 5px - legend - width auto - display inline-block + .options fieldset + border 0px .field.option float left diff --git a/www/graph/test.jade b/www/graph/test.jade index 906ecf6..5033daa 100644 --- a/www/graph/test.jade +++ b/www/graph/test.jade @@ -8,4 +8,3 @@ append styles mixin css('graph.css') mixin css('isotope.css') - diff --git a/www/layout.jade b/www/layout.jade index a76e3ee..ba83c88 100644 --- a/www/layout.jade +++ b/www/layout.jade @@ -21,16 +21,17 @@ html body block body - header.navbar.navbar-fixed-top: .navbar-inner: .container - block header - h1: a(href="/") Kraken - block nav - nav#menu: ul - block menu-items - li.home Home + //- + header + block header + h1: a(href="/") Kraken + block nav + nav#menu: ul + block menu-items + li.home Home section#content - .spacer + //- .spacer .inner block content diff --git a/www/modules.yaml b/www/modules.yaml index 6afc2de..e089e90 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -39,6 +39,7 @@ all: - template: - graph.jade - graph-option.jade + - graph-scaffold.jade - scaffold: - scaffold-model - scaffold-view -- 1.7.0.4