-# ChartType
+# Chart
-ChartType is an abstraction on charting libraries to allow them to plug into the GraphKit framework.
+Chart is an abstraction on charting libraries to allow them to plug into the GraphKit framework.
### Charting
-- Finish ChartType & Chart wrapper
+- Finish Chart & Chart wrapper
- Benchmark line on the graph
- Per-series styles
- Meta-metrics:
/**
* @class Base mixin class. Extend this to create a new mixin, attaching the
- * donor methods as you would instance methods. Your constructor must delegate
- * to its superclass, passing itself as the context.
- *
- * Then, to mingle your mixin with another class or object, invoke the mixin
- * *without* the `new` operator:
+ * donor methods as you would instance methods.
+ *
+ * To mingle your mixin with another class or object:
*
* class MyMixin extends Mixin
- * -> return Mixin.call MyMixin, it
* foo: -> "foo!"
*
* # Mix into an object...
- * o = MyMixin { bar:1 }
+ * o = MyMixin.mix { bar:1 }
*
* # Mix into a Coco class...
* class Bar
- * MyMixin this
+ * MyMixin.mix this
* bar : 1
*
*/
class exports.Mixin
/**
- * Mixes this mixin into If the target is not a class, a new object will be
- * returned which inherits from the mixin.
- * @constructor
+ * Mixes this mixin into the target. If `target` is not a class, a new
+ * object will be returned which inherits from the mixin.
*/
- (target) ->
- return unless target
+ @mix = (target) ->
+ return that unless target
MixinClass = Mixin
MixinClass = @constructor if this instanceof Mixin
else
target = _.clone(MixinClass::) import target
- return target
+ (target.__mixins__ or= []).push MixinClass
+ target
+ /**
+ * Coco metaprogramming hook to propagate class properties and methods.
+ */
+ @extended = (SubClass) ->
+ SuperClass = this
+ for own k, v in SuperClass
+ SubClass[k] = v unless SubClass[k]
+ SubClass
-{ EventEmitter,
-} = require 'events'
+moment = require 'moment'
+Backbone = require 'backbone'
{ _, op,
} = require 'kraken/util'
+{ ReadyEmitter,
+} = require 'kraken/util/event'
{ Parsers, ParserMixin,
} = require 'kraken/util/parser'
/**
- * @class Specification for an option.
- */
-class exports.ChartTypeOption
- SPEC_KEYS : <[ name type default desc tags examples ]>
-
- name : null
- type : 'String'
- default : null
- desc : ''
- tags : null
- examples : null
-
-
- (@chartType, @spec) ->
- throw new Error('Each ChartTypeOption requires a name!') unless @spec.name
-
- for k of @SPEC_KEYS
- v = @spec[k]
- @[k] = v if v?
- @tags or= []
- @parseValue = @chartType.getParser @type
-
- parseValue : Parsers.parseString
-
- isDefault: (v) ->
- # @default is v
- _.isEqual @default, v
-
- toString: -> "(#{@name}: #{@type})"
-
-
-
-/**
* Map of known libraries by name.
* @type Object
*/
/**
* @class Abstraction of a chart-type or charting library, encapsulating its
- * logic and options.
+ * logic and options. In addition, a `ChartType` also mediates the
+ * transformation of the domain-specific data types (the model and its view)
+ * with its specific needs.
+ *
+ * `ChartType`s mix in `ParserMixin`: when implementing a `ChartType`, you can
+ * add or supplement parsers merely by subclassing and overriding the
+ * corresponding `parseXXX` method (such as `parseArray` or `parseDate`).
+ *
+ * @extends EventEmitter
+ * @borrows ParserMixin
*/
-class exports.ChartType extends EventEmitter
+class exports.ChartType extends ReadyEmitter
+
+ ### Class Methods
+
/**
- * Ordered ChartTypeOption objects.
- * @type ChartTypeOption[]
+ * Register a new chart type.
*/
- options_ordered : null
+ @register = (Subclass) ->
+ KNOWN_CHART_TYPES[ Subclass::typeName ] = Subclass
+
+ /**
+ * Look up a `ChartType` by `typeName`.
+ */
+ @lookup = (name) ->
+ name = name.get('chartType') if name instanceof Backbone.Model
+ KNOWN_CHART_TYPES[name]
+
+ /**
+ * Look up a chart type by name, returning a new instance
+ * with the given model (and, optionally, view).
+ * @returns {ChartType}
+ */
+ @create = (model, view) ->
+ return null unless Type = @lookup model
+ new Type model, view
+
+
+ ### Class Properties
+ /*
+ * These are "class properties": each is set on the prototype at the class-level,
+ * and the reference is therefore shared by all instances. It is expected you
+ * will not modify this on the instance-level.
+ */
+
+ /**
+ * URL for the Chart Spec JSON. Loaded once, the first time an instance of
+ * that class is created.
+ * @type String
+ * @readonly
+ */
+ CHART_SPEC_URL : null
+
+ /**
+ * Chart-type name.
+ * @type String
+ * @readonly
+ */
+ typeName: null
/**
- * Map of option name to ChartTypeOption objects.
- * @type { name:ChartTypeOption, ... }
+ * Map of option name to ChartOption objects.
+ * @type { name:ChartOption, ... }
+ * @readonly
*/
options : null
+ /**
+ * Ordered ChartOption objects.
+ *
+ * This is a "class-property": it is set on the prototype at the class-level,
+ * and the reference is shared by all instances. It is expected you will not
+ * modify that instance.
+ *
+ * @type ChartOption[]
+ * @readonly
+ */
+ options_ordered : null
+
+ /**
+ * Hash of role-names to the selector which, when applied to the view,
+ * returns the correct element.
+ * @type Object
+ */
+ roles :
+ viewport : '.viewport'
+
+ /**
+ * Whether the ChartType has loaded all its data and is ready.
+ * @type Boolean
+ */
+ ready: false
+
+
+
+ ### Instance properties
+
+ /**
+ * Model to be rendered as a chart.
+ * @type Backbone.Model
+ */
+ model : null
+
+ /**
+ * View to render the chart into.
+ * @type Backbone.View
+ */
+ view : null
+
+ /**
+ * Last chart rendered by this ChartType.
+ * @private
+ */
+ chart: null
+
+
/**
* @constructor
- * @param {String} name Library name.
- * @param {Array} options List of options objects, each specifying the
- * name, type, default, description (etc) of a chart option.
*/
- (@name, options) ->
- @options_ordered = _.map options, (opt) ~> new ChartTypeOption this, opt
- @options = _.synthesize @options_ordered, -> [it.name, it]
- ChartType.register this
+ (@model, @view) ->
+ @roles or= {}
+ _.bindAll this, ...@__bind__ # TODO: roll up MRO
+ @loadSpec() unless @ready
+
+
+ # Builder Pattern
+ withModel : (@model) -> this
+ withView : (@view) -> this
+
/**
- * @returns {ChartTypeOption} Get an option's spec by name.
+ * Load the corresponding chart specification, which includes
+ * info about valid options, along with their types and defaults.
*/
- get: (name, def) ->
- @options[name] or def
+ loadSpec: ->
+ return this if @ready
+ proto = @constructor::
+ jQuery.ajax do
+ url : @CHART_SPEC_URL
+ success : (spec) ~>
+ proto.options_ordered = spec
+ proto.options = _.synthesize spec, -> [it.name, it]
+ proto.ready = true
+ @emit 'ready', this
+ error: ~> console.error "Error loading #{@typeName} spec! #it"
+ this
+
/**
- * @returns {Array} List of values found at the given attr on each
- * option spec object.
+ * @returns {ChartOption} Get an option's spec by name.
*/
- pluck: (attr) ->
- _.pluck @spec, attr
+ getOption: (name, def) ->
+ @options[name] or def
+
/**
* @returns {Object} An object, mapping from option.name to the
* result of the supplied function.
*/
map: (fn, context=this) ->
- _.synthesize @spec, ~> [it.name, fn.call(context, it, it.name, this)]
+ _.synthesize @options, ~> [it.name, fn.call(context, it, it.name, this)]
/**
- * @returns {Boolean} Whether the supplied value is the same as
- * the default value for the given key.
+ * @param {String} attr Attribute to look up on each options object.
+ * @returns {Object} Map from name to the value found at the given attr.
*/
- isDefault: (name, value) ->
- @get name .isDefault value
+ pluck: (attr) ->
+ @map -> it[attr]
- serialize: (v, k) ->
- # if v!?
- # v = ''
- if _.isBoolean v
- v = Number v
- else if _.isObject v
- v = JSON.stringify v
- String v
+ /**
+ * @returns {Boolean} Whether the supplied value is the same as
+ * the default value for the given key.
+ */
+ isDefault: (name, value) ->
+ _.isEqual @getOption(name).default, value
- ### Parsers
+ ### }}}
+ ### Parsers & Serialization {{{
/**
* When implementing a ChartType, you can add or override parsers
* merely by subclassing.
+ * @borrows ParserMixin
*/
- ParserMixin this
+ ParserMixin.mix this
+ /**
+ * @returns {Function} Parser for the given option name.
+ */
getParserFor: (name) ->
- @getParser @get(name).type
+ @getParser @getOption(name).type
+ /**
+ * Parses a single serialized option value into its proper type.
+ *
+ * @param {String} name Option-name of the value being parsed.
+ * @param {String} value Value to parse.
+ * @returns {*} Parsed value.
+ */
parseOption: (name, value) ->
@getParserFor(name)(value)
+ /**
+ * Parses options using `parseOption(name, value)`.
+ *
+ * @param {Object} options Options to parse.
+ * @returns {Object} Parsed options.
+ */
parseOptions: (options) ->
out = {}
for k, v in options
out
+ /**
+ * Serializes option-value to a String.
+ *
+ * @param {*} v Value to serialize.
+ * @param {String} k Option-name of the given value.
+ * @returns {String} The serialized value
+ */
+ serialize: (v, k) ->
+ # if v!?
+ # v = ''
+ if _.isBoolean v
+ v = Number v
+ else if _.isObject v
+ v = JSON.stringify v
+ String v
+
- ### Class Methods
+ ### }}}
+ ### Formatters {{{
/**
- * Register a new chart type.
+ * Formats a date for display on an axis: `MM/YYYY`
+ * @param {Date} d Date to format.
+ * @returns {String}
*/
- @register = (chartType) ->
- KNOWN_CHART_TYPES[chartType.name] = chartType
+ axisDateFormatter: (d) ->
+ moment(d).format 'MM/YYYY'
/**
- * Look up a chart type by name.
+ * Formats a date for display in the legend: `DD MMM YYYY`
+ * @param {Date} d Date to format.
+ * @returns {String}
*/
- @lookup = (name) ->
- KNOWN_CHART_TYPES[name]
+ dateFormatter: (d) ->
+ moment(d).format 'DD MMM YYYY'
+
+ /**
+ * Formats a number for display, first dividing by the greatest suffix
+ * of {B = Billions, M = Millions, K = Thousands} that results in a
+ * absolute value greater than 0, and then rounding to `digits` using
+ * `result.toFixed(digits)`.
+ *
+ * @param {Number} n Number to format.
+ * @param {Number} [digits=2] Number of digits after the decimal to always display.
+ * @returns {String} Formatted number.
+ */
+ numberFormatter: (n, digits=2) ->
+ for [suffix, d] of [['B', 1000000000], ['M', 1000000], ['K', 1000], ['', NaN]]
+ break if isNaN d
+ if n >= d
+ n = n / d
+ break
+ s = n.toFixed(digits)
+ parts = s.split '.'
+ whole = _.rchop parts[0], 3 .join ','
+ fraction = '.' + parts.slice(1).join '.'
+ { n, digits, whole, fraction, suffix }
+
+
+ ### }}}
+ ### Rendering {{{
+
+ /**
+ * Finds the element in the view which plays the given role in the chart.
+ * Canonically, all charts have a "viewport" element. Other roles might
+ * include a "legend" element, or several "axis" elements.
+ *
+ * Default implementation looks up a selector in the `roles` hash, and if
+ * found, queries the view for matching children.
+ *
+ * @param {String} role Name of the role to look up.
+ * @returns {jQuery|null} $-wrapped DOM element.
+ */
+ getElementsForRole: (role) ->
+ return null unless @view
+ if @roles[role]
+ @view.$ that
+ else
+ null
+
+
+ /**
+ * Transform/extract the data for this chart from the model. Default
+ * implementation calls `model.getData()`.
+ *
+ * @returns {*} Data object for the chart.
+ */
+ getData: ->
+ @model.getData()
+
+ /**
+ * Map from option-name to default value. Note that this reference will be
+ * modified by `.render()`.
+ *
+ * @returns {Object} Default options.
+ */
+ getDefaultOptions: ->
+ @pluck 'default'
+
+
+ /**
+ * Transforms domain data and applies it to the chart library to
+ * render or update the corresponding chart.
+ *
+ * @returns {Chart}
+ */
+ render: ->
+ data = @getData()
+ options = @getDefaultOptions() import @transform @model, @view
+ viewport = @getElementsForRole 'viewport'
+ @lastChart = @renderChart data, viewport, options, @chart
+
+
+ /**
+ * Transforms the domain objects into a hash of derived values using
+ * chart-type-specific keys.
+ *
+ * @abstract
+ * @returns {Object} The derived data.
+ */
+ transform: ->
+ ...
+
+
+ /**
+ * Called to render the chart.
+ *
+ * @abstract
+ * @returns {Chart}
+ */
+ renderChart: (data, viewport, options, lastChart) ->
+ ...
+
+ ### }}}
+
+#
+# /**
+# * @class Specification for an option.
+# */
+# class exports.ChartOption
+# SPEC_KEYS : <[ name type default desc tags examples ]>
+#
+# name : null
+# type : 'String'
+# default : null
+# desc : ''
+# tags : null
+# examples : null
+#
+#
+# (@spec) ->
+# throw new Error('Each ChartOption requires a name!') unless @spec.name
+#
+# for k of @SPEC_KEYS
+# v = @spec[k]
+# @[k] = v if v?
+# @tags or= []
+#
+# isDefault: (v) ->
+# # @default is v
+# _.isEqual @default, v
+#
+# toString: -> "(#{@name}: #{@type})"
+#
+#
+#
_ = require 'kraken/util/underscore'
-{ ChartType, ChartTypeOption,
+{ ChartType,
} = require 'kraken/chart/chart-type'
class exports.DygraphsChartType extends ChartType
- name : 'dygraphs'
+ __bind__ : <[ dygNumberFormatter dygNumberFormatterHTML ]>
+ CHART_SPEC_URL : '/schema/dygraph.json'
+ typeName : 'dygraphs'
+ ChartType.register this
- (options) ->
- super 'dygraphs', options
- render: ->
- ...
+ /**
+ * Hash of role-names to the selector which, when applied to the view,
+ * returns the correct element.
+ * @type Object
+ */
+ roles :
+ viewport : '.viewport'
+ legend : '.graph-legend'
+
+
+ /**
+ * @constructor
+ */
+ -> super ...
+
+
+
+ ### Formatters {{{
+
+ # XXX: Dygraphs-specific
+ makeAxisFormatter: (fmttr) ->
+ (n, granularity, opts, g) -> fmttr n, opts, g
+
+ # XXX: Dygraphs-specific
+ dygAxisDateFormatter: (n, granularity, opts, g) ->
+ moment(n).format 'MM/YYYY'
+
+ # XXX: Dygraphs-specific
+ dygDateFormatter: (n, opts, g) ->
+ moment(n).format 'DD MMM YYYY'
+
+ # XXX: Dygraphs-specific
+ dygNumberFormatter: (n, opts, g) ->
+ digits = if typeof opts('digitsAfterDecimal') is 'number' then that else 2
+ { whole, fraction, suffix } = @numberFormatter n, digits
+ "#whole#fraction#suffix"
+
+ # XXX: Dygraphs-specific
+ dygNumberFormatterHTML: (n, opts, g) ->
+ digits = if typeof opts('digitsAfterDecimal') is 'number' then that else 2
+ # digits = opts('digitsAfterDecimal') ? 2
+ { whole, fraction, suffix } = @numberFormatter n, digits
+ # coco will trim the whitespace
+ "<span class='value'>
+ <span class='whole'>#whole</span>
+ <span class='fraction'>#fraction</span>
+ <span class='suffix'>#suffix</span>
+ </span>"
+
+
+ ### }}}
+ ### Rendering {{{
+
+ /**
+ * Determines chart viewport size.
+ * @return { width, height }
+ */
+ determineSize: ->
+ modelW = width = @model.get 'width'
+ modelH = height = @model.get 'height'
+ return { width, height } unless @view.ready and width and height
+
+ viewport = @getElementsForRole 'viewport'
+ legend = @getElementsForRole 'legend'
+
+ if width is 'auto'
+ # Remove old style, as it confuses dygraph after options update
+ delete viewport.prop('style').width
+ vpWidth = viewport.innerWidth() or 300
+ legendW = legend.outerWidth() or 228
+ width = vpWidth - legendW - 10 - (vpWidth - legend.position().left - legendW)
+ width ?= modelW
+
+ if height is 'auto'
+ # Remove old style, as it confuses dygraph after options update
+ delete viewport.prop('style').height
+ height = viewport.innerHeight() or 320
+ height ?= modelH
+
+ { width, height }
+
+
+ resizeViewport: ->
+ @getElementsForRole 'viewport' .css @determineSize()
+ this
+
+
+ /**
+ * Transforms the domain objects into a hash of derived values using
+ * chart-type-specific keys.
+ * @returns {Object} The derived chart options.
+ */
+ transform: ->
+ dataset = @model.dataset
+ options = @view.chartOptions() import @determineSize()
+ options import do
+ colors : dataset.getColors()
+ labels : dataset.getLabels()
+ labelsDiv : @getElementsForRole 'legend' .0
+ valueFormatter : @dygNumberFormatterHTML
+ axes:
+ x:
+ axisLabelFormatter : @dygAxisDateFormatter
+ valueFormatter : @dygDateFormatter
+ y:
+ axisLabelFormatter : @makeAxisFormatter @dygNumberFormatter
+ valueFormatter : @dygNumberFormatterHTML
+
+
+ /**
+ * @returns {Dygraph} The Dygraph chart object.
+ */
+ renderChart: (data, viewport, options, lastChart) ->
+ @resizeViewport()
+
+ # console.log "#this.render!"
+ # _.dump options, 'options'
+
+ # Always rerender the chart to sidestep the case where we need
+ # to push defaults into Dygraphs to reset the current option state.
+ lastChart?.destroy()
+ new Dygraph viewport.0, data, options
+
+
+
+ ### }}}
+
+
-chart_type = require 'kraken/chart/chart-type'
+chart = require 'kraken/chart/chart-type'
dygraphs = require 'kraken/chart/dygraphs'
models = require 'kraken/chart/chart-option-model'
views = require 'kraken/chart/chart-option-view'
-exports import chart_type import dygraphs import models import views
+exports import chart import dygraphs import models import views
*/
exportChart: (evt) ->
# The following code is dygraph specific, thus should not
- # be implemented in this class. Rather in the Dygraphs ChartType-subclass.
+ # be implemented in this class. Rather in the Dygraphs Chart-subclass.
# The same is true for the 'renderChart' method above.
#
# The Dygraph.Export module is from http://cavorite.com/labs/js/dygraphs-export/
### }}}
### Rendering {{{
+ # TODO: refactor this to ChartType?
chartOptions: (values, opts) ->
# Handle @chartOptions(k, v, opts)
if arguments.length > 1 and typeof values is 'string'
@checkWaiting()
root.title = "#{@get 'name'} | GraphKit"
GraphEditView.__super__.render ...
- @renderChart()
# @updateURL()
@unwait()
@isRendering = false
@constructor.register this
@parents = new GraphList
-
- # TODO: Load on-demand
- @chartType = ChartType.lookup @get 'chartType'
+ @chartType = ChartType.create this
# Insert submodels in place of JSON
@dataset = new DataSet {id:@id, ...@get 'data'}
@triggerReady 'dataReady', 'data-ready'
this
+ getData: ->
+ @dataset.getData()
+
onDataSetChange: ->
console.log "#this.onDataSetChange!"
@set 'data', @dataset, {+silent}
__debounce__: <[ render ]>
tagName : 'section'
+ /**
+ * The chart type backing this graph.
+ * @type ChartType
+ */
+ chartType : null
+
+
constructor: function GraphView
BaseView ...
initialize : (o={}) ->
@model or= new Graph
@id = @graph_id = _.domize 'graph', (@model.id or @model.get('slug') or @model.cid)
+ @chartType = @model.chartType.withView this
GraphView.__super__.initialize ...
for name of @__debounce__
@wait()
Seq()
.seq_ (next) ~>
+ @chartType.once 'ready', next.ok
+ .seq_ (next) ~>
@model.once 'ready', next.ok .load()
.seq_ (next) ~>
@model.once 'data-ready', next.ok .loadData()
toTemplateLocals: ->
- attrs = _.clone @model.attributes
+ attrs = _.extend {}, @model.attributes
delete attrs.options
{ @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs
@wait()
@checkWaiting()
GraphView.__super__.render ...
- @renderChart()
+
+ # @renderChart()
+ @chart = @chartType.render()
+
@unwait()
@checkWaiting()
this
} = require 'kraken/util'
{ BaseView, BaseModel, BaseList,
} = require 'kraken/base'
-{ ChartType, DygraphsChartType,
+{ ChartType,
} = require 'kraken/chart'
{ DataSource, DataSourceList,
} = require 'kraken/dataset'
main = ->
# Set up Dygraph chart type spec
# TODO: load this on-demand
- dyglib = new DygraphsChartType CHART_OPTIONS_SPEC
+ # dyglib = new DygraphsChartType CHART_OPTIONS_SPEC
# Bind to URL changes
History.Adapter.bind window, 'statechange', ->
data = {}
# If we got querystring args, apply them to the graph
- if loc.split '?' .1
- data = _.uncollapseObject _.fromKV that.replace('#', '%23')
- data.parents = JSON.parse that if data.parents
- data.options = _.synthesize do
- data.options or {}
- (v, k) -> [ k, dyglib.parseOption(k,v) ]
+ # if loc.split '?' .1
+ # data = _.uncollapseObject _.fromKV that.replace('#', '%23')
+ # data.parents = JSON.parse that if data.parents
+ # data.options = _.synthesize do
+ # data.options or {}
+ # (v, k) -> [ k, dyglib.parseOption(k,v) ]
# Extract id from URL
if match = /\/graphs\/([^\/?]+)/i.exec loc
# Instantiate model & view
graph = root.graph = new Graph data, {+parse}
view = root.view = new GraphEditView do
- graph_spec : root.CHART_OPTIONS_SPEC # FIXME: necessary?
model : graph
$ '#content .inner' .append view.el
# Load data files
-Seq([ <[ CHART_OPTIONS_SPEC /schema/dygraph.json ]>
-])
-.parEach_ (next, [key, url]) ->
- jQuery.ajax do
- url : url,
- success : (res) ->
- root[key] = res
- next.ok()
- error : (err) -> console.error err
-.seq ->
- jQuery main
+# Seq([ <[ CHART_OPTIONS_SPEC /schema/dygraph.json ]>
+# ])
+# .parEach_ (next, [key, url]) ->
+# jQuery.ajax do
+# url : url,
+# success : (res) ->
+# root[key] = res
+# next.ok()
+# error : (err) -> console.error err
+# .seq ->
+# jQuery main
+jQuery main
# NODE_ENV = exports.NODE_ENV = if IS_PROD then 'prod' else if IS_TEST then 'test' else 'dev'
-sources = exports.sources = (modulesFile, node_env=NODE_ENV) ->
+SOURCES_ENV = if process.env.KRAKEN_FORCE_BUNDLES then 'prod' else NODE_ENV
+sources = exports.sources = (modulesFile, node_env=SOURCES_ENV) ->
# node_env = 'dev' if _.startsWith node_env, 'dev'
mods = yaml.load fs.readFileSync modulesFile, 'utf8'
modlist = (mods.all or []).concat (mods[node_env] or [])
/**
* Triggers the 'ready' event if it has not yet been triggered.
* Subsequent listeners added to this event will be auto-triggered.
+ * @param {Boolean} [force=false] Trigger the event even if already ready.
* @returns {this}
*/
- triggerReady: ->
- return this if @ready
+ triggerReady: (force) ->
+ return this if @ready and not force
@ready = true
@emit @readyEventName, this
this
/**
* Resets the 'ready' event to its non-triggered state, firing a
* 'ready-reset' event.
+ * @param {Boolean} [force=false] Trigger the event even if already reset.
* @returns {this}
*/
- resetReady: ->
- return this unless @ready
+ resetReady: (force) ->
+ return this unless @ready and not force
@ready = false
@emit "#{@readyEventName}-reset", this
this
return this if not callback
super ...
if @ready and -1 is not events.split(/\s+/).indexOf @readyEventName
- callback.call context, this
+ setTimeout ~> callback.call context, this
this
# - Pros: mixing in `parseXXX()` methods makes it easy to
# override in the target class.
# - Cons: `parse()` is a Backbone method, which bit me once
- # already, so conflicts aren't unlikely.
+ # already (hence `parseValue()`), so conflicts aren't unlikely.
#
# Other ideas:
# - Parsers live at `@__parsers__`, and each instance gets its own clone
- # - Parser lookup uses a Cascade from the object. (Why not just use prototype, tho?)
+ # -> Parser lookup uses a Cascade from the object. (Why not just use prototype, tho?)
parseValue: (v, type) ->
@getParser(type)(v)
* @extends BaseModel
* @borrows ParserMixin
*/
-ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin do
+ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin.mix do
constructor: function ParsingModel then BaseModel ...
* @extends BaseList
* @borrows ParserMixin
*/
-ParsingList = exports.ParsingList = BaseList.extend ParserMixin do
+ParsingList = exports.ParsingList = BaseList.extend ParserMixin.mix do
constructor: function ParsingList then BaseList ...
* @extends BaseView
* @borrows ParserMixin
*/
-ParsingView = exports.ParsingView = BaseView.extend ParserMixin do
+ParsingView = exports.ParsingView = BaseView.extend ParserMixin.mix do
constructor: function ParsingView then BaseView ...