Updates todo and adds notes from various planning meetings.
authordsc <dsc@less.ly>
Wed, 29 Feb 2012 17:03:40 +0000 (09:03 -0800)
committerdsc <dsc@less.ly>
Wed, 29 Feb 2012 17:05:37 +0000 (09:05 -0800)
16 files changed:
lib/base.co
lib/graph/graph-model.co
lib/graph/graph-view.co
lib/main.co
lib/scaffold/scaffold-model.co
lib/scaffold/scaffold-view.co
lib/server/server.co
lib/template/graph.jade
lib/util/op.co
lib/vis/vis-model.co
lib/vis/vis-view.co
www/css/colors.styl
www/css/graph.styl
www/graph/test.jade
www/layout.jade
www/modules.yaml

index f3766f4..3f96421 100644 (file)
@@ -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
index 2728efa..372fb66 100644 (file)
@@ -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()
index 4087cf5..3d87cb0 100644 (file)
@@ -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
index 43962e4..a90bd2a 100644 (file)
@@ -1,3 +1,4 @@
+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
 
index 8142295..65814ff 100644 (file)
@@ -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
index 8f2c770..5e4cbfb 100644 (file)
@@ -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()
index db5df45..3281595 100755 (executable)
@@ -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.
  */
index 962f9e7..0216850 100644 (file)
@@ -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
-        
+
index a47465b..ad42271 100644 (file)
@@ -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)
index 5bd89c6..99cd073 100644 (file)
@@ -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'})"
 
 # }}}
index b707d58..71ff152 100644 (file)
@@ -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!'