Moves timeseries classes.
authordsc <dsc@wikimedia.org>
Tue, 24 Apr 2012 19:56:28 +0000 (12:56 -0700)
committerdsc <dsc@wikimedia.org>
Tue, 24 Apr 2012 19:56:28 +0000 (12:56 -0700)
27 files changed:
lib/base/base-view.co
lib/base/index.co
lib/chart/chart-option-model.co
lib/chart/chart-option-view.co
lib/dashboard/dashboard-view.co
lib/dataset/data-view.co
lib/dataset/dataset-view.co
lib/dataset/datasource-model.co
lib/dataset/datasource-ui-view.co
lib/dataset/metric-edit-view.co
lib/dataset/metric-model.co
lib/graph/graph-display-view.co
lib/graph/graph-edit-view.co
lib/main-edit.co
lib/scaffold/scaffold-view.co
lib/template/chart-scaffold.jade
lib/template/data.jade
lib/template/dataset.jade
lib/template/datasource-ui.jade
lib/template/datasource.jade
lib/template/graph-edit.jade
lib/template/metric-edit.jade
lib/timeseries/csv.co [moved from lib/util/timeseries/csv.co with 95% similarity]
lib/timeseries/index.co [moved from lib/util/timeseries/index.co with 100% similarity]
lib/timeseries/timeseries.co [moved from lib/util/timeseries/timeseries.co with 99% similarity]
lib/util/index.co
lib/util/op.co

index 7d22832..dce79ce 100644 (file)
@@ -4,6 +4,8 @@ Backbone = require 'backbone'
 } = require 'kraken/util'
 { BaseBackboneMixin, mixinBase,
 } = require 'kraken/base/base-mixin'
+{ DataBinding,
+} = require 'kraken/base/data-binding'
 
 
 
@@ -13,15 +15,20 @@ Backbone = require 'backbone'
  */
 BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
     tagName : 'section'
+    model : BaseModel
     
     /**
-     * The identifier for this view class.
-     * By default, the class name is converted to underscore-case, and a
-     * trailing '_view' suffix is dropped.
-     * Example: "CamelCaseView" becomes "camel_case"
+     * Method-name called by `onReturnKeypress` when used as an event-handler.
      * @type String
      */
-    __view_type_id__: null
+    callOnReturnKeypress: null
+    
+    
+    /**
+     * Parent view of this view.
+     * @type BaseView
+     */
+    parent : null
     
     /**
      * Array of [view, selector]-pairs.
@@ -33,7 +40,7 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
      * Whether this view has been added to the DOM.
      * @type Boolean
      */
-    _parented: false
+    isAttached: false
     
     
     
@@ -42,7 +49,8 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
         @__superclass__ = @..__super__.constructor
         @__view_type_id__ or= _.str.underscored @getClassName() .replace /_view$/, ''
         @waitingOn      = 0
-        @subviews       = []
+        @subviews       = new ViewList
+        @onReturnKeypress = _.debounce @onReturnKeypress.bind(this), 50
         Backbone.View ...
         @trigger 'create', this
     
@@ -65,63 +73,130 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
             @$el.data { @model, view:this }
             @model.on 'change',  @render, this
             @model.on 'destroy', @remove, this
-        @model
+            @trigger 'change:model', this, model
+        model
     
     
     
     ### Subviews
     
-    addSubview: (selector, view) ->
-        [view, selector] = [selector, null] unless view
-        @subviews.push [view, selector]
+    setParent: (parent) ->
+        [old_parent, @parent] = [@parent, parent]
+        @trigger 'parent', this, parent, old_parent
+        this
+    
+    unsetParent: ->
+        [old_parent, @parent] = [@parent, null]
+        @trigger 'unparent', this, old_parent
+        this
+    
+    
+    addSubview: (view) ->
+        @removeSubview view
+        @subviews.push view
+        view.setParent this
         view
     
     removeSubview: (view) ->
-        for [v, sel], idx of @subviews
-            if v is view
-                @subviews.splice(idx, 1)
-                return [v, sel]
-        null
+        if @hasSubview view
+            view.remove()
+            @subviews.remove view
+            view.unsetParent()
+        view
     
     hasSubview: (view) ->
-        _.any @subviews, ([v]) -> v is view
+        @subviews.contains view
     
     invokeSubviews: ->
-        _ _.pluck(@subviews, 0) .invoke ...arguments
+        @subviews.invoke ...arguments
     
-    attachSubviews: ->
-        for [view, selector] of @subviews
-            return unless view
-            view.undelegateEvents()
-            return unless el = view.render()?.el
-            if selector
-                @$el.find selector .append el
-            else
-                @$el.append el
-            view.delegateEvents()
+    removeAllSubviews: ->
+        @subviews.forEach @removeSubview, this
+        @subviews = new ViewList
         this
     
-    removeAllSubviews: ->
-        @invokeSubviews 'remove'
-        _.pluck @subviews, 0 .forEach @removeSubview, this
+    
+    
+    ### UI Utilities
+    
+    attach: (el) ->
+        # @undelegateEvents()
+        @$el.appendTo el
+        # only trigger the event the first time
+        return this if @isAttached
+        @isAttached = true
+        _.delay do
+            ~> # have to let DOM settle to ensure elements can be found
+                @delegateEvents()
+                @trigger 'attach', this
+            50
         this
     
-    bubbleEvent: (evt) ->
-        @invokeSubviews 'trigger', ...arguments
+    remove : ->
+        # @undelegateEvents()
+        @$el.remove()
+        return this unless @isAttached
+        @isAttached = false
+        @trigger 'unattach', this
+        this
+    
+    clear  : ->
+        @remove()
+        @model.destroy()
+        @trigger 'clear', this
+        this
+    
+    hide : -> @$el.hide(); @trigger('hide', this); this
+    show : -> @$el.show(); @trigger('show', this); this
+    
+    /**
+     * Attach each subview to its bind-point.
+     * @returns {this}
+     */
+    attachSubviews: ->
+        bps = @getOwnSubviewBindPoints()
+        if @subviews.length and not bps.length
+            console.warn "#this.attachSubviews(): no subview bind-points found!"
+            return this
+        for view of @subviews
+            if bp = @findSubviewBindPoint view, bps
+                view.attach bp
+            else
+                console.warn "#this.attachSubviews(): Unable to find bind-point for #view!"
         this
     
+    /**
+     * Finds all subview bind-points under this view's element, but not under
+     * the view element of any subview.
+     * @returns {jQuery|undefined}
+     */
+    getOwnSubviewBindPoints: ->
+        @$ '[data-subview]' .not @$ '[data-subview] [data-subview]'
+    
+    /**
+     * Find the matching subview bind-point for the given view.
+     */
+    findSubviewBindPoint: (view, bind_points) ->
+        bind_points or= @getOwnSubviewBindPoints()
+        
+        # check if any bindpoint specifies this subview by id
+        if view.id
+            bp = bind_points.filter "[data-subview$=':#{view.id}']"
+            return bp.eq 0 if bp.length
+        
+        # Find all elements that specify this type as the subview type
+        bp = bind_points.filter "[data-subview='#{view.getClassName()}']"
+        return bp.eq 0 if bp.length
+    
+    
     
     ### Rendering Chain
     
     toTemplateLocals: ->
-        json = {value:v} = @model.toJSON()
-        if _.isArray(v) or _.isObject(v)
-            json.value = JSON.stringify v
-        json
+        @model.toJSON()
     
-    $template: (locals={}) ->
-        $ @template do
-            { $, _, op, @model, view:this } import @toTemplateLocals() import locals
+    $template: ->
+        $ @template { _, op, @model, view:this, ...@toTemplateLocals() }
     
     build: ->
         return this unless @template
@@ -129,59 +204,52 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
         @$el.html outer.html()
             .attr do
                 id    : outer.attr 'id'
-                class : outer.attr('class')
+                class : outer.attr 'class'
         @attachSubviews()
+        @isBuilt = true
         this
     
     render: ->
-        @build()
+        if @isBuilt
+            @update()
+        else
+            @build()
+        @renderSubviews()
         @trigger 'render', this
         this
     
     renderSubviews: ->
-        _.invoke _.pluck(@subviews, 0), 'render'
+        @attachSubviews()
+        @subviews.invoke 'render'
         this
     
-    
-    attach: (el) ->
-        @$el.appendTo el
-        # only trigger the event the first time
-        return this if @_parented
-        @_parented = true
-        _.delay do
-            ~> # have to let DOM settle to ensure elements can be found
-                @delegateEvents()
-                @trigger 'parent', this
-            50
+    update: ->
+        new DataBinding this .update @toTemplateLocals()
+        @trigger 'update', this
         this
     
-    remove : ->
-        @undelegateEvents()
-        @$el.remove()
-        return this unless @_parented
-        @_parented = false
-        @trigger 'unparent', this
-        this
     
+    /* * * *  Events  * * * */
     
-    ### UI Utilities
+    bubbleEvent: (evt) ->
+        @invokeSubviews 'trigger', ...arguments
+        this
     
-    hide   : -> @$el.hide();      @trigger('hide', this);       this
-    show   : -> @$el.show();      @trigger('show', this);       this
-    clear  : -> @model.destroy(); @trigger('clear', this);      @remove()
+    redispatch: (evt) ->
+        @trigger ...arguments
+        this
     
+    onlyOnReturn: (fn, ...args) ->
+        fn = _.debounce fn.bind(this), 50
+        (evt) ~> fn.apply this, args if evt.keyCode is 13
     
-    # remove : ->
-    #     if (p = @$el.parent()).length
-    #         @$parent or= p
-    #         # @parent_index = p.children().indexOf @$el
-    #     @$el.remove()
-    #     this
-    # 
-    # reparent : (parent=@$parent) ->
-    #     parent = $ parent
-    #     @$el.appendTo parent if parent?.length
-    #     this
+    /**
+     * Call a delegate on keypress == the return key.
+     * @returns {Function} Keypress event handler.
+     */
+    onReturnKeypress: (evt) ->
+        fn = this[@callOnReturnKeypress] if @callOnReturnKeypress
+        fn.call this if fn and evt.keyCode is 13
     
     toString : ->
         "#{@getClassName()}(model=#{@model})"
@@ -194,3 +262,27 @@ BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
 
 # }}}
 
+
+
+class exports.ViewList extends Array
+    
+    (views=[]) ->
+        super ...
+    
+    extend: (views) ->
+        _.each views, ~> @push it
+        this
+    
+    findByModel: (model) ->
+        @find -> it.model is model
+    
+    toString: ->
+        contents = if @length then "\"#{@join '","'}\"" else ''
+        "ViewList[#{@length}](#contents)"
+
+
+<[ each contains invoke pluck find remove compact flatten without union intersection difference unique uniq ]>
+    .forEach (methodname) ->
+        ViewList::[methodname] = -> _[methodname].call _, this, ...arguments
+
+
index b3b17f8..9a31587 100644 (file)
@@ -1,6 +1,7 @@
-mixins    = require 'kraken/base/base-mixin'
-models    = require 'kraken/base/base-model'
-views     = require 'kraken/base/base-view'
-cache     = require 'kraken/base/model-cache'
-cascading = require 'kraken/base/cascading-model'
-exports import mixins import models import views import cache import cascading
+mixins       = require 'kraken/base/base-mixin'
+models       = require 'kraken/base/base-model'
+views        = require 'kraken/base/base-view'
+cache        = require 'kraken/base/model-cache'
+cascading    = require 'kraken/base/cascading-model'
+data_binding = require 'kraken/base/data-binding'
+exports import mixins import models import views import cache import cascading import data_binding
index a531efe..cdba717 100644 (file)
@@ -59,8 +59,8 @@ ChartOption = exports.ChartOption = Field.extend do # {{{
         KNOWN_TAGS.update @getCategory()
         
         # Ignore functions/callbacks and, ahem, hidden tags.
-        type = @get 'type', '' .toLowerCase()
-        tags = @get 'tags', []
+        type = @get 'type' .toLowerCase() or ''
+        tags = @get('tags') or []
         if _.str.include(type, 'function') or _.intersection(tags, @IGNORED_TAGS).length
             @set 'ignore', true
     
@@ -69,7 +69,7 @@ ChartOption = exports.ChartOption = Field.extend do # {{{
     # will not trigger the 'changed:tags' event.
     addTag: (tag) ->
         return this unless tag
-        tags = @get('tags', [])
+        tags = @get('tags') or []
         tags.push tag
         @set 'tags', tags
         this
@@ -78,7 +78,7 @@ ChartOption = exports.ChartOption = Field.extend do # {{{
     # will not trigger the 'changed:tags' event.
     removeTag: (tag) ->
         return this unless tag
-        tags = @get('tags', [])
+        tags = @get('tags') or []
         _.remove tags, tag
         @set 'tags', tags
         this
@@ -93,7 +93,7 @@ ChartOption = exports.ChartOption = Field.extend do # {{{
     
     # A field's category is its first tag.
     getCategory: ->
-        @get('tags', [])[0]
+        tags = (@get('tags') or [])[0]
     
     getCategoryIndex: ->
         @getTagIndex @getCategory()
@@ -112,7 +112,7 @@ ChartOption = exports.ChartOption = Field.extend do # {{{
  * @class List of ChartOption fields.
  */
 ChartOptionList = exports.ChartOptionList = FieldList.extend do # {{{
-    model      : ChartOption
+    model : ChartOption
     
     
     constructor: function ChartOptionList
index bf7329a..86d86e0 100644 (file)
@@ -19,9 +19,9 @@ ChartOptionView = exports.ChartOptionView = FieldView.extend do # {{{
     isCollapsed : true
     
     events :
-        'blur .value'                        : 'update'
-        'click input[type="checkbox"].value' : 'update'
-        'submit .value'                      : 'update'
+        'blur .value'                        : 'change'
+        'click input[type="checkbox"].value' : 'change'
+        'submit .value'                      : 'change'
         'click .close'                       : 'toggleCollapsed'
         'click h3'                           : 'toggleCollapsed'
         'click .collapsed'                   : 'onClick'
@@ -32,6 +32,7 @@ ChartOptionView = exports.ChartOptionView = FieldView.extend do # {{{
     
     render: ->
         FieldView::render ...
+        @$el.addClass 'ignore'    if @get 'ignore'
         @$el.addClass 'collapsed' if @isCollapsed
         this
     
@@ -106,9 +107,10 @@ ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{
     
     render: ->
         console.log "#this.render(ready=#{@ready}) -> .isotope()"
-        # Scaffold::render ...
+        Scaffold::render ...
         return this unless @ready
-        container = if @fields then @$el.find @fields else @$el
+        
+        container = if @fields then @$ @fields else @$el
         container
             .addClass 'isotope'
             .find '.chart-option.field' .addClass 'isotope-item'
@@ -125,21 +127,21 @@ ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{
         this
     
     getOptionsFilter: ->
-        data = @$el.find '.options-filter-button.active' .toArray().map -> $ it .data()
+        data = @$ '.options-filter-button.active' .toArray().map -> $ it .data()
         sel = data.reduce do
             (sel, d) ->
                 sel += that if d.filter
                 sel
-            ''
+            ':not(.ignore)'
         sel
     
     collapseAll: ->
-        _.invoke @_subviews, 'collapse', true
+        _.invoke @subviews, 'collapse', true
         # @renderSubviews()
         false
     
     expandAll: ->
-        _.invoke @_subviews, 'collapse', false
+        _.invoke @subviews, 'collapse', false
         # @renderSubviews()
         false
     
@@ -148,9 +150,10 @@ ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{
      * Add a ChartOption to this scaffold, rerendering the isotope
      * layout after collapse events.
      */
-    addOne: (field) ->
-        view = Scaffold::addOne ...
-        view.on 'change:collapse render', @render, this
+    addField: (field) ->
+        view = Scaffold::addField ...
+        # view.on 'change:collapse render', @render, this
+        view.on 'change:collapse', @render, this
         view
     
     toKV: ->
index 470647b..fe66048 100644 (file)
@@ -68,7 +68,7 @@ DashboardView = exports.DashboardView = BaseView.extend do # {{{
                 @trigger 'ready', this
     
     attachGraphs: ->
-        graphs_el = @$el.find '#graphs'
+        graphs_el = @$ '#graphs'
         for id of @graph_ids
             break unless graph = @graphs.get id
             continue if graph.view.isAttached
index d031f97..f07549e 100644 (file)
@@ -1,7 +1,7 @@
 Seq = require 'seq'
 { _, op,
 } = require 'kraken/util'
-{ BaseView,
+{ BaseView, ViewList,
 } = require 'kraken/base'
 { DataSetView,
 } = require 'kraken/dataset/dataset-view'
@@ -18,7 +18,6 @@ DataView = exports.DataView = BaseView.extend do # {{{
     className      : 'data-ui'
     template       : require 'kraken/template/data'
     
-    data        : {}
     datasources : null
     
     
@@ -29,36 +28,41 @@ DataView = exports.DataView = BaseView.extend do # {{{
     initialize: ->
         @graph_id = @options.graph_id
         BaseView::initialize ...
+        @metric_views = new ViewList
         @datasources = @model.sources
+        @model.metrics
+            .on 'add',      @addMetric,     this
+            .on 'remove',   @removeMetric,  this
         @on 'ready', @onReady, this
         @load()
     
     onReady: ->
         dataset = @model
-        @metric_edit_view = @addSubview new MetricEditView  {@graph_id, dataset, @datasources}
-        @metric_edit_view
-            .on 'update',           @onUpdateMetric,      this
+        @model.metrics.each @addMetric, this
+        # @metric_edit_view = @addSubview new MetricEditView  {@graph_id, dataset, @datasources}
+        # @metric_edit_view
+        #     .on 'update',           @onUpdateMetric,    this
         
-        @dataset_view = @addSubview new DataSetView {@model, @graph_id, dataset, @datasources}
-        @dataset_view
+        @dataset_view = new DataSetView {@model, @graph_id, dataset, @datasources}
+        @addSubview @dataset_view
             .on 'add-metric',       @onMetricsChanged,  this
             .on 'remove-metric',    @onMetricsChanged,  this
             .on 'edit-metric',      @editMetric,        this
         
-        @attachSubviews()
+        @render()
         this
     
     
     
     load: ->
         @wait()
-        $.getJSON '/datasources/all', (@data) ~>
-            _.each @data, @canonicalizeDataSource, this
-            @model.sources.reset _.map @data, -> it
-            @ready = true
-            @unwait()
-            @render()
-            @trigger 'ready', this
+        # $.getJSON '/datasources/all', (@data) ~>
+        #     _.each @data, @canonicalizeDataSource, this
+        #     @model.sources.reset _.map @data, -> it
+        @ready = true
+        @unwait()
+        # @render()
+        @trigger 'ready', this
     
     /**
      * Transform the `columns` field to ensure an Array of {label, type} objects.
@@ -84,22 +88,33 @@ DataView = exports.DataView = BaseView.extend do # {{{
     
     toTemplateLocals: ->
         attrs = _.clone @model.attributes
-        { $, _, op, @model, view:this, @graph_id, @datasources } import attrs
+        { @graph_id, @datasources } import attrs
     
-    # attachSubviews: ->
-    #     @$el.empty()
-    #     BaseView::attachSubviews ...
-    #     @$el.append '<div class="clearer"/>'
+    # Don't rebuild HTML, simply notify subviews
+    # render: ->
+    #     @renderSubviews()
+    #     @trigger 'render', this
     #     this
     
-    # Don't rebuild HTML, simply notify subviews
-    render: ->
-        @renderSubviews()
-        @trigger 'render', this
-        this
+    addMetric: (metric) ->
+        console.log "#this.addMetric!", metric
+        return metric if @metric_views.findByModel metric
+        view = new MetricEditView {model:metric, @graph_id, dataset, @datasources}
+        @metric_views.push @addSubview view
+        metric
+    
+    removeMetric: (metric) ->
+        console.log "#this.removeMetric!", metric
+        return unless view = @metric_views.findByModel metric
+        @metric_views.remove view
+        @removeSubview view
+        metric
     
     editMetric: (metric) ->
-        @metric_edit_view.editMetric metric
+        console.log "#this.editMetric!", metric
+        @metric_views.invoke 'hide'
+        @metric_edit_view = @metric_views.findByModel metric
+        @metric_edit_view?.show()
         @onMetricsChanged()
     
     onMetricsChanged: ->
@@ -107,7 +122,7 @@ DataView = exports.DataView = BaseView.extend do # {{{
         newMinHeight = Math.max do
             oldMinHeight
             @dataset_view.$el.height()
-            @metric_edit_view.$el.height()
+            @metric_edit_view?.$el.height()
         # console.log 'onMetricsChanged!', oldMinHeight, '-->', newMinHeight
         @$el.css 'min-height', newMinHeight
     
index efb7d0d..b201229 100644 (file)
@@ -48,7 +48,7 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
         
         view = @addSubview new DataSetMetricView {model:metric, @graph_id}
         @views_by_cid[metric.cid] = view
-        @$el.find '.metrics' .append view.render().el
+        @$ '.metrics' .append view.render().el
         
         # @render()
         @trigger 'add-metric', metric, view, this
@@ -68,13 +68,13 @@ DataSetView = exports.DataSetView = BaseView.extend do # {{{
         this
     
     editMetric: (metric) ->
-        console.log "#this.editMetric!", metric
+        # console.log "#this.editMetric!", metric
         if metric instanceof [jQuery.Event, Event]
             metric = $ metric.currentTarget .data 'model'
         view = @active_view = @views_by_cid[metric.cid]
-        console.log '  --> metric:', metric, 'view:', view
+        console.log "#this.editMetric!", metric
         
-        @$el.find '.metrics .dataset-metric' .removeClass 'metric-active'
+        @$ '.metrics .dataset-metric' .removeClass 'metric-active'
         view.$el.addClass 'metric-active'
         view.$el.find '.activity-arrow' .css 'font-size', 2+view.$el.height()
         
index b5b8674..cb3bfb4 100644 (file)
@@ -1,5 +1,7 @@
-{ _, op, CSVData,
+{ _, op,
 } = require 'kraken/util'
+{ TimeSeriesData, CSVData,
+} = require 'kraken/timeseries'
 { BaseModel, BaseList, BaseView,
 } = require 'kraken/base'
 { Metric, MetricList,
@@ -59,7 +61,7 @@ DataSource = exports.DataSource = BaseModel.extend do # {{{
         @constructor.register this
         @metrics = new MetricList @attributes.metrics
         @on 'change:metrics', @onMetricChange, this
-        @load()
+        # @load()
     
     
     canonicalize: (ds) ->
@@ -98,7 +100,7 @@ DataSource = exports.DataSource = BaseModel.extend do # {{{
         $.ajax do
             url      : url
             dataType : 'json'
-            success  : @onLoadSuccess
+            success  : (data) ~> @onLoadSuccess new TimeSeriesData data
             error    : @onLoadError
         this
     
@@ -190,6 +192,7 @@ DataSource import do
             Cls = this
             @register new Cls {id}
                 .on 'ready', -> cb.call cxt, null, it
+                .load()
     
 
 _.bindAll DataSource, 'register', 'get', 'lookup'
index affc7d5..5765b41 100644 (file)
@@ -14,7 +14,8 @@ DataSourceUIView = exports.DataSourceUIView = BaseView.extend do # {{{
     template       : require 'kraken/template/datasource-ui'
     
     events :
-        'click .datasource-summary': 'onHeaderClick'
+        'click .datasource-summary'       : 'onHeaderClick'
+        'click .datasource-source-metric' : 'onSelectMetric'
     
     graph_id           : null
     dataset            : null
@@ -50,4 +51,10 @@ DataSourceUIView = exports.DataSourceUIView = BaseView.extend do # {{{
     onHeaderClick: ->
         @$el.toggleClass 'in'
     
+    onSelectMetric: (evt) ->
+        tr = evt.currentTarget
+        idx = @$ '.source-metrics .datasource-source-metric' .toArray().indexOf tr
+        return unless idx is not -1
+        
+    
 # }}}
index b8f743b..47904f4 100644 (file)
@@ -17,6 +17,10 @@ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
     className      : 'metric-edit-ui'
     template       : require 'kraken/template/metric-edit'
     
+    callOnReturnKeypress : 'onChanged'
+    events:
+        'keydown .metric-label' : 'onReturnKeypress'
+    
     graph_id           : null
     dataset            : null
     datasources        : null
@@ -30,8 +34,10 @@ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
         this import @options.{graph_id, dataset, datasources}
         @model or= new Metric
         BaseView::initialize ...
-        @datasource_ui_view = @addSubview '.metric-datasource', new DataSourceUIView {@model, @graph_id, @dataset, @datasources}
-        @$el.find '.metric-datasource' .append @datasource_ui_view.render().el
+        @datasource_ui_view = new DataSourceUIView {@model, @graph_id, @dataset, @datasources}
+        @addSubview @datasource_ui_view
+            .on 'update', ~> @trigger 'update', this
+        @$ '.metric-datasource' .append @datasource_ui_view.render().el
     
     
     toTemplateLocals: ->
@@ -41,12 +47,25 @@ MetricEditView = exports.MetricEditView = BaseView.extend do # {{{
     build: ->
         BaseView::build ...
         if @datasource_ui_view
-            @$el.find '.metric-datasource' .append @datasource_ui_view.render().el
+            @$ '.metric-datasource' .append @datasource_ui_view.render().el
+        this
+    
+    update: ->
+        color = @model.get 'color'
+        @$ '.color-swatch' .css 'background-color', color
+        @$ '.metric-color' .val color
+        @$ '.metric-label' .val @model.get 'label'
+        
         this
     
+    onChanged: ->
+        attrs = @$ 'form.metric-edit-form' .formData()
+        @model.set attrs, {+silent}
+        @trigger 'update', this
+    
     editMetric: (metric) ->
         console.log "#this.editMetric!", metric
-        @datasource_ui_view.model = @model = metric
+        @datasource_ui_view.setModel @setModel metric
         @render()
         @show()
         this
index d339283..ffef7a9 100644 (file)
@@ -49,8 +49,8 @@ Metric = exports.Metric = BaseModel.extend do # {{{
         @lookupSource()
     
     
-    getDates: ->
-        @source.getDates()
+    getDateColumn: ->
+        @source.getDateColumn()
     
     getData: ->
         @source.getColumn @get 'source_col'
index c4da4d8..e698d7b 100644 (file)
@@ -123,9 +123,9 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
         return { width, height } unless @ready
         
         # Remove old style, as it confuses dygraph after options update
-        viewport = @$el.find '.viewport'
+        viewport = @$ '.viewport'
         viewport.attr 'style', ''
-        label = @$el.find '.graph-legend'
+        label = @$ '.graph-legend'
         
         if width is 'auto'
             vpWidth = viewport.innerWidth()
@@ -147,12 +147,12 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
     renderChart: ->
         data = @model.get 'dataset' #.getData()
         size = @resizeViewport()
-        viewport = @$el.find '.viewport'
+        viewport = @$ '.viewport'
         
         # XXX: use @model.changedAttributes() to calculate what to update
         options = @chartOptions() #import size
         options import do
-            labelsDiv          : @$el.find '.graph-legend' .0
+            labelsDiv          : @$ '.graph-legend' .0
             valueFormatter     : @numberFormatterHTML
             axes:
                 x:
@@ -186,8 +186,8 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
     
     update: ->
         locals = @toTemplateLocals()
-        @$el.find '.graph-name a' .text(locals.name or '')
-        @$el.find '.graph-desc' .html jade.filters.markdown locals.desc or ''
+        @$ '.graph-name a' .text(locals.name or '')
+        @$ '.graph-desc' .html jade.filters.markdown locals.desc or ''
         this
     
     render: ->
@@ -268,7 +268,7 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{
         # last action that happens.  If we don't
         # defer, the focusing click will 
         # unselect the text.
-        _.defer( ~> @$el.find '.graph-permalink input' .select() )
+        _.defer( ~> @$ '.graph-permalink input' .select() )
 
     # Needed because (sigh) _.debounce returns undefined
     stopAndRender: ->
index 30edab8..e7d403f 100644 (file)
@@ -27,12 +27,12 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         valueFormatter xValueFormatter yValueFormatter
     ]>
     __bind__  : <[
-        render renderAll stopAndRender stopAndRenderAll resizeViewport wait unwait checkWaiting
+        render stopAndRender resizeViewport wait unwait checkWaiting
         numberFormatter numberFormatterHTML
         onReady onSync onModelChange onScaffoldChange 
         onFirstClickRenderOptionsTab onFirstClickRenderDataTab
     ]>
-    __debounce__: <[ render renderAll ]>
+    __debounce__: <[ render ]>
     tagName   : 'section'
     className : 'graph-edit graph'
     template  : require 'kraken/template/graph-edit'
@@ -68,7 +68,6 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         BaseView ...
     
     initialize : (o={}) ->
-        # @data = {}
         @model or= new Graph
         @id = @graph_id = _.domize 'graph', (@model.id or @model.get('slug') or @model.cid)
         BaseView::initialize ...
@@ -98,23 +97,23 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
             .on 'ready',            @onReady,       this
         
         ### Chart Options Tab, Scaffold
-        @scaffold = @addSubview '.graph-options-pane', new ChartOptionScaffold
-        @$el.find '.graph-options-pane' .append @scaffold.el
+        @scaffold = @addSubview new ChartOptionScaffold
+        # @$ '.graph-options-pane' .append @scaffold.el
         @scaffold.collection.reset that if o.graph_spec
         
         @scaffold.on 'change', @onScaffoldChange
         @chartOptions @model.getOptions(), {+silent}
         
-        @$el.on 'click', '.graph-data-tab', @onFirstClickRenderDataTab
         
-        # Rerender the options boxes once the tab is visible
+        # Rerender once the tab is visible
         # Can't use @events because we need to bind before registering
-        @$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab
+        @$el.on 'click', '.graph-data-tab',     @onFirstClickRenderDataTab
+        @$el.on 'click', '.graph-options-tab',  @onFirstClickRenderOptionsTab
         
         ### Graph Data UI
-        @data = @addSubview '.graph-data-pane', new DataView { model:@model.get('data'), graph_id:@id }
-        @$el.find '.graph-data-pane' .append @data.render().el
-        @data
+        @data_view = @addSubview new DataView { model:@model.get('data'), graph_id:@id }
+        # @$ '.graph-data-pane' .append @data_view.render().el
+        @data_view
             .on 'change',        @onDataChange, this
             .on 'start-waiting', @wait,         this
             .on 'stop-waiting',  @unwait,       this
@@ -180,9 +179,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
     toTemplateLocals: ->
         attrs = _.clone @model.attributes
         delete attrs.options
-        # delete attrs.dataset
-        # attrs.data = @data
-        { $, _, op, @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs
+        { @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs
     
     
     /**
@@ -194,11 +191,11 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         modelH = height = @model.get 'height'
         return { width, height } unless @ready
         
-        viewport = @$el.find '.viewport'
+        viewport = @$ '.viewport'
         
         # Remove old style, as it confuses dygraph after options update
         viewport.attr 'style', ''
-        label = @$el.find '.graph-label'
+        label = @$ '.graph-label'
         
         if width is 'auto'
             vpWidth = viewport.innerWidth()
@@ -218,7 +215,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
     
     # Repopulate UI from Model
     renderDetails: ->
-        form = @$el.find 'form.graph-details'
+        form = @$ 'form.graph-details'
         for k, v in @model.attributes
             continue if k is 'options'
             txt = @model.serialize v
@@ -232,7 +229,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
             form.find "textarea[name=#k]" .text txt
         
         # Graph Name field is not part of the form due to the layout.
-        @$el.find "input.graph-name[name='name']" .val @get 'name'
+        @$ "input.graph-name[name='name']" .val @get 'name'
         this
     
     # Redraw chart inside viewport.
@@ -247,7 +244,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         options = @chartOptions() #import size
         options import do
             labels             : dataset.getLabels()
-            labelsDiv          : @$el.find '.graph-label' .0
+            labelsDiv          : @$ '.graph-label' .0
             valueFormatter     : @numberFormatterHTML
             axes:
                 x:
@@ -264,13 +261,13 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         # dygraphs to reset the current option state.
         @chart?.destroy()
         @chart = new Dygraph do
-            @$el.find '.viewport' .0
+            @$ '.viewport' .0
             data
             options
         
         # unless @chart
         #     @chart = new Dygraph do
-        #         @$el.find '.viewport' .0
+        #         @$ '.viewport' .0
         #         data
         #         options
         # else
@@ -280,33 +277,23 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         this
     
     attachSubviews: ->
-        @$el.find '.graph-options-pane' .append @scaffold.el        if @scaffold
-        @$el.find '.graph-data-pane'    .append @data.render().el   if @data
+        BaseView::attachSubviews ...
         @checkWaiting()
     
-    render: ->
-        return this unless @ready and not @_rendering
-        @_rendering = true
-        @wait()
-        @checkWaiting()
-        @renderDetails()
-        @attachSubviews()
-        # _.invoke @subviews, 'render'
-        @renderChart()
-        @updateURL()
-        @trigger 'render', this
-        @unwait()
-        @_rendering = false
-        this
-    
-    renderAll: ->
-        return this unless @ready
-        # console.log "#this.renderAll!"
-        @wait()
-        _.invoke @scaffold.subviews, 'render'
-        @scaffold.render()
-        @render()
-        @unwait()
+    # render: ->
+    #     return this unless @ready and not @_rendering
+    #     @_rendering = true
+    #     @wait()
+    #     @checkWaiting()
+    #     @renderDetails()
+    #     @attachSubviews()
+    #     # _.invoke @subviews, 'render'
+    #     @renderChart()
+    #     @updateURL()
+    #     @trigger 'render', this
+    #     @unwait()
+    #     @_rendering = false
+    #     this
     
     
     /**
@@ -324,7 +311,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
      * Retrieve or construct the spinner.
      */
     spinner: ->
-        el = @$el.find '.graph-spinner'
+        el = @$ '.graph-spinner'
         unless el.data 'spinner'
             ### Spin.js Options ###
             opts =
@@ -407,7 +394,6 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
     
     onReady: ->
         return if @ready
-        # $.getJSON '/datasources/all', (@data) ~>
         console.log "(#this via GraphEditView).ready!"
         @ready = @scaffold.ready = true
         @unwait() # clears `wait()` from `initialize`
@@ -420,10 +406,8 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         return unless @ready
         console.info "#this.sync() --> success!"
         # TODO: UI alert
-        # @change()
-        # @model.change()
         @chartOptions @model.getOptions(), {+silent}
-        @renderAll()
+        @render()
     
     onStartWaiting: ->
         console.log "#this.onStartWaiting!", @checkWaiting()
@@ -472,17 +456,15 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
         @scaffold.render()
     
     onFirstClickRenderDataTab: ->
-        # @$el.off 'click', '.graph-data-tab', @onFirstClickRenderDataTab
-        _.defer ~> @data.onMetricsChanged()
+        @$el.off 'click', '.graph-data-tab', @onFirstClickRenderDataTab
+        _.defer ~> @data_view.onMetricsChanged()
     
     onKeypress: (evt) ->
         $(evt.target).submit() if evt.keyCode is 13
     
     onDetailsSubmit: ->
         console.log "#this.onDetailsSubmit!"
-        data = _.synthesize do
-            @$el.find('form.graph-details').serializeArray()
-            -> [it.name, it.value]
+        data = @$ 'form.graph-details' .formData()
         @model.set data
         false
     
@@ -493,7 +475,6 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{
     
     # Needed because (sigh) _.debounce returns undefined, and we need to preventDefault()
     stopAndRender    : -> @render ... ;     false
-    stopAndRenderAll : -> @renderAll ... ;  false
     
     # }}}
     
index 2bd6679..d020caa 100644 (file)
@@ -3,6 +3,8 @@ Backbone = require 'backbone'
 
 { _, op,
 } = require 'kraken/util'
+{ TimeSeriesData, CSVData,
+} = require 'kraken/timeseries'
 { BaseView, BaseModel, BaseList,
 } = require 'kraken/base'
 { Field, FieldList, FieldView, Scaffold,
index 1b74bdb..5690062 100644 (file)
@@ -13,8 +13,8 @@ FieldView = exports.FieldView = BaseView.extend do # {{{
     type : 'string'
     
     events :
-        'blur .value'   : 'update'
-        'submit .value' : 'update'
+        'blur .value'   : 'change'
+        'submit .value' : 'change'
     
     
     constructor: function FieldView
@@ -23,19 +23,25 @@ FieldView = exports.FieldView = BaseView.extend do # {{{
     initialize: ->
         # console.log "#this.initialize!"
         BaseView::initialize ...
-        @type = @model.get('type', 'string').toLowerCase()
+        @type = @model.get('type').toLowerCase() or 'string'
     
-    update: ->
+    toTemplateLocals: ->
+        json = {value:v} = @model.toJSON()
+        if _.isArray(v) or _.isPlainObject(v)
+            json.value = JSON.stringify v
+        json
+    
+    change: ->
         if @type is 'boolean'
-            val = !! @$el.find('.value').attr('checked')
+            val = !! @$('.value').attr('checked')
         else
-            val = @model.getParser() @$el.find('.value').val()
+            val = @model.getParser() @$('.value').val()
         
         current = @model.getValue()
         return if _.isEqual val, current
-        console.log "#this.update( #current -> #val )"
+        console.log "#this.change( #current -> #val )"
         @model.setValue val, {+silent}
-        @trigger 'update', this
+        @trigger 'change', this
     
     render: ->
         return @remove() if @model.get 'ignore', false
@@ -61,7 +67,7 @@ FieldView = exports.FieldView = BaseView.extend do # {{{
 #   model, collection, el, id, className, tagName, attributes
 
 Scaffold = exports.Scaffold = BaseView.extend do # {{{
-    __bind__       : <[ addOne addAll ]>
+    __bind__       : <[ addField resetFields ]>
     tagName        : 'form'
     className      : 'scaffold'
     
@@ -78,19 +84,14 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{
         @model = (@collection or= new CollectionType)
         BaseView::initialize ...
         
-        @collection.on 'add',   @addOne
-        @collection.on 'reset', @addAll
+        @collection.on 'add',   @addField, this
+        @collection.on 'reset', @resetFields, this
         
         @$el.data { model:@collection, view:this } .addClass @className
     
     
-    renderSubviews: ->
-        _.invoke @_subviews, 'render'
-        this
-    
-    addOne: (field) ->
-        # console.log "[S] #this.addOne!", @..__super__
-        _.remove @_subviews, field.view if field.view
+    addField: (field) ->
+        @removeSubview field.view if field.view
         
         # avoid duplicating event propagation
         field.off 'change:value', @change, this
@@ -99,19 +100,15 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{
         field.on 'change:value', @change, this
         
         SubviewType = @subviewType
-        view = new SubviewType model:field
-        @_subviews.push view
-        container = if @fields then @$el.find @fields else @$el
-        container.append view.render().el unless field.get 'ignore'
-        view.on 'update', @change.bind(this, field)
+        view = @addSubview new SubviewType model:field
+        view.on 'change', @change.bind(this, field)
         
         @render()
         view
     
-    addAll: ->
-        _.invoke @_subviews, 'remove'
-        @_subviews = []
-        @collection.each @addOne
+    resetFields: ->
+        @removeAllSubviews()
+        @collection.each @addField
         this
     
     change: (field) ->
index 565cd6b..7b7c895 100644 (file)
@@ -9,6 +9,5 @@ form.chart-options.scaffold.form-inline
             a.advanced-filter-button.options-filter-button.btn(href="#", data-filter="") Advanced
         
     
-    .fields
-    //- .fields.control-group
+    .fields(data-subview="ChartOptionView")
 
index e51e63b..cd827c9 100644 (file)
@@ -1,16 +1,4 @@
 section.data-ui
-    //- 
-        .row-fluid
-        //- 
-            .dataset-controls.dropdown
-                select(name="dataset")
-                    for datum, k in datasources
-                        - var selected = (datum.url === dataset ? 'selected' : null);
-                        option(value=datum.url, name=datum.id, selected=selected) #{datum.name}
-        
-        //- 
-            a.span3.dataset.dropdown-toggle(data-toggle="dropdown", data-target="#{graph_id} .dataset-dropdown", href="#dataset-dropdown") Select...
-            ul.dropdown-menu
-                for datum, k in datasources
-                    li: a(href="#", data-dataset="#{datum.id}") #{datum.name}
+    .metric_edit_view_pane(data-subview="MetricEditView")
+    .data_set_view_pane(data-subview="DataSetView")
 
index 1eb4e62..289d571 100644 (file)
@@ -18,7 +18,7 @@ section.dataset-ui.dataset: div.inner
                     th.col-source   Source
                     th.col-times    Timespan
                     th.col-actions  Actions
-            tbody.metrics
+            tbody.metrics(data-subview="DataSetMetricView")
                 //- DataSetMetricViews attach here
     
 
index 193b4eb..13c2ec5 100644 (file)
@@ -23,6 +23,7 @@ section.datasource-ui
                         li(class=activeClass): a(href="#datasource-selector_datasource-source-#{ds.id}", data-toggle="tab", data-target=ds_target) #{ds.shortName}
                 
                 .datasource-sources-info.tab-content
+                    //- DataSourceViews attach here
                     for source, k in datasources.models
                         - var ds = source.attributes
                         - var activeClass = (source_id === ds.id ? 'active' : '')
index e69de29..eec762a 100644 (file)
@@ -0,0 +1,28 @@
+- var ds = source.attributes
+- var activeClass = (source_id === ds.id ? 'active' : '')
+.datasource-source.tab-pane(class="datasource-source-#{ds.id} #{activeClass}")
+    .datasource-source-details.well
+        .source-name #{ds.name}
+        .source-id #{ds.id}
+        .source-format #{ds.format}
+        .source-charttype #{ds.chart.chartType}
+        input.source-url(type="text", name="source-url", value=ds.url)
+        .datasource-source-time
+            .source-time-start #{ds.timespan.start}
+            .source-time-end #{ds.timespan.end}
+            .source-time-step #{ds.timespan.step}
+    .datasource-source-metrics
+        table.table.table-striped
+            thead
+                tr
+                    th.source-metric-idx #
+                    th.source-metric-label Label
+                    th.source-metric-type Type
+            tbody.source-metrics
+                for m, idx in ds.metrics.slice(1)
+                    - var activeColClass = (activeClass && source_col === m.idx) ? 'active' : ''
+                    tr.datasource-source-metric(class=activeColClass)
+                        td.source-metric-idx #{m.idx}
+                        td.source-metric-label #{m.label}
+                        td.source-metric-type #{m.type}
+                    
index a8824d4..83179c1 100644 (file)
@@ -1,9 +1,7 @@
 - var graph_id = view.id || model.id || model.cid
 section.graph-edit.graph(id=graph_id)
     
-    //- Graph Name field is not part of the form due to the layout.
     .graph-name-row.graph-details.row-fluid.control-group
-        //- label.name.control-label(for="#{id}_name"): h3 Graph Name
         input.span6.graph-name(type='text', id="#{graph_id}_name", name="name", placeholder='Graph Name', value=name)
     
     .graph-viewport-row.row-fluid
@@ -66,12 +64,12 @@ section.graph-edit.graph(id=graph_id)
                                     <textarea class="span3 desc" id="#{graph_id}_desc" name="desc" placeholder="Graph description.">#{desc}</textarea>
                                     p.help-block A description of the graph.
                 
-                .graph-data-pane.tab-pane(id="#{graph_id}-tab-data")
+                .graph-data-pane.tab-pane(id="#{graph_id}-tab-data", data-subview="DataView")
                     //- 
                         .row-fluid
                             label.dataset.control-label(for="#{graph_id}_dataset") Data Set
                             input.span3.dataset(type='text', id="#{graph_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_id}-tab-options")
+                .graph-options-pane.tab-pane(id="#{graph_id}-tab-options", data-subview="ChartOptionScaffold")
 
index ffe0a7f..d8c37cc 100644 (file)
@@ -1,12 +1,12 @@
 section.metric-edit-ui
-    .inner: form.form-horizontal
+    .inner: form.metric-edit-form.form-horizontal
         
         .metric-header.control-group
             .color-swatch(style="background-color: #{color};")
             input.metric-color(type='hidden', id="#{graph_id}_metric", name="color", value=color)
             input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='Metric Label', value=label)
         
-        .metric-datasource.control-group
+        .metric-datasource.control-group(data-subview="DataSourceUIView")
         
         .metric-actions.control-group
             a.delete-button.btn.btn-danger(href="#")
similarity index 95%
rename from lib/util/timeseries/csv.co
rename to lib/timeseries/csv.co
index 8e9409e..ec87318 100644 (file)
@@ -1,7 +1,5 @@
 _  = require 'kraken/util/underscore'
-op = require 'kraken/util/op'
-
-TimeSeriesData = require 'kraken/util/timeseries/timeseries'
+TimeSeriesData = require 'kraken/timeseries/timeseries'
 
 
 DASH_PATTERN       = /-/g
@@ -56,8 +54,7 @@ class CSVData extends TimeSeriesData
      * @returns {this}
      */
     parse: (@rawData) ->
-        if typeof rawData is not 'string'
-            
+        return this if typeof rawData is not 'string'
         o = @options
         
         lines = rawData.split o.rowSep
similarity index 99%
rename from lib/util/timeseries/timeseries.co
rename to lib/timeseries/timeseries.co
index 216bc76..f9ff9cb 100644 (file)
@@ -1,5 +1,4 @@
 _  = require 'kraken/util/underscore'
-op = require 'kraken/util/op'
 
 
 
index e98d502..1b5c01e 100644 (file)
@@ -1,21 +1,36 @@
-_  = require 'kraken/util/underscore'
+_  = exports._  = require 'kraken/util/underscore'
+op = exports.op = require 'kraken/util/op'
 
-root = do -> this
-root.console or= _ <[ log info warn error dir table group groupCollapsed groupEnd ]> .synthesize -> [it, nop]
+# Root object -- `window` in the browser, `global` in Node.
+root = exports.root = do -> this
 
+# Stub out console with empty methods
+root.console or= _ <[ log info warn error dir table group groupCollapsed groupEnd ]> .synthesize -> [it, op.nop]
+
+### Extend jQuery with useful functions
+
+/**
+ * @returns {Object} Object of the data from the form, via `.serializeArray()`.
+ */
+root.jQuery?.fn.formData = ->
+    _.synthesize do
+        this.serializeArray()
+        -> [it.name, it.value]
+
+/**
+ * Invokes a jQuery method on each element, returning the array of the result.
+ * @returns {Array} Results.
+ */
 root.jQuery?.fn.invoke = (method, ...args) ->
     for el, idx of this
-        el = jQuery(el)
-        el[method] ...args
-
-op        = require 'kraken/util/op'
-backbone  = require 'kraken/util/backbone'
-parser    = require 'kraken/util/parser'
-Cascade   = require 'kraken/util/cascade'
-CSVData   = require 'kraken/util/csv'
-exports import { root, _, op, backbone, parser, Cascade, CSVData, }
-
-# HashSet   = require 'kraken/util/hashset'
-# BitString = require 'kraken/util/bitstring'
-# {crc32}   = require 'kraken/util/crc'
-# exports import { HashSet, BitString, crc32, }
+        jQuery(el)[method] ...args
+
+
+backbone = exports.backbone = require 'kraken/util/backbone'
+parser   = exports.parser   = require 'kraken/util/parser'
+Cascade  = exports.Cascade  = require 'kraken/util/cascade'
+
+# HashSet   = exports.HashSet   = require 'kraken/util/hashset'
+# BitString = exports.BitString = require 'kraken/util/bitstring'
+# {crc32}   = exports.{crc32}   = require 'kraken/util/crc'
+
index 9129433..83477f0 100644 (file)
@@ -1,11 +1,15 @@
+STRIP_PAT = /(^\s*|\s*$)/g
+strip = (s) ->
+    if s then s.replace STRIP_PAT, '' else s
 
 FALSEY = /^\s*(?:no|off|false)\s*$/i
 parseBool = (s) ->
     i = parseInt(s or 0)
     !! if isNaN(i) then not FALSEY.test(s) else i
 
+
+
 module.exports = op =
-    
     I       : (x) -> x
     K       : (k) -> -> k
     nop     : ->
@@ -13,7 +17,7 @@ module.exports = op =
     kObject : -> {}
     kArray  : -> []
     
-    # values
+    ### values
     val     : (def,o) -> o ? def
     ok      : (o)     -> o?
     notOk   : (o)     -> o!?
@@ -32,14 +36,14 @@ module.exports = op =
             arguments[1] = a
             fn.apply this, arguments
     
-    # reduce-ordered values & accessors
+    ### reduce-ordered values & accessors
     khas      : (k,o)       ->  k in o
     kget      : (k,o)       ->  o[k]
     defkget   : (def,k,o)   ->  if k in o then o[k] else def
     thisget   : (k)         ->  this[k]
     vkset     : (o,v,k)     ->  o[k] = v if o and k?; o
     
-    # curry-ordered values & accessors
+    ### curry-ordered values & accessors
     has       : (o,k)       ->  k in o
     get       : (o,k)       ->  o[k]
     getdef    : (o,k,def)   ->  if k in o then o[k] else def
@@ -52,15 +56,19 @@ module.exports = op =
             obj[name] ...args.concat(_args) if obj?[name]
     isK       : (k) -> (v) -> v is k
     
-    # type coercion (w/ limited parameters for mapping)
+    ### type coercion (w/ limited parameters for mapping)
     parseBool   : parseBool
     toBool      : parseBool
     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' and strip(v)
+            JSON.parse v
+        else
+            v
     
-    # comparison
+    ### comparison
     cmp       : (x,y)  ->  if x < y then -1 else (if x > y then 1 else 0)
     eq        : (x,y)  ->  x == y
     ne        : (x,y)  ->  x != y
@@ -69,7 +77,7 @@ module.exports = op =
     lt        : (x,y)  ->  x  < y
     le        : (x,y)  ->  x <= y
     
-    # math
+    ### math
     add       : (x,y)  ->  x  + y
     sub       : (x,y)  ->  x  - y
     mul       : (x,y)  ->  x  * y
@@ -80,14 +88,14 @@ module.exports = op =
     log2      : (n)    -> Math.log n / Math.LN2
     
     
-    # logic
+    ### logic
     is        : (x,y)  ->  x is y
     isnt      : (x,y)  ->  x is not y
     and       : (x,y)  ->  x and y
     or        : (x,y)  ->  x or y
     not       : (x)    ->  not x
     
-    # bitwise
+    ### bitwise
     bitnot    : (x)    ->  ~x
     bitand    : (x,y)  ->  x  & y
     bitor     : (x,y)  ->  x  | y
@@ -96,7 +104,7 @@ module.exports = op =
     rshift    : (x,y)  ->  x >> y
     # zrshift   : (x,y)     ->  x >>> y
     
-    # binary
+    ### binary
     
     # Binary representation of the number.
     bin : (n) ->
@@ -119,5 +127,5 @@ module.exports = op =
     ord    : -> String(it).charCodeAt 0
     encode : -> it and $ "<div>#it</div>" .html().replace /"/g, '&quot;'
     decode : -> it and $ "<div>#it</div>" .text()
-    
+    strip  : strip