Lightweight Mixins in CoffeeScript
The Mixin class:
class Mixin
@attachTo: (klass) ->
klass::[k] = v for k,v of this.prototype when k isnt 'constructor'
@included? klass
Edit: The original version did not test the property key, it resulted in the original constructor not being accessible anymore.
A simple mixin:
class Suspendable extends Mixin
start: ->
# lazy context binding of handleFrameRequest
unless @handleFrameRequest.bound
f = @handleFrameRequest
@handleFrameRequest = => f.apply this, argument
@handleFrameRequest.bound = true
# animation start
unless @animated
@lastTime = new Date()
requestAnimationFrame @handleFrameRequest
@animated = true
stop: -> @animated = false if @animated
handleFrameRequest: ->
if @animated
currentTime = new Date()
delta = currentTime - @lastTime
@animate delta
requestAnimationFrame @handleFrameRequest
@lastTime = currentTime
animate: -> # override to create your animations
Parameterized mixins:
Equatable = (properties...) ->
class extends Mixin
equals: (o) -> o? and properties.every (p) =>
if @[p].equals? then @[p].equals o[p] else o[p] is @[p]
Formattable = (classname, properties...) ->
class extends Mixin
toString: ->
if properties.length is 0
"[#{classname}]"
else
formattedProperties = ("#{p}=#{@[p]}" for p in properties)
"[#{classname}(#{formattedProperties.join ', '})]"
classname: -> classname
Decorating a Class:
class Dummy
Suspendable.attachTo Dummy
Equatable('position', 'speed').attachTo Dummy
Formattable('Dummy','position', 'speed').attachTo Dummy
constructor: (@position, @speed) ->
animate: (delta) ->
@position = @position.add(@speed.scale(delta / 1000))
# ...
Nice way to wrap many mixin inclusions:
class Rectangle
PROPERTIES = ['x','y','width','height','rotation']
[
Equatable.apply(null, PROPERTIES)
Formattable.apply(null, ['Rectangle'].concat PROPERTIES)
Sourcable.apply(null, ['geomjs.Rectangle'].concat PROPERTIES)
Parameterizable('rectangleFrom', {
x: NaN
y: NaN
width: NaN
height: NaN
rotation: NaN
})
Cloneable, Geometry, Surface
Path, Triangulable, Intersections
].forEach (mixin) -> mixin.attachTo Rectangle
#....
Edit: As to respond to @sheerun suggestion, some other way to wrap mixins inclusions:
Through a base Module class:
class Module
@include: (mixins...) ->
if Object::toString.call(mixins[0]).indexOf('Array') >= 0
mixins = mixins[0]
mixins.forEach (mixin) -> mixin.attachTo this
class Dummy extends Module
@include Suspendable,
Equatable('position', 'speed'),
Formattable('Dummy','position', 'speed')
Or if you can't, or don't want to use inheritance:
include = (mixins...) ->
if Object::toString.call(mixins[0]).indexOf('Array') >= 0
mixins = mixins[0]
in: (klass) -> mixins.forEach (mixin) -> mixin.attachTo klass
class Dummy extends Module
include(
Suspendable,
Equatable('position', 'speed'),
Formattable('Dummy','position', 'speed')
).in Dummy
Written by Cédric Néhémie
Related protips
3 Responses
That wrapper code begs for some helper.
What is the usage of @included in Mixin class ? I do not see any other reference here.
I guess it is a function but where is it defined ?
You can define an included method on your class so than the class can know it's included into another class, useful for example when your mixin will generates methods whose names aren't known when defining the mixin.