diff --git a/.gitignore b/.gitignore index 3042ecd..3ae9f09 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ _site *.swp *.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/LICENSE-CC-BY.textile b/LICENSE-CC-BY.textile index d6744e4..df9928b 100644 --- a/LICENSE-CC-BY.textile +++ b/LICENSE-CC-BY.textile @@ -20,6 +20,7 @@ BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE B "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. + 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: @@ -40,6 +41,7 @@ The above rights may be exercised in all media and formats whether now known or You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. + 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. @@ -50,6 +52,7 @@ UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS T This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. + 8. Miscellaneous Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 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 8434992..20fa234 100644 --- a/_config.yml +++ b/_config.yml @@ -1,12 +1,13 @@ -pygments: true +baseurl: '' +safe: true +highlighter: pygments lsi: false +markdown: redcarpet exclude: - - README + - README.md - CNAME - - TODO.textile + - TODO.md - script - Procfile - Gemfile - Gemfile.lock - - 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 42cf547..eb1a03c 100644 --- a/_layouts/recipe.html +++ b/_layouts/recipe.html @@ -1,28 +1,79 @@ + + CoffeeScript Cookbook » {{ page.title }} - + + -
-

- CoffeeScript Cookbook » - {{ page.chapter }} » - {{ page.title }} -

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

óCoffeeScript Cookbook

+
+ +
+ +
+

{{ page.title }}

+ {{ content }} +
+ +
+

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

+ +
+
+
-
-

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 new file mode 100644 index 0000000..2504368 --- /dev/null +++ b/authors-guide.md @@ -0,0 +1,182 @@ +--- +layout: default +title: Author's Guide +--- +# 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]({{ site.baseurl }}/developers-guide) to get a test version of the cookbook up and running on your machine, and get to work! + +## General Guidelines + +* Feel free to add new pages, or even chapters. Just keep them organized. _(See "How to Add a Chapter" below)_ +* Try to write well-styled, idiomatic CoffeeScript. +* Try to stay within the Problem/Solution/Discussion format. If you can't think of a good problem for your recipe... hang onto it for a while. +* Sharing code that you think might only be used rarely is fine. Sharing esoteric code tricks is less so. There's a difference between sharing a translator for an obscure format and sharing a weird bit-shifting trick that does fast but inaccurate multiplication. +* Don't forget to add your name to the authors page! + +## Use Cookbook Problem/Solution/Discussion Format + +A typical cookbook page will have three sections (four if you count the title): + +* **Problem** A one- or two-sentence description of the problem, such as "You want to access parts of a string" or "You want to format a floating point number as currency, with a leading dollar sign, two digits of precision and comma-separated triples." Where possible, phrase the problem as though speaking directly to the reader: "You want to...", "You have an X but you want a Y", etc. +* **Solution** State the solution as briefly as possible, ideally as a single sentence that identifies the strategy you would use, and give example code. It's tempting to explain the solution, but don't do it yet. Remember that the reader will look this solution up many times after the first time, and that they will be looking for a quick reference each time. You're going to explain the solution in the **Discussion**, and the first time reader will read your solution, think about it, and then proceed to the discussion if necessary. For example, for "accessing parts of a string", a good Solution sentence might be "Use CoffeeScript's array range subscripts, or JavaScript's slice function." +* **Discussion** Here you should explain why your solution works, or give further examples such as edge cases. If your solution can break on some edge cases, be sure to note them here. For example, if a percentage function crashes when given a zero, you could note this in the discussion and give a workaround. NOTE: If your solution has really dangerous edge cases, so dangerous that you would include them in the solution, please consider whether your recipe should be included at all. Remember, this Cookbook is for good code! + +## 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]({{ 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. + +## Tag the page with Jekyll frontmatter + +...that's a lot of fancy words that mean "don't forget to put the layout, chapter and page title at the top of the file in a YAML block". For example, the string interpolation page begins with + +{% highlight text %} +--- +layout: recipe +title: String Interpolation +chapter: Strings +--- +{% endhighlight %} + +## Use Liquid highlighting templates + +Turn on syntax highlighting for CoffeeScript with `highlight coffeescript`. + +test2 + +{% highlight coffeescript %} +# Calculate the square of a number +square = (x) -> x * x + +square(16) +# => 256 +{% endhighlight %} + +This produces the following: + +{% highlight coffeescript %} +# Calculate the square of a number +square = (x) -> x * x + +square(16) +# => 256 +{% endhighlight %} + +## Include Output + +After writing an important expression, show the reader what the output would be by adding it with a `# =>` on the following line. Once we get automated script testing working up in this joint, we'll actually load up your recipe snippets and evaluate its expressions against the output comment you wrote. (In other words, `# =>` tells the reader what the output will be, but it tells automated checker what the assertEquals should be) + +{% highlight coffeescript %} +# right +[1,2,3].map (x) -> x * 2 +# => [ 2, 4, 6 ] + +# very wrong! +[1,2,3].map (x) -> x * 2 + +# right; only add for important/results statements +evens = x for x in [0..10] by 2 +evens.some (x) -> x == 6 +# => true + +# less wrong; may require tweaking the automatic checker +[1,2,3].map (x) -> x * 2 # => [ 2, 4, 6 ] + +# less wrong (possibly not wrong at all--the output merely does not match what the coffee interpreter renders) +[1,2,3].map (x) -> x * 2 +# => [2,4,6] +{% endhighlight %} + +Not all snippets evaluate to something useful. For example, array.forEach has side effects but does not return anything. Also, the console.log() and alert() commands are also side-effect-only commands that return nothing. When possible, try to have your snippets evaluate to something inspectable, and leave displaying to the console or browser out of it. (Unless your snippet is _about_ displaying to the console or browser.) + +When in doubt about what output to show, try evaluating your snippet in the coffee interpreter and see what IT thinks. Ideally your output should match, or at least be machine-comparable. + +# Grindy HOWTOs + +## How to Add a Recipe + +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 %} +--- +layout: recipe +title: Title of The Recipe +chapter: Chapter Name +--- + +## Problem + +You have a problem. + +## Solution + +Do this about it. + +## Discussion + +Here's why. +{% endhighlight %} + +One fussy little bit, the chapter name should match the directory the chapter is in, otherwise the page won't render correctly. For example, if you're writing a recipe for arrays, make sure the chapter is "Arrays", not "arrays" or "aray" ...or especially not "Chapter Name". + +## How to Add a Chapter + +* Open chapters/index.html and your chapter's name to the yaml list of chapters. +* cd into chapters/ and create the directory for the chapter name. Downcase the name and replace spaces with underscores. +* add an index.html file that uses `layout: chapter`. For convenience, just copy the index.html from another chapter and update the yaml frontmatter to reflect the name of your new chapter. + +For example, to add a chapter called "Dates and Times", you would add it to the chapters array: + +{% highlight yaml %} +chapters: +- Syntax +- Objects +- Arrays +- Dates and Times +{% endhighlight %} + +...and then create that chapter in the file system: + +{% highlight bash %} +cd chapters +mkdir dates_and_times +{% endhighlight %} + +Now create a new page in that chapter (remember to add its YAML front matter) and once jekyll regenerates the chapter index, your new page should appear. + +# FAQ + +## I Have a Weird Recipe. Should I Share It? + +Maybe! The real question is, is it really useful, or is it just clever? If you have a formatter for a weird Albanian GIS format, that's a real problem that almost nobody would ever have -- but when somebody DOES have that problem, they REALLY need a solution. If you have a bit shifting trick that can swap two numbers using three xor-equals operations, that's a really clever solution but it's not very good CoffeeScript. (For one thing, `x ^= y ^= x ^= y` is not idiomatic, while `[x,y]=[y,x]` is. For another, there's a bug in that code. And once you fix it, there's another bug caused by extrapolating this register trick to a reference-based language where -- look, it's just a bad idea, okay?) + +If you have a cool but weird recipe, ask yourself if a reader would genuinely find it useful. Here are two very good questions to consider: + +* Does it really solve a problem that an actual person might have? +* If somebody really does have that problem, would your recipe really be the best solution? + +If the answer to either question is no, you might have some code that is a "clever solution in search of a problem". If in doubt, ask. + +## 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 feature set. If the code has abysmal performance characteristics, be sure to warn the reader in the Discussion. + +## Can I Edit An Existing Recipe? + +Yes. Please improve anything and everything! Be sure to test your changes and make sure that your solution really is better. + +## I Have a Really Efficient Solution, But It's Not As Readable As the Existing Recipe. Should I Add It? + +See the "Weird Recipe" note above. Do real people in the real world ever hit the performance constraint? If so, then by all means, add your strategy to the existing solution, and be sure to explain why your solution is not idiomatic. If a reader really has that problem, they'll be glad for the extra options. + +## 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]({{ 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? + +Absolutely! diff --git a/authors-guide.textile b/authors-guide.textile deleted file mode 100644 index e0e7f01..0000000 --- a/authors-guide.textile +++ /dev/null @@ -1,194 +0,0 @@ ---- -layout: default -title: Author's Guide ---- - -h1. Author's Guide - -h2. 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 to get a test version of the cookbook up and running on your machine, and get to work! - - -h2. General Guidelines - -* Feel free to add new pages, or even chapters. Just keep them organized. _(See "How to Add a Chapter" below)_ -* Try to write well-styled, idiomatic CoffeeScript. -* Try to stay within the Problem/Solution/Discussion format. If you can't think of a good problem for your recipe... hang onto it for a while. -* Sharing code that you think might only be used rarely is fine. Sharing esoteric code tricks is less so. There's a difference between sharing a translator for an obscure format and sharing a weird bit-shifting trick that does fast but inaccurate multiplication. -* Don't forget to add your name to the authors page! - -h2. Use Cookbook Problem/Solution/Discussion Format - -A typical cookbook page will have three sections (four if you count the title): - -* +Problem+ A one- or two-sentence description of the problem, such as "You want to access parts of a string" or "You want to format a floating point number as currency, with a leading dollar sign, two digits of precision and comma-separated triples." Where possible, phrase the problem as though speaking directly to the reader: "You want to...", "You have an X but you want a Y", etc. -* +Solution+ State the solution as briefly as possible, ideally as a single sentence that identifies the strategy you would use, and give example code. It's tempting to explain the solution, but don't do it yet. Remember that the reader will look this solution up many times after the first time, and that they will be looking for a quick reference each time. You're going to explain the solution in the +Discussion+, and the first time reader will read your solution, think about it, and then proceed to the discussion if necessary. For example, for "accessing parts of a string", a good Solution sentence might be "Use CoffeeScript's array range subscripts, or JavaScript's slice function." -* +Discussion+ Here you should explain why your solution works, or give further examples such as edge cases. If your solution can break on some edge cases, be sure to note them here. For example, if a percentage function crashes when given a zero, you could note this in the discussion and give a workaround. NOTE: If your solution has really dangerous edge cases, so dangerous that you would include them in the solution, please consider whether your recipe should be included at all. Remember, this Cookbook is for good code! - - -h2. 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. If you DO have permission and the author would like credit, please add them to the authors page. - -Also, just a stylistic note, please do not yank code directly from 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 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. - - -h2. Tag the page with Jekyll frontmatter - -...that's a lot of fancy words that mean "don't forget to put the layout, chapter and page title at the top of the file in a YAML block". For example, the string interpolation page begins with - ---- -layout: recipe -title: String Interpolation -chapter: Strings ---- - - -h2. Use Liquid highlighting templates - -Turn on syntax highlighting for CoffeeScript with highlight coffeescript. - -{% highlight coffeescript %} -# Calculate the square of a number -square = (x) -> x * x - -square(16) -# => 256 -{% endhighlight %} - -This produces the following: - -{% highlight coffeescript %} -# Calculate the square of a number -square = (x) -> x * x - -square(16) -# => 256 -{% endhighlight %} - - -h2. Include Output - -After writing an important expression, show the reader what the output would be by adding it with a # => on the following line. Once we get automated script testing working up in this joint, we'll actually load up your recipe snippets and evaluate its expressions against the output comment you wrote. (In other words, # => tells the reader what the output will be, but it tells automated checker what the assertEquals should be) - -{% highlight coffeescript %} -# right -[1,2,3].map (x) -> x * 2 -# => [ 2, 4, 6 ] - -# very wrong! -[1,2,3].map (x) -> x * 2 - -# right; only add for important/results statements -evens = x for x in [0..10] by 2 -evens.some (x) -> x == 6 -# => true - -# less wrong; may require tweaking the automatic checker -[1,2,3].map (x) -> x * 2 # => [ 2, 4, 6 ] - -# less wrong (possibly not wrong at all--the output merely does not match what the coffee interpreter renders) -[1,2,3].map (x) -> x * 2 -# => [2,4,6] -{% endhighlight %} - -Not all snippets evaluate to something useful. For example, array.forEach has side effects but does not return anything. Also, the console.log() and alert() commands are also side-effect-only commands that return nothing. When possible, try to have your snippets evaluate to something inspectable, and leave displaying to the console or browser out of it. (Unless your snippet is _about_ displaying to the console or browser.) - -When in doubt about what output to show, try evaluating your snippet in the coffee interpreter and see what IT thinks. Ideally your output should match, or at least be machine-comparable. - - -h1. Grindy HOWTOs - -h2. How to Add a Recipe - -Create a new textile page (or copy the Recipe Template. The filename should be about the problem, e.g. finding-last-day-of-the-month.textile or reversing-arrays.textile. In your file, start with the following template: - -{% highlight text %} ---- -layout: recipe -title: Title of The Recipe -chapter: Chapter Name ---- - -h2. Problem - -You have a problem. - -h2. Solution - -Do this about it. - -h2. Discussion - -Here's why. -{% endhighlight %} - -One fussy little bit, the chapter name should match the directory the chapter is in, otherwise the page won't render correctly. For example, if you're writing a recipe for arrays, make sure the chapter is "Arrays", not "arrays" or "aray" ...or especially not "Chapter Name". - - -h2. How to Add a Chapter - -* Open chapters/index.textile and your chapter's name to the yaml list of chapters. -* cd into chapters/ and create the directory for the chapter name. Downcase the name and replace spaces with underscores. -* add an index.textile file that uses layout: chapter. For convenience, just copy the index.textile from another chapter and update the yaml frontmatter to reflect the name of your new chapter. - -For example, to add a chapter called "Dates and Times", you would add it to the chapters array: - -{% highlight yaml %} -chapters: -- Syntax -- Objects -- Arrays -- Dates and Times -{% endhighlight %} - -...and then create that chapter in the file system: - -{% highlight bash %} -cd chapters -mkdir dates_and_times -{% endhighlight %} - -Now create a new page in that chapter (remember to add its YAML front matter) and once jekyll regenerates the chapter index, your new page should appear. - - -h1. FAQ - - -h2. I Have a Weird Recipe. Should I Share It? - -Maybe! The real question is, is it really useful, or is it just clever? If you have a formatter for a weird Albanian GIS format, that's a real problem that almost nobody would ever have—but when somebody DOES have that problem, they REALLY need a solution. If you have a bit shifting trick that can swap two numbers using three xor-equals operations, that's a really clever solution but it's not very good CoffeeScript. (For one thing, x ^= y ^= x ^= y is not idiomatic, while [x,y]=[y,x] is. For another, there's a bug in that code. And once you fix it, there's another bug caused by extrapolating this register trick to a reference-based language where—look, it's just a bad idea, okay?) - -If you have a cool but weird recipe, ask yourself if a reader would genuinely find it useful. Here are two very good questions to consider: - -* Does it really solve a problem that an actual person might have? -* If somebody really does have that problem, would your recipe really be the best solution? - -If the answer to either question is no, you might have some code that is a "clever solution in search of a problem". If in doubt, ask. - - -h2. 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. - - -h2. Can I Edit An Existing Recipe? - -Yes. Please improve anything and everything! Be sure to test your changes and make sure that your solution really is better. - - -h2. I Have a Really Efficient Solution, But It's Not As Readable As the Existing Recipe. Should I Add It? - -See the "Weird Recipe" note above. Do real people in the real world ever hit the performance constraint? If so, then by all means, add your strategy to the existing solution, and be sure to explain why your solution is not idiomatic. If a reader really has that problem, they'll be glad for the extra options. - - -h2. 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.) 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. - - -h2. I Found a Typo. Is That Enough of a Fix? Does That Count? - -Absolutely! - diff --git a/authors.md b/authors.md new file mode 100644 index 0000000..d5181f6 --- /dev/null +++ b/authors.md @@ -0,0 +1,46 @@ +--- +layout: default +title: Authors +--- + +# Authors + +The following people are totally rad and awesome because they have contributed recipes! + +* David Brady *ratgeyser@gmail.com* +* John Ford *jwford@gmail.com* +* Steven Reid *steven @ reidnorthwest . com* +* David Moulton *dave@themoultons.net* +* Sebastian Slomski *sebastian@simple-systems.org* +* Aaron Weinberger *aw9994@cs.ship.edu* +* James C. Holder *coffeescriptcookbook.com@thirdtruck.org* +* Jason Giedymin *jasong@apache.org* +* Phil Cohen *github@phlippers.net* +* João Moreno *coffeecb @joaomoreno .com* +* 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 + +*The following people are amazingly rad and awesome because they have helped fix the code for the site!* + +* David Brady *ratgeyser@gmail.com* +* Mike Moore *mike@blowmage.com* +* Peter Hellberg *peter@c7.se* +* 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! + +* [Amsul](http://github.com/amsul) reach@amsul.ca +* ...You! Check out the [contributing]({{ site.baseurl }}/contributing) section and get cracking! diff --git a/authors.textile b/authors.textile deleted file mode 100644 index f6e75aa..0000000 --- a/authors.textile +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: default -title: Authors ---- - -h1. Authors - -_The following people are totally rad and awesome because they have contributed recipes!_ - -* David Brady _ratgeyser@gmail.com_ -* John Ford _jwford@gmail.com_ -* Steven Reid _steven @ reidnorthwest . com_ -* David Moulton _dave@themoultons.net_ -* Sebastian Slomski _sebastian@simple-systems.org_ -* Aaron Weinberger _aw9994@cs.ship.edu_ -* James C. Holder _cs_cookbook@thirdtruck.org_ -* ...You! What are you waiting for? Check out the contributing section and get cracking! - - -h1. Developers - -_The following people are amazingly rad and awesome because they have helped fix the code for the site!_ - -* 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 section and get cracking! - - -h1. Designers - -_The following people are astonishingly rad and awesome because they did great design for the site!_ - -* Oh please SOMEBODY put their name here! Check out the 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.textile b/chapters/ajax/index.html similarity index 76% rename from chapters/ajax/index.textile rename to chapters/ajax/index.html index 004ced5..0dfcd24 100644 --- a/chapters/ajax/index.textile +++ b/chapters/ajax/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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 new file mode 100644 index 0000000..a023791 --- /dev/null +++ b/chapters/arrays/concatenating-arrays.md @@ -0,0 +1,71 @@ +--- +layout: recipe +title: Concatenating Arrays +chapter: Arrays +--- +## Problem + +You want to join two arrays together. + +## Solution + +There are two standard options for concatenating arrays in JavaScript. + +The first is to use JavaScript's Array `concat()` method: + +{% highlight coffeescript %} +array1 = [1, 2, 3] +array2 = [4, 5, 6] +array3 = array1.concat array2 +# => [1, 2, 3, 4, 5, 6] +{% endhighlight %} + +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 %} + +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 + +CoffeeScript lacks a special syntax for joining arrays, but `concat()` and `push()` are standard JavaScript methods. diff --git a/chapters/arrays/concatenating-arrays.textile b/chapters/arrays/concatenating-arrays.textile deleted file mode 100644 index 8113b84..0000000 --- a/chapters/arrays/concatenating-arrays.textile +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: recipe -title: Concatenating Arrays -chapter: Arrays ---- - -h1. Concatenating Arrays - -h2. Problem - -You want to join two arrays together. - -h2. Solution - -Use JavaScript's Array concat() method: - -{% highlight coffeescript %} -ray1 = [1,2,3] -ray2 = [4,5,6] -ray3 = ray1.concat(ray2) -# => [1, 2, 3, 4, 5, 6] -{% endhighlight %} - -h2. Discussion - -CoffeeScript lacks a special syntax for joining arrays, but concat is a standard JavaScript method. - -Note that ray1 is _not_ modified by the operation. The concatenated array is returned as a new object. 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 new file mode 100644 index 0000000..bcd8693 --- /dev/null +++ b/chapters/arrays/creating-a-string-from-an-array.md @@ -0,0 +1,21 @@ +--- +layout: recipe +title: Creating a String from an Array +chapter: Arrays +--- +## Problem + +You want to create a string from an array. + +## Solution + +Use JavaScript's Array toString() method: + +{% highlight coffeescript %} +["one", "two", "three"].toString() +# => 'one,two,three' +{% endhighlight %} + +## Discussion + +`toString()` is a standard JavaScript method. Don't forget the parentheses. 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.textile b/chapters/arrays/filtering-arrays.md similarity index 59% rename from chapters/arrays/filtering-arrays.textile rename to chapters/arrays/filtering-arrays.md index ce8c253..2476aea 100644 --- a/chapters/arrays/filtering-arrays.textile +++ b/chapters/arrays/filtering-arrays.md @@ -1,14 +1,13 @@ --- layout: recipe title: Filtering Arrays -chapter: Filtering Arrays +chapter: Arrays --- - -h2. Problem +## Problem You want to be able to filter arrays based on a boolean condition. -h2. Solution +## Solution Use Array.filter (ECMAScript 5): @@ -19,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] @@ -38,6 +38,6 @@ filtered_array = array.filter gt_five # => [6,7,8,9,10] {% endhighlight %} -h2. Discussion +## Discussion -This is similair to using ruby's Array#select method. +This is similar to using ruby's Array#select method. diff --git a/chapters/arrays/index.textile b/chapters/arrays/index.html similarity index 76% rename from chapters/arrays/index.textile rename to chapters/arrays/index.html index afa1d87..906700a 100644 --- a/chapters/arrays/index.textile +++ b/chapters/arrays/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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.textile b/chapters/arrays/list-comprehensions.md similarity index 75% rename from chapters/arrays/list-comprehensions.textile rename to chapters/arrays/list-comprehensions.md index 9794c0b..3ab2700 100644 --- a/chapters/arrays/list-comprehensions.textile +++ b/chapters/arrays/list-comprehensions.md @@ -3,14 +3,13 @@ layout: recipe title: List Comprehensions chapter: Arrays --- - -h2. Problem +## Problem You have an array of objects and want to map them to another array, similar to Python's list comprehensions. -h2. Solution +## Solution -Use a list comprehension, but don't forget about 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" }, @@ -24,7 +23,6 @@ names = (muppet.name for muppet in electric_mayhem) # => [ 'Doctor Teeth', 'Janice', 'Sgt. Floyd Pepper', 'Zoot', 'Lips', 'Animal' ] {% endhighlight %} -h2. 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 might be more elegant. +## 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]({{ site.baseurl }}/chapters/arrays/mapping-arrays) might be more elegant. diff --git a/chapters/arrays/mapping-arrays.textile b/chapters/arrays/mapping-arrays.md similarity index 84% rename from chapters/arrays/mapping-arrays.textile rename to chapters/arrays/mapping-arrays.md index 6bf7e5a..1be0e65 100644 --- a/chapters/arrays/mapping-arrays.textile +++ b/chapters/arrays/mapping-arrays.md @@ -3,14 +3,13 @@ layout: recipe title: Mapping Arrays chapter: Arrays --- - -h2. Problem +## Problem You have an array of objects and want to map them to another array, similar to Ruby's map. -h2. Solution +## 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" }, @@ -24,9 +23,8 @@ names = electric_mayhem.map (muppet) -> muppet.name # => [ 'Doctor Teeth', 'Janice', 'Sgt. Floyd Pepper', 'Zoot', 'Lips', 'Animal' ] {% endhighlight %} -h2. Discussion +## Discussion 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 new file mode 100644 index 0000000..25f1472 --- /dev/null +++ b/chapters/arrays/max-array-value.md @@ -0,0 +1,29 @@ +--- +layout: recipe +title: Max Array Value +chapter: Arrays +--- +## Problem + +You need to find the largest value contained in an array. + +## Solution + +You can use Math.max() JavaScript method along with splats. + +{% highlight coffeescript %} +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. + +{% highlight coffeescript %} +# ECMAScript 5 +[12,32,11,67,1,3].reduce (a,b) -> Math.max a, b +# => 67 +{% endhighlight %} + +## Discussion + +`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/max-array-value.textile b/chapters/arrays/max-array-value.textile deleted file mode 100644 index bbad4b7..0000000 --- a/chapters/arrays/max-array-value.textile +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: recipe -title: Max Array Value -chapter: Arrays ---- - -h2. Problem - -You need to find the largest value contained in an array. - -h2. Solution - -In ECMAScript 5, use Array#reduce. In older javascripts, use Math.max over a list comprehension: - -{% highlight coffeescript %} -# ECMAScript 5 -[12,32,11,67,1,3].reduce (a,b) -> Math.max(a,b) -# => 67 - -# 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 -# => 67 -{% endhighlight %} - -h2. 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. diff --git a/chapters/arrays/reducing-arrays.md b/chapters/arrays/reducing-arrays.md new file mode 100644 index 0000000..6b52690 --- /dev/null +++ b/chapters/arrays/reducing-arrays.md @@ -0,0 +1,42 @@ +--- +layout: recipe +title: Reducing Arrays +chapter: Arrays +--- +## Problem + +You have an array of objects and want to reduce them to a value, similar to Ruby's `reduce()` and `reduceRight()`. + +## Solution + +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 +# => 10 +{% endhighlight %} + +{% highlight coffeescript %} +["words", "of", "bunch", "A"].reduceRight (x, y) -> x + " " + y +# => 'A bunch of words' +{% endhighlight %} + +Or it may be something more complex such as aggregating elements from a list into a combined object. + +{% highlight coffeescript %} +people = [ + { name: 'alec', age: 10 } + { name: 'bert', age: 16 } + { name: 'chad', age: 17 } +] + +people.reduce (x, y) -> + x[y.name]= y.age + x +, {} +# => { alec: 10, bert: 16, chad: 17 } +{% endhighlight %} + +## Discussion + +Javascript introduced `reduce` and `reduceRight` in version 1.8. Coffeescript provides a natural and simple way to express anonymous functions. Both go together cleanly in the problem of merging a collection's items into a combined result. diff --git a/chapters/arrays/removing-duplicate-elements-from-arrays.md b/chapters/arrays/removing-duplicate-elements-from-arrays.md new file mode 100644 index 0000000..545ae17 --- /dev/null +++ b/chapters/arrays/removing-duplicate-elements-from-arrays.md @@ -0,0 +1,28 @@ +--- +layout: recipe +title: Removing Duplicate Elements from Arrays +chapter: Arrays +--- +## Problem + +You want to remove duplicate elements from an array. + +## Solution + +{% highlight coffeescript %} +Array::unique = -> + output = {} + output[@[key]] = @[key] for key in [0...@length] + value for key, value of output + +[1,1,2,2,2,3,4,5,6,6,6,"a","a","b","d","b","c"].unique() +# => [ 1, 2, 3, 4, 5, 6, 'a', 'b', 'd', 'c' ] +{% endhighlight %} + +## 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/reversing-arrays.textile b/chapters/arrays/reversing-arrays.md similarity index 64% rename from chapters/arrays/reversing-arrays.textile rename to chapters/arrays/reversing-arrays.md index 60b6070..ba599ec 100644 --- a/chapters/arrays/reversing-arrays.textile +++ b/chapters/arrays/reversing-arrays.md @@ -3,14 +3,11 @@ layout: recipe title: Reversing Arrays chapter: Arrays --- - -h1. Reversing an Array - -h2. Problem +## Problem You want to reverse an array. -h2. Solution +## Solution Use JavaScript's Array reverse() method: @@ -19,8 +16,6 @@ Use JavaScript's Array reverse() method: # => ["three", "two", "one"] {% endhighlight %} +## Discussion -h2. Discussion - -reverse() is a standard JavaScript method. Don't forget the parentheses. - +`reverse()` is a standard JavaScript method. Don't forget the parentheses. 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 new file mode 100644 index 0000000..773a107 --- /dev/null +++ b/chapters/arrays/testing-every-element.md @@ -0,0 +1,49 @@ +--- +layout: recipe +title: Testing Every Element +chapter: Arrays +--- +## Problem + +You want to be able to check that every element in an array meets a particular condition. + +## Solution + +Use Array.every (ECMAScript 5): + +{% highlight coffeescript %} +evens = (x for x in [0..10] by 2) + +evens.every (x)-> x % 2 == 0 +# => true +{% endhighlight %} + +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, pretend you have a multiple select list that looks like: + +{% highlight html %} + +{% endhighlight %} + +Now you want to verify that the user selected only numbers. Let's use Array.every: + +{% highlight coffeescript %} +validateNumeric = (item)-> + parseFloat(item) == parseInt(item) && !isNaN(item) + +values = $("#my-select-list").val() + +values.every validateNumeric +{% endhighlight %} + +## Discussion + +This is similar to using ruby's Array#all? method. + +[underscore]: http://documentcloud.github.com/underscore/ diff --git a/chapters/arrays/using-arrays-to-swap-variables.textile b/chapters/arrays/using-arrays-to-swap-variables.md similarity index 79% rename from chapters/arrays/using-arrays-to-swap-variables.textile rename to chapters/arrays/using-arrays-to-swap-variables.md index 1049002..0ff8905 100644 --- a/chapters/arrays/using-arrays-to-swap-variables.textile +++ b/chapters/arrays/using-arrays-to-swap-variables.md @@ -3,16 +3,13 @@ layout: recipe title: Using Arrays to Swap Variables chapter: Arrays --- - -h1. Using Arrays to Swap Variables - -h2. Problem +## Problem You want to use an array to swap variables. -h2. Solution +## Solution -Use CoffeeScript's "destructuring assignment":http://jashkenas.github.com/coffee-script/#destructuring syntax: +Use CoffeeScript's [destructuring assignment](http://jashkenas.github.com/coffee-script/#destructuring) syntax: {% highlight coffeescript %} a = 1 @@ -27,8 +24,7 @@ b # => 1 {% endhighlight %} - -h2. Discussion +## Discussion Destructuring assignment allows swapping two values without the use of a temporary variable. diff --git a/chapters/arrays/where-for-arrays-of-objects.md b/chapters/arrays/where-for-arrays-of-objects.md new file mode 100644 index 0000000..99cb556 --- /dev/null +++ b/chapters/arrays/where-for-arrays-of-objects.md @@ -0,0 +1,80 @@ +--- +layout: recipe +title: where for arrays of objects +chapter: Arrays +--- +## Problem + +You want to get an array of objects that match your request for some properties + +You have an Array of Objects, such as: + +{% highlight coffeescript %} +cats = [ + { + name: "Bubbles" + favoriteFood: "mice" + age: 1 + }, + { + name: "Sparkle" + favoriteFood: "tuna" + }, + { + name: "flyingCat" + favoriteFood: "mice" + age: 1 + } +] +{% endhighlight %} + +You want to filter with some properties, like cats.where({ age: 1}) or cats.where({ age: 1, favoriteFood: "mice"}) + +## Solution + +You can extend Array like this : + +{% highlight coffeescript %} +Array::where = (query) -> + return [] if typeof query isnt "object" + hit = Object.keys(query).length + @filter (item) -> + match = 0 + for key, val of query + match += 1 if item[key] is val + if match is hit then true else false + +cats.where age:1 +# => [ { name: 'Bubbles', favoriteFood: 'mice', age: 1 },{ name: 'flyingCat', favoriteFood: 'mice', age: 1 } ] + +cats.where age:1, name: "Bubbles" +# => [ { name: 'Bubbles', favoriteFood: 'mice', age: 1 } ] + +cats.where age:1, favoriteFood:"tuna" +# => [] +{% endhighlight %} + +## Discussion + +This is an exact match. we could make it more flexible with a matcher function : + +{% highlight coffeescript %} +Array::where = (query, matcher = (a,b) -> a is b) -> + return [] if typeof query isnt "object" + hit = Object.keys(query).length + @filter (item) -> + match = 0 + for key, val of query + match += 1 if matcher(item[key], val) + if match is hit then true else false + +cats.where name:"bubbles" +# => [] +# it's case sensitive + +cats.where name:"bubbles", (a, b) -> "#{ a }".toLowerCase() is "#{ b }".toLowerCase() +# => [ { name: 'Bubbles', favoriteFood: 'mice', age: 1 } ] +# now it's case insensitive +{% endhighlight %} + +it's more a method to deal with collection and it could be rename as "find" but popular libraries like underscore or lodash name it "where". diff --git a/chapters/arrays/zip-function.md b/chapters/arrays/zip-function.md new file mode 100644 index 0000000..bf1dfca --- /dev/null +++ b/chapters/arrays/zip-function.md @@ -0,0 +1,24 @@ +--- +layout: recipe +title: Python-like Zip Function +chapter: Arrays +--- +## Problem + +You want to zip together multiple arrays into an array of arrays, similar to Python's zip function. Python's zip function returns an array of tuples, where each tuple contains the i-th element from each of the argument arrays. + +## Solution + +Use the following CoffeeScript code: + +{% highlight coffeescript %} +# Usage: zip(arr1, arr2, arr3, ...) +zip = () -> + lengthArray = (arr.length for arr in arguments) + length = Math.min(lengthArray...) + for i in [0...length] + arr[i] for arr in arguments + +zip([0, 1, 2, 3], [0, -1, -2, -3]) +# => [[0, 0], [1, -1], [2, -2], [3, -3]] +{% endhighlight %} diff --git a/chapters/objects/chaining.textile b/chapters/classes_and_objects/chaining.md similarity index 73% rename from chapters/objects/chaining.textile rename to chapters/classes_and_objects/chaining.md index 8035604..d8d099e 100644 --- a/chapters/objects/chaining.textile +++ b/chapters/classes_and_objects/chaining.md @@ -1,32 +1,32 @@ --- layout: recipe title: Chaining Calls to an Object -chapter: Objects +chapter: Classes and Objects --- - -h2. Problem +## Problem You want to call multiple methods on a single object without having to reference that object each time. -h2. Solution +## Solution -Return the *this* (i.e. *@*) object after every chained method. +Return the `this` (i.e. `@`) object after every chained method. {% highlight coffeescript %} class CoffeeCup - properties: - strength: 'medium' - cream: false - sugar: false + constructor: -> + @properties= + strength: 'medium' + cream: false + sugar: false strength: (newStrength) -> @properties.strength = newStrength - @ + this cream: (newCream) -> @properties.cream = newCream - @ + this sugar: (newSugar) -> @properties.sugar = newSugar - @ + this morningCup = new CoffeeCup() @@ -38,7 +38,7 @@ eveningCup.properties # => { strength: 'dark', cream: true, sugar: true } {% endhighlight %} -h2. Discussion +## Discussion The jQuery library uses a similar approach by returning a selector object from every relevant method, modifying it as subsequent methods tweak the selection: @@ -58,17 +58,17 @@ addChainedAttributeAccessor = (obj, propertyAttr, attr) -> obj class TeaCup - properties: - size: 'medium' - type: 'black' - sugar: false - cream: false - -addChainedAttributeAccessor(TeaCup.prototype, 'properties', attr) for attr of TeaCup.prototype.properties + constructor: -> + @properties= + size: 'medium' + type: 'black' + sugar: false + cream: false + addChainedAttributeAccessor(this, 'properties', attr) for attr of @properties -earlgrey = new TeaCup().size('small').type('Earl Grey').sugar('false') +earlgrey = new TeaCup().size('small').type('Earl Grey').sugar(false) -earlgrey.properties # => { size: 'small', type: 'Earl Grey', sugar: false } +earlgrey.properties # => { size: 'small', type: 'Earl Grey', sugar: false, cream: false } earlgrey.sugar true diff --git a/chapters/classes_and_objects/class-methods-and-instance-methods.md b/chapters/classes_and_objects/class-methods-and-instance-methods.md new file mode 100644 index 0000000..62d34af --- /dev/null +++ b/chapters/classes_and_objects/class-methods-and-instance-methods.md @@ -0,0 +1,61 @@ +--- +layout: recipe +title: Class Methods and Instance Methods +chapter: Classes and Objects +--- +## Problem + +You want to create class and instance methods. + +## Solution + +### Class Method + +{% highlight coffeescript %} + +class Songs + @_titles: 0 # Although it's directly accessible, the leading _ defines it by convention as private property. + + @get_count: -> + @_titles + + constructor: (@artist, @title) -> + @constructor._titles++ # Refers to ._titles, in this case Songs.titles + +Songs.get_count() +# => 0 + +song = new Songs("Rick Astley", "Never Gonna Give You Up") +Songs.get_count() +# => 1 + +song.get_count() +# => TypeError: Object has no method 'get_count' + +{% endhighlight %} + +### Instance Method +{% highlight coffeescript %} + +class Songs + _titles: 0 # Although it's directly accessible, the leading _ defines it by convention as private property. + + get_count: -> + @_titles + + constructor: (@artist, @title) -> + @_titles++ + +song = new Songs("Rick Astley", "Never Gonna Give You Up") +song.get_count() +# => 1 + +Songs.get_count() +# => TypeError: Object function Songs(artist, title) ... has no method 'get_count' + +{% endhighlight %} + + +## Discussion + +Coffeescript will store class methods (also called static methods) on the object itself rather than on the object prototype (and thus on individual object instances), which conserves memory and gives a central location to store class-level values. diff --git a/chapters/classes_and_objects/class-variables-and-instance-variables.md b/chapters/classes_and_objects/class-variables-and-instance-variables.md new file mode 100644 index 0000000..23acd37 --- /dev/null +++ b/chapters/classes_and_objects/class-variables-and-instance-variables.md @@ -0,0 +1,99 @@ +--- +layout: recipe +title: Class Variables and Instance Variables +chapter: Classes and Objects +--- +## Problem + +You want to create class variables and instance variables (properties). + +## Solution + +### Class Variables + +{% highlight coffeescript %} +class Zoo + @MAX_ANIMALS: 50 + MAX_ZOOKEEPERS: 3 + + helpfulInfo: => + "Zoos may contain a maximum of #{@constructor.MAX_ANIMALS} animals and #{@MAX_ZOOKEEPERS} zoo keepers." + +Zoo.MAX_ANIMALS +# => 50 + +Zoo.MAX_ZOOKEEPERS +# => undefined (it is a prototype member) + +Zoo::MAX_ZOOKEEPERS +# => 3 + +zoo = new Zoo +zoo.MAX_ZOOKEEPERS +# => 3 +zoo.helpfulInfo() +# => "Zoos may contain a maximum of 50 animals and 3 zoo keepers." + +zoo.MAX_ZOOKEEPERS = "smelly" +zoo.MAX_ANIMALS = "seventeen" +zoo.helpfulInfo() +# => "Zoos may contain a maximum of 50 animals and smelly zoo keepers." +{% endhighlight %} + + +### Instance Variables + +You have to define instance variables (i.e. properties) inside a class' method, initialize your defaults in the constructor. + +{% highlight coffeescript %} +class Zoo + constructor: -> + @animals = [] # Here the instance variable is defined + + addAnimal: (name) -> + @animals.push name + + +zoo = new Zoo() +zoo.addAnimal 'elephant' + +otherZoo = new Zoo() +otherZoo.addAnimal 'lion' + +zoo.animals +# => ['elephant'] + +otherZoo.animals +# => ['lion'] +{% endhighlight %} + +#### WARNING! +*Do not add the variable accidently to the prototype, by defining it outside the constructor* (Even if mentioned [elsewhere](http://arcturo.github.io/library/coffeescript/03_classes.html#content), this does not work as intended, due to the underlying JavaScript prototype concept). + +{% highlight coffeescript %} +class BadZoo + animals: [] # Translates to BadZoo.prototype.animals = []; and is thus shared between instances + + addAnimal: (name) -> + @animals.push name # Works due to the prototype concept of Javascript + + +zoo = new BadZoo() +zoo.addAnimal 'elephant' + +otherZoo = new BadZoo() +otherZoo.addAnimal 'lion' + +zoo.animals +# => ['elephant','lion'] # Oops... + +otherZoo.animals +# => ['elephant','lion'] # Oops... + +BadZoo::animals +# => ['elephant','lion'] # The value is stored in the prototype +{% endhighlight %} + +## Discussion + +Coffeescript will store the values of class variables on the class itself rather than on the prototype it defines. These are useful for defining variables on classes which can't be overwritten by instance attribute variables. diff --git a/chapters/classes_and_objects/class-variables.textile b/chapters/classes_and_objects/class-variables.textile deleted file mode 100644 index 594f9a9..0000000 --- a/chapters/classes_and_objects/class-variables.textile +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: recipe -title: Class Variables -chapter: Classes and Objects ---- - -h2. Creating Class Variables - -You want to create a class variable. - -h2. Solution - -Use json notation in the class body; use :: to access it outside: - -{% highlight coffeescript %} -class Zoo - MAX_ANIMALS: 50 - -Zoo::MAX_ZOOKEEPERS = 5 - -Zoo::MAX_ANIMALS -# => 50 -{% endhighlight %} - -h2. Discussion - -Coffeescript will store these values on the class prototype (e.g. Zoo.prototype.MAX_ANIMALS) rather than on individual object instances, which conserves memory and gives a central location to store class-level values. diff --git a/chapters/classes_and_objects/cloning.textile b/chapters/classes_and_objects/cloning.md similarity index 76% rename from chapters/classes_and_objects/cloning.textile rename to chapters/classes_and_objects/cloning.md index 49b56d9..7325e03 100644 --- a/chapters/classes_and_objects/cloning.textile +++ b/chapters/classes_and_objects/cloning.md @@ -1,20 +1,30 @@ --- layout: recipe -title: Cloning an object (deep copy) +title: Cloning an Object (Deep Copy) chapter: Classes and Objects --- - -h2. Problem +## Problem You want to clone an object with all its sub-objects. -h2. Solution +## Solution {% highlight coffeescript %} clone = (obj) -> if not obj? or typeof obj isnt 'object' return obj + if obj instanceof Date + return new Date(obj.getTime()) + + if obj instanceof RegExp + flags = '' + flags += 'g' if obj.global? + flags += 'i' if obj.ignoreCase? + flags += 'm' if obj.multiline? + flags += 'y' if obj.sticky? + return new RegExp(obj.source, flags) + newInstance = new obj.constructor() for key of obj @@ -34,7 +44,7 @@ console.log x.foo isnt y.foo, x.foo, y.foo # => true, bar, test {% endhighlight %} -h2. Discussion +## Discussion The difference between copying an object through assignment and through this clone-function is how they handle references. The assignment only copies the object's reference, whereas the clone-function creates a complete new object by @@ -43,6 +53,7 @@ The difference between copying an object through assignment and through this clo * repeating these steps for all sub-objects by calling the clone-function recursively. Example of an assignment copy: + {% highlight coffeescript %} x = foo: 'bar' diff --git a/chapters/classes_and_objects/create-object-literal-if-not-exist.textile b/chapters/classes_and_objects/create-object-literal-if-not-exist.textile deleted file mode 100644 index 3cbb5cc..0000000 --- a/chapters/classes_and_objects/create-object-literal-if-not-exist.textile +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: recipe -title: Create an object literal if it does not already exist -chapter: Classes and Objects ---- - -h2. Problem - -You want to initialize an object literal, but you do not want to overwrite the object if it already exists. - - -h2. Solution - -Use the Existential operator - -{% highlight coffeescript %} -window.MY_NAMESPACE ?= {} -{% endhighlight %} - - -h2. Discussion - -This is equivalent to the following JavaScript: - -{% highlight javascript %} -window.MY_NAMESPACE = window.MY_NAMESPACE || {}; -{% endhighlight %} - -Common JavaScript technique, using object literal to define a namespace. This saves us from clobbering the namespace if it already exists. diff --git a/chapters/classes_and_objects/index.textile b/chapters/classes_and_objects/index.html similarity index 77% rename from chapters/classes_and_objects/index.textile rename to chapters/classes_and_objects/index.html index 7a2dcea..013fd47 100644 --- a/chapters/classes_and_objects/index.textile +++ b/chapters/classes_and_objects/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/classes_and_objects/mixins.md b/chapters/classes_and_objects/mixins.md new file mode 100644 index 0000000..8f99d84 --- /dev/null +++ b/chapters/classes_and_objects/mixins.md @@ -0,0 +1,49 @@ +--- +layout: recipe +title: Mixins for classes +chapter: Classes and Objects +--- +## Problem + +You have a few utility methods that you want to include in a number of different classes. + +## Solution + +Use a mixOf factory function that generates a mixed superclass for you. + +{% highlight coffeescript %} +mixOf = (base, mixins...) -> + class Mixed extends base + for mixin in mixins by -1 #earlier mixins override later ones + for name, method of mixin:: + Mixed::[name] = method + Mixed + +... + +class DeepThought + answer: -> + 42 + +class PhilosopherMixin + pontificate: -> + console.log "hmm..." + @wise = yes + +class DeeperThought extends mixOf DeepThought, PhilosopherMixin + answer: -> + @pontificate() + super() + +earth = new DeeperThought +earth.answer() +# hmm... +# => 42 +{% endhighlight %} + +## Discussion + +This is intended for lightweight mixins. Thus you inherit methods of the +base and its ancestors, and those of the mixins, but not those of the ancestors of +the mixins. Also, after declaring a mixed class, further changes in the mixins are not +reflected. diff --git a/chapters/classes_and_objects/object-literal.md b/chapters/classes_and_objects/object-literal.md new file mode 100644 index 0000000..602ccf3 --- /dev/null +++ b/chapters/classes_and_objects/object-literal.md @@ -0,0 +1,48 @@ +--- +layout: recipe +title: Create an Object Literal if It Does Not Already Exist +chapter: Classes and Objects +--- +## Problem + +You want to initialize an object literal, but you do not want to overwrite the object if it already exists. + +## Solution + +Use the Existential operator + +{% highlight coffeescript %} +window.MY_NAMESPACE ?= {} +{% endhighlight %} + +## Discussion + +This is equivalent to the following JavaScript: + +{% highlight javascript %} +if(window.MY_NAMESPACE === null || window.MY_NAMESPACE === undefined) { + window.MY_NAMESPACE = {}; +} +{% endhighlight %} + +## Problem + +You want to make a conditonal assignment if it does not exists or if it is falsy (empty, 0, null, false) + +## Solution + +Use the Conditional assignment operator + +{% highlight coffeescript %} +window.my_variable ||= {} +{% endhighlight %} + +## Discussion + +This is equivalent to the following JavaScript: + +{% highlight javascript %} +window.my_variable = window.my_variable || {}; +{% endhighlight %} + +Common JavaScript technique, using conditional assignment to ensure that we have an object that is not falsy diff --git a/chapters/classes_and_objects/type-function.md b/chapters/classes_and_objects/type-function.md new file mode 100644 index 0000000..d9355c0 --- /dev/null +++ b/chapters/classes_and_objects/type-function.md @@ -0,0 +1,39 @@ +--- +layout: recipe +title: A CoffeeScript Type Function +chapter: Classes and Objects +--- +## Problem + +You'd like to know the type of a object without using typeof. (See http://javascript.crockford.com/remedial.html for more information on why typeof is pretty inferior.) + +## Solution + +Use the following function: + +{% highlight coffeescript %} + type = (obj) -> + if obj == undefined or obj == null + return String obj + classToType = { + '[object Boolean]': 'boolean', + '[object Number]': 'number', + '[object String]': 'string', + '[object Function]': 'function', + '[object Array]': 'array', + '[object Date]': 'date', + '[object RegExp]': 'regexp', + '[object Object]': 'object' + } + return classToType[Object.prototype.toString.call(obj)] +{% endhighlight %} + +## Discussion + +This function was modeled on jQuery's [$.type function](http://api.jquery.com/jQuery.type/). + +Note that, as an alternative to type checking, you can often use duck typing and the existential operator together to eliminating the need to examine an object's type, in certain cases. For example, here is exception-free code that pushes an element to an array, if myArray is in fact an array (or array-like, with a push function), and does nothing otherwise. + +{% highlight coffeescript %} +myArray?.push? myValue +{% endhighlight %} diff --git a/chapters/databases/index.html b/chapters/databases/index.html new file mode 100644 index 0000000..abf1318 --- /dev/null +++ b/chapters/databases/index.html @@ -0,0 +1,18 @@ +--- +layout: chapter +title: Databases +chapter: Databases +--- + +{% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} +{% capture indexurl %}{{ url }}/index.html{% endcapture %} + +
    +{% for page in site.pages %} + {% if page.url contains url %} + {% unless page.url == indexurl %} +
  • {{ page.title }}
  • + {% endunless %} + {% endif %} +{% endfor %} +
diff --git a/chapters/databases/mongodb.md b/chapters/databases/mongodb.md new file mode 100644 index 0000000..161ec6f --- /dev/null +++ b/chapters/databases/mongodb.md @@ -0,0 +1,67 @@ +--- +layout: recipe +title: MongoDB +chapter: Databases +--- +## Problem + +You need to interface with a MongoDB database. + +## Solution + +### For Node.js + +### Setup +* [Install MongoDB](http://www.mongodb.org/display/DOCS/Quickstart) on your computer if you have not already. + +* [Install the native MongoDB module](https://github.com/christkv/node-mongodb-native). + +#### Saving Records + +{% highlight coffeescript %} +mongo = require 'mongodb' + +server = new mongo.Server "127.0.0.1", 27017, {} + +client = new mongo.Db 'test', server, {w:1} + +# save() updates existing records or inserts new ones as needed +exampleSave = (dbErr, collection) -> + console.log "Unable to access database: #{dbErr}" if dbErr + collection.save { _id: "my_favorite_latte", flavor: "honeysuckle" }, (err, docs) -> + console.log "Unable to save record: #{err}" if err + client.close() + +client.open (err, database) -> + client.collection 'coffeescript_example', exampleSave +{% endhighlight %} + +#### Finding Records + +{% highlight coffeescript %} +mongo = require 'mongodb' + +server = new mongo.Server "127.0.0.1", 27017, {} + +client = new mongo.Db 'test', server, {w:1} + +exampleFind = (dbErr, collection) -> + console.log "Unable to access database: #{dbErr}" if dbErr + collection.find({ _id: "my_favorite_latte" }).nextObject (err, result) -> + if err + console.log "Unable to find record: #{err}" + else + console.log result # => { id: "my_favorite_latte", flavor: "honeysuckle" } + client.close() + +client.open (err, database) -> + client.collection 'coffeescript_example', exampleFind +{% endhighlight %} + +### For Browsers + +A [REST-based interface](https://github.com/tdegrunt/mongodb-rest) is in the works. This will provide AJAX-based access. + +## Discussion + +This recipe breaks the *save* and *find* into separate examples in order to separate the MongoDB-specific concerns from the task of connection and callback management. The [async module](https://github.com/caolan/async) can help with that. diff --git a/chapters/databases/sqlite.md b/chapters/databases/sqlite.md new file mode 100644 index 0000000..3453da0 --- /dev/null +++ b/chapters/databases/sqlite.md @@ -0,0 +1,92 @@ +--- +layout: recipe +title: SQLite +chapter: Databases +--- +## Problem + +You need to interface with a [SQLite](http://www.sqlite.org/) database from inside of Node.js. + +## Solution + +Use the [SQLite module](http://code.google.com/p/node-sqlite/). + +{% highlight coffeescript %} +sqlite = require 'sqlite' + +db = new sqlite.Database + +# The module uses asynchronous methods, +# so we chain the calls the db.execute +exampleCreate = -> + db.execute "CREATE TABLE snacks (name TEXT(25), flavor TEXT(25))", + (exeErr, rows) -> + throw exeErr if exeErr + exampleInsert() + +exampleInsert = -> + db.execute "INSERT INTO snacks (name, flavor) VALUES ($name, $flavor)", + { $name: "Potato Chips", $flavor: "BBQ" }, + (exeErr, rows) -> + throw exeErr if exeErr + exampleSelect() + +exampleSelect = -> + db.execute "SELECT name, flavor FROM snacks", + (exeErr, rows) -> + throw exeErr if exeErr + console.log rows[0] # => { name: 'Potato Chips', flavor: 'BBQ' } + +# :memory: creates a DB in RAM +# You can supply a filepath (like './example.sqlite') to create/open one on disk +db.open ":memory:", (openErr) -> + throw openErr if openErr + exampleCreate() +{% endhighlight %} + +## Discussion + +You can also prepare your SQL queries beforehand: + +{% highlight coffeescript %} +sqlite = require 'sqlite' +async = require 'async' # Not required but added to make the example more concise + +db = new sqlite.Database + +createSQL = "CREATE TABLE drinks (name TEXT(25), price NUM)" + +insertSQL = "INSERT INTO drinks (name, price) VALUES (?, ?)" + +selectSQL = "SELECT name, price FROM drinks WHERE price < ?" + +create = (onFinish) -> + db.execute createSQL, (exeErr) -> + throw exeErr if exeErr + onFinish() + +prepareInsert = (name, price, onFinish) -> + db.prepare insertSQL, (prepErr, statement) -> + statement.bindArray [name, price], (bindErr) -> + statement.fetchAll (fetchErr, rows) -> # Called so that it executes the insert + onFinish() + +prepareSelect = (onFinish) -> + db.prepare selectSQL, (prepErr, statement) -> + statement.bindArray [1.00], (bindErr) -> + statement.fetchAll (fetchErr, rows) -> + console.log rows[0] # => { name: "Mia's Root Beer", price: 0.75 } + onFinish() + +db.open ":memory:", (openErr) -> + async.series([ + (onFinish) -> create onFinish, + (onFinish) -> prepareInsert "LunaSqueeze", 7.95, onFinish, + (onFinish) -> prepareInsert "Viking Sparkling Grog", 4.00, onFinish, + (onFinish) -> prepareInsert "Mia's Root Beer", 0.75, onFinish, + (onFinish) -> prepareSelect onFinish + ]) +{% endhighlight %} + +The [SQLite version of SQL](http://www.sqlite.org/lang.html) and the [node-sqlite module documentation](https://github.com/orlandov/node-sqlite#readme) provide more complete information. + diff --git a/chapters/dates_and_times/date-of-easter.md b/chapters/dates_and_times/date-of-easter.md new file mode 100644 index 0000000..09ad7bb --- /dev/null +++ b/chapters/dates_and_times/date-of-easter.md @@ -0,0 +1,50 @@ +--- +layout: recipe +title: Calculate the Date of Easter Sunday +chapter: Dates and Times +--- +## Problem + +You need to find the month and day of the Easter Sunday for given year. + +## Solution + +The following function returns array with two elements: month (1-12) and day of the Easter Sunday. If no arguments are given +result is for the current year. +This is an implementation of [Anonymous Gregorian algorithm](http://en.wikipedia.org/wiki/Computus#Anonymous_Gregorian_algorithm) in CoffeeScript. + +{% highlight coffeescript %} + +gregorianEaster = (year = (new Date).getFullYear()) -> + a = year % 19 + b = ~~(year / 100) + c = year % 100 + d = ~~(b / 4) + e = b % 4 + f = ~~((b + 8) / 25) + g = ~~((b - f + 1) / 3) + h = (19 * a + b - d - g + 15) % 30 + i = ~~(c / 4) + k = c % 4 + l = (32 + 2 * e + 2 * i - h - k) % 7 + m = ~~((a + 11 * h + 22 * l) / 451) + n = h + l - 7 * m + 114 + month = ~~(n / 31) + day = (n % 31) + 1 + [month, day] + +{% endhighlight %} + +## Discussion + +NB! Javascript numbers months from 0 to 11 so .getMonth() for date in March will return 2, this function will return 3. +You can modify the function if you want this to be consistent. + +The function uses ~~ trick instead of Math.floor(). + +{% highlight coffeescript %} + +gregorianEaster() # => [4, 24] (April 24th in 2011) +gregorianEaster 1972 # => [4, 2] + +{% endhighlight %} diff --git a/chapters/dates_and_times/date-of-thanksgiving.md b/chapters/dates_and_times/date-of-thanksgiving.md new file mode 100644 index 0000000..a26b36c --- /dev/null +++ b/chapters/dates_and_times/date-of-thanksgiving.md @@ -0,0 +1,53 @@ +--- +layout: recipe +title: Calculate the Date of Thanksgiving (USA and Canada) +chapter: Dates and Times +--- +## Problem + +You need to calculate when Thanksgiving is in a given year. + +## Solution + +The following functions return the day of Thanksgiving for a given year. If no year is given then current year is used. + +In the USA Thanksgiving is celebrated on the fourth Thursday in November: + +{% highlight coffeescript %} + +thanksgivingDayUSA = (year = (new Date).getFullYear()) -> + first = new Date year, 10, 1 + day_of_week = first.getDay() + 22 + (11 - day_of_week) % 7 + +{% endhighlight %} + +In Canada it is the second Monday in October: + +{% highlight coffeescript %} + +thanksgivingDayCA = (year = (new Date).getFullYear()) -> + first = new Date year, 9, 1 + day_of_week = first.getDay() + 8 + (8 - day_of_week) % 7 + +{% endhighlight %} + +## Discussion + +{% highlight coffeescript %} + +thanksgivingDayUSA() #=> 24 (November 24th, 2011) + +thanksgivingDayCA() # => 10 (October 10th, 2011) + +thanksgivingDayUSA(2012) # => 22 (November 22nd) + +thanksgivingDayCA(2012) # => 8 (October 8th) + +{% endhighlight %} + +The idea is very simple: +1. Find out what day of the week is the first day of respective month (November for USA, October for Canada). +2. Calculate offset from that day to the next occurrence of weekday required (Thursday for USA, Monday for Canada). +3. Add that offset to the first possible date of the holiday (22nd for USA Thanksgiving, 8th for Canada). diff --git a/chapters/dates_and_times/days-between-two-dates.md b/chapters/dates_and_times/days-between-two-dates.md new file mode 100644 index 0000000..b6a94e9 --- /dev/null +++ b/chapters/dates_and_times/days-between-two-dates.md @@ -0,0 +1,37 @@ +--- +layout: recipe +title: Get Days Between Two Dates +chapter: Dates and Times +--- +## Problem + +You need to find how many seconds, minutes, hours, days, months or years have passed between two dates. + +## Solution + +Use JavaScript's Date function getTime(). Which provides how much time in milliseconds have passed since 01/01/1970: + +{% highlight coffeescript %} +DAY = 1000 * 60 * 60 * 24 + +d1 = new Date('02/01/2011') +d2 = new Date('02/06/2011') + +days_passed = Math.round((d2.getTime() - d1.getTime()) / DAY) +{% endhighlight %} + +## Discussion + +Using milliseconds makes the life easier to avoid overflow mistakes with Dates. So we first calculate how many milliseconds are in a day. +Then, given two distinct dates, get the difference in milliseconds between two dates and then divide by how many milliseconds are in a day. It will return the days between two distinct dates. + +If you'd like to calculate the hours between two date objects, you can do that by dividing the difference in milliseconds by the conversion of milliseconds to hours. The same goes for minutes and seconds. + +{% highlight coffeescript %} +HOUR = 1000 * 60 * 60 + +d1 = new Date('02/01/2011 02:20') +d2 = new Date('02/06/2011 05:20') + +hour_passed = Math.round((d2.getTime() - d1.getTime()) / HOUR) +{% endhighlight %} diff --git a/chapters/dates_and_times/days-between-two-dates.textile b/chapters/dates_and_times/days-between-two-dates.textile deleted file mode 100644 index 7649c81..0000000 --- a/chapters/dates_and_times/days-between-two-dates.textile +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: recipe -title: Get Days Between two Dates -chapter: Dates and Times ---- - -h2. Problem - -You need to find how much seconds minutes, hours, days, months or years has passed between two dates. - -h2. Solution - -Use JavaScript's Date function getTime(). Which provides how much time in miliseconds has passed since 01/01/1970: - -{% highlight coffeescript %} -DAY = 1000 * 60 * 60 * 24 - -d1 = new Date('02/01/2011') -d2 = new Date('02/06/2011') - -days_passed = Math.round((d2.getTime() - d1.getTime()) / DAY) -{% endhighlight %} - -h2. Discussion - -Using miliseconds makes the life easier to avoid overflow mistakes with Dates. So we first calculate how much miliseconds has a day. -Then, given two distincit dates, just get the diference in miliseconds betwen two dates and then divide by how much miliseconds has a -day. It will get you the days between two distinct dates. - -If you'd like to calculate the hours between two dates objects you can do that just by dividing the diference in miliseconds by the -convertion of miliseconds to hours. The same goes to minutes and seconds. - -{% highlight coffeescript %} -HOUR = 1000 * 60 * 60 - -d1 = new Date('02/01/2011 02:20') -d2 = new Date('02/06/2011 05:20') - -hour_passed = Math.round((d2.getTime() - d1.getTime()) / HOUR) -{% endhighlight %} - diff --git a/chapters/dates_and_times/finding-last-day-of-the-month.textile b/chapters/dates_and_times/finding-last-day-of-the-month.md similarity index 71% rename from chapters/dates_and_times/finding-last-day-of-the-month.textile rename to chapters/dates_and_times/finding-last-day-of-the-month.md index 6cd96b2..98ccec6 100644 --- a/chapters/dates_and_times/finding-last-day-of-the-month.textile +++ b/chapters/dates_and_times/finding-last-day-of-the-month.md @@ -3,22 +3,19 @@ layout: recipe title: Finding the Last Day of the Month chapter: Dates and Times --- - -h2. Problem +## Problem You need to find the last day of the month, but don't want to keep a lookup table of the number of days in each month of the year. -h2. Solution +## Solution Use JavaScript's Date underflow to find the -1th day of the following month: {% highlight coffeescript %} now = new Date -lastDayOfTheMonth = new Date(1900+now.getYear(), now.getMonth()+1, -1) +lastDayOfTheMonth = new Date(1900+now.getYear(), now.getMonth()+1, 0) {% endhighlight %} -h2. Discussion - -JavaScript's Date constructor cheerfully handles overflow and underflow conditions, which makes date math very easy. Given this ease of manipulation, it doesn't make sense to worry about how many days are in a given month; just nudge the math around. In December, the solution above will actually ask for the -1th day of the 13th month of the current year, which works out to the -1th day of January of NEXT year, which works out to the 31st day of December of the current year. - +## Discussion +JavaScript's Date constructor cheerfully handles overflow and underflow conditions, which makes date math very easy. Given this ease of manipulation, it doesn't make sense to worry about how many days are in a given month; just nudge the math around. In December, the solution above will actually ask for the 0th day of the 13th month of the current year, which works out to the day before the 1st day of January of NEXT year, which works out to the 31st day of December of the current year. diff --git a/chapters/dates_and_times/finding-last-or-next-month.textile b/chapters/dates_and_times/finding-last-or-next-month.md similarity index 97% rename from chapters/dates_and_times/finding-last-or-next-month.textile rename to chapters/dates_and_times/finding-last-or-next-month.md index 19195be..81ea611 100644 --- a/chapters/dates_and_times/finding-last-or-next-month.textile +++ b/chapters/dates_and_times/finding-last-or-next-month.md @@ -3,12 +3,11 @@ layout: recipe title: Finding Last (or Next) Month chapter: Dates and Times --- - -h2. Problem +## Problem You need to calculate a relative date range like "last month" or "next month". -h2. Solution +## Solution Add or subtract from the current month, secure in the knowledge that JavaScript's Date constructor will fix up the math. @@ -25,7 +24,7 @@ lastMonthEnd = new Date 1900+now.getYear(), now.getMonth(), 0 # => "Sat, 30 Apr 2011 06:00:00 GMT" {% endhighlight %} -h2. Discussion +## Discussion JavaScript Date objects will cheerfully handle underflows and overflows in the month and day fields, and will adjust the date object accordingly. You can ask for the 42nd of March, for example, and will get the 11th of April. @@ -42,4 +41,3 @@ The same is true for overflows: thirtyNinthOfFourteember = new Date 1900+now.getYear(), 13, 39 # => "Sat, 10 Mar 2012 07:00:00 GMT" {% endhighlight %} - diff --git a/chapters/dates_and_times/index.textile b/chapters/dates_and_times/index.html similarity index 77% rename from chapters/dates_and_times/index.textile rename to chapters/dates_and_times/index.html index 671240e..3009762 100644 --- a/chapters/dates_and_times/index.textile +++ b/chapters/dates_and_times/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/dates_and_times/moon-phase-for-date.md b/chapters/dates_and_times/moon-phase-for-date.md new file mode 100644 index 0000000..c97d444 --- /dev/null +++ b/chapters/dates_and_times/moon-phase-for-date.md @@ -0,0 +1,136 @@ +--- +layout: recipe +title: Calculate Phase of the Moon for a Date +chapter: Dates and Times +--- +## Problem + +You want to find the current phase of the moon. + +## Solution + +The following code provides a method to calculate the phase of the moon for a given date. + +{% highlight coffeescript %} +# moonPhase.coffee + +# Moon-phase calculator +# Roger W. Sinnott, Sky & Telescope, June 16, 2006 +# http://www.skyandtelescope.com/observing/objects/javascript/moon_phases +# +# Translated to CoffeeScript by Mike Hatfield @WebCoding4Fun + +proper_ang = (big) -> + tmp = 0 + if big > 0 + tmp = big / 360.0 + tmp = (tmp - (~~tmp)) * 360.0 + else + tmp = Math.ceil(Math.abs(big / 360.0)) + tmp = big + tmp * 360.0 + + tmp + +jdn = (date) -> + month = date.getMonth() + day = date.getDate() + year = date.getFullYear() + zone = date.getTimezoneOffset() / 1440 + + mm = month + dd = day + yy = year + + yyy = yy + mmm = mm + if mm < 3 + yyy = yyy - 1 + mmm = mm + 12 + + day = dd + zone + 0.5 + a = ~~( yyy / 100 ) + b = 2 - a + ~~( a / 4 ) + jd = ~~( 365.25 * yyy ) + ~~( 30.6001 * ( mmm+ 1 ) ) + day + 1720994.5 + jd + b if jd > 2299160.4999999 + +moonElong = (jd) -> + dr = Math.PI / 180 + rd = 1 / dr + meeDT = Math.pow((jd - 2382148), 2) / (41048480 * 86400) + meeT = (jd + meeDT - 2451545.0) / 36525 + meeT2 = Math.pow(meeT, 2) + meeT3 = Math.pow(meeT, 3) + meeD = 297.85 + (445267.1115 * meeT) - (0.0016300 * meeT2) + (meeT3 / 545868) + meeD = (proper_ang meeD) * dr + meeM1 = 134.96 + (477198.8676 * meeT) + (0.0089970 * meeT2) + (meeT3 / 69699) + meeM1 = (proper_ang meeM1) * dr + meeM = 357.53 + (35999.0503 * meeT) + meeM = (proper_ang meeM) * dr + + elong = meeD * rd + 6.29 * Math.sin( meeM1 ) + elong = elong - 2.10 * Math.sin( meeM ) + elong = elong + 1.27 * Math.sin( 2*meeD - meeM1 ) + elong = elong + 0.66 * Math.sin( 2*meeD ) + elong = proper_ang elong + elong = Math.round elong + + moonNum = ( ( elong + 6.43 ) / 360 ) * 28 + moonNum = ~~( moonNum ) + + if moonNum is 28 then 0 else moonNum + +getMoonPhase = (age) -> + moonPhase = "new Moon" + moonPhase = "first quarter" if age > 3 and age < 11 + moonPhase = "full Moon" if age > 10 and age < 18 + moonPhase = "last quarter" if age > 17 and age < 25 + + if ((age is 1) or (age is 8) or (age is 15) or (age is 22)) + moonPhase = "1 day past " + moonPhase + + if ((age is 2) or (age is 9) or (age is 16) or (age is 23)) + moonPhase = "2 days past " + moonPhase + + if ((age is 3) or (age is 1) or (age is 17) or (age is 24)) + moonPhase = "3 days past " + moonPhase + + if ((age is 4) or (age is 11) or (age is 18) or (age is 25)) + moonPhase = "3 days before " + moonPhase + + if ((age is 5) or (age is 12) or (age is 19) or (age is 26)) + moonPhase = "2 days before " + moonPhase + + if ((age is 6) or (age is 13) or (age is 20) or (age is 27)) + moonPhase = "1 day before " + moonPhase + + moonPhase + +MoonPhase = exports? and exports or @MoonPhase = {} + +class MoonPhase.Calculator + getMoonDays: (date) -> + jd = jdn date + moonElong jd + + getMoonPhase: (date) -> + jd = jdn date + getMoonPhase( moonElong jd ) +{% endhighlight %} + +## Discussion + +This code exposes a MoonPhase Calculator object with two methods. Calculator -> getMoonPhase will return a text representation of the lunar phase for the date provided. + +This can be used in both the browser and Node.js. + +{% highlight console %} +$ node +> var MoonPhase = require('./moonPhase.js'); + undefined +> var calc = new MoonPhase.Calculator(); + undefined +> calc.getMoonPhase(new Date()); + 'full moon' +> calc.getMoonPhase(new Date(1972, 6, 30)); + '3 days before last quarter' +{% endhighlight %} \ No newline at end of file diff --git a/chapters/design_patterns/adapter.md b/chapters/design_patterns/adapter.md new file mode 100644 index 0000000..e82d19b --- /dev/null +++ b/chapters/design_patterns/adapter.md @@ -0,0 +1,54 @@ +--- +layout: recipe +title: Adapter pattern +chapter: Design patterns +--- +## Problem + +Imagine you are traveling to a foreign country and once at your hotel room you realize your power cord socket is not compatible with the wall socket. +Luckily, you remembered you've brought your power adapter with you. +It will connect your power cord socket on one side and wall socket on the other side, allowing for communication between them. + +The same situation may arise in code, when 2 (or more) instances (of classes, modules, etc.) want to talk to each other, but whose communication protocol (e.i. the language they use to communicate) is different from each other. +In such a situation, the [Adapter Pattern]({{ site.baseurl }}//en.wikipedia.org/wiki/Adapter_pattern) comes in handy. It will do the translation, from one side to the other. + +## Solution + +{% highlight coffeescript %} +# a fragment of 3-rd party grid component +class AwesomeGrid + constructor: (@datasource)-> + @sort_order = 'ASC' + @sorter = new NullSorter # in this place we use NullObject pattern (another useful pattern) + setCustomSorter: (@customSorter) -> + @sorter = customSorter + sort: () -> + @datasource = @sorter.sort @datasource, @sort_order + # don't forget to change sort order + + +class NullSorter + sort: (data, order) -> # do nothing; it is just a stub + +class RandomSorter + sort: (data)-> + for i in [data.length-1..1] #let's shuffle the data a bit + j = Math.floor Math.random() * (i + 1) + [data[i], data[j]] = [data[j], data[i]] + return data + +class RandomSorterAdapter + constructor: (@sorter) -> + sort: (data, order) -> + @sorter.sort data + +agrid = new AwesomeGrid ['a','b','c','d','e','f'] +agrid.setCustomSorter new RandomSorterAdapter(new RandomSorter) +agrid.sort() # sort data with custom sorter through adapter + +{% endhighlight %} + +## Discussion + +Adapter is useful when you have to organize an interaction between two objects with different interfaces. It can happen when you use 3rd party libraries or you work with legacy code. +In any case be careful with adapter: it can be helpful but it can instigate design errors. diff --git a/chapters/design_patterns/bridge.textile b/chapters/design_patterns/bridge.md similarity index 94% rename from chapters/design_patterns/bridge.textile rename to chapters/design_patterns/bridge.md index 80be632..b9d6f56 100644 --- a/chapters/design_patterns/bridge.textile +++ b/chapters/design_patterns/bridge.md @@ -3,12 +3,11 @@ layout: recipe title: Bridge Pattern chapter: Design Patterns --- - -h2. Problem +## Problem You need to maintain a reliable interface for code that can change frequently or change between multiple implementations. -h2. Solution +## Solution Use the Bridge pattern as an intermediate between the different implementations and the rest of the code. @@ -50,6 +49,6 @@ else if root? saver.save data {% endhighlight %} -h2. Discussion +## Discussion -The Bridge pattern helps you to move the implementation-specific code out of sight so that you can focus on your program's specific code. In the above example, the rest of your application can call _saver.save data_ without regard for where the file ultimately ends up. +The Bridge pattern helps you to move the implementation-specific code out of sight so that you can focus on your program's specific code. In the above example, the rest of your application can call `saver.save data` without regard for where the file ultimately ends up. diff --git a/chapters/design_patterns/builder.textile b/chapters/design_patterns/builder.md similarity index 85% rename from chapters/design_patterns/builder.textile rename to chapters/design_patterns/builder.md index 9fc8191..97ccc01 100644 --- a/chapters/design_patterns/builder.textile +++ b/chapters/design_patterns/builder.md @@ -3,16 +3,15 @@ layout: recipe title: Builder Pattern chapter: Design Patterns --- - -h2. Problem +## Problem You need to prepare a complicated, multi-part object, but you expect to do it more than once or with varying configurations. -h2. Solution +## Solution Create a Builder to encapsulate the object production process. -The Todo.txt format provides an advanced but still plain-text method for maintaining lists of to-do items. Typing out each item by hand would provide exhausting and error-prone, however, so a TodoTxtBuilder class could save us the trouble: +The [Todo.txt](http://todotxt.com) format provides an advanced but still plain-text method for maintaining lists of to-do items. Typing out each item by hand would provide exhausting and error-prone, however, so a TodoTxtBuilder class could save us the trouble: {% highlight coffeescript %} class TodoTxtBuilder @@ -51,11 +50,11 @@ workBuilder.newTodo "Remind Sean about the failing unit tests", contexts: ["meet {% endhighlight %} -h2. Discussion +## Discussion The TodoTxtBuilder class takes care of all the heavy lifting of text generation and lets the programmer focus on the unique elements of each to-do item. Additionally, a command line tool or GUI could plug into this code and still retain support for later, more advanced versions of the format with ease. -h3. Pre-Construction +### Pre-Construction Instead of creating a new instance of the needed object from scratch every time, we shift the burden to a separate object that we can then tweak during the object creation process. @@ -85,7 +84,7 @@ builder.newTodo "Fill gas tank" # => '2011-10-13 Fill gas tank +summerVacation' {% endhighlight %} -h3. Exercises -* Expand the project- and context-tag generation code to filter out duplicate entries. -** Some Todo.txt users like to insert project and context tags inside the description of their to-do items. Add code to identify these tags and filter them out of the end tags. +### Exercises +* Expand the project- and context-tag generation code to filter out duplicate entries. +* Some Todo.txt users like to insert project and context tags inside the description of their to-do items. Add code to identify these tags and filter them out of the end tags. diff --git a/chapters/design_patterns/command.md b/chapters/design_patterns/command.md new file mode 100644 index 0000000..3c1355d --- /dev/null +++ b/chapters/design_patterns/command.md @@ -0,0 +1,59 @@ +--- +layout: recipe +title: Command Pattern +chapter: Design Patterns +--- +## Problem + +You need to let another object handle when your private code is executed. + +## Solution + +Use the [Command pattern](http://en.wikipedia.org/wiki/Command_pattern) to pass along references to your functions. + +{% highlight coffeescript %} +# Using a private variable to simulate external scripts or modules +incrementers = (() -> + privateVar = 0 + + singleIncrementer = () -> + privateVar += 1 + + doubleIncrementer = () -> + privateVar += 2 + + commands = + single: singleIncrementer + double: doubleIncrementer + value: -> privateVar +)() + +class RunsAll + constructor: (@commands...) -> + run: -> command() for command in @commands + +runner = new RunsAll(incrementers.single, incrementers.double, incrementers.single, incrementers.double) +runner.run() +incrementers.value() # => 6 +{% endhighlight %} + +## Discussion + +With functions as first-class objects and with the function-bound variable scope inherited from Javascript, the CoffeeScript language makes the pattern nearly invisible. In fact, any function passed along as callbacks can act as a *Command*. + +The `jqXHR` object returned by jQuery AJAX methods uses this pattern. + +{% highlight coffeescript %} +jqxhr = $.ajax + url: "/" + +logMessages = "" + +jqxhr.success -> logMessages += "Success!\n" +jqxhr.error -> logMessages += "Error!\n" +jqxhr.complete -> logMessages += "Completed!\n" + +# On a valid AJAX request: +# logMessages == "Success!\nCompleted!\n" +{% endhighlight %} + diff --git a/chapters/design_patterns/decorator.textile b/chapters/design_patterns/decorator.md similarity index 96% rename from chapters/design_patterns/decorator.textile rename to chapters/design_patterns/decorator.md index 89cee57..a40ed5c 100644 --- a/chapters/design_patterns/decorator.textile +++ b/chapters/design_patterns/decorator.md @@ -3,12 +3,11 @@ layout: recipe title: Decorator Pattern chapter: Design Patterns --- - -h2. Problem +## Problem You have a set of data that you need to process in multiple, possibly varying ways. -h2. Solution +## Solution Use the Decorator pattern in order to structure how you apply the changes. @@ -27,7 +26,9 @@ miniMarkdown = (line) -> stripComments = (line) -> line.replace /\s*\/\/.*$/, '' # Removes one-line, double-slash C-style comments -TextProcessor = (@processors) -> +class TextProcessor + constructor: (@processors) -> + reducer: (existing, processor) -> if processor processor(existing or '') @@ -53,7 +54,7 @@ processor.processString exampleText # => "

A level 1 header

\n

A regular line

\n\n

A level 2 header

\n

A line

" {% endhighlight %} -h3. Results +### Results {% highlight html %}

A level 1 header

@@ -63,7 +64,7 @@ h3. Results

A line

{% endhighlight %} -h2. Discussion +## Discussion The TextProcessor serves the role of Decorator by binding the individual, specialized text processors together. This frees up the miniMarkdown and stripComments components to focus on handling nothing but a single line of text. Future developers only have to write functions that return a string and add it to the array of processors. diff --git a/chapters/design_patterns/factory_method.textile b/chapters/design_patterns/factory_method.md similarity index 80% rename from chapters/design_patterns/factory_method.textile rename to chapters/design_patterns/factory_method.md index bb027df..70e2770 100644 --- a/chapters/design_patterns/factory_method.textile +++ b/chapters/design_patterns/factory_method.md @@ -3,16 +3,15 @@ layout: recipe title: Factory Method Pattern chapter: Design Patterns --- - -h2. Problem +## Problem You don't know what kind of object you will need until runtime. -h2. Solution +## Solution -Use the "Factory Method":http://en.wikipedia.org/wiki/Factory_method_pattern pattern and choose the object to be generated dynamically. +Use the [Factory Method](http://en.wikipedia.org/wiki/Factory_method_pattern) pattern and choose the object to be generated dynamically. -Say that you need to load a file into an editor but you don't know its format until the user chooses the file. A class using the "Factory Method":http://en.wikipedia.org/wiki/Factory_method_pattern pattern can serve up different parsers depending on the file's extension. +Say that you need to load a file into an editor but you don't know its format until the user chooses the file. A class using the [Factory Method](http://en.wikipedia.org/wiki/Factory_method_pattern) pattern can serve up different parsers depending on the file's extension. {% highlight coffeescript %} class HTMLParser @@ -45,6 +44,6 @@ factory.makeParser("example.md").type # => "Markdown parser" factory.makeParser("example.json").type # => "JSON parser" {% endhighlight %} -h2. Discussion +## Discussion In the example, you can ignore the specifics of the file's format and focus on the parsed content. A more advanced Factory Method might, for instance, also search for versioning data within the file itself before returning a more precise parser (e.g. an HTML5 parser instead of an HTML v4 parser). diff --git a/chapters/design_patterns/index.textile b/chapters/design_patterns/index.html similarity index 77% rename from chapters/design_patterns/index.textile rename to chapters/design_patterns/index.html index dc44fbd..a548aad 100644 --- a/chapters/design_patterns/index.textile +++ b/chapters/design_patterns/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/design_patterns/interpreter.textile b/chapters/design_patterns/interpreter.md similarity index 92% rename from chapters/design_patterns/interpreter.textile rename to chapters/design_patterns/interpreter.md index 8d4e49e..e3fa133 100644 --- a/chapters/design_patterns/interpreter.textile +++ b/chapters/design_patterns/interpreter.md @@ -3,12 +3,11 @@ layout: recipe title: Interpreter Pattern chapter: Design Patterns --- - -h2. Problem +## Problem Someone else needs to run parts of your code in a controlled fashion. Alternately, your language of choice cannot express the problem domain in a concise fashion. -h2. Solution +## Solution Use the Interpreter pattern to create a domain-specific language that you translate into specific code. @@ -50,7 +49,7 @@ class StackCalculator left / right else throw "Unrecognized operator: #{operator}" - + @stack.push result calc = new StackCalculator @@ -82,14 +81,14 @@ catch error error # => "Unrecognized operator: foo" {% endhighlight %} -h2. Discussion +## Discussion As an alternative to writing our own interpreter, you can co-op the existing CoffeeScript interpreter in a such a way that its normal syntax makes for more natural (and therefore more comprehensible) expressions of your algorithm. {% highlight coffeescript %} class Sandwich constructor: (@customer, @bread='white', @toppings=[], @toasted=false)-> - + white = (sw) -> sw.bread = 'white' sw @@ -141,4 +140,4 @@ send toasted ham turkey sandwich to 'Rachel' # => "Rachel requested a toasted, w send toasted turkey ham swiss sandwich to 'Matt' # => "Matt requested a toasted, white bread sandwich with swiss, ham and turkey" {% endhighlight %} -This example allows for layers of functions by how it returns the modified object so that outer functions can modify it in turn. By borrowing a very and the particle _to_, the example lends natural grammar to the construction and ends up reading like an actual sentence when used correctly. This way, both your CoffeeScript skills and your existing language skills can help catch code problems. +This example allows for layers of functions by how it returns the modified object so that outer functions can modify it in turn. By borrowing a verb and the preposition _to_, the example lends natural grammar to the construction and ends up reading like an actual sentence when used correctly. This way, both your CoffeeScript skills and your existing language skills can help catch code problems. diff --git a/chapters/design_patterns/memento.textile b/chapters/design_patterns/memento.md similarity index 76% rename from chapters/design_patterns/memento.textile rename to chapters/design_patterns/memento.md index 50d9aa2..5c897dc 100644 --- a/chapters/design_patterns/memento.textile +++ b/chapters/design_patterns/memento.md @@ -3,16 +3,15 @@ layout: recipe title: Memento Pattern chapter: Design Patterns --- - -h2. Problem +## Problem You want to anticipate the reversion of changes to an object. -h2. Solution +## Solution -Use the "Memento pattern":http://en.wikipedia.org/wiki/Memento_pattern to track changes to an object. The class using the pattern will export a _memento_ object stored elsewhere. +Use the [Memento pattern](http://en.wikipedia.org/wiki/Memento_pattern) to track changes to an object. The class using the pattern will export a `memento` object stored elsewhere. -If you have application where the user can edit a text file, for example, they may want to undo their last action. You can save the current state of the file before the user changes it and then roll back to that at a later point. +If you have application where the user can edit a text file, for example, they may want to undo their last action. You can save the current state of the file before the user changes it and then roll back to that at a later point. {% highlight coffeescript %} class PreserveableText @@ -40,6 +39,6 @@ pt.restore memento pt.text # => "The original string" {% endhighlight %} -h2. Discussion +## Discussion -The Memento object returned by _PreserveableText#save_ stores the important state information separately for safe-keeping. You could even serialize this Memento in order to maintain an "undo" buffer on the hard disk or remotely for such data-intensive objects as edited images. +The Memento object returned by `PreserveableText#save` stores the important state information separately for safe-keeping. You could even serialize this Memento in order to maintain an "undo" buffer on the hard disk or remotely for such data-intensive objects as edited images. diff --git a/chapters/design_patterns/observer.md b/chapters/design_patterns/observer.md new file mode 100644 index 0000000..48047bb --- /dev/null +++ b/chapters/design_patterns/observer.md @@ -0,0 +1,47 @@ +--- +layout: recipe +title: Observer Pattern +chapter: Design patterns +--- +## Problem + +You have to notify some objects about an event happen + +## Solution + +Use an [Observer Pattern](http://en.wikipedia.org/wiki/Observer_pattern) + +{% highlight coffeescript %} + +class PostOffice + constructor: () -> + @subscribers = [] + notifyNewItemReleased: (item) -> + subscriber.callback(item) for subscriber in @subscribers when subscriber.item is item + subscribe: (to, onNewItemReleased) -> + @subscribers.push {'item':to, 'callback':onNewItemReleased} + +class MagazineSubscriber + onNewMagazine: (item) -> + alert "I've got new "+item + +class NewspaperSubscriber + onNewNewspaper: (item) -> + alert "I've got new "+item + +postOffice = new PostOffice() +sub1 = new MagazineSubscriber() +sub2 = new NewspaperSubscriber() +postOffice.subscribe "Mens Health", sub1.onNewMagazine +postOffice.subscribe "Times", sub2.onNewNewspaper +postOffice.notifyNewItemReleased "Times" +postOffice.notifyNewItemReleased "Mens Health" + +{% endhighlight %} + +## Discussion + +Here you have an observer object (PostOffice) and observable objects (MagazineSubscriber, NewspaperSubscriber). +To be notified about an event of publishing new periodical observable object should make subscription on PostOffice. +Every of subscribed objects is stored internally in the PostOffice array of subscriptions. +Every subscriber is notified on new concrete periodical is published. diff --git a/chapters/design_patterns/singleton.md b/chapters/design_patterns/singleton.md new file mode 100644 index 0000000..aef94dc --- /dev/null +++ b/chapters/design_patterns/singleton.md @@ -0,0 +1,81 @@ +--- +layout: recipe +title: Singleton Pattern +chapter: Design Patterns +--- +## Problem + +Many times you only want one, and only one, instance of a class. For example, you may only need one class that creates server resources and you want to ensure that the one object can control those resources. Beware, however, because the singleton pattern can be easily abused to mimic unwanted global variables. + + +## Solution + +The publicly available class only contains the method to get the one true instance. The instance is kept within the closure of that public object and is always returned. + +This works because CoffeeScript allows you to define executable statements inside a class definition. However, because most CoffeeScript compiles into a [IIFE][] wrapper you do not have to place the private class inside the class definition if this style suits you. The later might be useful when developing modular code such as found in [CommonJS][] (Node.js) or [Require.js][] (See the discussion for an example). + +[IIFE]: http://benalman.com/news/2010/11/immediately-invoked-function-expression/ +[CommonJS]: http://www.commonjs.org/ +[Require.js]: http://requirejs.org/ + +{% highlight coffeescript %} +class Singleton + # You can add statements inside the class definition + # which helps establish private scope (due to closures) + # instance is defined as null to force correct scope + instance = null + # Create a private class that we can initialize however + # defined inside this scope to force the use of the + # singleton class. + class PrivateClass + constructor: (@message) -> + echo: -> @message + # This is a static method used to either retrieve the + # instance or create a new one. + @get: (message) -> + instance ?= new PrivateClass(message) + +a = Singleton.get "Hello A" +a.echo() # => "Hello A" + +b = Singleton.get "Hello B" +b.echo() # => "Hello A" + +Singleton.instance # => undefined +a.instance # => undefined +Singleton.PrivateClass # => undefined +{% endhighlight %} + + +## Discussion + +See in the above example how all instances are outputting from the same instance of the Singleton class. You can also see that the PrivateClass and instance variable are not accessible outside the Singleton class. In essence the Singleton class provides a static method get which returns only one instance of PrivateClass and only one. It also hides the PrivateClass from the world so that you can not create your own. + +The idea of hiding or making private the inner workings is preference. Especially since by default CoffeeScript wraps the compiled code inside it's own IIFE (closure) allowing you to define classes without worry that it might be accessible from outside the file. In this example, note that I am using the idiomatic module export feature to emphasize the publicly accessible portion of the module. (See this discussion for further explanation on [exporting to the global namespace][1]). + +[1]: http://stackoverflow.com/questions/4214731/coffeescript-global-variables + +{% highlight coffeescript %} +root = exports ? this + +# Create a private class that we can initialize however +# defined inside the wrapper scope. +class ProtectedClass + constructor: (@message) -> + echo: -> @message + +class Singleton + # You can add statements inside the class definition + # which helps establish private scope (due to closures) + # instance is defined as null to force correct scope + instance = null + # This is a static method used to either retrieve the + # instance or create a new one. + @get: (message) -> + instance ?= new ProtectedClass(message) + +# Export Singleton as a module +root.Singleton = Singleton +{% endhighlight %} + +Note how incredibly simple coffeescript makes this design pattern. For reference and discussion on nice javascript implementations, check out [Essential JavaScript Design Patterns For Beginners](http://addyosmani.com/resources/essentialjsdesignpatterns/book/). diff --git a/chapters/design_patterns/strategy.textile b/chapters/design_patterns/strategy.md similarity index 76% rename from chapters/design_patterns/strategy.textile rename to chapters/design_patterns/strategy.md index 0ca8402..415f72d 100644 --- a/chapters/design_patterns/strategy.textile +++ b/chapters/design_patterns/strategy.md @@ -3,30 +3,34 @@ layout: recipe title: Strategy Pattern chapter: Design Patterns --- - -h2. Problem +## Problem You have more than one way to solve a problem but you need to choose (or even switch) between these methods at run time. -h2. Solution +## Solution Encapsulate your algorithms inside of Strategy objects. Given an unsorted list, for example, we can change the sorting algorithm under different circumstances. +###The base class: + {% highlight coffeescript %} -StringSorter = (@algorithm) -> - sort: (list) -> - @algorithm list +StringSorter = (algorithm) -> + sort: (list) -> algorithm list +{% endhighlight %} +###The strategies: + +{% highlight coffeescript %} bubbleSort = (list) -> anySwaps = false swapPass = -> - for r in [0..list.length-1] + for r in [0..list.length-2] if list[r] > list[r+1] anySwaps = true [list[r], list[r+1]] = [list[r+1], list[r]] - + swapPass() while anySwaps anySwaps = false @@ -40,13 +44,17 @@ reverseBubbleSort = (list) -> if list[r] < list[r-1] anySwaps = true [list[r], list[r-1]] = [list[r-1], list[r]] - + swapPass() while anySwaps anySwaps = false swapPass() list +{% endhighlight %} +###Using the strategies: + +{% highlight coffeescript %} sorter = new StringSorter bubbleSort unsortedList = ['e', 'b', 'd', 'c', 'x', 'a'] @@ -66,10 +74,10 @@ sorter.sort unsortedList # => ['a', 'b', 'c', 'd', 'e', 'w', 'x'] {% endhighlight %} -h2. Discussion +## Discussion "No plan survives first contact with the enemy", nor users, but we can use the knowledge gained from changing circumstances to adapt. Near the end of the example, for instance, the newest item in the array now lies out of order. Knowing that detail, we can then speed the sort up by switching to an algorithm optimized for that exact scenario with nothing but a simple reassignment. -h3. Exercises +### Exercises -* Expand StringSorter into an AlwaysSortedArray class that implements all of the functionality of a regular array but which automatically sorts new items based on the method of insertion (e.g. push vs. shift). +* Expand `StringSorter` into an `AlwaysSortedArray` class that implements all of the functionality of a regular array but which automatically sorts new items based on the method of insertion (e.g. `push` vs. `shift`). diff --git a/chapters/design_patterns/template_method.md b/chapters/design_patterns/template_method.md new file mode 100644 index 0000000..31fe475 --- /dev/null +++ b/chapters/design_patterns/template_method.md @@ -0,0 +1,44 @@ +--- +layout: recipe +title: Template Method Pattern +chapter: Design Patterns +--- +## Problem + +Define the structure of an algorithm as a series of high-level steps, making it possible to specify the behaviour of each step, giving rise to a family of algorithms that have the same structure but different behaviours. + +## Solution + +Use the Template Method to describe the algorithm structure in a superclass, delegating the implementation of some steps to one or more concrete subclasses. + +For example, imagine you wish to model the production of various types of document and each one may contain a header and a body. + +{% highlight coffeescript %} +class Document + produceDocument: -> + @produceHeader() + @produceBody() + + produceHeader: -> + produceBody: -> + +class DocWithHeader extends Document + produceHeader: -> + console.log "Producing header for DocWithHeader" + + produceBody: -> + console.log "Producing body for DocWithHeader" + +class DocWithoutHeader extends Document + produceBody: -> + console.log "Producing body for DocWithoutHeader" + +docs = [new DocWithHeader, new DocWithoutHeader] +doc.produceDocument() for doc in docs +{% endhighlight %} + +## Discussion + +In this example, the algorithm consists of two steps describing document production: one for producing a document header and the second for producing the document body. An empty method implementation for each step is present in the superclass and polymorphism is exploited such that each concrete subclass can provide a different implementation for a step by overriding a step method. In this example,the DocWithHeader implements both the body and header steps, whereas the DocWithoutHeader only implements the body step. + +The production of different types of document is then straightforward when document objects are stored in an array, and it is then a simple of matter of iterating over each document object and calling its produceDocument method. \ No newline at end of file diff --git a/chapters/functions/debounce.md b/chapters/functions/debounce.md new file mode 100644 index 0000000..19d99a6 --- /dev/null +++ b/chapters/functions/debounce.md @@ -0,0 +1,44 @@ +--- +layout: recipe +title: Debounce Functions +chapter: Functions +--- +## Problem + +You want to execute a function only once, coalescing multiple sequential calls into a single execution at the beginning or end. + +## Solution + +With a named function: + +{% highlight coffeescript %} +debounce: (func, threshold, execAsap) -> + timeout = null + (args...) -> + obj = this + delayed = -> + func.apply(obj, args) unless execAsap + timeout = null + if timeout + clearTimeout(timeout) + else if (execAsap) + func.apply(obj, args) + timeout = setTimeout delayed, threshold || 100 +{% endhighlight %} + +{% highlight coffeescript %} +mouseMoveHandler: (e) -> + @debounce((e) -> + # Do something here, but only once 300 milliseconds after the mouse cursor stops. + 300) + +someOtherHandler: (e) -> + @debounce((e) -> + # Do something here, but only once 250 milliseconds after initial execution. + 250, true) +{% endhighlight %} + +## Discussion + +Learn about [debouncing JavaScript methods](http://unscriptable.com/2009/03/20/debouncing-javascript-methods/) at John Hann's excellent blog article. + diff --git a/chapters/functions/index.textile b/chapters/functions/index.html similarity index 76% rename from chapters/functions/index.textile rename to chapters/functions/index.html index dbeb3c9..eb3b4e9 100644 --- a/chapters/functions/index.textile +++ b/chapters/functions/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/functions/parentheses.textile b/chapters/functions/parentheses.md similarity index 74% rename from chapters/functions/parentheses.textile rename to chapters/functions/parentheses.md index 690ec92..457cd04 100644 --- a/chapters/functions/parentheses.textile +++ b/chapters/functions/parentheses.md @@ -1,18 +1,17 @@ --- layout: recipe -title: When Function Parentheses are not Optional +title: When Function Parentheses Are Not Optional chapter: Functions --- - -h2. Problem +## Problem You want to call a function that takes no arguments, but don't want to use parentheses. -h2. Solution +## Solution Use parentheses anyway. -Another alternative is to utilise the do-notation like so: +Another alternative is to utilize the do-notation like so: {% highlight coffeescript %} notify = -> alert "Hello, user!" @@ -31,11 +30,11 @@ if (condition) { } {% endhighlight %} -h2. Discussion +## Discussion Like Ruby, CoffeeScript allows you to drop parentheses to method calls. Unlike Ruby, however, CoffeeScript treats a bare function name as the pointer to the function. The practical upshot of this is that if you give no arguments to a method, CoffeeScript cannot tell if you want to call the function or use it as a reference. -Is this good or bad? It's just different. It creates an unexpected syntax case—parentheses aren't _always_ optional—but in exchange it gives you the ability to pass and receive functions fluently by name, something that's a bit klunky in Ruby. +Is this good or bad? It's just different. It creates an unexpected syntax case -- parentheses aren't _always_ optional -- but in exchange it gives you the ability to pass and receive functions fluently by name, something that's a bit clunky in Ruby. This usage of the do-notation is a neat approach for CoffeeScript with parenphobia. Some people simply prefer to write out the parentheses in the function call, though. diff --git a/chapters/functions/recursion.md b/chapters/functions/recursion.md new file mode 100644 index 0000000..3bddb59 --- /dev/null +++ b/chapters/functions/recursion.md @@ -0,0 +1,33 @@ +--- +layout: recipe +title: Recursive Functions +chapter: Functions +--- +## Problem + +You want to call a function from within that same function. + +## Solution + +With a named function: + +{% highlight coffeescript %} +ping = -> + console.log "Pinged" + setTimeout ping, 1000 +{% endhighlight %} + +With an unnamed function, using @arguments.callee@: + +{% highlight coffeescript %} +delay = 1000 + +setTimeout((-> + console.log "Pinged" + setTimeout arguments.callee, delay + ), delay) +{% endhighlight %} + +## Discussion + +While `arguments.callee` allows for the recursion of anonymous functions and might have the advantage in a very memory-intensive application, named functions keep their purpose more explicit and make for more maintainable code. diff --git a/chapters/functions/splat_arguments.md b/chapters/functions/splat_arguments.md new file mode 100644 index 0000000..70bd1b6 --- /dev/null +++ b/chapters/functions/splat_arguments.md @@ -0,0 +1,48 @@ +--- +layout: recipe +title: Splat Arguments +chapter: Functions +--- +## Problem + +Your function will be called with a varying number of arguments. + +## Solution + +Use _splats_. + +{% highlight coffeescript %} +loadTruck = (firstDibs, secondDibs, tooSlow...) -> + truck: + driversSeat: firstDibs + passengerSeat: secondDibs + trunkBed: tooSlow + +loadTruck("Amanda", "Joel") +# => { truck: { driversSeat: "Amanda", passengerSeat: "Joel", trunkBed: [] } } + +loadTruck("Amanda", "Joel", "Bob", "Mary", "Phillip") +# => { truck: { driversSeat: "Amanda", passengerSeat: "Joel", trunkBed: ["Bob", "Mary", "Phillip"] } } +{% endhighlight %} + +With a trailing argument: + +{% highlight coffeescript %} +loadTruck = (firstDibs, secondDibs, tooSlow..., leftAtHome) -> + truck: + driversSeat: firstDibs + passengerSeat: secondDibs + trunkBed: tooSlow + taxi: + passengerSeat: leftAtHome + +loadTruck("Amanda", "Joel", "Bob", "Mary", "Phillip", "Austin") +# => { truck: { driversSeat: 'Amanda', passengerSeat: 'Joel', trunkBed: [ 'Bob', 'Mary', 'Phillip' ] }, taxi: { passengerSeat: 'Austin' } } + +loadTruck("Amanda") +# => { truck: { driversSeat: "Amanda", passengerSeat: undefined, trunkBed: [] }, taxi: undefined } +{% endhighlight %} + +## Discussion + +By adding an ellipsis (`...`) next to no more than one of a function's arguments, CoffeeScript will combine all of the argument values not captured by other named arguments into a list. It will serve up an empty list even if some of the named arguments were not supplied. diff --git a/chapters/index.html b/chapters/index.html new file mode 100644 index 0000000..51b99cf --- /dev/null +++ b/chapters/index.html @@ -0,0 +1,29 @@ +--- +layout: default +title: Cookbook +--- + +
+ + + + + +
diff --git a/chapters/index.textile b/chapters/index.textile deleted file mode 100644 index 8e93be3..0000000 --- a/chapters/index.textile +++ /dev/null @@ -1,36 +0,0 @@ ---- -layout: default -title: Cookbook -chapters: -- Syntax -- Classes and Objects -- Strings -- Arrays -- Dates and Times -- Math -- Functions -- Metaprogramming -- jQuery -- Regular Expressions -- AJAX -- Networking -- Design Patterns ---- - - -{% for chapter in page.chapters %} - {% capture url %}/chapters/{{ chapter | replace: ' ', '_' | downcase }}{% endcapture %} - {% capture indexurl %}{{ url }}/index.html{% endcapture %} - -h2. {{ chapter }} - - {% for page in site.pages %} - {% if page.url contains url %} - {% unless page.url == indexurl %} - * {{ page.title }} - {% endunless %} - {% endif %} - {% endfor %} -{% endfor %} - - diff --git a/chapters/jquery/ajax.textile b/chapters/jquery/ajax.md similarity index 79% rename from chapters/jquery/ajax.textile rename to chapters/jquery/ajax.md index 2f9f561..54d89a5 100644 --- a/chapters/jquery/ajax.textile +++ b/chapters/jquery/ajax.md @@ -3,12 +3,11 @@ layout: recipe title: AJAX chapter: jQuery --- - -h2. Problem +## Problem You want to make AJAX calls using jQuery. -h2. Solution +## Solution {% highlight coffeescript %} $ ?= require 'jquery' # For Node.js compatibility @@ -26,21 +25,22 @@ $(document).ready -> # Advanced Settings $.ajax '/', type: 'GET' - dataType: 'html' error: (jqXHR, textStatus, errorThrown) -> + dataType: 'html' + error: (jqXHR, textStatus, errorThrown) -> $('body').append "AJAX Error: #{textStatus}" success: (data, textStatus, jqXHR) -> $('body').append "Successful AJAX call: #{data}" - + {% endhighlight %} -jQuery 1.5 and later have added a new, supplimental API for handling different callbacks. - +jQuery 1.5 and later have added a new, supplemental API for handling different callbacks. + {% highlight coffeescript %} request = $.get '/' request.success (data) -> $('body').append "Successfully got the page again." request.error (jqXHR, textStatus, errorThrown) -> $('body').append "AJAX Error: ${textStatus}." {% endhighlight %} -h2. Discussion +## Discussion -The jQuery and $ variables can be used interchangeably. See also "Callback bindings":../jquery/callback-bindings-jquery. +The jQuery and $ variables can be used interchangeably. See also [Callback bindings]({{ site.baseurl }}/chapters/jquery/callback-bindings-jquery). diff --git a/chapters/jquery/callback-bindings-jquery.textile b/chapters/jquery/callback-bindings-jquery.md similarity index 54% rename from chapters/jquery/callback-bindings-jquery.textile rename to chapters/jquery/callback-bindings-jquery.md index c656dc3..242927d 100644 --- a/chapters/jquery/callback-bindings-jquery.textile +++ b/chapters/jquery/callback-bindings-jquery.md @@ -1,14 +1,13 @@ --- layout: recipe -title: Callback bindings # using => instead of -> +title: Callback Bindings # using => instead of -> chapter: jQuery --- - -h2. Problem +## Problem You want to bind a callback function to an object. -h2. Solution +## Solution {% highlight coffeescript %} $ -> @@ -17,7 +16,7 @@ $ -> @products = [] $('.product').click (event) => - @add $(event.target).attr 'id' + @add $(event.currentTarget).attr 'id' add: (product) -> @products.push product @@ -26,7 +25,7 @@ $ -> new Basket() {% endhighlight %} -h2. Discussion +## Discussion -By using the fat arrow => instead of the normal arrow -> the function gets -automatically bound to the object and can access the @-variable +By using the fat arrow (`=>`) instead of the normal arrow (`->`) the function gets +automatically bound to the object and can access the `@-variable`. diff --git a/chapters/jquery/index.textile b/chapters/jquery/index.html similarity index 76% rename from chapters/jquery/index.textile rename to chapters/jquery/index.html index c046341..770dd77 100644 --- a/chapters/jquery/index.textile +++ b/chapters/jquery/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/jquery/plugin.md b/chapters/jquery/plugin.md new file mode 100644 index 0000000..df95cf1 --- /dev/null +++ b/chapters/jquery/plugin.md @@ -0,0 +1,59 @@ +--- +layout: recipe +title: Create a jQuery Plugin +chapter: jQuery +--- +## Problem + +You'd like to create jQuery plugin using CoffeeScript + +## Solution + +{% highlight coffeescript %} +# Reference jQuery +$ = jQuery + +# Adds plugin object to jQuery +$.fn.extend + # Change pluginName to your plugin's name. + pluginName: (options) -> + # Default settings + settings = + option1: true + option2: false + debug: false + + # Merge default settings with options. + settings = $.extend settings, options + + # Simple logger. + log = (msg) -> + console?.log msg if settings.debug + + # _Insert magic here._ + return @each ()-> + log "Preparing magic show." + # You can use your settings in here now. + log "Option 1 value: #{settings.option1}" +{% endhighlight %} + +## Discussion + +Here are a couple of examples of how to use your new plugin. + +### JavaScript + +{% highlight javascript %} +$("body").pluginName({ + debug: true +}); + +{% endhighlight %} + +### CoffeeScript: + +{% highlight coffeescript %} +$("body").pluginName + debug: true + +{% endhighlight %} diff --git a/chapters/math/constants.textile b/chapters/math/constants.md similarity index 76% rename from chapters/math/constants.textile rename to chapters/math/constants.md index 18462ba..d3d25c7 100644 --- a/chapters/math/constants.textile +++ b/chapters/math/constants.md @@ -3,12 +3,11 @@ layout: recipe title: Math Constants chapter: Math --- - -h2. Problem +## Problem You need to use common mathematical constants like pi or e. -h2. Solution +## Solution Use Javascript's Math object to provide commonly needed mathematical constants. @@ -18,7 +17,7 @@ Math.PI # Note: Capitalization matters! This produces no output, it's undefined. Math.Pi -# => +# => Math.E # => 2.718281828459045 @@ -44,6 +43,6 @@ Math.LOG10E {% endhighlight %} -h2. Discussion +## Discussion -For another example of how a math constant is used in a real world problem, refer to the 'Converting Radians and Degrees' section of this Math chapter. \ No newline at end of file +For another example of how a math constant is used in a real world problem, refer to the [Converting Radians and Degrees]({{ site.baseurl }}/chapters/math/radians-degrees) section of this Math chapter. diff --git a/chapters/math/fast-fibonacci.md b/chapters/math/fast-fibonacci.md new file mode 100644 index 0000000..a6a54c4 --- /dev/null +++ b/chapters/math/fast-fibonacci.md @@ -0,0 +1,101 @@ +--- +layout: recipe +title: Faster Fibonacci Algorithm +chapter: Math +--- +## Problem + +You would like to calculate a number N in the Fibonacci sequence but want +to do it quickly. + +## Solution + +The following solution (which can still be improved on) was originally +talked about on Robin Houston's blog. + +Here are a few links talking about the algorithm and ways to improve it: +* [http://bosker.wordpress.com/2011/04/29/the-worst-algorithm-in-the-world/](http://bosker.wordpress.com/2011/04/29/the-worst-algorithm-in-the-world/) +* [http://www.math.rutgers.edu/~erowland/fibonacci](http://www.math.rutgers.edu/~erowland/fibonacci.html) +* [http://jsfromhell.com/classes/bignumber](http://jsfromhell.com/classes/bignumber) +* [http://www.math.rutgers.edu/~erowland/fibonacci](http://www.math.rutgers.edu/~erowland/fibonacci.html) +* [http://bigintegers.blogspot.com/2010/11/square-division-power-square-root](http://bigintegers.blogspot.com/2010/11/square-division-power-square-root.html) +* [http://bugs.python.org/issue3451](http://bugs.python.org/issue3451) + +This code is in gist form here: +[https://gist.github.com/1032685](https://gist.github.com/1032685) + +{% highlight coffeescript %} +### +Author: Jason Giedymin + http://www.jasongiedymin.com + https://github.com/JasonGiedymin + +This CoffeeScript Javascript Fast Fibonacci code is +based on the python code from Robin Houston's blog. +See below links. + +A few things I want to introduce in time are implementations of +Newtonian, Burnikel / Ziegler, and Binet's algorithms on top +of a Big Number framework. + +Todo: +- https://github.com/substack/node-bigint +- BZ and Newton mods. +- Timing + +### + +MAXIMUM_JS_FIB_N = 1476 + +fib_bits = (n) -> + #Represent an integer as an array of binary digits. + + bits = [] + while n > 0 + [n, bit] = divmodBasic n, 2 + bits.push bit + + bits.reverse() + return bits + +fibFast = (n) -> + #Fast Fibonacci + + if n < 0 + console.log "Choose an number >= 0" + return + + [a, b, c] = [1, 0, 1] + + for bit in fib_bits n + if bit + [a, b] = [(a+c)*b, b*b + c*c] + else + [a, b] = [a*a + b*b, (a+c)*b] + + c = a + b + return b + +divmodNewton = (x, y) -> + throw new Error "Method not yet implemented yet." + +divmodBZ = () -> + throw new Error "Method not yet implemented yet." + +divmodBasic = (x, y) -> + ### + Absolutely nothing special here. Maybe later versions will be Newtonian or + Burnikel / Ziegler _if_ possible... + ### + + return [(q = Math.floor x/y), (r = if x < y then x else x % y)] + +start = (new Date).getTime(); +calc_value = fibFast(MAXIMUM_JS_FIB_N) +diff = (new Date).getTime() - start; +console.log "[#{calc_value}] took #{diff} ms." +{% endhighlight %} + +## Discussion + +Questions? diff --git a/chapters/math/fast-inv-square.md b/chapters/math/fast-inv-square.md new file mode 100644 index 0000000..328df01 --- /dev/null +++ b/chapters/math/fast-inv-square.md @@ -0,0 +1,124 @@ +--- +layout: recipe +title: Fast Inverse Square Root +chapter: Math +--- +## Problem + +You would like to calculate a the inverse square root of a number [quickly][5]. + +## Solution +Appearing in the Quake III Arena [source code][1], this strange algorithm uses +integer operations along with a 'magic number' to calculate floating point +approximation values of inverse square roots. + +In this CoffeeScript variant I supply the original classic, and newer optimal +32 bit magic numbers found by [Chris Lomont][2]. Also supplied is the 64-bit +sized magic number. + +Another feature included is the ability to alter the level of precision. +This is done by controlling the number of iterations for performing [Newton's +method][3]. + +Depending on the machine and level of precision this algorithm may still +provide performance increases over the classic. + +To run this, compile the script with coffee: + coffee -c script.coffee + +Then copy & paste the compiled js code in to the JavaScript console of your +browser. + +Note: You will need a browser which supports [typed-arrays][4]. + +References: +1. [ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip](ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip) +2. [http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf](http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf) +3. [http://en.wikipedia.org/wiki/Newton%27s_method](http://en.wikipedia.org/wiki/Newton%27s_method) +4. [https://developer.mozilla.org/en/JavaScript_typed_arrays](https://developer.mozilla.org/en/JavaScript_typed_arrays) +5. [http://en.wikipedia.org/wiki/Fast_inverse_square_root](http://en.wikipedia.org/wiki/Fast_inverse_square_root) + +[1]: ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip "ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip" +[2]: http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf "http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf" +[3]: http://en.wikipedia.org/wiki/Newton%27s_method "http://en.wikipedia.org/wiki/Newton%27s_method" +[4]: https://developer.mozilla.org/en/JavaScript_typed_arrays "https://developer.mozilla.org/en/JavaScript_typed_arrays" +[5]: http://en.wikipedia.org/wiki/Fast_inverse_square_root "http://en.wikipedia.org/wiki/Fast_inverse_square_root" + +This code is in gist form here: +[https://gist.github.com/1036533](https://gist.github.com/1036533) + +{% highlight coffeescript %} +### + +Author: Jason Giedymin + http://www.jasongiedymin.com + https://github.com/JasonGiedymin + +Appearing in the Quake III Arena source code[1], this strange algorithm uses +integer operations along with a 'magic number' to calculate floating point +approximation values of inverse square roots[5]. + +In this CoffeeScript variant I supply the original classic, and newer optimal +32 bit magic numbers found by Chris Lomont[2]. Also supplied is the 64-bit +sized magic number. + +Another feature included is the ability to alter the level of precision. +This is done by controlling the number of iterations for performing Newton's +method[3]. + +Depending on the machine and level of precision this algorithm may still +provide performance increases over the classic. + +To run this, compile the script with coffee: + coffee -c .coffee + +Then copy & paste the compiled js code in to the JavaScript console of your +browser. + +Note: You will need a browser which supports typed-arrays[4]. + +References: +[1] ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip +[2] http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf +[3] http://en.wikipedia.org/wiki/Newton%27s_method +[4] https://developer.mozilla.org/en/JavaScript_typed_arrays +[5] http://en.wikipedia.org/wiki/Fast_inverse_square_root + +### + +approx_const_quake_32 = 0x5f3759df # See [1] +approx_const_32 = 0x5f375a86 # See [2] +approx_const_64 = 0x5fe6eb50c7aa19f9 # See [2] + +fastInvSqrt_typed = (n, precision=1) -> + # Using typed arrays. Right now only works in browsers. + # Node.JS version coming soon. + + y = new Float32Array(1) + i = new Int32Array(y.buffer) + + y[0] = n + i[0] = 0x5f375a86 - (i[0] >> 1) + + for iter in [1...precision] + y[0] = y[0] * (1.5 - ((n * 0.5) * y[0] * y[0])) + + return y[0] + +### Sample single runs ### +testSingle = () -> + example_n = 10 + + console.log("Fast InvSqrt of 10, precision 1: #{fastInvSqrt_typed(example_n)}") + console.log("Fast InvSqrt of 10, precision 5: #{fastInvSqrt_typed(example_n, 5)}") + console.log("Fast InvSqrt of 10, precision 10: #{fastInvSqrt_typed(example_n, 10)}") + console.log("Fast InvSqrt of 10, precision 20: #{fastInvSqrt_typed(example_n, 20)}") + console.log("Classic of 10: #{1.0 / Math.sqrt(example_n)}") + +testSingle() + +{% endhighlight %} + +## Discussion + +Questions? diff --git a/chapters/math/generating-predictable-random-numbers.textile b/chapters/math/generating-predictable-random-numbers.md similarity index 81% rename from chapters/math/generating-predictable-random-numbers.textile rename to chapters/math/generating-predictable-random-numbers.md index 5cf9da9..128bc61 100644 --- a/chapters/math/generating-predictable-random-numbers.textile +++ b/chapters/math/generating-predictable-random-numbers.md @@ -3,12 +3,11 @@ layout: recipe title: Generating Predictable Random Numbers chapter: Math --- - -h2. Problem +## Problem You need to generate a random number in a certain range, but you also need to be able to "seed" the generator to deliver predictable values. -h2. Solution +## Solution Write your own random number generator. There are a LOT of ways to do this. Here's a simple one. _This generator is +ABSOLUTELY NOT+ acceptable for cryptographic purposes!_ @@ -45,14 +44,14 @@ class Rand min + this.rand(max-min) {% endhighlight %} -h2. Discussion +## Discussion JavaScript and CoffeeScript do not provide a seedable random number generator. Writing your own will be an exercise in trading off the amount of randomness with the simplicity of the generator. A full discussion of randomness is beyond the scope of this cookbook; for further reading consult Donald Knuth's _The Art of Computer Programming_, Volume II, Chapter 3, "Random Numbers", and _Numerical Recipes in C_, 2nd Edition, Chapter 7, "Random Numbers". -A brief explanation of this random number generator is in order, however. It is a Linear Congruential Pseudorandom Number Generator. LCPRNG's operate on the mathematical formula Ij+1 = (aIj+c) % m, where a is the multiplier, c is the addition offset, and m is the modulus. - Each time a random number is requested, a very large multiplication and addition are performed—"very large" relative to the key space—and the resulting number is modulused back down into the keyspace. +A brief explanation of this random number generator is in order, however. It is a Linear Congruential Pseudorandom Number Generator. LCPRNG's operate on the mathematical formula `Ij+1 = (aIj+c) % m`, where a is the multiplier, c is the addition offset, and m is the modulus. + Each time a random number is requested, a very large multiplication and addition are performed -- "very large" relative to the key space -- and the resulting number is modulused back down into the keyspace. -This generator has a period of 232. It is absolutely unacceptable for cryptographic purposes, but for most simple randomness requirements it is quite adequate. randn() will traverse the entire keyspace before repeating itself, and the next number is determined by the previous one. +This generator has a period of 232. It is absolutely unacceptable for cryptographic purposes, but for most simple randomness requirements it is quite adequate. `randn()` will traverse the entire keyspace before repeating itself, and the next number is determined by the previous one. If you want to tinker with this generator, you are _strongly_ encouraged to read Chapter 3 of Knuth's _The Art of Computer Programming_. Random number generation is VERY easy to screw up, and Knuth explains how to tell a good RNG from a bad one. @@ -63,6 +62,4 @@ Avoid the temptation to modulus the output of this generator. If you need an int r.randn() % 2 {% endhighlight %} -because you will most definitely not get random digits. Use r.randi(2) instead. - - +because you will most definitely not get random digits. Use `r.rand(2)` instead. diff --git a/chapters/math/generating-random-numbers.textile b/chapters/math/generating-random-numbers.md similarity index 69% rename from chapters/math/generating-random-numbers.textile rename to chapters/math/generating-random-numbers.md index 3359205..e1b3a44 100644 --- a/chapters/math/generating-random-numbers.textile +++ b/chapters/math/generating-random-numbers.md @@ -3,12 +3,11 @@ layout: recipe title: Generating Random Numbers chapter: Math --- - -h2. Problem +## Problem You need to generate a random number in a certain range. -h2. Solution +## Solution Use JavaScript's Math.random() to get floating-point numbers from 0 <= x < 1.0. Use multiplication and Math.floor to get a number in a certain range. @@ -25,20 +24,18 @@ percentile = Math.floor(Math.random() * 100) dice = Math.floor(Math.random() * 6) + 1 1 <= dice <= 6 # => true + +max = 42 +min = -13 +range = Math.random() * (max - min) + min +-13 <= range < 42 +# => true {% endhighlight %} -h2. Discussion +## Discussion This is a straight lift from JavaScript. -Note that JavaScripts's Math.random() does not allow you to seed the random number generator to force certain values. See Generating Predictable Random Numbers for that. +Note that JavaScript's Math.random() does not allow you to seed the random number generator to force certain values. See [Generating Predictable Random Numbers]({{ site.baseurl }}/chapters/math/generating-predictable-random-numbers) for that. To generate a number from 0 up to (but not including) n, multiply by n. To generate a number from 1 to n (inclusive), multiply by n and add 1. - - - - - - - - diff --git a/chapters/math/index.textile b/chapters/math/index.html similarity index 76% rename from chapters/math/index.textile rename to chapters/math/index.html index 9fb796d..32eafac 100644 --- a/chapters/math/index.textile +++ b/chapters/math/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/math/radians-degrees.textile b/chapters/math/radians-degrees.md similarity index 91% rename from chapters/math/radians-degrees.textile rename to chapters/math/radians-degrees.md index c186ddb..8be5400 100644 --- a/chapters/math/radians-degrees.textile +++ b/chapters/math/radians-degrees.md @@ -3,12 +3,11 @@ layout: recipe title: Converting Radians and Degrees chapter: Math --- - -h2. Problem +## Problem You need to convert between radians and degrees. -h2. Solution +## Solution Use Javascript's Math.PI and a simple formula to convert between the two. @@ -28,6 +27,6 @@ degreesToRadians(1) # => 0.017453292519943295 {% endhighlight %} -h2. Discussion +## Discussion -Questions? \ No newline at end of file +Questions? diff --git a/chapters/math/random-integer.md b/chapters/math/random-integer.md new file mode 100644 index 0000000..052c8e6 --- /dev/null +++ b/chapters/math/random-integer.md @@ -0,0 +1,29 @@ +--- +layout: recipe +title: A Random Integer Function +chapter: Math +--- +## Problem + +You'd like to get a random integer between two integers, inclusive. + +## Solution + +Use the following function. + +{% highlight coffeescript %} +randomInt = (lower, upper) -> + [lower, upper] = [0, lower] unless upper? # Called with one argument + [lower, upper] = [upper, lower] if lower > upper # Lower must be less then upper + Math.floor(Math.random() * (upper - lower + 1) + lower) # Last statement is a return value + +(randomInt(1) for i in [0...10]) +# => [0,1,1,0,0,0,1,1,1,0] + +(randomInt(1, 10) for i in [0...10]) +# => [7,3,9,1,8,5,4,10,10,8] +{% endhighlight %} + +## Discussion + +Questions? diff --git a/chapters/math/working-with-exponents-and-logarithms.md b/chapters/math/working-with-exponents-and-logarithms.md new file mode 100644 index 0000000..13aedaf --- /dev/null +++ b/chapters/math/working-with-exponents-and-logarithms.md @@ -0,0 +1,37 @@ +--- +layout: recipe +title: Working with Exponents and Logarithms +chapter: Math +--- +## Problem + +You need to do some calculations that involve exponents and logarithms. + +## Solution + +Use Javascript's Math object to provide common mathematical functions. + +{% highlight coffeescript %} +# Math.pow(x, y) returns x^y +Math.pow(2, 4) +# => 16 + +# Math.exp(x) returns E^x and is shorthand for Math.pow(Math.E, x) +Math.exp(2) +# => 7.38905609893065 + +# Math.log returns the natural (base E) log +Math.log(5) +# => 1.6094379124341003 +Math.log(Math.exp(42)) +# => 42 + +# To get a log with some other base n, divide by Math.log(n) +Math.log(100) / Math.log(10) +# => 2 + +{% endhighlight %} + +## Discussion + +For more information on the Math object see the documentation on the [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math). Also refer to [Math Constants]({{ site.baseurl }}/chapters/math/constants) for discussion of the various constants in the Math object. diff --git a/chapters/metaprogramming/detecting-and-replacing-functions.md b/chapters/metaprogramming/detecting-and-replacing-functions.md new file mode 100644 index 0000000..4ba7b64 --- /dev/null +++ b/chapters/metaprogramming/detecting-and-replacing-functions.md @@ -0,0 +1,62 @@ +--- +layout: recipe +title: Detecting and Creating Missing Functions +chapter: Metaprogramming +--- +## Problem + +You want to detect if a function exists and create it if it does not (such as an ECMAScript 5 function in Internet Explorer 8). + +## Solution + +Use the existential assignment operator (`?=`) to assign a function to the classes' prototype (using the `::` shorthand), and wrap it all in a IIFE (`do ->`) to contain the variables. + +{% highlight coffeescript %} +do -> Array::filter ?= (callback) -> + element for element in this when callback element + +array = [1..10] + +array.filter (x) -> x > 5 +# => [6,7,8,9,10] +{% endhighlight %} + +## Discussion + +Objects in JavaScript (and thus, in CoffeeScript) have a prototype member that defines what member functions should be available on all objects based on that prototype. +In Coffeescript, you access this prototype using the `::` shortcut. So, if you want to add a filter function to the array class, you do `Array::filter = ...`. This will add the filter function to all arrays. + +However, we don't ever want to overwrite a prototype that we haven't created in the first place. For example, if `Array::filter` already exists in a fast native form in the browser, or a library maker has their own specific version of `Array::filter`, then you'll either replace the quick native version with a slow Javascript version, or you will break the library that depends on their own Array::shuffle. +What you need to do is only add the function if it doesn't already exist. That's where the existential assignment operator (`?=`) comes in. If we do `Array::filter ?= ...` instead, it will see if `Array::filter` already exists. If it does, then it will use the current version. If it doesn't, it will add yours. + +Finally, because the existential assignment operator--when compiled--creates a few variables, we clean up the code by wrapping it in an [Immediately-Invoked Function Expression (IIFE)](http://benalman.com/news/2010/11/immediately-invoked-function-expression/). This hides those internal-use-only variables from leaking outside. So, if the function we're writing already exists, it runs, does basically nothing, and exits, affecting absolutely none of your code. But, if the function we're writing *doesn't* exist, we send out only the function we're writing as a closure, so only the function you've made affects the code. The internal workings of `?=` are hidden either way. + +### Example + +Below, we've compiled and annotated the coffeescript written in the solution above + +{% highlight javascript %} +// (function(){ ... })() is an IIFE, compiled in thanks to `do ->` +(function() { + + // This is from the `?=` operator, used to check if Array.prototype.filter (`Array::filter`) exists. + // If it does, we set it to itself, and return. If it doesn't, then we set it to the function, and return the function. + // The IIFE is only used to hide _base and _ref from the outside world. + var _base, _ref; + return (_ref = (_base = Array.prototype).filter) != null ? _ref : _base.filter = function(callback) { + + // `element for element in this when callback element` + var element, _i, _len, _results; + _results = []; + for (_i = 0, _len = this.length; _i < _len; _i++) { + element = this[_i]; + if (callback(element)) { + _results.push(element); + } + } + return _results; + + }; +// The end of the IIFE from `do ->` +})(); +{% endhighlight %} diff --git a/chapters/metaprogramming/detecting-and-replacing-functions.textile b/chapters/metaprogramming/detecting-and-replacing-functions.textile deleted file mode 100644 index e87566b..0000000 --- a/chapters/metaprogramming/detecting-and-replacing-functions.textile +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: recipe -title: Detecting and Creating Missing Functions -chapter: Metaprogramming ---- - -h2. Problem - -You want to detect if a function exists and create it if it does not (such as an ECMAScript 5 function in Internet Explorer 8). - -h2. Solution - -Use :: to detect the function, and assign to it if it does not exist. - -{% highlight coffeescript %} -unless Array::filter - Array::filter = (callback) -> - element for element in this when callback(element) - -array = [1..10] - -array.filter (x) -> x > 5 -# => [6,7,8,9,10] -{% endhighlight %} - -h2. Discussion - -Objects in JavaScript (and thus, in CoffeeScript) have a prototype member that defines what member functions should be available on all objects based on that prototype. In CoffeeScript, you can access the prototype directly via the :: operator. - diff --git a/chapters/metaprogramming/extending-built-in-objects.md b/chapters/metaprogramming/extending-built-in-objects.md new file mode 100644 index 0000000..24eeab8 --- /dev/null +++ b/chapters/metaprogramming/extending-built-in-objects.md @@ -0,0 +1,26 @@ +--- +layout: recipe +title: Extending Built-in Objects +chapter: Metaprogramming +--- +## Problem + +You want to extend a class to add new functionality or replace old. + +## Solution + +Use `::` to assign your new function to the prototype of the object or class. + +{% highlight coffeescript %} +String::capitalize = () -> + (this.split(/\s+/).map (word) -> word[0].toUpperCase() + word[1..-1].toLowerCase()).join ' ' + +"foo bar baz".capitalize() +# => 'Foo Bar Baz' +{% endhighlight %} + +## Discussion + +Objects in JavaScript (and thus, in CoffeeScript) have a prototype member that defines what member functions should be available on all objects based on that prototype. In CoffeeScript, you can access the prototype directly via the `::` operator. + +**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-native-builtins/)). diff --git a/chapters/metaprogramming/extending-classes.textile b/chapters/metaprogramming/extending-classes.textile deleted file mode 100644 index 0038724..0000000 --- a/chapters/metaprogramming/extending-classes.textile +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: recipe -title: Extending Classes -chapter: Metaprogramming ---- - -h2. Problem - -You want to extend a class to add new functionality or replace old. - -h2. Solution - -Use :: to assign your new function to the prototype of the object or class. - -{% highlight coffeescript %} -String::capitalize = () -> - (this.split(/\s+/).map (word) -> word[0].toUpperCase() + word[1..-1].toLowerCase()).join ' ' - -"foo bar baz".capitalize() -# => 'Foo Bar Baz' -{% endhighlight %} - -h2. Discussion - -Objects in JavaScript (and thus, in CoffeeScript) have a prototype member that defines what member functions should be available on all objects based on that prototype. In CoffeeScript, you can access the prototype directly via the :: operator. - diff --git a/chapters/metaprogramming/index.textile b/chapters/metaprogramming/index.html similarity index 77% rename from chapters/metaprogramming/index.textile rename to chapters/metaprogramming/index.html index 2e886be..4a53a21 100644 --- a/chapters/metaprogramming/index.textile +++ b/chapters/metaprogramming/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/networking/basic-client.textile b/chapters/networking/basic-client.md similarity index 67% rename from chapters/networking/basic-client.textile rename to chapters/networking/basic-client.md index 34cf594..e9556db 100644 --- a/chapters/networking/basic-client.textile +++ b/chapters/networking/basic-client.md @@ -3,17 +3,15 @@ layout: recipe title: Basic Client chapter: Networking --- - -h2. Problem +## Problem You want to access a service provided over the network. - -h2. Solution +## Solution Create a basic TCP client. -h3. In Node.js +### In Node.js {% highlight coffeescript %} net = require 'net' @@ -31,9 +29,9 @@ connection.on 'data', (data) -> connection.end() {% endhighlight %} -h3. Example Usage +### Example Usage -Accessing the Basic Server: +Accessing the [Basic Server]({{ site.baseurl }}/chapters/networking/basic-server): {% highlight console %} $ coffee basic-client.coffee @@ -41,11 +39,12 @@ Opened connection to localhost:9001 Received: Hello, World! {% endhighlight %} -h2. Discussion +## Discussion The most important work takes place in the _connection.on 'data'_ handler, where the client receives its response from the server and would most likely arrange for responses to it. -See also the Basic Server, Bi-Directional Client, and Bi-Directional Server recipes. +See also the [Basic Server]({{ site.baseurl }}/chapters/networking/basic-server), [Bi-Directional Client]({{ site.baseurl }}/chapters/networking/bi-directional-client), and [Bi-Directional Server]({{ site.baseurl }}/chapters/networking/bi-directional-server) recipes. + +### Exercises -h3. Exercises * Add support for choosing the target domain and port based on command-line arguments or from a configuration file. diff --git a/chapters/networking/basic-http-client.md b/chapters/networking/basic-http-client.md new file mode 100644 index 0000000..0faef9f --- /dev/null +++ b/chapters/networking/basic-http-client.md @@ -0,0 +1,63 @@ +--- +layout: recipe +title: Basic HTTP Client +chapter: Networking +--- + +## Problem + +You want to create a HTTP client. + +## Solution + +In this recipe, we'll use [node.js](http://nodejs.org/)'s HTTP library. We'll go from a simple GET request example to a client which returns the external IP of a computer. + +### GET something + +{% highlight coffeescript %} +http = require 'http' + +http.get { host: 'www.google.com' }, (res) -> + console.log res.statusCode +{% endhighlight %} + +The `get` function, from node.js's `http` module, issues a GET request to a HTTP server. The response comes in the form of a callback, which we can handle in a function. This example merely prints the response status code. Check it out: + +{% highlight console %} +$ coffee http-client.coffee +200 + +{% endhighlight %} + +### What's my IP? + +If you are inside a network which relies on [NAT](http://en.wikipedia.org/wiki/Network_address_translation) such as a LAN, you probably have faced the issue of finding out what's your external IP address. Let's write a small coffeescript for this. + +{% highlight coffeescript %} +http = require 'http' + +http.get { host: 'checkip.dyndns.org' }, (res) -> + data = '' + res.on 'data', (chunk) -> + data += chunk.toString() + res.on 'end', () -> + console.log data.match(/([0-9]+\.){3}[0-9]+/)[0] +{% endhighlight %} + +We can get the data from the result object by listening on its `'data'` event; and know that it has come to an end once the `'end'` event has been fired. When that happens, we can do a simple regular expression match to extract our IP address. Try it: + +{% highlight console %} +$ coffee http-client.coffee +123.123.123.123 +{% endhighlight %} + +## Discussion + +Note that `http.get` is a shortcut of `http.request`. The latter allows you to issue HTTP requests with different methods, such as POST or PUT. + +For API and overall information on this subject, check node.js's [http](http://nodejs.org/docs/latest/api/http.html) and [https](http://nodejs.org/docs/latest/api/https.html) documentation pages. Also, the [HTTP spec](http://www.ietf.org/rfc/rfc2616.txt) might come in handy. + +### Exercises + +* Create a client for the key-value store HTTP server, from the [Basic HTTP Server](basic-http-server) recipe. + diff --git a/chapters/networking/basic-http-server.md b/chapters/networking/basic-http-server.md new file mode 100644 index 0000000..cbd11e9 --- /dev/null +++ b/chapters/networking/basic-http-server.md @@ -0,0 +1,271 @@ +--- +layout: recipe +title: Basic HTTP Server +chapter: Networking +--- + +## Problem + +You want to create a HTTP server over a network. Over the course of this recipe, we'll go step by step from the smallest server possible to a functional key-value store. + +## Solution + +We'll use [node.js](http://nodejs.org/)'s HTTP library to our own selfish purposes and create the simplest web server possible in Coffeescript. + +### Say 'hi\n' + +We can start by importing node.js's HTTP module. This contains `createServer` which, given a simple request handler, returns a HTTP server. We can use that server to listen on a TCP port. + +{% highlight coffeescript %} +http = require 'http' +server = http.createServer (req, res) -> res.end 'hi\n' +server.listen 8000 +{% endhighlight %} + +To run this example, simply put in a file and run it. You can kill it with `Ctrl-C`. We can test it using the `curl` command, available on most \*nix platforms: + +{% highlight console %} +$ curl -D - http://localhost:8000/ +HTTP/1.1 200 OK +Connection: keep-alive +Transfer-Encoding: chunked + +hi +{% endhighlight %} + +### What's going on? + +Let's get a little bit more feedback on what's happening on our server. While we're at it, we could also be friendlier to our clients and provide them some HTTP headers. + +{% highlight coffeescript %} +http = require 'http' + +server = http.createServer (req, res) -> + console.log req.method, req.url + data = 'hi\n' + res.writeHead 200, + 'Content-Type': 'text/plain' + 'Content-Length': data.length + res.end data + +server.listen 8000 +{% endhighlight %} + +Try to access it once again, but this time use different URL paths, such as `http://localhost:8000/coffee`. You'll see something like this on the server console: + +{% highlight console %} +$ coffee http-server.coffee +GET / +GET /coffee +GET /user/1337 +{% endhighlight %} + +### GETting stuff + +What if our webserver was able to hold some data? We'll try to come up with a simple key-value store in which elements are retrievable via GET requests. Provide a key on the request path and the server will return the corresponding value — or 404 if it doesn't exist. + +{% highlight coffeescript %} +http = require 'http' + +store = # we'll use a simple object as our store + foo: 'bar' + coffee: 'script' + +server = http.createServer (req, res) -> + console.log req.method, req.url + + value = store[req.url[1..]] + + if not value + res.writeHead 404 + else + res.writeHead 200, + 'Content-Type': 'text/plain' + 'Content-Length': value.length + 1 + res.write value + '\n' + + res.end() + +server.listen 8000 +{% endhighlight %} + +We can try several URLs to see how it responds: + +{% highlight console %} +$ curl -D - http://localhost:8000/coffee +HTTP/1.1 200 OK +Content-Type: text/plain +Content-Length: 7 +Connection: keep-alive + +script + +$ curl -D - http://localhost:8000/oops +HTTP/1.1 404 Not Found +Connection: keep-alive +Transfer-Encoding: chunked + +{% endhighlight %} + +### Use your head(ers) + +Let's face it, `text/plain` is kind of lame. How about if we use something hip like `application/json` or `text/xml`? Also, our store retrieval process could use a bit of refactoring — how about some exception throwing & handling? Let's see what we can come up with: + +{% highlight coffeescript %} +http = require 'http' + +# known mime types +[any, json, xml] = ['*/*', 'application/json', 'text/xml'] + +# gets a value from the db in format [value, contentType] +get = (store, key, format) -> + value = store[key] + throw 'Unknown key' if not value + switch format + when any, json then [JSON.stringify({ key: key, value: value }), json] + when xml then ["#{ key }\n#{ value }", xml] + else throw 'Unknown format' + +store = + foo: 'bar' + coffee: 'script' + +server = http.createServer (req, res) -> + console.log req.method, req.url + + try + key = req.url[1..] + [value, contentType] = get store, key, req.headers.accept + code = 200 + catch error + contentType = 'text/plain' + value = error + code = 404 + + res.writeHead code, + 'Content-Type': contentType + 'Content-Length': value.length + 1 + res.write value + '\n' + res.end() + +server.listen 8000 +{% endhighlight %} + +This server will still return the value which matches a given key, or 404 if non-existent. But it will structure the response either in JSON or XML, according to the `Accept` header. See for yourself: + +{% highlight console %} +$ curl http://localhost:8000/ +Unknown key + +$ curl http://localhost:8000/coffee +{"key":"coffee","value":"script"} + +$ curl -H "Accept: text/xml" http://localhost:8000/coffee +coffee +script + +$ curl -H "Accept: image/png" http://localhost:8000/coffee +Unknown format +{% endhighlight %} + +### You gotta give to get back + +The obvious last step in our adventure is to provide the client the ability to store data. We'll keep our RESTiness by listening to POST requests for this purpose. + +{% highlight coffeescript %} +http = require 'http' + +# known mime types +[any, json, xml] = ['*/*', 'application/json', 'text/xml'] + +# gets a value from the db in format [value, contentType] +get = (store, key, format) -> + value = store[key] + throw 'Unknown key' if not value + switch format + when any, json then [JSON.stringify({ key: key, value: value }), json] + when xml then ["#{ key }\n#{ value }", xml] + else throw 'Unknown format' + +# puts a value in the db +put = (store, key, value) -> + throw 'Invalid key' if not key or key is '' + store[key] = value + +store = + foo: 'bar' + coffee: 'script' + +# helper function that responds to the client +respond = (res, code, contentType, data) -> + res.writeHead code, + 'Content-Type': contentType + 'Content-Length': data.length + res.write data + res.end() + +server = http.createServer (req, res) -> + console.log req.method, req.url + key = req.url[1..] + contentType = 'text/plain' + code = 404 + + switch req.method + when 'GET' + try + [value, contentType] = get store, key, req.headers.accept + code = 200 + catch error + value = error + respond res, code, contentType, value + '\n' + + when 'POST' + value = '' + req.on 'data', (chunk) -> value += chunk + req.on 'end', () -> + try + put store, key, value + value = '' + code = 200 + catch error + value = error + '\n' + respond res, code, contentType, value + +server.listen 8000 +{% endhighlight %} + +Notice how the data is received in a POST request. By attaching some handlers on the `'data'` and `'end'` events of the request object, we're able to buffer and finally save the data from the client in the `store`. + +{% highlight console %} +$ curl -D - http://localhost:8000/cookie +HTTP/1.1 404 Not Found # ... +Unknown key + +$ curl -D - -d "monster" http://localhost:8000/cookie +HTTP/1.1 200 OK # ... + +$ curl -D - http://localhost:8000/cookie +HTTP/1.1 200 OK # ... +{"key":"cookie","value":"monster"} +{% endhighlight %} + +## Discussion + +Give `http.createServer` a function in the shape of `(request, response) -> ...` and it will return a server object, which we can use to listen on a port. Interact with the `request` and `response` objects to give the server its behaviour. Listen on port 8000 using `server.listen 8000`. + +For API and overall information on this subject, check node.js's [http](http://nodejs.org/docs/latest/api/http.html) and [https](http://nodejs.org/docs/latest/api/https.html) documentation pages. Also, the [HTTP spec](http://www.ietf.org/rfc/rfc2616.txt) might come in handy. + +### Exercises + +* Create a layer in between the server and the developer which would allow the developer to do something like: + +{% highlight coffeescript %} +server = layer.createServer + 'GET /': (req, res) -> + ... + 'GET /page': (req, res) -> + ... + 'PUT /image': (req, res) -> + ... +{% endhighlight %} + diff --git a/chapters/networking/basic-server.textile b/chapters/networking/basic-server.md similarity index 71% rename from chapters/networking/basic-server.textile rename to chapters/networking/basic-server.md index 16ae3fd..90bb381 100644 --- a/chapters/networking/basic-server.textile +++ b/chapters/networking/basic-server.md @@ -3,17 +3,15 @@ layout: recipe title: Basic Server chapter: Networking --- - -h2. Problem +## Problem You want to provide a service over a network. - -h2. Solution +## Solution Create a basic TCP server. -h3. In Node.js +### In Node.js {% highlight coffeescript %} net = require 'net' @@ -30,9 +28,9 @@ console.log "Listening to #{domain}:#{port}" server.listen port, domain {% endhighlight %} -h3. Example Usage +### Example Usage -Accessed by the Basic Client: +Accessed by the [Basic Client]({{ site.baseurl }}/chapters/networking/basic-client): {% highlight console %} $ coffee basic-server.coffee @@ -42,11 +40,12 @@ Received connection from 127.0.0.1 [...] {% endhighlight %} -h2. Discussion +## Discussion The function passed to @net.createServer@ receives the new socket provided for each new connection to a client. This basic server simply socializes with its visitors but a hard-working server would pass this socket along to a dedicated handler and then return to the task of waiting for the next client. -See also the Basic Client, Bi-Directional Server, and Bi-Directional Client recipes. +See also the [Basic Client]({{ site.baseurl }}/chapters/networking/basic-client), [Bi-Directional Server]({{ site.baseurl }}/chapters/networking/bi-directional-server), and [Bi-Directional Client]({{ site.baseurl }}/chapters/networking/bi-directional-client) recipes. + +### Exercises -h3. Exercises * Add support for choosing the target domain and port based on command-line arguments or from a configuration file. diff --git a/chapters/networking/bi-directional-client.textile b/chapters/networking/bi-directional-client.md similarity index 78% rename from chapters/networking/bi-directional-client.textile rename to chapters/networking/bi-directional-client.md index 7b41d4e..99953d7 100644 --- a/chapters/networking/bi-directional-client.textile +++ b/chapters/networking/bi-directional-client.md @@ -3,16 +3,15 @@ layout: recipe title: Bi-Directional Client chapter: Networking --- - -h2. Problem +## Problem You want to a persistent service over a network, one which maintains an on-going connection with its clients. -h2. Solution +## Solution Create a bi-directional TCP client. -h3. In Node.js +### In Node.js {% highlight coffeescript %} net = require 'net' @@ -40,9 +39,9 @@ connection.on 'end', (data) -> process.exit() {% endhighlight %} -h3. Example Usage +### Example Usage -Accessing the Bi-Directional Server: +Accessing the [Bi-Directional Server]({{ site.baseurl }}/chapters/networking/bi-directional-server): {% highlight console %} $ coffee bi-directional-client.coffee @@ -57,11 +56,12 @@ Received: You have 1 peer on this server Connection closed {% endhighlight %} -h2. Discussion +## Discussion This particular example initiates contact with the server and starts the conversation in the @connection.on 'connect'@ handler. The bulk of the work in a real client, however, will lie in the @connection.on 'data'@ handler, which processes output from the server. The @ping@ function only recurses in order to illustrate continuous communication with the server and can be removed from a real client. -See also the Bi-Directional Server, Basic Client, and Basic Server recipes. +See also the [Bi-Directional Server]({{ site.baseurl }}/chapters/networking/bi-directional-server), [Basic Client]({{ site.baseurl }}/chapters/networking/basic-client), and [Basic Server]({{ site.baseurl }}/chapters/networking/basic-server) recipes. + +### Exercises -h3. Exercises * Add support for choosing the target domain and port based on command-line arguments or from a configuration file. diff --git a/chapters/networking/bi-directional-server.textile b/chapters/networking/bi-directional-server.md similarity index 74% rename from chapters/networking/bi-directional-server.textile rename to chapters/networking/bi-directional-server.md index e3d9201..cb1b982 100644 --- a/chapters/networking/bi-directional-server.textile +++ b/chapters/networking/bi-directional-server.md @@ -3,16 +3,15 @@ layout: recipe title: Bi-Directional Server chapter: Networking --- - -h2. Problem +## Problem You want to provide a persistent service over a network, one which maintains an on-going connection with a client. -h2. Solution +## Solution Create a bi-directional TCP server. -h3. In Node.js +### In Node.js {% highlight coffeescript %} net = require 'net' @@ -32,9 +31,9 @@ console.log "Listening to #{domain}:#{port}" server.listen port, domain {% endhighlight %} -h3. Example Usage +### Example Usage -Accessed by the Bi-Directional Client: +Accessed by the [Bi-Directional Client]({{ site.baseurl }}/chapters/networking/bi-directional-client): {% highlight console %} $ coffee bi-directional-server.coffee @@ -46,12 +45,12 @@ New connection from 127.0.0.1 [...] {% endhighlight %} -h2. Discussion +## Discussion The bulk of the work lies in the @socket.on 'data'@ handler, which processes all of the input from the client. A real server would likely pass the data onto another function to process it and generate any responses so that the original handler. -See also the Bi-Directional Client, Basic Client, and Basic Server recipes. +See also the [Bi-Directional Client]({{ site.baseurl }}/chapters/networking/bi-directional-client), [Basic Client]({{ site.baseurl }}/chapters/networking/basic-client), and [Basic Server]({{ site.baseurl }}/chapters/networking/basic-server) recipes. -h3. Exercises -* Add support for choosing the target domain and port based on command-line arguments or on a configuration file. +### Exercises +* Add support for choosing the target domain and port based on command-line arguments or on a configuration file. diff --git a/chapters/networking/index.textile b/chapters/networking/index.html similarity index 76% rename from chapters/networking/index.textile rename to chapters/networking/index.html index cf90e3a..27de60d 100644 --- a/chapters/networking/index.textile +++ b/chapters/networking/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/regular_expressions/heregexes.textile b/chapters/regular_expressions/heregexes.md similarity index 87% rename from chapters/regular_expressions/heregexes.textile rename to chapters/regular_expressions/heregexes.md index cc913c9..31b09d0 100644 --- a/chapters/regular_expressions/heregexes.textile +++ b/chapters/regular_expressions/heregexes.md @@ -3,12 +3,11 @@ layout: recipe title: Using Heregexes chapter: Regular Expressions --- - -h2. Problem +## Problem You need to write a complex regular expression. -h2. Solution +## Solution Use Coffeescript's "heregexes" -- extended regular expressions that ignore internal whitespace and can contain comments. @@ -22,15 +21,14 @@ pattern = /// # => ['555', '123', '4567'] {% endhighlight %} -h2. Discussion +## Discussion Breaking up your complex regular expressions and commenting key sections makes them a lot more decipherable and maintainable. For example, changing this regex to allow an optional space between the prefix and line number would now be fairly obvious. -h3. Whitespace characters in heregexes +### Whitespace characters in heregexes -Whitespace is ignored in heregexes -- so what do you do if you need to match a -literal ASCII space? +Whitespace is ignored in heregexes -- so what do you do if you need to match a literal ASCII space? One solution is to use the @\s@ character class, which will match spaces, tabs and line breaks. If you only want to match a space, though, you'll need to use -@\x20@ to denote a literal ASCII space. +`\x20` to denote a literal ASCII space. diff --git a/chapters/regular_expressions/index.textile b/chapters/regular_expressions/index.html similarity index 77% rename from chapters/regular_expressions/index.textile rename to chapters/regular_expressions/index.html index f90d8a4..4af6fec 100644 --- a/chapters/regular_expressions/index.textile +++ b/chapters/regular_expressions/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/regular_expressions/replacing-html-tags-with-html-named-entities.md b/chapters/regular_expressions/replacing-html-tags-with-html-named-entities.md new file mode 100644 index 0000000..4b975d0 --- /dev/null +++ b/chapters/regular_expressions/replacing-html-tags-with-html-named-entities.md @@ -0,0 +1,25 @@ +--- +layout: recipe +title: Replacing HTML Tags with HTML Named Entities +chapter: Regular Expressions +--- +## Problem + +You need to replace HTML tags with named entities: + +`
=> <br/>` + +## Solution + +{% highlight coffeescript %} +htmlEncode = (str) -> + str.replace /[&<>"']/g, ($0) -> + "&" + {"&":"amp", "<":"lt", ">":"gt", '"':"quot", "'":"#39"}[$0] + ";" + +htmlEncode('Barnes & Noble') +# => '<a href="http://bn.com">Barnes & Noble</a>' +{% endhighlight %} + +## Discussion + +There are probably better ways to implement the above method. diff --git a/chapters/regular_expressions/replacing-substrings.md b/chapters/regular_expressions/replacing-substrings.md new file mode 100644 index 0000000..25468ab --- /dev/null +++ b/chapters/regular_expressions/replacing-substrings.md @@ -0,0 +1,39 @@ +--- +layout: recipe +title: Replacing Substrings +chapter: Regular Expressions +--- +## Problem + +You need to replace a portion of a string with another value. + +## Solution + +Use the JavaScript `replace` method. `replace` matches with the given string, and returns the edited string. + +The first version takes 2 arguments: _pattern_ and _string replacement_ + +{% highlight coffeescript %} +"JavaScript is my favorite!".replace /Java/, "Coffee" +# => 'CoffeeScript is my favorite!' + +"foo bar baz".replace /ba./, "foo" +# => 'foo foo baz' + +"foo bar baz".replace /ba./g, "foo" +# => 'foo foo foo' +{% endhighlight %} + +The second version takes 2 arguments: _pattern_ and _callback function_ + +{% highlight coffeescript %} +"CoffeeScript is my favorite!".replace /(\w+)/g, (match) -> + match.toUpperCase() +# => 'COFFEESCRIPT IS MY FAVORITE!' +{% endhighlight %} + +The callback function is invoked for each match, and the match value is passed as the argument to the callback. + +## Discussion + +Regular Expressions are a powerful way to match and replace strings. diff --git a/chapters/regular_expressions/searching-for-substrings.md b/chapters/regular_expressions/searching-for-substrings.md new file mode 100644 index 0000000..888c774 --- /dev/null +++ b/chapters/regular_expressions/searching-for-substrings.md @@ -0,0 +1,63 @@ +--- +layout: recipe +title: Searching for Substrings +chapter: Regular Expressions +--- +## Problem + +You need to search for a substring, and return either the starting position of the match or the matching value itself. + +## Solution + +There are several ways to accomplish this using regular expressions. Some methods are called on a `RegExp` pattern or object and some are called on `String` objects. + +### `RegExp` objects + +The first way is to call the `test` method on a `RegExp` pattern or object. The `test` method returns a boolean value: + +{% highlight coffeescript %} +match = /sample/.test("Sample text") +# => false + +match = /sample/i.test("Sample text") +# => true +{% endhighlight %} + +The next way to is to call the `exec` method on a `RegExp` pattern or object. The `exec` method returns an array with the match information or `null`: + +{% highlight coffeescript %} +match = /s(amp)le/i.exec "Sample text" +# => [ 'Sample', 'amp', index: 0, input: 'Sample text' ] + +match = /s(amp)le/.exec "Sample text" +# => null +{% endhighlight %} + +### `String` objects + +The `match` method matches a given string with the `RegExp`. With 'g' flag returns an array containing the matches, without 'g' flag returns just the first match or if no match is found returns `null`. + +{% highlight coffeescript %} +"Watch out for the rock!".match(/r?or?/g) +# => [ 'o', 'or', 'ro' ] + +"Watch out for the rock!".match(/r?or?/) +# => [ 'o', index: 6, input: 'Watch out for the rock!' ] + +"Watch out for the rock!".match(/ror/) +# => null +{% endhighlight %} + +The `search` method matches `RegExp` with string and returns the index of the beginning of the match if found, -1 if not. + +{% highlight coffeescript %} +"Watch out for the rock!".search /for/ +# => 10 + +"Watch out for the rock!".search /rof/ +# => -1 +{% endhighlight %} + +## Discussion + +Regular Expressions are a powerful way to test and match substrings. diff --git a/chapters/strings/capitalizing-words.textile b/chapters/strings/capitalizing-words.md similarity index 89% rename from chapters/strings/capitalizing-words.textile rename to chapters/strings/capitalizing-words.md index 4074ea1..28ac74d 100644 --- a/chapters/strings/capitalizing-words.textile +++ b/chapters/strings/capitalizing-words.md @@ -3,12 +3,11 @@ layout: recipe title: Capitalizing Words chapter: Strings --- - -h2. Problem +## Problem You want to capitalize the first letter of every word in a string. -h2. Solution +## Solution Use the split, map, join pattern: Split the string into words, then use a map to capitalize the first letter and lowercase all other letters of each word before gluing the string back together with join. @@ -24,9 +23,9 @@ Or do the same thing using a list comprehension: # => 'Foo Bar Baz' {% endhighlight %} -h2. Discussion +## Discussion -Split, map, join is a common scripting pattern dating back to perl. This function may benefit from being placed directly onto the String class by Extending Classes. +Split, map, join is a common scripting pattern dating back to Perl. This function may benefit from being placed directly onto the String class by [Extending Classes]({{ site.baseurl }}/chapters/objects/extending-classes). Be aware that two wrinkles can appear in the split, map, join pattern. The first is that the split text works best when it is constant. If the source string has multiple spaces in it, the split will need to take this into account to prevent getting extra, empty words. One way to do this is with a regular expression to split on runs of whitespace instead of a single space: @@ -38,4 +37,3 @@ Be aware that two wrinkles can appear in the split, map, join pattern. The first ...but this leads us to the second wrinkle: notice that the runs of whitespace are now compressed down to a single character by the join. Quite often one or both of these wrinkles is acceptable, however, so the split, map, join pattern can be a powerful tool. - diff --git a/chapters/strings/finding-substrings.textile b/chapters/strings/finding-substrings.md similarity index 65% rename from chapters/strings/finding-substrings.textile rename to chapters/strings/finding-substrings.md index 713c1fa..ee81e89 100644 --- a/chapters/strings/finding-substrings.textile +++ b/chapters/strings/finding-substrings.md @@ -3,30 +3,29 @@ layout: recipe title: Finding Substrings chapter: Strings --- - -h2. Problem +## Problem You need to find the first or last occurrence of a search string within a message. -h2. Solution +## Solution Use Javascript's indexOf() and lastIndexOf() to find the first and last occurrences of a string, respectively. -Syntax: string.indexOf(searchstring, start) +Syntax: string.indexOf searchstring, start {% highlight coffeescript %} message = "This is a test string. This has a repeat or two. This might even have a third." -message.indexOf("This",0) +message.indexOf "This", 0 # => 0 # Modifying the start parameter -message.indexOf("This",5) +message.indexOf "This", 5 # => 23 -message.lastIndexOf("This") +message.lastIndexOf "This" # => 49 -{% endhighlight %} - -h2. Discussion +# Count occurrences of a given string +message.split(" a ").length - 1 +# => 3 -Still need recipe to count occurrences of a given string within a message. \ No newline at end of file +{% endhighlight %} diff --git a/chapters/strings/generating-a-unique-id.md b/chapters/strings/generating-a-unique-id.md new file mode 100644 index 0000000..20851c3 --- /dev/null +++ b/chapters/strings/generating-a-unique-id.md @@ -0,0 +1,28 @@ +--- +layout: recipe +title: Generating a Unique ID +chapter: Strings +--- +## Problem + +You want to generate a random unique identifier. + +## Solution + +You can create a Base 36 encoded string from a random number. + +{% highlight coffeescript %} +uniqueId = (length=8) -> + id = "" + id += Math.random().toString(36).substr(2) while id.length < length + id.substr 0, length + +uniqueId() # => n5yjla3b +uniqueId(2) # => 0d +uniqueId(20) # => ox9eo7rt3ej0pb9kqlke +uniqueId(40) # => xu2vo4xjn4g0t3xr74zmndshrqlivn291d584alj +{% endhighlight %} + +## Discussion + +There are other possible techniques, but this is relatively performant and flexible. diff --git a/chapters/strings/index.textile b/chapters/strings/index.html similarity index 76% rename from chapters/strings/index.textile rename to chapters/strings/index.html index 6186231..b74efe3 100644 --- a/chapters/strings/index.textile +++ b/chapters/strings/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
    {% 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/strings/interpolation.textile b/chapters/strings/interpolation.md similarity index 81% rename from chapters/strings/interpolation.textile rename to chapters/strings/interpolation.md index 259f30e..a437be4 100644 --- a/chapters/strings/interpolation.textile +++ b/chapters/strings/interpolation.md @@ -3,16 +3,16 @@ layout: recipe title: String Interpolation chapter: Strings --- - -h2. Problem +## Problem You want to create a string that contains a text representation of a CoffeeScript Variable. -h2. Solution +## Solution Use CoffeeScript's ruby-like string interpolation instead of -JavaScript's string addition. +JavaScript's string addition. You must use Double-quoted strings to +allow for interpolation. Single-quoted strings are treated as literals. Interpolation: @@ -30,10 +30,10 @@ message = "The square of 7 is #{square 7}." # => "The square of 7 is 49." {% endhighlight %} -h2. Discussion +## Discussion CoffeeScript interpolates strings in similar fashion to ruby. Most -expressions are valid inside the #{...} interpolation syntax. +expressions are valid inside the `#{...}` interpolation syntax. CoffeeScript permits multiple expressions inside the interpolation which can have side effects, but this is discouraged. Only the last @@ -47,4 +47,3 @@ message = "The square of 10 is #{muppet='Animal'; square 10}. Oh, and your favor # => "The square of 10 is 100. Oh, and your favorite muppet is now Animal." {% endhighlight %} - diff --git a/chapters/strings/lowercasing-a-string.md b/chapters/strings/lowercasing-a-string.md new file mode 100644 index 0000000..f39dd91 --- /dev/null +++ b/chapters/strings/lowercasing-a-string.md @@ -0,0 +1,47 @@ +--- +layout: recipe +title: Lowercasing a String +chapter: Strings +--- +## Problem + +You want to lowercase a string. + +## Solution + +Use JavaScript's String toLowerCase() method: + +{% highlight coffeescript %} +"ONE TWO THREE".toLowerCase() +# => 'one two three' +{% endhighlight %} + +## Discussion + +`toLowerCase()` is a standard JavaScript method. Don't forget the parentheses. + +### Syntax Sugar + +You can add some Ruby-like syntax sugar with the following shortcut: + +{% highlight coffeescript %} +String::downcase = -> @toLowerCase() +"ONE TWO THREE".downcase() +# => 'one two three' +{% endhighlight %} + +The snippet above demonstrates a few features of CoffeeScript: + +* The double-colon `::` is shorthand for saying `.prototype.` +* The "at" sign `@` is shorthand for saying `this.` + +The code above compiles in to the following JavaScript: + +{% highlight javascript %} +String.prototype.downcase = function() { + return this.toLowerCase(); +}; +"ONE TWO THREE".downcase(); +{% 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](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/)). \ No newline at end of file diff --git a/chapters/strings/matching-strings.md b/chapters/strings/matching-strings.md new file mode 100644 index 0000000..1f16f05 --- /dev/null +++ b/chapters/strings/matching-strings.md @@ -0,0 +1,49 @@ +--- +layout: recipe +title: Matching Strings +chapter: Strings +--- +## Problem + +You want to match two or more strings. + +## Solution + +Calculate the edit distance, or number of operations required to transform one string into the other. + +{% highlight coffeescript %} + + levenshtein = (str1, str2) -> + + l1 = str1.length + l2 = str2.length + prevDist = [0..l2] + nextDist = [0..l2] + + for i in [1..l1] by 1 + nextDist[0] = i + for j in [1..l2] by 1 + if (str1.charAt i-1) == (str2.charAt j-1) + nextDist[j] = prevDist[j-1] + else + nextDist[j] = 1 + Math.min prevDist[j], nextDist[j-1], prevDist[j-1] + [prevDist,nextDist]=[nextDist, prevDist] + + prevDist[l2] + +{% endhighlight %} + +## Discussion + +You can use either Hirschberg or Wagner–Fischer's algorithm to calculate a Levenshtein distance. This example uses Wagner–Fischer's algorithm. + +This version of Levenshtein algorithm is linear in memory, quadratic in time. + +str.charAt i is preferred here to str[i] because the latter syntax is not supported by some browsers (e.g. IE7). + +At first glance the use of "by 1" in the two loops might look useless. It is actually here to avoid a common danger +of the coffeescript [i..j] syntax. If str1 or str2 is an empty string, then [1..l1] or [1..l2] will return [1,0]. +The loops with the "by 1" statement also compiles to cleaner / slightly more performant javascript. + +Finally the optimization of recycling of arrays at the end of the loops is mainly here to +demonstrate the syntax of coffeescript for swapping two variables. diff --git a/chapters/strings/repeating.textile b/chapters/strings/repeating.md similarity index 61% rename from chapters/strings/repeating.textile rename to chapters/strings/repeating.md index 6db88f2..78214ab 100644 --- a/chapters/strings/repeating.textile +++ b/chapters/strings/repeating.md @@ -3,24 +3,30 @@ layout: recipe title: Repeating a String chapter: Strings --- - -h2. Problem +## Problem You want to repeat a string. -h2. Solution +## Solution Create an array of n+1 nulls, and then join it with the repetition string as the glue: {% highlight coffeescript %} # create a string of 10 foos -Array(11).join('foo') +Array(11).join 'foo' # => "foofoofoofoofoofoofoofoofoofoo" {% endhighlight %} -h2. Discussion +## Repeat method for Strings -JavaScript lacks a string repeat function, as does CoffeeScript. List comprehensions and maps can be pressed into service here, but in the case of a simple string repeat it's easier to simply build an array of n+1 nulls and then glue them together. +You could also create a method for this in String prototype. It is as simple as that: + +{% highlight coffeescript %} +# add repeat method for all strings, that returns string repeated n times +String::repeat = (n) -> Array(n+1).join(this) +{% endhighlight %} +## Discussion +JavaScript lacks a string repeat function, as does CoffeeScript. List comprehensions and maps can be pressed into service here, but in the case of a simple string repeat it's easier to simply build an array of n+1 nulls and then glue them together. diff --git a/chapters/strings/replacing-sub-strings.md b/chapters/strings/replacing-sub-strings.md new file mode 100644 index 0000000..add9945 --- /dev/null +++ b/chapters/strings/replacing-sub-strings.md @@ -0,0 +1,29 @@ +--- +layout: recipe +title: Replacing Sub-Strings Within a String +chapter: Strings +--- +## Problem + +You want to replace a sub-string with a new sub-string. + +## Solution + +Split the string using the sub-string you want to remove as a delimiter. Then re-join using the new sub-string as the delimiter. + +{% highlight coffeescript %} +"Orange is the new Black".split("Orange").join("Pink") +# => "Pink is the new Black" + +"I am so sad. I cannot believe how sad I am today!".split("sad").join("happy") +# => "I am so happy. I cannot believe how happy I am today!" + +"I am not a crook.".split("not ").join("") +# => "I am a crook." +{% endhighlight %} + +## Discussion + +You can also use regexes. If you're matching an exact string, this way is simpler and 10x faster. + +If you use regexes, remember that you must escape certain characters. diff --git a/chapters/strings/splitting-a-string.textile b/chapters/strings/splitting-a-string.md similarity index 95% rename from chapters/strings/splitting-a-string.textile rename to chapters/strings/splitting-a-string.md index 83c1162..51f3350 100644 --- a/chapters/strings/splitting-a-string.textile +++ b/chapters/strings/splitting-a-string.md @@ -3,12 +3,11 @@ layout: recipe title: Splitting a String chapter: Strings --- - -h2. Problem +## Problem You want to split a string. -h2. Solution +## Solution Use JavaScript's String split() method: @@ -17,7 +16,7 @@ Use JavaScript's String split() method: # => [ 'foo', 'bar', 'baz' ] {% endhighlight %} -h2. Discussion +## Discussion String's split() is a standard JavaScript method. It can be used to split a string on any delimiter, including regular expressions. It also accepts a second parameter that specifies the number of splits to return. diff --git a/chapters/strings/trimming-whitespace-from-a-string.md b/chapters/strings/trimming-whitespace-from-a-string.md new file mode 100644 index 0000000..e7e9ecd --- /dev/null +++ b/chapters/strings/trimming-whitespace-from-a-string.md @@ -0,0 +1,64 @@ +--- +layout: recipe +title: Trimming Whitespace from a String +chapter: Strings +--- +## Problem + +You want to trim whitespace from a string. + +## Solution + +Use JavaScript's Regular Expression support to replace whitespace. + +To trim leading and trailing whitespace, use the following: + +{% highlight coffeescript %} +" padded string ".replace /^\s+|\s+$/g, "" +# => 'padded string' +{% endhighlight %} + +To trim only leading whitespace, use the following: + +{% highlight coffeescript %} +" padded string ".replace /^\s+/g, "" +# => 'padded string ' +{% endhighlight %} + +To trim only trailing whitespace, use the following: + +{% highlight coffeescript %} +" padded string ".replace /\s+$/g, "" +# => ' padded string' +{% endhighlight %} + +## Discussion + +Opera, Firefox and Chrome all have a native string prototype `trim` method, and the other browsers could add one as well. For this particular method, I would use the built-in method where possible, otherwise create a polyfill: + +{% highlight coffeescript %} +unless String::trim then String::trim = -> @replace /^\s+|\s+$/g, "" + +" padded string ".trim() +# => 'padded string' +{% endhighlight %} + + +### Syntax Sugar + +You can add some Ruby-like syntax sugar with the following shortcuts: + +{% highlight coffeescript %} +String::strip = -> if String::trim? then @trim() else @replace /^\s+|\s+$/g, "" +String::lstrip = -> @replace /^\s+/g, "" +String::rstrip = -> @replace /\s+$/g, "" + +" padded string ".strip() +# => 'padded string' +" padded string ".lstrip() +# => 'padded string ' +" padded string ".rstrip() +# => ' padded string' +{% endhighlight %} + +For an interesting discussion and benchmarks of JavaScript `trim` performance, see [this blog post](http://blog.stevenlevithan.com/archives/faster-trim-javascript) by Steve Levithan. diff --git a/chapters/strings/uppercasing-a-string.md b/chapters/strings/uppercasing-a-string.md new file mode 100644 index 0000000..7cbc242 --- /dev/null +++ b/chapters/strings/uppercasing-a-string.md @@ -0,0 +1,47 @@ +--- +layout: recipe +title: Uppercasing a String +chapter: Strings +--- +## Problem + +You want to uppercase a string. + +## Solution + +Use JavaScript's String toUpperCase() method: + +{% highlight coffeescript %} +"one two three".toUpperCase() +# => 'ONE TWO THREE' +{% endhighlight %} + +## Discussion + +`toUpperCase()` is a standard JavaScript method. Don't forget the parentheses. + +### Syntax Sugar + +You can add some Ruby-like syntax sugar with the following shortcut: + +{% highlight coffeescript %} +String::upcase = -> @toUpperCase() +"one two three".upcase() +# => 'ONE TWO THREE' +{% endhighlight %} + +The snippet above demonstrates a few features of CoffeeScript: + +* The double-colon `::` is shorthand for saying `.prototype.` +* The "at" sign `@` is shorthand for saying `this.` + +The code above compiles in to the following JavaScript: + +{% highlight javascript %} +String.prototype.upcase = function() { + return this.toUpperCase(); +}; +"one two three".upcase(); +{% 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](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/syntax/code_reuse_on_client_and_server.md b/chapters/syntax/code_reuse_on_client_and_server.md new file mode 100644 index 0000000..92e706b --- /dev/null +++ b/chapters/syntax/code_reuse_on_client_and_server.md @@ -0,0 +1,90 @@ +--- +layout: recipe +title: Code Reuse on Client and Server +chapter: Syntax +--- +## Problem + +You have created some functionality in CoffeeScript that you wish to use on the client with a web browser and on the server with Node.js. + +## Solution + +Export the functionality in the following manner: + +{% highlight coffeescript %} + +# simpleMath.coffee + +# these methods are private +add = (a, b) -> + a + b + +subtract = (a, b) -> + a - b + +square = (x) -> + x * x + +# create a namespace to export our public methods +SimpleMath = exports? and exports or @SimpleMath = {} + +# items attached to our namespace are available in Node.js as well as client browsers +class SimpleMath.Calculator + add: add + subtract: subtract + square: square + +{% endhighlight %} + +## Discussion + +In the above example, we create a new namespace called SimpleMath. If `export` is available, our class is exported as a Node.js module. If `export` is *not* available, then SimpleMath is added to the global namespace and available to our web page. + +In Node.js, we can include our module using the `require` command. + +{% highlight console %} + +$ node + +> var SimpleMath = require('./simpleMath'); +undefined +> var Calc = new SimpleMath.Calculator(); +undefined +> console.log("5 + 6 = ", Calc.add(5, 6)); +5 + 6 = 11 +undefined +> + +{% endhighlight %} + +In our web page, we can include our module by including it as a script. + +{% highlight html %} + + + + + + SimpleMath Module Example + + + + + +

A SimpleMath Example

+
    + + + +{% endhighlight %} + +Result: + +#A SimpleMath Example +* 5 + 6 = 11 \ No newline at end of file diff --git a/chapters/syntax/comparing_ranges.textile b/chapters/syntax/comparing_ranges.md similarity index 94% rename from chapters/syntax/comparing_ranges.textile rename to chapters/syntax/comparing_ranges.md index 10f8b52..ae2b1df 100644 --- a/chapters/syntax/comparing_ranges.textile +++ b/chapters/syntax/comparing_ranges.md @@ -3,12 +3,11 @@ layout: recipe title: Comparing Ranges chapter: Syntax --- - -h2. Problem +## Problem You want to know if a variable is inside a given range. -h2. Solution +## Solution Use CoffeeScript's chained comparison syntax. @@ -22,7 +21,7 @@ normalHeight = maxDwarfism < height < minAcromegaly # => true {% endhighlight %} -h2. Discussion +## Discussion This is a nice feature lifted from Python. Instead of writing out the full comparison like @@ -31,5 +30,3 @@ normalHeight = height > maxDwarfism && height < minAcromegaly {% endhighlight %} CoffeeScript allows us to chain the two comparisons together in a form that more closely matches the way a mathematician would write it. - - diff --git a/chapters/syntax/embedding_javascript.textile b/chapters/syntax/embedding_javascript.md similarity index 66% rename from chapters/syntax/embedding_javascript.textile rename to chapters/syntax/embedding_javascript.md index e082caf..69d386c 100644 --- a/chapters/syntax/embedding_javascript.textile +++ b/chapters/syntax/embedding_javascript.md @@ -3,12 +3,11 @@ layout: recipe title: Embedding JavaScript chapter: Syntax --- - -h2. Problem +## Problem You want to include some found/pre-written JavaScript code inline with your CoffeeScript. -h2. Solution +## Solution Wrap the JavaScript with backticks: @@ -22,9 +21,9 @@ greet "Coffee" # => "Hello Coffee" {% endhighlight %} -h2. Discussion +## Discussion -This is a simple way to integrate small snippets of JavaScript code into your CoffeeScript without converting it over to use CoffeeScript syntax. As shown in the "CoffeeScript Language Reference":http://jashkenas.github.com/coffee-script/#embedded you can mix to the two languages to a certain extent: +This is a simple way to integrate small snippets of JavaScript code into your CoffeeScript without converting it over to use CoffeeScript syntax. As shown in the [CoffeeScript Language Reference](http://jashkenas.github.com/coffee-script/#embedded) you can mix the two languages to a certain extent: {% highlight coffeescript %} hello = `function (name) { @@ -34,4 +33,5 @@ hello "Coffee" # => "Hello Coffee" {% endhighlight %} -Here the "hello" variable is still in CoffeeScript, but is assigned a function written in JavaScript. \ No newline at end of file + +Here the `hello` variable is still in CoffeeScript, but is assigned a function written in JavaScript. diff --git a/chapters/syntax/for_loops.textile b/chapters/syntax/for_loops.md similarity index 93% rename from chapters/syntax/for_loops.textile rename to chapters/syntax/for_loops.md index 80622bb..e56802a 100644 --- a/chapters/syntax/for_loops.textile +++ b/chapters/syntax/for_loops.md @@ -3,12 +3,11 @@ layout: recipe title: For Loops chapter: Syntax --- - -h2. Problem +## Problem You need to iterate over an array, object or range with a for loop. -h2. Solution +## Solution {% highlight coffeescript %} # for(i = 1; i<= 10; i++) @@ -25,6 +24,6 @@ x * x for x in [1..10] # = > [1,4,9,16,25,36,49,64,81,100] {% endhighlight %} -h2. Discussion +## Discussion Comprehensions replace for loops in CoffeeScript, but they simply compile into the traditional javascript equivalent for-loop. \ No newline at end of file diff --git a/chapters/syntax/index.textile b/chapters/syntax/index.html similarity index 76% rename from chapters/syntax/index.textile rename to chapters/syntax/index.html index 3605ea3..03ab758 100644 --- a/chapters/syntax/index.textile +++ b/chapters/syntax/index.html @@ -7,11 +7,12 @@ {% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} {% capture indexurl %}{{ url }}/index.html{% endcapture %} +
      {% 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/testing/images/jasmine_failing_all.jpg b/chapters/testing/images/jasmine_failing_all.jpg new file mode 100644 index 0000000..29a601c Binary files /dev/null and b/chapters/testing/images/jasmine_failing_all.jpg differ diff --git a/chapters/testing/images/jasmine_failing_better.jpg b/chapters/testing/images/jasmine_failing_better.jpg new file mode 100644 index 0000000..0f212b0 Binary files /dev/null and b/chapters/testing/images/jasmine_failing_better.jpg differ diff --git a/chapters/testing/images/jasmine_passing.jpg b/chapters/testing/images/jasmine_passing.jpg new file mode 100644 index 0000000..8c3f2d7 Binary files /dev/null and b/chapters/testing/images/jasmine_passing.jpg differ diff --git a/chapters/testing/index.html b/chapters/testing/index.html new file mode 100644 index 0000000..228a39f --- /dev/null +++ b/chapters/testing/index.html @@ -0,0 +1,18 @@ +--- +layout: chapter +title: Testing +chapter: Testing +--- + +{% capture url %}/chapters/{{ page.chapter | replace: ' ', '_' | downcase }}{% endcapture %} +{% capture indexurl %}{{ url }}/index.html{% endcapture %} + +
      +{% for page in site.pages %} + {% if page.url contains url %} + {% unless page.url == indexurl %} +
    • {{ page.title }}
    • + {% endunless %} + {% endif %} +{% endfor %} +
    diff --git a/chapters/testing/testing_with_jasmine.md b/chapters/testing/testing_with_jasmine.md new file mode 100644 index 0000000..9d77961 --- /dev/null +++ b/chapters/testing/testing_with_jasmine.md @@ -0,0 +1,200 @@ +--- +layout: recipe +title: Testing with Jasmine +chapter: Testing +--- +## Problem + +You are writing a simple calculator using CoffeeScript and you want to verify it functions as expected. You decide to use the Jasmine test framework. + +## Discussion + +When using the Jasmine test framework, you write tests in a specification (spec) file that describes the expected functionality of the code to be tested. + +For example, we expect our calculator will be able to add and subtract and will function correctly with both positive and negative numbers. Our spec is listed below. + +{% highlight coffeescript %} + +# calculatorSpec.coffee + +describe 'Calculator', -> + + it 'can add two positive numbers', -> + calculator = new Calculator() + result = calculator.add 2, 3 + expect(result).toBe 5 + + it 'can handle negative number addition', -> + calculator = new Calculator() + result = calculator.add -10, 5 + expect(result).toBe -5 + + it 'can subtract two positive numbers', -> + calculator = new Calculator() + result = calculator.subtract 10, 6 + expect(result).toBe 4 + + it 'can handle negative number subtraction', -> + calculator = new Calculator() + result = calculator.subtract 4, -6 + expect(result).toBe 10 + +{% endhighlight %} + + +### Configuring Jasmine + +Before you can run your tests, you must download and configure Jasmine. This involves: +1. downloading the latest Jasmine zip file; +2. creating a spec and a spec/jasmine folder in your project; +3. extracting the downloaded Jasmine files into the spec/jasmine folder; and +4. creating a test runner. + +### Create a Test Runner + +Jasmine can run your tests within a web browser by using a spec runner HTML file. The spec runner is a simple HTML page that links the necessary JavaScript and CSS files for both Jasmine and your code. A sample is below. + +{% highlight html linenos %} + + + + + Jasmine Spec Runner + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +This spec runner can be downloaded from this GitHub gist. + +To use the SpecRunner.html, simply reference your compiled JavaScript files and compiled tests after jasmine.js and its dependencies. + +In the above example, we include our yet-to-be-developed calculator.js file on line 14 and our compiled calculatorSpec.js file on line 17. + +## Running the Tests + +To run our tests, simply open SpecRunner.html in a web browser. In our example we see 4 failing specs with a total of 8 failures (below). + +All failing tests + +It appears our tests are failing because Jasmine cannot find the variable Calculator. That's because it has not been created yet. Let's do that now by creating a new file named js/calculator.coffee. + + +{% highlight coffeescript %} + +# calculator.coffee + +window.Calculator = class Calculator + +{% endhighlight %} + +Compile calculator.coffee and refresh the browser to re-run the test suite. + +Still failing, but better + +We now have 4 failures instead of our previous 8. That's a 50% improvement with only one line of code. + +## Getting the Tests to Pass + +Let's implement our methods and see if we can get these tests to pass. + +{% highlight coffeescript %} + +# calculator.coffee + +window.Calculator = class Calculator + add: (a, b) -> + a + b + + subtract: (a, b) -> + a - b + +{% endhighlight %} + +When we refresh we see they all pass. + +All passing + + +## Refactoring the Tests + +Now that our tests pass, we should look to see if our code or our test(s) can be refactored. + +In our spec file, each test creates its own calculator instance. This can make our tests quite repetitive especially for larger test suites. Ideally, we should consider moving that initialization code into a routine that runs before each test. + +Luckily Jasmine has a beforeEach function just for this purpose. + +{% highlight coffeescript %} + +describe 'Calculator', -> + calculator = null + + beforeEach -> + calculator = new Calculator() + + it 'can add two positive numbers', -> + result = calculator.add 2, 3 + expect(result).toBe 5 + + it 'can handle negative number addition', -> + result = calculator.add -10, 5 + expect(result).toBe -5 + + it 'can subtract two positive numbers', -> + result = calculator.subtract 10, 6 + expect(result).toBe 4 + + it 'can handle negative number subtraction', -> + result = calculator.subtract 4, -6 + expect(result).toBe 10 + +{% endhighlight %} + +When we recompile our spec and refresh the browser we see the tests still all pass. + +All passing diff --git a/chapters/testing/testing_with_nodeunit.md b/chapters/testing/testing_with_nodeunit.md new file mode 100644 index 0000000..ec610d2 --- /dev/null +++ b/chapters/testing/testing_with_nodeunit.md @@ -0,0 +1,226 @@ +--- +layout: recipe +title: Testing with Nodeunit +chapter: Testing +--- +## Problem + +You are writing a simple calculator using CoffeeScript and you want to verify it functions as expected. You decide to use the Nodeunit test framework. + +## Discussion + +Nodeunit is a JavaScript implementation of the xUnit family of Unit Testing libraries, similar libraries are available for Java, Python, Ruby, Smalltalk etc. + +When using xUnit family test frameworks, you write tests in a file that describes the expected functionality of the code to be tested. + +For example, we expect our calculator will be able to add and subtract and will function correctly with both positive and negative numbers. Our test is listed below. + +{% highlight coffeescript %} + +# test/calculator.test.coffee + +Calculator = require '../calculator' + +exports.CalculatorTest = + + 'test can add two positive numbers': (test) -> + calculator = new Calculator + result = calculator.add 2, 3 + test.equal(result, 5) + test.done() + + 'test can handle negative number addition': (test) -> + calculator = new Calculator + result = calculator.add -10, 5 + test.equal(result, -5) + test.done() + + 'test can subtract two positive numbers': (test) -> + calculator = new Calculator + result = calculator.subtract 10, 6 + test.equal(result, 4) + test.done() + + 'test can handle negative number subtraction': (test) -> + calculator = new Calculator + result = calculator.subtract 4, -6 + test.equal(result, 10) + test.done() + +{% endhighlight %} + +### Installing Nodeunit + +Before you can run your tests, you must install Nodeunit: + +First of all create a `package.json` file + +{% highlight javascript %} +{ + "name": "calculator", + "version": "0.0.1", + "scripts": { + "test": "./node_modules/.bin/nodeunit test" + }, + "dependencies": { + "coffee-script": "~1.4.0", + "nodeunit": "~0.7.4" + } +} +{% endhighlight %} + +Next from a terminal run. + +{% highlight bash %} +$ npm install +{% endhighlight %} + +## Running the Tests + +It's easy to run the tests from the command-line: + +{% highlight bash %} +$ npm test +{% endhighlight %} + +The test runner should fail, because we have no calculator.coffee + + suki@Yuzuki:nodeunit_testing (master)$ npm test + npm WARN package.json calculator@0.0.1 No README.md file found! + + > calculator@0.0.1 test /Users/suki/tmp/nodeunit_testing + > ./node_modules/.bin/nodeunit test + + + /Users/suki/tmp/nodeunit_testing/node_modules/nodeunit/lib/nodeunit.js:72 + if (err) throw err; + ^ + Error: ENOENT, stat '/Users/suki/tmp/nodeunit_testing/test' + npm ERR! Test failed. See above for more details. + npm ERR! not ok code 0 + +Let's create a simple file + +{% highlight coffeescript %} + +# calculator.coffee + +class Calculator + +module.exports = Calculator +{% endhighlight %} + +And re-run the test suite. + + suki@Yuzuki:nodeunit_testing (master)$ npm test + npm WARN package.json calculator@0.0.1 No README.md file found! + + > calculator@0.0.1 test /Users/suki/tmp/nodeunit_testing + > ./node_modules/.bin/nodeunit test + + + calculator.test + ✖ CalculatorTest - test can add two positive numbers + + TypeError: Object # has no method 'add' + ... + + ✖ CalculatorTest - test can handle negative number addition + + TypeError: Object # has no method 'add' + ... + + ✖ CalculatorTest - test can subtract two positive numbers + + TypeError: Object # has no method 'subtract' + ... + + ✖ CalculatorTest - test can handle negative number subtraction + + TypeError: Object # has no method 'subtract' + ... + + + FAILURES: 4/4 assertions failed (31ms) + npm ERR! Test failed. See above for more details. + npm ERR! not ok code 0 + + +## Getting the Tests to Pass + +Let's implement our methods and see if we can get these tests to pass. + +{% highlight coffeescript %} + +# calculator.coffee + +class Calculator + + add: (a, b) -> + a + b + + subtract: (a, b) -> + a - b + +module.exports = Calculator +{% endhighlight %} + +When we rerun the tests we see they're all passing: + + suki@Yuzuki:nodeunit_testing (master)$ npm test + npm WARN package.json calculator@0.0.1 No README.md file found! + + > calculator@0.0.1 test /Users/suki/tmp/nodeunit_testing + > ./node_modules/.bin/nodeunit test + + + calculator.test + ✔ CalculatorTest - test can add two positive numbers + ✔ CalculatorTest - test can handle negative number addition + ✔ CalculatorTest - test can subtract two positive numbers + ✔ CalculatorTest - test can handle negative number subtraction + + OK: 4 assertions (27ms) + + +## Refactoring the Tests + +Now that our tests pass, we should look to see if our code or our test(s) can be refactored. + +In our test file, each test creates its own calculator instance. This can make our tests quite repetitive especially for larger test suites. Ideally, we should consider moving that initialization code into a routine that runs before each test. + +In common with other xUnit libraries, Nodeunit provides a setUp (and tearDown) function which will be called before each test. + +{% highlight coffeescript %} + +Calculator = require '../calculator' + +exports.CalculatorTest = + + setUp: (callback) -> + @calculator = new Calculator + callback() + + 'test can add two positive numbers': (test) -> + result = @calculator.add 2, 3 + test.equal(result, 5) + test.done() + + 'test can handle negative number addition': (test) -> + result = @calculator.add -10, 5 + test.equal(result, -5) + test.done() + + 'test can subtract two positive numbers': (test) -> + result = @calculator.subtract 10, 6 + test.equal(result, 4) + test.done() + + 'test can handle negative number subtraction': (test) -> + result = @calculator.subtract 4, -6 + test.equal(result, 10) + test.done() + +{% endhighlight %} + +We can rerun the tests and everything should continue to pass. diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..f1f14a7 --- /dev/null +++ b/contributing.md @@ -0,0 +1,30 @@ +--- +layout: default +title: Contributing +--- +# Contributing + +The Cookbook needs your help! + +Here's the Contribution Recipe: + +1. Fork the repository at [https://github.com/coffeescript-cookbook/coffeescript-cookbook.github.com](https://github.com/coffeescript-cookbook/coffeescript-cookbook.github.com) +2. Do awesomeness! +3. Send a pull request to coffeescript-cookbook +4. If we merge your pull request, you get commit access. BAM. Go back to step 2 and stay there as long as you want. + +## Wanted Recipes + +Want to help, but don't know where to start? Want a recipe, but don't know how to write it? Check out the [Wanted Recipes]({{ site.baseurl }}/wanted-recipes) page! + +## Authors + +Write recipes! Fork the repository, author some pages, and send us a pull request. For more information read the [Author's Guide]({{ site.baseurl }}/authors-guide). + +## Developers + +Improve the code! Fork the repository, extend or improve the site coded, and send a pull request. For more information read the [Developer's Guide]({{ site.baseurl }}/developers-guide). + +## Designers + +Make this site look pretty! Fork the repository, extend, improve or update the design, and send a pull request. For more information read the [Designer's Guide]({{ site.baseurl }}/designers-guide). diff --git a/contributing.textile b/contributing.textile deleted file mode 100644 index 19ba91d..0000000 --- a/contributing.textile +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: default -title: Contributing ---- -h1. Contributing - -The Cookbook needs your help! - -Here's the Contribution Recipe: - -# Fork the repository at http://github.com/coffeescript-cookbook/coffeescript-cookbook.github.com -# Do awesomeness! -# Send a pull request to coffeescript-cookbook -# If we merge your pull request, you get commit access. BAM. Go back to step 2 and stay there as long as you want. - - -h2. Wanted Recipes - -Want to help, but don't know where to start? Want a recipe, but don't know how to write it? Check out the Wanted Recipes page! - -h2. Authors - -Write recipes! Fork the repository, author some pages, and send us a pull request. For more information read the author's guide. - -h2. Developers - -Improve the code! Fork the repository, extend or improve the site coded, and send a pull request. For more information read the developer's guide. - -h2. Designers - -Make this site look pretty! Fork the repository, extend, improve or update the design, and send a pull request. For more information read the designer's guide. - diff --git a/css/default.css b/css/default.css index d12653a..fb906a4 100644 --- a/css/default.css +++ b/css/default.css @@ -61,6 +61,36 @@ ul, ol { margin-bottom: 22px; } +nav ul { + list-style:none; +} + +nav ul li { + border-left:1px solid rgba(0, 0, 0, 0.2); + display:inline-block; + padding:0 10px; +} + +nav ul li:first-child { + border:none; +} + +nav ol { + list-style:none; +} + +nav ol li { + display:inline-block; +} + +nav ol.breadcrumbs li:before { + content: '\000bb\000a0'; /* Insert »  between list items. */ +} + +nav ol.breadcrumbs li:first-child:before { + content: ''; +} + /* Typography */ body { @@ -149,4 +179,4 @@ h3 { font-size: 18px; line-height: 22px; } .highlight .vc { color: #19177C } /* Name.Variable.Class */ .highlight .vg { color: #19177C } /* Name.Variable.Global */ .highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/css/solarized-light.css b/css/solarized-light.css new file mode 100644 index 0000000..610e1d4 --- /dev/null +++ b/css/solarized-light.css @@ -0,0 +1,90 @@ +/* +base03: #002b36; +base02: #073642; +base01: #586e75; +base00: #657b83; +base0: #839496; +base1: #93a1a1; +base2: #eee8d5; +base3: #fdf6e3; +yellow: #b58900; +orange: #cb4b16; +red: #dc322f; +magenta: #d33682; +violet: #6c71c4; +blue: #268bd2; +cyan: #2aa198; +green: #859900; +*/ + +.highlight { background: #fdf6e3; color: #657b83; line-height: 1em; font-size: 13px; } +.highlight .lineno { background-color: #eee8d5; color: #93a1a1; padding-left: .5em; padding-right: .5em; } +.highlight .hll { background-color: #eee8d5; } /* Don't know what this is... */ +.highlight .c { color: #93a1a1; font-style: italic; } /* Comment */ +.highlight .err { color: #dc322f; } /* Error */ +.highlight .g { color: #839496; } /* Generic */ +.highlight .k { color: #859900; } /* Keyword */ +.highlight .l { color: #839496; } /* Literal */ +.highlight .n { color: #839496; } /* Name */ +.highlight .o { color: #859900; } /* Operator */ +.highlight .x { color: #839496; } /* Other */ +.highlight .p { color: #839496; } /* Punctuation */ +.highlight .cm { color: #93a1a1; font-style: italic; } /* Comment.Multiline */ +.highlight .cp { color: #93a1a1; font-style: italic; } /* Comment.Preproc */ +.highlight .c1 { color: #93a1a1; font-style: italic; } /* Comment.Single */ +.highlight .cs { color: #93a1a1; font-style: italic; } /* Comment.Special */ +.highlight .gd { color: #dc322f; } /* Generic.Deleted */ +.highlight .ge { color: #839496; font-style: italic; } /* Generic.Emph */ +.highlight .gr { color: #839496; } /* Generic.Error */ +.highlight .gh { color: #839496; font-weight: bold; } /* Generic.Heading */ +.highlight .gi { color: #859900; } /* Generic.Inserted */ +.highlight .go { color: #839496; } /* Generic.Output */ +.highlight .gp { color: #839496; font-weight: bold; } /* Generic.Prompt */ +.highlight .gs { color: #839496; font-weight: bold; } /* Generic.Strong */ +.highlight .gu { color: #839496; font-weight: bold; } /* Generic.Subheading */ +.highlight .gt { color: #839496; } /* Generic.Traceback */ +.highlight .kc { color: #839496; } /* Keyword.Constant */ +.highlight .kd { color: #839496; } /* Keyword.Declaration */ +.highlight .kn { color: #839496; } /* Keyword.Namespace */ +.highlight .kp { color: #2aa198; } /* Keyword.Pseudo */ +.highlight .kr { color: #839496; } /* Keyword.Reserved */ +.highlight .kt { color: #839496; } /* Keyword.Type */ +.highlight .ld { color: #839496; } /* Literal.Date */ +.highlight .m { color: #839496; } /* Literal.Number */ +.highlight .s { color: #2aa198; } /* Literal.String */ +.highlight .na { color: #839496; } /* Name.Attribute */ +.highlight .nb { color: #cb4b16; } /* Name.Builtin */ +.highlight .nc { color: #839496; } /* Name.Class */ +.highlight .no { color: #b58900; } /* Name.Constant */ +.highlight .nd { color: #839496; } /* Name.Decorator */ +.highlight .ni { color: #839496; } /* Name.Entity */ +.highlight .ne { color: #839496; } /* Name.Exception */ +.highlight .nf { color: #268bd2; } /* Name.Function */ +.highlight .nl { color: #839496; } /* Name.Label */ +.highlight .nn { color: #cb4b16; } /* Name.Namespace */ +.highlight .nx { color: #839496; } /* Name.Other */ +.highlight .py { color: #839496; } /* Name.Property */ +.highlight .nt { color: #839496; } /* Name.Tag */ +.highlight .nv { color: #839496; } /* Name.Variable */ +.highlight .ow { color: #839496; } /* Operator.Word */ +.highlight .w { color: #839496; } /* Text.Whitespace */ +.highlight .mf { color: #2aa198; } /* Literal.Number.Float */ +.highlight .mh { color: #2aa198; } /* Literal.Number.Hex */ +.highlight .mi { color: #2aa198; } /* Literal.Number.Integer */ +.highlight .mo { color: #2aa198; } /* Literal.Number.Oct */ +.highlight .sb { color: #2aa198; } /* Literal.String.Backtick */ +.highlight .sc { color: #2aa198; } /* Literal.String.Char */ +.highlight .sd { color: #2aa198; } /* Literal.String.Doc */ +.highlight .s2 { color: #2aa198; } /* Literal.String.Double */ +.highlight .se { color: #dc322f; } /* Literal.String.Escape */ +.highlight .sh { color: #2aa198; } /* Literal.String.Heredoc */ +.highlight .si { color: #2aa198; } /* Literal.String.Interpol */ +.highlight .sx { color: #2aa198; } /* Literal.String.Other */ +.highlight .sr { color: #2aa198; } /* Literal.String.Regex */ +.highlight .s1 { color: #2aa198; } /* Literal.String.Single */ +.highlight .ss { color: #2aa198; } /* Literal.String.Symbol */ +.highlight .bp { color: #839496; } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #839496; } /* Name.Variable.Class */ +.highlight .vg { color: #839496; } /* Name.Variable.Global */ +.highlight .vi { color: #839496; } /* Name.Variable.Instance */ +.highlight .il { color: #839496; } /* Literal.Number.Integer.Long */ diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..9caf5c6 --- /dev/null +++ b/css/style.css @@ -0,0 +1,945 @@ +/* + Author: Amsul - http://github.com/amsul + Description: CoffeeScript Cookbook stylings + Version: 1.0 + Last Updated: 11 September, 2012 +*/ + + + +/* + Font-face +======================================================================== */ + +@font-face { + font-family: 'Entypo'; + src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'), + url(http://github.com/fonts/entypo.ttf) format('truetype'), + url(http://github.com/fonts/entypo.svg#EntypoRegular) format('svg'); +} + + + + + + +/* ======================================================================== + + Cross-browser normalize (from HTML5 Boilerplate v4.0) + +======================================================================== */ + + + +/* + HTML5 element display (old browsers) +======================================================================== */ + +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; height: 0; } +[hidden] { display: none; } + + + +/* + Base +======================================================================== */ + +html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + +html, button, input, select, textarea { font-family: "Lucida Grande", "Lucida Sans Unicode", sans-serif; font-weight: 200; color: #424242; } + +body { margin: 0; } + + +::-moz-selection { background: #b3d4fc; text-shadow: none; } +::selection { background: #b3d4fc; text-shadow: none; } + + + + +/* + Links +======================================================================== */ + +a:hover, +a:active { + outline: 0; +} + +a:focus { outline: thin dotted; } + + + + +/* + Typography +======================================================================== */ + +h1 { + font-size: 2em; + margin: .67em 0; +} + +h2 { + font-size: 1.5em; + margin: .83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: .83em; + margin: 1.67em 0; +} + +h6 { + font-size: .75em; + margin: 2.33em 0; +} + +abbr[title] { border-bottom: 1px dotted; } + +b, strong { font-weight: bold; } + +blockquote { margin: 1em 40px; } + +dfn { font-style: italic; } + +mark { background: #ff0; color: #000; } + +p, pre { margin: 1em 0; } + +pre, code, kbd, samp { font-family: 'Courier', 'Monaco', monospace, serif; font-size: 1em; } + +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } + +q { quotes: none; } +q:before, q:after { content: ""; content: none; } + +small { font-size: 80%; } + +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -.5em; } +sub { bottom: -.25em; } + + + + +/* + Lists +======================================================================== */ + +dl, menu, ol, ul { margin: 1em 0; } + +dd { margin: 0 0 0 40px; } + +menu, ol, ul { padding: 0 0 0 40px; } + +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + + + + +/* + Embedded content +======================================================================== */ + +object, embed, img { max-width: 100%; } + +img { border: 0; -ms-interpolation-mode: bicubic; } + +svg:not(:root) { overflow: hidden; } + + + +/* + Figures +======================================================================== */ + +figure { margin: 0; } + + + + + +/* + Forms +======================================================================== */ + +form { margin: 0; } + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: .35em .625em .75em; +} + +legend { border: 0; padding: 0; white-space: normal; } + +label:hover { cursor: default; } + + +button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; } + +button, +html input[type=button], +input[type=reset], +input[type=submit] { + -webkit-appearance: button; + cursor: pointer; +} + +button[disabled], input[disabled] { cursor: default; } + +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } + +input[type=checkbox], input[type=radio] { + cursor: pointer; + padding: 0; +} + +input[type=search] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +textarea { overflow: auto; vertical-align: top; resize: vertical; } + + + + +/* + Tables +======================================================================== */ + +table { + border-spacing: 0; + border-collapse: collapse; +} + + + + + + + + + + +/* + ======================================================================== + ======================================================================== + ======================================================================== +*/ + + + + + + + + + + + + +/* ======================================================================== + + Primary styling + +======================================================================== */ + + + +/* + Base +======================================================================== */ + +body { + font-size: 16px; + line-height: 1.5; +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; +} + +img { + vertical-align: middle; + height: auto; + max-width: 100%; +} + +a { + cursor: pointer; + text-decoration: underline; + color: #27ace3; +} + + + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: bold; + line-height: 1.2; +} + +h1:first-child, +h2:first-child, +h3:first-child, +h4:first-child, +h5:first-child, +h6:first-child, +p:first-child { + margin-top: 0; +} + +h1 { + font-size: 32px; + margin-bottom: .66em; +} + +h2 { + font-size: 24px; + margin: 1.33em 0 .66em; +} + +h3 { + font-size: 20px; + margin-bottom: .25em; +} + +h4 { + font-size: 18px; + margin-bottom: .75em; +} + +h5 { + font-size: 16px; + margin: 1.5em 0 .75em; +} + +h6 { + font-size: 14px; + margin: 1.5em 0 .25em; +} + + + +/* + Lists +======================================================================== */ + +ul:not(.plain-list) li { + margin-bottom: 5px; +} + +ul.plain-list, +.pseudo-list { + list-style: none; + margin: 0; + padding: 0; +} + +.pseudo-list li { + position: relative; + padding-left: 14px; +} + +.pseudo-list li:before { + content: "\2013"; + position: absolute; + margin-left: -12px; +} + +.inline-list li { + display: inline-block; + margin-right: 20px; +} + + + + +/* + Tables +======================================================================== */ + +table { + background: #fff; + border: 1px solid #ccc; + line-height: 1.5; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +th, +td { + vertical-align: top; + padding: 6px 12px; + border-top: 1px solid #e2e2e2; + border-right: 1px solid #e2e2e2; +} +th:last-child, +td:last-child { + border-right: 0; +} + +thead th { + background: #f5f5f5; + border-top: 0; +} +thead th:first-child { + -webkit-border-radius: 5px 0 0 0; + -moz-border-radius: 5px 0 0 0; + border-radius: 5px 0 0 0; +} +thead th:last-child { + -webkit-border-radius: 0 5px 0 0; + -moz-border-radius: 0 5px 0 0; + border-radius: 0 5px 0 0; +} + +tbody th, +tbody td { + font-size: 12px; +} + +tbody tr:nth-child(even) th, +tbody tr:nth-child(even) td { + background-color: #f9f9f9; +} + +tbody tr:last-child th:first-child, +tbody tr:last-child td:first-child { + -webkit-border-radius: 0 0 0 5px; + -moz-border-radius: 0 0 0 5px; + border-radius: 0 0 0 5px; +} + +tbody tr:last-child th:last-child, +tbody tr:last-child td:last-child { + -webkit-border-radius: 0 0 5px 0; + -moz-border-radius: 0 0 5px 0; + border-radius: 0 0 5px 0; +} + + + + +/* + Form elements +======================================================================== */ + +fieldset { + border: 0; + margin: 0 0 14px; + padding: 0; +} + + +input[type=checkbox], +input[type=radio] { + margin-right: 5px; +} + +label:hover { + color: #27ace3; +} + + + + +/* + Code blocks +======================================================================== */ + +pre { display: table; width: 100%; margin: 0; padding: 24px; } + +pre > code { display: block; } + + + + + + + + + + +/* + Site structure +======================================================================== */ + +.container { + margin-left: 444px; + min-width: 666px; + max-width: 1000px; + padding: 40px; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.header a, +.navigation a { + text-decoration: none; +} + +.navigation:not(.pages-nav) a:hover { + text-decoration: underline; +} + + +/** Header **/ + +.header { + margin-bottom: 32px; +} + +.header .entypo { + font-size: 82px; + line-height: 0.2; + margin-right: 10px; + color: #27ace3; + position: relative; + top: -2px; +} + +.header .title { + background: url(http://github.com/images/title.png) no-repeat 0 0 transparent; + width: 421px; + height: 33px; + display: inline-block; + vertical-align: top; +} + + +/** Footer **/ + +.footer { + border-top: 1px solid #eee; + padding-top: 42px; + font-size: 12px; + color: #888; +} + + + +/** Content blocks and sections **/ + +.content { + margin-right: 250px; +} + +.content-block { + margin-bottom: 100px; +} + +.content-full { + width: auto; +} + + + + + + + + +/* + Sidebar +======================================================================== */ + +#sidebar { + background: #f0f5f6; + border-right: 1px solid #e0e5e6; + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 444px; + padding: 40px 20px 40px 40px; + font-size: 12px; + overflow-y: scroll; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.sidebar-block { + margin-bottom: 54px; +} + +.sidebar-block .navigation a { + color: #424242; +} + +.block-short { + margin-bottom: 24px; +} + + +.sidebar-subtitle { + font-size: 14px; + margin-bottom: 6px; +} + + +/** Pages nav **/ + +.pages-nav .nav-link { + text-transform: uppercase; +} +.pages-nav .nav-link:hover { + text-decoration: none; + color: #27ace3; +} + +.pages-nav .entypo { + font-size: 36px; + vertical-align: baseline; + position: relative; + top: 4px; + margin-right: 8px; + line-height: .5; +} + +.home-nav .nav-link { + text-transform: none; +} +.home-nav .entypo { + margin-right: 4px; +} + + + +/** Chapters index **/ + +.chapters-list { + margin: 0; +} + +.chapter-title { + color: #424242 +} + + +.recipes-list { + line-height: 1.25; + margin-bottom: 24px; +} + +.recipes-list .list-item { + color: #888; +} + + + + + + + +/* + Recipes page +======================================================================== */ + +.recipe-content h1 { + font-size: 28px; +} + +.recipe-content h2 { + font-size: 20px; + margin-bottom: .5em; +} + +.recipe-content p { + margin-top: 0; +} + + + + + +/* + Active page +======================================================================== */ + +.page-active { + position: relative; + background: #fff; + padding-right: 24px; + + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +.page-active:after { + content: 'M'; + position: absolute; + top: 4px; + right: 0; + font-family: 'Entypo'; + font-size: 36px; + line-height: 6px; + color: #666; +} + +.recipes-list .page-active:after { + top: 1px; +} + + + + + + + + + +/* + ======================================================================== + ======================================================================== + ======================================================================== +*/ + + + + + + + + + + + + + +/* + Non-semantic helper classes +======================================================================== */ + +.dimmed, a.dimmed { color: #888; } + +.warning { color: red; } + + +.hidden { display: none; visibility: hidden; } +.invisible { visibility: hidden; } + + +.thin { font-weight: 100; } + +.entypo { + font-family: 'Entypo'; + line-height: 1; + text-transform: initial; +} + + +.alignleft { float: left; } +.alignright { float: right; } + + +.clear:before, .clear:after { content: " "; display: table; } +.clear:after { clear: both; } +.clear { *zoom: 1; } + + +.ir { + background-color: transparent; + border: 0; + overflow: hidden; + *text-indent: -9999px; +} +.ir:before { + content: ""; + display: block; + width: 0; + height: 100%; +} + + + + + + + + + +/* + Code highlighting +======================================================================== */ + +/* +base03: #002b36; +base02: #073642; +base01: #586e75; +base00: #657b83; +base0: #839496; +base1: #93a1a1; +base2: #eee8d5; +base3: #fdf6e3; +yellow: #b58900; +orange: #cb4b16; +red: #dc322f; +magenta: #d33682; +violet: #6c71c4; +blue: #268bd2; +cyan: #2aa198; +green: #859900; +*/ + +.highlight { + background: #f8f8f8; + font-size: 14px; + overflow-x: scroll; + margin-right: -250px; + margin-bottom: 36px; +} +.highlight .hll { background-color: #ffffcc } +.highlight .c { color: #408080; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ +.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #7D9029 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #A0A000 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ + + + + +/* ========================================================================== + + Media Queries + +========================================================================== */ + +@media (max-width: 1110px) { + + body:after { + content: 'narrow'; + display: none; + } + + #sidebar { + position: absolute; + bottom: auto; + border-bottom: 1px solid #e0e5e6; + } + +} + + + + + + + +/* + Print media queries +======================================================================== */ + +@media print { + * { + background: transparent !important; + color: #000 !important; + box-shadow:none !important; + text-shadow: none !important; + } + a, a:visited { text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } + tr, img { page-break-inside: avoid; } + img { max-width: 100% !important; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3 { page-break-after: avoid; } +} + + diff --git a/designers-guide.md b/designers-guide.md new file mode 100644 index 0000000..df6381c --- /dev/null +++ b/designers-guide.md @@ -0,0 +1,14 @@ +--- +layout: default +title: Designer's Guide +--- + +# Designer's Guide + +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 started! + +If you have been waiting for your chance to literally _write the designer's guide_ for a website that is also a cookbook about CoffeeScript... + +...welcome home. + +The default color scheme is based on [Solarized](http://ethanschoonover.com/solarized) by Ethan Schoonover. diff --git a/designers-guide.textile b/designers-guide.textile deleted file mode 100644 index 80d0808..0000000 --- a/designers-guide.textile +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: default -title: Designer's Guide ---- - -h1. Designer's Guide - -Start with the Developer's Guide to get a test version of the cookbook up and running on your machine, and get started! - -First designer to actually DO some design gets to write this page! - -If you have been waiting for your chance to literally _write the designer's guide_ for a website that is also a cookbook about CoffeeScript... - -...welcome home. - diff --git a/developers-guide.md b/developers-guide.md new file mode 100644 index 0000000..9e39f2f --- /dev/null +++ b/developers-guide.md @@ -0,0 +1,66 @@ +--- +layout: default +title: Developer's Guide +--- + +# Developer's Guide + +_Please help out by updating this page_ + +### Operating System + +It works on Mac OS X. Probably works without any changes or issues on Linux. +A masochist could probably get it working on Windows. + +## Installation + +### Clone the repo + +{% highlight bash %} +git clone git://github.com/coffeescript-cookbook/coffeescript-cookbook.github.com.git +{% endhighlight %} + +### Ruby environment + +You probably want to have [RVM](http://rvm.io/) installed. + +The project includes a `.ruby-version` file locked to +*1.9.3* since that is what Github Pages are currently using. + +There is also a `.ruby-gemset` that is set to *coffeescript-cookbook* + +### Required dependencies + +We are using [Bundler](http://bundler.io/) to install the required Ruby dependencies. + +{% highlight bash %} +bundle install +{% endhighlight %} + +#### Install pygments + +You'll need python installed for this. +Macs and most Linuces come with it preinstalled. + +{% highlight bash %} +easy_install pygments # for syntax highlighting +{% endhighlight %} + +## Building and Viewing the Website + +Open a terminal window, cd into the project folder and run `foreman start` from the project root. + +{% highlight bash %} +foreman start +{% endhighlight %} + +Leave this window running while you work. +Any time you change a file, jekyll will rerender it into the `_site` folder. + +Open a browser and visit and you should see the site. + +## Minutiae and Other Trivialities + +Jekyll can take a second or two to catch up when you save a file. +If you edit a file and don't see the changes in your browser, give it a second or two and try again. +As long as it prints `Successfully generated site` you should be alright. diff --git a/developers-guide.textile b/developers-guide.textile deleted file mode 100644 index 6347369..0000000 --- a/developers-guide.textile +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: default -title: Developer's Guide ---- - -h1. Developer's Guide - -_Please help out by updating this page_ - -h3. Operating System - -It works on Mac OSX. Probably works without any changes or issues on linux. A masochist could probably get it working on Windows. - -h2. Installation - -h3. Clone the repo - -{% highlight bash %} -git clone git://github.com/coffeescript-cookbook/coffeescript-cookbook.github.com.git -{% endhighlight %} - -h3. Create a Ruby Gemset - -Optional, but highly recommended. - -{% highlight bash %} -$ rvm gemset create jekyll -$ echo 'rvm gemset use jekyll' >> .rvmrc -{% endhighlight %} - -h3. Install Required Gems - -{% highlight bash %} -gem install jekyll # needed for testing building the site -gem install RedCloth # needed for .textile rendering -gem install serve # needed for resolving .html files w/o extension -gem install thin # optional; more efficient webserver than Webrick but not strictly necessary -{% endhighlight %} - -h3. Install pygments - -You'll need python installed for this. Macs and most linuces come with it preinstalled. - -{% highlight bash %} -easy_install pygments # for syntax highlighting -{% endhighlight %} - -h2. Building and Viewing the Website - -h3. Run jekyll - -Open a terminal window, cd into the project folder and run jekyll from the project root. - -{% highlight bash %} -jekyll --auto -{% endhighlight %} - -Leave this window running while you work. Any time you change a file, jekyll will rerender it into the _site folder. - -h3. Run serve - -Open another terminal window, cd into the project folder, then cd into the _site subfolder, and run - -{% highlight bash %} -serve -{% endhighlight %} - -This will start a webserver in the _site folder. Open a browser and visit http://localhost:4000/ and you should see the site. - -h2. Minituiae and Other Trivialities - -jekyll can take a second or two to catch up when you save a file. If you edit a file and don't see the changes in your browser, give it a second or two and try again. - - diff --git a/fonts/entypo.svg b/fonts/entypo.svg new file mode 100644 index 0000000..c24d92a --- /dev/null +++ b/fonts/entypo.svg @@ -0,0 +1,198 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Creative Commons CC BYSA 2012 +Designer : Daniel Bruce +Foundry : Daniel Bruce +Foundry URL : wwwdanielbrucese + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/entypo.ttf b/fonts/entypo.ttf new file mode 100644 index 0000000..89b7a19 Binary files /dev/null and b/fonts/entypo.ttf differ diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..9f242eb Binary files /dev/null and b/images/favicon.ico differ diff --git a/images/title.png b/images/title.png new file mode 100644 index 0000000..dd526ca Binary files /dev/null and b/images/title.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..cd3f572 --- /dev/null +++ b/index.html @@ -0,0 +1,11 @@ +--- +layout: default +title: Home +--- + + +

    Welcome to the CoffeeScript Cookbook!

    +

    CoffeeScript recipes for the community by the community. Head over to the Contribute page and see what you can do to help out!

    + + + diff --git a/index.textile b/index.textile deleted file mode 100644 index cfe32fd..0000000 --- a/index.textile +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: default -title: Home ---- - -h1. Welcome - -Welcome to the CoffeeScript Cookbook! It's a bit of a mess right now, and VERY lonely and empty in these pages. That's where _you_ come in. Head over to the Contributing page and see what you can do to help out! - -Contributions URGENTLY needed NOW: - -* +NEW!+ Check out the Wanted Recipes page! -* +NEW!+ For authors: we've added a Recipe Template to help get you started. -* Authors: Write recipes! Here's how. -* Developers: Expand the Cookbook site! Here's how. -* Designers: Make this place look like not-crap! Here's how. - diff --git a/js/scripts.js b/js/scripts.js new file mode 100644 index 0000000..42e30b8 --- /dev/null +++ b/js/scripts.js @@ -0,0 +1,33 @@ + +/*jshint browser: true, devel: true, debug: true */ + + +(function( window, document, undefined ) { + + var activePage, position, sidebar, + narrowScreen = ( window.getComputedStyle( document.body, ':after' ).getPropertyValue( 'content' ) === 'narrow' ) ? true : false + + + // if it's not the index page and not a narrow screen + if ( window.location.pathname.length && !narrowScreen ) { + + // get the sidebar + sidebar = document.getElementById( 'sidebar' ) + + // query the dom for the active page + activePage = document.getElementsByClassName( 'page-active' ) + + // if an active page was found + if ( activePage.length ) { + + // get the offset position and give a padding of 80px + position = activePage[0].offsetTop - 80 + + // set the scroll position of the sidebar to the new position + sidebar.scrollTop = position + } + } + + +})( window, document ) + diff --git a/js/tumbleweed.js b/js/tumbleweed.js deleted file mode 100644 index 2b933b8..0000000 --- a/js/tumbleweed.js +++ /dev/null @@ -1 +0,0 @@ -// tumbleweed.js - nothing to see here, podner. diff --git a/license.md b/license.md new file mode 100644 index 0000000..2c473db --- /dev/null +++ b/license.md @@ -0,0 +1,8 @@ +--- +layout: default +title: License +--- + +coffeescript-cookbook.github.io is licensed under the [Creative Commons Attribution 3.0 Unported (CC BY 3.0)](http://creativecommons.org/licenses/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. + +[LICENSE]({{ site.baseurl }}/LICENSE-CC-BY) diff --git a/license.textile b/license.textile deleted file mode 100644 index d69c019..0000000 --- a/license.textile +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: default -title: License ---- - -coffeescriptcookbook.com is licensed 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. - -LICENSE - diff --git a/recipe-template.textile b/recipe-template.md similarity index 51% rename from recipe-template.textile rename to recipe-template.md index 8fab668..1c53641 100644 --- a/recipe-template.textile +++ b/recipe-template.md @@ -3,28 +3,25 @@ layout: default title: Recipe Template --- -h2. Sample recipe template +## Sample recipe template -Create a new *my_recipe.textile* file and use this text as a start. +Create a new `my-recipe.md` file and use this text as a start. -
    +{% highlight text %}
     ---
     layout: recipe
     title: Title of The Recipe
     chapter: Chapter Name
     ---
    -
    -h2. Problem
    +## Problem
     
     You have a problem.
     
    -
    -h2. Solution
    +## Solution
     
     Do this about it.
     
    -
    -h2. Discussion
    +## Discussion
     
     Here's why.
    -
    +{% endhighlight %} diff --git a/terms-of-use.textile b/terms-of-use.md similarity index 97% rename from terms-of-use.textile rename to terms-of-use.md index c41bd4e..ec07bc7 100644 --- a/terms-of-use.textile +++ b/terms-of-use.md @@ -3,4 +3,3 @@ layout: default title: Terms of Use --- - diff --git a/wanted-recipes.md b/wanted-recipes.md new file mode 100644 index 0000000..4a38f1e --- /dev/null +++ b/wanted-recipes.md @@ -0,0 +1,107 @@ +--- +layout: default +title: Wanted Recipes +--- +# Wanted Recipes + +Here's a list of recipes we think we need. Pick one, implement it, and remove it from the page. Alternately, add a quick note here for a recipe you'd like to see so someone else can add it. + +In the notes below, "JS" means the recipe is just a simple pass-through to an existing JavaScript method. + +## Syntax + +* Ensuring variables are closed over # with "do" + +## Strings + +* HTML methods # JS .sup(), .sub(), .blink(), .link(url), etc. May not exist in your JS implementation! +* substr +{% highlight coffeescript %} +str.substr(x,y) === str[x..x+y-1] === str[x...x+y] +{% endhighlight %} +* substring +{% highlight coffeescript %} +str.substring(x,y) === str.slice(x,y) === str[x..y-1] === str[x...y] +{% endhighlight %} +* Replacing substrings + +## Arrays + +* Testing every element in an array +{% highlight coffeescript %} +evens = (x for x in [0..10] by 2) +even = (x) -> x % 2 == 0 +evens.every even +# => true +{% endhighlight %} +* Detecting presence of matching items in an array +{% highlight coffeescript %} +[1..10].some (x) -> x % 2 == 0 # => true +{% endhighlight %} + +## Math + +* square root # JS Math.sqrt +* Constants # JS Math.PI, Math.E +* floor, ceil, round # JS Math.floor, Math.ceil, Math.round +* Raising a number to a power # JS Math.pow(x, y) +* Logarithms # Math.log +* Finding the base-n log # Math.log(num) / Math.log(base) +* Exponents # Math.exp +* Check if a credit card is valid (checksum, Luhn algorithm) + +## Functions + +* Nested functions + +{% highlight coffeescript %} +hypotenuse = (a, b) -> + square = (x) -> x * x + Math.sqrt(square(a) + square(b)) + +console.log hypotenuse 3, 4 +# => 5 + +square 5 +# ReferenceError: square is not defined +{% endhighlight %} + +* Optional Arguments # use arg? to detect presence: if arg=0, arg? == true + +{% highlight coffeescript %} +foo = (a, b=42, c) -> if c? then a*b*c else a*b +[Function] +foo 6 +# => 252 +foo 1, 2 +# => 2 +foo 1, 2, 3 +# => 6 +{% endhighlight %} + +## Design patterns + +* Creational Patterns + * Abstract Factory + * Prototype + +* Structural Patterns + * Adapter + * Composite + * Facade + * Flyweight + * Proxy + +* Behavioral Patterns + * Chain of Responsibility + * Iterator + * Mediator + * Observer + * State + * Template Method + * Visitor + +## Databases + +* Couch access +* MySQL/PostgreSQL access diff --git a/wanted-recipes.textile b/wanted-recipes.textile deleted file mode 100644 index 994cf6b..0000000 --- a/wanted-recipes.textile +++ /dev/null @@ -1,148 +0,0 @@ ---- -layout: default -title: Wanted Recipes ---- -h1. Wanted Recipes - -Here's a list of recipes we think we need. Pick one, implement it, and remove it from the page. Alternately, add a quick note here for a recipe you'd like to see so someone else can add it. - -In the notes below, "JS" means the recipe is just a simple passthrough to an existing JavaScript method. - -h2. Syntax - -* Ensuring variables are closed over # with "do" - -h2. Objects - -h2. Strings - -* HTML methods # JS .sup(), .sub(), .blink(), .link(url), etc. May not exist in your JS impl! -* substr # str.substr(x,y) === str[x..x+y-1] === str[x...x+y] -* substring # str.substring(x,y) === str.slice(x,y) === str[x..y-1] === str[x...y] -* Uppercasing a string # JS toUpperCase() -* Lowercasing a string # JS toLowerCase() -* Replacing substrings -* Trimming whitespace from the end of a string - -h2. Arrays - -* Reducing arrays to values (ruby inject) with reduce # [1..10].reduce (a,b) -> a+b # 55 -* Reducing arrays in reverse order # JS reduceRight -{% highlight coffeescript %} -["example", "contrived ", "pretty ", "a ", "is ", "here "].reduceRight (x,y) -> x+y -# => 'here is a pretty contrived example' -{% endhighlight %} -* Testing every element in an array -{% highlight coffeescript %} -evens = (x for x in [0..10] by 2) -even = (x) -> x % 2 == 0 -evens.every even -# => true -{% endhighlight %} -* Filtering arrays # [1..10.filter (x) -> x % 2 == 0 # => [ 2, 4, 6, 8, 10 ] -* Detecting presence of matching items in an array # [1..10].some (x) -> x % 2 == 0 # => true -* Processing an array item by item # [10..1].forEach (x) -> console.log x # => nothing;, but a countdown is displayed on the console -* Creating a string from an array -* Replace all duplicates of an array - -h2. Dates and Times - -* Calculating the phase of the moon -* Holidays: Calculating Easter -* Holidays: Calculating US Thanksgiving -* Holidays: Calculating CA Thanksgiving -* Number of days between two dates - -h2. Math - -* square root # JS Math.sqrt -* Constants # JS Math.PI, Math.E -* floor, ceil, round # JS Math.floor, Math.ceil, Math.round -* Raising a number to a power # JS Math.pow(x, y) -* Logarithms # Math.log -* Finding the base-n log # Math.log(num) / Math.log(base) -* Exponents # Math.exp -* Check if a creditcard is valid (checksum, Luhn algorithm) - -h2. Functions - -* Nested functions - -{% highlight coffeescript %} -hypotenuse = (a, b) -> - square = (x) -> x * x - Math.sqrt(square(a) + square(b)) - -console.log hypotenuse 3, 4 -# => 5 - -square 5 -# ReferenceError: square is not defined -{% endhighlight %} - -* Optional Arguments # use arg? to detect presence: if arg=0, arg? == true - -{% highlight coffeescript %} -foo = (a, b=42, c) -> if c? then a*b*c else a*b -[Function] -foo 6 -# => 252 -foo 1, 2 -# => 2 -foo 1, 2, 3 -# => 6 -{% endhighlight %} - -* Variable arguments with splats -* Recursion of unnamed functions with arguments.callee - -{% highlight coffeescript %} -console.log [1,2,3,4,5].map (x) -> - if x <= 1 then return 1 - return x * arguments.callee(x-1) -# => [1, 2, 6, 24, 120 ] -{% endhighlight %} - -h2. jQuery - -h2. Regular Expressions - -* Searching for substrings # "foo bar baz".match(/ba./) # => [ 'bar', index: 4, input: 'foo bar baz' ] -* Searching for substrings # "foo bar baz".search(/ba./) # => 4 -* Replacing substrings # "foo bar baz".replace( /ba./, 'foo') # => "foo foo baz" -* Replace HTML tags with named HTML entities #
    => <br/> - -h2. Networking - -* Basic HTTP server -* Basic HTTP client - -h2. AJAX - -* Getting data from a remote server # using raw XHTTPRequest instead of jQuery's $.ajax - -h2. Design patterns - -* Creational Patterns -** Abstract Factory -** Prototype -** Singleton - -* Structural Patterns -** Adapter -** Composite -** Facade -** Flyweight -** Proxy - -* Behavioral Patterns -** Chain of Responsibility -** Command -** Iterator -** Mediator -** Observer -** State -** Template Method -** Visitor - -