blog

Fun with CoffeeScript and Backbone.js : Part 1

by

CoffeeScript has been all the rage lately and I’ve been wanting to hop on board the bandwagon. I’ve also seen Backbone.js mentioned quite a bit and was even more intrigued after listening to this .NET Rocks podcast. I decided to convert some plain JavaScript code I had in a side project to use both CoffeeScript and Backbone.js and see how things went.

The project is a simplified morse code simulator that animates morse code being sent over a telegraph line. The complete source is available here and the running code can be seen here.

In this post I’ll discuss the code that handles the user input.

Initialization

init = ->
   decoder = new MorseDecoder
   communicationLine = new CommunicationLine(
    'decoder': decoder)
   straightKey = new StraightKeyInput(
    'el': $('#straight-key-div'),
    'model': communicationLine)
   communicationLineView = new CommunicationLineView(
    'el': $('#communication-line-div'),
    'model': communicationLine)
   decoderView = new DecoderView(
    'el': $('messageBoxDiv'),
    'model': decoder)
   drawSignalLine()
$(document).ready init

The init function sets up all of the Backbone.js model/view classes. Note that each Backbone view is initialized with both its corresponding HTML element and corresponding model object.

The init function also draws the "communication line" in a canvas element.

drawSignalLine = ->
   context = document.getElementById(
    "communicationLineCanvas").getContext('2d')
   context.clearRect(0, 0,
    context.canvas.width, context.canvas.height)
   context.moveTo(0, 30)
   context.lineTo(500, 30)
   context.strokeStyle = "#000"
   context.closePath()
   context.stroke()

Input View

The application processes user input using the StraighKeyInput class, a Backbone view class associated with an input button:

class StraightKeyInput extends Backbone.View
   initialize: ->
      @dashTimer = null
      @dashFlag = false
      @wordStopTimer = null
      @wordStopFlag = false
   events:
      'mousedown #straight-key': 'startTimers',
      'mouseup #straight-key': 'sendUserInput'
   startTimers: =>
      @dashTimer = setTimeout(@dashTimerExpired, 250)
      @wordStopTimer = setTimeout(
       @wordStopTimerExpired, 1000)
   dashTimerExpired: =>
      @dashFlag = true
   wordStopTimerExpired: =>
      @wordStopFlag = true
   sendUserInput: =>
      if @wordStopFlag
         @model.addToken(kWordStopToken)
      else if @dashFlag
         @model.addToken(kDashToken)
      else
         @model.addToken(kDotToken)
      clearTimeout(@dashTimer)
      clearTimeout(@wordStopTimer)
      @dashFlag = false
      @wordStopFlag = false

The class uses two timers to determine which "token" a user intends to send across the line. Using Backbone’s declarative style for event binding, I bind the mousedown event to a function that starts both timers. The mouseup event is bound to a function that pushes the appropriate token to the model based on how long the user held down the input button.

Input Model

class CommunicationLine extends Backbone.Model
   initialize: (options) =>
      @inputQueue = []
      @communicationLine =
       ['', '', '', '', '', '', '', '', '', '']
      @decoder = options.decoder
      setInterval(@moveDataOneStep, 100)
   addToken: (token) =>
      @inputQueue.push(token)
   moveDataOneStep: =>
      @decoder.processToken(@communicationLine.pop())
      nextToken = @inputQueue.shift()
      @communicationLine.unshift(
       if undefined == nextToken then '' else nextToken)
      @trigger('hasNewData', @communicationLine)

The CommunicationLine class is a Backbone model class that represents the tokens currently moving across the telegraph line. It also contains a queue that holds the input tokens generated by the user. The class also holds a reference to the object responsible for decoding the user input (more on that tomorrow).

Every 100 milliseconds, the communication line shifts its tokens over one spot. The token popped off the line is passed to the decoder. The model then checks the input queue to determine if a user-generated token is available to be pushed onto the line. If not, the model pushes a default token (an empty string). The model then triggers an event to indicate the communication line state has changed and that the object representing the communication line view needs to be re-rendered (more on that tomorrow).

+ more