homeASCIIcasts

267: CoffeeScript Basics 

(view original Railscast)

Other translations: Ja It Es

CoffeeScript is a language that compiles into JavaScript. It has been included in Rails 3.1 so a lot of Rails developers are going to be taking a look at it for the first time soon. In this episode we’re going to convert some existing JavaScript code into CoffeeScript as this is a great way to learn it. The JavaScript we’ll covert is the code we wrote back in episode 261 [watch, read] to validate credit card numbers.

If you’ve not used CoffeeScript the CoffeeScript website is a great place to start. There you can see a number of examples of CoffeeScript code along with the JavaScript that each compiles into. The site also has a page where you can enter CoffeeScript code and see it compiled into JavaScript. The compiled JavaScript can be run in the browser and all of this takes place on the client without any AJAX calls to the server.

The JavaScript code that we’re going to convert is used on the page below and fires when a user tabs out of the credit card number field. It uses mod 10 validation to perform some basic validation on the number that’s entered and shows an error message next to the text field if that number is invalid.

The credit card validation page.

The JavaScript that does this is shown here.

var CreditCard = {
  cleanNumber: function(number) {
    return number.replace(/[- ]/g, "");
  },
  validNumber: function(number) {
    var total = 0;
    number = this.cleanNumber(number);
    for (var i=number.length-1; i >= 0; i--) {
      var n = +number[i];
      if ((i+number.length) % 2 == 0) {
        n = n*2 > 9 ? n*2 - 9 : n*2;
      }
      total += n;
    };
    return total % 10 == 0;
  }
};
$(function() {
  $("#order_credit_card_number").blur(function() {
    if (CreditCard.validNumber(this.value)) {
      $("#credit_card_number_error").text("");
    } else {
      $("#credit_card_number_error").text("Invalid credit card number.");
    }
  });
});

The version of Rails we’re using in this application is 3.1 Release Candidate 1 which had just been announced at the time of writing. You can upgrade by running gem install rails --pre.

First Changes

To change a JavaScript file to a CoffeeScript one we just have to append .coffee to the file name. We can still use normal JavaScript files in Rails 3.1 applications by keeping the extension as .js; CoffeeScript is completely optional.

We’ll start by commenting out the code in the JavaScript file and then replace it bit-by-bit with the equivalent CoffeeScript. The first section we’ll convert is the cleanNumber function whose purpose is to clean up any entered card number by removing spaces and dashes.

/app/assets/javascripts/orders.js.coffee

var CreditCard = {
  cleanNumber: function(number) {
    return number.replace(/[- ]/g, "");
  }
}

The equivalent CoffeeScript code is this:

/app/assets/javascripts/orders.js.coffee

CreditCard =
  cleanNumber: (number) ->
    number.replace /[- ]/g, ""

With CoffeeScript we can remove a lot of the JavaScript syntax. CoffeeScript uses tabs do define the block level so we can remove all of the curly brackets. This means that we’ll need to be consistent with our use of whitespace.

We can also remove any use of the var keyword as this is un-needed and at the end of a function we don’t need the return keyword. The final value in a function will automatically be returned, just like it is in Ruby. Semicolons are also unnecessary in CoffeeScript and can be removed too.

Any function calls that pass in arguments don’t need to have the arguments wrapped in parentheses so we can remove those. The exception to this is when a function with no arguments is called. Parentheses are necessary here in order for CoffeeScript to know that a function is being called.

Finally we have to change the way a function is called. We need to remove the function keyword and replace it with -> after the function’s arguments. This takes a little getting used to but it’s a concise way to define functions in CoffeeScript.

Having converted this part of our code we can compile it now to see what JavaScript it creates. The output looks very similar to our original code.

var CreditCard;
CreditCard = {
  cleanNumber: function(number) {
    return number.replace(/[- ]/g, "");
  }
};

Next we’ll take a look at the big validNumber function in our JavaScript code.

/app/assets/javascripts/orders.js.coffee

validNumber: function(number) {
  var total = 0;
  number = this.cleanNumber(number);
  for (var i=number.length-1; i >= 0; i--) {
    var n = +number[i];
    if ((i+number.length) % 2 == 0) {
      n = n*2 > 9 ? n*2 - 9 : n*2;
    }
    total += n;
  };
  return total % 10 == 0;
}

We can follow similar steps to convert this code and end up with this CoffeeScript.

/app/assets/javascripts/orders.js.coffee

validNumber: (number) ->
  total = 0
  number = @cleanNumber(number)
  for i in [(number.length-1)..0]
    n = +number[i]
    if (i+number.length) % 2 == 0
      n = if n*2 > 9 then n*2 - 9 else n*2
    total += n
  total % 10 == 0

Again we’ve removed the curly brackets and the semicolons. We’ve also removed the var and return keywords and replaced function with ->. We’ve also made some other changes to clean the code up.

Anywhere we see a reference to this we can replace it with the @ sign so this.cleanNumber becomes @number. We can remove the outer brackets from the if statement as they aren’t required. The ternary operator has changed too and we can replace the C-like question mark and colon with a if then else statement, adding the if at the start, replacing the question mark with then and the colon with else.

The rest of the code looks good; all we have left to change is the for loop. CoffeeScript handles iterations differently from JavaScript so before we change our code let’s take a look at how it does it. We can iterate over an array with code like this:

for number in [1,2,3]
 alert number

This generates the following JavaScript:

var number, _i, _len, _ref;
_ref = [1, 2, 3];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  number = _ref[_i];
  alert(number);
}

Alternatively we could write the code this way to generate the same JavaScript.

alert number for number in [1,2,3]

For a sequence of numbers we can use a range instead of an array.

for number in [1..3]
 alert number

This simplifies the generated JavaScript:

var number;
for (number = 1; number <= 3; number++) {
  alert(number);
}

If we want our loop to count down rather than up we just need to reverse the numbers in the range.

for number in [3..1]
 alert number

This is similar to what the for loop in our code does so we can replace it with a similar count down.

We’ll move on now to our last piece of JavaScript code.

/app/assets/javascripts/orders.js.coffee

$(function() {
  $("#order_credit_card_number").blur(function() {
    if (CreditCard.validNumber(this.value)) {
      $("#credit_card_number_error").text("");
    } else {
      $("#credit_card_number_error").text("Invalid credit ↵
        card number.");
    }
  });
});

This piece of jQuery code attaches the validation code to the blur event on the credit card number field. We don’t need to do anything special to handle jQuery code in CoffeeScript. The equivalent CoffeeScript code is this:

/app/assets/javascripts/orders.js.coffee

jQuery ->
  $("#order_credit_card_number").blur ->
    if CreditCard.validNumber(@value)
      $("#credit_card_number_error").text("")
    else
      $("#credit_card_number_error").text("Invalid credit ↵
        card number.")

Just as before we’ve removed the curly brackets and semicolons, replaced function with -> and references to this with @. There’s one more change we’ve made which is to change the first call to $ to jQuery. This has no effect on the functionality but it makes it more obvious that we’re using jQuery here.

Now that we’ve changed all of the JavaScript code into CoffeeScript lets reload the page in the browser and see if it all works as it did before.

The CoffeeScript code behaves just as the JavaScript did.

It does. If we enter an invalid credit card number we’ll see the error message and it disappears when we enter a valid number. If we look at the bottom of the generated JavaScript file we’ll see the JavaScript compiled from the CoffeeScript file.

http://localhost:3000/assets/application.js

(function() {
  var CreditCard;
  CreditCard = {
    cleanNumber: function(number) {
      return number.replace(/[- ]/g, "");
    },
    validNumber: function(number) {
      var i, n, total, _ref;
      total = 0;
      number = this.cleanNumber(number);
      for (i = _ref = number.length - 1; _ref <= 0 ? i <= 0 : ↵
        i >= 0; _ref <= 0 ? i++ : i--) {
        n = +number[i];
        if ((i + number.length) % 2 === 0) {
          n = n * 2 > 9 ? n * 2 - 9 : n * 2;
        }
        total += n;
      }
      return total % 10 === 0;
    }
  };
  jQuery(function() {
    return $("#order_credit_card_number").blur(function() {
      if (CreditCard.validNumber(this.value)) {
        return $("#credit_card_number_error").text("");
      } else {
        return $("#credit_card_number_error").text("Invalid ↵ 
          credit card number.");
      }
    });
  });
}).call(this);

Debugging

What happens if we have a syntax error in the CoffeeScript code? If we change our CoffeeScript file so that it breaks and view the page in the browser we won’t see any errors as the JavaScript request is separate. If we look in the developer console, however, we’ll see the error listed there.

The CoffeeScript error is shown in the browser’s development console.

There’s enough information in the error message to tell us what went wrong and on what line of the code so we know where to start debugging the code.

That’s it for this episode. There’s a lot about CoffeeScript that we’ve not covered and it’s well worth taking a look at the CoffeeScript site to learn more about this fun little language.