-
Notifications
You must be signed in to change notification settings - Fork 65
Expand file tree
/
Copy path06_applications.html
More file actions
297 lines (212 loc) · 16.2 KB
/
06_applications.html
File metadata and controls
297 lines (212 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>The Little Book on CoffeeScript - Applications</title>
<link rel="stylesheet" href="site/site.css" type="text/css" charset="utf-8">
<link rel="stylesheet" href="site/highlight.css" type="text/css" charset="utf-8">
<script src="site/jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="site/highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="site/coffee-script.js" type="text/javascript" charset="utf-8"></script>
<script src="site/preview.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
hljs.initHighlightingOnLoad();
</script>
</head>
<body>
<div id="container">
<header>
<h1><a href="index.html">The Little Book on CoffeeScript</a></h1>
</header>
<div id="content">
<div class="back"><a href="index.html">« Back to all chapters</a></div>
<h1>Creating Applications</h1>
<p>Now you've been given an overview of the syntax, lets explore actually structuring and creating CoffeeScript applications. This section aims to be useful to all CoffeeScript developers, novice or advanced. Indeed, it should be relevant to pure JavaScript developers too.</p>
<p>For some reason, when developers are building client side JavaScript applications, tried and tested patterns and conventions often fly out the window, and the end result is a spaghetti mess of un-maintainable coupled JavaScript. I can't stress enough how important application architecture is; if you're writing any JavaScript/CoffeeScript beyond simple form validation you should implement a form of application structure, such as <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">MVC</a>.</p>
<p>The secret to building maintainable large applications is not to build large applications. In other words, build a series of modular de-coupled components. Keep application logic as generic as possible, abstracting it out as appropriate. Lastly separate out your logic into views, models and controllers (MVC). Implementing MVC is beyond the scope of this chapter, for that I recommend you check out my book on <a href="http://oreilly.com/catalog/9781449307530/">JavaScript Web Applications</a> and use a framework like <a href="http://documentcloud.github.com/backbone/">Backbone</a> or <a href="https://github.com/maccman/spine">Spine</a>. Rather than that, here we're going to cover structuring applications using CommonJS modules.</p>
<h2>Structure & CommonJS</h2>
<p>So what exactly are CommonJS modules? Well, If you've used <a href="http://nodejs.org/">NodeJS</a> before you've used CommonJS modules, probably without realizing it. CommonJS modules were initially developed for writing server side JavaScript libraries, in an attempt to deal with loading, namespacing and scoping issues. They were a common format that would be compatible across all JavaScript implementations. The aim was that a library written for <a href="http://www.mozilla.org/rhino/">Rhino</a> would work for Node. Eventually these ideas transitioned back to browsers, and now we have great libraries like <a href="http://requirejs.org">RequireJS</a> and <a href="https://github.com/jbrantly/yabble">Yabble</a> to use modules client-side.</p>
<p>Practically speaking, modules ensure that your code is run in a local namespace (code encapsulation), that you can load other modules with the <code>require()</code> function, and expose module properties via <code>module.exports</code>. Let's dive into that in a bit more depth now.</p>
<h3>Requiring files</h3>
<p>You can load in other modules and libraries using <code>require()</code>. Simply pass a module name and, if it's in the load path, it'll return an object representing that module. For example:</p>
<pre><code>User = require("models/user")
</code></pre>
<p>Synchronous require support is a contentious issue, but has mostly been resolved with the mainstream loader libraries and latest CommonJS <a href="http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition">proposals</a>. It may be something you'll have to look into if you decided to take a separate route than the one I'm advocating with Stitch below.</p>
<h3>Exporting properties</h3>
<p>By default, modules don't expose any properties so their contents are completely invisible to <code>require()</code> calls. If you want a particular property to be accessible from your module, you'll need to set it on <code>module.exports</code>:</p>
<pre><code># random_module.js
module.exports.myFineProperty = ->
# Some shizzle
</code></pre>
<p>Now, whenever this module is required then <code>myFineProperty</code> will be exposed:</p>
<pre><code>myFineProperty = require("random_module").myFineProperty
</code></pre>
<h2>Stitch it up</h2>
<p>Formatting your code as CommonJS modules is all fine and dandy, but how do you actually get this working on the client in practice? Well, my method of choice is the rather unheard of <a href="https://github.com/sstephenson/stitch">Stitch</a> library. Stitch is by Sam Stephenson, the mind behind <a href="http://www.prototypejs.org">Prototype.js</a> amongst other things, and solves the module problem so elegantly it makes me want to dance for joy! Rather than try and dynamically resolve dependencies, Stitch simply bundles up all your JavaScript files into one, wrapping them in some CommonJS magic. Oh, and did I mention it'll compile your CoffeeScript, JS templates, <a href="http://lesscss.org">LESS CSS</a> and <a href="http://sass-lang.com">Sass</a> files too!</p>
<p>First things first, you'll need to install <a href="http://nodejs.org/">Node.js</a> and <a href="http://npmjs.org/">npm</a> if you haven't already, we'll be using those throughout this chapter.</p>
<p>Now let's create our application structure. If you're using <a href="https://github.com/maccman/spine">Spine</a>, you can automate this with <a href="http://github.com/maccman/spine.app">Spine.App</a>, otherwise it's something you'll need to do manually. I usually have an <code>app</code> folder for all the application specific code, and a <code>lib</code> folder for general libraries. Then anything else, including static assets, goes in the <code>public</code> directory.</p>
<pre><code>app
app/controllers
app/views
app/models
app/lib
lib
public
public/index.html
</code></pre>
<p>Now to actually boot up the Stitch server. Let's create a file called <code>index.coffee</code> and fill it with the following script:</p>
<p><span class="csscript"></span></p>
<pre><code>require("coffee-script")
stitch = require("stitch")
express = require("express")
argv = process.argv.slice(2)
package = stitch.createPackage(
# Specify the paths you want Stitch to automatically bundle up
paths: [ __dirname + "/app" ]
# Specify your base libraries
dependencies: [
# __dirname + '/lib/jquery.js'
]
)
app = express.createServer()
app.configure ->
app.set "views", __dirname + "/views"
app.use app.router
app.use express.static(__dirname + "/public")
app.get "/application.js", package.createServer()
port = argv[0] or process.env.PORT or 9294
console.log "Starting server on port: #{port}"
app.listen port
</code></pre>
<p>You can see some dependencies listed: <code>coffee-script</code>, <code>stitch</code> and <code>express</code>. We need to create a <code>package.json</code> file, listing these dependencies so npm can pick them up. Our <code>./package.json</code> file will look like this:</p>
<pre><code>{
"name": "app",
"version": "0.0.1",
"dependencies": {
"coffee-script": "~1.1.2",
"stitch": "~0.3.2",
"express": "~2.5.0",
"eco": "1.1.0-rc-1"
}
}
</code></pre>
<p>And let's install those dependencies with npm:</p>
<pre><code>npm install .
npm install -g coffee-script
</code></pre>
<p>Rightio, we're almost there. Now run:</p>
<pre><code>coffee index.coffee
</code></pre>
<p>You'll hopefully have a Stitch server up and running. Let's go ahead and test it out by putting an <code>app.coffee</code> script in the <code>app</code> folder. This will be the file that'll bootstrap our application.</p>
<p><span class="csscript"></span></p>
<pre><code>module.exports = App =
init: ->
# Bootstrap the app
</code></pre>
<p>Now let's create our main page <code>index.html</code> which, if we're building a single page app, will be the only page the user actually navigates to. This is a static asset, so it's located under the <code>public</code> directory.</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Application</title>
<!-- Require the main Stitch file -->
<script src="/application.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
document.addEventListener("DOMContentLoaded", function(){
var App = require("app");
App.init();
}, false);
</script>
</head>
<body>
</body>
</html>
</code></pre>
<p>When the page loads, our <em>DOMContentLoaded</em> event callback is requiring the <code>app.coffee</code> script (which is automatically compiled), and invoking our <code>init()</code> function. That's all there is to it, we've got CommonJS modules up and running, as well as a HTTP server and CoffeeScript compiler. If, say, we wanted to include a module, it's just a case of calling <code>require()</code>. Let's create a new class, <code>User</code>, and reference it from <code>app.coffee</code>:</p>
<p><span class="csscript"></span></p>
<pre><code># app/models/user.coffee
module.exports = class User
constructor: (@name) ->
# app/app.coffee
User = require("models/user")
</code></pre>
<h2>JavaScript templates</h2>
<p>If you're moving logic to the client side, then you'll definitely need some sort of templating library. JavaScript templating is very similar to templates on the server, such as Ruby's ERB or Python's text interpolation, expect of course it runs client side. There are a whole host of templating libraries out there, so I encourage you to do some research and check them out. By default, Stitch comes with support for <a href="https://github.com/sstephenson/eco">Eco</a> templates baked right in.</p>
<p>JavaScript templates are very similar to server side ones. You have template tags interoperated with HTML, and during rendering those tags get evaluated and replaced. The great thing about <a href="https://github.com/sstephenson/eco">Eco</a> templates, is they're actually written in CoffeeScript.</p>
<p>Here's an example:</p>
<pre><code><% if @projects.length: %>
<% for project in @projects: %>
<a href="<%= project.url %>"><%= project.name %></a>
<p><%= project.description %></p>
<% end %>
<% else: %>
No projects
<% end %>
</code></pre>
<p>As you can see, the syntax is remarkably straightforward. Just use <code><%</code> tags for evaluating expressions, and <code><%=</code> tags for printing them. A partial list of template tags is as follows:</p>
<ul>
<li><p><code><% expression %></code><br/>
Evaluate a CoffeeScript expression without printing its return value.</p></li>
<li><p><code><%= expression %></code><br/>
Evaluate a CoffeeScript expression, escape its return value, and print it.</p></li>
<li><p><code><%- expression %></code><br/>
Evaluate a CoffeeScript expression and print its return value without escaping it.</p></li>
</ul>
<p>You can use any CoffeeScript expression inside the templating tags, but there's one thing to look out for. CoffeeScript is whitespace-sensitive, but your Eco templates aren't. Therefore, Eco template tags that begin an indented CoffeeScript block must be suffixed with a colon. To indicate the end of an indented block, use the special tag <code><% end %></code>. For example:</p>
<pre><code><% if @project.isOnHold(): %>
On Hold
<% end %>
</code></pre>
<p>You don't need to write the <code>if</code> and <code>end</code> tags on separate lines:</p>
<pre><code><% if @project.isOnHold(): %> On Hold <% end %>
</code></pre>
<p>And you can use the single-line postfix form of <code>if</code> as you'd expect:</p>
<pre><code><%= "On Hold" if @project.isOnHold() %>
</code></pre>
<p>Now we've got a handle on the syntax, let's define an Eco template in <code>views/users/show.eco</code>:</p>
<pre><code><label>Name: <%= @name %></label>
</code></pre>
<p>Stitch will automatically compile our template and include it in <code>application.js</code>. Then, in our application's controllers we can require the template, like it was a module, and execute it passing any data required.</p>
<p><span class="csscript"></span></p>
<pre><code>require("views/users/show")(new User("Brian"))
</code></pre>
<p>Our <code>app.coffee</code> file should now look like this, rendering the template and appending it to the page when the document loads:</p>
<p><span class="csscript"></span></p>
<pre><code>User = require("models/user")
App =
init: ->
template = require("views/users/show")
view = template(new User("Brian"))
# Obviously this could be spruced up by jQuery
element = document.createElement("div")
element.innerHTML = view
document.body.appendChild(element)
module.exports = App
</code></pre>
<p>Open up <a href="http://localhost:9294/">the application</a> and give it a whirl! Hopefully this tutorial has given you a good idea of how to structure client-side CoffeeScript applications. For your next steps, I recommend checking out a client-side framework like <a href="http://documentcloud.github.com/backbone/">Backbone</a> or <a href="http://spinejs.com">Spine</a>, They'll provide a basic MVC structure for you, freeing you up for the interesting stuff.</p>
<h2>Bonus - 30 second deployment with Heroku</h2>
<p><a href="http://heroku.com/">Heroku</a> is an incredibly awesome web host that manages all the servers and scaling for you, letting you get on with the exciting stuff (building awesome JavaScript applications). You'll need an account with Heroku for this tutorial to work, but the great news is that their basic plan is completely free. While traditionally a Ruby host, Heroku have recently released their Cedar stack, which includes Node support.</p>
<p>Firstly we need to make a <code>Procfile</code>, which will inform Heroku about our application.</p>
<pre><code>echo "web: coffee index.coffee" > Procfile
</code></pre>
<p>Now, if you haven't already, you'll need to create a local git repository for your application.</p>
<pre><code>git init
git add .
git commit -m "First commit"
</code></pre>
<p>And now to deploy the application, we'll use the <code>heroku</code> gem (which you'll need to install if you haven't already).</p>
<pre><code>heroku create myAppName --stack cedar
git push heroku master
heroku open
</code></pre>
<p>That's it! Seriously, that's all there is to it. Hosting Node applications has never been easier.</p>
<h2>Additional libraries</h2>
<p><a href="https://github.com/sstephenson/stitch">Stitch</a> and <a href="https://github.com/sstephenson/eco">Eco</a> aren't the only libraries you can use for creating CoffeeScript & Node applications, there are a variety of alternatives.</p>
<p>For example, when it comes to templating, you can use <a href="http://mustache.github.com">Mustache</a>, <a href="http://jade-lang.com">Jade</a> or write your HTML in pure CoffeeScript using <a href="http://coffeekup.org">CoffeeKup</a>.</p>
<p>As for serving up application, <a href="http://github.com/maccman/hem">Hem</a> is a great choice, supporting both CommonJS and NPM modules and integrating seamlessly with the CoffeeScript MVC framework <a href="http://spinejs.com">Spine</a>. <a href="https://github.com/substack/node-browserify">node-browsify</a> is another similar project. Or if you want to go lower level with <a href="http://expressjs.com/">express</a> integration, there's Trevor Burnham's <a href="https://github.com/TrevorBurnham/connect-assets">connect-assets</a></p>
<p>You can find a full list of CoffeeScript web framework plugins, on the <a href="https://github.com/jashkenas/coffee-script/wiki/Web-framework-plugins">project's wiki</a>.</p>
<div class="back"><a href="07_the_bad_parts.html">Chapter 7 (The Bad Parts) »</a></div>
</div>
</div>
</body>
</html>