From 5683315468b9c3352e8896ead63f9b6d1011df7d Mon Sep 17 00:00:00 2001 From: Julio Cesar Ody Date: Fri, 24 Jun 2011 15:28:32 +1000 Subject: [PATCH 1/4] reset, new TOC styles, fancy! --- coffeescript/index.html | 83 ++++++++++---- coffeescript/site/site.css | 228 +++++++++++++++++++------------------ 2 files changed, 174 insertions(+), 137 deletions(-) diff --git a/coffeescript/index.html b/coffeescript/index.html index fe7951f..70e77fb 100644 --- a/coffeescript/index.html +++ b/coffeescript/index.html @@ -1,28 +1,61 @@ - - - - -The Little Book on CoffeeScript - - - - -
-
-

The Little Book on CoffeeScript

-
- - -
- + + + + + The Little Book on CoffeeScript + + + + + +
+

+ + + + + The Little Book on CoffeeScript + +

+ +
+ \ No newline at end of file diff --git a/coffeescript/site/site.css b/coffeescript/site/site.css index ff55061..6acb047 100644 --- a/coffeescript/site/site.css +++ b/coffeescript/site/site.css @@ -1,124 +1,128 @@ -body { - font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; - font-size: 15px; - line-height: 22px; - color: #252519; - margin: 0; padding: 0; -} - -a { - color: #261a3b; -} - -a:visited { - color: #261a3b; -} - -img { - margin-bottom: 30px; -} - -p { - margin: 0 0 15px 0; - line-height: 1.7em; -} - -h1, h2, h3, h4, h5, h6 { - margin: 15px 0 15px 0; -} - -h2 { - padding-top: 10px; - margin-top: 20px; - font-size: 1.2em; -} - -h1 { - padding: 10px 0 8px 0; - margin-top: 30px; - border-bottom: 1px solid #e5e5ee; -} - -pre, code { - font-size: 12px; line-height: 18px; - font-family: Monaco, Consolas, "Lucida Console", monospace; - margin: 0; padding: 0; -} - -pre { - width: 610px; - overflow: auto; - border: 1px solid #e5e5ee; - margin: 0 0 15px 0; - background: ghostWhite; - padding: 5px; -} - -ul { +/* html5boilerplate.com reset */ +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; padding: 0; - margin: 10px 0; - list-style-position: inside; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; } -header { +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { display: block; - padding: 8px 50px; - background: ghostWhite; - border-bottom: 1px solid #DDD; -} - -header h1 { - border: 0; - margin: 0; - font-size: 20px; } -header h1 a { - text-decoration: none; -} +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, +q:before, q:after { content: ''; content: none; } +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } +input, select { vertical-align: middle; } -#content { - margin: 0 0 50px 50px; - max-width: 625px; -} -.back { - float: right; - margin: 10px 5px 0 0; -} +body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */ +select, input, textarea, button { font:99% sans-serif; } -#content .wrap { - width: 625px; - position: relative; -} +pre, code, kbd, samp { font-family: monospace, sans-serif; } -#content .wrap pre { - width: auto; -} -#content .wrap .handle { +html { overflow: hidden } +a:hover, a:active { outline: none; } +ul, ol { margin-left: 2em; } +ol { list-style-type: decimal; } +nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } +small { font-size: 85%; } +strong, th { font-weight: bold; } +td { vertical-align: top; } +sub, sup { font-size: 75%; line-height: 0; position: relative; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } +pre { + white-space: pre; white-space: pre-wrap; word-wrap: break-word; + padding: 15px; +} +textarea { overflow: auto; } +.ie6 legend, .ie7 legend { margin-left: -7px; } +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } + +label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } +button, input, select, textarea { margin: 0; } +input:valid, textarea:valid { } +input:invalid, textarea:invalid { + border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; +} +.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } + +::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } +::selection { background:#FF5E99; color:#fff; text-shadow: none; } + +a:link { -webkit-tap-highlight-color: #FF5E99; } +button { width: auto; overflow: visible; } +.ie7 img { -ms-interpolation-mode: bicubic; } + +h1, h2, h3, h4, h5, h6 { font-weight: bold; } + +.hidden { display: none; visibility: hidden; } +.invisible { visibility: hidden; } +.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } +.clearfix:after { clear: both; } +.clearfix { zoom: 1; } +/* End reset */ + +a { text-decoration: none } + +/* Mad typesets and font colours */ +body, a { color: #000 } +aside { color: #ccc } +h1 { font-family: 'ff-meta-web-pro' } +p, li { font-family: 'ff-meta-web-pro-condensed' } + +/* Ze grid! */ +#book { width: 900px; margin: 0 auto; padding: 3% 2em; box-shadow: 0 0 5px rgba(0,0,0,0.2); } + +#book > h1 { font-size: 3em; text-align: center; } +#book > h1 svg { margin-bottom: -7px; } +#book > h1 aside { font-size: 1rem } + +#toc { margin: 3em 0 } +#toc ol { counter-reset: li; margin-left: 130px } +#toc ol > li { position: relative; width: 50%; margin: 0.5em 0; padding: 0.25em 0.5em; border-radius: 0.5em } +#toc ol > li:hover { + background: #f8d43f; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f8d43f), to(#fceaa1)); + background: -moz-linear-gradient(19% 75% 90deg,#fceaa1, #f8d43f); +} +#toc ol > li:hover aside { color: #000 } + +#toc ol > li:before { position: absolute; - top: 0px; - right: 0px; - width: 15px; - height: 15px; - background: #BFBDC9; - border: 0; - cursor: pointer; -} - -.pages { - margin: 2em 0 0 0; - padding: 0; -} - -.pages li { - border-bottom: 1px solid #EEE; - line-height: 1.7em; - margin: 15px 0px 3px; - list-style-position: inside; -} - -.pages li a { - text-decoration: none; -} \ No newline at end of file + top: 0.3em; left: -2em; + width: 1em; + padding: 0.15em 0.25em; + text-align: center; + color: #fff !important; background: #000; + content: counter(li); + counter-increment: li; + color: inherit; + font-size: 1.6em; + border-radius: 0.75em ; +} + +#toc li a { font-size: 2em } +#toc aside { font-size: 1.4rem } \ No newline at end of file From 5242b8e217af4c956cb855e8827e8fc83e1244f9 Mon Sep 17 00:00:00 2001 From: Julio Cesar Ody Date: Fri, 24 Jun 2011 17:33:50 +1000 Subject: [PATCH 2/4] chapter 2, and stuff --- coffeescript/01_introduction.html | 110 +++--- coffeescript/02_syntax.html | 561 +++++++++++++++--------------- coffeescript/index.html | 2 +- coffeescript/site/site.css | 31 +- 4 files changed, 358 insertions(+), 346 deletions(-) diff --git a/coffeescript/01_introduction.html b/coffeescript/01_introduction.html index 9144537..5e09b09 100644 --- a/coffeescript/01_introduction.html +++ b/coffeescript/01_introduction.html @@ -1,79 +1,79 @@ - - -The Little Book on CoffeeScript - Introduction - - - - - - - - - -
-
-

The Little Book on CoffeeScript

-
- -
- + + + The Little Book on CoffeeScript - Introduction + + + + + + +
+

+ + + + + The Little Book on CoffeeScript +

+
+

What is CoffeeScript?

+

CoffeeScript is a little language that compiles down to JavaScript. The syntax is inspired by Ruby and Python, and implements many features from those two languages. This book is designed to help you learn CoffeeScript, understand best practices and start building awesome client side applications. The book is little, only five chapters, but that's rather apt as CoffeeScript is a little language too.

+

This book is completely open source, and was written by Alex MacCaw (or @maccman) with great contributions from David Griffiths, Satoshi Murakami, and Jeremy Ashkenas.

-

What is CoffeeScript?

+

If you have any errata or suggestions, please don't hesitate to open a ticket on the book's GitHub page. Readers may also be interested in JavaScript Web Applications by O'Reilly, a book that explores rich JavaScript applications and moving state to the client side.

-

CoffeeScript is a little language that compiles down to JavaScript. The syntax is inspired by Ruby and Python, and implements many features from those two languages. This book is designed to help you learn CoffeeScript, understand best practices and start building awesome client side applications. The book is little, only five chapters, but that's rather apt as CoffeeScript is a little language too.

+

So let's dive right into it; why is CoffeeScript better than writing pure JavaScript? Well for a start, there's less code to write - CoffeeScript is very succinct, and takes white-space into account. In my experience this reduces code by a third to a half of the original pure JavaScript. In addition, CoffeeScript has some neat features, such as array comprehensions, prototype aliases and classes that further reduce the amount of typing you need to do.

-

This book is completely open source, and was written by Alex MacCaw (or @maccman) with great contributions from David Griffiths, Satoshi Murakami, and Jeremy Ashkenas.

+

More importantly though, JavaScript has a lot of skeletons in its closet which can often trip up inexperienced developers. CoffeeScript neatly sidesteps these, by only exposing a curated selection of JavaScript features, fixing many of the language's oddities.

-

If you have any errata or suggestions, please don't hesitate to open a ticket on the book's GitHub page. Readers may also be interested in JavaScript Web Applications by O'Reilly, a book that explores rich JavaScript applications and moving state to the client side.

+

CoffeeScript is not a superset of JavaScript, so although you can use external JavaScript libraries from inside CoffeeScript, you'll get syntax errors if you compile JavaScript as-is, without converting it. The compiler converts CoffeeScript code into its counterpart JavaScript, there's no interpretation at runtime.

-

So let's dive right into it; why is CoffeeScript better than writing pure JavaScript? Well for a start, there's less code to write - CoffeeScript is very succinct, and takes white-space into account. In my experience this reduces code by a third to a half of the original pure JavaScript. In addition, CoffeeScript has some neat features, such as array comprehensions, prototype aliases and classes that further reduce the amount of typing you need to do.

+

First to get some common fallacies out the way. You will need to know JavaScript in order to write CoffeeScript, as runtime errors require JavaScript knowledge. However, having said that, runtime errors are usually pretty obvious, and so far I haven't found mapping JavaScript back to CoffeeScript to be an issue. The second problem I've often heard associated with CoffeeScript is speed; i.e. the code produced by the CoffeeScript compiler would run slower than it's equivalent written in pure JavaScript. In practice though, it turns out this isn't a problem either. CoffeeScript tends to run as fast, or faster than hand-written JavaScript.

-

More importantly though, JavaScript has a lot of skeletons in its closet which can often trip up inexperienced developers. CoffeeScript neatly sidesteps these, by only exposing a curated selection of JavaScript features, fixing many of the language's oddities.

+

What are the disadvantages of using CoffeeScript? Well, it introduces another compile step between you and your JavaScript. CoffeeScript tries to mitigate the issue as best it can by producing clean and readable JavaScript, and with its server integrations which automate compilation. The other disadvantage, as with any new language, is the fact that the community is still small at this point, and you'll have a hard time finding fellow collaborators who already know the language. CoffeeScript is quickly gaining momentum though, and its IRC list is well staffed; any questions you have are usually answered promptly.

-

CoffeeScript is not a superset of JavaScript, so although you can use external JavaScript libraries from inside CoffeeScript, you'll get syntax errors if you compile JavaScript as-is, without converting it. The compiler converts CoffeeScript code into its counterpart JavaScript, there's no interpretation at runtime.

+

CoffeeScript is not limited to the browser, and can be used to great effect in server side JavaScript implementations, such as Node.js. Additionally, CoffeeScript is getting much wider use and integration, such as being a default in Rails 3.1. Now is definitely the time to jump on the CoffeeScript train. The time you invest in learning about the language now will be repaid by major time savings later.

-

First to get some common fallacies out the way. You will need to know JavaScript in order to write CoffeeScript, as runtime errors require JavaScript knowledge. However, having said that, runtime errors are usually pretty obvious, and so far I haven't found mapping JavaScript back to CoffeeScript to be an issue. The second problem I've often heard associated with CoffeeScript is speed; i.e. the code produced by the CoffeeScript compiler would run slower than it's equivalent written in pure JavaScript. In practice though, it turns out this isn't a problem either. CoffeeScript tends to run as fast, or faster than hand-written JavaScript.

+

Initial setup

-

What are the disadvantages of using CoffeeScript? Well, it introduces another compile step between you and your JavaScript. CoffeeScript tries to mitigate the issue as best it can by producing clean and readable JavaScript, and with its server integrations which automate compilation. The other disadvantage, as with any new language, is the fact that the community is still small at this point, and you'll have a hard time finding fellow collaborators who already know the language. CoffeeScript is quickly gaining momentum though, and its IRC list is well staffed; any questions you have are usually answered promptly.

+

One of the easiest ways to initially play around with the library is to use it right inside the browser. Navigate to http://coffeescript.org and click on the Try CoffeeScript tab. The site uses a browser version of the CoffeeScript compiler, converting any CoffeeScript typed inside the left panel, to JavaScript in the right panel.

-

CoffeeScript is not limited to the browser, and can be used to great effect in server side JavaScript implementations, such as Node.js. Additionally, CoffeeScript is getting much wider use and integration, such as being a default in Rails 3.1. Now is definitely the time to jump on the CoffeeScript train. The time you invest in learning about the language now will be repaid by major time savings later.

+

In fact you can use browser-based compiler yourself, by including this script in a page, marking up any CoffeeScript script tags with the correct type.

-

Initial setup

- -

One of the easiest ways to initially play around with the library is to use it right inside the browser. Navigate to http://coffeescript.org and click on the Try CoffeeScript tab. The site uses a browser version of the CoffeeScript compiler, converting any CoffeeScript typed inside the left panel, to JavaScript in the right panel.

- -

In fact you can use browser-based compiler yourself, by including this script in a page, marking up any CoffeeScript script tags with the correct type.

- -
<script src="http://jashkenas.github.com/coffee-script/extras/coffee-script.js" type="text/javascript" charset="utf-8"></script>
+        
<script src="http://jashkenas.github.com/coffee-script/extras/coffee-script.js" type="text/javascript" charset="utf-8"></script>
 <script type="text/coffeescript">
-  # Some CoffeeScript
-</script>
-
- -

Obviously, in production, you don't want to be interpreting CoffeeScript at runtime, as it'll slow thing up for your clients, so CoffeeScript offers a Node.js compiler to pre-process CoffeeScript files.

+# Some CoffeeScript +</script>
-

To install it, first make sure you have a working copy of the latest stable version of Node.js, and npm (the Node Package Manager). You can then install CoffeeScript with npm:

+

Obviously, in production, you don't want to be interpreting CoffeeScript at runtime, as it'll slow thing up for your clients, so CoffeeScript offers a Node.js compiler to pre-process CoffeeScript files.

-
npm install coffee-script
-
+

To install it, first make sure you have a working copy of the latest stable version of Node.js, and npm (the Node Package Manager). You can then install CoffeeScript with npm:

-

This will give you a coffee executable. If you execute it without any command line options, it'll give you the CoffeeScript console, which you can use to quickly execute CoffeeScript statements. To pre-process files, pass the --compile option.

+
npm install coffee-script
-
coffee --compile my-script.coffee
-
+

This will give you a coffee executable. If you execute it without any command line options, it'll give you the CoffeeScript console, which you can use to quickly execute CoffeeScript statements. To pre-process files, pass the --compile option.

-

If --output is not specified, CoffeeScript will write to a JavaScript file with the same name, in this case my-script.js. This will overwrite any existing files, so be careful you're not overwriting any JavaScript files unintentionally. For a full list of the command line options available, pass --help.

+
coffee --compile my-script.coffee
-

As you can see above, the default extension of CoffeeScript files is .coffee. Amongst other things, this will allow text editors like TextMate to work out which language the file contains, giving it the appropriate syntax highlighting. By default, TextMate doesn't include support for CoffeeScript, but you can easily install the bundle to do so.

+

If --output is not specified, CoffeeScript will write to a JavaScript file with the same name, in this case my-script.js. This will overwrite any existing files, so be careful you're not overwriting any JavaScript files unintentionally. For a full list of the command line options available, pass --help.

-

If all this compilation seems like a bit of an inconvenience and bother, that's because it is. We'll be getting onto ways to solve this by automatically compiling CoffeeScript files when they're first requested, but first lets take a look at the languages's syntax.

+

As you can see above, the default extension of CoffeeScript files is .coffee. Amongst other things, this will allow text editors like TextMate to work out which language the file contains, giving it the appropriate syntax highlighting. By default, TextMate doesn't include support for CoffeeScript, but you can easily install the bundle to do so.

-
-
- +

If all this compilation seems like a bit of an inconvenience and bother, that's because it is. We'll be getting onto ways to solve this by automatically compiling CoffeeScript files when they're first requested, but first lets take a look at the languages's syntax.

+ + + + + + + + \ No newline at end of file diff --git a/coffeescript/02_syntax.html b/coffeescript/02_syntax.html index 758ed21..2e9b1a9 100644 --- a/coffeescript/02_syntax.html +++ b/coffeescript/02_syntax.html @@ -1,433 +1,432 @@ - - -The Little Book on CoffeeScript - Syntax - - - - - - - - - -
-
-

The Little Book on CoffeeScript

-
- -
- - - -

CoffeeScript Syntax

- -

Firstly, before we get any further into this section, I want to reiterate that while CoffeeScript's syntax is often identical with JavaScript's, it's not a superset, and therefore some JavaScript keywords, such as function and var aren't permitted, and will throw syntax errors. If you're writing a CoffeeScript file, it needs to be pure CoffeeScript; you can't intermingle the two languages.

- -

Why isn't CoffeeScript a superset? Well, the very fact that whitespace is significant in CoffeeScript programs prevents it being a superset. And, once that's decision's been made, the team decided you might as well go the full hog and deprecate some JavaScript keywords and features in the name of simplicity and in an effort to reduce many commonly occurring bugs.

- -

What I find mind-blowing, in a meta sort of way, is that the CoffeeScript interpreter itself is actually written in CoffeeScript. It looks like the chicken or egg paradox has finally been solved!

- -

Right, so firstly let's tackle the basic stuff. There are no semicolons in CoffeeScript, it'll add them automatically for you upon compilation. Semicolons were the cause of much debate in the JavaScript community, and behind some weird interpreter behavior. Anyway, CoffeeScript resolves this problem for you by simply removing semi-colons from its syntax, adding them as needed behind the scenes.

- -

Comments are in the same format as Ruby comments, starting with a hash character.

- -
# A comment
-
- -

Multiline comments are also supported, and are brought forward to the generated JavaScript. They're enclosed by three hash characters.

- -

- -
###
+  
+    
+    The Little Book on CoffeeScript - Introduction
+    
+    
+    
+        
+  
+  
+    
+

+ + + + + The Little Book on CoffeeScript +

+
+

CoffeeScript Syntax

+

Firstly, before we get any further into this section, I want to reiterate that while CoffeeScript's syntax is often identical with JavaScript's, it's not a superset, and therefore some JavaScript keywords, such as function and var aren't permitted, and will throw syntax errors. If you're writing a CoffeeScript file, it needs to be pure CoffeeScript; you can't intermingle the two languages.

+

Why isn't CoffeeScript a superset? Well, the very fact that whitespace is significant in CoffeeScript programs prevents it being a superset. And, once that's decision's been made, the team decided you might as well go the full hog and deprecate some JavaScript keywords and features in the name of simplicity and in an effort to reduce many commonly occurring bugs.

+

What I find mind-blowing, in a meta sort of way, is that the CoffeeScript interpreter itself is actually written in CoffeeScript. It looks like the chicken or egg paradox has finally been solved!

+ +

Right, so firstly let's tackle the basic stuff. There are no semicolons in CoffeeScript, it'll add them automatically for you upon compilation. Semicolons were the cause of much debate in the JavaScript community, and behind some weird interpreter behavior. Anyway, CoffeeScript resolves this problem for you by simply removing semi-colons from its syntax, adding them as needed behind the scenes.

+ +

Comments are in the same format as Ruby comments, starting with a hash character.

+ +
# A comment
+ +

Multiline comments are also supported, and are brought forward to the generated JavaScript. They're enclosed by three hash characters.

+ +

+ +
###
   A multiline comment, perhaps a LICENSE.
-###
-
- -

As I briefly alluded to, whitespace is significant in CoffeeScript. In practice, this means that you can replace curly brackets ({}) with a tab. This takes inspiration from Python's syntax, and has the excellent side effect of ensuring that your script is formatted in a sane manner, otherwise it won't even compile!

+###
-

Variables & Scope

+

As I briefly alluded to, whitespace is significant in CoffeeScript. In practice, this means that you can replace curly brackets ({}) with a tab. This takes inspiration from Python's syntax, and has the excellent side effect of ensuring that your script is formatted in a sane manner, otherwise it won't even compile!

-

CoffeeScript fixes one of the major bugbears with JavaScript, global variables. In JavaScript, it's all too easy to accidentally declare a global variable by forgetting to include var before the variable assignment. CoffeeScript solves this by simply removing global variables. Behind the scenes, CoffeeScript wraps up scripts with an anonymous function, keeping the local context, and automatically prefixes all variable assignments with var. For example, take this simple variable assignment in CoffeeScript:

+

Variables & Scope

-

+

CoffeeScript fixes one of the major bugbears with JavaScript, global variables. In JavaScript, it's all too easy to accidentally declare a global variable by forgetting to include var before the variable assignment. CoffeeScript solves this by simply removing global variables. Behind the scenes, CoffeeScript wraps up scripts with an anonymous function, keeping the local context, and automatically prefixes all variable assignments with var. For example, take this simple variable assignment in CoffeeScript:

-
myVariable = "test"
-
+

-

Notice the dark grey box in the top right of the code example above. Give that a click, and the code will toggle between CoffeeScript and the compiled JavaScript. This is rendered right inside the page at runtime, so you assured the compiled output is accurate.

+
myVariable = "test"
+        
-

As you can see, the variable assignment is kept completely local, it's impossible to accidentally create a global variable. CoffeeScript actually takes this a step further, and makes it impossible to shadow a higher-level variable. This goes a great deal to prevent some of the most common mistakes developers make in JavaScript.

+

Notice the dark grey box in the top right of the code example above. Give that a click, and the code will toggle between CoffeeScript and the compiled JavaScript. This is rendered right inside the page at runtime, so you assured the compiled output is accurate.

-

However, sometimes it's useful to create global variables. You can either do this by directly setting them as properties on the global object (window in browsers), or with the following pattern:

+

As you can see, the variable assignment is kept completely local, it's impossible to accidentally create a global variable. CoffeeScript actually takes this a step further, and makes it impossible to shadow a higher-level variable. This goes a great deal to prevent some of the most common mistakes developers make in JavaScript.

-

+

However, sometimes it's useful to create global variables. You can either do this by directly setting them as properties on the global object (window in browsers), or with the following pattern:

-
exports = this
-exports.MyVariable = "foo-bar"
-
+

-

In the root context, this is equal to the global object, and by creating a local exports variable you're making it really obvious to anyone reading your code exactly which global variables a script is creating. Additionally, it paves the way for CommonJS modules, which we're going to cover later in the book.

+
exports = this
+          exports.MyVariable = "foo-bar"
+        
-

Functions

+

In the root context, this is equal to the global object, and by creating a local exports variable you're making it really obvious to anyone reading your code exactly which global variables a script is creating. Additionally, it paves the way for CommonJS modules, which we're going to cover later in the book.

-

CoffeeScript removes the rather verbose function statement, and replaces it with a thin arrow: ->. Functions can be one liners or indented on multiple lines. The last expression in the function is implicitly returned. In other words, you don't need to use the return statement unless you want to return earlier inside the function.

+

Functions

-

With that in mind, let's take a look at an example:

+

CoffeeScript removes the rather verbose function statement, and replaces it with a thin arrow: ->. Functions can be one liners or indented on multiple lines. The last expression in the function is implicitly returned. In other words, you don't need to use the return statement unless you want to return earlier inside the function.

-

+

With that in mind, let's take a look at an example:

-
func = -> "bar"
-
+

-

You can see in the resultant compilation, the -> is turned into a function statement, and the "bar" string is automatically returned.

+
func = -> "bar"
+        
-

As mentioned earlier, there's no reason why we can't use multiple lines, as long we indent the function body properly.

+

You can see in the resultant compilation, the -> is turned into a function statement, and the "bar" string is automatically returned.

-

+

As mentioned earlier, there's no reason why we can't use multiple lines, as long we indent the function body properly.

-
func = ->
-  # An extra line
-  "bar"
-
+

-

Function arguments

+
func = ->
+          # An extra line
+          "bar"
+        
-

How about specifying arguments? Well, CoffeeScript lets you do that by specifying arguments in parentheses before the arrow.

+

Function arguments

-

+

How about specifying arguments? Well, CoffeeScript lets you do that by specifying arguments in parentheses before the arrow.

-
times = (a, b) -> a * b
-
+

-

CoffeeScript supports default arguments too, for example:

+
times = (a, b) -> a * b
+        
-

+

CoffeeScript supports default arguments too, for example:

-
times = (a = 1, b = 2) -> a * 2
-
+

-

You can also use splats to accept multiple arguments, denoted by ...

+
times = (a = 1, b = 2) -> a * 2
+        
-

+

You can also use splats to accept multiple arguments, denoted by ...

-
sum = (nums...) -> 
-  result = 0
-  nums.forEach (n) -> result += n
-  result
-
+

-

In the example above, nums is an array of all the arguments passed to the function. It's not an arguments object, but rather a real array, so you don't need to concern yourself with Array.prototype.splice or jQuery.makeArray() if you want to manipulate it.

+
sum = (nums...) -> 
+          result = 0
+          nums.forEach (n) -> result += n
+          result
+        
-

+

In the example above, nums is an array of all the arguments passed to the function. It's not an arguments object, but rather a real array, so you don't need to concern yourself with Array.prototype.splice or jQuery.makeArray() if you want to manipulate it.

-
trigger = (events...) ->
-  events.splice(1, 0, this)
-  this.parent.trigger.apply(events)
-
+

-

Function invocation

+
trigger = (events...) ->
+          events.splice(1, 0, this)
+          this.parent.trigger.apply(events)
+        
-

Functions can be invoked exactly as in JavaScript, with parens (), apply() or call(). However, like Ruby, CoffeeScript will automatically call functions if they are invoked with at least one argument.

+

Function invocation

-

+

Functions can be invoked exactly as in JavaScript, with parens (), apply() or call(). However, like Ruby, CoffeeScript will automatically call functions if they are invoked with at least one argument.

-
a = "Howdy!"
+        

-alert a -# Equivalent to: -alert(a) +
a = "Howdy!"
 
-alert inspect a
-# Equivalent to:
-alert(inspect(a))
-
+ alert a + # Equivalent to: + alert(a) -

Although parenthesis is optional, I'd recommend using it if it's not immediately obvious what's being invoked, and with which arguments. In the last example, with inspect, I'd definitely recommend wrapping at least the inspect invocation in parens.

+ alert inspect a + # Equivalent to: + alert(inspect(a)) +
-

+

Although parenthesis is optional, I'd recommend using it if it's not immediately obvious what's being invoked, and with which arguments. In the last example, with inspect, I'd definitely recommend wrapping at least the inspect invocation in parens.

-
alert inspect(a)
-
+

-

If you don't pass any arguments with an invocation, CoffeeScript has no way of working out if you intend to invoke the function, or just treat it like a variable. In this respect, CoffeeScript's behavior differs from Ruby which always invokes references to functions, and more similar to Python's. This has been the source of a few errors in my CoffeeScript programs, so it's worth keeping an eye out for cases where you intend to call a function without any arguments, and include parenthesis.

+
alert inspect(a)
+        
-

Function context

+

If you don't pass any arguments with an invocation, CoffeeScript has no way of working out if you intend to invoke the function, or just treat it like a variable. In this respect, CoffeeScript's behavior differs from Ruby which always invokes references to functions, and more similar to Python's. This has been the source of a few errors in my CoffeeScript programs, so it's worth keeping an eye out for cases where you intend to call a function without any arguments, and include parenthesis.

-

Context changes are rife within JavaScript, especially with event callbacks, so CoffeeScript provides a few helpers to manage this. One such helper is a variation on ->, the fat arrow function: =>

+

Function context

-

Using the fat arrow instead of the thin arrow ensures that the function context will be bound to the local one. For example:

+

Context changes are rife within JavaScript, especially with event callbacks, so CoffeeScript provides a few helpers to manage this. One such helper is a variation on ->, the fat arrow function: =>

-

+

Using the fat arrow instead of the thin arrow ensures that the function context will be bound to the local one. For example:

-
this.clickHandler = -> alert "clicked"
-element.addEventListener "click", (e) => this.clickHandler(e)
-
+

-

The reason you might want to do this, is that callbacks from addEventListener() are executed in the context of the element, i.e. this equals the element. If you want to keep this equal to the local context, without doing a self = this dance, fat arrows are the way to go.

+
this.clickHandler = -> alert "clicked"
+          element.addEventListener "click", (e) => this.clickHandler(e)
+        
-

This binding idea is a similar concept to jQuery's proxy() or ES5's bind() functions.

+

The reason you might want to do this, is that callbacks from addEventListener() are executed in the context of the element, i.e. this equals the element. If you want to keep this equal to the local context, without doing a self = this dance, fat arrows are the way to go.

-

Object literals & array definition

+

This binding idea is a similar concept to jQuery's proxy() or ES5's bind() functions.

-

Object literals can be specified exactly as in JavaScript, with a pair of braces and key/value statements. However, like with function invocation, CoffeeScript makes the braces optional. In fact, you can also use indentation and new lines instead of comma separation.

+

Object literals & array definition

-

+

Object literals can be specified exactly as in JavaScript, with a pair of braces and key/value statements. However, like with function invocation, CoffeeScript makes the braces optional. In fact, you can also use indentation and new lines instead of comma separation.

-
object1 = {one: 1, two: 2}
+        

-# Without braces -object2 = one: 1, two: 2 +
object1 = {one: 1, two: 2}
 
-# Using new lines instead of commas
-object3 = 
-  one: 1
-  two: 2
+          # Without braces
+          object2 = one: 1, two: 2
 
-User.create(name: "John Smith")
-
+ # Using new lines instead of commas + object3 = + one: 1 + two: 2 -

Likewise, arrays can use whitespace instead of comma separators, although the square brackets ([]) are still required.

+ User.create(name: "John Smith") +
-

+

Likewise, arrays can use whitespace instead of comma separators, although the square brackets ([]) are still required.

-
array1 = [1, 2, 3]
+        

-array2 = [ - 1 - 2 - 3 -] +
array1 = [1, 2, 3]
 
-array3 = [1,2,3,]
-
+ array2 = [ + 1 + 2 + 3 + ] -

As you can see in the example above, CoffeeScript has also stripped the trailing comma in array3, another common source of cross-browser errors.

+ array3 = [1,2,3,] +
-

Flow control

+

As you can see in the example above, CoffeeScript has also stripped the trailing comma in array3, another common source of cross-browser errors.

-

The convention of optional parentheses continues with CoffeeScript's if and else keywords.

+

Flow control

-

+

The convention of optional parentheses continues with CoffeeScript's if and else keywords.

-
if true == true
-  "We're ok"
+        

-if true != true then "Panic" +
if true == true
+          "We're ok"
 
-# Equivalent to:
-#  (1 > 0) ? "Ok" : "Y2K!"
-if 1 > 0 then "Ok" else "Y2K!"
-
+ if true != true then "Panic" -

As you can see above, if the if statement is on one line, you'll need to use the then keyword, so CoffeeScript knows when the block begins. Conditional operators (?:) are not supported, instead you should use a single line if/else statement.

+ # Equivalent to: + # (1 > 0) ? "Ok" : "Y2K!" + if 1 > 0 then "Ok" else "Y2K!" +
-

CoffeeScript also includes a Ruby idiom of allowing suffixed if statements.

+

As you can see above, if the if statement is on one line, you'll need to use the then keyword, so CoffeeScript knows when the block begins. Conditional operators (?:) are not supported, instead you should use a single line if/else statement.

-

+

CoffeeScript also includes a Ruby idiom of allowing suffixed if statements.

-
alert "It's cold!" if heat < 5
-
+

-

Instead of using the exclamation mark (!) for negation, you can also use the not keyword - which can sometimes make your code more readable as exclamation marks can be easy to miss.

+
alert "It's cold!" if heat < 5
+        
-

+

Instead of using the exclamation mark (!) for negation, you can also use the not keyword - which can sometimes make your code more readable as exclamation marks can be easy to miss.

-
if not true then "Panic"
-
+

-

In the example above, we could also use the CoffeeScript's unless statement, the opposite of if.

+
if not true then "Panic"
+        
-

+

In the example above, we could also use the CoffeeScript's unless statement, the opposite of if.

-
unless true
-  "Panic"
-
+

-

In a similar fashion to not, CoffeeScript also introduces the is statement, which translates to ===.

+
unless true
+          "Panic"
+        
-

+

In a similar fashion to not, CoffeeScript also introduces the is statement, which translates to ===.

-
if true is 1
-  "Type coercion fixed!"
-
+

-

You may have noticed in the examples above, that CoffeeScript is converting == operators into === and != into !==. This is one of my favorite features to the language, and yet one of the most simple. What's the reasoning behind this? Well frankly JavaScript's type coercion is a bit odd, and its equality operator coerces types in order to compare them, leading to some confusing behaviors and the source of many bugs.

+
if true is 1
+          "Type coercion fixed!"
+        
-

The example below is taken from JavaScript Garden's equality section which delves into the issue in some depth.

+

You may have noticed in the examples above, that CoffeeScript is converting == operators into === and != into !==. This is one of my favorite features to the language, and yet one of the most simple. What's the reasoning behind this? Well frankly JavaScript's type coercion is a bit odd, and its equality operator coerces types in order to compare them, leading to some confusing behaviors and the source of many bugs.

-

+

The example below is taken from JavaScript Garden's equality section which delves into the issue in some depth.

-
""           ==   "0"           // false
-0            ==   ""            // true
-0            ==   "0"           // true
-false        ==   "false"       // false
-false        ==   "0"           // true
-false        ==   undefined     // false
-false        ==   null          // false
-null         ==   undefined     // true
-" \t\r\n"    ==   0             // true
-
+

-

The solution is to use the strict equality operator, which consists of three equal signs: ===. It works exactly like the normal equality operator, but without any type coercion. It's recommended to always use the strict equality operator, and explicitly convert types if needs be. As mentioned earlier, this is the default in CoffeeScript, with any weak equality operators being converted into strict ones.

+
""           ==   "0"           // false
+          0            ==   ""            // true
+          0            ==   "0"           // true
+          false        ==   "false"       // false
+          false        ==   "0"           // true
+          false        ==   undefined     // false
+          false        ==   null          // false
+          null         ==   undefined     // true
+          " \t\r\n"    ==   0             // true
+        
-

+

The solution is to use the strict equality operator, which consists of three equal signs: ===. It works exactly like the normal equality operator, but without any type coercion. It's recommended to always use the strict equality operator, and explicitly convert types if needs be. As mentioned earlier, this is the default in CoffeeScript, with any weak equality operators being converted into strict ones.

-
if 10 == "+10" then "type coercion fail"
-
+

-

String interpolation

+
if 10 == "+10" then "type coercion fail"
+        
-

CoffeeScript brings Ruby style string interpolation to JavaScript. Double quotes strings can contain #{} tags, which contain expressions to be interpolated into the string.

+

String interpolation

-

+

CoffeeScript brings Ruby style string interpolation to JavaScript. Double quotes strings can contain #{} tags, which contain expressions to be interpolated into the string.

-
favourite_color = "Blue. No, yel..."
-question = "Bridgekeeper: What... is your favourite color?
-            Galahad: #{favourite_color}
-            Bridgekeeper: Wrong!
-            "
-
+

-

As you can see in the example above, multiline strings are also allowed, without having to prefix each line with a +:

+
favourite_color = "Blue. No, yel..."
+          question = "Bridgekeeper: What... is your favourite color?
+          Galahad: #{favourite_color}
+          Bridgekeeper: Wrong!
+          "
+        
-

Loops and Comprehensions

+

As you can see in the example above, multiline strings are also allowed, without having to prefix each line with a +:

-

Array iteration in JavaScript has a rather archaic syntax, reminiscent of an older language like C rather than a modern object orientated one. The introduction of ES5 improved that situation somewhat, with the forEach() function, but that still requires a function call every iteration and is therefore much slower. Again, CoffeeScript comes to the rescue, with a beautiful syntax:

+

Loops and Comprehensions

-

+

Array iteration in JavaScript has a rather archaic syntax, reminiscent of an older language like C rather than a modern object orientated one. The introduction of ES5 improved that situation somewhat, with the forEach() function, but that still requires a function call every iteration and is therefore much slower. Again, CoffeeScript comes to the rescue, with a beautiful syntax:

-
for name in ["Roger", "Roderick", "Brian"]
-  alert "Release #{name}"
-
+

-

If you need the current iteration index, just pass an extra argument:

+
for name in ["Roger", "Roderick", "Brian"]
+          alert "Release #{name}"
+        
-

+

If you need the current iteration index, just pass an extra argument:

-
for name, i in ["Roger the pickpocket", "Roderick the robber"]
-  alert "#{i} - Release #{name}"
-
+

-

You can also iterate on one line, using the postfix form.

+
for name, i in ["Roger the pickpocket", "Roderick the robber"]
+          alert "#{i} - Release #{name}"
+        
-

+

You can also iterate on one line, using the postfix form.

-
release prisoner for prisoner in ["Roger", "Roderick", "Brian"]
-
+

-

As with Python comprehensions, you can filter them:

+
release prisoner for prisoner in ["Roger", "Roderick", "Brian"]
+        
-

+

As with Python comprehensions, you can filter them:

-
prisoners = ["Roger", "Roderick", "Brian"]
-release prisoner for prisoner in prisoners when prisoner[0] is "R" 
-
+

-

You can also use comprehensions for iterating over properties in objects. Instead of the in keyword, use of.

+
prisoners = ["Roger", "Roderick", "Brian"]
+          release prisoner for prisoner in prisoners when prisoner[0] is "R" 
+        
-

+

You can also use comprehensions for iterating over properties in objects. Instead of the in keyword, use of.

-
names = sam: seaborn, donna: moss
-alert("#{first} #{last}") for first, last of names
-
+

-

The only low-level loop that CoffeeScript exposes is the while loop. This has similar behavior to the while loop in pure JavaScript, but has the added advantage that it returns an array of results, i.e. like the Array.prototype.map() function.

+
names = sam: seaborn, donna: moss
+          alert("#{first} #{last}") for first, last of names
+        
-

+

The only low-level loop that CoffeeScript exposes is the while loop. This has similar behavior to the while loop in pure JavaScript, but has the added advantage that it returns an array of results, i.e. like the Array.prototype.map() function.

-
num = 6
-minstrel = while num -= 1
-  num + " Brave Sir Robin ran away"
-
+

-

Arrays

+
num = 6
+          minstrel = while num -= 1
+          num + " Brave Sir Robin ran away"
+        
-

CoffeeScript takes inspiration from Ruby when it comes to array slicing by using ranges. Ranges are created by two numerical values, the first and last positions in the range, separated by .. or .... If a range isn't prefixed by anything, CoffeeScript expands it out into an array.

+

Arrays

-

+

CoffeeScript takes inspiration from Ruby when it comes to array slicing by using ranges. Ranges are created by two numerical values, the first and last positions in the range, separated by .. or .... If a range isn't prefixed by anything, CoffeeScript expands it out into an array.

-
range = [1..5]
-
+

-

If, however, the range is specified immediately after a variable, CoffeeScript converts it into a slice() method call.

+
range = [1..5]
+        
-

+

If, however, the range is specified immediately after a variable, CoffeeScript converts it into a slice() method call.

-
firstTwo = ["one", "two", "three"][0..1]
-
+

-

In the example above, the range returns a new array, containing only the first two elements of the original array. You can also use the same syntax for replacing an array segment with another array.

+
firstTwo = ["one", "two", "three"][0..1]
+        
-

+

In the example above, the range returns a new array, containing only the first two elements of the original array. You can also use the same syntax for replacing an array segment with another array.

-
numbers = [0..9]
-numbers[3..5] = [-3, -4, -5]
-
+

-

What's neat, is that JavaScript allows you to call slice() on strings too, so you can use ranges with string to return a new subset of characters.

+
numbers = [0..9]
+          numbers[3..5] = [-3, -4, -5]
+        
-

+

What's neat, is that JavaScript allows you to call slice() on strings too, so you can use ranges with string to return a new subset of characters.

-
my = "my string"[0..2]
-
+

-

Checking to see if a value exists inside an array is always a bore in JavaScript, particular as indexOf() doesn't yet have full cross-browser support (IE, I'm talking about you). CoffeeScript solves this with the in operator, for example.

+
my = "my string"[0..2]
+        
-

+

Checking to see if a value exists inside an array is always a bore in JavaScript, particular as indexOf() doesn't yet have full cross-browser support (IE, I'm talking about you). CoffeeScript solves this with the in operator, for example.

-
words = ["rattled", "roudy", "rebbles", "ranks"]
-alert "Stop wagging me" if "ranks" in words 
-
+

-

Aliases & the Existential Operator

+
words = ["rattled", "roudy", "rebbles", "ranks"]
+          alert "Stop wagging me" if "ranks" in words 
+        
-

CoffeeScript includes some useful aliases to save some typing. One of which is @, which is an alias for this.

+

Aliases & the Existential Operator

-

+

CoffeeScript includes some useful aliases to save some typing. One of which is @, which is an alias for this.

-
@saviour = true
-
+

-

Another is ::, which is an alias for prototype

+
@saviour = true
+        
-

+

Another is ::, which is an alias for prototype

-
User::first = -> @records[0]
-
+

-

Using if for null checks in JavaScript is common, but has a few pitfalls in that empty strings and zero are both coerced into false, which can catch you out. CoffeeScript existential operator ? returns true unless a variable is null or undefined, similar to Ruby's nil?.

+
User::first = -> @records[0]
+        
-

+

Using if for null checks in JavaScript is common, but has a few pitfalls in that empty strings and zero are both coerced into false, which can catch you out. CoffeeScript existential operator ? returns true unless a variable is null or undefined, similar to Ruby's nil?.

-
praise if brian?
-
+

-

You can also use it in place of the || operator:

+
praise if brian?
+        
-

+

You can also use it in place of the || operator:

-
velocity = southern ? 40
-
+

-

If you're using a null check before accessing a property, you can skip that by placing the existential operator right before the opening parens. This is similar to Ruby's try method.

+
velocity = southern ? 40
+        
-

+

If you're using a null check before accessing a property, you can skip that by placing the existential operator right before the opening parens. This is similar to Ruby's try method.

-
blackKnight.getLegs()?.kick()
-
+

-

You can safely call a value in the same manner, checking function-ness beforehand.

+
blackKnight.getLegs()?.kick()
+        
-

+

You can safely call a value in the same manner, checking function-ness beforehand.

-
whiteKnight.guard? us
-
+

-
-
- +
whiteKnight.guard? us
+        
+ + + + + + + + \ No newline at end of file diff --git a/coffeescript/index.html b/coffeescript/index.html index 70e77fb..d5d583d 100644 --- a/coffeescript/index.html +++ b/coffeescript/index.html @@ -10,7 +10,7 @@ -
+

diff --git a/coffeescript/site/site.css b/coffeescript/site/site.css index 6acb047..53629be 100644 --- a/coffeescript/site/site.css +++ b/coffeescript/site/site.css @@ -39,7 +39,6 @@ select, input, textarea, button { font:99% sans-serif; } pre, code, kbd, samp { font-family: monospace, sans-serif; } -html { overflow: hidden } a:hover, a:active { outline: none; } ul, ol { margin-left: 2em; } ol { list-style-type: decimal; } @@ -87,18 +86,32 @@ h1, h2, h3, h4, h5, h6 { font-weight: bold; } a { text-decoration: none } -/* Mad typesets and font colours */ body, a { color: #000 } aside { color: #ccc } h1 { font-family: 'ff-meta-web-pro' } p, li { font-family: 'ff-meta-web-pro-condensed' } +code { font-family: 'Monaco', monospace; background: #f8f8ff } -/* Ze grid! */ -#book { width: 900px; margin: 0 auto; padding: 3% 2em; box-shadow: 0 0 5px rgba(0,0,0,0.2); } +#book { width: 900px; margin: 0 auto 2em; padding: 3% 2em; box-shadow: 0 0 5px rgba(0,0,0,0.2); } +#book.toc { padding: 3% 2em } +#book.chapter { padding: 1% 2em } -#book > h1 { font-size: 3em; text-align: center; } -#book > h1 svg { margin-bottom: -7px; } -#book > h1 aside { font-size: 1rem } +#book.toc > h1 { font-size: 3em; text-align: center; } +#book.toc > h1 svg { margin-bottom: -7px; } +#book.toc > h1 aside { font-size: 1rem } + +#book.chapter > h1 { font-size: 1.4em; margin-bottom: 1em } +#book.toc > h1 svg { margin-bottom: -7px } + +#content > h1, +#content > h2 { margin: 0.5em 0 } +#content > h1 { font-size: 2.6em } +#content > h2 { font-size: 2em } +#content > p { font-size: 1.3em; margin: 0.5em 0; text-indent: 1em } +#content a { color: #f88a3f } +#content p code { font-size: 90% } +#content .wrap { min-width: 70%; position: relative } +#content .wrap .handle { position: absolute; top: 0; right: 0 } #toc { margin: 3em 0 } #toc ol { counter-reset: li; margin-left: 130px } @@ -109,7 +122,6 @@ p, li { font-family: 'ff-meta-web-pro-condensed' } background: -moz-linear-gradient(19% 75% 90deg,#fceaa1, #f8d43f); } #toc ol > li:hover aside { color: #000 } - #toc ol > li:before { position: absolute; top: 0.3em; left: -2em; @@ -125,4 +137,5 @@ p, li { font-family: 'ff-meta-web-pro-condensed' } } #toc li a { font-size: 2em } -#toc aside { font-size: 1.4rem } \ No newline at end of file +#toc aside { font-size: 1.4rem } + From c4244a1f13b23e14ccfadf9a6eb9f62bac123e33 Mon Sep 17 00:00:00 2001 From: Julio Cesar Ody Date: Fri, 24 Jun 2011 18:33:58 +1000 Subject: [PATCH 3/4] I forget --- coffeescript/02_syntax.html | 182 ++++++++++++++---------------------- coffeescript/index.html | 3 +- coffeescript/site/site.css | 26 ++++-- 3 files changed, 94 insertions(+), 117 deletions(-) diff --git a/coffeescript/02_syntax.html b/coffeescript/02_syntax.html index 2e9b1a9..8d60f7e 100644 --- a/coffeescript/02_syntax.html +++ b/coffeescript/02_syntax.html @@ -48,8 +48,7 @@

Variables & Scope

-
myVariable = "test"
-        
+
myVariable = "test"

Notice the dark grey box in the top right of the code example above. Give that a click, and the code will toggle between CoffeeScript and the compiled JavaScript. This is rendered right inside the page at runtime, so you assured the compiled output is accurate.

@@ -60,8 +59,7 @@

Variables & Scope

exports = this
-          exports.MyVariable = "foo-bar"
-        
+exports.MyVariable = "foo-bar"

In the root context, this is equal to the global object, and by creating a local exports variable you're making it really obvious to anyone reading your code exactly which global variables a script is creating. Additionally, it paves the way for CommonJS modules, which we're going to cover later in the book.

@@ -73,8 +71,7 @@

Functions

-
func = -> "bar"
-        
+
func = -> "bar"

You can see in the resultant compilation, the -> is turned into a function statement, and the "bar" string is automatically returned.

@@ -83,9 +80,8 @@

Functions

func = ->
-          # An extra line
-          "bar"
-        
+ # An extra line + "bar"

Function arguments

@@ -93,34 +89,30 @@

Function arguments

-
times = (a, b) -> a * b
-        
+
times = (a, b) -> a * b

CoffeeScript supports default arguments too, for example:

-
times = (a = 1, b = 2) -> a * 2
-        
+
times = (a = 1, b = 2) -> a * 2

You can also use splats to accept multiple arguments, denoted by ...

sum = (nums...) -> 
-          result = 0
-          nums.forEach (n) -> result += n
-          result
-        
+ result = 0 + nums.forEach (n) -> result += n + result

In the example above, nums is an array of all the arguments passed to the function. It's not an arguments object, but rather a real array, so you don't need to concern yourself with Array.prototype.splice or jQuery.makeArray() if you want to manipulate it.

trigger = (events...) ->
-          events.splice(1, 0, this)
-          this.parent.trigger.apply(events)
-        
+ events.splice(1, 0, this) + this.parent.trigger.apply(events)

Function invocation

@@ -129,22 +121,19 @@

Function invocation

a = "Howdy!"
+alert a
+# Equivalent to:
+alert(a)
 
-          alert a
-          # Equivalent to:
-          alert(a)
-
-          alert inspect a
-          # Equivalent to:
-          alert(inspect(a))
-        
+alert inspect a +# Equivalent to: +alert(inspect(a))

Although parenthesis is optional, I'd recommend using it if it's not immediately obvious what's being invoked, and with which arguments. In the last example, with inspect, I'd definitely recommend wrapping at least the inspect invocation in parens.

-
alert inspect(a)
-        
+
alert inspect(a)

If you don't pass any arguments with an invocation, CoffeeScript has no way of working out if you intend to invoke the function, or just treat it like a variable. In this respect, CoffeeScript's behavior differs from Ruby which always invokes references to functions, and more similar to Python's. This has been the source of a few errors in my CoffeeScript programs, so it's worth keeping an eye out for cases where you intend to call a function without any arguments, and include parenthesis.

@@ -157,8 +146,7 @@

Function context

this.clickHandler = -> alert "clicked"
-          element.addEventListener "click", (e) => this.clickHandler(e)
-        
+element.addEventListener "click", (e) => this.clickHandler(e)

The reason you might want to do this, is that callbacks from addEventListener() are executed in the context of the element, i.e. this equals the element. If you want to keep this equal to the local context, without doing a self = this dance, fat arrows are the way to go.

@@ -172,16 +160,15 @@

Object literals & array definition

object1 = {one: 1, two: 2}
 
-          # Without braces
-          object2 = one: 1, two: 2
+# Without braces
+object2 = one: 1, two: 2
 
-          # Using new lines instead of commas
-          object3 = 
-          one: 1
-          two: 2
+# Using new lines instead of commas
+object3 = 
+one: 1
+two: 2
 
-          User.create(name: "John Smith")
-        
+User.create(name: "John Smith")

Likewise, arrays can use whitespace instead of comma separators, although the square brackets ([]) are still required.

@@ -189,14 +176,13 @@

Object literals & array definition

array1 = [1, 2, 3]
 
-          array2 = [
-          1
-          2
-          3
-          ]
+array2 = [
+  1
+  2
+  3
+]
 
-          array3 = [1,2,3,]
-        
+array3 = [1,2,3,]

As you can see in the example above, CoffeeScript has also stripped the trailing comma in array3, another common source of cross-browser errors.

@@ -207,14 +193,13 @@

Flow control

if true == true
-          "We're ok"
+  "We're ok"
 
-          if true != true then "Panic"
+if true != true then "Panic"
 
-          # Equivalent to:
-          #  (1 > 0) ? "Ok" : "Y2K!"
-          if 1 > 0 then "Ok" else "Y2K!"
-        
+# Equivalent to: +# (1 > 0) ? "Ok" : "Y2K!" +if 1 > 0 then "Ok" else "Y2K!"

As you can see above, if the if statement is on one line, you'll need to use the then keyword, so CoffeeScript knows when the block begins. Conditional operators (?:) are not supported, instead you should use a single line if/else statement.

@@ -222,31 +207,27 @@

Flow control

-
alert "It's cold!" if heat < 5
-        
+
alert "It's cold!" if heat < 5

Instead of using the exclamation mark (!) for negation, you can also use the not keyword - which can sometimes make your code more readable as exclamation marks can be easy to miss.

-
if not true then "Panic"
-        
+
if not true then "Panic"

In the example above, we could also use the CoffeeScript's unless statement, the opposite of if.

unless true
-          "Panic"
-        
+ "Panic"

In a similar fashion to not, CoffeeScript also introduces the is statement, which translates to ===.

if true is 1
-          "Type coercion fixed!"
-        
+ "Type coercion fixed!"

You may have noticed in the examples above, that CoffeeScript is converting == operators into === and != into !==. This is one of my favorite features to the language, and yet one of the most simple. What's the reasoning behind this? Well frankly JavaScript's type coercion is a bit odd, and its equality operator coerces types in order to compare them, leading to some confusing behaviors and the source of many bugs.

@@ -255,22 +236,20 @@

Flow control

""           ==   "0"           // false
-          0            ==   ""            // true
-          0            ==   "0"           // true
-          false        ==   "false"       // false
-          false        ==   "0"           // true
-          false        ==   undefined     // false
-          false        ==   null          // false
-          null         ==   undefined     // true
-          " \t\r\n"    ==   0             // true
-        
+0 == "" // true +0 == "0" // true +false == "false" // false +false == "0" // true +false == undefined // false +false == null // false +null == undefined // true +" \t\r\n" == 0 // true

The solution is to use the strict equality operator, which consists of three equal signs: ===. It works exactly like the normal equality operator, but without any type coercion. It's recommended to always use the strict equality operator, and explicitly convert types if needs be. As mentioned earlier, this is the default in CoffeeScript, with any weak equality operators being converted into strict ones.

-
if 10 == "+10" then "type coercion fail"
-        
+
if 10 == "+10" then "type coercion fail"

String interpolation

@@ -279,11 +258,10 @@

String interpolation

favourite_color = "Blue. No, yel..."
-          question = "Bridgekeeper: What... is your favourite color?
-          Galahad: #{favourite_color}
-          Bridgekeeper: Wrong!
-          "
-        
+question = "Bridgekeeper: What... is your favourite color? + Galahad: #{favourite_color} + Bridgekeeper: Wrong! + "

As you can see in the example above, multiline strings are also allowed, without having to prefix each line with a +:

@@ -294,48 +272,42 @@

Loops and Comprehensions

for name in ["Roger", "Roderick", "Brian"]
-          alert "Release #{name}"
-        
+ alert "Release #{name}"

If you need the current iteration index, just pass an extra argument:

for name, i in ["Roger the pickpocket", "Roderick the robber"]
-          alert "#{i} - Release #{name}"
-        
+ alert "#{i} - Release #{name}"

You can also iterate on one line, using the postfix form.

-
release prisoner for prisoner in ["Roger", "Roderick", "Brian"]
-        
+
release prisoner for prisoner in ["Roger", "Roderick", "Brian"]

As with Python comprehensions, you can filter them:

prisoners = ["Roger", "Roderick", "Brian"]
-          release prisoner for prisoner in prisoners when prisoner[0] is "R" 
-        
+ release prisoner for prisoner in prisoners when prisoner[0] is "R"

You can also use comprehensions for iterating over properties in objects. Instead of the in keyword, use of.

names = sam: seaborn, donna: moss
-          alert("#{first} #{last}") for first, last of names
-        
+alert("#{first} #{last}") for first, last of names

The only low-level loop that CoffeeScript exposes is the while loop. This has similar behavior to the while loop in pure JavaScript, but has the added advantage that it returns an array of results, i.e. like the Array.prototype.map() function.

num = 6
-          minstrel = while num -= 1
-          num + " Brave Sir Robin ran away"
-        
+minstrel = while num -= 1 + num + " Brave Sir Robin ran away"

Arrays

@@ -350,31 +322,27 @@

Arrays

-
firstTwo = ["one", "two", "three"][0..1]
-        
+
firstTwo = ["one", "two", "three"][0..1]

In the example above, the range returns a new array, containing only the first two elements of the original array. You can also use the same syntax for replacing an array segment with another array.

numbers = [0..9]
-          numbers[3..5] = [-3, -4, -5]
-        
+numbers[3..5] = [-3, -4, -5]

What's neat, is that JavaScript allows you to call slice() on strings too, so you can use ranges with string to return a new subset of characters.

-
my = "my string"[0..2]
-        
+
my = "my string"[0..2]

Checking to see if a value exists inside an array is always a bore in JavaScript, particular as indexOf() doesn't yet have full cross-browser support (IE, I'm talking about you). CoffeeScript solves this with the in operator, for example.

words = ["rattled", "roudy", "rebbles", "ranks"]
-          alert "Stop wagging me" if "ranks" in words 
-        
+alert "Stop wagging me" if "ranks" in words

Aliases & the Existential Operator

@@ -382,43 +350,37 @@

Aliases & the Existential Operator

-
@saviour = true
-        
+
@saviour = true

Another is ::, which is an alias for prototype

-
User::first = -> @records[0]
-        
+
User::first = -> @records[0]

Using if for null checks in JavaScript is common, but has a few pitfalls in that empty strings and zero are both coerced into false, which can catch you out. CoffeeScript existential operator ? returns true unless a variable is null or undefined, similar to Ruby's nil?.

-
praise if brian?
-        
+
praise if brian?

You can also use it in place of the || operator:

-
velocity = southern ? 40
-        
+
velocity = southern ? 40

If you're using a null check before accessing a property, you can skip that by placing the existential operator right before the opening parens. This is similar to Ruby's try method.

-
blackKnight.getLegs()?.kick()
-        
+
blackKnight.getLegs()?.kick()

You can safely call a value in the same manner, checking function-ness beforehand.

-
whiteKnight.guard? us
-        
+
whiteKnight.guard? us
diff --git a/coffeescript/index.html b/coffeescript/index.html index d5d583d..a0f2db7 100644 --- a/coffeescript/index.html +++ b/coffeescript/index.html @@ -5,7 +5,8 @@ The Little Book on CoffeeScript - + + diff --git a/coffeescript/site/site.css b/coffeescript/site/site.css index 53629be..7a354e7 100644 --- a/coffeescript/site/site.css +++ b/coffeescript/site/site.css @@ -51,7 +51,6 @@ sup { top: -0.5em; } sub { bottom: -0.25em; } pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; - padding: 15px; } textarea { overflow: auto; } .ie6 legend, .ie7 legend { margin-left: -7px; } @@ -84,7 +83,7 @@ h1, h2, h3, h4, h5, h6 { font-weight: bold; } .clearfix { zoom: 1; } /* End reset */ -a { text-decoration: none } +a { text-decoration: none; outline: none } body, a { color: #000 } aside { color: #ccc } @@ -100,7 +99,7 @@ code { font-family: 'Monaco', monospace; background: #f8f8ff } #book.toc > h1 svg { margin-bottom: -7px; } #book.toc > h1 aside { font-size: 1rem } -#book.chapter > h1 { font-size: 1.4em; margin-bottom: 1em } +#book.chapter > h1 { font-size: 1.4em; margin-bottom: 1em; text-align: right } #book.toc > h1 svg { margin-bottom: -7px } #content > h1, @@ -109,9 +108,21 @@ code { font-family: 'Monaco', monospace; background: #f8f8ff } #content > h2 { font-size: 2em } #content > p { font-size: 1.3em; margin: 0.5em 0; text-indent: 1em } #content a { color: #f88a3f } + +/* Codes */ #content p code { font-size: 90% } +#content > pre code, +#content .wrap code { width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box } #content .wrap { min-width: 70%; position: relative } -#content .wrap .handle { position: absolute; top: 0; right: 0 } +#content .wrap .handle { + position: absolute; + top: 0; right: 0; + width: 10px; height: 10px; + font-size: 0.8em; + content: '\2668'; + background: #BFBDC9; + border: 0; +} #toc { margin: 3em 0 } #toc ol { counter-reset: li; margin-left: 130px } @@ -124,7 +135,7 @@ code { font-family: 'Monaco', monospace; background: #f8f8ff } #toc ol > li:hover aside { color: #000 } #toc ol > li:before { position: absolute; - top: 0.3em; left: -2em; + top: 0.3em; left: -2em; width: 1em; padding: 0.15em 0.25em; text-align: center; @@ -136,6 +147,9 @@ code { font-family: 'Monaco', monospace; background: #f8f8ff } border-radius: 0.75em ; } -#toc li a { font-size: 2em } +#toc li a { display: block; font-size: 2em } #toc aside { font-size: 1.4rem } +@media all and (max-device-width) { + +} \ No newline at end of file From 09eba239231485bf88c653fe104e07d18db4244f Mon Sep 17 00:00:00 2001 From: Julio Cesar Ody Date: Sat, 25 Jun 2011 23:44:00 +1000 Subject: [PATCH 4/4] ok! ported all pages across --- coffeescript/03_classes.html | 204 +++++++++++++-------------- coffeescript/04_idioms.html | 224 ++++++++++++++---------------- coffeescript/05_applications.html | 200 +++++++++++++------------- coffeescript/site/site.css | 2 + 4 files changed, 304 insertions(+), 326 deletions(-) diff --git a/coffeescript/03_classes.html b/coffeescript/03_classes.html index 4ea38c7..f5220f8 100644 --- a/coffeescript/03_classes.html +++ b/coffeescript/03_classes.html @@ -1,131 +1,117 @@ - - -The Little Book on CoffeeScript - Classes - - - - - - - - - -
-
-

The Little Book on CoffeeScript

-
- -
- - - -

Classes

- -

Classes in JavaScript seem to have the kind of effect that cloves of garlic have to Dracula for some purists; although, let's be honest, if you're that way inclined, you're unlikely to be reading a book on CoffeeScript. However, it turns out that classes are just as damn useful in JavaScript as they are in other languages and CoffeeScript provides a great abstraction.

- -

Behind the scenes, CoffeeScript is using JavaScript's native prototype to create classes; adding a bit of syntactic sugar for static property inheritance and context persistence. As a developer all that's exposed to you is the class keyword.

- -

- -
class Animal
-
- -

In the example above, Animal is the name of the class, and also the name of the resultant variable that you can use to create instances. Behind the scenes CoffeeScript is using construction functions, which means you can instantiate classes using the new operator.

- -

- -
animal = new Animal
-
- -

Defining constructors (functions that get invoked upon instantiation) is simple, just use a function named constructor. This is akin to using Ruby's initialize or Python's __init__.

- -

- -
class Animal
+  
+    
+    The Little Book on CoffeeScript - Introduction
+    
+    
+    
+        
+  
+  
+    
+

+ + + + + The Little Book on CoffeeScript +

+
+

Classes

+

Classes in JavaScript seem to have the kind of effect that cloves of garlic have to Dracula for some purists; although, let's be honest, if you're that way inclined, you're unlikely to be reading a book on CoffeeScript. However, it turns out that classes are just as damn useful in JavaScript as they are in other languages and CoffeeScript provides a great abstraction.

+

Behind the scenes, CoffeeScript is using JavaScript's native prototype to create classes; adding a bit of syntactic sugar for static property inheritance and context persistence. As a developer all that's exposed to you is the class keyword.

+

+ +
class Animal
+ +

In the example above, Animal is the name of the class, and also the name of the resultant variable that you can use to create instances. Behind the scenes CoffeeScript is using construction functions, which means you can instantiate classes using the new operator.

+ +

+ +
animal = new Animal
+ +

Defining constructors (functions that get invoked upon instantiation) is simple, just use a function named constructor. This is akin to using Ruby's initialize or Python's __init__.

+ +

+ +
class Animal
   constructor: (name) ->
-    @name = name
-
+ @name = name
-

In fact, CoffeeScript provides a shorthand for the common pattern of setting instance properties. By prefixing argument's with @, CoffeeScript will automatically set the arguments as instance properties in the constructor. Indeed, this shorthand will also work for normal functions outside classes. The example below is equivalent to the last example, where we set the instance properties manually.

+

In fact, CoffeeScript provides a shorthand for the common pattern of setting instance properties. By prefixing argument's with @, CoffeeScript will automatically set the arguments as instance properties in the constructor. Indeed, this shorthand will also work for normal functions outside classes. The example below is equivalent to the last example, where we set the instance properties manually.

-

+

-
class Animal
-  constructor: (@name) ->
-
+
class Animal
+  constructor: (@name) ->
-

As you'd expect, any arguments passed on instantiation are proxied to the constructor function.

+

As you'd expect, any arguments passed on instantiation are proxied to the constructor function.

-

+

-
animal = new Animal("Parrot")
-alert "Animal is a #{animal.name}"
-
+
animal = new Animal("Parrot")
+alert "Animal is a #{animal.name}"
-

Instance properties

+

Instance properties

-

Adding additional instance properties to a class is very straightforward, it's exactly the syntax as adding properties onto an object. Just make sure properties are indented correctly inside the class body.

+

Adding additional instance properties to a class is very straightforward, it's exactly the syntax as adding properties onto an object. Just make sure properties are indented correctly inside the class body.

-

+

-
class Animal
+        
class Animal
   price: 5
 
   sell: (customer) ->
 
 animal = new Animal
-animal.sell(new Customer)
-
+animal.sell(new Customer)
-

Context changes are rife within JavaScript, and earlier in the Syntax chapter we talked about how CoffeeScript can lock the value of this to a particular context using a fat arrow function: =>. This ensures that whatever context a function is called under, it'll always execute inside the context it was created in. CoffeeScript has extended support for fat arrows to classes, so by using a fat arrow for an instance method you'll ensure that it's invoked in the correct context, and that this is always equal to the current instance.

+

Context changes are rife within JavaScript, and earlier in the Syntax chapter we talked about how CoffeeScript can lock the value of this to a particular context using a fat arrow function: =>. This ensures that whatever context a function is called under, it'll always execute inside the context it was created in. CoffeeScript has extended support for fat arrows to classes, so by using a fat arrow for an instance method you'll ensure that it's invoked in the correct context, and that this is always equal to the current instance.

-

+

-
class Animal
+        
class Animal
   price: 5
 
   sell: =>
     alert "Give me #{@price} shillings!"
 
 animal = new Animal
-$("#sell").click(animal.sell)
-
+$("#sell").click(animal.sell)
-

As demonstrated in the example above, this is especially useful in event callbacks. Normally the sell() function would be invoked in the context of the #sell element. However, by using fat arrows for sell(), we're ensuring the correct context is being maintained, and that this.price equals 5.

+

As demonstrated in the example above, this is especially useful in event callbacks. Normally the sell() function would be invoked in the context of the #sell element. However, by using fat arrows for sell(), we're ensuring the correct context is being maintained, and that this.price equals 5.

-

Static properties

+

Static properties

-

How about defining class (i.e. static) properties? Well, it turns out that inside a class definition, this refers to the class object. In other words you can set class properties by setting them directly on this.

+

How about defining class (i.e. static) properties? Well, it turns out that inside a class definition, this refers to the class object. In other words you can set class properties by setting them directly on this.

-

+

-
class Animal
+        
class Animal
   this.find = (name) ->      
 
-Animal.find("Parrot")
-
+Animal.find("Parrot")
-

In fact, as you may remember, CoffeeScript aliases this to @, which lets you write static properties even more succinctly:

+

In fact, as you may remember, CoffeeScript aliases this to @, which lets you write static properties even more succinctly:

-

+

-
class Animal
+        
class Animal
   @find: (name) ->
 
-Animal.find("Parrot")
-
+Animal.find("Parrot")
-

Inheritance & Super

+

Inheritance & Super

-

It wouldn't be a proper class implementation without some form of inheritance, and CoffeeScript doesn't disappoint. You can inherit from another class by using the extends keyword. In the example below, Parrot extends from Animal, inheriting all of its instance properties, such as alive()

+

It wouldn't be a proper class implementation without some form of inheritance, and CoffeeScript doesn't disappoint. You can inherit from another class by using the extends keyword. In the example below, Parrot extends from Animal, inheriting all of its instance properties, such as alive()

-

+

-
class Animal
+        
class Animal
   constructor: (@name) ->
 
   alive: ->
@@ -136,18 +122,17 @@ 

Inheritance & Super

super("Parrot") dead: -> - not @alive() -
+ not @alive()
-

You'll notice that in the example above, we're using the super() keyword. Behind the scenes, this is translated into a function call on the class' parent prototype, invoked in the current context. In this case, it'll be Parrot.__super__.constructor.call(this, "Parrot");. In practice, this will have exactly the same effect as invoking super in Ruby or Python, invoking the overridden inherited function.

+

You'll notice that in the example above, we're using the super() keyword. Behind the scenes, this is translated into a function call on the class' parent prototype, invoked in the current context. In this case, it'll be Parrot.__super__.constructor.call(this, "Parrot");. In practice, this will have exactly the same effect as invoking super in Ruby or Python, invoking the overridden inherited function.

-

Unless you override the constructor, by default CoffeeScript will invoke the parent's constructor when instances are created.

+

Unless you override the constructor, by default CoffeeScript will invoke the parent's constructor when instances are created.

-

CoffeeScript uses prototypal inheritance to automatically inherit all of a class's instance properties. This ensures that classes are dynamic; even if you add properties to a parent class after a child has been created, the property will still be propagated to all of its inherited children.

+

CoffeeScript uses prototypal inheritance to automatically inherit all of a class's instance properties. This ensures that classes are dynamic; even if you add properties to a parent class after a child has been created, the property will still be propagated to all of its inherited children.

-

+

-
class Animal
+        
class Animal
   constructor: (@name) ->
 
 class Parrot extends Animal
@@ -155,18 +140,17 @@ 

Inheritance & Super

Animal::rip = true parrot = new Parrot("Macaw") -alert("This parrot is no more") if parrot.rip -
+alert("This parrot is no more") if parrot.rip
-

It's worth pointing out though that static properties are copied to subclasses, rather than inherited using prototype as instance properties are. This is due to implementation details with JavaScript's prototypal architecture, and is a difficult problem to work around.

+

It's worth pointing out though that static properties are copied to subclasses, rather than inherited using prototype as instance properties are. This is due to implementation details with JavaScript's prototypal architecture, and is a difficult problem to work around.

-

Mixins

+

Mixins

-

Mixins are not something supported natively by CoffeeScript, for the good reason that they can be trivially implemented. For example, here's two functions, extend() and include() that'll add class and instance properties respectively to a class.

+

Mixins are not something supported natively by CoffeeScript, for the good reason that they can be trivially implemented. For example, here's two functions, extend() and include() that'll add class and instance properties respectively to a class.

-

+

-
extend = (obj, mixin) ->
+        
extend = (obj, mixin) ->
   obj[name] = method for name, method of mixin        
   obj
 
@@ -177,12 +161,18 @@ 

Mixins

include Parrot, isDeceased: true -(new Parrot).isDeceased -
- -

Mixins are a great pattern for sharing common logic between modules when inheritance is not suited. The advantage of mixins, is that you can include multiple ones, compared to inheritance where only one class can be inherited from.

+(new Parrot).isDeceased
+ +

Mixins are a great pattern for sharing common logic between modules when inheritance is not suited. The advantage of mixins, is that you can include multiple ones, compared to inheritance where only one class can be inherited from.

+ + + + + + + + + -
-
- - \ No newline at end of file diff --git a/coffeescript/04_idioms.html b/coffeescript/04_idioms.html index 6f18cf0..2a8ba40 100644 --- a/coffeescript/04_idioms.html +++ b/coffeescript/04_idioms.html @@ -1,200 +1,188 @@ - - -The Little Book on CoffeeScript - Idioms - - - - - - - - - -
-
-

The Little Book on CoffeeScript

-
- -
- - - -

Common CoffeeScript idioms

- -

Every language has a set of idioms and practices, and CoffeeScript is no exception. This chapter will explore those conventions, and show you some JavaScript to CoffeeScript comparisons so you can get a practical sense of the language.

- -

Each

- -

In JavaScript to iterate over every item in an array, we could either use the newly added forEach() function, or an old C style for loop. If you're planning to use some of JavaScript's latest features introduced in ECMAScript 5, I advise you also include a shim in the page to emulate support in older browsers.

- -
for (var i=0; i < array.length; i++)
+  
+    
+    The Little Book on CoffeeScript - Introduction
+    
+    
+    
+        
+  
+  
+    
+

+ + + + + The Little Book on CoffeeScript +

+
+

Common CoffeeScript idioms

+ +

Every language has a set of idioms and practices, and CoffeeScript is no exception. This chapter will explore those conventions, and show you some JavaScript to CoffeeScript comparisons so you can get a practical sense of the language.

+ +

Each

+ +

In JavaScript to iterate over every item in an array, we could either use the newly added forEach() function, or an old C style for loop. If you're planning to use some of JavaScript's latest features introduced in ECMAScript 5, I advise you also include a shim in the page to emulate support in older browsers.

+ +
for (var i=0; i < array.length; i++)
   myFunction(array[i]);
 
 array.forEach(function(item, i){
   myFunction(item)
-});
-
+});
-

Although the forEach() syntax is much more succinct and readable, it suffers from the drawback that the callback function will be invoked every iteration of the array, and is therefore much slower than the equivalent for loop. Let's see how it looks in CoffeeScript.

+

Although the forEach() syntax is much more succinct and readable, it suffers from the drawback that the callback function will be invoked every iteration of the array, and is therefore much slower than the equivalent for loop. Let's see how it looks in CoffeeScript.

-

+

-
myFunction(item) for item in array
-
+
myFunction(item) for item in array
-

It's a readable and concise syntax, I'm sure you'll agree, and what's great is that it compiles to a for loop behind the scenes. In other words CoffeeScript's syntax offers the same expressiveness as forEach(), but without the speed and shimming caveats.

+

It's a readable and concise syntax, I'm sure you'll agree, and what's great is that it compiles to a for loop behind the scenes. In other words CoffeeScript's syntax offers the same expressiveness as forEach(), but without the speed and shimming caveats.

-

Map

+

Map

-

As with forEach(), ES5 also includes a native map function that has a much more succinct syntax than the classic for loop, namely map(). Unfortunately it suffers from much the same caveats that forEach() does, its speed is greatly reduced due to the function calls.

+

As with forEach(), ES5 also includes a native map function that has a much more succinct syntax than the classic for loop, namely map(). Unfortunately it suffers from much the same caveats that forEach() does, its speed is greatly reduced due to the function calls.

-
var result = []
+        
var result = []
 for (var i=0; i < array.length; i++)
   result.push(array[i].name)
 
 var result = array.map(function(item, i){
   return item.name;
-});
-
+});
-

As we covered in the syntax chapter, CoffeeScript's comprehensions can be used to get the same behavior as map(). Notice we're surrounding the comprehension with parens, which is absolutely critical in ensuring the comprehension returns what you'd expect, the mapped array.

+

As we covered in the syntax chapter, CoffeeScript's comprehensions can be used to get the same behavior as map(). Notice we're surrounding the comprehension with parens, which is absolutely critical in ensuring the comprehension returns what you'd expect, the mapped array.

-

+

-
result = (item.name for item in array)
-
+
result = (item.name for item in array)
+        
-

Select

+

Select

-

Again, ES5 has a utility function filter() for reducing arrays:

+

Again, ES5 has a utility function filter() for reducing arrays:

-
var result = []
+        
var result = []
 for (var i=0; i < array.length; i++)
   if (array[i].name == "test")
     result.push(array[i])
 
 result = array.filter(function(item, i){
   return item.name == "test"
-});
-
+});
-

CoffeeScript's basic syntax uses the when keyword to filter items with a comparison. Behind the scenes a for loop is generated. The whole execution is performed in an anonymous function to ward against scope leakage and variable conflict.

+

CoffeeScript's basic syntax uses the when keyword to filter items with a comparison. Behind the scenes a for loop is generated. The whole execution is performed in an anonymous function to ward against scope leakage and variable conflict.

-

+

-
result = (item for item in array when item.name is "test")
-
+
result = (item for item in array when item.name is "test")
-

Don't forgot to include the parens, as otherwise result will be the last item in the array. -CoffeeScript's comprehensions are so flexible that they allow you to do powerful selections as in the following example:

+

Don't forgot to include the parens, as otherwise result will be the last item in the array. + CoffeeScript's comprehensions are so flexible that they allow you to do powerful selections as in the following example:

-

+

-
passed = []
+        
passed = []
 failed = []
 (if score > 60 then passed else failed).push score for score in [49, 58, 76, 82, 88, 90]
 
 # Or
-passed = (score for score in scores when score > 60)
-
+passed = (score for score in scores when score > 60)
-

Includes

+

Includes

-

Checking to see if a value is inside an array is typically done with indexOf(), which rather mind-bogglingly still requires a shim, as Internet Explorer hasn't implemented it.

+

Checking to see if a value is inside an array is typically done with indexOf(), which rather mind-bogglingly still requires a shim, as Internet Explorer hasn't implemented it.

-
var included = (array.indexOf("test") != -1)
-
+
var included = (array.indexOf("test") != -1)
-

CoffeeScript has a neat alternative to this which Pythonists may recognize, namely in.

+

CoffeeScript has a neat alternative to this which Pythonists may recognize, namely in.

-

+

-
included = "test" in array
-
+
included = "test" in array
-

Behind the scenes, CoffeeScript is using Array.prototype.indexOf(), and shimming if necessary, to detect if the value is inside the array. Unfortunately this means the same in syntax won't work for strings. We need to revert back to using indexOf() and testing if the result is negative:

+

Behind the scenes, CoffeeScript is using Array.prototype.indexOf(), and shimming if necessary, to detect if the value is inside the array. Unfortunately this means the same in syntax won't work for strings. We need to revert back to using indexOf() and testing if the result is negative:

-

+

-
included = "a long test string".indexOf("test") isnt -1
-
+
included = "a long test string".indexOf("test") isnt -1
-

Or even better, hijack the bitwise operator so we don't have to do a -1 comparison.

+

Or even better, hijack the bitwise operator so we don't have to do a -1 comparison.

-

+

-
string   = "a long test string"
-included = !!~ string.indexOf "test"
-
+
string   = "a long test string"
+included = !!~ string.indexOf "test"
-

Min/Max

+

Min/Max

-

This technique is not specific to CoffeeScript, but I thought it useful to demonstrate anyway. Math.max and Math.min take multiple arguments, so you can easily use ... to pass an array to them, retrieving the maximum and minimum values in the array.

+

This technique is not specific to CoffeeScript, but I thought it useful to demonstrate anyway. Math.max and Math.min take multiple arguments, so you can easily use ... to pass an array to them, retrieving the maximum and minimum values in the array.

-

+

-
Math.max [14, 35, -7, 46, 98]... # 98
-Math.min [14, 35, -7, 46, 98]... # -7
-
+
Math.max [14, 35, -7, 46, 98]... # 98
+Math.min [14, 35, -7, 46, 98]... # -7
-

And/or

+

And/or

-

CoffeeScript style guides indicates that or is preferred over ||, and and is preferred over &&. I can see why, as the former is somewhat more readable. Nevertheless, the two styles have identical results.

+

CoffeeScript style guides indicates that or is preferred over ||, and and is preferred over &&. I can see why, as the former is somewhat more readable. Nevertheless, the two styles have identical results.

-

This preference over more English style code also applies to using is over == and isnt over !=.

+

This preference over more English style code also applies to using is over == and isnt over !=.

-

+

-
string = "migrating coconuts"
+        
string = "migrating coconuts"
 string == string # true
-string is string # true
-
+string is string # true
-

One extremely nice addition to CoffeeScript is the 'or equals', which is a pattern Rubyists may recognize as ||=:

+

One extremely nice addition to CoffeeScript is the 'or equals', which is a pattern Rubyists may recognize as ||=:

-

+

-
hash or= {}
-
+
hash or= {}
-

If hash evaluates to false, then it's set to an empty object. It's important to note here that this expression also recognizes 0, "" and [] as false. If that isn't your intention, you'll need to use CoffeeScript's existential operator, which only gets activated if hash is undefined or null:

+

If hash evaluates to false, then it's set to an empty object. It's important to note here that this expression also recognizes 0, "" and [] as false. If that isn't your intention, you'll need to use CoffeeScript's existential operator, which only gets activated if hash is undefined or null:

-

+

-
hash ?= {}
-
+
hash ?= {}
-

Destructuring assignments

+

Destructuring assignments

-

Destructuring assignments can be used with any depth of array and object nesting, to help pull out deeply nested properties.

+

Destructuring assignments can be used with any depth of array and object nesting, to help pull out deeply nested properties.

-
someObject = { a: 'value for a', b: 'value for b' }
+        
someObject = { a: 'value for a', b: 'value for b' }
 { a, b } = someObject
-console.log "a is '#{a}', b is '#{b}'"
-
+console.log "a is '#{a}', b is '#{b}'"
-

External libraries

+

External libraries

-

Using external libraries is exactly the same as calling functions on CoffeeScript libraries; since at the end of the day everything is compiled down to JavaScript. Using CoffeeScript with jQuery is especially elegant, due to the amount of callbacks in jQuery's API.

+

Using external libraries is exactly the same as calling functions on CoffeeScript libraries; since at the end of the day everything is compiled down to JavaScript. Using CoffeeScript with jQuery is especially elegant, due to the amount of callbacks in jQuery's API.

-

+

-
# Use local alias
+        
# Use local alias
 $ = jQuery
 
 $ ->
   # DOMContentLoaded
   $(".el").click ->
-    alert("Clicked!")
-
- -

Since all of CoffeeScript's output is wrapped in an anonymous function, we can set a local $ alias for jQuery. This will make sure that even if jQuery's no conflict mode is enabled and the $ re-defined, our script will still function as intended.

- -
-
- + alert("Clicked!") + +

Since all of CoffeeScript's output is wrapped in an anonymous function, we can set a local $ alias for jQuery. This will make sure that even if jQuery's no conflict mode is enabled and the $ re-defined, our script will still function as intended.

+ + + + + + + + \ No newline at end of file diff --git a/coffeescript/05_applications.html b/coffeescript/05_applications.html index c9ec885..4a6523f 100644 --- a/coffeescript/05_applications.html +++ b/coffeescript/05_applications.html @@ -1,78 +1,72 @@ - - -The Little Book on CoffeeScript - Applications - - - - - - - - - -
-
-

The Little Book on CoffeeScript

-
- -
- + + + The Little Book on CoffeeScript - Introduction + + + + + + +
+

+ + + + + The Little Book on CoffeeScript +

+
+

Creating Applications

+

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.

-

Creating Applications

+

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 MVC.

-

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.

+

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 JavaScript Web Applications and use a framework like Backbone or Spine. Rather than that, here we're going to cover structuring applications using CommonJS modules.

-

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 MVC.

+

Structure & CommonJS

-

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 JavaScript Web Applications and use a framework like Backbone or Spine. Rather than that, here we're going to cover structuring applications using CommonJS modules.

+

So what exactly are CommonJS modules? Well, If you've used NodeJS 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 Rhino would work for Node. Eventually these ideas transitioned back to browsers, and now we have great libraries like RequireJS and Yabble to use modules client-side.

-

Structure & CommonJS

+

Practically speaking, modules ensure that your code is run in a local namespace (code encapsulation), that you can load other modules with the require() function, and expose module properties via module.exports. Let's dive into that in a bit more depth now.

-

So what exactly are CommonJS modules? Well, If you've used NodeJS 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 Rhino would work for Node. Eventually these ideas transitioned back to browsers, and now we have great libraries like RequireJS and Yabble to use modules client-side.

+

Requiring files

-

Practically speaking, modules ensure that your code is run in a local namespace (code encapsulation), that you can load other modules with the require() function, and expose module properties via module.exports. Let's dive into that in a bit more depth now.

+

You can load in other modules and libraries using require(). Simply pass a module name and, if it's in the load path, it'll return an object representing that module. For example:

-

Requiring files

+
var User = require("models/user");
-

You can load in other modules and libraries using require(). Simply pass a module name and, if it's in the load path, it'll return an object representing that module. For example:

+

Synchronous require support is a contentious issue, but has mostly been resolved with the mainstream loader libraries and latest CommonJS proposals. 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.

-
var User = require("models/user");
-
+

Exporting properties

-

Synchronous require support is a contentious issue, but has mostly been resolved with the mainstream loader libraries and latest CommonJS proposals. 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.

+

By default, modules don't expose any properties so their contents are completely invisible to require() calls. If you want a particular property to be accessible from your module, you'll need to set it on module.exports:

-

Exporting properties

- -

By default, modules don't expose any properties so their contents are completely invisible to require() calls. If you want a particular property to be accessible from your module, you'll need to set it on module.exports:

- -
// random_module.js
+        
// random_module.js
 module.exports.myFineProperty = function(){
   // Some shizzle
-}
-
+}
-

Now, whenever this module is required then myFineProperty will be exposed:

+

Now, whenever this module is required then myFineProperty will be exposed:

-
var myFineProperty = require("random_module").myFineProperty;
-
+
var myFineProperty = require("random_module").myFineProperty;
-

Stitch it up

+

Stitch it up

-

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 Stitch library. Stitch is by Sam Stephenson, the mind behind Prototype.js 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, LESS CSS and Sass files too!

+

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 Stitch library. Stitch is by Sam Stephenson, the mind behind Prototype.js 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, LESS CSS and Sass files too!

-

First things first, let's get Stitch installed. You'll need to install Node.js and npm if you haven't already, then run:

+

First things first, let's get Stitch installed. You'll need to install Node.js and npm if you haven't already, then run:

-
npm install stitch
-
+
npm install stitch
-

Now let's create our application structure. If you're using Spine, you can automate this with Spine.App, otherwise it's something you'll need to do manually. I usually have an app folder for all the application specific code, and a lib folder for general libraries. Then anything else, including static assets, goes in the public directory.

+

Now let's create our application structure. If you're using Spine, you can automate this with Spine.App, otherwise it's something you'll need to do manually. I usually have an app folder for all the application specific code, and a lib folder for general libraries. Then anything else, including static assets, goes in the public directory.

-
app/controllers
+        
app/controllers
 app/views
 app/models
 app/lib
@@ -80,12 +74,11 @@ 

Stitch it up

public public/index.html public/css -public/css/views -
+public/css/views
-

Now to actually boot up the Stitch server. Let's create a file called server.js and fill it with the following script:

+

Now to actually boot up the Stitch server. Let's create a file called server.js and fill it with the following script:

-
#!/usr/bin/env node
+        
#!/usr/bin/env node
 var stitch  = require('stitch'),
     express = require('express'),
     util    = require('util'),
@@ -121,21 +114,19 @@ 

Stitch it up

var port = argv[0] || process.env.PORT || 9294; util.puts("Starting server on port: " + port); -app.listen(port); -
+app.listen(port);
-

Rightio, we're almost there. Now if you invoke server.js with Node you'll hopefully have a Stitch server up and running. Let's go ahead and test it out by putting an app.coffee script in the app folder. This will be the file that'll bootstrap our application.

+

Rightio, we're almost there. Now if you invoke server.js with Node you'll hopefully have a Stitch server up and running. Let's go ahead and test it out by putting an app.coffee script in the app folder. This will be the file that'll bootstrap our application.

-

+

-
module.exports = App =
-  init: ->
-    # Bootstrap this mofo
-
+
module.exports = App =
+init: ->
+  # Bootstrap this mofo
-

Now let's create our main page index.html 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 under the public directory.

+

Now let's create our main page index.html 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 under the public directory.

-
<!DOCTYPE html>
+        
<!DOCTYPE html>
 <html>
 <head>
   <meta charset=utf-8>
@@ -152,72 +143,79 @@ 

Stitch it up

</head> <body> </body> -</html> -
+</html>
-

When the page loads, our inline jQuery callback is requiring the app.coffee script (which is automatically compiled), and invoking our init() 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 require().

+

When the page loads, our inline jQuery callback is requiring the app.coffee script (which is automatically compiled), and invoking our init() 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 require().

-

+

-
# models/user.coffee
+        
# models/user.coffee
 module.exports = class User
   constructor: (@name) ->
 
 # app.coffee
-User = require("models/user")
-
+User = require("models/user")
-

JavaScript templates

+

JavaScript templates

-

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 eco templates baked right in. However, if you're using another templating library don't despair. Stitch has a rather neat feature that lets you add custom compilers for particular extensions.

+

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 eco templates baked right in. However, if you're using another templating library don't despair. Stitch has a rather neat feature that lets you add custom compilers for particular extensions.

-

For example, let's add support for the jQuery.tmpl library, which I often use instead of eco due to a few implementation details:

+

For example, let's add support for the jQuery.tmpl library, which I often use instead of eco due to a few implementation details:

-
stitch.compilers.tmpl = function(module, filename) {
+        
stitch.compilers.tmpl = function(module, filename) {
   var content = fs.readFileSync(filename, 'utf8');
   content = ["var template = jQuery.template(", JSON.stringify(content), ");", 
              "module.exports = (function(data){ return jQuery.tmpl(template, data); });\n"].join("");
   return module._compile(content, filename);
-};
-
+};
-

Notice that above we're setting a function on module.exports which, when called, will render our template. Now, let's define a template in views/users/show.tmpl:

+

Notice that above we're setting a function on module.exports which, when called, will render our template. Now, let's define a template in views/users/show.tmpl:

-
<label>Name: ${name}</label>
-
+
<label>Name: ${name}</label>
-

Since we defined a tmpl compiler handler, Stitch will automatically compile our template and include it in application.js. Then, in our application's controllers we can require the template, like it was a module, and execute it passing any data required.

+

Since we defined a tmpl compiler handler, Stitch will automatically compile our template and include it in application.js. Then, in our application's controllers we can require the template, like it was a module, and execute it passing any data required.

-
require("views/users/show")(new User("name"))
-
+
require("views/users/show")(new User("name"))
-

Bonus - 30 second deployment with Heroku

+

Bonus - 30 second deployment with Heroku

-

Heroku 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.

+

Heroku 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.

-

Firstly we need to make a Procfile, which will inform Heroku about our application.

+

Firstly we need to make a Procfile, which will inform Heroku about our application.

-
echo "web: node server.js" > Procfile
-
+
echo "web: node server.js" > Procfile
-

Now, if you haven't already, you'll need to create a local git repository for your application.

+

Now, if you haven't already, you'll need to create a local git repository for your application.

-
git init
+        
git init
 git add .
-git commit -m "First commit"    
-
+git commit -m "First commit"
-

And now to deploy the application, we'll use the heroku gem (which you'll need to install if you haven't already).

+

And now to deploy the application, we'll use the heroku gem (which you'll need to install if you haven't already).

-
heroku create myAppName --stack cedar
+        
heroku create myAppName --stack cedar
 git push heroku master
 heroku ps:scale web=1
-heroku open
-
+heroku open
+ +

That's it! Seriously, that's all there is to it. Hosting Node applications has never been easier.

+
+
+ + + + + + + + + + + + + + -

That's it! Seriously, that's all there is to it. Hosting Node applications has never been easier.

-
-
- - \ No newline at end of file diff --git a/coffeescript/site/site.css b/coffeescript/site/site.css index 7a354e7..c616a11 100644 --- a/coffeescript/site/site.css +++ b/coffeescript/site/site.css @@ -1,3 +1,5 @@ +/* Styles and markup made with ♥ by @julio_ody */ + /* html5boilerplate.com reset */ html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre,