Simplify your JavaScript with CoffeeScript
CoffeeScript helps generate readable code. Jonathan Barrett, of accounting software provider FreeAgent, walks you through validating an online form to demonstrate how
This article first appeared in issue 221 of .net magazine – the world's best-selling magazine for web designers and developers.
JavaScript isn’t very readable, and unreadable code is hard to maintain. Compared with Ruby or Python, there are brackets, braces and quotes everywhere. Often, there’s more syntax than software.
Enter CoffeeScript. It isn’t a framework, but instead compiles to runnable JavaScript. You write CoffeeScript, compile it, and out pops clean, tight JavaScript ready for the browser. You get optimised JavaScript, but work with clean, understandable, maintainable code.
CoffeeScript starts to make real sense once you’ve written some, so let’s get going. First, we’re going to install the CoffeeScript compiler, and have it convert our CoffeeScript files into JavaScript that we can load in our browser.
To get CoffeeScript installed on your development machine, you’ll need a *nix-like environment, a text editor, a terminal, and a browser to check the results. First we install node.js (a JavaScript runtime that CoffeeScript needs to do its magic). We then install node’s package manager (npm) and use that to install CoffeeScript itself. On OS X, the simplest way to do this is with homebrew. Make sure you have XCode installed, then follow the instructions to install homebrew, or just open a terminal session and type:
/usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/gist/323731)"Install node.js: brew install node
then npm and CoffeeScript:
curl http://npmjs.org/install.sh | sh npm install -g coffee-script
If you’re running Linux, your package manager can install node, then install npm and CoffeeScript. If, on the other hand, you’re using Windows, try following Matthew Podwysocki’s instructions at CodeBetter. This tutorial is all about the CoffeeScript syntax, so let’s keep the project goal simple: enable client-side validation on a form. Here’s our form, in index.html:
<form id="contact_form"> <ol> <li> <label>Name</label> <input type="text" name="name"> </li> <li> <label>Email</label> <input type="email" name="email"> </li> <li> <label>Enquiry</label> <textarea name="enquiry"></textarea> </li> </ol> <ol><li><input type="submit"></li></ol> </form>
We also have a directory, script, containing a blank file called form.coffee.
Boiling the water
Let’s write CoffeeScript! Open form.coffee and add the following:
required_field_names = ['name','email']
Just JavaScript so far, but it’s a start. Let’s compile form.coffee. Open a terminal and cd into the script directory:
Get top Black Friday deals sent straight to your inbox: Sign up now!
We curate the best offers on creative kit and give our expert recommendations to save you time this Black Friday. Upgrade your setup for less with Creative Bloq.
cd public/script
CoffeeScript’s coffee utility can compile the file:
coffee -c form.coffee
The -c flag tells coffee to compile the CoffeeScript to a file. Without it, coffee compiles and runs the file. -c will write JavaScript out to a file called form.js, in the same directory as the form.coffee file. Let’s look at form.js:
(function() { var required_field_names; required_field_names = ['name', 'email']; }).call(this);
What’s happened? The required_field_names variable has been scoped by var, and the whole script has been wrapped in a namespace. This protects us against one of the most common sources of bugs in JavaScript: accidental global variables. In CoffeeScript, variables are local by default, instead of global as in regular JavaScript. If you’ve never worried about scoping in JavaScript before, you’re very lucky. If you have, then this is a lifesaver.
The first sip
Let’s include our JavaScript in index.html:
<head> <script src="/script/form.js"></script> </head>
and update the form to call a function when submitted:
<form onsubmit="return validate(this);">
Let’s declare our required fields array in this validate function and return false to prevent the form submitting while developing. In JS, we might do:
var validate = function(form) { var required_field_names = ['name','email']; return false; }
With CoffeeScript, we can lose var call. Additionally:
function ( args ) {
becomes:
(args) ->
and in place of braces, we use indentation to define the function’s content:
validate = (form) -> required_field_names = ['name','email'] return false
Notice we’ve dropped trailing semi-colons too. One last trick: CoffeeScript returns the result of the last expression in a function, so we can lose the return:
validate = (form) -> required_field_names = ['name','email'] false
Compile:
coffee -c form.coffee
and look at form.js to check. Reload our form, hit Submit and ... the form submits. What’s gone wrong?
CoffeeScript creates everything in a namespace and with local scope. That means that while validate is available to anything within form.coffee, it’s not available outside that file, so onsubmit can’t access it. This prevents another JavaScript file redefining validate, but isn’t very helpful here.
To make the function global, we just change the assignment:
window.validate = (form) ->
This attaches validate to the window object, making it globally available. Compile again:
coffee -c form.coffee
then reload, submit the form and ... no submission! Compiling on every change is getting tedious, so let’s use the ‘watch’ functionality instead:
coffee -cw *.coffee
-w starts the compiler and leaves it running, compiling any changed file matching the *.coffee pattern. With this running, we can edit our CoffeeScript files, save and reload the compiled version in the browser almost immediately.
Grinding the beans
We have a list of required field names and a submit handler to check them. To grab all the fields with the names given, we might do the following in JS:
var required_fields = []; for ( var name in required_field_names ) { var field = form.elements[name]; required_fields.push(field); }
We could transliterate into CoffeeScript: lose the braces, use indentation. We can also lose brackets around arguments passed to functions too, just like in Ruby, giving:
required_fields = [] for name in required_field_names field = form.elements[name] required_fields.push field
We can go one better:
required_fields = for name in required_field_names return form.elements[name]
In other words, for() will return an array containing the return values from each iteration. Remember, we don’t need an explicit return in CoffeeScript, so:
required_fields = for name in required_field_names form.elements[name]
The for block is now just a single statement, and CoffeeScript gives us the following trick, putting the condition after the statement we want to run:
required_fields = ( form.elements[name] for name in required_field_names )
This reads like a sentence:
> Required Fields are the form elements for each name in Required Field Names
We’ve distilled five lines of JavaScript down to a single, clean line of code that expresses precisely what it does. validate itself is now four lines long. The compiled JavaScript is sitting at around 18 lines of bullet-proof, tight and memory-efficient code. There’s a lot to like about CoffeeScript.
Checking the roast
We have an array of input elements, so let’s check that each has a value. In JavaScript, we could do this:
var errors = []; for ( var field in required_fields ) { if ( field.value == '' ) { errors.push(field.name); } }
This would collect an array of bad field names. Let’s tidy this up:
errors = [] for field in required_fields if field.value == '' errors.push field_name
This doesn’t seem to be much of a saving. Notice that there’s only a single statement in the if block. This means we can do the same trick we managed with the for block – putting the conditional statement in front of the condition:
errors = [] for field in required_fields errors.push field_name if field.value == ''
We could even repeat the trick and move everything to a single line:
errors = [] (errors.push field_name if field.value == '') for field in required_fields
Wait. Brackets? The brackets ensure precedence and highlight a point: in CoffeeScript, brackets are optional, not banned. CoffeeScript aims to improve JavaScript’s readability, so if using brackets helps with that, then in they go.
Our validate function now:
window.validate = (form) -> required_field_names = ['name','email'] errors = [] required_fields = (form.elements[name] for name in required_field_names) (errors.push field.name if field.value == '') for field in required_fields false
There are two things left to do – prevent the form from submitting only if we have errors and report those errors to the user.
Serving the perfect cup
Preventing submission on error is now trivial. Replace the last line with a check on the number of errors:
window.validate = (form) -> ... errors.length == 0
validate now explicitly provides the yes/no answer to: are there zero errors on the form? This is very readable and maintainable: the final line of the function sums up exactly what the function does. The error reporting could be similarly simple, adding the following before the last line:
alert errors.join(', ') if errors.length > 0
This is too simple, though: no descriptions, just a list of field names. Let’sbreak this out into an error handling function, report. Replace the above with:
report errors
then add the following below validate:
report = (errors) -> alert "This form has errors:\n\n- " + errors.join("\n- ") if errors.length > 0
Our final source code looks like this:
window.validate = (form) -> required_field_names = ['name','email'] required_fields = (form.elements[name] for name in required_field_names) errors = [] (errors.push field.name if field.value == '') for field in required_fields report errors errors.length == 0 report = (errors) -> alert "This form has errors:\n\n- " + errors.join("\n- ") if errors.length > 0
Cleaning up
This sort of quick refactoring has always been a pain in JavaScript. Creating functions can lead to scoping issues, and the syntactical soup prevents refactoring from adding to the readability of the code. With CoffeeScript, pulling out functionality such as the error handling is trivial and does nothing but aid readability. We could continue this to clean up validation further:
window.validate = (form) -> errors = get_errors form, ['name','email'] report errors errors.length == 0 get_errors = (form, field_names) -> errors = [] required_fields = (form.elements[name] for name in field_names) (errors.push field.name if field.value == '') for field in required_fields errors report = (errors) -> alert "This form has errors:\n\n- " + errors.join("\n- ") if errors.length > 0
Thirteen lines of clean, readable code, versus 35 lines of efficient, but pretty much unreadable JavaScript? Now that’s a wake-up call.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.