Object-oriented Handlebars Helpers in Coffee Script

Handlebars helpers are just functions you register as helpers, so it's not surprising that normally people write them in the purely functional style.

Recently I've been building some fairly complex helpers though, and I wanted to write them in an object-oriented fashion: Coffee Script makes it so effortless to define classes so why not?

Dealing with the context

The only real challenge to building helpers in an O-O fashion is dealing with the Handlebars context. Normally, when handlebars calls your function,
'this' is set to the Handlebars context which has your template variables. So a naive approach like this is going to fail horribly:

class HorseHelpers
    HORSES = [ 'Rosie', 'Jack', 'Charlie', 'Billy', 'Ruby' ]
    listHorses: () ->
        (@horseRow(horse) for horse in HorseHelpers.HORSES).join('')
    horseRow: (horse) ->
        """<div class="horse">#{horse}</div>"""

If we register our horse listing function like this:

hbs.registerHelper new HorseHelpers().listHorses

Our helper is going to blow up when it tries to call @horseRow, because 'this' is the handlebars context, not the instance of HorseHelpers as we
would like it to be! I've fixed this by registering the helper using this setupHelper method:

setupHelper: (name) ->
    self = this # our Helper instance's ctx
    (args...) ->
        ctx = this # Handlebars ctx
        self[name](ctx, args...)

Now we can register the helper function like this:

hbs.registerHelper new HorseHelpers().setupHelper('listHorses')

This returns a closure which does some magic for us. First we keep track of the proper object context of our helper instance as 'self', this will then be used by the closure we return. Now when Handlebars calls the closure function it has a reference to the object's context as 'self', as well as the handlebars context as 'this'.

The closure function then calls the helper method
via 'self' (which indirectly means the method called will have the 'this' context set to 'self'), and passing the handlebars context as the first argument. Any arguments passed by handlebars will be passed as additional
arguments.

This means we can still access other instance methods with @ no problem, but we can still access the Handlebars context via the first ctx argument. Sweet!

Wrapping up

Because we're all object-oriented and shit we can add this helper method to a super class like so:

class BaseHandlebarsHelpers
    setupHelper: (name) ->
        self = this # our Helper instance's ctx
        (args...) ->
            ctx = this # Handlebars ctx
            self[name](ctx, args...)

And now our HorseHelpers class looks like so:

class HorseHelpers extends BaseHandlebarsHelpers
    HORSES = [ 'Rosie', 'Jack', 'Charlie', 'Billy', 'Ruby' ]
    listHorses: () ->
        (@horseRow(horse) for horse in HorseHelpers.HORSES).join('')
    horseRow: (horse) ->
        """<div class="horse">#{horse}</div>"""

(Disclaimer: I was too lazy to test this code so email me if there are some typos!)