best friends together at last!
Then when you add CoffeeScript into the mix you've got a whole new set of problems. Ultimately you want one big file containing the logic of your application, but unless you've passed your discrete .coffee files to the coffee compiler (or included them in the head of your html) in a specific order, you won't be able to execute functions before they've been declared.
oh no save me, RequireJS
What RequireJS does in this situation is provide a solution to the dependency problem mentioned above. What you do is create your separate script files to the AMD format, which tells you to explicitly mention the files (modules) that each script file depends upon. You do this by wrapping your script in a call to the function define, where the parameters passed to define reference the dependencies of your new script.
By returning from this define function, you decide what to expose from this module when it's requested by something else. This is similar to the exports.blah = ExportedObject format within CommonJS. If we manipulate exactly what is returned we can expose any number of class definitions, functions or arbitrary values that we choose.
We're going to make a test project to understand how to make these things work together. This will give us an idea of how to structure our application in a way that removes the problems of dependency resolution. The first thing we'll need to do is to get the required libraries to run RequireJS with CoffeeScript. The specific files we need are as follows:
- The RequireJS library
- The CoffeeScript plugin for RequireJS
- The latest coffee-script.js compiler (for use with the above plugin)
Next we'll create some folders for our application, and for the sake of this example we'll organise the above files that we've downloaded as follows.
|Our file layout on disk|
Next we'll create our html file for loading the scripts, this'll be nothing more than a standard html document with a script tag in the head to load the require.js library.
Note the data-main attribute on the script tag. This is specific to RequireJS, and it defines the entry point to our application. What we're doing is telling RequireJS where the first file we want to execute is located, in this case it's at "src/main.js" (we've intentionally left out the .js suffix on this path).
We could set this to be anywhere in the directory tree, but it's important that the coffee-script.js and cs.js files are located in the same place as the entry point. Also at this point we want to rename coffee-script.js to CoffeeScript.js. This is a requirement specific to the cs.js plugin.
Now we'll create a simple main.js file for use in our application. We'll make sure it follows the AMD format by wrapping everything in a define call, and because we don't yet depend on anything it'll be straightforward.
Our tree should now look like this:
From here we can serve the files from the root of the project, and we should see our message output in the console for the index page.
We'll create a simple test application in coffeescript. Let's make it simple and add some text to the dom when the user clicks on the page. We'll do it in three steps
- populate the dom with a super cool html button
- bind to a click event and write to the console
- bind to the same event and add some text to the page
For demonstrating how to organise an application, we'll also experiment with different ways of returning values from a module to show how we can expose different objects.
Adding a button to the page
We'll do this by creating a module which simply adds a standard button element to the page. We'll call into this module from our main.js module utilising the CoffeeScript plugin we have sitting nicely in the src folder.
What's happening here is that within the define call we are saying "here comes a module with no dependencies" - then within that function we are defining a single function which manipulates the dom to add a button to the document
-> input = document.createElement "button" input.innerText = "Super Cool Test Button!" input.setAttribute "id", "the-button" document.body.appendChild "input"
In this case we've imported the module we just created and associated it with the parameter AddButton. Now within the context of main.js we have access to whatever we exported, which in this case was the single function we defined. By executing it directly we're performing the add button functionality.
Next let's look at something less trivial than adding a button, and instead organise our classes into something resembling a traditional application.