From 2841065c3b9868b86ca3b33eb4269ee4e510bfbd Mon Sep 17 00:00:00 2001 From: dsc Date: Tue, 22 May 2012 04:09:33 -0700 Subject: [PATCH] Refactor of ChartType. --- lib/base/base-mixin.co | 33 ++-- lib/chart/chart-type.co | 411 +++++++++++++++++++++++++++++++-------- lib/chart/dygraphs.co | 139 +++++++++++++- lib/chart/index.co | 4 +- lib/graph/graph-display-view.co | 2 +- lib/graph/graph-edit-view.co | 2 +- lib/graph/graph-model.co | 7 +- lib/graph/graph-view.co | 17 ++- lib/main-edit.co | 40 ++-- lib/server/view-helpers.co | 3 +- lib/util/event/ready-emitter.co | 12 +- lib/util/parser.co | 10 +- 12 files changed, 541 insertions(+), 139 deletions(-) diff --git a/lib/base/base-mixin.co b/lib/base/base-mixin.co index 29a8581..c6764cb 100644 --- a/lib/base/base-mixin.co +++ b/lib/base/base-mixin.co @@ -145,34 +145,30 @@ BaseBackboneMixin = exports.BaseBackboneMixin = /** * @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 @@ -183,8 +179,17 @@ class exports.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 diff --git a/lib/chart/chart-type.co b/lib/chart/chart-type.co index 0831a29..68357c2 100644 --- a/lib/chart/chart-type.co +++ b/lib/chart/chart-type.co @@ -1,47 +1,16 @@ -{ 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 */ @@ -50,88 +19,220 @@ KNOWN_CHART_TYPES = exports.KNOWN_CHART_TYPES = {} /** * @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 @@ -139,22 +240,174 @@ class exports.ChartType extends EventEmitter 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})" +# +# +# diff --git a/lib/chart/dygraphs.co b/lib/chart/dygraphs.co index bdaada1..1702859 100644 --- a/lib/chart/dygraphs.co +++ b/lib/chart/dygraphs.co @@ -1,14 +1,141 @@ _ = 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 + " + #whole + #fraction + #suffix + " + + + ### }}} + ### 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 + + + + ### }}} + + diff --git a/lib/chart/index.co b/lib/chart/index.co index e25febb..8cc50ae 100644 --- a/lib/chart/index.co +++ b/lib/chart/index.co @@ -1,5 +1,5 @@ -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 diff --git a/lib/graph/graph-display-view.co b/lib/graph/graph-display-view.co index eb34859..3460149 100644 --- a/lib/graph/graph-display-view.co +++ b/lib/graph/graph-display-view.co @@ -69,7 +69,7 @@ GraphDisplayView = exports.GraphDisplayView = GraphView.extend do # {{{ */ 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/ diff --git a/lib/graph/graph-edit-view.co b/lib/graph/graph-edit-view.co index 05bbbac..1b70b4d 100644 --- a/lib/graph/graph-edit-view.co +++ b/lib/graph/graph-edit-view.co @@ -110,6 +110,7 @@ GraphEditView = exports.GraphEditView = GraphView.extend do # {{{ ### }}} ### Rendering {{{ + # TODO: refactor this to ChartType? chartOptions: (values, opts) -> # Handle @chartOptions(k, v, opts) if arguments.length > 1 and typeof values is 'string' @@ -139,7 +140,6 @@ GraphEditView = exports.GraphEditView = GraphView.extend do # {{{ @checkWaiting() root.title = "#{@get 'name'} | GraphKit" GraphEditView.__super__.render ... - @renderChart() # @updateURL() @unwait() @isRendering = false diff --git a/lib/graph/graph-model.co b/lib/graph/graph-model.co index 2d231f5..2f96a1b 100644 --- a/lib/graph/graph-model.co +++ b/lib/graph/graph-model.co @@ -92,9 +92,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{ @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'} @@ -179,6 +177,9 @@ Graph = exports.Graph = BaseModel.extend do # {{{ @triggerReady 'dataReady', 'data-ready' this + getData: -> + @dataset.getData() + onDataSetChange: -> console.log "#this.onDataSetChange!" @set 'data', @dataset, {+silent} diff --git a/lib/graph/graph-view.co b/lib/graph/graph-view.co index 4ff25d3..0a857e2 100644 --- a/lib/graph/graph-view.co +++ b/lib/graph/graph-view.co @@ -29,6 +29,13 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ __debounce__: <[ render ]> tagName : 'section' + /** + * The chart type backing this graph. + * @type ChartType + */ + chartType : null + + constructor: function GraphView BaseView ... @@ -36,6 +43,7 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ 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__ @@ -69,6 +77,8 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ @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() @@ -124,7 +134,7 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ toTemplateLocals: -> - attrs = _.clone @model.attributes + attrs = _.extend {}, @model.attributes delete attrs.options { @model, view:this, @graph_id, slug:'', name:'', desc:'' } import attrs @@ -215,7 +225,10 @@ GraphView = exports.GraphView = BaseView.extend do # {{{ @wait() @checkWaiting() GraphView.__super__.render ... - @renderChart() + + # @renderChart() + @chart = @chartType.render() + @unwait() @checkWaiting() this diff --git a/lib/main-edit.co b/lib/main-edit.co index 64b54cf..16e4151 100644 --- a/lib/main-edit.co +++ b/lib/main-edit.co @@ -5,7 +5,7 @@ Backbone = require 'backbone' } = require 'kraken/util' { BaseView, BaseModel, BaseList, } = require 'kraken/base' -{ ChartType, DygraphsChartType, +{ ChartType, } = require 'kraken/chart' { DataSource, DataSourceList, } = require 'kraken/dataset' @@ -22,7 +22,7 @@ CHART_DEFAULT_OPTIONS = {} 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', -> @@ -35,12 +35,12 @@ main = -> 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 @@ -52,22 +52,22 @@ main = -> # 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 diff --git a/lib/server/view-helpers.co b/lib/server/view-helpers.co index dc3ad99..70a7c66 100644 --- a/lib/server/view-helpers.co +++ b/lib/server/view-helpers.co @@ -32,7 +32,8 @@ NODE_ENV = exports.NODE_ENV = (process.env.NODE_ENV or 'dev').toLowerCase() # 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 []) diff --git a/lib/util/event/ready-emitter.co b/lib/util/event/ready-emitter.co index d3f893e..a247026 100644 --- a/lib/util/event/ready-emitter.co +++ b/lib/util/event/ready-emitter.co @@ -12,10 +12,11 @@ class ReadyEmitter extends EventEmitter /** * 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 @@ -23,10 +24,11 @@ class ReadyEmitter extends EventEmitter /** * 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 @@ -46,7 +48,7 @@ class ReadyEmitter extends EventEmitter 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 diff --git a/lib/util/parser.co b/lib/util/parser.co index 42c7c80..1783703 100644 --- a/lib/util/parser.co +++ b/lib/util/parser.co @@ -62,11 +62,11 @@ class exports.ParserMixin extends Mixin # - 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) @@ -103,7 +103,7 @@ class exports.ParserMixin extends Mixin * @extends BaseModel * @borrows ParserMixin */ -ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin do +ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin.mix do constructor: function ParsingModel then BaseModel ... @@ -112,7 +112,7 @@ ParsingModel = exports.ParsingModel = BaseModel.extend ParserMixin do * @extends BaseList * @borrows ParserMixin */ -ParsingList = exports.ParsingList = BaseList.extend ParserMixin do +ParsingList = exports.ParsingList = BaseList.extend ParserMixin.mix do constructor: function ParsingList then BaseList ... @@ -121,7 +121,7 @@ ParsingList = exports.ParsingList = BaseList.extend ParserMixin do * @extends BaseView * @borrows ParserMixin */ -ParsingView = exports.ParsingView = BaseView.extend ParserMixin do +ParsingView = exports.ParsingView = BaseView.extend ParserMixin.mix do constructor: function ParsingView then BaseView ... -- 1.7.0.4