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.
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
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:
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.
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.
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
Hope it helps!