CoffeeScript & JavaScript

CoffeeScript

For most of our language centric syntax issues we’ve adopted CoffeeLint to enforce our style guide. Our standard ruleset is here. In the future we’re going to get more strict and change “warning” level rules to “error” and also error on method complexity.

Naming

Camel case variables and method names.

# Do
someVar = 5
someFn = ->
  doStuff()

# Don't
some_var = 5
some_fn = ->
  do_stuff()

Capitalize and snake case constants.

MY_CONST = "I don't change"

Style

Note that ||= is distinct from ?=. The latter only does the assignment if the variable /attribute is undefined. The former does the assignment if the variable / attribute is a falsy value. This can burn you when working with Booleans.

x = { sunny: false }
x.sunny ?= true
x.sunny # false
x.sunny ||= true
x.sunny # true

Use destructuring assignments to work with deeply nested objects more easily.

# easy to work with
{Participants} = PollEv.Editor.View

# equivalent to
Participants = PollEv.Editor.View.Participants

Use in to check if items are in an array.

bestBooks = ["Huck Finn", "Tom Sawyer"]

if "Huck Finn" in bestBooks
  goRafting()

Prefer native methods when browser support allows it.

# We used to support IE8 which doesn't have the method String#trim
# Now that we support more modern browsers, we should use the native implementation

# Do
userInputString.trim()

# Don't
$.trim(userInputString)

Rely on length of an empty array being falsy.

# Do
read() if bestBooks.length

# Don't
read() if bestBooks.length > 0

Inline simple conditionals.

# Do
sleep() unless insomniac()

# Don't
unless insomniac()
  sleep()

Always use the multiline if / else instead of CoffeeScript’s ternary if expression.

# Do
if tired
  goToSleep()
else
  stayAwake()

# Don't
if tired then goToSleep() else stayAwake()

Always put constants on the lefthand side when comparing-this is to prevent accidental assignments.

timeOfDay = "Evening"

# Do
if "Morning" == timeOfDay
...
else
...

# Don't
if timeOfDay == "Morning"
...
else
...

Backbone

Never use Model#on to set up event listeners. Always use View#listenTo instead. This will automatically clean up listeners when View#remove is called so you don’t leak model references or events. Bind all events in initialize or from a method called from initialize.

# Do
initialize: ->
  @listenTo @model, "change:shortcode", =>
    @renderShortcode()
    @someOtherStuff()

# or
initialize: ->
  @_bindEvents()

_bindEvents: =>
  @listenTo @model, "change:shortcode", =>
    @renderShortcode()
    @someOtherStuff()

# Don't
initialize: ->
  @model.on "change:shortcode", =>
    @renderShortcode()
    @someOtherStuff()

# or
anyOtherMethod: =>
  @model.on "change:shortcode", =>
    @renderShortcode()
    @someOtherStuff()

Define View#remove if you have any subviews. The default remove will clean up jQuery bindings and bindings set up with View#listenTo, but will miss any subview references and bindings from the subviews.

initialize: ->
  @mySubview = ...

render: =>
  @$el.html @template()

  @$(".my-subview").append @mySubview.render().$el

  @

remove: =>
  @mySubview.remove()
  super

When creating a view never pass in an el. This couples your code to DOM state and makes testing and reuse difficult.

# Do
view = new myView
  model: somePoll

$(".something-in-the-dom").append view.render().$el

# Don't
view = new myView
  model: somePoll
  el: $(".something-in-the-dom")

view.render()

Only call render once for each view. Subsequent updates should be handled by model listeners calling individual sub render methods.

render: =>
  @$el.html @template()

  @renderTitle()
  @renderDescription()
  @renderCost()

  @

Use Backbone’s $ method as a shortcut for find inside of views.

# Do
@$(".child-element")

# Don't
@$el.find(".child-element")

Add newlines to group event hashes.

events:
  "mouseover .row": "highlightRow"
  "mouseleave .row": "unhighlightRow"

  "click .edit": "editRow"

Avoid methods that only use the e event object to prevent the default action. Instead deal with this behavior at the app level. Now in your markup you can declaratively cancel the default action by adding the class prevent-default and avoid calling e.preventDefault() in every link click handler.

# @$el is the root element of your application
@$el.on "click", ".prevent-default", (e) ->
  e.preventDefault()

Avoid methods that use the e event object when possible. It’s hard to mock and has different behavior than a standard jQuery collection.

# Do
makeAnonymous: =>
  @$(".anonymous").prop "checked", true

# Avoid
makeAnonymous: (e) =>
  $(e.currentTarget).prop "checked", true

jQuery

Don’t ever use jQuery#show and jQuery#hide. Use our own plugin jQuery#toggleVisibility. The standard show and hide methods add inline styles to the matching elements, which are hard to animate and override with CSS.

# Do
# Adds the class hidden to matching elements
$(".some-element").toggleVisibility(false)

# Don't
# Adds the inline style 'display:none;'
$(".some-element").hide()

Prefix variables with a $ when referring to jQuery collections.

# Do
$participantGroup = $(".participant-group")

# Don't
participantGroup = $(".participant-group")

If you find yourself manipulating DOM elements in the same way many times, write a simple jQuery plugin. This will help us solidify common conventions. $(mySelector).disable() is nicer to read than $(mySelector).attr('disabled', 'disabled').

do ($ = jQuery) ->
  $.fn.disable = ->
    return this.each ->
      $(this).attr("disabled", "disabled")

  $.fn.enable = ->
    return this.each ->
      $(this).removeAttr("disabled")

Object literals

Optionally inline them when there’s only one key.

authorDescriptions = { markTwain: "pretty good" }

If there are two or more keys, always multiline. Don’t use commas to separate multiline object literal entries.

authorDescriptions =
  markTwain: "pretty good"
  bradGessler: "don't trust him"

Numbers

JavaScript has only one number type. Don’t use decimal points to try and force float math.

  # Do
  1 / 2

  # Don't
  1.0 / 2

Regular Expressions

Use String#match if you plan on using a value from the returned array (which includes capture groups from the RegExp).

Use RegExp#test if you are using the RegExp in a boolean condition, only checking if it’s matched.

Common Patterns

Initialize an objects hash to the empty object in method definitions.

initialize = (options = {}) =>
  # this way you don't need to
  # check for existence of options
  @groups = options.groups

Simple callback execution.

save = (attributes, cb) =>
  @model.set(attributes)

  # this will only get called if cb
  # is defined and is a function
  cb?(@model)

Only expose public methods when defining a view mixin.

# This is prone to conflict
View.Mixin = (self) ->
  helperMethod: ->
    ...
  publicMethod: ->
    ...

# This is much safer
helperMethod = ->
  ...

View.Mixin = (self) ->
  publicMethod: ->
    ...