diff --git a/.gitignore b/.gitignore index 587c487..3ae9f09 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ _site *.swo .DS_Store + +# IntelliJ IDEA / WebStorm +/.idea/ +/*.iml \ No newline at end of file diff --git a/.ruby-gemset b/.ruby-gemset new file mode 100644 index 0000000..119b7fd --- /dev/null +++ b/.ruby-gemset @@ -0,0 +1 @@ +coffeescript-cookbook diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..77fee73 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +1.9.3 diff --git a/CNAME b/CNAME deleted file mode 100644 index 75d2340..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -coffeescriptcookbook.com diff --git a/Gemfile b/Gemfile index b744ba1..b700031 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,9 @@ -source :rubygems +source 'https://rubygems.org' group :development do - gem "RedCloth", "~> 4.2" - gem "foreman", "~> 0.13" - gem "serve", "~> 1.0" - gem "jekyll", "~> 0.10" - gem "thin", "~> 1.2" + gem "github-pages" + gem "tzinfo-data" + + gem "foreman" + gem "serve" end diff --git a/Gemfile.lock b/Gemfile.lock index 8b54f2a..682c357 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,47 +1,157 @@ GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: - RedCloth (4.2.7) - activesupport (3.0.7) - classifier (1.3.3) - fast-stemmer (>= 1.0.0) - daemons (1.1.3) - directory_watcher (1.4.0) - eventmachine (0.12.10) - fast-stemmer (1.0.0) - foreman (0.13.0) - term-ansicolor (~> 1.0.5) - thor (>= 0.13.6) - i18n (0.4.2) - jekyll (0.10.0) - classifier (>= 1.3.1) - directory_watcher (>= 1.1.1) - liquid (>= 1.9.0) - maruku (>= 0.5.9) - liquid (2.2.2) - maruku (0.6.0) - syntax (>= 1.0.0) - rack (1.2.2) - serve (1.0.0) - activesupport (~> 3.0.1) - i18n (~> 0.4.1) - rack (~> 1.2.1) - tzinfo (~> 0.3.23) - syntax (1.0.0) - term-ansicolor (1.0.5) - thin (1.2.11) - daemons (>= 1.0.9) - eventmachine (>= 0.12.6) - rack (>= 1.0.0) - thor (0.14.6) - tzinfo (0.3.27) + RedCloth (4.2.9) + activesupport (3.2.22.5) + i18n (~> 0.6, >= 0.6.4) + multi_json (~> 1.0) + addressable (2.4.0) + blankslate (2.1.2.4) + classifier-reborn (2.1.0) + fast-stemmer (~> 1.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + colorator (0.1) + ethon (0.10.1) + ffi (>= 1.3.0) + execjs (2.7.0) + faraday (0.10.1) + multipart-post (>= 1.2, < 3) + fast-stemmer (1.0.2) + ffi (1.9.14) + foreman (0.82.0) + thor (~> 0.19.1) + gemoji (2.1.0) + github-pages (39) + RedCloth (= 4.2.9) + github-pages-health-check (~> 0.2) + jekyll (= 2.4.0) + jekyll-coffeescript (= 1.0.1) + jekyll-feed (= 0.3.1) + jekyll-mentions (= 0.2.1) + jekyll-redirect-from (= 0.8.0) + jekyll-sass-converter (= 1.3.0) + jekyll-sitemap (= 0.8.1) + jemoji (= 0.5.0) + kramdown (= 1.5.0) + liquid (= 2.6.2) + maruku (= 0.7.0) + mercenary (~> 0.3) + pygments.rb (= 0.6.3) + rdiscount (= 2.1.7) + redcarpet (= 3.3.2) + terminal-table (~> 1.4) + github-pages-health-check (0.3.2) + net-dns (~> 0.6) + public_suffix (~> 1.4) + typhoeus (~> 0.7) + html-pipeline (1.9.0) + activesupport (>= 2) + nokogiri (~> 1.4) + i18n (0.7.0) + jekyll (2.4.0) + classifier-reborn (~> 2.0) + colorator (~> 0.1) + jekyll-coffeescript (~> 1.0) + jekyll-gist (~> 1.0) + jekyll-paginate (~> 1.0) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 1.1) + kramdown (~> 1.3) + liquid (~> 2.6.1) + mercenary (~> 0.3.3) + pygments.rb (~> 0.6.0) + redcarpet (~> 3.1) + safe_yaml (~> 1.0) + toml (~> 0.1.0) + jekyll-coffeescript (1.0.1) + coffee-script (~> 2.2) + jekyll-feed (0.3.1) + jekyll-gist (1.4.0) + octokit (~> 4.2) + jekyll-mentions (0.2.1) + html-pipeline (~> 1.9.0) + jekyll (~> 2.0) + jekyll-paginate (1.1.0) + jekyll-redirect-from (0.8.0) + jekyll (>= 2.0) + jekyll-sass-converter (1.3.0) + sass (~> 3.2) + jekyll-sitemap (0.8.1) + jekyll-watch (1.5.0) + listen (~> 3.0, < 3.1) + jemoji (0.5.0) + gemoji (~> 2.0) + html-pipeline (~> 1.9) + jekyll (>= 2.0) + kramdown (1.5.0) + liquid (2.6.2) + listen (3.0.8) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + maruku (0.7.0) + mercenary (0.3.6) + mini_portile2 (2.1.0) + multi_json (1.12.1) + multipart-post (2.0.0) + net-dns (0.8.0) + nokogiri (1.7.0.1) + mini_portile2 (~> 2.1.0) + octokit (4.6.2) + sawyer (~> 0.8.0, >= 0.5.3) + parslet (1.5.0) + blankslate (~> 2.0) + posix-spawn (0.3.12) + public_suffix (1.5.3) + pygments.rb (0.6.3) + posix-spawn (~> 0.3.6) + yajl-ruby (~> 1.2.0) + rack (1.5.5) + rack-test (0.6.3) + rack (>= 1.0) + rb-fsevent (0.9.8) + rb-inotify (0.9.7) + ffi (>= 0.5.0) + rdiscount (2.1.7) + redcarpet (3.3.2) + safe_yaml (1.0.4) + sass (3.4.23) + sawyer (0.8.1) + addressable (>= 2.3.5, < 2.6) + faraday (~> 0.8, < 1.0) + serve (1.5.2) + activesupport (~> 3.2.12) + i18n + rack (~> 1.5.2) + rack-test (~> 0.6.2) + tilt (~> 1.3.3) + tzinfo + terminal-table (1.7.3) + unicode-display_width (~> 1.1.1) + thor (0.19.4) + thread_safe (0.3.5) + tilt (1.3.7) + toml (0.1.2) + parslet (~> 1.5.0) + typhoeus (0.8.0) + ethon (>= 0.8.0) + tzinfo (1.2.2) + thread_safe (~> 0.1) + tzinfo-data (1.2016.10) + tzinfo (>= 1.0.0) + unicode-display_width (1.1.2) + yajl-ruby (1.2.1) PLATFORMS ruby DEPENDENCIES - RedCloth (~> 4.2) - foreman (~> 0.13) - jekyll (~> 0.10) - serve (~> 1.0) - thin (~> 1.2) + foreman + github-pages + serve + tzinfo-data + +BUNDLED WITH + 1.13.7 diff --git a/Procfile b/Procfile index c41ebb6..26a670a 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -jekyll: bundle exec jekyll --auto +jekyll: bundle exec jekyll build --watch serve: bundle exec serve 4000 development _site diff --git a/README.md b/README.md index 7378cd1..ce574db 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ We want you to write recipes and make the site better! Contributing ----------- -You can find details about contributing [on the site](http://coffeescriptcookbook.com/contributing). For now here is a simple formula: +You can find details about contributing [on the site](http://coffeescript-cookbook.github.io/contributing). For now here is a simple formula: 1. Fork the repository at [GitHub](http://github.com/coffeescript-cookbook/coffeescript-cookbook.github.com) 2. Do awesomeness! @@ -16,11 +16,11 @@ You can find details about contributing [on the site](http://coffeescriptcookboo Here are some relevant links from the site. -* [Contributing](http://coffeescriptcookbook.com/contributing) -* [Recipe Template](http://coffeescriptcookbook.com/recipe-template) -* [Author's Guide](http://coffeescriptcookbook.com/authors-guide) -* [Developer's Guide](http://coffeescriptcookbook.com/developers-guide) -* [Designer's Guide](http://coffeescriptcookbook.com/designers-guide) +* [Contributing](http://coffeescript-cookbook.github.io/contributing) +* [Recipe Template](http://coffeescript-cookbook.github.io/recipe-template) +* [Author's Guide](http://coffeescript-cookbook.github.io/authors-guide) +* [Developer's Guide](http://coffeescript-cookbook.github.io/developers-guide) +* [Designer's Guide](http://coffeescript-cookbook.github.io/designers-guide) Jekyll ------ @@ -34,4 +34,4 @@ CoffeeScript Cookbook is currently implemented as a jekyll site. Jekyll is aweso License ------- -This site and all contributions are [licensed](http://coffeescriptcookbook.com/LICENSE-CC-BY) under the Creative Commons Attribution 3.0 Unported (CC BY 3.0) license. By submitting information to this site you agree to grant this license to all users of the site, and that your editing of the authors page constitutes satisfactory attribution. \ No newline at end of file +This site and all contributions are [licensed](http://coffeescript-cookbook.github.io/LICENSE-CC-BY) under the Creative Commons Attribution 3.0 Unported (CC BY 3.0) license. By submitting information to this site you agree to grant this license to all users of the site, and that your editing of the authors page constitutes satisfactory attribution. \ No newline at end of file diff --git a/_config.yml b/_config.yml index f61488e..20fa234 100644 --- a/_config.yml +++ b/_config.yml @@ -1,5 +1,8 @@ -pygments: true +baseurl: '' +safe: true +highlighter: pygments lsi: false +markdown: redcarpet exclude: - README.md - CNAME diff --git a/_data/chapters.yml b/_data/chapters.yml new file mode 100644 index 0000000..9c47918 --- /dev/null +++ b/_data/chapters.yml @@ -0,0 +1,16 @@ +--- +- Syntax +- Classes and Objects +- Strings +- Arrays +- Dates and Times +- Math +- Functions +- Metaprogramming +- jQuery +- Ajax +- Regular Expressions +- Networking +- Design Patterns +- Databases +- Testing diff --git a/_layouts/chapter.html b/_layouts/chapter.html index 39349c4..3daeeed 100644 --- a/_layouts/chapter.html +++ b/_layouts/chapter.html @@ -1,25 +1,80 @@ + + CoffeeScript Cookbook » {{ page.title }} - + + -
-

CoffeeScript Cookbook

- Chapter Index | - Contributing | - Authors | - License -
-
-

{{ page.title }}

- {{ content }} + +
+ +
+

óCoffeeScript Cookbook

+
+ +
+ +
+

{{ page.title }}

+ {{ content }} +
+ + + +
+
- - - + + + + + + diff --git a/_layouts/default.html b/_layouts/default.html index abbd566..cbb539a 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -1,24 +1,77 @@ + + CoffeeScript Cookbook » {{ page.title }} - + + -
-

CoffeeScript Cookbook

- Chapter Index | - Contributing | - Authors | - License -
-
- {{ content }} + +
+ +
+

óCoffeeScript Cookbook

+
+ +
+ +
{{ content }}
+ +
+

Don't see a recipe you want? See an entire missing chapter? Add it yourself by reading the Contributor's Guide, or request it by adding it to Wanted Recipes.

+ +
+ +
+
-
-

Don't see a recipe you want? See an entire missing chapter? Add it yourself by reading the Contributor's Guide, or request it by adding it to Wanted Recipes.

- -
- - + + + + + + diff --git a/_layouts/recipe.html b/_layouts/recipe.html index 7405cc7..eb1a03c 100644 --- a/_layouts/recipe.html +++ b/_layouts/recipe.html @@ -1,32 +1,79 @@ + + CoffeeScript Cookbook » {{ page.title }} - + + -
-

CoffeeScript Cookbook

- -
-
-

{{ page.title }}

- {{ content }} + +
+ +
+

óCoffeeScript Cookbook

+
+ +
+ +
+

{{ page.title }}

+ {{ content }} +
+ +
+

Is this recipe wrong, incomplete, or non idiomatic? Help fix it by reading the Contributor's Guide!

+ +
+
+
- + + + + + - diff --git a/authors-guide.md b/authors-guide.md index f5de511..2504368 100644 --- a/authors-guide.md +++ b/authors-guide.md @@ -6,7 +6,7 @@ title: Author's Guide ## tl;dr: Look at Other Recipes, and Blend In -Look at the source of other recipe pages and follow that page structure. Start with the [Developer's Guide](/developers-guide) to get a test version of the cookbook up and running on your machine, and get to work! +Look at the source of other recipe pages and follow that page structure. Start with the [Developer's Guide]({{ site.baseurl }}/developers-guide) to get a test version of the cookbook up and running on your machine, and get to work! ## General Guidelines @@ -26,7 +26,7 @@ A typical cookbook page will have three sections (four if you count the title): ## Copyright Issues -Do not post code that is copyrighted by another user, unless you have permission to use that code AND to relicense that code under the [CC BY 3.0](/license) license. If you DO have permission and the author would like credit, please add them to the [authors](/authors) page. +Do not post code that is copyrighted by another user, unless you have permission to use that code AND to relicense that code under the [CC BY 3.0]({{ site.baseurl }}/license) license. If you DO have permission and the author would like credit, please add them to the [authors]({{ site.baseurl }}/authors) page. Also, just a stylistic note, please do not yank code directly from [http://coffeescript.org/](http://coffeescript.org/) and post it with little or no discussion. The CoffeeScript Cookbook is not affiliated with them. We think they're awesome and want them to like us, too! Make sure that anything taken from [http://coffeescript.org/](http://coffeescript.org/) is permissible use and that it stands alone as a valid recipe. If the recipe is too terse, consider adding more examples and discussion. @@ -99,7 +99,7 @@ When in doubt about what output to show, try evaluating your snippet in the coff ## How to Add a Recipe -Create a new markdown page (or copy the [Recipe Template](/recipe-template). The filename should be about the problem, e.g. `finding-last-day-of-the-month.md` or `reversing-arrays.md`. In your file, start with the following template: +Create a new markdown page (or copy the [Recipe Template]({{ site.baseurl }}/recipe-template). The filename should be about the problem, e.g. `finding-last-day-of-the-month.md` or `reversing-arrays.md`. In your file, start with the following template: {% highlight text %} --- @@ -163,7 +163,7 @@ If the answer to either question is no, you might have some code that is a "clev ## What If My Recipe is Inefficient/Too Big/Too Slow? -If it solves a problem to which the alternative is to _not_ solve the problem, share it. Let the reader decide if they want to use it. Sometimes we want tight efficient code, other times we want a robust featureset. If the code has abysmal performance characteristics, be sure to warn the reader in the Discussion. +If it solves a problem to which the alternative is to _not_ solve the problem, share it. Let the reader decide if they want to use it. Sometimes we want tight efficient code, other times we want a robust feature set. If the code has abysmal performance characteristics, be sure to warn the reader in the Discussion. ## Can I Edit An Existing Recipe? @@ -175,7 +175,7 @@ See the "Weird Recipe" note above. Do real people in the real world ever hit the ## I Have A Problem/Solution, But It's Basically Just JavaScript. Should I Add It? -Yes! CoffeeScript compiles to JavaScript, and that means that some of its functionality comes straight from JavaScript. (For example, see [Reversing Arrays](/chapters/arrays/reversing-arrays).) But if you're programming in CoffeeScript and you need to reverse an array, this Cookbook should stand ready to tell you it's available to you in CoffeeScript -- even if it's just a straight call into a JavaScript library. +Yes! CoffeeScript compiles to JavaScript, and that means that some of its functionality comes straight from JavaScript. (For example, see [Reversing Arrays]({{ site.baseurl }}/chapters/arrays/reversing-arrays).) But if you're programming in CoffeeScript and you need to reverse an array, this Cookbook should stand ready to tell you it's available to you in CoffeeScript -- even if it's just a straight call into a JavaScript library. ## I Found a Typo. Is That Enough of a Fix? Does That Count? diff --git a/authors.md b/authors.md index 0ac943e..d5181f6 100644 --- a/authors.md +++ b/authors.md @@ -17,7 +17,14 @@ The following people are totally rad and awesome because they have contributed r * Jason Giedymin *jasong@apache.org* * Phil Cohen *github@phlippers.net* * João Moreno *coffeecb @joaomoreno .com* -* ...You! What are you waiting for? Check out the [contributing](/contributing) section and get cracking! +* Jeff Pickhardt *pickhardt (at) gmail (dot) com* +* Frederic Hemberger +* Mike Hatfield *oakraven13@gmail.com* +* [Anton Rissanen](http://github.com/antris) *hello@anton.fi* +* Calum Robertson *http://github.com/randusr836* +* Jake Burkhead *https://github.com/jlburkhead* +* [Alex Johnson](https://github.com/nonsensery) +* ...You! What are you waiting for? Check out the [contributing]({{ site.baseurl }}/contributing) section and get cracking! # Developers @@ -26,11 +33,14 @@ The following people are totally rad and awesome because they have contributed r * David Brady *ratgeyser@gmail.com* * Mike Moore *mike@blowmage.com* * Peter Hellberg *peter@c7.se* -* ...You! What are you waiting for? Check out the [contributing](/contributing) section and get cracking! +* Jamie Gaskins *jgaskins@gmail.com* +* Sami Pussinen *me@samipussinen.com* +* [Devin Weaver (@sukima)](https://github.com/sukima) +* ...You! What are you waiting for? Check out the [contributing]({{ site.baseurl }}/contributing) section and get cracking! # Designers The following people are astonishingly rad and awesome because they did great design for the site! -* Tad Thorley -* ...You! Check out the [contributing](/contributing) section and get cracking! +* [Amsul](http://github.com/amsul) reach@amsul.ca +* ...You! Check out the [contributing]({{ site.baseurl }}/contributing) section and get cracking! diff --git a/chapters/ajax/ajax_request_without_jquery.md b/chapters/ajax/ajax_request_without_jquery.md new file mode 100644 index 0000000..8dd9530 --- /dev/null +++ b/chapters/ajax/ajax_request_without_jquery.md @@ -0,0 +1,102 @@ +--- +layout: recipe +title: Ajax Request Without jQuery +chapter: Ajax +--- +## Problem + +You want to load data from your server via AJAX without using the jQuery library. + +## Solution + +You will use the native XMLHttpRequest object. + +Let's set up a simple test HTML page with a button. + +{% highlight html %} + + + + + XMLHttpRequest Tester + + +

XMLHttpRequest Tester

+ + + + + +{% endhighlight %} + +When the button is clicked, we want to send an Ajax request to the server to retrieve some data. For this sample, we have a small JSON file. + +{% highlight javascript %} +// data.json +{ + message: "Hello World" +} +{% endhighlight %} + +Next, create the CoffeeScript file to hold the page logic. The code in this file creates a function to be called when the Load Data button is clicked. + +{% highlight coffeescript linenos %} +# XMLHttpRequest.coffee +loadDataFromServer = -> + req = new XMLHttpRequest() + + req.addEventListener 'readystatechange', -> + if req.readyState is 4 # ReadyState Complete + successResultCodes = [200, 304] + if req.status in successResultCodes + data = eval '(' + req.responseText + ')' + console.log 'data message: ', data.message + else + console.log 'Error loading data...' + + req.open 'GET', 'data.json', false + req.send() + +loadDataButton = document.getElementById 'loadDataButton' +loadDataButton.addEventListener 'click', loadDataFromServer, false +{% endhighlight %} + +## Discussion + +In the above code we grab a handle to the button in our HTML (line 16) and add a *click* event listener (line 17). In our event listener, we define our callback function as loadDataFromServer. + +We define our loadDataFromServer callback beginning on line 2. + +We create a XMLHttpRequest request object (line 3) and add a *readystatechange* event handler. This fires whenever the request's readyState changes. + +In the event handler we check to see if the readyState = 4, indicating the request has completed. Then, we check the request status value. Both 200 or 304 represent a successful request. Anything else represents an error condition. + +If the request was indeed successful, we eval the JSON returned from the server and assign it to a data variable. At this point, we can use the returned data in any way we need to. + +The last thing we need to do is actually make our request. + +Line 13 opens a 'GET' request to retrieve the data.json file. + +Line 14 sends our request to the server. + +## Older Browser Support + +If your application needs to target older versions of Internet Explorer, you will need to ensure the XMLHttpRequest object exists. You can do this by including this code before creating the XMLHttpRequest instance. + +{% highlight coffeescript %} +if (typeof @XMLHttpRequest == "undefined") + console.log 'XMLHttpRequest is undefined' + @XMLHttpRequest = -> + try + return new ActiveXObject("Msxml2.XMLHTTP.6.0") + catch error + try + return new ActiveXObject("Msxml2.XMLHTTP.3.0") + catch error + try + return new ActiveXObject("Microsoft.XMLHTTP") + catch error + throw new Error("This browser does not support XMLHttpRequest.") +{% endhighlight %} + +This code ensures the XMLHttpRequest object is available in the global namespace. diff --git a/chapters/ajax/index.html b/chapters/ajax/index.html index 5ca402b..0dfcd24 100644 --- a/chapters/ajax/index.html +++ b/chapters/ajax/index.html @@ -11,7 +11,7 @@ {% for page in site.pages %} {% if page.url contains url %} {% unless page.url == indexurl %} -
  • {{ page.title }}
  • +
  • {{ page.title }}
  • {% endunless %} {% endif %} {% endfor %} diff --git a/chapters/arrays/check-type-is-array.md b/chapters/arrays/check-type-is-array.md new file mode 100644 index 0000000..1206ef1 --- /dev/null +++ b/chapters/arrays/check-type-is-array.md @@ -0,0 +1,44 @@ +--- +layout: recipe +title: Check if type of value is an Array +chapter: Arrays +--- +## Problem + +You want to check if a value is an `Array`. + +{% highlight coffeescript %} +myArray = [] +console.log typeof myArray // outputs 'object' +{% endhighlight %} + +The `typeof` operator gives a faulty output for arrays. + +## Solution + +Use the following code: + +{% highlight coffeescript %} +typeIsArray = Array.isArray || ( value ) -> return {}.toString.call( value ) is '[object Array]' +{% endhighlight %} + +To use this, just call `typeIsArray` as such: + +{% highlight coffeescript %} +myArray = [] +typeIsArray myArray // outputs true +{% endhighlight %} + +## Discussion + +The method above has been adopted from "the Miller Device". An alternative is to use Douglas Crockford's snippet: + +{% highlight coffeescript %} +typeIsArray = ( value ) -> + value and + typeof value is 'object' and + value instanceof Array and + typeof value.length is 'number' and + typeof value.splice is 'function' and + not ( value.propertyIsEnumerable 'length' ) +{% endhighlight %} diff --git a/chapters/arrays/concatenating-arrays.md b/chapters/arrays/concatenating-arrays.md index af614ac..a023791 100644 --- a/chapters/arrays/concatenating-arrays.md +++ b/chapters/arrays/concatenating-arrays.md @@ -9,17 +9,63 @@ You want to join two arrays together. ## Solution -Use JavaScript's Array concat() method: +There are two standard options for concatenating arrays in JavaScript. + +The first is to use JavaScript's Array `concat()` method: {% highlight coffeescript %} -ray1 = [1,2,3] -ray2 = [4,5,6] -ray3 = ray1.concat ray2 +array1 = [1, 2, 3] +array2 = [4, 5, 6] +array3 = array1.concat array2 # => [1, 2, 3, 4, 5, 6] {% endhighlight %} -## Discussion +Note that `array1` is _not_ modified by the operation. The concatenated array is returned as a new object. + +If you want to merge two arrays without creating a new object, you can use the following technique: + +{% highlight coffeescript %} +array1 = [1, 2, 3] +array2 = [4, 5, 6] +Array::push.apply array1, array2 +array1 +# => [1, 2, 3, 4, 5, 6] +{% endhighlight %} -CoffeeScript lacks a special syntax for joining arrays, but concat is a standard JavaScript method. +In the example above, the `Array.prototype.push.apply(a, b)` approach modifies `array1` in place without creating a new array object. + +We can simplify the pattern above using CoffeeScript by creating a new `merge()` method for Arrays. + +{% highlight coffeescript %} +Array::merge = (other) -> Array::push.apply @, other + +array1 = [1, 2, 3] +array2 = [4, 5, 6] +array1.merge array2 +array1 +# => [1, 2, 3, 4, 5, 6] +{% endhighlight %} + +Alternatively, we can pass a CoffeeScript splat (`array2...`) directly into `push()`, avoiding the Array prototype. + +{% highlight coffeescript %} +array1 = [1, 2, 3] +array2 = [4, 5, 6] +array1.push array2... +array1 +# => [1, 2, 3, 4, 5, 6] +{% endhighlight %} + +A more idiomatic approach is to use the splat operator (`...`) directly in an array literal. This can be used to concatenate any number of arrays. + +{% highlight coffeescript %} +array1 = [1, 2, 3] +array2 = [4, 5, 6] +array3 = [array1..., array2...] +array3 +# => [1, 2, 3, 4, 5, 6] +{% endhighlight %} + +## Discussion -Note that ray1 is _not_ modified by the operation. The concatenated array is returned as a new object. +CoffeeScript lacks a special syntax for joining arrays, but `concat()` and `push()` are standard JavaScript methods. diff --git a/chapters/arrays/creating-a-dictionary-object-from-an-array.md b/chapters/arrays/creating-a-dictionary-object-from-an-array.md new file mode 100644 index 0000000..cf8dd53 --- /dev/null +++ b/chapters/arrays/creating-a-dictionary-object-from-an-array.md @@ -0,0 +1,63 @@ +--- +layout: recipe +title: Creating a dictionary Object from an Array +chapter: Arrays +--- +## Problem + +You have an Array of Objects, such as: + +{% highlight coffeescript %} +cats = [ + { + name: "Bubbles" + age: 1 + }, + { + name: "Sparkle" + favoriteFood: "tuna" + } +] +{% endhighlight %} + +But you want to access it as a dictionary by key, like `cats["Bubbles"]`. + +## Solution + +You need to convert your array into an Object. Use reduce for this. + +{% highlight coffeescript %} +# key = The key by which to index the dictionary +Array::toDict = (key) -> + @reduce ((dict, obj) -> dict[ obj[key] ] = obj if obj[key]?; return dict), {} +{% endhighlight %} + +To use this: + +{% highlight coffeescript %} + catsDict = cats.toDict('name') + catsDict["Bubbles"] + # => { age: 1, name: "Bubbles" } +{% endhighlight %} + +## Discussion + +Alternatively, you can use an Array comprehension: + +{% highlight coffeescript %} +Array::toDict = (key) -> + dict = {} + dict[obj[key]] = obj for obj in this when obj[key]? + dict +{% endhighlight %} + +If you use Underscore.js, you can create a mixin: + +{% highlight coffeescript %} +_.mixin toDict: (arr, key) -> + throw new Error('_.toDict takes an Array') unless _.isArray arr + _.reduce arr, ((dict, obj) -> dict[ obj[key] ] = obj if obj[key]?; return dict), {} +catsDict = _.toDict(cats, 'name') +catsDict["Sparkle"] +# => { favoriteFood: "tuna", name: "Sparkle" } +{% endhighlight %} \ No newline at end of file diff --git a/chapters/arrays/creating-a-string-from-an-array.md b/chapters/arrays/creating-a-string-from-an-array.md index d2b83bb..bcd8693 100644 --- a/chapters/arrays/creating-a-string-from-an-array.md +++ b/chapters/arrays/creating-a-string-from-an-array.md @@ -13,7 +13,7 @@ Use JavaScript's Array toString() method: {% highlight coffeescript %} ["one", "two", "three"].toString() -# => 'three,two,one' +# => 'one,two,three' {% endhighlight %} ## Discussion diff --git a/chapters/arrays/define-ranges.md b/chapters/arrays/define-ranges.md new file mode 100644 index 0000000..a75a3f4 --- /dev/null +++ b/chapters/arrays/define-ranges.md @@ -0,0 +1,50 @@ +--- +layout: recipe +title: Define Ranges Array +chapter: Arrays +--- +## Problem + +You want to define a range in an array. + +## Solution + +There are two ways to define a range of array elements in CoffeeScript. + +{% highlight coffeescript %} + +# inclusive +myArray = [1..10] +# => [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] + +{% endhighlight %} + +{% highlight coffeescript %} + +# exclusive +myArray = [1...10] +# => [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + +{% endhighlight %} + +We can also reverse the range of element by writing it this way. + +{% highlight coffeescript %} + +myLargeArray = [10..1] +# => [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ] + +{% endhighlight %} + +{% highlight coffeescript %} +myLargeArray = [10...1] +# => [ 10, 9, 8, 7, 6, 5, 4, 3, 2 ] + +{% endhighlight %} + +## Discussion + +Inclusive ranges are defined by the '..' operator and include the last value. + +Exclusive ranges are defined by '...', and always omit the last value. + diff --git a/chapters/arrays/filtering-arrays.md b/chapters/arrays/filtering-arrays.md index 34e14cb..2476aea 100644 --- a/chapters/arrays/filtering-arrays.md +++ b/chapters/arrays/filtering-arrays.md @@ -18,12 +18,13 @@ array.filter (x) -> x > 5 # => [6,7,8,9,10] {% endhighlight %} -In pre-EC5 implementations, extend the Array prototype to add a filter function which will take a callback and perform a comprension over itself, collecting all elements for which the callback is true. +In pre-EC5 implementations, extend the Array prototype to add a filter function which will take a callback and perform a comprehension over itself, collecting all elements for which the callback is true. Be sure to check if the function is already implemented before overwriting it: {% highlight coffeescript %} # Extending Array's prototype -Array::filter = (callback) -> - element for element in this when callback(element) +unless Array::filter + Array::filter = (callback) -> + element for element in this when callback(element) array = [1..10] diff --git a/chapters/arrays/index.html b/chapters/arrays/index.html index 80b9b41..906700a 100644 --- a/chapters/arrays/index.html +++ b/chapters/arrays/index.html @@ -11,7 +11,7 @@ {% for page in site.pages %} {% if page.url contains url %} {% unless page.url == indexurl %} -
  • {{ page.title }}
  • +
  • {{ page.title }}
  • {% endunless %} {% endif %} {% endfor %} diff --git a/chapters/arrays/list-comprehensions.md b/chapters/arrays/list-comprehensions.md index 68e7c88..3ab2700 100644 --- a/chapters/arrays/list-comprehensions.md +++ b/chapters/arrays/list-comprehensions.md @@ -9,7 +9,7 @@ You have an array of objects and want to map them to another array, similar to P ## Solution -Use a list comprehension, but don't forget about [mapping arrays](/chapters/arrays/mapping-arrays). +Use a list comprehension, but don't forget about [mapping arrays]({{ site.baseurl }}/chapters/arrays/mapping-arrays). {% highlight coffeescript %} electric_mayhem = [ { name: "Doctor Teeth", instrument: "piano" }, @@ -25,4 +25,4 @@ names = (muppet.name for muppet in electric_mayhem) ## Discussion -Because CoffeeScript directly support list comprehensions, they work pretty much as advertised wherever you would use one in Python. For simple mappings, list comprehensions are much more readable. For complicated transformations or for chained mappings, [mapping arrays](/chapters/arrays/mapping-arrays) might be more elegant. +Because CoffeeScript directly support list comprehensions, they work pretty much as advertised wherever you would use one in Python. For simple mappings, list comprehensions are much more readable. For complicated transformations or for chained mappings, [mapping arrays]({{ site.baseurl }}/chapters/arrays/mapping-arrays) might be more elegant. diff --git a/chapters/arrays/mapping-arrays.md b/chapters/arrays/mapping-arrays.md index 6a54964..1be0e65 100644 --- a/chapters/arrays/mapping-arrays.md +++ b/chapters/arrays/mapping-arrays.md @@ -9,7 +9,7 @@ You have an array of objects and want to map them to another array, similar to R ## Solution -Use map() with an anonymous function, but don't forget about list comprehensions. +Use map() with an anonymous function, but don't forget about list comprehensions. {% highlight coffeescript %} electric_mayhem = [ { name: "Doctor Teeth", instrument: "piano" }, @@ -27,4 +27,4 @@ names = electric_mayhem.map (muppet) -> muppet.name Because CoffeeScript has clean support for anonymous functions, mapping an array in CoffeeScript is nearly as easy as it is in Ruby. -Maps are are good way to handle complicated transforms and chained mappings in CoffeeScript. If your transformation is as simple as the one above, however, it may read more cleanly as a list comprehension. +Maps are are good way to handle complicated transforms and chained mappings in CoffeeScript. If your transformation is as simple as the one above, however, it may read more cleanly as a list comprehension. diff --git a/chapters/arrays/max-array-value.md b/chapters/arrays/max-array-value.md index 537344c..25f1472 100644 --- a/chapters/arrays/max-array-value.md +++ b/chapters/arrays/max-array-value.md @@ -9,22 +9,21 @@ You need to find the largest value contained in an array. ## Solution -In ECMAScript 5, use `Array#reduce`. In older javascripts, use Math.max over a list comprehension: +You can use Math.max() JavaScript method along with splats. {% highlight coffeescript %} -# ECMAScript 5 -[12,32,11,67,1,3].reduce (a,b) -> Math.max a, b +Math.max [12, 32, 11, 67, 1, 3]... # => 67 +{% endhighlight %} + +Alternatively, it's possible to use ES5 `reduce` method. For backward compatibility with older JavaScript implementations, use the above. -# Pre-EC5 -max = Math.max(max or= num, num) for num in [12,32,11,67,1,3] -# => [ 12, 32, 32, 67, 67, 67 ] -max +{% highlight coffeescript %} +# ECMAScript 5 +[12,32,11,67,1,3].reduce (a,b) -> Math.max a, b # => 67 {% endhighlight %} ## Discussion -`Math.max` compares two numbers and returns the larger of the two; the rest of this recipe (both versions) is just iterating over the array to find the largest one. It is interesting to note that when assigning from a list comprehension, the last item evaluated will be returned to the assignment but the expression itself (such as in the node.js coffeescript interpreter) evaluates to the list comprehension's complete mapping. This means that the Pre-EC5 version will duplicate the array. - -For very large arrays in ECMAScript 4, a more memory efficient approach might be to initialize `max` to the first element of the array, and then loop over it with a traditional for loop. Since non-idiomatic CoffeeScript is discouraged in the Cookbook, this approach is left as an exercise to the reader. +`Math.max` compares every argument and returns the largest number from arguments. The ellipsis (`...`) converts every array value into argument which is given to the function. You can also use it with other functions which take variable amount of arguments, such as `console.log`. diff --git a/chapters/arrays/reducing-arrays.md b/chapters/arrays/reducing-arrays.md index 7e85463..6b52690 100644 --- a/chapters/arrays/reducing-arrays.md +++ b/chapters/arrays/reducing-arrays.md @@ -9,7 +9,7 @@ You have an array of objects and want to reduce them to a value, similar to Ruby ## Solution -You can simply use Array's `reduce()` and `reduceRight()` methods along with an anonoymous function, keeping the code clean and readable. The reduction may be something simple such as using the `+` operator with numbers or strings. +You can simply use Array's `reduce()` and `reduceRight()` methods along with an anonymous function, keeping the code clean and readable. The reduction may be something simple such as using the `+` operator with numbers or strings. {% highlight coffeescript %} [1,2,3,4].reduce (x,y) -> x + y @@ -24,10 +24,11 @@ You can simply use Array's `reduce()` and `reduceRight()` methods along with an Or it may be something more complex such as aggregating elements from a list into a combined object. {% highlight coffeescript %} -people = - { name: '', age: 10 } - { name: '', age: 16 } - { name: '', age: 17 } +people = [ + { name: 'alec', age: 10 } + { name: 'bert', age: 16 } + { name: 'chad', age: 17 } +] people.reduce (x, y) -> x[y.name]= y.age diff --git a/chapters/arrays/removing-duplicate-elements-from-arrays.md b/chapters/arrays/removing-duplicate-elements-from-arrays.md index 7ecd731..545ae17 100644 --- a/chapters/arrays/removing-duplicate-elements-from-arrays.md +++ b/chapters/arrays/removing-duplicate-elements-from-arrays.md @@ -1,6 +1,6 @@ --- layout: recipe -title: Removing duplicate elements from Arrays +title: Removing Duplicate Elements from Arrays chapter: Arrays --- ## Problem @@ -22,3 +22,7 @@ Array::unique = -> ## Discussion There are many implementations of the `unique` method in JavaScript. This one is based on "The fastest method to find unique items in array" found [here](http://www.shamasis.net/2009/09/fast-algorithm-to-find-unique-items-in-javascript-array/). + +**Note:** Although it's quite common in languages like Ruby, extending native objects is often considered bad practice in JavaScript (see: [Maintainable JavaScript: Don’t modify objects you don’t own](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/); [Extending built-in native objects. Evil or not?](http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/)). + + diff --git a/chapters/arrays/shuffling-array-elements.md b/chapters/arrays/shuffling-array-elements.md new file mode 100644 index 0000000..c5f4193 --- /dev/null +++ b/chapters/arrays/shuffling-array-elements.md @@ -0,0 +1,107 @@ +--- +layout: recipe +title: Shuffling Array Elements +chapter: Arrays +--- +## Problem + +You want to shuffle the elements in an array. + +## Solution + +The [Fisher-Yates shuffle] is a highly efficient and completely unbiased way to randomize +the elements in an array. It's a fairly simple method: Start at the end of the list, and +swap the last element with a random element from earlier in the list. Go down one and +repeat, until you're at the beginning of the list, with all of the shuffled elements +at the end of the list. This [Fisher-Yates shuffle Visualization] may help you understand +the algorithm. + +{% highlight coffeescript %} +shuffle = (source) -> + # Arrays with < 2 elements do not shuffle well. Instead make it a noop. + return source unless source.length >= 2 + # From the end of the list to the beginning, pick element `index`. + for index in [source.length-1..1] + # Choose random element `randomIndex` to the front of `index` to swap with. + randomIndex = Math.floor Math.random() * (index + 1) + # Swap `randomIndex` with `index`, using destructured assignment + [source[index], source[randomIndex]] = [source[randomIndex], source[index]] + source + +shuffle([1..9]) +# => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ] +{% endhighlight %} + +[Fisher-Yates shuffle]: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle +[Fisher-Yates Shuffle Visualization]: http://bost.ocks.org/mike/shuffle/ + +## Discussion + +### The Wrong Way to do it + +There is a common--but terribly wrong way--to shuffle an array, by sorting by a random +number. + +{% highlight coffeescript %} +shuffle = (a) -> a.sort -> 0.5 - Math.random() +{% endhighlight %} + +If you do a sort randomly, it should give you a random order, right? Even [Microsoft used +this random-sort algorithm][msftshuffle]. Turns out, [this random-sort algorithm produces +biased results][naive], because it only has the illusion of shuffling. Randomly sorting +will not result in a neat, tidy shuffle; it will result in a wild mass of inconsistent +sorting. + +[msftshuffle]: http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html +[naive]: http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html + +### Optimizing for speed and space + +The solution above isn't as fast, or as lean, as it can be. The list comprehension, when +transformed into Javascript, is far more complex than it needs to be, and the +destructured assignment is far slower than dealing with bare variables. The following +code is less idiomatic, and takes up more source-code space... but will compile down +smaller and run a bit faster: + +{% highlight coffeescript %} +shuffle = (a) -> + i = a.length + while --i > 0 + j = ~~(Math.random() * (i + 1)) # ~~ is a common optimization for Math.floor + t = a[j] + a[j] = a[i] + a[i] = t + a +{% endhighlight %} + +### Extending Javascript to include this shuffle. + +The following code adds the shuffle function to the Array prototype, which means that +you are able to run it on any array you wish, in a much more direct manner. + +{% highlight coffeescript %} +Array::shuffle ?= -> + if @length > 1 then for i in [@length-1..1] + j = Math.floor Math.random() * (i + 1) + [@[i], @[j]] = [@[j], @[i]] + this + +[1..9].shuffle() +# => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ] +{% endhighlight %} + +**Note:** Although it's quite common in languages like Ruby, extending native objects is +often considered bad practice in JavaScript (see: [Maintainable JavaScript: Don’t modify +objects you don’t own][dontown]; [Extending built-in native objects. Evil or not?] +[extendevil]). That being said, the code above is really quite safe to add. It only adds +`Array::shuffle` if it doesn't exist already, thanks to the existential assignment +operator (`?=`). That way, we don't overwrite someone else's, or a native browser method. + +Also, if you think you'll be using a lot of these utility functions, consider using a +utility library, like [Lo-dash](http://lodash.com/)^†. They include a lot of nifty +features, like maps and forEach, in a cross-browser, lean, high-performance way. + +^† [Underscore](http://underscorejs.org/) is also a good alternative to Lo-dash. + +[dontown]: http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/ +[extendevil]: http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/ diff --git a/chapters/arrays/testing-every-element.md b/chapters/arrays/testing-every-element.md index 5978500..773a107 100644 --- a/chapters/arrays/testing-every-element.md +++ b/chapters/arrays/testing-every-element.md @@ -12,15 +12,15 @@ You want to be able to check that every element in an array meets a particular c Use Array.every (ECMAScript 5): {% highlight coffeescript %} -evens = (x for x in [1..10] by 2) +evens = (x for x in [0..10] by 2) evens.every (x)-> x % 2 == 0 # => true {% endhighlight %} -Array.every was addded to Mozilla's Javascript 1.6 and made standard with EcmaScript 5. If you to support browsers that do not implement EC5 then check out [`_.all` from underscore.js][underscore]. +Array.every was added to Mozilla's Javascript 1.6 and made standard with ECMAScript 5. If you to support browsers that do not implement EC5 then check out [`_.all` from underscore.js][underscore]. -For a real world example, prentend you have a multiple select list that looks like: +For a real world example, pretend you have a multiple select list that looks like: {% highlight html %}