m2sfwq
Last Updated: February 25, 2016
·
1.862K
· abe33
6c20dd9e98b93e3b3b36cf92b3cfcc39

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
Say Thanks
Respond

3 Responses
Add your response

1725
Photo on 08.01.2013 at 04.15

That wrapper code begs for some helper.

over 1 year ago ·
8416
6d505cfd144f564c75aaca283737077d

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 ?

over 1 year ago ·
13657
6c20dd9e98b93e3b3b36cf92b3cfcc39

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.

over 1 year ago ·