writing jquery plugins with coffeescript

At the job, we're constantly trying to improve the UX of eLocal.com by making the site behave harder, better, faster, stronger. For some reason, recently we've had a surge in these kind of feature requests from the "peanut gallery" of sales and operations staff who are the primary source of feedback for the application. Most of what I do every day is solving bugs or adding new features to this big monolithic app, which powers the vast majority of our business. It is used by the staff on a daily basis to accomplish all sorts of tasks, from an in-house CRM to an advanced billing system (complete with recurring charges and invoicing), as well as basic control of our paying customers' ZIP code ads and lead dissemination.

Recently, I've been trying to codify and refactor the way I write JavaScript code. Using CoffeeScript has been a major advantage, as it makes the code much more readable for the way I develop my jQuery plugins. As a proponent of the object-oriented plugin design pattern, CoffeeScript has always been in my radar for doing this kind of thing, and now that I've learned enough about the language I think it's now possible to codify how I've been writing my JavaScript code, so the next guy doesn't have to write as much.

how we write our javascript components

All of our components are instantiated with the [jQuery][jq] framework. If you're dealing with the DOM, there's just no substitute. jQuery handles a lot of boilerplate we'd have to build ourselves, and is cross-browser to boot! The major reason we're talking about jQuery here, however, is its minimal plugin architecture.

A simple jQuery plugin written in CoffeeScript might look like:

jQuery.fn.viewAllButton = ->
  @each (i, element) ->
    $(element).on 'click', (event) ->
      container = $(this)
      button = container.find('button')
      form = container.closest('form')
      per_page = button.attr('data-per-page')

      form.find('input[name="per_page"]').val per_page
      form.find('input[type="submit"]').click()

Pretty easy to understand. For every element selected by this plugin, bind a click action that submits the form without its per_page param sent over, effectively returning all pages of all content. Most of the details are just for the specific implementation on eLocal's admin system.

But once plugins become complected to a further degree than this, the functional model employed by the previous example begins to break down. Having a lot of functions in your scope (that's any scope, not just the global one) in JavaScript makes your code harder to read, and eventually harder to debug. It is at this point when jQuery is no longer enough to write effective, efficient and most of all easy to debug JavaScript components. You need some bigger guns.

Thankfully, [CoffeeScript][cs] is here to save the day. CoffeeScript "fixes" the absolutely disgusting syntax that JavaScript provides to build objects. If you can imagine trying to stuff OOP into the concept of a function, you basically got what JavaScript is doing. Using a relatively common keyword, class, CoffeeScript gets the job done by sticking to JavaScript best practices and, at the most simple level, builds JavaScript object definitions in much the same way that I used to by hand.

Here's an example of a CoffeeScript plugin that was built to solve a simple UX problem when dealing with Rails fields_for associated forms:

jQuery.fn.undoFieldsFor = (pluginOptions) ->
  @each (i, element) -> new UndoFieldsFor($(element), pluginOptions)

class UndoFieldsFor
  defaults:
    form: 'form'
    item: '.line-item'

  # Assign the `<form>` and `<input>` buttons that we're working with
  # to properly "undo" changes made to the line items in a form.
  constructor: (@element, options) ->
    @options = _.extend @defaults, options
    @form = $(@options.form)
    @item = @element.closest(@options.item)
    @initialize()

  # Bind a removal event onClick
  initialize: ->
    @item.find('input[type=checkbox]').hide()
    @element.on 'click', @removeItem

  removeItem: (event) =>
    event.stopPropagation() and event.preventDefault()
    @item.remove()
    @_disableSubmission() if @form.find(@options.item).length == 0

  _disableSubmission: ->
    submit = @form.find 'input[type="submit"]'

    submit.removeAttr "value"
    submit.val "Disabled"
    submit.addClass "inactive"
    submit.attr "disabled", true

Basically, it replaces the _destroy checkbox on every additional item with an "UNDO" button. When this button is clicked, the surrounding line item element is destroyed and Rails will not submit that data as part of the form, because it's no longer on the page. The plugin takes advantage of the way Rails associations work, as long as you have an index number higher than that of its previous index, you're good to go. So that _destroy checkbox is really just there for compatibility, as clients without JavaScript will not be able to take advantage of this nice feature.

You can see just in this short example how easy it is to understand a slightly more complex plugin like undoFieldsFor. For each selected item that jQuery.undoFieldsFor() is applied to, the _destroy checkbox is hidden and replaced with an "Undo" button. The click event on this button (actually an <a> link) is bound to a JS action that destroys the current .line-item but also disables form submission in the event that no line items are in the DOM at the time the event is fired.

sumatra tastes exactly how it smells...delicious

You can already see a lot of boilerplate going on in the above CoffeeScript plugin example. For starters, I always have to do that little code on line 1 which sets up the jQuery plugin and tells it to instantiate a helper class every time. Also, because UndoFieldsFor is an object, I need to construct it with an $(element) jQuery object and the options hash passed into the plugin at time of instantiation. Wouldn't it be sweet if I didn't have to do this every time? Wouldn't it be nice if I could use an interface that allowed practically every part of the object building process to be customized, but in most conventional setups, would default to some sane settings that could be used for quickly writing jQuery plugins in a beautiful way.

Here's the same plugin implemented using the JavaScript framework/library I made to codify this technique, [sumatra][sum]:

sumatra 'undoFieldsFor', ->
  class UndoFieldsFor extends SumatraPlugin
    action: 'click'
    defaults:
      form: 'form'
      item: '.line-item'

    # Set up the <form>, .line-item and submit button we'll
    # be working with, and hide the checkbox from view.
    initialize: ->
      @form = $(@options.form)
      @submit = @form.find 'input[type="submit"]'
      @item = @element.closest(@options.item)
      @item.find('input[type=checkbox]').hide()

    # Remove the surrounding .line-item
    perform: (event) =>
      event.stopPropagation() and event.preventDefault()
      @item.remove()
      @_disableSubmission() unless @_lineItemsExist()

    # If no more items are left, disable the submit button on this form.
    _disableSubmission: ->
      @submit.removeAttr "value"
      @submit.val "Disabled"
      @submit.addClass "inactive"
      @submit.attr "disabled", true

    # Check if any line items still exist in the form.
    _lineItemsExist: ->
      @form.find(@options.item).length

By comparing these two bits of code, you can clearly see how we've abstracted some common functionality and increased the readability of our code. I have yet to find a JavaScript problem that I can't apply this solution to. As long as I have to create a new plugin, this always seems like the best way to do it. Let's break down what I'm doing here..

sumatra() is a globally-available function that performs that first line of jQuery from the control example. Since it's written in CoffeeScript, this function also returns the object defined on its last line, which is typically the implementation of the plugin itself. You can name the plugin here, defining how you will call it via jQuery, and that code you pass in as a function is called as the body of the jQuery plugin.

sumatra 'undoFieldsFor', ->

Now we're defining a new class which extends from SumatraPlugin. The object it extends from has some [really awesome time-saving features][sp], but it is purely optional and has no bearing on the plugin you actually make.

  class UndoFieldsFor extends SumatraPlugin
    action: 'click'
    defaults:
      form: 'form'
      item: '.line-item'

In the class itself, we define an action which is the event that we're listening for, and when fired will cause perform() to execute. This perform() method is another thing we have to define in the class itself, as it is the hard-set event handler for the plugin.

Additionally, Sumatra gives us a place to define default option values for the options hash that can be passed into any Sumatra plugin. You can set up this hash in defaults, which is merged with the passed in options to build the public @options property.

    initialize: ->
      @form = $(@options.form)
      @submit = @form.find 'input[type="submit"]'
      @item = @element.closest(@options.item)
      @item.find('input[type=checkbox]').hide()

While the constructor sets us up with some sane, minimal defaults, the initialize() method is for code that would normally go in the constructor. It is bad practice to override SumatraPlugin's constructor, since initialize() provides everything you need to get the class support up and running. Here, we're just caching some "instance" variables to some other elements that UndoFieldsFor needs to know about, like the <form> surrounding all of this, and the <input type="submit"> button that is used to submit the form. Additionally, we have the ability to set the item class in case of a conflict, so we also cache the item property here and delete its corresponding checkbox.

    perform: (event) =>
      event.stopPropagation() and event.preventDefault()
      @item.remove()
      @_disableSubmission() unless @_lineItemsExist()

Now it comes time to actually define what this plugin is doing. Notice that here, unlike the other plugin, it's much less "baroque", and basically defines the control flow of th event handler, instead of its logic. Using Sumatra directly influenced the design of this plugin, because now that we have a trusted framework set up to handle all of the boilerplate crap (that I make EVERY TIME), we can go ahead and abstract more of, if not all of, the actual logic into methods.

Since we've already defined this.item, we can just remove() it as it's just a jQuery DOM object. And the line above that, preventing the event from bubbling up and firing the default browser action, is also standard for plugin/event authoring. That leaves us with the two custom private methods, this._disableSubmission() and this._lineItemsExist(). As you can see, we denote private methods with a preceding _ in the method name. There are no private methods in JavaScript, so this convention is how we solve for that. Note that the distinction between public and private is purely semantic, and has no bearing on the logic of the code. They're to show you that the methods were not written to be called externally on this object.

Let's take a look at _disableSubmission():

    _disableSubmission: ->
      @submit.removeAttr "value"
      @submit.val "Disabled"
      @submit.addClass "inactive"
      @submit.attr "disabled", true

We already know about this.submit because it was saved up in the initialize method. So basically, all this method does is disable submission until a new line item is added. But this method is only called when another method returns, true:

    _lineItemsExist: ->
      @form.find(@options.item).length

Here's a method that offers no real code line reduction, but increases readability and visibility in the perform() method. This is the kind of thing that Sumatra makes it easy for you to do, add methods that increase readability and should enable a higher understanding of the code out of its readers.

doing it yourself because you're hardcore

Sumatra is 121 lines of CoffeeScript and its concepts aren't very hard to implement from scratch in the first place. Realistically, the most complex part is the sumatra() function, which uses a bit of meta-programming to get the job done and allow you to choose any class name as your service object. Other than that, I'm just giving you an "interface" prototype object to build your plugin off of, with very limited resources. This keeps the codebase light and easy to read, and lets you take care of the important things.

when you SHOULDN'T use sumatra

Sumatra's great for small jQuery plugins, but doesn't offer much more than traditional jQuery for building and organizing huge, JavaScript-centric applications. For that, you should use a more complete framework like [Ember][ejs], [Backbone][bjs], or [Angular][ajs], which provide more support and an MVC style for designing your code base for both efficiency and readability. Sumatra is best used as an addendum to mostly-HTML apps which desire little bits of Ajax functionality or fun animations.

check it out!

I'm interested to hear what others think of this, and I always at least read over any contribution made by pull request to http://github.com/tubbo/sumatra. That's where the codebase for Sumatra is stored. To install it, you can use [bower][bwr]:

bower install sumatra

Or, you can use the handy-dandy [sumatra-rails][smr] gem that I made to use Sumatra within Rails' "asset pipeline". For efficiency and consistency, each Sumatra package pushed to Bower is correspondingly pushed to sumatra-rails, so simply by updating your Gemfile you can start using the latest edition of Sumatra:

gem 'sumatra-rails'

The breakdown:

I hope this was at the very least entertaining, and at best helped you learn a little more about the way we do things here at eLocal. A short disclaimer: we do not use sumatra in production (yet!), but I thought it'd be a great way to show you guys our conventions as sumatra is somewhat of a codification of those techniques.

Posted on .