Skip to content

Simplify using static class properties in Backbone.js with Coffeescript

Do your Backbone models ever need to share common data between them? Then you might want to consider using class properties and class methods. I’ve been exploring this recently, and thought I’d share my findings. The source included in this post can be found in my sample-backbone-static github repository. There is also an example of the Factory pattern, courtesy of Johnny Oshika, that isn’t described in this post.

First, I’ll start with a basic example in javascript that shows how to define a backbone model with class properties.

var Sample = Backbone.Model.extend({
  initialize: function() {
  }
},{
  message: "No message has been set",
  setMessage: function(value) {
    this.message = value;
  }
});

The first parameter passed to Backbone.Model.extend should contain standard Backbone properties, such as initialize, defaults, validate, etc.

If a second parameter is passed to extend, the properties in it become class properties that are attached directly to the constructor function. In other words, the class properties message and setMessage would be accessed using the Sample class, and not from an instance of that class:

Sample.setMessage("Hello World");
console.log(Sample.message);

These class properties may also be referred to as static properties or static methods. Keep in mind that these values do not change for any given instance of the Sample class. They are not instance properties, but static class properties.

Also note that the setMessage function uses the this keyword. Normally, this is reserved for use within instances of a class. But when it is used in a static function, it accesses other static properties or functions within that class. It is important to understand this distinction, because a static function can not access an instance’s properties.

Now, let’s switch from javascript to coffeescript. Backbone has excellent coffeescript integration, and working in coffeescript feels much cleaner to me. Below is a more elaborate example in coffeescript. Unfortunately, WordPress doesn’t support coffeescript highlighting.

class Animal extends Backbone.Model
  # Distance this animal instance has moved
  distance: 0

  # Move this animal instance
  move: (meters) ->
    # Display message about animal and distance moved
    console.log "#{@get('name')} #{@constructor.motion} #{meters}m."
    # Record distance move by this animal instance
    @distance += meters
    # console.log @constructor.name
    # Record this distance in static storage
    @constructor.addDistance @, meters

  # Get the distance this animal instance moved
  getDistance: ->
    @distance

  # Static storage object to record total distances moved
  # by each animal type
  @distances: {}

  # Static function to add a distance to an animal type
  @addDistance: (inst, meters) ->
    # Get the type of anmial to add a distance to
    type = inst.constructor.type
    # Exit if running on the base Animal class
    return if not type
    # Retrieve data or initalize this type in storage
    @distances[type] = @distances[type] or 0
    # Add new distance to total animal type distance
    @distances[type] += meters

  # Static function to retrieve the distance moved by an
  # animal type. If called on base Animal, returns total
  # distance for all animals
  @getDistance: ->
    # Return distance moved by just one type of animal
    return @distances[@type] if @type
    # Get an array of distances moved by each animal type
    meters = (val for key, val of @distances)
    # Return the sum of all distances moved by all animals
    _.reduce(meters, ((memo, num) -> memo + num), 0)

The base Animal class defined above is used to display messages about movements that animals make. It is based on the sample in the coffeescript documentation. I’ve enhanced Animal to also keep track of the total distance a particular (instance) animal moves as well as the total distance that all animals (class) of each animal type has moved. This is accomplished by using both instance and class properties and methods.

The first thing to notice above is that class properties are prefixed with an @ symbol, as you can see in the definition of @distances, @addDistance, and @getDistance. In contrast, the move function does not have this prefix, which means it is an instance method, not a static function.

There are several great things about using coffeescript when working with classes, inheritance, and class properties. First is that you can define all of your functions and properties in any order you want, and simply prefix static ones with an @ symbol. Secondly, coffeescript provides a nice extends syntax for creating a class hierarchy. Also, inside of a method, you can use the @ symbol to represent the this object. In the context of an instance, @ refers to the instance itself, but in the context of a class method, it refers to the class constructor.

class Snake extends Animal
  @type: 'Snake'
  @motion: 'slithers'
  move: (m)->
    console.log "#{@get('name')} hides under a rock"
    super m

class Horse extends Animal
  @type: 'Horse'
  @motion: 'gallops'

As you can see, subclass implementations of Animal are very simple. I’ve defined the @type and @motion as class properties. I’ve overridden the move function for Snake.

Now let’s do something with this code:

sam = new Snake
  name: 'Sammy the Python'
tom = new Horse
  name: 'Tommy the Palomino'
jon = new Horse
  name: 'Jonny the Arabian'
sam.move(5)
tom.move(45)
jon.move(40)
sam.move(10)
console.log "Sammy moved #{sam.getDistance()}m total."
console.log "Tommy moved #{tom.getDistance()}m total."
console.log "Jonny moved #{jon.getDistance()}m total."
console.log "All snakes moved #{Snake.getDistance()}m."
console.log "All horses moved #{Horse.getDistance()}m."
console.log "All animals moved #{Animal.getDistance()}m."

Which outputs:

Sammy the Python hides under a rock
Sammy the Python slithers 5m.
Tommy the Palomino gallops 45m.
Jonny the Arabian gallops 40m.
Sammy the Python hides under a rock
Sammy the Python slithers 10m.
Sammy moved 15m total.
Tommy moved 45m total.
Jonny moved 40m total.
All snakes moved 15m.
All horses moved 85m.
All animals moved 100m.

Here’s what’s going on when an animal moves:

  • If the subclass has move defined, as in Snake, then it overrides the move function in Animal. If the subclass does not define move, as in Horse, then the move function in Animal is executed.
  • If a method in a subclass calls super, the corresponding method in the base class will be executed, as can be seen in Snake. Note that super is another nice feature of coffeescript which would correspond to the following javascript in Snake: Animal.prototype.move.call(this, m). It is not required to call super, only do so if you want the code in the base class to be executed as well as the code in your subclass.
  • Animal.move prints a message with the animal’s name, motion, and distance. The key thing to notice here is that the name is retrieved from the model instance, but the motion is pulled from the static class properties using the syntax @constructor.motion.
  • The distance moved by a specific animal is recorded as an instance property called distance.
  • The static method addDistance is called, passing it the animal instance, and the distance.
  • It then determines the type of animal with inst.constructor.type. We exit if the type doesn’t exist, preventing Animal.addDistance from being run, as it can only be run on a subclass.
  • Lastly, we make sure the static property @distances has a value defined for this type of animal, and we add the new distance to it.

And when we retrieve the distance moved using getDistance:

  • If getDistance is called on an instance, such as sam.getDistance(), then the distance moved by Sammy is returned. This is because the instance method getDistance is called, not the static method @getDistance.
  • If @getDistance is called on a subclass, such as Snake.getDistance(), then the distance moved by all Snakes is returned.
  • Otherwise, if Animal.getDistance() is called, then the distances of all animals is added up and returned.

I’d like to improve this so that I don’t need to define @type on all subclasses. In many browsers, it is possible to get the name of the class using @constructor.name, which would do the trick. But of course, this does not work in IE. I found defining @type to be simple and effective, but I’m open to other suggestions.

This just touches the surface of using class properties and methods. I’m sure I’ll be using this in my projects. Please share your thoughts in the comments!

Advertisements