Ever since Rails 3.1 adopted coffeescript within the asset pipeline as a default, coffescript has become an increasingly popular language for easily writing your javascript code.
Despite a lot of tutorials out there on how to write coffee and take advantage of the Rails asset pipeline, there are rather few guidelines on how to properly organize your code using these technologies; so let me outline a few techniques we have been using on some of our projects, such as Verboice and ResourceMap (both of them open source, so feel free to check them out).
Knowing when to execute
One of the key features of the asset pipeline is that all your js code is concatenated, minified and compressed into a single file, with aggresive client-side caching, so that the user only needs to download a single file the first time she visits your site.
However, this will imply that all of your javascript code will execute on every page in your site, unless you do something to prevent it. An easy way to solve this is to simply execute if there is a particular object in your DOM:
Eventually, if you find yourself relying too much on a particular object which indicates a complex functionality is required, you can abstract that to a global function, and use it whenever you need it:
A more comprehensive approach is the one explained here, which allows you to specify particular code to execute for each controller and each action without needing to rely on a particular DOM object or defining an "onFunctionality" function.
Basically, it works by defining functions in a global object with names that match controllers and their actions, such as:
Modify your application's template so it renders as data attributes of the HTML body which are the controller and action executed:
And load that information from an executor function that runs on page load, executing the function that matches the current controller and action.
This approach, although it requires a larger setup, works very well for large applications, in which you have clearly separated behaviours in different pages of your app. Otherwise, relying on a simple check for a certain DOM element is a clean solution.
One class per file
One of the goodnesses of coffeescript is that it isolates each unit of code in a separate context, and makes each function local to that particular context, to avoid those pesky global functions so ubiquitous in traditional javascript. If you want to split the behaviour for a particular page in multiple files, if you are using any of the techniques described above, you will run into the problem that whatever classes you defined in file A.js.coffee within a callback at onCondition, will not be visible within the code you write in B.js.coffee in a similar callback.
If you want a class to be usable outside the context in which it is defined, the key is to define it as an attribute of the global window object, as described here:
Even more succint, you can use the "@" syntax, as it will always refer to 'this', which, when defining the class, will be the global object:
This will allow you to refer to class Foo from any other file in your application easily. But what happens when you need to refer to class Foo before it has been defined, such as when you need to inherit from it? Consider the following case:
What you need to do is to instruct sprockets that file B.js.coffee needs to be included before file A. Considering your sprockets application.js file is probably simply doing a require_tree of the entire assets/javascripts folder, you want to keep this file as simple as possible, keeping the directive of including B before A within A or B.
As the require directive in sprockets will include the file only if it has not been included yet, you can simply add a require directive for file B at the beginning of A:
This way, sprockets will execute the following flow:
- Starts from application.js, reads the require_tree directive and starts processing all files in the tree
- First file in the tree is A.js.coffee, but begins with a directive to include B first
- Includes file B.js.coffee, as specified by the directive in A
- Proceeds to include the contents A.js.coffee
- Continues processing the remaining files in the tree, skipping B.js.coffee as it has already included it
Also, in the case you want to have a file containing certain global functions or classes, which will be used by other files in your app, you can take advantage of sprockets loading order.
Although you can use a require global directive at the beginning of each of your files, if you keep yout 'global' code in the root of your javascripts directory and all page-specific files organised in folders, sprockets' require_tree directive will first load the files in the root of the directory, and then proceed with the sub-folders.
Another simple alternative is to add a require path/to/globals instruction before the require_tree in application.js, but you will have to follow this procedure for each global or shared file you add.
Wrapping up
There are lots of different techniques you can use to split your coffeescript code in different files and keep it organised. The key concepts to keep in mind when doing so are:
- You have to prepend @ to a class name if you want to use it from a different context
- Be careful when writing code that depends on something defined in a different file, make sure to require it or ensure that sprockets will load it before
- All your javascript code will always execute unless you execute it conditionally based on the HTML being rendered
Hope it helps!