Fixes issue pushing toggled checkboxes to model; revises event flow; fixes legend.
authordsc <dsc@wikimedia.org>
Wed, 7 Mar 2012 18:41:51 +0000 (10:41 -0800)
committerdsc <dsc@wikimedia.org>
Wed, 7 Mar 2012 18:41:51 +0000 (10:41 -0800)
12 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/template/graph.jade
lib/underscore/object.co
lib/util/index.co
lib/util/op.co
lib/vis/vis-model.co
lib/vis/vis-view.co

index 33fb3bf..602ce71 100644 (file)
@@ -16,9 +16,9 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{
         _.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
@@ -26,11 +26,11 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{
     
     /**
      * 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
@@ -41,8 +41,14 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{
      * 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})"
 
@@ -57,8 +63,8 @@ BaseModel import do
      * @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
 
@@ -71,11 +77,14 @@ BaseModel import do
 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})"
 # }}}
index 372fb66..5df59fd 100644 (file)
@@ -54,7 +54,7 @@ GraphOption = exports.GraphOption = Field.extend do # {{{
         # 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
     
     
@@ -103,5 +103,12 @@ GraphOption = exports.GraphOption = Field.extend do # {{{
 GraphOptionList = exports.GraphOptionList = FieldList.extend do # {{{
     ctorName   : 'GraphOptionList'
     model      : GraphOption
+    
+    /**
+     * Override to omit defaults from URL.
+     */
+    toKVPairs: ->
+         _.collapseObject @values {-keepDefaults, +serialize}
+    
 # }}}
 
index 8e7cccc..0cb3982 100644 (file)
@@ -99,8 +99,8 @@ GraphOptionsScaffold = exports.GraphOptionsScaffold = Scaffold.extend do # {{{
         view.on 'change:collapse render', @render
         view
     
-    toKVPairs: ->
-        @collection.toKVPairs ...
+    toKV: ->
+        @collection.toKV ...
     
 # }}}
 
index fc5a88c..70da9c1 100644 (file)
@@ -12,26 +12,31 @@ Seq = require 'seq'
 } = 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
index 65814ff..9d242a6 100644 (file)
@@ -96,7 +96,7 @@ Field = exports.Field = BaseModel.extend do # {{{
         {id:@id} import do
             _.clone(@attributes) import { value:@getValue(), def:@get('default') }
     
-    toKVObject: ->
+    toKVPairs: ->
         { "#{@id}":@serializeValue() }
     
     toString: -> "(#{@id}: #{@serializeValue()})"
@@ -112,19 +112,22 @@ FieldList = exports.FieldList = BaseList.extend do # {{{
      * @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})"
 # }}}
index 2f717d6..a766afb 100644 (file)
@@ -10,17 +10,24 @@ FieldView = exports.FieldView = BaseView.extend do # {{{
     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 )"
@@ -111,7 +118,7 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{
 
 
 # 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
 
index 264ca81..df5272d 100644 (file)
@@ -27,10 +27,6 @@ section.graph(id=graph_id)
                                 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
@@ -42,6 +38,11 @@ section.graph(id=graph_id)
                             .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")
                 
index b47d650..1416a49 100644 (file)
@@ -37,7 +37,7 @@ _obj = do
     
     
     
-    toKVPairs: (o, item_delim='&', kv_delim='=') ->
+    toKV: (o, item_delim='&', kv_delim='=') ->
         _.reduce do
             o
             (acc, v, k) ->
@@ -46,7 +46,7 @@ _obj = do
             []
         .join item_delim
     
-    fromKVPairs: (qs, item_delim='&', kv_delim='=') ->
+    fromKV: (qs, item_delim='&', kv_delim='=') ->
         _.reduce do
             qs.split item_delim
             (acc, pair) ->
index 9c24959..745238e 100644 (file)
@@ -15,5 +15,17 @@ backbone  = require 'kraken/util/backbone'
 # {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, }
index ad42271..0c7c0b3 100644 (file)
@@ -119,5 +119,4 @@ module.exports = op =
     encode : -> it and $ "<div>#it</div>" .html().replace /"/g, '&quot;'
     decode : -> it and $ "<div>#it</div>" .text()
     
-    
 
index 178a155..c534e1d 100644 (file)
@@ -10,11 +10,13 @@ root = do -> this
  * 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'
@@ -23,7 +25,7 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{
     
     
     defaults: ->
-        {
+        ({
             slug    : ''
             name    : ''
             desc    : ''
@@ -31,8 +33,7 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{
             # 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) ->
@@ -59,19 +60,53 @@ VisModel = exports.VisModel = BaseModel.extend do # {{{
     
     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})"
 
 # }}}
 
index 6ff0119..6b13eba 100644 (file)
@@ -19,6 +19,7 @@ _ = require 'kraken/underscore'
  * - 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'
@@ -41,11 +42,11 @@ VisView = exports.VisView = BaseView.extend do # {{{
         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)
         
@@ -53,11 +54,11 @@ VisView = exports.VisView = BaseView.extend do # {{{
         @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'
@@ -67,8 +68,8 @@ VisView = exports.VisView = BaseView.extend do # {{{
         @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}
@@ -101,7 +102,12 @@ VisView = exports.VisView = BaseView.extend do # {{{
                 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
     
     
     /**
@@ -109,18 +115,23 @@ VisView = exports.VisView = BaseView.extend do # {{{
      * @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
@@ -145,29 +156,37 @@ VisView = exports.VisView = BaseView.extend do # {{{
     
     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
         
@@ -200,8 +219,11 @@ VisView = exports.VisView = BaseView.extend do # {{{
     toJSON: ->
         @model.toJSON()
     
-    toKVPairs: ->
-        @model.toKVPairs.apply @model, arguments
+    toKV: ->
+        @model.toKV.apply @model, arguments
+    
+    toURL: ->
+        @model.toURL()
     
     toString: -> "#{@ctorName}(#{@model})"
 # }}}