├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── javascripts │ │ ├── application.js.coffee │ │ ├── foo.js │ │ ├── main.js.coffee │ │ ├── models │ │ │ └── todo.coffee │ │ └── views │ │ │ ├── helpers.coffee │ │ │ └── todo_view.coffee │ └── stylesheets │ │ ├── .gitkeep │ │ ├── application.css │ │ ├── scaffold.css │ │ └── todos.css ├── controllers │ ├── application_controller.rb │ └── todos_controller.rb ├── helpers │ └── application_helper.rb ├── models │ └── todo.rb └── views │ ├── layouts │ └── application.html.haml │ └── todos │ └── index.html.haml ├── config.ru ├── config ├── application.rb ├── assets.yml ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backbone.rb │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── secret_token.rb │ └── session_store.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ └── 20101104122721_create_todos.rb ├── schema.rb └── seeds.rb ├── doc └── README_FOR_APP ├── lib └── tasks │ └── .gitkeep ├── public ├── 404.html ├── 422.html ├── 500.html ├── SpecRunner.html ├── favicon.ico ├── images │ └── destroy.png └── robots.txt ├── script └── rails ├── spec ├── assets │ └── javascripts │ │ ├── spec_helper.coffee │ │ ├── specs.js.coffee │ │ └── todo_spec.coffee ├── javascripts │ └── support │ │ ├── jasmine.yml │ │ ├── jasmine_config.rb │ │ └── jasmine_runner.rb ├── models │ └── todo_spec.rb └── spec_helper.rb └── vendor ├── assets ├── javascripts │ ├── backbone.js │ ├── handlebars.js │ ├── jasmine-html.js │ ├── jasmine.js │ ├── jquery.js │ └── underscore.js └── stylesheets │ └── jasmine.css └── plugins └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .livereload 3 | .rvmrc 4 | .DS_Store 5 | db/*.sqlite3 6 | log/*.log 7 | tmp/**/* 8 | /public/javascripts/compiled 9 | /spec/javascripts/compiled 10 | .redcar 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '>=3.1.rc' 4 | 5 | gem 'rake', '0.8.7' 6 | 7 | gem 'sqlite3', :require => 'sqlite3' 8 | 9 | gem 'haml' 10 | gem 'jquery-rails' 11 | gem 'json' 12 | gem 'sass' 13 | gem 'coffee-script' 14 | gem 'uglifier' 15 | 16 | group :development, :test do 17 | gem 'awesome_print', :require => 'ap' 18 | gem 'rspec-rails', '~> 2.0' 19 | gem 'jasmine' 20 | end 21 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | actionmailer (3.1.0.rc1) 5 | actionpack (= 3.1.0.rc1) 6 | mail (~> 2.3.0) 7 | actionpack (3.1.0.rc1) 8 | activemodel (= 3.1.0.rc1) 9 | activesupport (= 3.1.0.rc1) 10 | builder (~> 3.0.0) 11 | erubis (~> 2.7.0) 12 | i18n (~> 0.6.0beta1) 13 | rack (~> 1.3.0.beta2) 14 | rack-cache (~> 1.0.1) 15 | rack-mount (~> 0.8.1) 16 | rack-test (~> 0.6.0) 17 | sprockets (~> 2.0.0.beta.5) 18 | tzinfo (~> 0.3.27) 19 | activemodel (3.1.0.rc1) 20 | activesupport (= 3.1.0.rc1) 21 | bcrypt-ruby (~> 2.1.4) 22 | builder (~> 3.0.0) 23 | i18n (~> 0.6.0beta1) 24 | activerecord (3.1.0.rc1) 25 | activemodel (= 3.1.0.rc1) 26 | activesupport (= 3.1.0.rc1) 27 | arel (~> 2.1.1) 28 | tzinfo (~> 0.3.27) 29 | activeresource (3.1.0.rc1) 30 | activemodel (= 3.1.0.rc1) 31 | activesupport (= 3.1.0.rc1) 32 | activesupport (3.1.0.rc1) 33 | multi_json (~> 1.0) 34 | arel (2.1.1) 35 | awesome_print (0.4.0) 36 | bcrypt-ruby (2.1.4) 37 | builder (3.0.0) 38 | childprocess (0.1.9) 39 | ffi (~> 1.0.6) 40 | coffee-script (2.2.0) 41 | coffee-script-source 42 | execjs 43 | coffee-script-source (1.1.1) 44 | diff-lcs (1.1.2) 45 | erubis (2.7.0) 46 | execjs (1.1.2) 47 | multi_json (~> 1.0) 48 | ffi (1.0.9) 49 | haml (3.1.1) 50 | hike (1.0.0) 51 | i18n (0.6.0) 52 | jasmine (1.0.2.1) 53 | json_pure (>= 1.4.3) 54 | rack (>= 1.1) 55 | rspec (>= 1.3.1) 56 | selenium-webdriver (>= 0.1.3) 57 | jquery-rails (1.0.9) 58 | railties (~> 3.0) 59 | thor (~> 0.14) 60 | json (1.5.1) 61 | json_pure (1.5.1) 62 | mail (2.3.0) 63 | i18n (>= 0.4.0) 64 | mime-types (~> 1.16) 65 | treetop (~> 1.4.8) 66 | mime-types (1.16) 67 | multi_json (1.0.3) 68 | polyglot (0.3.1) 69 | rack (1.3.0) 70 | rack-cache (1.0.2) 71 | rack (>= 0.4) 72 | rack-mount (0.8.1) 73 | rack (>= 1.0.0) 74 | rack-ssl (1.3.2) 75 | rack 76 | rack-test (0.6.0) 77 | rack (>= 1.0) 78 | rails (3.1.0.rc1) 79 | actionmailer (= 3.1.0.rc1) 80 | actionpack (= 3.1.0.rc1) 81 | activerecord (= 3.1.0.rc1) 82 | activeresource (= 3.1.0.rc1) 83 | activesupport (= 3.1.0.rc1) 84 | bundler (~> 1.0) 85 | railties (= 3.1.0.rc1) 86 | railties (3.1.0.rc1) 87 | actionpack (= 3.1.0.rc1) 88 | activesupport (= 3.1.0.rc1) 89 | rack-ssl (~> 1.3.2) 90 | rake (>= 0.8.7) 91 | thor (~> 0.14.6) 92 | rake (0.8.7) 93 | rspec (2.6.0) 94 | rspec-core (~> 2.6.0) 95 | rspec-expectations (~> 2.6.0) 96 | rspec-mocks (~> 2.6.0) 97 | rspec-core (2.6.3) 98 | rspec-expectations (2.6.0) 99 | diff-lcs (~> 1.1.2) 100 | rspec-mocks (2.6.0) 101 | rspec-rails (2.6.1) 102 | actionpack (~> 3.0) 103 | activesupport (~> 3.0) 104 | railties (~> 3.0) 105 | rspec (~> 2.6.0) 106 | rubyzip (0.9.4) 107 | sass (3.1.2) 108 | selenium-webdriver (0.2.1) 109 | childprocess (>= 0.1.7) 110 | ffi (>= 1.0.7) 111 | json_pure 112 | rubyzip 113 | sprockets (2.0.0.beta.9) 114 | hike (~> 1.0) 115 | rack (~> 1.0) 116 | tilt (~> 1.1, != 1.3.0) 117 | sqlite3 (1.3.3) 118 | thor (0.14.6) 119 | tilt (1.3.2) 120 | treetop (1.4.9) 121 | polyglot (>= 0.3.1) 122 | tzinfo (0.3.27) 123 | uglifier (0.5.4) 124 | execjs (>= 0.3.0) 125 | multi_json (>= 1.0.2) 126 | 127 | PLATFORMS 128 | ruby 129 | 130 | DEPENDENCIES 131 | awesome_print 132 | coffee-script 133 | haml 134 | jasmine 135 | jquery-rails 136 | json 137 | rails (>= 3.1.rc) 138 | rake (= 0.8.7) 139 | rspec-rails (~> 2.0) 140 | sass 141 | sqlite3 142 | uglifier 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | This is sample Rails application that demonstrates usage of 5 | 6 | * [Backbone.js](http://documentcloud.github.com/backbone/) 7 | * [CoffeeScript](http://jashkenas.github.com/coffee-script/) 8 | * [Jasmine](http://pivotal.github.com/jasmine/) 9 | * Rails 3.1 asset pipeline 10 | 11 | This code is from the demo used during [RailsWayCon 2011](http://railswaycon.com/2011/sessions#session-17838) conference presentation [Rails-like JavaScript using CoffeeScript, Backbone.js and Jasmine](http://www.slideshare.net/rsim/railslike-javascript-using-coffeescript-backbonejs-and-jasmine-8196890). 12 | Source code is based on original [Todos application](http://documentcloud.github.com/backbone/docs/todos.html) by [Jérôme Gravel-Niquet](https://github.com/jeromegn). 13 | 14 | In this fork, I've moved it around and upgraded it to rails 3.1 15 | 16 | Running application 17 | =================== 18 | 19 | Install gems, run migrations and start application with 20 | 21 | bundle install 22 | rake db:migrate 23 | rails server 24 | 25 | And then visit `http://localhost:3000`. 26 | 27 | Running tests 28 | ============= 29 | 30 | Run `rake jasmine` and then visit `http://localhost:8888` to execute Jasmine tests and see results. 31 | 32 | Application source code 33 | ======================= 34 | 35 | Backbone.js models and views written in CoffeeScript are located in `app/assets/javascripts` directory and Jasmine tests are located in `spec/coffeescripts` directory. 36 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | BackboneTodo::Application.load_tasks 8 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js.coffee: -------------------------------------------------------------------------------- 1 | window.TodoApp = {} 2 | 3 | $ -> 4 | TodoApp.appView = new TodoApp.AppView collection: new TodoApp.TodoList 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superchris/backbone_coffeescript_demo/1ae3124fd8fba731544bcd4b59c36a6401b096a6/app/assets/javascripts/foo.js -------------------------------------------------------------------------------- /app/assets/javascripts/main.js.coffee: -------------------------------------------------------------------------------- 1 | #= require jquery 2 | #= require underscore 3 | #= require backbone 4 | #= require handlebars 5 | #= require application 6 | #= require_tree . -------------------------------------------------------------------------------- /app/assets/javascripts/models/todo.coffee: -------------------------------------------------------------------------------- 1 | # Todo Model 2 | # ---------- 3 | 4 | # Our basic **Todo** model has `content`, `order`, and `done` attributes. 5 | class TodoApp.Todo extends Backbone.Model 6 | 7 | # If you don't provide a todo, one will be provided for you. 8 | EMPTY: "empty todo..." 9 | 10 | # Ensure that each todo created has `content`. 11 | initialize: -> 12 | unless @get "content" 13 | @set content: @EMPTY 14 | 15 | # Toggle the `done` state of this todo item. 16 | toggle: -> 17 | @save done: not @get "done" 18 | 19 | 20 | # Todo Collection 21 | # --------------- 22 | # 23 | # The collection of todos is backed by TodosController 24 | # 25 | class TodoApp.TodoList extends Backbone.Collection 26 | 27 | # Reference to this collection's model. 28 | model: TodoApp.Todo 29 | 30 | # Save all of the todo items under the `"todos"` namespace. 31 | url: '/todos' 32 | 33 | # Filter down the list of all todo items that are finished. 34 | done: -> 35 | @filter (todo) -> todo.get 'done' 36 | 37 | # Filter down the list to only todo items that are still not finished. 38 | remaining: -> 39 | @without this.done()... 40 | 41 | # We keep the Todos in sequential order, despite being saved by unordered 42 | # GUID in the database. This generates the next order number for new items. 43 | nextOrder: -> 44 | if @length then @last().get('order') + 1 else 1 45 | 46 | # Todos are sorted by their original insertion order. 47 | comparator: (todo) -> 48 | todo.get 'order' 49 | -------------------------------------------------------------------------------- /app/assets/javascripts/views/helpers.coffee: -------------------------------------------------------------------------------- 1 | # Compile and store template function from DOM element with specified selector 2 | TodoApp.template = (selector) -> 3 | template = null 4 | -> 5 | template ?= Handlebars.compile(if selector.charAt(0) == "#" then $(selector).html() else selector) 6 | template.apply this, arguments 7 | 8 | Handlebars.registerHelper 'debug', (ctx) -> 9 | context = typeof ctx == "function" ? ctx.call this : ctx 10 | console.log context 11 | 12 | 13 | Handlebars.registerHelper 'pluralize', (count, word, fn) -> 14 | if count != 1 then word+"s" else word 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/views/todo_view.coffee: -------------------------------------------------------------------------------- 1 | # Todo Item View 2 | # -------------- 3 | 4 | # The DOM element for a todo item... 5 | class TodoApp.TodoView extends Backbone.View 6 | # ... is a list tag. 7 | tagName: "li" 8 | 9 | # Cache the template function for a single item. 10 | template: TodoApp.template '#item-template' 11 | 12 | # The DOM events specific to an item. 13 | events: 14 | "click .check" : "toggleDone" 15 | "dblclick div.todo-content" : "edit" 16 | "click span.todo-destroy" : "destroy" 17 | "keypress .todo-input" : "updateOnEnter" 18 | 19 | # The TodoView listens for changes to its model, re-rendering. Since there's 20 | # a one-to-one correspondence between a **Todo** and a **TodoView** in this 21 | # app, we set a direct reference on the model for convenience. 22 | initialize: -> 23 | _.bindAll this, 'render', 'close' 24 | @model.bind 'change', @render 25 | @model.bind 'destroy', => @remove() 26 | 27 | # Re-render the contents of the todo item. 28 | render: -> 29 | $(@el).html @template @model.toJSON() 30 | @setContent() 31 | this 32 | 33 | # To avoid XSS (not that it would be harmful in this particular app), 34 | # we use `jQuery.text` to set the contents of the todo item. 35 | setContent: -> 36 | content = @model.get 'content' 37 | @$('.todo-content').text content 38 | @input = @$('.todo-input') 39 | @input.blur @close 40 | @input.val content 41 | 42 | # Toggle the `"done"` state of the model. 43 | toggleDone: -> 44 | @model.toggle() 45 | 46 | # Switch this view into `"editing"` mode, displaying the input field. 47 | edit: -> 48 | $(@el).addClass "editing" 49 | @input.focus() 50 | 51 | # Close the `"editing"` mode, saving changes to the todo. 52 | close: -> 53 | @model.save content: @input.val() 54 | $(@el).removeClass "editing" 55 | 56 | # If you hit `enter`, we're through editing the item. 57 | updateOnEnter: (e) -> 58 | @close() if e.keyCode == 13 59 | 60 | # Destroy the model. 61 | destroy: -> 62 | @model.destroy() 63 | 64 | 65 | # The Application 66 | # --------------- 67 | 68 | # Our overall **AppView** is the top-level piece of UI. 69 | class TodoApp.AppView extends Backbone.View 70 | 71 | # Instead of generating a new element, bind to the existing skeleton of 72 | # the App already present in the HTML. 73 | el: "#todoapp" 74 | 75 | # Our template for the line of statistics at the bottom of the app. 76 | statsTemplate: TodoApp.template '#stats-template' 77 | 78 | # Delegated events for creating new items, and clearing completed ones. 79 | events: 80 | "keypress #new-todo" : "createOnEnter" 81 | "keyup #new-todo" : "showTooltip" 82 | "click .todo-clear a" : "clearCompleted" 83 | 84 | # At initialization we bind to the relevant events on the `Todos` 85 | # collection, when items are added or changed. Kick things off by 86 | # loading any preexisting todos that might be saved 87 | initialize: -> 88 | _.bindAll this, 'addOne', 'addAll', 'renderStats' 89 | 90 | @input = @$("#new-todo") 91 | 92 | @collection.bind 'add', @addOne 93 | @collection.bind 'refresh', @addAll 94 | @collection.bind 'all', @renderStats 95 | 96 | @collection.fetch() 97 | 98 | # Re-rendering the App just means refreshing the statistics -- the rest 99 | # of the app doesn't change. 100 | renderStats: -> 101 | @$('#todo-stats').html @statsTemplate 102 | total: @collection.length 103 | done: @collection.done().length 104 | remaining: @collection.remaining().length 105 | 106 | # Add a single todo item to the list by creating a view for it, and 107 | # appending its element to the `