spectacular
Version:
Advanced BDD framework for CoffeeScript and JavaScript
215 lines (170 loc) • 5.95 kB
text/coffeescript
#### Mixins
class spectacular.HasAncestors
@included: (ctor) ->
ctor.getter 'ancestors', ->
ancestors = []
parent = @parent
while parent
ancestors.push parent
parent = parent.parent
ancestors
ctor.ancestorsScope = (name, block) ->
@getter name, -> @ancestors.filter block
nthAncestor: (level) ->
level = 1 if level < 1
parent = this
n = 0
for n in [0..level]
parent = parent.parent
parent
class spectacular.Globalizable
@unglobalizable: ['globalize', 'unglobalize', 'keepContext', 'globalizeMember', 'unglobalizeMember', 'globalizable', 'globalized', 'alternate', 'previous']
keepContext: true
globalize: ->
@previous ||= {}
@globalizable.forEach (k) =>
unless k in (@constructor.unglobalizable or Globalizable.unglobalizable)
@globalizeMember k, @[k]
@globalized = true
unglobalize: ->
@globalizable.forEach (k) =>
unless k in (@constructor.unglobalizable or Globalizable.unglobalizable)
@unglobalizeMember k, @[k]
@globalized = false
globalizeMember: (key, value, alternate=true) ->
_global = spectacular.global
@previous[key] = _global[key] if _global[key]?
self = this
if typeof value is 'function'
value._name = key unless value._name?
if @keepContext
proxy = -> value.apply self, arguments
proxy._name = key
_global[key] = proxy
else
_global[key] = value
else
_global[key] = value
if alternate
alt = @alternate key
@globalizeMember alt, value, false if alt isnt key
unglobalizeMember: (key, value, alternate=true) ->
_global = spectacular.global
if @previous[key]?
_global[key] = @previous[key]
else
delete _global[key]
if alternate
alt = @alternate key
@unglobalizeMember alt, value, false if alt isnt key
alternate: (key) ->
if key.indexOf('_') is -1
utils.underscore key
else
utils.camelize key
class spectacular.Hookable
@hook: (name) ->
capitalizedName = utils.capitalize name
@mergeUp "#{name}Hooks"
@accessor "own#{capitalizedName}Hooks", {
get: -> @["__own#{capitalizedName}Hooks"] ||= []
set: (v) -> @["__own#{capitalizedName}Hooks"] = v
}
registerHook: (name, block) ->
capitalizedName = utils.capitalize name
@["own#{capitalizedName}Hooks"].push block
class spectacular.GlobalizableObject
@include spectacular.Globalizable
@unglobalizable = spectacular.Globalizable.unglobalizable.concat 'set'
constructor: (@__globalizable...) ->
Object.defineProperty this.prototype, 'globalizable',
configurable: true
get: ->
if @__globalizable.length > 0
@__globalizable
else
utils.keys this
set: (property, value) ->
@[property] = value
@globalizeMember property, value if @globalized
spectacular.HasCollection = (plural, singular) ->
capitalizedSingular = utils.capitalize singular
capitalizedPlural = utils.capitalize plural
mixin = class ConcreteHasCollection
@included: (ctor) ->
ctor["#{plural}Scope"] = (name, block) ->
ctor.getter name, -> @[plural].filter block
mixin::["add#{capitalizedSingular}"] = (items...) ->
@[plural].push item for item in items when item not in @[plural]
mixin::["remove#{capitalizedSingular}"] = (items...) ->
newArray = []
newArray.push item for item in @[plural] when item not in items
@[plural] = newArray
mixin::["#{singular}At"] = (index) ->
@[plural][index]
mixin::["find#{capitalizedSingular}"] =
mixin::["indexOf#{capitalizedSingular}"] = (item) ->
@[plural].indexOf item
mixin::["has#{capitalizedSingular}"] =
mixin::["contains#{capitalizedSingular}"] = (item) ->
@[plural].indexOf(item) isnt -1
mixin::["#{plural}Length"] =
mixin::["#{plural}Size"] =
mixin::["#{plural}Count"] = ->
@[plural].length
mixin
spectacular.HasNestedCollection = (name, options={}) ->
through = options.through
throw new Error('missing through option') unless through?
mixin = class ConcreteHasNestedCollection
@included: (ctor) ->
ctor["#{name}Scope"] = (scopeName, block) ->
ctor.getter scopeName, -> @[name].filter block
ctor.getter name, ->
items = []
@[through].forEach (item) ->
items.push(item)
items = items.concat(item[name]) if item[name]?
items
mixin
class spectacular.AncestorsProperties
@followUp: (property) ->
capitalizedProperty = utils.capitalize property
privateProperty = "own#{capitalizedProperty}"
@accessor property, {
get: -> @[privateProperty] or @parent?[property]
set: (value) -> @[privateProperty] = value
}
@mergeUp: (property) ->
capitalizedProperty = utils.capitalize property
privateProperty = "own#{capitalizedProperty}"
@accessor property, {
get: ->
a = @[privateProperty]
a = @parent[property].concat a if @parent?
a
set: (value) -> @[privateProperty] = value
}
class spectacular.Describable
@included: (ctor) ->
ctor.getter 'description', ->
if @parent?.description?
space = ''
space = ' ' unless @noSpaceBeforeDescription
"#{@parent.description}#{space}#{@ownDescription}"
else
@ownDescription
class spectacular.Event
constructor: (@name, @target) ->
class spectacular.EventDispatcher
on: (event, callback) ->
@listeners ||= {}
@listeners[event] ||= []
@listeners[event].push callback unless callback in @listeners[event]
off: (event, callback) ->
if @listeners?[event]? and callback in @listeners[event]
@listeners[event].splice @listeners[event].indexOf(callback), 1
hasListener: (event) ->
@listeners?[event]? and @listeners[event].length > 0
dispatch: (event) ->
@listeners?[event.name]?.forEach (listener) -> listener event