+++ /dev/null
-{ _, op,
-} = require 'kraken/util'
-
-Backbone = require 'backbone'
-
-
-
-BaseBackboneMixin = exports.BaseBackboneMixin =
-
- initialize: ->
- @__apply_bind__()
-
-
- ### Auto-Bound methods
-
- /**
- * A list of method-names to bind on `initialize`; set this on a subclass to override.
- * @type Array<String>
- */
- __bind__ : []
-
- /**
- * Applies the contents of `__bind__`.
- */
- __apply_bind__: ->
- names = _ @pluckSuperAndSelf '__bind__' .chain().flatten().compact().unique().value()
- _.bindAll this, ...names if names.length
-
-
-
- ### Synchronization
-
- /**
- * Count of outstanding tasks.
- * @type Number
- */
- waitingOn : 0
-
-
- /**
- * Increment the waiting task counter.
- * @returns {this}
- */
- wait: ->
- count = @waitingOn
- @waitingOn += 1
- console.log "#this.wait! #count --> #{@waitingOn}"
- @trigger('start-waiting', this) if count is 0 and @waitingOn > 0
- this
-
- /**
- * Decrement the waiting task counter.
- * @returns {this}
- */
- unwait: ->
- count = @waitingOn
- @waitingOn -= 1
- console.warn "#this.unwait! #{@waitingOn} < 0" if @waitingOn < 0
- console.log "#this.unwait! #count --> #{@waitingOn}"
- @trigger('stop-waiting', this) if @waitingOn is 0 and count > 0
- this
-
- /**
- * @param {Function} fn Function to wrap.
- * @returns {Function} A function wrapping the passed function with a call
- * to `unwait()`, then delegating with current context and arguments.
- */
- unwaitAnd: (fn) ->
- self = this
- ->
- console.log "#self.unwaitAnd( function #{fn.name or fn.displayName}() )"
- self.unwait()
- fn ...
-
-
-
-mixinBase = exports.mixinBase = (body) ->
- _.clone(BaseBackboneMixin) import body
-
-
-/**
- * @class Base model, extending Backbone.Model, used by scaffold and others.
- * @extends Backbone.Model
- */
-BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
-
- constructor : function BaseModel
- @__class__ = @constructor
- @__superclass__ = @..__super__.constructor
- @waitingOn = 0
- Backbone.Model ...
- @trigger 'create', this
-
-
-
-
- ### Accessors
-
- has: (key) ->
- @get(key)?
-
- get: (key) ->
- _.getNested @attributes, key
-
- # set: (key, value, opts) ->
- # if _.isObject(key) and key?
- # [values, opts] = [key, value]
- # else
- # values = { "#key": value }
- #
- # # TODO: Validation
- # @_changed or= {}
- #
- # for key, value in values
- # if _.str.contains key, '.'
- # _.setNested @attributes, key, value, opts
- # else
- # Backbone.Model::set.call this, key, value, opts
- #
- # this
- #
- # unset : (key, opts) ->
- #
-
-
-
-
-
- ### Serialization
-
- serialize: (v) ->
- # if v!?
- # v = ''
- if _.isBoolean v
- v = Number v
- else if _.isObject v
- v = JSON.stringify v
- String v
-
- /**
- * Like `.toJSON()` in that it should return a plain object with no functions,
- * but for the purpose of `.toKV()`, allowing you to customize the values
- * included and keys used.
- * @returns {Object}
- */
- toKVPairs: ->
- kvo = _.collapseObject @toJSON()
- for k, v in kvo
- kvo[k] = @serialize v
- kvo
-
- /**
- * Serialize the model into a `www-form-encoded` string suitable for use as
- * a query string or a POST body.
- * @returns {String}
- */
- toKV: (item_delim='&', kv_delim='=') ->
- _.toKV @toKVPairs(), item_delim, kv_delim
-
- /**
- * @returns {String} URL identifying this model.
- */
- toURL: ->
- "?#{@toKV ...}"
-
- toString: -> "#{@..name or @..displayName}(cid=#{@cid}, id=#{@id})"
-
-
-# Class Methods
-BaseModel import do
- /**
- * Factory method which constructs an instance of this model from a string of KV-pairs.
- * This is a class method inherited by models which extend {BaseModel}.
- * @static
- * @param {String|Object} o Serialized KV-pairs (or a plain object).
- * @returns {BaseModel} An instance of this model.
- */
- fromKV: (o, item_delim='&', kv_delim='=') ->
- o = _.fromKV o, item_delim, kv_delim if typeof o is 'string'
- Cls = if typeof this is 'function' then this else this.constructor
- new Cls _.uncollapseObject o
-
-# }}}
-
-/**
- * @class Base collection, extending Backbone.Collection, used by scaffold and others.
- * @extends Backbone.Collection
- */
-BaseList = exports.BaseList = Backbone.Collection.extend mixinBase do # {{{
-
-
- constructor : function BaseList
- @__class__ = @constructor
- @__superclass__ = @..__super__.constructor
- @waitingOn = 0
- Backbone.Collection ...
- @trigger 'create', this
-
-
- ### Serialization
-
- toKVPairs: ->
- _.collapseObject @toJSON()
-
- toKV: (item_delim='&', kv_delim='=') ->
- _.toKV @toKVPairs(), item_delim, kv_delim
-
- toURL: (item_delim='&', kv_delim='=') ->
- "?#{@toKV ...}"
-
- toString: -> "#{@..name or @..displayName}(length=#{@length})"
-# }}}
-
-
-/**
- * @class Base view, extending Backbone.View, used by scaffold and others.
- * @extends Backbone.View
- */
-BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
- tagName : 'section'
-
- /**
- * Array of [view, selector]-pairs.
- * @type Array<[BaseView, String]>
- */
- subviews : []
-
-
-
- constructor : function BaseView
- @__class__ = @constructor
- @__superclass__ = @..__super__.constructor
- @waitingOn = 0
- @subviews = []
- Backbone.View ...
- @trigger 'create', this
-
- initialize: ->
- @__apply_bind__()
-
- @setModel @model
- @build()
-
- setModel: (model) ->
- if @model
- @model.off 'change', @render, this
- @model.off 'destroy', @remove, this
- delete @model.view
- data = @$el.data()
- delete data.model
- delete data.view
- if @model = model
- @model.view = this
- @$el.data { @model, view:this }
- @model.on 'change', @render, this
- @model.on 'destroy', @remove, this
- @model
-
-
-
- ### Subviews
-
- addSubview: (selector, view) ->
- [view, selector] = [selector, null] unless view
- @subviews.push [view, selector]
- view
-
- removeSubview: (view) ->
- for [v, sel], idx of @subviews
- if v is view
- @subviews.splice(idx, 1)
- return [v, sel]
- null
-
- hasSubview: (view) ->
- _.any @subviews, ([v]) -> v is view
-
- 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()
- this
-
-
- ### Rendering Chain
-
- toTemplateLocals: ->
- json = {value:v} = @model.toJSON()
- if _.isArray(v) or _.isObject(v)
- json.value = JSON.stringify v
- json
-
- $template: (locals={}) ->
- $ @template do
- { $, _, op, @model, view:this } import @toTemplateLocals() import locals
-
- build: ->
- return this unless @template
- outer = @$template()
- @$el.html outer.html()
- .attr do
- id : outer.attr 'id'
- class : outer.attr('class')
- @attachSubviews()
- this
-
- render: ->
- @build()
- @trigger 'render', this
- this
-
- renderSubviews: ->
- _.invoke _.pluck(@subviews, 0), 'render'
- this
-
-
-
-
- ### UI Utilities
-
- hide : -> @$el.hide(); this
- show : -> @$el.show(); this
- remove : -> @$el.remove(); this
- clear : -> @model.destroy(); @remove()
-
-
- # 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
-
- toString : -> "#{@..name or @..displayName}(model=#{@model})"
-
-
-# Proxy model methods
-<[ get set unset toJSON toKV toURL ]>
- .forEach (methodname) ->
- BaseView::[methodname] = -> @model[methodname].apply @model, arguments
-
-# }}}
-
--- /dev/null
+Backbone = require 'backbone'
+
+{ _, op,
+} = require 'kraken/util'
+
+
+
+BaseBackboneMixin = exports.BaseBackboneMixin =
+
+ initialize: ->
+ @__apply_bind__()
+
+
+ ### Auto-Bound methods
+
+ /**
+ * A list of method-names to bind on `initialize`; set this on a subclass to override.
+ * @type Array<String>
+ */
+ __bind__ : []
+
+ /**
+ * Applies the contents of `__bind__`.
+ */
+ __apply_bind__: ->
+ names = _ @pluckSuperAndSelf '__bind__' .chain().flatten().compact().unique().value()
+ _.bindAll this, ...names if names.length
+
+
+
+ ### Synchronization
+
+ /**
+ * Count of outstanding tasks.
+ * @type Number
+ */
+ waitingOn : 0
+
+
+ /**
+ * Increment the waiting task counter.
+ * @returns {this}
+ */
+ wait: ->
+ count = @waitingOn
+ @waitingOn += 1
+ # console.log "#this.wait! #count --> #{@waitingOn}"
+ # console.trace()
+ @trigger('start-waiting', this) if count is 0 and @waitingOn > 0
+ this
+
+ /**
+ * Decrement the waiting task counter.
+ * @returns {this}
+ */
+ unwait: ->
+ count = @waitingOn
+ @waitingOn -= 1
+ # console.warn "#this.unwait! #{@waitingOn} < 0" if @waitingOn < 0
+ # console.log "#this.unwait! #count --> #{@waitingOn}"
+ # console.trace()
+ @trigger('stop-waiting', this) if @waitingOn is 0 and count > 0
+ this
+
+ /**
+ * @param {Function} fn Function to wrap.
+ * @returns {Function} A function wrapping the passed function with a call
+ * to `unwait()`, then delegating with current context and arguments.
+ */
+ unwaitAnd: (fn) ->
+ self = this
+ ->
+ # console.log "#self.unwaitAnd( function #{fn.name or fn.displayName}() )"
+ # console.trace()
+ self.unwait(); fn ...
+
+
+
+mixinBase = exports.mixinBase = (body) ->
+ _.clone(BaseBackboneMixin) import body
+
+
--- /dev/null
+Backbone = require 'backbone'
+
+{ _, op,
+} = require 'kraken/util'
+{ BaseBackboneMixin, mixinBase,
+} = require 'kraken/base/base-mixin'
+
+
+
+/**
+ * @class Base model, extending Backbone.Model, used by scaffold and others.
+ * @extends Backbone.Model
+ */
+BaseModel = exports.BaseModel = Backbone.Model.extend mixinBase do # {{{
+
+ constructor : function BaseModel
+ @__class__ = @constructor
+ @__superclass__ = @..__super__.constructor
+ @waitingOn = 0
+ Backbone.Model ...
+ @trigger 'create', this
+
+
+
+
+ ### Accessors
+
+ has: (key) ->
+ @get(key)?
+
+ get: (key) ->
+ _.getNested @attributes, key
+
+ # set: (key, value, opts) ->
+ # if _.isObject(key) and key?
+ # [values, opts] = [key, value]
+ # else
+ # values = { "#key": value }
+ #
+ # # TODO: Validation
+ # @_changed or= {}
+ #
+ # for key, value in values
+ # if _.str.contains key, '.'
+ # _.setNested @attributes, key, value, opts
+ # else
+ # Backbone.Model::set.call this, key, value, opts
+ #
+ # this
+ #
+ # unset : (key, opts) ->
+ #
+
+
+
+
+
+ ### Serialization
+
+ serialize: (v) ->
+ # if v!?
+ # v = ''
+ if _.isBoolean v
+ v = Number v
+ else if _.isObject v
+ v = JSON.stringify v
+ String v
+
+ /**
+ * Like `.toJSON()` in that it should return a plain object with no functions,
+ * but for the purpose of `.toKV()`, allowing you to customize the values
+ * included and keys used.
+ * @returns {Object}
+ */
+ toKVPairs: ->
+ kvo = _.collapseObject @toJSON()
+ for k, v in kvo
+ kvo[k] = @serialize v
+ kvo
+
+ /**
+ * Serialize the model into a `www-form-encoded` string suitable for use as
+ * a query string or a POST body.
+ * @returns {String}
+ */
+ toKV: (item_delim='&', kv_delim='=') ->
+ _.toKV @toKVPairs(), item_delim, kv_delim
+
+ /**
+ * @returns {String} URL identifying this model.
+ */
+ toURL: ->
+ "?#{@toKV ...}"
+
+ toString: -> "#{@..name or @..displayName}(cid=#{@cid}, id=#{@id})"
+
+
+# Class Methods
+BaseModel import do
+ /**
+ * Factory method which constructs an instance of this model from a string of KV-pairs.
+ * This is a class method inherited by models which extend {BaseModel}.
+ * @static
+ * @param {String|Object} o Serialized KV-pairs (or a plain object).
+ * @returns {BaseModel} An instance of this model.
+ */
+ fromKV: (o, item_delim='&', kv_delim='=') ->
+ o = _.fromKV o, item_delim, kv_delim if typeof o is 'string'
+ Cls = if typeof this is 'function' then this else this.constructor
+ new Cls _.uncollapseObject o
+
+# }}}
+
+
+/**
+ * @class Base collection, extending Backbone.Collection, used by scaffold and others.
+ * @extends Backbone.Collection
+ */
+BaseList = exports.BaseList = Backbone.Collection.extend mixinBase do # {{{
+
+
+ constructor : function BaseList
+ @__class__ = @constructor
+ @__superclass__ = @..__super__.constructor
+ @waitingOn = 0
+ Backbone.Collection ...
+ @trigger 'create', this
+
+
+ ### Serialization
+
+ toKVPairs: ->
+ _.collapseObject @toJSON()
+
+ toKV: (item_delim='&', kv_delim='=') ->
+ _.toKV @toKVPairs(), item_delim, kv_delim
+
+ toURL: (item_delim='&', kv_delim='=') ->
+ "?#{@toKV ...}"
+
+ toString: -> "#{@..name or @..displayName}(length=#{@length})"
+# }}}
+
+
--- /dev/null
+Backbone = require 'backbone'
+
+{ _, op,
+} = require 'kraken/util'
+{ BaseBackboneMixin, mixinBase,
+} = require 'kraken/base/base-mixin'
+
+
+
+/**
+ * @class Base view, extending Backbone.View, used by scaffold and others.
+ * @extends Backbone.View
+ */
+BaseView = exports.BaseView = Backbone.View.extend mixinBase do # {{{
+ tagName : 'section'
+
+ /**
+ * Array of [view, selector]-pairs.
+ * @type Array<[BaseView, String]>
+ */
+ subviews : []
+
+
+
+ constructor : function BaseView
+ @__class__ = @constructor
+ @__superclass__ = @..__super__.constructor
+ @waitingOn = 0
+ @subviews = []
+ Backbone.View ...
+ @trigger 'create', this
+
+ initialize: ->
+ @__apply_bind__()
+
+ @setModel @model
+ @build()
+
+ setModel: (model) ->
+ if @model
+ @model.off 'change', @render, this
+ @model.off 'destroy', @remove, this
+ delete @model.view
+ data = @$el.data()
+ delete data.model
+ delete data.view
+ if @model = model
+ @model.view = this
+ @$el.data { @model, view:this }
+ @model.on 'change', @render, this
+ @model.on 'destroy', @remove, this
+ @model
+
+
+
+ ### Subviews
+
+ addSubview: (selector, view) ->
+ [view, selector] = [selector, null] unless view
+ @subviews.push [view, selector]
+ view
+
+ removeSubview: (view) ->
+ for [v, sel], idx of @subviews
+ if v is view
+ @subviews.splice(idx, 1)
+ return [v, sel]
+ null
+
+ hasSubview: (view) ->
+ _.any @subviews, ([v]) -> v is view
+
+ 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()
+ this
+
+
+ ### Rendering Chain
+
+ toTemplateLocals: ->
+ json = {value:v} = @model.toJSON()
+ if _.isArray(v) or _.isObject(v)
+ json.value = JSON.stringify v
+ json
+
+ $template: (locals={}) ->
+ $ @template do
+ { $, _, op, @model, view:this } import @toTemplateLocals() import locals
+
+ build: ->
+ return this unless @template
+ outer = @$template()
+ @$el.html outer.html()
+ .attr do
+ id : outer.attr 'id'
+ class : outer.attr('class')
+ @attachSubviews()
+ this
+
+ render: ->
+ @build()
+ @trigger 'render', this
+ this
+
+ renderSubviews: ->
+ _.invoke _.pluck(@subviews, 0), 'render'
+ this
+
+
+
+
+ ### UI Utilities
+
+ hide : -> @$el.hide(); this
+ show : -> @$el.show(); this
+ remove : -> @$el.remove(); this
+ clear : -> @model.destroy(); @remove()
+
+
+ # 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
+
+ toString : -> "#{@..name or @..displayName}(model=#{@model})"
+
+
+# Proxy model methods
+<[ get set unset toJSON toKV toURL ]>
+ .forEach (methodname) ->
+ BaseView::[methodname] = -> @model[methodname].apply @model, arguments
+
+# }}}
+
--- /dev/null
+{ _, op,
+} = require 'kraken/util'
+{ BaseModel, BaseList,
+} = require 'kraken/base/base-model'
+
+Cascade = require 'kraken/util/cascade'
+
+
+
+/**
+ * @class A model that implements cascading lookups for its attributes.
+ */
+CascadingModel = exports.CascadingModel = BaseModel.extend do # {{{
+ /**
+ * The lookup cascade.
+ * @type Cascade
+ */
+ cascade : null
+
+
+ constructor: function CascadingModel (attributes={}, opts)
+ @cascade = new Cascade attributes
+ BaseModel.call this, attributes, opts
+
+ initialize: ->
+ BaseModel::initialize ...
+
+ get: (key) ->
+ @cascade.get key
+
+ toJSON: (opts={}) ->
+ opts = {-collapseCascade, ...opts}
+ if opts.collapseCascade
+ @cascade.toJSON()
+ else
+ BaseModel::toJSON()
+
+
+
+# Proxy Cascade methods
+<[
+ addLookup removeLookup popLookup shiftLookup unshiftLookup
+ isOwnProperty isOwnValue isInheritedValue isModifiedValue
+]>.forEach (methodname) ->
+ CascadingModel::[methodname] = -> @cascade[methodname].apply @cascade, arguments
+
+# }}}
+
+
+
--- /dev/null
+mixins = require 'kraken/base/base-mixin'
+models = require 'kraken/base/base-model'
+views = require 'kraken/base/base-view'
+cascading = require 'kraken/base/cascading-model'
+exports import mixins import models import views import cascading
* @returns {Boolean}
*/
isChangedOption: (k) ->
- @optionCascade.isChangedValue k
+ @optionCascade.isModifiedValue k
and not @isDefaultOption k
@_data = @_lookups[0] = data
this
+
+ /**
+ * @returns {Number} Number of lookup dictionaries.
+ */
+ size: ->
+ @_lookups.length
+
/**
* Adds a new lookup dictionary to the chain.
* @returns {this}
this
/**
- * Removes a lookup dictionary from the chain.
+ * Removes a lookup dictionary from the chain (but will not remove the data object).
* @returns {this}
*/
removeLookup: (dict) ->
- _.remove @_lookups, dict if dict
+ _.remove @_lookups, dict if dict and dict is not @_data
this
/**
+ * Pops the last dictionary off the lookup chain and returns it.
+ * @returns {*} The last dictionary, or `undefined` if there are no additional lookups.
+ */
+ popLookup: ->
+ return if @size() <= 1
+ @_lookups.pop()
+
+ /**
+ * Shifts the first additional lookup dictionary off the chain and returns it.
+ * @returns {*} The first dictionary, or `undefined` if there are no additional lookups.
+ */
+ shiftLookup: ->
+ return if @size() <= 1
+ @_lookups.splice(1, 1)[0]
+
+ /**
+ * Adds a lookup dictionary to the front of the chain, just after the Cascade's own data
+ * object.
+ * @returns {this}
+ */
+ unshiftLookup: (dict) ->
+ return this unless dict?
+ throw new Error "Lookup dictionary must be an object! dict=#dict" unless _.isObject dict
+ @_lookups.splice 1, 0, dict
+ this
+
+
+ /**
* @returns {Boolean} Whether `key` belongs to this object (not inherited
* from the cascade).
*/
- hasOwnProperty: (key) ->
+ isOwnProperty: (key) ->
meta = _.getNestedMeta(@_data, key)
meta?.obj and hasOwn.call meta.obj, key
* from the cascade) and is defined.
*/
isOwnValue: (key) ->
- @hasOwnProperty(key) and _.getNested(@_data, key, MISSING) is not MISSING
+ @isOwnProperty(key) and _.getNested(@_data, key, MISSING) is not MISSING
/**
* @returns {Boolean} Whether the value at `key` is different from that
* inherited by from the cascade.
*/
- isChangedValue: (key, strict=false) ->
- val = @get key
- cVal = @_getInCascade key, MISSING, 1
- if strict
- val is not cVal
- else
- not _.isEqual val, cVal
+ isModifiedValue: (key, strict=false) ->
+ not @isInheritedValue key, strict
/**
* @returns {Boolean} Whether the value at `key` is the same as that
* inherited by from the cascade.
*/
isInheritedValue: (key, strict=false) ->
- not @isChangedValue key, strict
-
- /**
- * @returns {Number} Number of lookup dictionaries.
- */
- size: ->
- @_lookups.length
+ val = @get key
+ cVal = @_getInCascade key, MISSING, 1
+ if strict
+ val is cVal
+ else
+ _.isEqual val, cVal
- parser
- cascade
- index
- - base
+ - base:
+ - base-mixin
+ - base-model
+ - base-view
+ - cascading-model
+ - index
- scaffold:
- scaffold-model
- scaffold-view