Vastly improves appearance of options, which means I've figured out how to use Bootst...
authordsc <dsc@less.ly>
Thu, 23 Feb 2012 10:13:53 +0000 (02:13 -0800)
committerdsc <dsc@less.ly>
Thu, 23 Feb 2012 10:13:53 +0000 (02:13 -0800)
16 files changed:
lib/graph.co
lib/scaffold/index.co [new file with mode: 0644]
lib/scaffold/model.co [new file with mode: 0644]
lib/scaffold/view.co [moved from lib/scaffold.co with 61% similarity]
lib/template/browser-helpers.jade [new file with mode: 0644]
lib/template/graph-option.jade
lib/template/graph.jade
lib/underscore/functions.co [new file with mode: 0644]
msc/dygraph-options/data.yaml
static/vendor/showdown.js [new file with mode: 0644]
static/vendor/showdown.min.js [new file with mode: 0644]
www/css/graph.styl
www/css/isotope.css [new file with mode: 0644]
www/graph/test.jade
www/layout.jade
www/modules.yaml

index 0d23855..eda01aa 100644 (file)
@@ -31,6 +31,7 @@ GraphOptionList = exports.GraphOptionList = FieldList.extend do # {{{
  * The view for a single configurable option.
  */
 GraphOptionView = exports.GraphOptionView = FieldView.extend do # {{{
+    # __bind__  : <[ onClick ]>
     ctorName  : 'GraphOptionView'
     tagName   : 'div'
     className : 'field option'
@@ -41,37 +42,38 @@ GraphOptionView = exports.GraphOptionView = FieldView.extend do # {{{
     events :
         'blur .value'                   : 'update'
         'submit .value'                 : 'update'
+        'click .close'                  : 'toggleCollapsed'
+        'click'                         : 'onClick'
     
     
-    initialize: ->
-        # console.log "#this.initialize!"
-        FieldView::initialize ...
-        @$el.on 'click', (evt) ~>
-            target = $ evt.target
-            @toggleCollapsed() if @el is evt.target or not target.is '.value, label, input'
-        
+    # initialize: ->
+    #     console.log "#this.initialize!"
+    #     FieldView::initialize ...
+    #     
     
     update: ->
         val     = @$el.find('.value').val()
         current = @model.get 'value'
         return if val is current
-        
         console.log "#this.update( #current -> #val )"
         @model.setValue val, {+silent}
     
     render: ->
-        FieldView::render.call this
+        @__super__.render ...
         @$el.addClass 'collapsed' if @isCollapsed
-        # outer = $ @template @model.toJSON()
-        # @$el.html outer.html()
-        #     .attr do
-        #         id    : outer.attr 'id'
-        #         class : outer.attr('class') + if @isCollapsed then ' collapsed' else ''
         this
     
+    onClick: (evt) ->
+        target = $ evt.target
+        console.log "#this.onClick()", target
+        @toggleCollapsed() if @$el.hasClass('collapsed') and not target.hasClass('close')
+    
     toggleCollapsed: ->
+        starting = @$el.hasClass 'collapsed' #@isCollapsed
         @$el.toggleClass 'collapsed'
         @isCollapsed = @$el.hasClass 'collapsed'
+        console.log "#this.toggleCollapsed!", starting, '->', @isCollapsed
+        @trigger 'change:collapse', this
         this
     
 # }}}
@@ -85,18 +87,26 @@ GraphOptionsScaffold = exports.GraphOptionsScaffold = Scaffold.extend do # {{{
     collectionType : GraphOptionList
     subviewType    : GraphOptionView
     
-    # initialize : ->
-    #     Scaffold::initialize ...
+    
+    initialize : ->
+        Scaffold::initialize ...
+        @render = _.debounce @render.bind(this), 50
     
     render: ->
+        # console.log "#this.render() -> .isotope()"
         @__super__.render ...
         @$el.isotope do
             itemSelector    : '.field.option'
             layoutMode      : 'masonry'
-            masonry         : columnWidth : 100
+            masonry         : columnWidth : 10
+            # itemPositionDataEnabled : true
             # animationEngine : 'jquery'
-            itemPositionDataEnabled : true
-        
+    
+    addOne: ->
+        # console.log "#this.addOne!"
+        view = @__super__.addOne ...
+        view.on 'change:collapse render', @render
+        view
     
 # }}}
 
@@ -141,6 +151,7 @@ GraphView = exports.GraphView = BaseView.extend do # {{{
     initialize : (o={}) ->
         @model or= new GraphModel
         BaseView::initialize ...
+        # console.log "#this.initialize!"
         
         @model.on 'change',  @render, this
         @model.on 'destroy', @remove, this
@@ -172,8 +183,6 @@ GraphView = exports.GraphView = BaseView.extend do # {{{
             options.values()
     
     render: ->
-        # BaseView::render ...
-        
         options = @chartOptions()
         w = options.width  or= @scaffold.get 'width'  .getValue() or 480
         h = options.height or= @scaffold.get 'height' .getValue() or 320
diff --git a/lib/scaffold/index.co b/lib/scaffold/index.co
new file mode 100644 (file)
index 0000000..03f7aa7
--- /dev/null
@@ -0,0 +1,3 @@
+models = require 'kraken/scaffold/model'
+views  = require 'kraken/scaffold/view'
+exports import models import views
diff --git a/lib/scaffold/model.co b/lib/scaffold/model.co
new file mode 100644 (file)
index 0000000..422de92
--- /dev/null
@@ -0,0 +1,88 @@
+_  = require 'kraken/underscore'
+op = require 'kraken/util/op'
+
+
+
+### Scaffold Models
+
+Field = exports.Field = Backbone.Model.extend do # {{{
+    ctorName : 'Field'
+    idAttribute : 'name'
+    
+    initialize: ->
+        @set 'value', @get('default'), {+silent} if not @has 'value'
+        # console.log "#this.initialize!"
+    
+    defaults: ->
+        {
+            name     : ''
+            type     : 'String'
+            default  : null
+            desc     : ''
+            category : 'General'
+            include  : 'diff'
+            tags     : []
+            examples : []
+        }
+    
+    getParser: (type) ->
+        type or= @get 'type'
+        t = _ type.toLowerCase()
+        
+        parser = op.toStr
+        if t.startsWith 'integer'
+            parser = op.toInt
+        if t.startsWith 'float'
+            parser = op.toFloat
+        if t.startsWith 'boolean'
+            parser = op.toBool
+        if t.startsWith 'object' or t.startsWith 'array'
+            parser = op.toObject
+        if t.startsWith 'function'
+            parser = (fn) -> eval "(#fn)"
+        
+        # TODO: handle 'or' by returning an array of parsers
+        parser
+    
+    
+    getValue: (def) ->
+        @getParser() @get 'value', def
+    
+    setValue: (v, options) ->
+        def = @get 'default'
+        if not v and def == null
+            val = null
+        else
+            val = @getParser()(v)
+        @set 'value', val, options
+    
+    clearValue: ->
+        @set 'value', @get('default')
+    
+    isDefault: ->
+        @get('value') is @get('default')
+    
+    toJSON: ->
+        {id:@id} import do
+            _.clone(@attributes) import { value:@getValue(), def:@get('default') }
+    
+    toString: -> "(#{@id}: #{@get 'value'})"
+# }}}
+
+
+FieldList = exports.FieldList = Backbone.Collection.extend do # {{{
+    ctorName : 'FieldList'
+    model : Field
+    
+    /**
+     * Collects a map of fields to their values, excluding those set to `null` or their default.
+     * @returns {Object}
+     */
+    values: ->
+        _.synthesize do
+            @models.filter -> not it.isDefault()
+            -> [ it.get('name'), it.getValue() ]
+    
+    toString: -> "#{@ctorName}(length=#{@length})"
+# }}}
+
similarity index 61%
rename from lib/scaffold.co
rename to lib/scaffold/view.co
index 474ff3c..332e289 100644 (file)
@@ -1,92 +1,7 @@
 _  = require 'kraken/underscore'
 op = require 'kraken/util/op'
-
-
-
-### Scaffold Models
-
-Field = exports.Field = Backbone.Model.extend do # {{{
-    ctorName : 'Field'
-    idAttribute : 'name'
-    
-    initialize: ->
-        @set 'value', @get('default'), {+silent} if not @has 'value'
-        # console.log "#this.initialize!"
-    
-    defaults: ->
-        {
-            name     : ''
-            type     : 'String'
-            default  : null
-            desc     : ''
-            category : 'General'
-            include  : 'diff'
-            tags     : []
-            examples : []
-        }
-    
-    getParser: (type) ->
-        type or= @get 'type'
-        t = _ type.toLowerCase()
-        
-        parser = op.toStr
-        if t.startsWith 'integer'
-            parser = op.toInt
-        if t.startsWith 'float'
-            parser = op.toFloat
-        if t.startsWith 'boolean'
-            parser = op.toBool
-        if t.startsWith 'object' or t.startsWith 'array'
-            parser = op.toObject
-        if t.startsWith 'function'
-            parser = (fn) -> eval "(#fn)"
-        
-        # TODO: handle 'or' by returning an array of parsers
-        parser
-    
-    
-    getValue: (def) ->
-        @getParser() @get 'value', def
-    
-    setValue: (v, options) ->
-        def = @get 'default'
-        if not v and def == null
-            val = null
-        else
-            val = @getParser()(v)
-        @set 'value', val, options
-    
-    clearValue: ->
-        @set 'value', @get('default')
-    
-    isDefault: ->
-        @get('value') is @get('default')
-    
-    toJSON: ->
-        {id:@id} import do
-            _.clone(@attributes) import { value:@getValue(), def:@get('default') }
-    
-    toString: -> "(#{@id}: #{@get 'value'})"
-# }}}
-
-
-FieldList = exports.FieldList = Backbone.Collection.extend do # {{{
-    ctorName : 'FieldList'
-    model : Field
-    
-    /**
-     * Collects a map of fields to their values, excluding those set to `null` or their default.
-     * @returns {Object}
-     */
-    values: ->
-        _.synthesize do
-            @models.filter -> not it.isDefault()
-            -> [ it.get('name'), it.getValue() ]
-    
-    toString: -> "#{@ctorName}(length=#{@length})"
-# }}}
-
-
+{ Field, FieldList,
+}  = require 'kraken/scaffold/model'
 
 
 ### Views
@@ -97,9 +12,8 @@ BaseView = exports.BaseView = Backbone.View.extend do # {{{
     
     
     initialize: ->
-        _.bindAll this, ...@__bind__
+        _.bindAll this, ...@__bind__ if @__bind__.length
         @__super__ = @constructor.__super__
-        Backbone.View::initialize ...
         
         @model.view = this
         @$el.data { @model, view:this }
@@ -127,6 +41,8 @@ BaseView = exports.BaseView = Backbone.View.extend do # {{{
     
     render: ->
         @build()
+        @trigger 'render', this
+        this
     
     hide   : -> @$el.hide();      this
     show   : -> @$el.show();      this
@@ -170,11 +86,11 @@ FieldView = exports.FieldView = BaseView.extend do # {{{
         val     = @$el.find('.value').val()
         current = @model.get 'value'
         return if val is current
-        console.log "#this.onUIChange( #current -> #val )"
+        # console.log "#this.onUIChange( #current -> #val )"
         @model.setValue val, {+silent}
     
     render: ->
-        return @remove() if @model.get 'hidden'
+        return @remove() if @model.get 'hidden', false
         return BaseView::render ... if @template
         
         name  = @model.get 'name'
@@ -219,12 +135,15 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{
     
     
     addOne: (field) ->
+        # console.log "[S] #this.addOne!", @__super__
         _.remove @subviews, field.view if field.view
         
         SubviewType = @subviewType
         view = new SubviewType model:field
         @subviews.push view
-        @$el.append view.render().el
+        @$el.append view.render().el unless field.get 'hidden'
+        
+        @render()
         view
     
     addAll: ->
diff --git a/lib/template/browser-helpers.jade b/lib/template/browser-helpers.jade
new file mode 100644 (file)
index 0000000..db4034f
--- /dev/null
@@ -0,0 +1,3 @@
+- window.Markdown || (window.Markdown = new (require('showdown').Showdown).converter());
+- (jade.filters || (jade.filters = {})).markdown = function (s, name){ return s && Markdown.makeHtml(s.replace(/\n/g, '\n\n')); };
+
index 0da61a1..01fae16 100644 (file)
@@ -1,25 +1,27 @@
-- var _            = require('kraken/underscore');
+include browser-helpers
+- var option_id    = _.domize('option', id);
 - var value_id     = _.domize('value', id);
 - var category_cls = _.domize('category', category);
 - var tags_cls     = tags.map(_.domize('tag')).join(' ');
 - var type_cls     = _.domize('type', type); 
 
-.field.option(id=_.domize('option', id), class="#{category_cls} #{tags_cls}")
+.field.option(id=option_id, class="#{category_cls} #{tags_cls}")
+    a.close &times;
+    
     h3.shortname #{_.shortname(name)}
     label.name(for=value_id) #{name}
-    input.value(type="text", id=value_id, name=name, value=value)
+    input.value(type="text", id=value_id, name=name, class=type_cls, value=value)
     
     .type(class=type_cls) #{type}
-    .default(class=type_cls) #{def}
+    .default(class=type_cls, title="Default: #{def} (#{type})") #{def}
     
     .desc
-        #{desc}
-        //- != jade.filters.markdown(desc)
+        != (jade.filters || {}).markdown(desc)
     
-    .tags: ul
+    .tags(data-toggle="collapse", data-target="ul"): ul.collapse
         for tag in tags
             li.tag(class=_.domize('tag', tag)) #{tag}
-    .examples: ul
+    .examples(data-toggle="collapse", data-target="ul"): ul.collapse
         for example in examples
             li.example
                 a(href="http://dygraphs.com/tests/#{example}.html", target="_blank") #{example}
index 29b193a..7dd5df7 100644 (file)
@@ -7,6 +7,6 @@ section.graph(id=id)
         form.details
             label.name(for='#{id}_name') Name
     
-    fieldset
+    fieldset.options
         legend Graph Options
         
diff --git a/lib/underscore/functions.co b/lib/underscore/functions.co
new file mode 100644 (file)
index 0000000..0dc8bf6
--- /dev/null
@@ -0,0 +1,248 @@
+_ = require 'underscore'
+
+
+slice       = [].slice
+hasOwn      = {}.hasOwnProperty
+objToString = {}.toString
+
+toArray = _.toArray
+
+
+
+decorate = (fn) ->
+    if not fn.__decorated__
+        for name of _pet.FUNCTION_METHODS
+            m = _[name]
+            fn[name] = m.__methodized__ or methodize m
+        fn.__decorated__ = true
+    return fn
+
+methodize = (fn) ->
+    m = fn.__methodized__
+    return m if m
+    
+    g = fn.__genericized__
+    return g.__wraps__ if g and g.__wraps__
+    
+    m = fn.__methodized__ = (args...) ->
+        args.unshift this
+        return fn.apply this, args
+    
+    m.__wraps__ = fn
+    return decorate m
+
+
+
+_pet = module.exports = \
+    function pet (o, start=0, end=undefined) ->
+        if _.isArguments o
+            o = _.toArray o, start, end
+        
+        return decorate o if typeof o is 'function'
+        return _ o
+
+# function methods to be attached on call to _(fn)
+_pet.FUNCTION_METHODS = [
+    'bind', 'bindAll', 'memoize', 
+    'delay', 'defer', 'throttle', 'debounce', 'once', 'after', 
+    'wrap', 'compose',
+    'unwrap', 'partial', 'curry', 'flip', 'methodize', 'aritize', 'limit'
+]
+
+
+class2name = "Boolean Number String Function Array Date RegExp Object"
+        .split(" ")
+        .reduce ((class2name, name) ->
+            class2name[ "[object "+name+"]" ] = name
+            return class2name), {}
+
+
+## Objects
+_.mixin
+    
+    has: (o, v) ->
+        vals = if _.isArray(o) then o else _.values(o)
+        return vals.indexOf(v) is not -1
+    
+    remove: (o, vs...) ->
+        if _.isArray(o)
+            _.each vs, (v) ->
+                idx = o.indexOf v
+                if idx is not -1
+                    o.splice idx, 1
+        else
+            _.each o, (v, k) ->
+                if vs.indexOf(v) != -1
+                    delete o[k]
+        return o
+    
+    set: (o, key, value, def) ->
+        if o and key? and (value? or def?)
+            o[key] = value ? def
+        return o
+    
+    attr: (o, key, value, def) ->
+        return o if not o or key is undefined
+        
+        if _.isPlainObject key
+            return _.extend o, key
+        
+        if (value ? def) is not undefined
+            return _.set o, key, value, def
+        
+        return o[key]
+    
+    
+
+## Types
+_.mixin
+    
+    basicTypeName: (o) ->
+        return if o is null then "null" else (class2name[objToString.call(o)] || "Object")
+    
+    isWindow: (o) ->
+        return o and typeof o is "object" and "setInterval" of o
+    
+    isPlainObject: (o) ->
+        # Must be an Object.
+        # Because of IE, we also have to check the presence of the constructor property.
+        # Make sure that DOM nodes and window objects don't pass through, as well
+        if not o or basicTypeName(o) is not "Object" or o.nodeType or _.isWindow(o)
+            return false
+        
+        # Not own constructor property? must be Object
+        C = o.constructor
+        if C and not hasOwn.call(o, "constructor") and not hasOwn.call(C.prototype, "isPrototypeOf")
+            return false
+        
+        # Own properties are enumerated firstly, so to speed up,
+        # if last one is own, then all properties are own.
+        for key in o
+            ; # semicolon **on new line** is required by coffeescript to denote empty statement.
+        
+        return key is undefined or hasOwn.call(o, key)
+    
+
+## Arrays
+_.mixin
+    
+    toArray: (iterable, start=0, end=undefined) ->
+        _.slice toArray(iterable), start, end
+    
+    flatten: (A) ->
+        _.reduce do
+            slice.call(arguments)
+            (flat, v) ->
+                flat.concat( if _.isArray v then _.reduce(v, arguments.callee, []) else v )
+            []
+    
+
+
+## Functions
+_ofArity = _.memoize(
+    (n, limit) ->
+        args       = ( '$'+i for i from 0 til n ).join(',')
+        name       = ( if limit then 'limited' else 'artized' )
+        apply_with = ( if limit then "[].slice.call(arguments, 0, #{n})" else 'arguments' )
+        return eval "
+            (function #{name}(fn){
+                var _fn = function(#{args}){ return fn.apply(this, #{apply_with}); };
+                _fn.__wraps__ = fn;
+                return _(_fn);
+            })"
+    )
+
+_.mixin
+    methodize: methodize
+    
+    unwrap: (fn) ->
+        (fn and _.isFunction(fn) and _.unwrap(fn.__wraps__)) or fn
+    
+    
+    partial: (fn, args...) ->
+        partially =  ->
+            fn.apply this, args.concat(slice.call(arguments))
+        partially.__wraps__ = fn
+        return _ partially
+    
+    
+    genericize: (fn) ->
+        g = fn.__genericized__
+        return g if g
+        
+        m = fn.__methodized__
+        return m.__wraps__ if m and m.__wraps__
+
+        g = fn.__genericized__ = (args...) ->
+                fn.apply args.shift(), args
+        
+        g.__wraps__ = fn
+        return _ g
+    
+    
+    curry: (fn, args...) ->
+        if not _.isFunction fn
+            return fn
+        
+        if fn.__curried__
+            return fn.apply this, args
+        
+        L = fn.length or _.unwrap(fn).length
+        if args.length >= L
+            return fn.apply this, args
+        
+        curried =  ->
+            _args = args.concat slice.call(arguments)
+            if _args.length >= L
+                return fn.apply this, _args
+            _args.unshift fn
+            return _.curry.apply this, _args
+        
+        curried.__wraps__ = fn
+        curried.__curried__ = args
+        return _ curried
+    
+    
+    flip: (fn) ->
+        f = fn.__flipped__
+        return f if f
+        
+        f = fn.__flipped__ = \
+            flipped =  ->
+                args    = arguments
+                hd      = args[0]
+                args[0] = args[1]
+                args[1] = hd
+                return fn.apply this, args
+        
+        f.__wraps__ = fn
+        return _ f
+    
+    
+    aritize: (fn, n) ->
+        return fn if fn.length is n
+        
+        cache = fn.__aritized__
+        if not cache
+            cache = fn.__aritized__ = {}
+        else if cache[n]
+            return cache[n]
+        
+        return ( cache[n] = _ofArity(n, false)(fn) )
+    
+    
+    limit: (fn, n) ->
+        cache = fn.__limited__
+        if not cache
+            cache = fn.__limited__ = {}
+        else if cache[n]
+            return cache[n]
+        
+        return ( cache[n] = _ofArity(n, true)(fn) )
+    
+    
+    
+
+
+
+_.extend _pet, _
index 92e3213..7f7931d 100644 (file)
     default: null
     desc: A function which parses x-values (i.e. the dependent series). Must return a number, even when
         the values are dates. In this case, millis since epoch are used. This is used primarily for parsing
-        CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for
+        CSV data.
+        
+        * Dygraphs is slightly more accepting in the dates which it will parse. See code for
         details.
     category: CSV parsing
     tags:
     type: Integer
     default: null
     desc: 'Prefer axes { x: { pixelsPerLabel } }'
+    hidden: true
     category: Deprecated
     tags:
     - deprecated
     type: Integer
     default: null
     desc: 'Prefer axes: { y: { pixelsPerLabel } }'
+    hidden: true
     category: Deprecated
     tags:
     - deprecated
     type: function
     default: null
     desc: 'Prefer axes { x: { axisLabelFormatter } }'
+    hidden: true
     category: Deprecated
     tags:
     - deprecated
     type: function
     default: null
     desc: 'Prefer axes: { x: { valueFormatter } }'
+    hidden: true
     category: Deprecated
     tags:
     - deprecated
     type: function
     default: null
     desc: 'Prefer axes: { y: { axisLabelFormatter } }'
+    hidden: true
     category: Deprecated
     tags:
     - deprecated
     type: function
     default: null
     desc: 'Prefer axes: { y: { valueFormatter } }'
+    hidden: true
     category: Deprecated
     tags:
     - deprecated
diff --git a/static/vendor/showdown.js b/static/vendor/showdown.js
new file mode 100644 (file)
index 0000000..43920d9
--- /dev/null
@@ -0,0 +1,1302 @@
+//\r
+// showdown.js -- A javascript port of Markdown.\r
+//\r
+// Copyright (c) 2007 John Fraser.\r
+//\r
+// Original Markdown Copyright (c) 2004-2005 John Gruber\r
+//   <http://daringfireball.net/projects/markdown/>\r
+//\r
+// Redistributable under a BSD-style open source license.\r
+// See license.txt for more information.\r
+//\r
+// The full source distribution is at:\r
+//\r
+//                             A A L\r
+//                             T C A\r
+//                             T K B\r
+//\r
+//   <http://www.attacklab.net/>\r
+//\r
+\r
+//\r
+// Wherever possible, Showdown is a straight, line-by-line port\r
+// of the Perl version of Markdown.\r
+//\r
+// This is not a normal parser design; it's basically just a\r
+// series of string substitutions.  It's hard to read and\r
+// maintain this way,  but keeping Showdown close to the original\r
+// design makes it easier to port new features.\r
+//\r
+// More importantly, Showdown behaves like markdown.pl in most\r
+// edge cases.  So web applications can do client-side preview\r
+// in Javascript, and then build identical HTML on the server.\r
+//\r
+// This port needs the new RegExp functionality of ECMA 262,\r
+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers\r
+// should do fine.  Even with the new regular expression features,\r
+// We do a lot of work to emulate Perl's regex functionality.\r
+// The tricky changes in this file mostly have the "attacklab:"\r
+// label.  Major or self-explanatory changes don't.\r
+//\r
+// Smart diff tools like Araxis Merge will be able to match up\r
+// this file with markdown.pl in a useful way.  A little tweaking\r
+// helps: in a copy of markdown.pl, replace "#" with "//" and\r
+// replace "$text" with "text".  Be sure to ignore whitespace\r
+// and line endings.\r
+//\r
+\r
+\r
+//\r
+// Showdown usage:\r
+//\r
+//   var text = "Markdown *rocks*.";\r
+//\r
+//   var converter = new Showdown.converter();\r
+//   var html = converter.makeHtml(text);\r
+//\r
+//   alert(html);\r
+//\r