├── lib └── tasks │ ├── .gitkeep │ └── package_assets.rake ├── public ├── favicon.ico ├── javascripts │ ├── .gitkeep │ ├── application.js │ ├── app │ │ ├── app.js │ │ ├── lib │ │ │ ├── backbone_datalink.js │ │ │ └── mongo_model.js │ │ ├── views │ │ │ └── projects │ │ │ │ ├── index.js │ │ │ │ ├── show_view.js │ │ │ │ ├── new_view.js │ │ │ │ ├── project_view.js │ │ │ │ └── index_view.js │ │ ├── models │ │ │ └── project.js │ │ └── controllers │ │ │ └── projects_controller.js │ └── vendor │ │ ├── rails.js │ │ ├── jquery.datalink.js │ │ ├── underscore.js │ │ └── backbone.js ├── stylesheets │ ├── .gitkeep │ ├── scaffold.css │ └── compiled │ │ └── screen.css ├── images │ └── rails.png ├── assets │ ├── common.css.gz │ ├── common.js.gz │ ├── application.js.gz │ ├── common-datauri.css.gz │ ├── application.js │ ├── common.css │ └── common-datauri.css ├── robots.txt ├── 422.html ├── 404.html └── 500.html ├── vendor └── plugins │ └── .gitkeep ├── app ├── helpers │ ├── home_helper.rb │ ├── projects_helper.rb │ └── application_helper.rb ├── views │ ├── home │ │ └── index.html.haml │ ├── projects │ │ ├── new.html.haml │ │ ├── edit.html.haml │ │ ├── show.html.haml │ │ ├── _form.html.haml │ │ └── index.html.haml │ ├── layouts │ │ ├── _flash.html.haml │ │ ├── _header.html.haml │ │ └── application.html.haml │ └── users │ │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── unlock_instructions.html.erb │ │ └── reset_password_instructions.html.erb │ │ ├── unlocks │ │ └── new.html.erb │ │ ├── passwords │ │ ├── new.html.erb │ │ └── edit.html.erb │ │ ├── confirmations │ │ └── new.html.erb │ │ ├── sessions │ │ └── new.html.erb │ │ ├── registrations │ │ ├── new.html.erb │ │ └── edit.html.erb │ │ └── shared │ │ └── _links.erb ├── controllers │ ├── home_controller.rb │ ├── application_controller.rb │ └── projects_controller.rb ├── models │ ├── project.rb │ └── user.rb ├── coffeescripts │ ├── templates │ │ └── projects │ │ │ ├── project.html.mustache │ │ │ ├── show.html.mustache │ │ │ ├── index.html.mustache │ │ │ └── new.html.mustache │ ├── app.coffee │ ├── models │ │ └── project.coffee │ ├── views │ │ └── projects │ │ │ ├── show_view.coffee │ │ │ ├── new_view.coffee │ │ │ ├── project_view.coffee │ │ │ └── index_view.coffee │ ├── lib │ │ ├── backbone_datalink.coffee │ │ └── mongo_model.coffee │ └── controllers │ │ └── projects_controller.coffee └── stylesheets │ ├── screen.scss │ ├── application │ ├── _header.scss │ └── _base.scss │ └── lib │ ├── _reset.scss │ └── _typography.scss ├── spec ├── fabricators │ ├── user_fabricator.rb │ └── project_fabricator.rb ├── support │ ├── mongoid.rb │ ├── database_cleaner.rb │ └── devise.rb ├── models │ ├── user_spec.rb │ └── project_spec.rb ├── controllers │ ├── home_controller_spec.rb │ └── projects_controller_spec.rb ├── helpers │ └── home_helper_spec.rb └── spec_helper.rb ├── .rvmrc ├── config ├── initializers │ ├── compass.rb │ ├── mime_types.rb │ ├── inflections.rb │ ├── backtrace_silencers.rb │ ├── session_store.rb │ ├── secret_token.rb │ ├── barista_config.rb │ └── devise.rb ├── boot.rb ├── locales │ ├── en.yml │ └── devise.en.yml ├── environment.rb ├── compass.rb ├── mongoid.yml ├── assets.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── routes.rb └── application.rb ├── config.ru ├── doc └── README_FOR_APP ├── .gitignore ├── Rakefile ├── script └── rails ├── db └── seeds.rb ├── Gemfile ├── README.md └── Gemfile.lock /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/javascripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/home_helper.rb: -------------------------------------------------------------------------------- 1 | module HomeHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/projects_helper.rb: -------------------------------------------------------------------------------- 1 | module ProjectsHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/fabricators/user_fabricator.rb: -------------------------------------------------------------------------------- 1 | Fabricator(:user) do 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/views/home/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Home#index 2 | %p Find me in app/views/home/index.html.haml -------------------------------------------------------------------------------- /spec/support/mongoid.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include Mongoid::Matchers 3 | end -------------------------------------------------------------------------------- /spec/fabricators/project_fabricator.rb: -------------------------------------------------------------------------------- 1 | Fabricator(:project) do 2 | name "" 3 | description "" 4 | end 5 | -------------------------------------------------------------------------------- /app/views/projects/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 New project 2 | 3 | = render 'form' 4 | 5 | = link_to 'Back', projects_path 6 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | end 4 | 5 | end 6 | -------------------------------------------------------------------------------- /public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebrew/rails3-backbone-coffeescript/HEAD/public/images/rails.png -------------------------------------------------------------------------------- /public/assets/common.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebrew/rails3-backbone-coffeescript/HEAD/public/assets/common.css.gz -------------------------------------------------------------------------------- /public/assets/common.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebrew/rails3-backbone-coffeescript/HEAD/public/assets/common.js.gz -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm --create 1.9.2@rails3-backbone-coffeescript 2 | 3 | if ! command -v bundle ; then 4 | gem install bundler 5 | fi 6 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class ProjectsController < InheritedResources::Base 2 | respond_to :html, :json 3 | end 4 | -------------------------------------------------------------------------------- /public/assets/application.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebrew/rails3-backbone-coffeescript/HEAD/public/assets/application.js.gz -------------------------------------------------------------------------------- /config/initializers/compass.rb: -------------------------------------------------------------------------------- 1 | require 'compass' 2 | require 'compass/app_integration/rails' 3 | Compass::AppIntegration::Rails.initialize! 4 | -------------------------------------------------------------------------------- /public/assets/common-datauri.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebrew/rails3-backbone-coffeescript/HEAD/public/assets/common-datauri.css.gz -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe User do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/project_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Project do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project 2 | include Mongoid::Document 3 | field :name, :type => String 4 | field :description, :type => String 5 | end 6 | -------------------------------------------------------------------------------- /app/views/projects/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Editing project 2 | 3 | = render 'form' 4 | 5 | = link_to 'Show', @project 6 | \| 7 | = link_to 'Back', projects_path 8 | -------------------------------------------------------------------------------- /public/assets/application.js: -------------------------------------------------------------------------------- 1 | (function(){window.App={Models:{},Controllers:{},Views:{},currentUser:function(a){if(a){return this.user=new App.Models.User(a)}else{return this.user}}}}).call(this); -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails3BackboneCoffeescript::Application 5 | -------------------------------------------------------------------------------- /app/coffeescripts/templates/projects/project.html.mustache: -------------------------------------------------------------------------------- 1 | {{name}} 2 | {{description}} 3 | Show 4 | Edit 5 | Destroy 6 | -------------------------------------------------------------------------------- /app/coffeescripts/templates/projects/show.html.mustache: -------------------------------------------------------------------------------- 1 |
2 | Name: 3 | {{name}} 4 |
5 | 6 |
7 | Description: 8 | {{description}} 9 |
10 | 11 | Back -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /app/views/layouts/_flash.html.haml: -------------------------------------------------------------------------------- 1 | #flash-messages 2 | - unless notice.blank? 3 | .notice 4 | .container 5 | %p= notice 6 | 7 | - unless alert.blank? 8 | .alert 9 | .container 10 | %p= alert -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /app/coffeescripts/app.coffee: -------------------------------------------------------------------------------- 1 | window.App = 2 | Models: {} 3 | Collections: {} 4 | Controllers: {} 5 | Views: {} 6 | 7 | currentUser: (user) -> 8 | if user 9 | @user = new App.Models.User(user) 10 | else 11 | return @user -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | require 'yaml' 4 | YAML::ENGINE.yamler= 'syck' 5 | 6 | 7 | # Initialize the rails application 8 | Rails3BackboneCoffeescript::Application.initialize! 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.sassc 3 | .sass-cache 4 | capybara-*.html 5 | .rspec 6 | /.bundle 7 | /vendor/bundle 8 | /log/* 9 | /tmp/* 10 | /db/*.sqlite3 11 | /public/system/* 12 | /coverage/ 13 | /spec/tmp/* 14 | **.orig 15 | rerun.txt 16 | pickle-email-*.html 17 | -------------------------------------------------------------------------------- /app/views/projects/show.html.haml: -------------------------------------------------------------------------------- 1 | %p#notice= notice 2 | 3 | %p 4 | %b Name: 5 | = @project.name 6 | %p 7 | %b Description: 8 | = @project.description 9 | 10 | = link_to 'Edit', edit_project_path(@project) 11 | \| 12 | = link_to 'Back', projects_path 13 | -------------------------------------------------------------------------------- /spec/controllers/home_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe HomeController do 4 | 5 | describe "GET 'index'" do 6 | it "should be successful" do 7 | get 'index' 8 | response.should be_success 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/coffeescripts/models/project.coffee: -------------------------------------------------------------------------------- 1 | class App.Models.Project extends Backbone.MongoModel 2 | urlRoot: "/projects" 3 | paramRoot: "project" 4 | 5 | class App.Collections.ProjectsCollection extends Backbone.Collection 6 | model: App.Models.Project 7 | url: '/projects' 8 | -------------------------------------------------------------------------------- /app/coffeescripts/views/projects/show_view.coffee: -------------------------------------------------------------------------------- 1 | App.Views.Projects ||= {} 2 | 3 | class App.Views.Projects.ShowView extends Backbone.View 4 | template: -> 5 | return JST["show"] 6 | 7 | render: -> 8 | $(this.el).html(this.template()(this.options.model.toJSON() )) 9 | return this -------------------------------------------------------------------------------- /app/views/users/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @resource.email %>!

2 | 3 |

You can confirm your account through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>

6 | -------------------------------------------------------------------------------- /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 | Rails3BackboneCoffeescript::Application.load_tasks 8 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | require 'database_cleaner' 3 | 4 | config.before(:suite) do 5 | DatabaseCleaner.strategy = :truncation 6 | DatabaseCleaner.orm = "mongoid" 7 | end 8 | 9 | config.before(:each) do 10 | DatabaseCleaner.clean 11 | end 12 | end -------------------------------------------------------------------------------- /app/coffeescripts/templates/projects/index.html.mustache: -------------------------------------------------------------------------------- 1 |

Listing projects

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
Name Description
12 |
13 | 14 | New Project -------------------------------------------------------------------------------- /app/stylesheets/screen.scss: -------------------------------------------------------------------------------- 1 | /*@import "compass/reset"; 2 | */ 3 | @import "compass/utilities"; 4 | @import "compass/css3"; 5 | 6 | @import "960/grid"; 7 | 8 | @import "lib/reset"; 9 | @import "lib/typography"; 10 | @include default-typography; 11 | 12 | @import "application/base"; 13 | @import "application/header"; 14 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | include Mongoid::Document 3 | # Include default devise modules. Others available are: 4 | # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable 5 | devise :database_authenticatable, :registerable, 6 | :recoverable, :rememberable, :trackable, :validatable 7 | 8 | end 9 | -------------------------------------------------------------------------------- /app/views/users/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive amount of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>

8 | -------------------------------------------------------------------------------- /spec/support/devise.rb: -------------------------------------------------------------------------------- 1 | module ControllerMacros 2 | def login!(user = :user) 3 | before(:each) do 4 | @current_user = Fabricate(user) 5 | sign_in @current_user 6 | end 7 | end 8 | end 9 | 10 | RSpec.configure do |config| 11 | config.include Devise::TestHelpers, :type => :controller 12 | config.extend ControllerMacros, :type => :controller 13 | end -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /app/views/layouts/_header.html.haml: -------------------------------------------------------------------------------- 1 | #header 2 | .container 3 | #logo 4 | %h1 rails3-backbone-coffeescript 5 | #nav 6 | %ul 7 | %li= link_to "home", root_path 8 | - if user_signed_in? 9 | %li= link_to "Settings", edit_user_registration_path 10 | %li= link_to "Sign out", destroy_user_session_path 11 | - else 12 | %li= link_to "Login", new_user_session_path 13 | .clearfix -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /app/views/users/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.submit "Resend unlock instructions" %>

10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.submit "Send me reset password instructions" %>

10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! Strict 2 | %html 3 | %head 4 | %title Example 5 | = include_stylesheets :common 6 | = include_javascripts :common, :application 7 | = csrf_meta_tag 8 | 9 | %body 10 | = render :partial => "/layouts/header" 11 | 12 | = render :partial => "/layouts/flash" 13 | 14 | #main 15 | .container 16 | = yield 17 | 18 | #footer 19 | .container 20 | footer -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.submit "Resend confirmation instructions" %>

10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /spec/helpers/home_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the HomeHelper. For example: 5 | # 6 | # describe HomeHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # helper.concat_strings("this","that").should == "this that" 10 | # end 11 | # end 12 | # end 13 | describe HomeHelper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /app/views/users/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password, and you can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /app/coffeescripts/views/projects/new_view.coffee: -------------------------------------------------------------------------------- 1 | App.Views.Projects ||= {} 2 | 3 | class App.Views.Projects.NewView extends Backbone.View 4 | template: -> 5 | return JST["new"] 6 | 7 | events: 8 | "submit #project-form": "save" 9 | 10 | save: -> 11 | @options.model.save() 12 | return false 13 | 14 | render: -> 15 | $(this.el).html(this.template()(this.options.model.toJSON() )) 16 | 17 | this.$("form").backboneLink(@options.model) 18 | 19 | return this -------------------------------------------------------------------------------- /app/views/projects/_form.html.haml: -------------------------------------------------------------------------------- 1 | = form_for @project do |f| 2 | -if @project.errors.any? 3 | #error_explanation 4 | %h2= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:" 5 | %ul 6 | - @project.errors.full_messages.each do |msg| 7 | %li= msg 8 | 9 | .field 10 | = f.label :name 11 | = f.text_field :name 12 | .field 13 | = f.label :description 14 | = f.text_field :description 15 | .actions 16 | = f.submit 'Save' 17 | -------------------------------------------------------------------------------- /app/coffeescripts/views/projects/project_view.coffee: -------------------------------------------------------------------------------- 1 | App.Views.Projects ||= {} 2 | 3 | class App.Views.Projects.ProjectView extends Backbone.View 4 | template: -> 5 | return JST["project"] 6 | 7 | events: 8 | "click .destroy" : "destroy" 9 | 10 | tagName: "tr" 11 | 12 | destroy: () -> 13 | @options.model.destroy() 14 | this.remove() 15 | 16 | return false 17 | 18 | render: -> 19 | $(this.el).html(this.template()(this.options.model.toJSON() )) 20 | return this -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails3BackboneCoffeescript::Application.config.session_store :cookie_store, :key => '_rails3-backbone-coffeescript_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Rails3BackboneCoffeescript::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /public/javascripts/app/app.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Fri, 18 Mar 2011 20:43:55 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/app.coffee 3 | */ 4 | 5 | (function() { 6 | window.App = { 7 | Models: {}, 8 | Collections: {}, 9 | Controllers: {}, 10 | Views: {}, 11 | currentUser: function(user) { 12 | if (user) { 13 | return this.user = new App.Models.User(user); 14 | } else { 15 | return this.user; 16 | } 17 | } 18 | }; 19 | }).call(this); 20 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Rails3BackboneCoffeescript::Application.config.secret_token = 'd25312c7e58bf99347cf84aa7ef590a9fca85fd697995d59017d0cdec02eb2ec108f71540daeb71dd45e2d6a14acbbd7d3785cdf475d7960c82a9a6850144efe' 8 | -------------------------------------------------------------------------------- /app/views/users/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign in

2 | 3 | <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %> 4 |

<%= f.label :email %>
5 | <%= f.email_field :email %>

6 | 7 |

<%= f.label :password %>
8 | <%= f.password_field :password %>

9 | 10 | <% if devise_mapping.rememberable? -%> 11 |

<%= f.check_box :remember_me %> <%= f.label :remember_me %>

12 | <% end -%> 13 | 14 |

<%= f.submit "Sign in" %>

15 | <% end %> 16 | 17 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/coffeescripts/lib/backbone_datalink.coffee: -------------------------------------------------------------------------------- 1 | (($) -> 2 | $.extend($.fn, 3 | backboneLink: (model) -> 4 | $(this).find(":input").each(() -> 5 | el = $(this) 6 | name = el.attr("name") 7 | 8 | model.bind("change:#{name}", () -> 9 | el.val(model.get(name)) 10 | ) 11 | 12 | $(this).bind("change", () -> 13 | el = $(this) 14 | attrs = {} 15 | attrs[el.attr("name")] = el.val() 16 | 17 | model.set(attrs) 18 | ) 19 | ) 20 | ) 21 | )(jQuery) 22 | -------------------------------------------------------------------------------- /app/coffeescripts/templates/projects/new.html.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 | 17 | Back -------------------------------------------------------------------------------- /config/compass.rb: -------------------------------------------------------------------------------- 1 | # This configuration file works with both the Compass command line tool and within Rails. 2 | require 'ninesixty' 3 | # Require any additional compass plugins here. 4 | 5 | project_type = :rails 6 | project_path = Compass::AppIntegration::Rails.root 7 | # Set this to the root of your project when deployed: 8 | http_path = "/" 9 | css_dir = "public/stylesheets/compiled/" 10 | sass_dir = "app/stylesheets" 11 | environment = Compass::AppIntegration::Rails.env 12 | # To enable relative paths to assets via compass helper functions. Uncomment: 13 | # relative_assets = true 14 | -------------------------------------------------------------------------------- /app/views/users/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.label :password %>
10 | <%= f.password_field :password %>

11 | 12 |

<%= f.label :password_confirmation %>
13 | <%= f.password_field :password_confirmation %>

14 | 15 |

<%= f.submit "Sign up" %>

16 | <% end %> 17 | 18 | <%= render :partial => "devise/shared/links" %> 19 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |

<%= f.label :password, "New password" %>
8 | <%= f.password_field :password %>

9 | 10 |

<%= f.label :password_confirmation, "Confirm new password" %>
11 | <%= f.password_field :password_confirmation %>

12 | 13 |

<%= f.submit "Change my password" %>

14 | <% end %> 15 | 16 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /lib/tasks/package_assets.rake: -------------------------------------------------------------------------------- 1 | require 'jammit' 2 | require 'compass' 3 | require 'compass/exec' 4 | 5 | namespace :package_assets do 6 | 7 | desc "package asssets with jammit" 8 | task :package do 9 | puts "compiling scss..." 10 | project = Compass::Commands::UpdateProject.new(Rails.root.to_s, :quiet => false, :force => true) 11 | project.perform 12 | 13 | puts "compiling coffeescripts..." 14 | Rake::Task["barista:brew"].invoke 15 | 16 | puts "packaging assets...." 17 | Jammit.package! 18 | puts "finished packaging assets" 19 | end 20 | 21 | end 22 | 23 | task :package_assets => ["package_assets:package"] -------------------------------------------------------------------------------- /config/mongoid.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: localhost 3 | # slaves: 4 | # - host: slave1.local 5 | # port: 27018 6 | # - host: slave2.local 7 | # port: 27019 8 | 9 | development: 10 | <<: *defaults 11 | database: rails3_backbone_coffeescript_development 12 | 13 | test: 14 | <<: *defaults 15 | database: rails3_backbone_coffeescript_test 16 | 17 | # set these environment variables on your prod server 18 | production: 19 | host: <%= ENV['MONGOID_HOST'] %> 20 | port: <%= ENV['MONGOID_PORT'] %> 21 | username: <%= ENV['MONGOID_USERNAME'] %> 22 | password: <%= ENV['MONGOID_PASSWORD'] %> 23 | database: <%= ENV['MONGOID_DATABASE'] %> -------------------------------------------------------------------------------- /app/stylesheets/application/_header.scss: -------------------------------------------------------------------------------- 1 | #header { 2 | -webkit-box-shadow: #558A21 -2px 1px 2px; 3 | background-color: #282828; 4 | @include linear-gradient(color-stops(#333, #1A1A1A)); 5 | border-bottom: 3px solid #4BC1DE; 6 | height: 45px; 7 | 8 | #logo { 9 | @include grid(10); 10 | h1 { 11 | color: #fff; 12 | font-size: 26px; 13 | padding-top: 5px; 14 | padding-bottom: 5px; 15 | } 16 | } 17 | } 18 | 19 | #nav { 20 | @include grid(14); 21 | ul { 22 | @include horizontal-list-container; 23 | padding: 10px 0px 10px 0px; 24 | float: right; 25 | 26 | li { 27 | @include horizontal-list-item(10px, "left"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/coffeescripts/views/projects/index_view.coffee: -------------------------------------------------------------------------------- 1 | App.Views.Projects ||= {} 2 | 3 | class App.Views.Projects.IndexView extends Backbone.View 4 | template: -> 5 | return JST["index"] 6 | 7 | initialize: () -> 8 | _.bindAll(this, 'addOne', 'addAll', 'render'); 9 | 10 | @options.projects.bind('refresh', this.addAll); 11 | 12 | addAll: () -> 13 | @options.projects.each(this.addOne) 14 | 15 | addOne: (project) -> 16 | pv = new App.Views.Projects.ProjectView({model : project}) 17 | this.$("#projects-table tbody").append(pv.render().el) 18 | 19 | render: -> 20 | $(this.el).html(this.template()(projects: this.options.projects.toJSON() )) 21 | @addAll() 22 | 23 | return this 24 | -------------------------------------------------------------------------------- /app/views/projects/index.html.haml: -------------------------------------------------------------------------------- 1 | #projects 2 | / %h1 Listing projects 3 | / 4 | / %table 5 | / %tr 6 | / %th Name 7 | / %th Description 8 | / %th 9 | / %th 10 | / %th 11 | / 12 | / - @projects.each do |project| 13 | / %tr 14 | / %td= project.name 15 | / %td= project.description 16 | / %td= link_to 'Show', project 17 | / %td= link_to 'Edit', edit_project_path(project) 18 | / %td= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete 19 | / 20 | / %br 21 | / 22 | / / = link_to 'New Project', new_project_path 23 | / = link_to 'New Project', "#/new" 24 | 25 | :javascript 26 | window.controller = new App.Controllers.ProjectsController({projects: #{@projects.to_json}}); 27 | Backbone.history.start(); 28 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /app/coffeescripts/controllers/projects_controller.coffee: -------------------------------------------------------------------------------- 1 | class App.Controllers.ProjectsController extends Backbone.Controller 2 | initialize: (options) -> 3 | @projects = new App.Collections.ProjectsCollection() 4 | @projects.refresh options.projects 5 | 6 | routes: 7 | "/new": "newProject" 8 | "/index": "index" 9 | "/:id": "show" 10 | ".*": "index" 11 | 12 | newProject: -> 13 | @view = new App.Views.Projects.NewView(model: new @projects.model()) 14 | $("#projects").html(@view.render().el) 15 | 16 | index: -> 17 | @view = new App.Views.Projects.IndexView(projects: @projects) 18 | $("#projects").html(@view.render().el) 19 | 20 | show:(id) -> 21 | project = @projects.get(id) 22 | @view = new App.Views.Projects.ShowView(model:project) 23 | $("#projects").html(@view.render().el) -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /config/assets.yml: -------------------------------------------------------------------------------- 1 | embed_assets: on 2 | javascript_compressor: yui 3 | template_extension: html.mustache 4 | template_function: "Handlebars.compile" 5 | 6 | javascripts: 7 | common: 8 | - public/javascripts/vendor/jquery.js 9 | - public/javascripts/vendor/rails.js 10 | - public/javascripts/vendor/underscore.js 11 | - public/javascripts/vendor/backbone.js 12 | - public/javascripts/vendor/handlebars.js 13 | - public/javascripts/vendor/jquery.datalink.js 14 | 15 | application: 16 | - public/javascripts/app/lib/* 17 | - public/javascripts/app/app.js 18 | - public/javascripts/app/models/* 19 | - app/coffeescripts/templates/**/* 20 | - public/javascripts/app/views/**/* 21 | - public/javascripts/app/controllers/* 22 | 23 | stylesheets: 24 | common: 25 | - public/stylesheets/compiled/screen.css 26 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.0.5' 4 | 5 | # Bundle edge Rails instead: 6 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 7 | 8 | # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) 9 | # gem 'ruby-debug' 10 | # gem 'ruby-debug19', :require => 'ruby-debug' 11 | 12 | gem "haml", ">= 3.0.0" 13 | gem "haml-rails" 14 | gem "compass" 15 | gem "compass-960-plugin" 16 | gem "mongoid", :git => "git://github.com/mongoid/mongoid.git" 17 | gem "bson_ext", ">= 1.2.4" 18 | gem "jammit" 19 | gem "barista", "~> 1.0" 20 | gem "devise", ">= 1.2.rc" 21 | gem 'inherited_resources', '~> 1.2.1' 22 | gem 'has_scope' 23 | 24 | group :test, :development do 25 | gem "rspec-rails", ">= 2.5" 26 | gem "database_cleaner" 27 | gem "fabrication" 28 | gem "mongoid-rspec", ">= 1.4.1" 29 | gem 'ruby-debug19' 30 | end 31 | 32 | -------------------------------------------------------------------------------- /public/javascripts/app/lib/backbone_datalink.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Tue, 05 Apr 2011 21:28:03 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/lib/backbone_datalink.coffee 3 | */ 4 | 5 | (function() { 6 | (function($) { 7 | return $.extend($.fn, { 8 | backboneLink: function(model) { 9 | return $(this).find(":input").each(function() { 10 | var el, name; 11 | el = $(this); 12 | name = el.attr("name"); 13 | model.bind("change:" + name, function() { 14 | return el.val(model.get(name)); 15 | }); 16 | return $(this).bind("change", function() { 17 | var attrs; 18 | el = $(this); 19 | attrs = {}; 20 | attrs[el.attr("name")] = el.val(); 21 | return model.set(attrs); 22 | }); 23 | }); 24 | } 25 | }); 26 | })(jQuery); 27 | }).call(this); 28 | -------------------------------------------------------------------------------- /app/stylesheets/application/_base.scss: -------------------------------------------------------------------------------- 1 | $ninesixty-columns: 24; 2 | 3 | .clearfix { 4 | clear: both; 5 | } 6 | 7 | .container { 8 | @include grid-container; 9 | padding-top: 10px; 10 | } 11 | 12 | body { 13 | background-color: #282828; 14 | color: #333; 15 | font-family: Arial, Helvetica, sans-serif; 16 | font-size: 14px; 17 | margin: 0px; 18 | padding: 0px 0px 40px; 19 | } 20 | 21 | #main { 22 | background-color: #E9E9E9; 23 | min-height: 700px; 24 | } 25 | 26 | #footer { 27 | background-color: #282828; 28 | padding: 20px 0px; 29 | } 30 | 31 | #flash-messages { 32 | background: #BCDD5B; 33 | left: 0px; 34 | line-height: 55px; 35 | z-index: 1; 36 | 37 | .container { 38 | padding-top: 0; 39 | } 40 | 41 | .alert { 42 | background: #F05931; 43 | } 44 | 45 | .notice, .alert { 46 | padding: 15px 0px; 47 | } 48 | 49 | p { 50 | line-height: 32px; 51 | color: white; 52 | display: block; 53 | font-size: 16px; 54 | font-weight: bold; 55 | } 56 | } -------------------------------------------------------------------------------- /app/coffeescripts/lib/mongo_model.coffee: -------------------------------------------------------------------------------- 1 | class Backbone.MongoModel extends Backbone.Model 2 | idAttribute: "_id" 3 | 4 | methodMap: 5 | 'create': 'POST' 6 | 'update': 'PUT' 7 | 'delete': 'DELETE' 8 | 'read' : 'GET' 9 | 10 | getUrl: (object) -> 11 | if (!(object && object.url)) 12 | return null 13 | 14 | if _.isFunction(object.url) then object.url() else object.url 15 | 16 | sync: (method, model, options) -> 17 | type = @methodMap[method]; 18 | 19 | # Default JSON-request options. 20 | params = _.extend({ 21 | type: type, 22 | contentType: 'application/json', 23 | dataType: 'json', 24 | processData: false 25 | }, options) 26 | 27 | if (!params.url) 28 | params.url = @getUrl(model) 29 | 30 | if (!params.data && model && (method == 'create' || method == 'update')) 31 | data = {} 32 | data[model.paramRoot] = model.toJSON() 33 | params.data = JSON.stringify(data) 34 | 35 | $.ajax(params) -------------------------------------------------------------------------------- /app/views/users/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

<%= f.label :email %>
7 | <%= f.email_field :email %>

8 | 9 |

<%= f.label :password %> (leave blank if you don't want to change it)
10 | <%= f.password_field :password %>

11 | 12 |

<%= f.label :password_confirmation %>
13 | <%= f.password_field :password_confirmation %>

14 | 15 |

<%= f.label :current_password %> (we need your current password to confirm your changes)
16 | <%= f.password_field :current_password %>

17 | 18 |

<%= f.submit "Update" %>

19 | <% end %> 20 | 21 |

Cancel my account

22 | 23 |

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.

24 | 25 | <%= link_to "Back", :back %> 26 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require File.expand_path("../../config/environment", __FILE__) 4 | require 'rspec/rails' 5 | 6 | # Requires supporting ruby files with custom matchers and macros, etc, 7 | # in spec/support/ and its subdirectories. 8 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 9 | 10 | RSpec.configure do |config| 11 | # == Mock Framework 12 | # 13 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 14 | # 15 | # config.mock_with :mocha 16 | # config.mock_with :flexmock 17 | # config.mock_with :rr 18 | config.mock_with :rspec 19 | 20 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 21 | # config.fixture_path = "#{::Rails.root}/spec/fixtures" 22 | 23 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 24 | # examples within a transaction, remove the following line or assign false 25 | # instead of true. 26 | # config.use_transactional_fixtures = true 27 | end 28 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails3BackboneCoffeescript::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /public/stylesheets/scaffold.css: -------------------------------------------------------------------------------- 1 | body { background-color: #fff; color: #333; } 2 | 3 | body, p, ol, ul, td { 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | pre { 10 | background-color: #eee; 11 | padding: 10px; 12 | font-size: 11px; 13 | } 14 | 15 | a { color: #000; } 16 | a:visited { color: #666; } 17 | a:hover { color: #fff; background-color:#000; } 18 | 19 | div.field, div.actions { 20 | margin-bottom: 10px; 21 | } 22 | 23 | #notice { 24 | color: green; 25 | } 26 | 27 | .field_with_errors { 28 | padding: 2px; 29 | background-color: red; 30 | display: table; 31 | } 32 | 33 | #error_explanation { 34 | width: 450px; 35 | border: 2px solid red; 36 | padding: 7px; 37 | padding-bottom: 0; 38 | margin-bottom: 20px; 39 | background-color: #f0f0f0; 40 | } 41 | 42 | #error_explanation h2 { 43 | text-align: left; 44 | font-weight: bold; 45 | padding: 5px 5px 5px 15px; 46 | font-size: 12px; 47 | margin: -7px; 48 | margin-bottom: 0px; 49 | background-color: #c00; 50 | color: #fff; 51 | } 52 | 53 | #error_explanation ul li { 54 | font-size: 12px; 55 | list-style: square; 56 | } 57 | -------------------------------------------------------------------------------- /app/views/users/shared/_links.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Sign in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%> 25 | <% end -%> -------------------------------------------------------------------------------- /public/javascripts/app/views/projects/index.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Fri, 18 Mar 2011 21:12:14 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/views/projects/index.coffee 3 | */ 4 | 5 | (function() { 6 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 7 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 8 | function ctor() { this.constructor = child; } 9 | ctor.prototype = parent.prototype; 10 | child.prototype = new ctor; 11 | child.__super__ = parent.prototype; 12 | return child; 13 | }; 14 | App.Views.Projects = {}; 15 | App.Views.Projects.IndexView = (function() { 16 | function IndexView() { 17 | IndexView.__super__.constructor.apply(this, arguments); 18 | } 19 | __extends(IndexView, Backbone.View); 20 | IndexView.prototype.template = function() { 21 | return JST["index"]; 22 | }; 23 | IndexView.prototype.render = function() { 24 | $(this.el).html(this.template()({ 25 | projects: this.options.projects.toJSON() 26 | })); 27 | return this; 28 | }; 29 | return IndexView; 30 | })(); 31 | }).call(this); 32 | -------------------------------------------------------------------------------- /public/javascripts/app/views/projects/show_view.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Sat, 19 Mar 2011 03:25:10 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/views/projects/show_view.coffee 3 | */ 4 | 5 | (function() { 6 | var _base; 7 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 8 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 9 | function ctor() { this.constructor = child; } 10 | ctor.prototype = parent.prototype; 11 | child.prototype = new ctor; 12 | child.__super__ = parent.prototype; 13 | return child; 14 | }; 15 | (_base = App.Views).Projects || (_base.Projects = {}); 16 | App.Views.Projects.ShowView = (function() { 17 | function ShowView() { 18 | ShowView.__super__.constructor.apply(this, arguments); 19 | } 20 | __extends(ShowView, Backbone.View); 21 | ShowView.prototype.template = function() { 22 | return JST["show"]; 23 | }; 24 | ShowView.prototype.render = function() { 25 | $(this.el).html(this.template()(this.options.model.toJSON())); 26 | return this; 27 | }; 28 | return ShowView; 29 | })(); 30 | }).call(this); 31 | -------------------------------------------------------------------------------- /public/javascripts/app/models/project.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Tue, 05 Apr 2011 21:18:15 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/models/project.coffee 3 | */ 4 | 5 | (function() { 6 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 7 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 8 | function ctor() { this.constructor = child; } 9 | ctor.prototype = parent.prototype; 10 | child.prototype = new ctor; 11 | child.__super__ = parent.prototype; 12 | return child; 13 | }; 14 | App.Models.Project = (function() { 15 | function Project() { 16 | Project.__super__.constructor.apply(this, arguments); 17 | } 18 | __extends(Project, Backbone.MongoModel); 19 | Project.prototype.urlRoot = "/projects"; 20 | Project.prototype.paramRoot = "project"; 21 | return Project; 22 | })(); 23 | App.Collections.ProjectsCollection = (function() { 24 | function ProjectsCollection() { 25 | ProjectsCollection.__super__.constructor.apply(this, arguments); 26 | } 27 | __extends(ProjectsCollection, Backbone.Collection); 28 | ProjectsCollection.prototype.model = App.Models.Project; 29 | ProjectsCollection.prototype.url = '/projects'; 30 | return ProjectsCollection; 31 | })(); 32 | }).call(this); 33 | -------------------------------------------------------------------------------- /public/javascripts/app/views/projects/new_view.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Tue, 05 Apr 2011 21:19:40 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/views/projects/new_view.coffee 3 | */ 4 | 5 | (function() { 6 | var _base; 7 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 8 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 9 | function ctor() { this.constructor = child; } 10 | ctor.prototype = parent.prototype; 11 | child.prototype = new ctor; 12 | child.__super__ = parent.prototype; 13 | return child; 14 | }; 15 | (_base = App.Views).Projects || (_base.Projects = {}); 16 | App.Views.Projects.NewView = (function() { 17 | function NewView() { 18 | NewView.__super__.constructor.apply(this, arguments); 19 | } 20 | __extends(NewView, Backbone.View); 21 | NewView.prototype.template = function() { 22 | return JST["new"]; 23 | }; 24 | NewView.prototype.events = { 25 | "submit #project-form": "save" 26 | }; 27 | NewView.prototype.save = function() { 28 | this.options.model.save(); 29 | return false; 30 | }; 31 | NewView.prototype.render = function() { 32 | $(this.el).html(this.template()(this.options.model.toJSON())); 33 | this.$("form").backboneLink(this.options.model); 34 | return this; 35 | }; 36 | return NewView; 37 | })(); 38 | }).call(this); 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rails3-backbone-coffeescript 2 | 3 | This is an example rails 3 app built with [backbone.js][backbone_js], [coffeescript][coffeescript] and [mongoDB][mongodb] (using mongoid) 4 | 5 | ## Help and Info 6 | 7 | To follow the development of this app, follow us on Twitter: 8 | [http://twitter.com/codebrewstudios](http://twitter.com/codebrewstudios). 9 | 10 | If you need any projects built using this stack feel free to contact us at [services@codebrewstudios.com](mailto:services@codebrewstudios.com) 11 | 12 | ## Dependencies 13 | 1. Ruby 1.9.2 14 | 2. Rails 3 15 | 3. [Coffeescript][coffeescript] 1.0 16 | 4. [mongoDB](http://www.mongodb.org) (any recent stable version should work, we use 1.8) 17 | 18 | Using [rvm][rvm] is recommended 19 | 20 | ## Project layout 21 | 22 | The most interesting part of the project exists inside the app/coffeescripts folder. 23 | Its broken down into 4 main parts 24 | 25 | 1. lib - javascript library files to use through out the application 26 | 2. Controllers dir - this is where our backbone controllers live 27 | 3. Model dir - this is where backbone models and collections are defined 28 | 4. Views - backbone views (widgets) 29 | 5. templates - handlebar.js templates to be used by our backbone views 30 | 31 | [backbone_js]: http://documentcloud.github.com/backbone "backbone.js" 32 | [coffeescript]: http://jashkenas.github.com/coffee-script "coffeescript" 33 | [mongodb]: http://www.mongodb.org "mongodb" 34 | [rvm]: http://rvm.beginrescueend.com "rvm" -------------------------------------------------------------------------------- /public/javascripts/app/views/projects/project_view.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Sat, 19 Mar 2011 03:25:50 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/views/projects/project_view.coffee 3 | */ 4 | 5 | (function() { 6 | var _base; 7 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 8 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 9 | function ctor() { this.constructor = child; } 10 | ctor.prototype = parent.prototype; 11 | child.prototype = new ctor; 12 | child.__super__ = parent.prototype; 13 | return child; 14 | }; 15 | (_base = App.Views).Projects || (_base.Projects = {}); 16 | App.Views.Projects.ProjectView = (function() { 17 | function ProjectView() { 18 | ProjectView.__super__.constructor.apply(this, arguments); 19 | } 20 | __extends(ProjectView, Backbone.View); 21 | ProjectView.prototype.template = function() { 22 | return JST["project"]; 23 | }; 24 | ProjectView.prototype.events = { 25 | "click .destroy": "destroy" 26 | }; 27 | ProjectView.prototype.tagName = "tr"; 28 | ProjectView.prototype.destroy = function() { 29 | this.options.model.destroy(); 30 | this.remove(); 31 | return false; 32 | }; 33 | ProjectView.prototype.render = function() { 34 | $(this.el).html(this.template()(this.options.model.toJSON())); 35 | return this; 36 | }; 37 | return ProjectView; 38 | })(); 39 | }).call(this); 40 | -------------------------------------------------------------------------------- /app/stylesheets/lib/_reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0b1 | 201101 3 | NOTE: WORK IN PROGRESS 4 | USE WITH CAUTION AND TEST WITH ABANDON */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, figcaption, figure, 16 | footer, header, hgroup, menu, nav, section, summary, 17 | time, mark, audio, video { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | outline: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | 46 | /* remember to define visible focus styles! 47 | :focus { 48 | outline: ?????; 49 | } */ 50 | 51 | /* remember to highlight inserts somehow! */ 52 | ins { 53 | text-decoration: none; 54 | } 55 | del { 56 | text-decoration: line-through; 57 | } 58 | 59 | table { 60 | border-collapse: collapse; 61 | border-spacing: 0; 62 | } -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails3BackboneCoffeescript::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /config/initializers/barista_config.rb: -------------------------------------------------------------------------------- 1 | # Configure barista. 2 | Barista.configure do |c| 3 | 4 | # Change the root to use app/scripts 5 | # c.root = Rails.root.join("app", "scripts") 6 | 7 | # Change the output root, causing Barista to compile into public/coffeescripts 8 | c.output_root = Rails.root.join("public/javascripts", "app") 9 | 10 | # Set the compiler 11 | 12 | # Disable wrapping in a closure: 13 | # c.no_wrap = true 14 | # ... or ... 15 | # c.no_wrap! 16 | 17 | # Change the output root for a framework: 18 | 19 | # c.change_output_prefix! 'framework-name', 'output-prefix' 20 | 21 | # or for all frameworks... 22 | 23 | # c.each_framework do |framework| 24 | # c.change_output_prefix! framework.name, "vendor/#{framework.name}" 25 | # end 26 | 27 | # or, prefix the path for the app files: 28 | 29 | # c.change_output_prefix! :default, 'my-app-name' 30 | 31 | # or, hook into the compilation: 32 | 33 | # c.before_compilation { |path| puts "Barista: Compiling #{path}" } 34 | # c.on_compilation { |path| puts "Barista: Successfully compiled #{path}" } 35 | # c.on_compilation_error { |path, output| puts "Barista: Compilation of #{path} failed with:\n#{output}" } 36 | # c.on_compilation_with_warning { |path, output| puts "Barista: Compilation of #{path} had a warning:\n#{output}" } 37 | 38 | # Turn off preambles and exceptions on failure 39 | 40 | # c.verbose = false 41 | 42 | # Or, make sure it is always on 43 | # c.verbose! 44 | 45 | # If you want to use a custom JS file, you can as well 46 | # e.g. vendoring CoffeeScript in your application: 47 | # c.js_path = Rails.root.join('public', 'javascripts', 'coffee-script.js') 48 | 49 | end 50 | -------------------------------------------------------------------------------- /public/javascripts/app/views/projects/index_view.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Sat, 19 Mar 2011 03:25:10 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/views/projects/index_view.coffee 3 | */ 4 | 5 | (function() { 6 | var _base; 7 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 8 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 9 | function ctor() { this.constructor = child; } 10 | ctor.prototype = parent.prototype; 11 | child.prototype = new ctor; 12 | child.__super__ = parent.prototype; 13 | return child; 14 | }; 15 | (_base = App.Views).Projects || (_base.Projects = {}); 16 | App.Views.Projects.IndexView = (function() { 17 | function IndexView() { 18 | IndexView.__super__.constructor.apply(this, arguments); 19 | } 20 | __extends(IndexView, Backbone.View); 21 | IndexView.prototype.template = function() { 22 | return JST["index"]; 23 | }; 24 | IndexView.prototype.initialize = function() { 25 | _.bindAll(this, 'addOne', 'addAll', 'render'); 26 | return this.options.projects.bind('refresh', this.addAll); 27 | }; 28 | IndexView.prototype.addAll = function() { 29 | return this.options.projects.each(this.addOne); 30 | }; 31 | IndexView.prototype.addOne = function(project) { 32 | var pv; 33 | pv = new App.Views.Projects.ProjectView({ 34 | model: project 35 | }); 36 | return this.$("#projects-table tbody").append(pv.render().el); 37 | }; 38 | IndexView.prototype.render = function() { 39 | $(this.el).html(this.template()({ 40 | projects: this.options.projects.toJSON() 41 | })); 42 | this.addAll(); 43 | return this; 44 | }; 45 | return IndexView; 46 | })(); 47 | }).call(this); 48 | -------------------------------------------------------------------------------- /public/javascripts/app/lib/mongo_model.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Sat, 19 Mar 2011 03:23:31 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/lib/mongo_model.coffee 3 | */ 4 | 5 | (function() { 6 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 7 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 8 | function ctor() { this.constructor = child; } 9 | ctor.prototype = parent.prototype; 10 | child.prototype = new ctor; 11 | child.__super__ = parent.prototype; 12 | return child; 13 | }; 14 | Backbone.MongoModel = (function() { 15 | function MongoModel() { 16 | MongoModel.__super__.constructor.apply(this, arguments); 17 | } 18 | __extends(MongoModel, Backbone.Model); 19 | MongoModel.prototype.idAttribute = "_id"; 20 | MongoModel.prototype.methodMap = { 21 | 'create': 'POST', 22 | 'update': 'PUT', 23 | 'delete': 'DELETE', 24 | 'read': 'GET' 25 | }; 26 | MongoModel.prototype.getUrl = function(object) { 27 | if (!(object && object.url)) { 28 | return null; 29 | } 30 | if (_.isFunction(object.url)) { 31 | return object.url(); 32 | } else { 33 | return object.url; 34 | } 35 | }; 36 | MongoModel.prototype.sync = function(method, model, options) { 37 | var data, params, type; 38 | type = this.methodMap[method]; 39 | params = _.extend({ 40 | type: type, 41 | contentType: 'application/json', 42 | dataType: 'json', 43 | processData: false 44 | }, options); 45 | if (!params.url) { 46 | params.url = this.getUrl(model); 47 | } 48 | if (!params.data && model && (method === 'create' || method === 'update')) { 49 | data = {}; 50 | data[model.paramRoot] = model.toJSON(); 51 | params.data = JSON.stringify(data); 52 | } 53 | return $.ajax(params); 54 | }; 55 | return MongoModel; 56 | })(); 57 | }).call(this); 58 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails3BackboneCoffeescript::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | 50 | # dont compile sass because heroku is read only 51 | Sass::Plugin.options[:never_update] = true 52 | end 53 | -------------------------------------------------------------------------------- /public/javascripts/app/controllers/projects_controller.js: -------------------------------------------------------------------------------- 1 | /* DO NOT MODIFY. This file was compiled Tue, 05 Apr 2011 20:51:21 GMT from 2 | * /Users/fitz/Projects/rails3-backbone-coffeescript/app/coffeescripts/controllers/projects_controller.coffee 3 | */ 4 | 5 | (function() { 6 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { 7 | for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } 8 | function ctor() { this.constructor = child; } 9 | ctor.prototype = parent.prototype; 10 | child.prototype = new ctor; 11 | child.__super__ = parent.prototype; 12 | return child; 13 | }; 14 | App.Controllers.ProjectsController = (function() { 15 | function ProjectsController() { 16 | ProjectsController.__super__.constructor.apply(this, arguments); 17 | } 18 | __extends(ProjectsController, Backbone.Controller); 19 | ProjectsController.prototype.initialize = function(options) { 20 | this.projects = new App.Collections.ProjectsCollection(); 21 | return this.projects.refresh(options.projects); 22 | }; 23 | ProjectsController.prototype.routes = { 24 | "/new": "newProject", 25 | "/index": "index", 26 | "/:id": "show", 27 | ".*": "index" 28 | }; 29 | ProjectsController.prototype.newProject = function() { 30 | this.view = new App.Views.Projects.NewView({ 31 | model: new this.projects.model() 32 | }); 33 | return $("#projects").html(this.view.render().el); 34 | }; 35 | ProjectsController.prototype.index = function() { 36 | this.view = new App.Views.Projects.IndexView({ 37 | projects: this.projects 38 | }); 39 | return $("#projects").html(this.view.render().el); 40 | }; 41 | ProjectsController.prototype.show = function(id) { 42 | var project; 43 | project = this.projects.get(id); 44 | this.view = new App.Views.Projects.ShowView({ 45 | model: project 46 | }); 47 | return $("#projects").html(this.view.render().el); 48 | }; 49 | return ProjectsController; 50 | })(); 51 | }).call(this); 52 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails3BackboneCoffeescript::Application.routes.draw do 2 | devise_for :users 3 | 4 | resources :projects 5 | 6 | root :to => "projects#index" 7 | 8 | # The priority is based upon order of creation: 9 | # first created -> highest priority. 10 | 11 | # Sample of regular route: 12 | # match 'products/:id' => 'catalog#view' 13 | # Keep in mind you can assign values other than :controller and :action 14 | 15 | # Sample of named route: 16 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 17 | # This route can be invoked with purchase_url(:id => product.id) 18 | 19 | # Sample resource route (maps HTTP verbs to controller actions automatically): 20 | # resources :products 21 | 22 | # Sample resource route with options: 23 | # resources :products do 24 | # member do 25 | # get 'short' 26 | # post 'toggle' 27 | # end 28 | # 29 | # collection do 30 | # get 'sold' 31 | # end 32 | # end 33 | 34 | # Sample resource route with sub-resources: 35 | # resources :products do 36 | # resources :comments, :sales 37 | # resource :seller 38 | # end 39 | 40 | # Sample resource route with more complex sub-resources 41 | # resources :products do 42 | # resources :comments 43 | # resources :sales do 44 | # get 'recent', :on => :collection 45 | # end 46 | # end 47 | 48 | # Sample resource route within a namespace: 49 | # namespace :admin do 50 | # # Directs /admin/products/* to Admin::ProductsController 51 | # # (app/controllers/admin/products_controller.rb) 52 | # resources :products 53 | # end 54 | 55 | # You can have the root of your site routed with "root" 56 | # just remember to delete public/index.html. 57 | # root :to => "welcome#index" 58 | 59 | # See how all your routes lay out with "rake routes" 60 | 61 | # This is a legacy wild controller route that's not recommended for RESTful applications. 62 | # Note: This route will make all actions in every controller accessible via GET requests. 63 | # match ':controller(/:action(/:id(.:format)))' 64 | end 65 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # require 'rails/all' 4 | require "action_controller/railtie" 5 | require "action_mailer/railtie" 6 | require "active_resource/railtie" 7 | # require 'rails/test_unit/railtie' 8 | 9 | # If you have a Gemfile, require the gems listed there, including any gems 10 | # you've limited to :test, :development, or :production. 11 | Bundler.require(:default, Rails.env) if defined?(Bundler) 12 | 13 | module Rails3BackboneCoffeescript 14 | class Application < Rails::Application 15 | config.generators do |g| 16 | g.test_framework :rspec, :fixture => true 17 | g.fixture_replacement :fabrication 18 | end 19 | # Settings in config/environments/* take precedence over those specified here. 20 | # Application configuration should go into files in config/initializers 21 | # -- all .rb files in that directory are automatically loaded. 22 | 23 | # Custom directories with classes and modules you want to be autoloadable. 24 | # config.autoload_paths += %W(#{config.root}/extras) 25 | 26 | # Only load the plugins named here, in the order given (default is alphabetical). 27 | # :all can be used as a placeholder for all plugins not explicitly named. 28 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 29 | 30 | # Activate observers that should always be running. 31 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 32 | 33 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 34 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 35 | # config.time_zone = 'Central Time (US & Canada)' 36 | 37 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 38 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 39 | # config.i18n.default_locale = :de 40 | 41 | # JavaScript files you want as :defaults (application.js is always included). 42 | config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 43 | 44 | # Configure the default encoding used in templates for Ruby 1.9. 45 | config.encoding = "utf-8" 46 | 47 | # Configure sensitive parameters which will be filtered from the log file. 48 | config.filter_parameters += [:password, :password_confirmation] 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at http://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | errors: 5 | messages: 6 | not_found: "not found" 7 | already_confirmed: "was already confirmed, please try signing in" 8 | not_locked: "was not locked" 9 | not_saved: 10 | one: "1 error prohibited this %{resource} from being saved:" 11 | other: "%{count} errors prohibited this %{resource} from being saved:" 12 | 13 | devise: 14 | failure: 15 | unauthenticated: 'You need to sign in or sign up before continuing.' 16 | unconfirmed: 'You have to confirm your account before continuing.' 17 | locked: 'Your account is locked.' 18 | invalid: 'Invalid email or password.' 19 | invalid_token: 'Invalid authentication token.' 20 | timeout: 'Your session expired, please sign in again to continue.' 21 | inactive: 'Your account was not activated yet.' 22 | sessions: 23 | signed_in: 'Signed in successfully.' 24 | signed_out: 'Signed out successfully.' 25 | passwords: 26 | send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' 27 | updated: 'Your password was changed successfully. You are now signed in.' 28 | confirmations: 29 | send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' 30 | confirmed: 'Your account was successfully confirmed. You are now signed in.' 31 | registrations: 32 | signed_up: 'Welcome! You have signed up successfully.' 33 | inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.' 34 | updated: 'You updated your account successfully.' 35 | destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' 36 | unlocks: 37 | send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' 38 | unlocked: 'Your account was successfully unlocked. You are now signed in.' 39 | omniauth_callbacks: 40 | success: 'Successfully authorized from %{kind} account.' 41 | failure: 'Could not authorize you from %{kind} because "%{reason}".' 42 | mailer: 43 | confirmation_instructions: 44 | subject: 'Confirmation instructions' 45 | reset_password_instructions: 46 | subject: 'Reset password instructions' 47 | unlock_instructions: 48 | subject: 'Unlock Instructions' 49 | -------------------------------------------------------------------------------- /app/stylesheets/lib/_typography.scss: -------------------------------------------------------------------------------- 1 | /* Facebook Defaults */ 2 | 3 | $main-color: #555555; 4 | $main-family: "lucida grande",tahoma,verdana,arial,sans-serif; 5 | $helvetica-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | 7 | /* All vertical blocks should be in increments of 18 */ 8 | 9 | $baseline: 18px; 10 | 11 | @mixin default-header() { 12 | color:$main-color; 13 | font-family:$helvetica-family; 14 | //font-weight: bold; 15 | margin:0; 16 | padding:0; 17 | } 18 | 19 | @mixin h1() { 20 | @include default-header; 21 | font-size: 18px; 22 | line-height: 20px; 23 | padding-bottom: 20px; 24 | } 25 | 26 | @mixin h2() { 27 | @include default-header; 28 | font-size: 16px; 29 | line-height: 16px; 30 | } 31 | 32 | @mixin h3() { 33 | @include default-header; 34 | font-size: 14px; 35 | line-height: 18px; 36 | } 37 | 38 | @mixin h4() { 39 | @include default-header; 40 | font-size: 12px; 41 | line-height: 18px; 42 | } 43 | 44 | @mixin default-typography() { 45 | body { 46 | background:none repeat scroll 0 0 #FFFFFF; 47 | color:$main-color; 48 | direction:ltr; 49 | font-family:$helvetica-family; 50 | font-size:12px; 51 | margin:0; 52 | padding:0; 53 | text-align:left; 54 | unicode-bidi:embed; 55 | } 56 | 57 | h1 { @include h1; } 58 | h2 { @include h2; } 59 | h3 { @include h3; } 60 | h4 { @include h4; } 61 | 62 | p { 63 | font-family:$helvetica-family; 64 | font-size:13px; 65 | text-align:left; 66 | line-height: 20px; 67 | } 68 | 69 | a, a:visited { 70 | cursor:pointer; 71 | color:#0062C4; 72 | -moz-outline-style:none; 73 | text-decoration:none; 74 | font-weight: normal; 75 | } 76 | 77 | a:hover { 78 | text-decoration:none; 79 | } 80 | 81 | img { 82 | border:0; 83 | } 84 | 85 | select { 86 | border:1px solid #bdc7d8; 87 | font-family:$main-family; 88 | font-size:11px; 89 | padding:2px; 90 | } 91 | 92 | .pipe { 93 | color:#cccccc; 94 | padding:0 3px; 95 | font-weight: normal; 96 | font-size: 11px; 97 | } 98 | 99 | .clearfix:after { 100 | clear:both; 101 | content:"."; 102 | display:block; 103 | font-size:0; 104 | height:0; 105 | line-height:0; 106 | visibility:hidden; 107 | } 108 | 109 | .clearfix { 110 | display:block; 111 | zoom:1; 112 | } 113 | 114 | form { 115 | margin:0; 116 | padding:0; 117 | } 118 | 119 | label { 120 | cursor:pointer; 121 | //color:#666; 122 | color: #000; 123 | font-weight:bold; 124 | vertical-align:middle; 125 | } 126 | 127 | label input { 128 | font-weight:normal; 129 | } 130 | 131 | textarea, 132 | .inputtext, 133 | .inputpassword { 134 | border:1px solid #bdc7d8; 135 | font-family:$helvetica-family; 136 | font-size:11px; 137 | padding:3px; 138 | } 139 | 140 | .inputtext, 141 | .inputpassword { 142 | margin:0; 143 | *margin:-1px 0; 144 | padding-bottom:4px; 145 | } 146 | } 147 | 148 | strong { 149 | font-weight: bold; 150 | } -------------------------------------------------------------------------------- /public/assets/common.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}strong{font-weight:bold}body{background:none repeat scroll 0 0 #fff;color:#555;direction:ltr;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;margin:0;padding:0;text-align:left;unicode-bidi:embed}h1{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:18px;line-height:20px;padding-bottom:20px}h2{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:16px;line-height:16px}h3{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:14px;line-height:18px}h4{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:12px;line-height:18px}p{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;text-align:left;line-height:20px}a,a:visited{cursor:pointer;color:#0062c4;-moz-outline-style:none;text-decoration:none;font-weight:normal}a:hover{text-decoration:none}img{border:0}select{border:1px solid #bdc7d8;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;font-size:11px;padding:2px}.pipe{color:#ccc;padding:0 3px;font-weight:normal;font-size:11px}.clearfix:after{clear:both;content:".";display:block;font-size:0;height:0;line-height:0;visibility:hidden}.clearfix{display:block;zoom:1}form{margin:0;padding:0}label{cursor:pointer;color:#000;font-weight:bold;vertical-align:middle}label input{font-weight:normal}textarea,.inputtext,.inputpassword{border:1px solid #bdc7d8;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:11px;padding:3px}.inputtext,.inputpassword{margin:0;*margin:-1px 0;padding-bottom:4px}.clearfix{clear:both}.container{margin-left:auto;margin-right:auto;width:960px;padding-top:10px}body{background-color:#282828;color:#333;font-family:Arial,Helvetica,sans-serif;font-size:14px;margin:0;padding:0 0 40px}#main{background-color:#e9e9e9;min-height:700px}#footer{background-color:#282828;padding:20px 0}#flash-messages{background:#bcdd5b;left:0;line-height:55px;z-index:1}#flash-messages .container{padding-top:0}#flash-messages .alert{background:#f05931}#flash-messages .notice,#flash-messages .alert{padding:15px 0}#flash-messages p{line-height:32px;color:white;display:block;font-size:16px;font-weight:bold}#header{-webkit-box-shadow:#558a21 -2px 1px 2px;background-color:#282828;background-image:-webkit-gradient(linear,0% 0,0% 100%,color-stop(0%,#333),color-stop(100%,#1a1a1a));background-image:-moz-linear-gradient(top,#333 0,#1a1a1a 100%);background-image:linear-gradient(top,#333 0,#1a1a1a 100%);border-bottom:3px solid #4bc1de;height:45px}#header #logo{display:inline;float:left;margin-left:10px;margin-right:10px;width:380px}#header #logo h1{color:#fff;font-size:26px;padding-top:5px;padding-bottom:5px}#nav{display:inline;float:left;margin-left:10px;margin-right:10px;width:540px}#nav ul{margin:0;padding:0;border:0;outline:0;overflow:hidden;*zoom:1;padding:10px 0 10px 0;float:right}#nav ul li{list-style-image:none;list-style-type:none;margin-left:0;white-space:nowrap;display:inline;float:left;padding-left:10px;padding-right:10px}#nav ul li:first-child,#nav ul li.first{padding-left:0}#nav ul li:last-child,#nav ul li.last{padding-right:0} -------------------------------------------------------------------------------- /public/assets/common-datauri.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}strong{font-weight:bold}body{background:none repeat scroll 0 0 #fff;color:#555;direction:ltr;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;margin:0;padding:0;text-align:left;unicode-bidi:embed}h1{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:18px;line-height:20px;padding-bottom:20px}h2{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:16px;line-height:16px}h3{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:14px;line-height:18px}h4{color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;margin:0;padding:0;font-size:12px;line-height:18px}p{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;text-align:left;line-height:20px}a,a:visited{cursor:pointer;color:#0062c4;-moz-outline-style:none;text-decoration:none;font-weight:normal}a:hover{text-decoration:none}img{border:0}select{border:1px solid #bdc7d8;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;font-size:11px;padding:2px}.pipe{color:#ccc;padding:0 3px;font-weight:normal;font-size:11px}.clearfix:after{clear:both;content:".";display:block;font-size:0;height:0;line-height:0;visibility:hidden}.clearfix{display:block;zoom:1}form{margin:0;padding:0}label{cursor:pointer;color:#000;font-weight:bold;vertical-align:middle}label input{font-weight:normal}textarea,.inputtext,.inputpassword{border:1px solid #bdc7d8;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:11px;padding:3px}.inputtext,.inputpassword{margin:0;*margin:-1px 0;padding-bottom:4px}.clearfix{clear:both}.container{margin-left:auto;margin-right:auto;width:960px;padding-top:10px}body{background-color:#282828;color:#333;font-family:Arial,Helvetica,sans-serif;font-size:14px;margin:0;padding:0 0 40px}#main{background-color:#e9e9e9;min-height:700px}#footer{background-color:#282828;padding:20px 0}#flash-messages{background:#bcdd5b;left:0;line-height:55px;z-index:1}#flash-messages .container{padding-top:0}#flash-messages .alert{background:#f05931}#flash-messages .notice,#flash-messages .alert{padding:15px 0}#flash-messages p{line-height:32px;color:white;display:block;font-size:16px;font-weight:bold}#header{-webkit-box-shadow:#558a21 -2px 1px 2px;background-color:#282828;background-image:-webkit-gradient(linear,0% 0,0% 100%,color-stop(0%,#333),color-stop(100%,#1a1a1a));background-image:-moz-linear-gradient(top,#333 0,#1a1a1a 100%);background-image:linear-gradient(top,#333 0,#1a1a1a 100%);border-bottom:3px solid #4bc1de;height:45px}#header #logo{display:inline;float:left;margin-left:10px;margin-right:10px;width:380px}#header #logo h1{color:#fff;font-size:26px;padding-top:5px;padding-bottom:5px}#nav{display:inline;float:left;margin-left:10px;margin-right:10px;width:540px}#nav ul{margin:0;padding:0;border:0;outline:0;overflow:hidden;*zoom:1;padding:10px 0 10px 0;float:right}#nav ul li{list-style-image:none;list-style-type:none;margin-left:0;white-space:nowrap;display:inline;float:left;padding-left:10px;padding-right:10px}#nav ul li:first-child,#nav ul li.first{padding-left:0}#nav ul li:last-child,#nav ul li.last{padding-right:0} -------------------------------------------------------------------------------- /spec/controllers/projects_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # This spec was generated by rspec-rails when you ran the scaffold generator. 4 | # It demonstrates how one might use RSpec to specify the controller code that 5 | # was generated by the Rails when you ran the scaffold generator. 6 | 7 | describe ProjectsController do 8 | 9 | def mock_project(stubs={}) 10 | @mock_project ||= mock_model(Project, stubs).as_null_object 11 | end 12 | 13 | describe "GET index" do 14 | it "assigns all projects as @projects" do 15 | Project.stub(:all) { [mock_project] } 16 | get :index 17 | assigns(:projects).should eq([mock_project]) 18 | end 19 | end 20 | 21 | describe "GET show" do 22 | it "assigns the requested project as @project" do 23 | Project.stub(:find).with("37") { mock_project } 24 | get :show, :id => "37" 25 | assigns(:project).should be(mock_project) 26 | end 27 | end 28 | 29 | describe "GET new" do 30 | it "assigns a new project as @project" do 31 | Project.stub(:new) { mock_project } 32 | get :new 33 | assigns(:project).should be(mock_project) 34 | end 35 | end 36 | 37 | describe "GET edit" do 38 | it "assigns the requested project as @project" do 39 | Project.stub(:find).with("37") { mock_project } 40 | get :edit, :id => "37" 41 | assigns(:project).should be(mock_project) 42 | end 43 | end 44 | 45 | describe "POST create" do 46 | describe "with valid params" do 47 | it "assigns a newly created project as @project" do 48 | Project.stub(:new).with({'these' => 'params'}) { mock_project(:save => true) } 49 | post :create, :project => {'these' => 'params'} 50 | assigns(:project).should be(mock_project) 51 | end 52 | 53 | it "redirects to the created project" do 54 | Project.stub(:new) { mock_project(:save => true) } 55 | post :create, :project => {} 56 | response.should redirect_to(project_url(mock_project)) 57 | end 58 | end 59 | 60 | describe "with invalid params" do 61 | it "assigns a newly created but unsaved project as @project" do 62 | Project.stub(:new).with({'these' => 'params'}) { mock_project(:save => false) } 63 | post :create, :project => {'these' => 'params'} 64 | assigns(:project).should be(mock_project) 65 | end 66 | 67 | it "re-renders the 'new' template" do 68 | Project.stub(:new) { mock_project(:save => false) } 69 | post :create, :project => {} 70 | response.should render_template("new") 71 | end 72 | end 73 | end 74 | 75 | describe "PUT update" do 76 | describe "with valid params" do 77 | it "updates the requested project" do 78 | Project.stub(:find).with("37") { mock_project } 79 | mock_project.should_receive(:update_attributes).with({'these' => 'params'}) 80 | put :update, :id => "37", :project => {'these' => 'params'} 81 | end 82 | 83 | it "assigns the requested project as @project" do 84 | Project.stub(:find) { mock_project(:update_attributes => true) } 85 | put :update, :id => "1" 86 | assigns(:project).should be(mock_project) 87 | end 88 | 89 | it "redirects to the project" do 90 | Project.stub(:find) { mock_project(:update_attributes => true) } 91 | put :update, :id => "1" 92 | response.should redirect_to(project_url(mock_project)) 93 | end 94 | end 95 | 96 | describe "with invalid params" do 97 | it "assigns the project as @project" do 98 | Project.stub(:find) { mock_project(:update_attributes => false) } 99 | put :update, :id => "1" 100 | assigns(:project).should be(mock_project) 101 | end 102 | 103 | it "re-renders the 'edit' template" do 104 | Project.stub(:find) { mock_project(:update_attributes => false) } 105 | put :update, :id => "1" 106 | response.should render_template("edit") 107 | end 108 | end 109 | end 110 | 111 | describe "DELETE destroy" do 112 | it "destroys the requested project" do 113 | Project.stub(:find).with("37") { mock_project } 114 | mock_project.should_receive(:destroy) 115 | delete :destroy, :id => "37" 116 | end 117 | 118 | it "redirects to the projects list" do 119 | Project.stub(:find) { mock_project } 120 | delete :destroy, :id => "1" 121 | response.should redirect_to(projects_url) 122 | end 123 | end 124 | 125 | end 126 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/mongoid/mongoid.git 3 | revision: 4a40af7272da83893d8fb1c3bf430121473b921e 4 | specs: 5 | mongoid (2.0.0.rc.7) 6 | activemodel (~> 3.0) 7 | mongo (~> 1.2) 8 | tzinfo (~> 0.3.22) 9 | will_paginate (~> 3.0.pre) 10 | 11 | GEM 12 | remote: http://rubygems.org/ 13 | specs: 14 | abstract (1.0.0) 15 | actionmailer (3.0.5) 16 | actionpack (= 3.0.5) 17 | mail (~> 2.2.15) 18 | actionpack (3.0.5) 19 | activemodel (= 3.0.5) 20 | activesupport (= 3.0.5) 21 | builder (~> 2.1.2) 22 | erubis (~> 2.6.6) 23 | i18n (~> 0.4) 24 | rack (~> 1.2.1) 25 | rack-mount (~> 0.6.13) 26 | rack-test (~> 0.5.7) 27 | tzinfo (~> 0.3.23) 28 | activemodel (3.0.5) 29 | activesupport (= 3.0.5) 30 | builder (~> 2.1.2) 31 | i18n (~> 0.4) 32 | activerecord (3.0.5) 33 | activemodel (= 3.0.5) 34 | activesupport (= 3.0.5) 35 | arel (~> 2.0.2) 36 | tzinfo (~> 0.3.23) 37 | activeresource (3.0.5) 38 | activemodel (= 3.0.5) 39 | activesupport (= 3.0.5) 40 | activesupport (3.0.5) 41 | archive-tar-minitar (0.5.2) 42 | arel (2.0.9) 43 | barista (1.0.0) 44 | coffee-script (~> 2.1.1) 45 | bcrypt-ruby (2.1.4) 46 | bson (1.2.4) 47 | bson_ext (1.2.4) 48 | builder (2.1.2) 49 | closure-compiler (1.0.0) 50 | coffee-script (2.1.3) 51 | coffee-script-source 52 | coffee-script-source (1.0.1) 53 | columnize (0.3.2) 54 | compass (0.10.6) 55 | haml (>= 3.0.4) 56 | compass-960-plugin (0.10.1) 57 | compass (>= 0.10.0) 58 | database_cleaner (0.6.5) 59 | devise (1.2.rc2) 60 | bcrypt-ruby (~> 2.1.2) 61 | orm_adapter (~> 0.0.3) 62 | warden (~> 1.0.3) 63 | diff-lcs (1.1.2) 64 | erubis (2.6.6) 65 | abstract (>= 1.0.0) 66 | fabrication (0.9.5) 67 | haml (3.0.25) 68 | haml-rails (0.3.4) 69 | actionpack (~> 3.0) 70 | activesupport (~> 3.0) 71 | haml (~> 3.0) 72 | railties (~> 3.0) 73 | has_scope (0.5.0) 74 | i18n (0.5.0) 75 | inherited_resources (1.2.1) 76 | has_scope (~> 0.5.0) 77 | responders (~> 0.6.0) 78 | jammit (0.6.0) 79 | closure-compiler (>= 0.1.0) 80 | yui-compressor (>= 0.9.1) 81 | linecache19 (0.5.11) 82 | ruby_core_source (>= 0.1.4) 83 | mail (2.2.15) 84 | activesupport (>= 2.3.6) 85 | i18n (>= 0.4.0) 86 | mime-types (~> 1.16) 87 | treetop (~> 1.4.8) 88 | mime-types (1.16) 89 | mongo (1.2.4) 90 | bson (>= 1.2.4) 91 | mongoid-rspec (1.4.1) 92 | mongoid (~> 2.0.0.rc.7) 93 | mongoid-rspec 94 | rspec (~> 2) 95 | orm_adapter (0.0.4) 96 | polyglot (0.3.1) 97 | rack (1.2.2) 98 | rack-mount (0.6.13) 99 | rack (>= 1.0.0) 100 | rack-test (0.5.7) 101 | rack (>= 1.0) 102 | rails (3.0.5) 103 | actionmailer (= 3.0.5) 104 | actionpack (= 3.0.5) 105 | activerecord (= 3.0.5) 106 | activeresource (= 3.0.5) 107 | activesupport (= 3.0.5) 108 | bundler (~> 1.0) 109 | railties (= 3.0.5) 110 | railties (3.0.5) 111 | actionpack (= 3.0.5) 112 | activesupport (= 3.0.5) 113 | rake (>= 0.8.7) 114 | thor (~> 0.14.4) 115 | rake (0.8.7) 116 | responders (0.6.2) 117 | rspec (2.5.0) 118 | rspec-core (~> 2.5.0) 119 | rspec-expectations (~> 2.5.0) 120 | rspec-mocks (~> 2.5.0) 121 | rspec-core (2.5.1) 122 | rspec-expectations (2.5.0) 123 | diff-lcs (~> 1.1.2) 124 | rspec-mocks (2.5.0) 125 | rspec-rails (2.5.0) 126 | actionpack (~> 3.0) 127 | activesupport (~> 3.0) 128 | railties (~> 3.0) 129 | rspec (~> 2.5.0) 130 | ruby-debug-base19 (0.11.24) 131 | columnize (>= 0.3.1) 132 | linecache19 (>= 0.5.11) 133 | ruby_core_source (>= 0.1.4) 134 | ruby-debug19 (0.11.6) 135 | columnize (>= 0.3.1) 136 | linecache19 (>= 0.5.11) 137 | ruby-debug-base19 (>= 0.11.19) 138 | ruby_core_source (0.1.4) 139 | archive-tar-minitar (>= 0.5.2) 140 | thor (0.14.6) 141 | treetop (1.4.9) 142 | polyglot (>= 0.3.1) 143 | tzinfo (0.3.25) 144 | warden (1.0.3) 145 | rack (>= 1.0.0) 146 | will_paginate (3.0.pre2) 147 | yui-compressor (0.9.4) 148 | 149 | PLATFORMS 150 | ruby 151 | 152 | DEPENDENCIES 153 | barista (~> 1.0) 154 | bson_ext (>= 1.2.4) 155 | compass 156 | compass-960-plugin 157 | database_cleaner 158 | devise (>= 1.2.rc) 159 | fabrication 160 | haml (>= 3.0.0) 161 | haml-rails 162 | has_scope 163 | inherited_resources (~> 1.2.1) 164 | jammit 165 | mongoid! 166 | mongoid-rspec (>= 1.4.1) 167 | rails (= 3.0.5) 168 | rspec-rails (>= 2.5) 169 | ruby-debug19 170 | -------------------------------------------------------------------------------- /public/javascripts/vendor/rails.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unobtrusive scripting adapter for jQuery 3 | * 4 | * Requires jQuery 1.4.3 or later. 5 | * https://github.com/rails/jquery-ujs 6 | */ 7 | 8 | (function($) { 9 | // Make sure that every Ajax request sends the CSRF token 10 | function CSRFProtection(xhr) { 11 | var token = $('meta[name="csrf-token"]').attr('content'); 12 | if (token) xhr.setRequestHeader('X-CSRF-Token', token); 13 | } 14 | if ('ajaxPrefilter' in $) $.ajaxPrefilter(function(options, originalOptions, xhr){ CSRFProtection(xhr) }); 15 | else $(document).ajaxSend(function(e, xhr){ CSRFProtection(xhr) }); 16 | 17 | // Triggers an event on an element and returns the event result 18 | function fire(obj, name, data) { 19 | var event = $.Event(name); 20 | obj.trigger(event, data); 21 | return event.result !== false; 22 | } 23 | 24 | // Submits "remote" forms and links with ajax 25 | function handleRemote(element) { 26 | var method, url, data, 27 | dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); 28 | 29 | if (fire(element, 'ajax:before')) { 30 | if (element.is('form')) { 31 | method = element.attr('method'); 32 | url = element.attr('action'); 33 | data = element.serializeArray(); 34 | // memoized value from clicked submit button 35 | var button = element.data('ujs:submit-button'); 36 | if (button) { 37 | data.push(button); 38 | element.data('ujs:submit-button', null); 39 | } 40 | } else { 41 | method = element.data('method'); 42 | url = element.attr('href'); 43 | data = null; 44 | } 45 | $.ajax({ 46 | url: url, type: method || 'GET', data: data, dataType: dataType, 47 | // stopping the "ajax:beforeSend" event will cancel the ajax request 48 | beforeSend: function(xhr, settings) { 49 | if (settings.dataType === undefined) { 50 | xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); 51 | } 52 | return fire(element, 'ajax:beforeSend', [xhr, settings]); 53 | }, 54 | success: function(data, status, xhr) { 55 | element.trigger('ajax:success', [data, status, xhr]); 56 | }, 57 | complete: function(xhr, status) { 58 | element.trigger('ajax:complete', [xhr, status]); 59 | }, 60 | error: function(xhr, status, error) { 61 | element.trigger('ajax:error', [xhr, status, error]); 62 | } 63 | }); 64 | } 65 | } 66 | 67 | // Handles "data-method" on links such as: 68 | // Delete 69 | function handleMethod(link) { 70 | var href = link.attr('href'), 71 | method = link.data('method'), 72 | csrf_token = $('meta[name=csrf-token]').attr('content'), 73 | csrf_param = $('meta[name=csrf-param]').attr('content'), 74 | form = $('
'), 75 | metadata_input = ''; 76 | 77 | if (csrf_param !== undefined && csrf_token !== undefined) { 78 | metadata_input += ''; 79 | } 80 | 81 | form.hide().append(metadata_input).appendTo('body'); 82 | form.submit(); 83 | } 84 | 85 | function disableFormElements(form) { 86 | form.find('input[data-disable-with], button[data-disable-with]').each(function() { 87 | var element = $(this), method = element.is('button') ? 'html' : 'val'; 88 | element.data('ujs:enable-with', element[method]()); 89 | element[method](element.data('disable-with')); 90 | element.attr('disabled', 'disabled'); 91 | }); 92 | } 93 | 94 | function enableFormElements(form) { 95 | form.find('input[data-disable-with]:disabled, button[data-disable-with]:disabled').each(function() { 96 | var element = $(this), method = element.is('button') ? 'html' : 'val'; 97 | if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); 98 | element.removeAttr('disabled'); 99 | }); 100 | } 101 | 102 | function allowAction(element) { 103 | var message = element.data('confirm'); 104 | return !message || (fire(element, 'confirm') && confirm(message)); 105 | } 106 | 107 | function requiredValuesMissing(form) { 108 | var missing = false; 109 | form.find('input[name][required]').each(function() { 110 | if (!$(this).val()) missing = true; 111 | }); 112 | return missing; 113 | } 114 | 115 | $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) { 116 | var link = $(this); 117 | if (!allowAction(link)) return false; 118 | 119 | if (link.data('remote') != undefined) { 120 | handleRemote(link); 121 | return false; 122 | } else if (link.data('method')) { 123 | handleMethod(link); 124 | return false; 125 | } 126 | }); 127 | 128 | $('form').live('submit.rails', function(e) { 129 | var form = $(this), remote = form.data('remote') != undefined; 130 | if (!allowAction(form)) return false; 131 | 132 | // skip other logic when required values are missing 133 | if (requiredValuesMissing(form)) return !remote; 134 | 135 | if (remote) { 136 | handleRemote(form); 137 | return false; 138 | } else { 139 | // slight timeout so that the submit button gets properly serialized 140 | setTimeout(function(){ disableFormElements(form) }, 13); 141 | } 142 | }); 143 | 144 | $('form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])').live('click.rails', function() { 145 | var button = $(this); 146 | if (!allowAction(button)) return false; 147 | // register the pressed submit button 148 | var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null; 149 | button.closest('form').data('ujs:submit-button', data); 150 | }); 151 | 152 | $('form').live('ajax:beforeSend.rails', function(event) { 153 | if (this == event.target) disableFormElements($(this)); 154 | }); 155 | 156 | $('form').live('ajax:complete.rails', function(event) { 157 | if (this == event.target) enableFormElements($(this)); 158 | }); 159 | })( jQuery ); 160 | -------------------------------------------------------------------------------- /public/javascripts/vendor/jquery.datalink.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Data Link plugin 1.0.0pre 3 | * http://github.com/jquery/jquery-datalink 4 | * 5 | * Copyright Software Freedom Conservancy, Inc. 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | */ 9 | (function( $, undefined ){ 10 | 11 | var oldcleandata = $.cleanData, 12 | links = [], 13 | fnSetters = { 14 | val: "val", 15 | html: "html", 16 | text: "text" 17 | }, 18 | eventNameSetField = "setField", 19 | eventNameChangeField = "changeField"; 20 | 21 | function getLinks(obj) { 22 | var data = $.data( obj ), 23 | cache, 24 | fn = data._getLinks || (cache={s:[], t:[]}, data._getLinks = function() { return cache; }); 25 | return fn(); 26 | } 27 | 28 | function bind(obj, wrapped, handler) { 29 | wrapped.bind( obj.nodeType ? "change" : eventNameChangeField, handler ); 30 | } 31 | function unbind(obj, wrapped, handler) { 32 | wrapped.unbind( obj.nodeType ? "change" : eventNameChangeField, handler ); 33 | } 34 | 35 | $.extend({ 36 | cleanData: function( elems ) { 37 | for ( var j, i = 0, elem; (elem = elems[i]) != null; i++ ) { 38 | // remove any links with this element as the source 39 | // or the target. 40 | var links = $.data( elem, "_getLinks" ); 41 | if ( links ) { 42 | links = links(); 43 | // links this element is the source of 44 | var self = $(elem); 45 | $.each(links.s, function() { 46 | unbind( elem, self, this.handler ); 47 | if ( this.handlerRev ) { 48 | unbind( this.target, $(this.target), this.handlerRev ); 49 | } 50 | }); 51 | // links this element is the target of 52 | $.each(links.t, function() { 53 | unbind( this.source, $(this.source), this.handler ); 54 | if ( this.handlerRev ) { 55 | unbind( elem, self, this.handlerRev ); 56 | } 57 | }); 58 | links.s = []; 59 | links.t = []; 60 | } 61 | } 62 | oldcleandata( elems ); 63 | }, 64 | convertFn: { 65 | "!": function(value) { 66 | return !value; 67 | } 68 | }, 69 | setField: function(target, field, value) { 70 | if ( target.nodeType ) { 71 | var setter = fnSetters[ field ] || "attr"; 72 | $(target)[setter](value); 73 | } else { 74 | var parts = field.split("."); 75 | parts[1] = parts[1] ? "." + parts[1] : ""; 76 | 77 | var $this = $( target ), 78 | args = [ parts[0], value ]; 79 | 80 | $this.triggerHandler( eventNameSetField + parts[1] + "!", args ); 81 | if ( value !== undefined ) { 82 | target[ field ] = value; 83 | } 84 | $this.triggerHandler( eventNameChangeField + parts[1] + "!", args ); 85 | } 86 | } 87 | }); 88 | 89 | function getMapping(ev, changed, newvalue, map) { 90 | var target = ev.target, 91 | isSetData = ev.type === eventNameChangeField, 92 | mappedName, 93 | convert, 94 | name; 95 | if ( isSetData ) { 96 | name = changed; 97 | if ( ev.namespace ) { 98 | name += "." + ev.namespace; 99 | } 100 | } else { 101 | name = (target.name || target.id); 102 | } 103 | 104 | if ( !map ) { 105 | mappedName = name; 106 | } else { 107 | var m = map[ name ]; 108 | if ( !m ) { 109 | return null; 110 | } 111 | mappedName = m.name; 112 | convert = m.convert; 113 | if ( typeof convert === "string" ) { 114 | convert = $.convertFn[ convert ]; 115 | } 116 | } 117 | return { 118 | name: mappedName, 119 | convert: convert, 120 | value: isSetData ? newvalue : $(target).val() 121 | }; 122 | } 123 | 124 | $.extend($.fn, { 125 | link: function(target, mapping) { 126 | var self = this; 127 | if ( !target ) { 128 | return self; 129 | } 130 | function matchByName(name) { 131 | var selector = "[name=" + name + "], [id=" + name +"]"; 132 | // include elements in this set that match as well a child matches 133 | return self.filter(selector).add(self.find(selector)); 134 | } 135 | if ( typeof target === "string" ) { 136 | target = $( target, this.context || null )[ 0 ]; 137 | } 138 | var hasTwoWay = !mapping, 139 | map, 140 | mapRev, 141 | handler = function(ev, changed, newvalue) { 142 | // a dom element change event occurred, update the target 143 | var m = getMapping( ev, changed, newvalue, map ); 144 | if ( m ) { 145 | var name = m.name, 146 | value = m.value, 147 | convert = m.convert; 148 | if ( convert ) { 149 | value = convert( value, ev.target, target ); 150 | } 151 | if ( value !== undefined ) { 152 | $.setField( target, name, value ); 153 | } 154 | } 155 | }, 156 | handlerRev = function(ev, changed, newvalue) { 157 | // a change or changeData event occurred on the target, 158 | // update the corresponding source elements 159 | var m = getMapping( ev, changed, newvalue, mapRev ); 160 | if ( m ) { 161 | var name = m.name, 162 | value = m.value, 163 | convert = m.convert; 164 | // find elements within the original selector 165 | // that have the same name or id as the field that updated 166 | matchByName(name).each(function() { 167 | newvalue = value; 168 | if ( convert ) { 169 | newvalue = convert( newvalue, target, this ); 170 | } 171 | if ( newvalue !== undefined ) { 172 | $.setField( this, "val", newvalue ); 173 | } 174 | }); 175 | } 176 | 177 | }; 178 | if ( mapping ) { 179 | $.each(mapping, function(n, v) { 180 | var name = v, 181 | convert, 182 | convertBack, 183 | twoWay; 184 | if ( $.isPlainObject( v ) ) { 185 | name = v.name || n; 186 | convert = v.convert; 187 | convertBack = v.convertBack; 188 | twoWay = v.twoWay !== false; 189 | hasTwoWay |= twoWay; 190 | } else { 191 | hasTwoWay = twoWay = true; 192 | } 193 | if ( twoWay ) { 194 | mapRev = mapRev || {}; 195 | mapRev[ n ] = { 196 | name: name, 197 | convert: convertBack 198 | }; 199 | } 200 | map = map || {}; 201 | map[ name ] = { name: n, convert: convert, twoWay: twoWay }; 202 | }); 203 | } 204 | 205 | // associate the link with each source and target so it can be 206 | // removed automaticaly when _either_ side is removed. 207 | self.each(function() { 208 | bind( this, $(this), handler ); 209 | var link = { 210 | handler: handler, 211 | handlerRev: hasTwoWay ? handlerRev : null, 212 | target: target, 213 | source: this 214 | }; 215 | getLinks( this ).s.push( link ); 216 | if ( target.nodeType ) { 217 | getLinks( target ).t.push( link ); 218 | } 219 | }); 220 | if ( hasTwoWay ) { 221 | bind( target, $(target), handlerRev ); 222 | } 223 | return self; 224 | }, 225 | unlink: function(target) { 226 | this.each(function() { 227 | var self = $(this), 228 | links = getLinks( this ).s; 229 | for (var i = links.length-1; i >= 0; i--) { 230 | var link = links[ i ]; 231 | if ( link.target === target ) { 232 | // unbind the handlers 233 | //wrapped.unbind( obj.nodeType ? "change" : "changeData", handler ); 234 | unbind( this, self, link.handler ); 235 | if ( link.handlerRev ) { 236 | unbind( link.target, $(link.target), link.handlerRev ); 237 | } 238 | // remove from source links 239 | links.splice( i, 1 ); 240 | // remove from target links 241 | var targetLinks = getLinks( link.target ).t, 242 | index = $.inArray( link, targetLinks ); 243 | if ( index !== -1 ) { 244 | targetLinks.splice( index, 1 ); 245 | } 246 | } 247 | } 248 | }); 249 | }, 250 | setField: function(field, value) { 251 | return this.each(function() { 252 | $.setField( this, field, value ); 253 | }); 254 | } 255 | }); 256 | 257 | })(jQuery); 258 | -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure devise mailer, warden hooks and so forth. The first 2 | # four configuration values can also be set straight in your models. 3 | Devise.setup do |config| 4 | # ==> Mailer Configuration 5 | # Configure the e-mail address which will be shown in DeviseMailer. 6 | config.mailer_sender = "please-change-me@config-initializers-devise.com" 7 | 8 | # Configure the class responsible to send e-mails. 9 | # config.mailer = "Devise::Mailer" 10 | 11 | # ==> ORM configuration 12 | # Load and configure the ORM. Supports :active_record (default) and 13 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 14 | # available as additional gems. 15 | require 'devise/orm/mongoid' 16 | 17 | # ==> Configuration for any authentication mechanism 18 | # Configure which keys are used when authenticating a user. The default is 19 | # just :email. You can configure it to use [:username, :subdomain], so for 20 | # authenticating a user, both parameters are required. Remember that those 21 | # parameters are used only when authenticating and not when retrieving from 22 | # session. If you need permissions, you should implement that in a before filter. 23 | # You can also supply a hash where the value is a boolean determining whether 24 | # or not authentication should be aborted when the value is not present. 25 | # config.authentication_keys = [ :email ] 26 | 27 | # Configure parameters from the request object used for authentication. Each entry 28 | # given should be a request method and it will automatically be passed to the 29 | # find_for_authentication method and considered in your model lookup. For instance, 30 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 31 | # The same considerations mentioned for authentication_keys also apply to request_keys. 32 | # config.request_keys = [] 33 | 34 | # Configure which authentication keys should be case-insensitive. 35 | # These keys will be downcased upon creating or modifying a user and when used 36 | # to authenticate or find a user. Default is :email. 37 | config.case_insensitive_keys = [ :email ] 38 | 39 | # Tell if authentication through request.params is enabled. True by default. 40 | # config.params_authenticatable = true 41 | 42 | # Tell if authentication through HTTP Basic Auth is enabled. False by default. 43 | # config.http_authenticatable = false 44 | 45 | # If http headers should be returned for AJAX requests. True by default. 46 | # config.http_authenticatable_on_xhr = true 47 | 48 | # The realm used in Http Basic Authentication. "Application" by default. 49 | # config.http_authentication_realm = "Application" 50 | 51 | # ==> Configuration for :database_authenticatable 52 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If 53 | # using other encryptors, it sets how many times you want the password re-encrypted. 54 | config.stretches = 10 55 | 56 | # Setup a pepper to generate the encrypted password. 57 | # config.pepper = "231e07a7b839d76aebba2727a1ea32b1be903eb8ddedeb9307d19369ffc7c8a3ad549b80487ec1fe74be3f01f0936dd22549310498b509b2cf286be5ef6036a0" 58 | 59 | # ==> Configuration for :confirmable 60 | # The time you want to give your user to confirm his account. During this time 61 | # he will be able to access your application without confirming. Default is 0.days 62 | # When confirm_within is zero, the user won't be able to sign in without confirming. 63 | # You can use this to let your user access some features of your application 64 | # without confirming the account, but blocking it after a certain period 65 | # (ie 2 days). 66 | # config.confirm_within = 2.days 67 | 68 | # Defines which key will be used when confirming an account 69 | # config.confirmation_keys = [ :email ] 70 | 71 | # ==> Configuration for :rememberable 72 | # The time the user will be remembered without asking for credentials again. 73 | # config.remember_for = 2.weeks 74 | 75 | # If true, a valid remember token can be re-used between multiple browsers. 76 | # config.remember_across_browsers = true 77 | 78 | # If true, extends the user's remember period when remembered via cookie. 79 | # config.extend_remember_period = false 80 | 81 | # If true, uses the password salt as remember token. This should be turned 82 | # to false if you are not using database authenticatable. 83 | config.use_salt_as_remember_token = true 84 | 85 | # ==> Configuration for :validatable 86 | # Range for password length. Default is 6..20. 87 | # config.password_length = 6..20 88 | 89 | # Regex to use to validate the email address 90 | # config.email_regexp = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i 91 | 92 | # ==> Configuration for :timeoutable 93 | # The time you want to timeout the user session without activity. After this 94 | # time the user will be asked for credentials again. Default is 30 minutes. 95 | # config.timeout_in = 30.minutes 96 | 97 | # ==> Configuration for :lockable 98 | # Defines which strategy will be used to lock an account. 99 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 100 | # :none = No lock strategy. You should handle locking by yourself. 101 | # config.lock_strategy = :failed_attempts 102 | 103 | # Defines which key will be used when locking and unlocking an account 104 | # config.unlock_keys = [ :email ] 105 | 106 | # Defines which strategy will be used to unlock an account. 107 | # :email = Sends an unlock link to the user email 108 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 109 | # :both = Enables both strategies 110 | # :none = No unlock strategy. You should handle unlocking by yourself. 111 | # config.unlock_strategy = :both 112 | 113 | # Number of authentication tries before locking an account if lock_strategy 114 | # is failed attempts. 115 | # config.maximum_attempts = 20 116 | 117 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 118 | # config.unlock_in = 1.hour 119 | 120 | # ==> Configuration for :recoverable 121 | # 122 | # Defines which key will be used when recovering the password for an account 123 | # config.reset_password_keys = [ :email ] 124 | 125 | # ==> Configuration for :encryptable 126 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use 127 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, 128 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) 129 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy 130 | # REST_AUTH_SITE_KEY to pepper) 131 | # config.encryptor = :sha512 132 | 133 | # ==> Configuration for :token_authenticatable 134 | # Defines name of the authentication token params key 135 | # config.token_authentication_key = :auth_token 136 | 137 | # If true, authentication through token does not store user in session and needs 138 | # to be supplied on each request. Useful if you are using the token as API token. 139 | # config.stateless_token = false 140 | 141 | # ==> Scopes configuration 142 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 143 | # "users/sessions/new". It's turned off by default because it's slower if you 144 | # are using only default views. 145 | config.scoped_views = true 146 | 147 | # Configure the default scope given to Warden. By default it's the first 148 | # devise role declared in your routes (usually :user). 149 | # config.default_scope = :user 150 | 151 | # Configure sign_out behavior. 152 | # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). 153 | # The default is true, which means any logout action will sign out all active scopes. 154 | # config.sign_out_all_scopes = true 155 | 156 | # ==> Navigation configuration 157 | # Lists the formats that should be treated as navigational. Formats like 158 | # :html, should redirect to the sign in page when the user does not have 159 | # access, but formats like :xml or :json, should return 401. 160 | # 161 | # If you have any extra navigational formats, like :iphone or :mobile, you 162 | # should add them to the navigational formats lists. 163 | # 164 | # The :"*/*" and "*/*" formats below is required to match Internet 165 | # Explorer requests. 166 | # config.navigational_formats = [:"*/*", "*/*", :html] 167 | 168 | # The default HTTP method used to sign out a resource. Default is :get. 169 | # config.sign_out_via = :get 170 | 171 | # ==> OmniAuth 172 | # Add a new OmniAuth provider. Check the wiki for more information on setting 173 | # up on your models and hooks. 174 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' 175 | 176 | # ==> Warden configuration 177 | # If you want to use other strategies, that are not supported by Devise, or 178 | # change the failure app, you can configure them inside the config.warden block. 179 | # 180 | # config.warden do |manager| 181 | # manager.failure_app = AnotherApp 182 | # manager.intercept_401 = false 183 | # manager.default_strategies(:scope => :user).unshift :some_external_strategy 184 | # end 185 | end 186 | -------------------------------------------------------------------------------- /public/stylesheets/compiled/screen.css: -------------------------------------------------------------------------------- 1 | /*@import "compass/reset"; 2 | */ 3 | /* http://meyerweb.com/eric/tools/css/reset/ 4 | v2.0b1 | 201101 5 | NOTE: WORK IN PROGRESS 6 | USE WITH CAUTION AND TEST WITH ABANDON */ 7 | /* line 17, ../../../app/stylesheets/lib/_reset.scss */ 8 | html, body, div, span, applet, object, iframe, 9 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 10 | a, abbr, acronym, address, big, cite, code, 11 | del, dfn, em, img, ins, kbd, q, s, samp, 12 | small, strike, strong, sub, sup, tt, var, 13 | b, u, i, center, 14 | dl, dt, dd, ol, ul, li, 15 | fieldset, form, label, legend, 16 | table, caption, tbody, tfoot, thead, tr, th, td, 17 | article, aside, canvas, details, figcaption, figure, 18 | footer, header, hgroup, menu, nav, section, summary, 19 | time, mark, audio, video { 20 | margin: 0; 21 | padding: 0; 22 | border: 0; 23 | outline: 0; 24 | font-size: 100%; 25 | font: inherit; 26 | vertical-align: baseline; 27 | } 28 | 29 | /* HTML5 display-role reset for older browsers */ 30 | /* line 28, ../../../app/stylesheets/lib/_reset.scss */ 31 | article, aside, details, figcaption, figure, 32 | footer, header, hgroup, menu, nav, section { 33 | display: block; 34 | } 35 | 36 | /* line 31, ../../../app/stylesheets/lib/_reset.scss */ 37 | body { 38 | line-height: 1; 39 | } 40 | 41 | /* line 34, ../../../app/stylesheets/lib/_reset.scss */ 42 | ol, ul { 43 | list-style: none; 44 | } 45 | 46 | /* line 37, ../../../app/stylesheets/lib/_reset.scss */ 47 | blockquote, q { 48 | quotes: none; 49 | } 50 | 51 | /* line 41, ../../../app/stylesheets/lib/_reset.scss */ 52 | blockquote:before, blockquote:after, 53 | q:before, q:after { 54 | content: ''; 55 | content: none; 56 | } 57 | 58 | /* remember to define visible focus styles! 59 | :focus { 60 | outline: ?????; 61 | } */ 62 | /* remember to highlight inserts somehow! */ 63 | /* line 52, ../../../app/stylesheets/lib/_reset.scss */ 64 | ins { 65 | text-decoration: none; 66 | } 67 | 68 | /* line 55, ../../../app/stylesheets/lib/_reset.scss */ 69 | del { 70 | text-decoration: line-through; 71 | } 72 | 73 | /* line 59, ../../../app/stylesheets/lib/_reset.scss */ 74 | table { 75 | border-collapse: collapse; 76 | border-spacing: 0; 77 | } 78 | 79 | /* Facebook Defaults */ 80 | /* All vertical blocks should be in increments of 18 */ 81 | /* line 148, ../../../app/stylesheets/lib/_typography.scss */ 82 | strong { 83 | font-weight: bold; 84 | } 85 | 86 | /* line 45, ../../../app/stylesheets/lib/_typography.scss */ 87 | body { 88 | background: none repeat scroll 0 0 #FFFFFF; 89 | color: #555555; 90 | direction: ltr; 91 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 92 | font-size: 12px; 93 | margin: 0; 94 | padding: 0; 95 | text-align: left; 96 | unicode-bidi: embed; 97 | } 98 | 99 | /* line 57, ../../../app/stylesheets/lib/_typography.scss */ 100 | h1 { 101 | color: #555555; 102 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 103 | margin: 0; 104 | padding: 0; 105 | font-size: 18px; 106 | line-height: 20px; 107 | padding-bottom: 20px; 108 | } 109 | 110 | /* line 58, ../../../app/stylesheets/lib/_typography.scss */ 111 | h2 { 112 | color: #555555; 113 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 114 | margin: 0; 115 | padding: 0; 116 | font-size: 16px; 117 | line-height: 16px; 118 | } 119 | 120 | /* line 59, ../../../app/stylesheets/lib/_typography.scss */ 121 | h3 { 122 | color: #555555; 123 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 124 | margin: 0; 125 | padding: 0; 126 | font-size: 14px; 127 | line-height: 18px; 128 | } 129 | 130 | /* line 60, ../../../app/stylesheets/lib/_typography.scss */ 131 | h4 { 132 | color: #555555; 133 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 134 | margin: 0; 135 | padding: 0; 136 | font-size: 12px; 137 | line-height: 18px; 138 | } 139 | 140 | /* line 62, ../../../app/stylesheets/lib/_typography.scss */ 141 | p { 142 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 143 | font-size: 13px; 144 | text-align: left; 145 | line-height: 20px; 146 | } 147 | 148 | /* line 69, ../../../app/stylesheets/lib/_typography.scss */ 149 | a, a:visited { 150 | cursor: pointer; 151 | color: #0062C4; 152 | -moz-outline-style: none; 153 | text-decoration: none; 154 | font-weight: normal; 155 | } 156 | 157 | /* line 77, ../../../app/stylesheets/lib/_typography.scss */ 158 | a:hover { 159 | text-decoration: none; 160 | } 161 | 162 | /* line 81, ../../../app/stylesheets/lib/_typography.scss */ 163 | img { 164 | border: 0; 165 | } 166 | 167 | /* line 85, ../../../app/stylesheets/lib/_typography.scss */ 168 | select { 169 | border: 1px solid #bdc7d8; 170 | font-family: "lucida grande", tahoma, verdana, arial, sans-serif; 171 | font-size: 11px; 172 | padding: 2px; 173 | } 174 | 175 | /* line 92, ../../../app/stylesheets/lib/_typography.scss */ 176 | .pipe { 177 | color: #cccccc; 178 | padding: 0 3px; 179 | font-weight: normal; 180 | font-size: 11px; 181 | } 182 | 183 | /* line 99, ../../../app/stylesheets/lib/_typography.scss */ 184 | .clearfix:after { 185 | clear: both; 186 | content: "."; 187 | display: block; 188 | font-size: 0; 189 | height: 0; 190 | line-height: 0; 191 | visibility: hidden; 192 | } 193 | 194 | /* line 109, ../../../app/stylesheets/lib/_typography.scss */ 195 | .clearfix { 196 | display: block; 197 | zoom: 1; 198 | } 199 | 200 | /* line 114, ../../../app/stylesheets/lib/_typography.scss */ 201 | form { 202 | margin: 0; 203 | padding: 0; 204 | } 205 | 206 | /* line 119, ../../../app/stylesheets/lib/_typography.scss */ 207 | label { 208 | cursor: pointer; 209 | color: #000; 210 | font-weight: bold; 211 | vertical-align: middle; 212 | } 213 | 214 | /* line 127, ../../../app/stylesheets/lib/_typography.scss */ 215 | label input { 216 | font-weight: normal; 217 | } 218 | 219 | /* line 133, ../../../app/stylesheets/lib/_typography.scss */ 220 | textarea, 221 | .inputtext, 222 | .inputpassword { 223 | border: 1px solid #bdc7d8; 224 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 225 | font-size: 11px; 226 | padding: 3px; 227 | } 228 | 229 | /* line 141, ../../../app/stylesheets/lib/_typography.scss */ 230 | .inputtext, 231 | .inputpassword { 232 | margin: 0; 233 | *margin: -1px 0; 234 | padding-bottom: 4px; 235 | } 236 | 237 | /* line 3, ../../../app/stylesheets/application/_base.scss */ 238 | .clearfix { 239 | clear: both; 240 | } 241 | 242 | /* line 7, ../../../app/stylesheets/application/_base.scss */ 243 | .container { 244 | margin-left: auto; 245 | margin-right: auto; 246 | width: 960px; 247 | padding-top: 10px; 248 | } 249 | 250 | /* line 12, ../../../app/stylesheets/application/_base.scss */ 251 | body { 252 | background-color: #282828; 253 | color: #333; 254 | font-family: Arial, Helvetica, sans-serif; 255 | font-size: 14px; 256 | margin: 0px; 257 | padding: 0px 0px 40px; 258 | } 259 | 260 | /* line 21, ../../../app/stylesheets/application/_base.scss */ 261 | #main { 262 | background-color: #E9E9E9; 263 | min-height: 700px; 264 | } 265 | 266 | /* line 26, ../../../app/stylesheets/application/_base.scss */ 267 | #footer { 268 | background-color: #282828; 269 | padding: 20px 0px; 270 | } 271 | 272 | /* line 31, ../../../app/stylesheets/application/_base.scss */ 273 | #flash-messages { 274 | background: #BCDD5B; 275 | left: 0px; 276 | line-height: 55px; 277 | z-index: 1; 278 | } 279 | /* line 37, ../../../app/stylesheets/application/_base.scss */ 280 | #flash-messages .container { 281 | padding-top: 0; 282 | } 283 | /* line 41, ../../../app/stylesheets/application/_base.scss */ 284 | #flash-messages .alert { 285 | background: #F05931; 286 | } 287 | /* line 45, ../../../app/stylesheets/application/_base.scss */ 288 | #flash-messages .notice, #flash-messages .alert { 289 | padding: 15px 0px; 290 | } 291 | /* line 49, ../../../app/stylesheets/application/_base.scss */ 292 | #flash-messages p { 293 | line-height: 32px; 294 | color: white; 295 | display: block; 296 | font-size: 16px; 297 | font-weight: bold; 298 | } 299 | 300 | /* line 1, ../../../app/stylesheets/application/_header.scss */ 301 | #header { 302 | -webkit-box-shadow: #558a21 -2px 1px 2px; 303 | background-color: #282828; 304 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #333333), color-stop(100%, #1a1a1a)); 305 | background-image: -moz-linear-gradient(top, #333333 0%, #1a1a1a 100%); 306 | background-image: linear-gradient(top, #333333 0%, #1a1a1a 100%); 307 | border-bottom: 3px solid #4BC1DE; 308 | height: 45px; 309 | } 310 | /* line 8, ../../../app/stylesheets/application/_header.scss */ 311 | #header #logo { 312 | display: inline; 313 | float: left; 314 | margin-left: 10px; 315 | margin-right: 10px; 316 | width: 380px; 317 | } 318 | /* line 10, ../../../app/stylesheets/application/_header.scss */ 319 | #header #logo h1 { 320 | color: #fff; 321 | font-size: 26px; 322 | padding-top: 5px; 323 | padding-bottom: 5px; 324 | } 325 | 326 | /* line 19, ../../../app/stylesheets/application/_header.scss */ 327 | #nav { 328 | display: inline; 329 | float: left; 330 | margin-left: 10px; 331 | margin-right: 10px; 332 | width: 540px; 333 | } 334 | /* line 21, ../../../app/stylesheets/application/_header.scss */ 335 | #nav ul { 336 | margin: 0; 337 | padding: 0; 338 | border: 0; 339 | outline: 0; 340 | overflow: hidden; 341 | *zoom: 1; 342 | padding: 10px 0px 10px 0px; 343 | float: right; 344 | } 345 | /* line 26, ../../../app/stylesheets/application/_header.scss */ 346 | #nav ul li { 347 | list-style-image: none; 348 | list-style-type: none; 349 | margin-left: 0px; 350 | white-space: nowrap; 351 | display: inline; 352 | float: left; 353 | padding-left: 10px; 354 | padding-right: 10px; 355 | } 356 | /* line 45, ../../../../../.rvm/gems/ruby-1.9.2-p180@rails3-backbone-coffeescript/gems/compass-0.10.6/frameworks/compass/stylesheets/compass/utilities/lists/_horizontal-list.scss */ 357 | #nav ul li:first-child, #nav ul li.first { 358 | padding-left: 0; 359 | } 360 | /* line 46, ../../../../../.rvm/gems/ruby-1.9.2-p180@rails3-backbone-coffeescript/gems/compass-0.10.6/frameworks/compass/stylesheets/compass/utilities/lists/_horizontal-list.scss */ 361 | #nav ul li:last-child, #nav ul li.last { 362 | padding-right: 0; 363 | } 364 | -------------------------------------------------------------------------------- /public/javascripts/vendor/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.1.4 2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | 9 | (function() { 10 | 11 | // Baseline setup 12 | // -------------- 13 | 14 | // Establish the root object, `window` in the browser, or `global` on the server. 15 | var root = this; 16 | 17 | // Save the previous value of the `_` variable. 18 | var previousUnderscore = root._; 19 | 20 | // Establish the object that gets returned to break out of a loop iteration. 21 | var breaker = {}; 22 | 23 | // Save bytes in the minified (but not gzipped) version: 24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype; 25 | 26 | // Create quick reference variables for speed access to core prototypes. 27 | var slice = ArrayProto.slice, 28 | unshift = ArrayProto.unshift, 29 | toString = ObjProto.toString, 30 | hasOwnProperty = ObjProto.hasOwnProperty; 31 | 32 | // All **ECMAScript 5** native function implementations that we hope to use 33 | // are declared here. 34 | var 35 | nativeForEach = ArrayProto.forEach, 36 | nativeMap = ArrayProto.map, 37 | nativeReduce = ArrayProto.reduce, 38 | nativeReduceRight = ArrayProto.reduceRight, 39 | nativeFilter = ArrayProto.filter, 40 | nativeEvery = ArrayProto.every, 41 | nativeSome = ArrayProto.some, 42 | nativeIndexOf = ArrayProto.indexOf, 43 | nativeLastIndexOf = ArrayProto.lastIndexOf, 44 | nativeIsArray = Array.isArray, 45 | nativeKeys = Object.keys; 46 | 47 | // Create a safe reference to the Underscore object for use below. 48 | var _ = function(obj) { return new wrapper(obj); }; 49 | 50 | // Export the Underscore object for **CommonJS**, with backwards-compatibility 51 | // for the old `require()` API. If we're not in CommonJS, add `_` to the 52 | // global object. 53 | if (typeof module !== 'undefined' && module.exports) { 54 | module.exports = _; 55 | _._ = _; 56 | } else { 57 | root._ = _; 58 | } 59 | 60 | // Current version. 61 | _.VERSION = '1.1.4'; 62 | 63 | // Collection Functions 64 | // -------------------- 65 | 66 | // The cornerstone, an `each` implementation, aka `forEach`. 67 | // Handles objects implementing `forEach`, arrays, and raw objects. 68 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 69 | var each = _.each = _.forEach = function(obj, iterator, context) { 70 | var value; 71 | if (obj == null) return; 72 | if (nativeForEach && obj.forEach === nativeForEach) { 73 | obj.forEach(iterator, context); 74 | } else if (_.isNumber(obj.length)) { 75 | for (var i = 0, l = obj.length; i < l; i++) { 76 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 77 | } 78 | } else { 79 | for (var key in obj) { 80 | if (hasOwnProperty.call(obj, key)) { 81 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 82 | } 83 | } 84 | } 85 | }; 86 | 87 | // Return the results of applying the iterator to each element. 88 | // Delegates to **ECMAScript 5**'s native `map` if available. 89 | _.map = function(obj, iterator, context) { 90 | var results = []; 91 | if (obj == null) return results; 92 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 93 | each(obj, function(value, index, list) { 94 | results[results.length] = iterator.call(context, value, index, list); 95 | }); 96 | return results; 97 | }; 98 | 99 | // **Reduce** builds up a single result from a list of values, aka `inject`, 100 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 101 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 102 | var initial = memo !== void 0; 103 | if (obj == null) obj = []; 104 | if (nativeReduce && obj.reduce === nativeReduce) { 105 | if (context) iterator = _.bind(iterator, context); 106 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 107 | } 108 | each(obj, function(value, index, list) { 109 | if (!initial && index === 0) { 110 | memo = value; 111 | initial = true; 112 | } else { 113 | memo = iterator.call(context, memo, value, index, list); 114 | } 115 | }); 116 | if (!initial) throw new TypeError("Reduce of empty array with no initial value"); 117 | return memo; 118 | }; 119 | 120 | // The right-associative version of reduce, also known as `foldr`. 121 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 122 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 123 | if (obj == null) obj = []; 124 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 125 | if (context) iterator = _.bind(iterator, context); 126 | return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 127 | } 128 | var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); 129 | return _.reduce(reversed, iterator, memo, context); 130 | }; 131 | 132 | // Return the first value which passes a truth test. Aliased as `detect`. 133 | _.find = _.detect = function(obj, iterator, context) { 134 | var result; 135 | any(obj, function(value, index, list) { 136 | if (iterator.call(context, value, index, list)) { 137 | result = value; 138 | return true; 139 | } 140 | }); 141 | return result; 142 | }; 143 | 144 | // Return all the elements that pass a truth test. 145 | // Delegates to **ECMAScript 5**'s native `filter` if available. 146 | // Aliased as `select`. 147 | _.filter = _.select = function(obj, iterator, context) { 148 | var results = []; 149 | if (obj == null) return results; 150 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 151 | each(obj, function(value, index, list) { 152 | if (iterator.call(context, value, index, list)) results[results.length] = value; 153 | }); 154 | return results; 155 | }; 156 | 157 | // Return all the elements for which a truth test fails. 158 | _.reject = function(obj, iterator, context) { 159 | var results = []; 160 | if (obj == null) return results; 161 | each(obj, function(value, index, list) { 162 | if (!iterator.call(context, value, index, list)) results[results.length] = value; 163 | }); 164 | return results; 165 | }; 166 | 167 | // Determine whether all of the elements match a truth test. 168 | // Delegates to **ECMAScript 5**'s native `every` if available. 169 | // Aliased as `all`. 170 | _.every = _.all = function(obj, iterator, context) { 171 | iterator = iterator || _.identity; 172 | var result = true; 173 | if (obj == null) return result; 174 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 175 | each(obj, function(value, index, list) { 176 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 177 | }); 178 | return result; 179 | }; 180 | 181 | // Determine if at least one element in the object matches a truth test. 182 | // Delegates to **ECMAScript 5**'s native `some` if available. 183 | // Aliased as `any`. 184 | var any = _.some = _.any = function(obj, iterator, context) { 185 | iterator = iterator || _.identity; 186 | var result = false; 187 | if (obj == null) return result; 188 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 189 | each(obj, function(value, index, list) { 190 | if (result = iterator.call(context, value, index, list)) return breaker; 191 | }); 192 | return result; 193 | }; 194 | 195 | // Determine if a given value is included in the array or object using `===`. 196 | // Aliased as `contains`. 197 | _.include = _.contains = function(obj, target) { 198 | var found = false; 199 | if (obj == null) return found; 200 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 201 | any(obj, function(value) { 202 | if (found = value === target) return true; 203 | }); 204 | return found; 205 | }; 206 | 207 | // Invoke a method (with arguments) on every item in a collection. 208 | _.invoke = function(obj, method) { 209 | var args = slice.call(arguments, 2); 210 | return _.map(obj, function(value) { 211 | return (method ? value[method] : value).apply(value, args); 212 | }); 213 | }; 214 | 215 | // Convenience version of a common use case of `map`: fetching a property. 216 | _.pluck = function(obj, key) { 217 | return _.map(obj, function(value){ return value[key]; }); 218 | }; 219 | 220 | // Return the maximum element or (element-based computation). 221 | _.max = function(obj, iterator, context) { 222 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); 223 | var result = {computed : -Infinity}; 224 | each(obj, function(value, index, list) { 225 | var computed = iterator ? iterator.call(context, value, index, list) : value; 226 | computed >= result.computed && (result = {value : value, computed : computed}); 227 | }); 228 | return result.value; 229 | }; 230 | 231 | // Return the minimum element (or element-based computation). 232 | _.min = function(obj, iterator, context) { 233 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); 234 | var result = {computed : Infinity}; 235 | each(obj, function(value, index, list) { 236 | var computed = iterator ? iterator.call(context, value, index, list) : value; 237 | computed < result.computed && (result = {value : value, computed : computed}); 238 | }); 239 | return result.value; 240 | }; 241 | 242 | // Sort the object's values by a criterion produced by an iterator. 243 | _.sortBy = function(obj, iterator, context) { 244 | return _.pluck(_.map(obj, function(value, index, list) { 245 | return { 246 | value : value, 247 | criteria : iterator.call(context, value, index, list) 248 | }; 249 | }).sort(function(left, right) { 250 | var a = left.criteria, b = right.criteria; 251 | return a < b ? -1 : a > b ? 1 : 0; 252 | }), 'value'); 253 | }; 254 | 255 | // Use a comparator function to figure out at what index an object should 256 | // be inserted so as to maintain order. Uses binary search. 257 | _.sortedIndex = function(array, obj, iterator) { 258 | iterator = iterator || _.identity; 259 | var low = 0, high = array.length; 260 | while (low < high) { 261 | var mid = (low + high) >> 1; 262 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 263 | } 264 | return low; 265 | }; 266 | 267 | // Safely convert anything iterable into a real, live array. 268 | _.toArray = function(iterable) { 269 | if (!iterable) return []; 270 | if (iterable.toArray) return iterable.toArray(); 271 | if (_.isArray(iterable)) return iterable; 272 | if (_.isArguments(iterable)) return slice.call(iterable); 273 | return _.values(iterable); 274 | }; 275 | 276 | // Return the number of elements in an object. 277 | _.size = function(obj) { 278 | return _.toArray(obj).length; 279 | }; 280 | 281 | // Array Functions 282 | // --------------- 283 | 284 | // Get the first element of an array. Passing **n** will return the first N 285 | // values in the array. Aliased as `head`. The **guard** check allows it to work 286 | // with `_.map`. 287 | _.first = _.head = function(array, n, guard) { 288 | return n && !guard ? slice.call(array, 0, n) : array[0]; 289 | }; 290 | 291 | // Returns everything but the first entry of the array. Aliased as `tail`. 292 | // Especially useful on the arguments object. Passing an **index** will return 293 | // the rest of the values in the array from that index onward. The **guard** 294 | // check allows it to work with `_.map`. 295 | _.rest = _.tail = function(array, index, guard) { 296 | return slice.call(array, _.isUndefined(index) || guard ? 1 : index); 297 | }; 298 | 299 | // Get the last element of an array. 300 | _.last = function(array) { 301 | return array[array.length - 1]; 302 | }; 303 | 304 | // Trim out all falsy values from an array. 305 | _.compact = function(array) { 306 | return _.filter(array, function(value){ return !!value; }); 307 | }; 308 | 309 | // Return a completely flattened version of an array. 310 | _.flatten = function(array) { 311 | return _.reduce(array, function(memo, value) { 312 | if (_.isArray(value)) return memo.concat(_.flatten(value)); 313 | memo[memo.length] = value; 314 | return memo; 315 | }, []); 316 | }; 317 | 318 | // Return a version of the array that does not contain the specified value(s). 319 | _.without = function(array) { 320 | var values = slice.call(arguments, 1); 321 | return _.filter(array, function(value){ return !_.include(values, value); }); 322 | }; 323 | 324 | // Produce a duplicate-free version of the array. If the array has already 325 | // been sorted, you have the option of using a faster algorithm. 326 | // Aliased as `unique`. 327 | _.uniq = _.unique = function(array, isSorted) { 328 | return _.reduce(array, function(memo, el, i) { 329 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; 330 | return memo; 331 | }, []); 332 | }; 333 | 334 | // Produce an array that contains every item shared between all the 335 | // passed-in arrays. 336 | _.intersect = function(array) { 337 | var rest = slice.call(arguments, 1); 338 | return _.filter(_.uniq(array), function(item) { 339 | return _.every(rest, function(other) { 340 | return _.indexOf(other, item) >= 0; 341 | }); 342 | }); 343 | }; 344 | 345 | // Zip together multiple lists into a single array -- elements that share 346 | // an index go together. 347 | _.zip = function() { 348 | var args = slice.call(arguments); 349 | var length = _.max(_.pluck(args, 'length')); 350 | var results = new Array(length); 351 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 352 | return results; 353 | }; 354 | 355 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 356 | // we need this function. Return the position of the first occurrence of an 357 | // item in an array, or -1 if the item is not included in the array. 358 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 359 | // If the array is large and already in sort order, pass `true` 360 | // for **isSorted** to use binary search. 361 | _.indexOf = function(array, item, isSorted) { 362 | if (array == null) return -1; 363 | if (isSorted) { 364 | var i = _.sortedIndex(array, item); 365 | return array[i] === item ? i : -1; 366 | } 367 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 368 | for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; 369 | return -1; 370 | }; 371 | 372 | 373 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 374 | _.lastIndexOf = function(array, item) { 375 | if (array == null) return -1; 376 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 377 | var i = array.length; 378 | while (i--) if (array[i] === item) return i; 379 | return -1; 380 | }; 381 | 382 | // Generate an integer Array containing an arithmetic progression. A port of 383 | // the native Python `range()` function. See 384 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 385 | _.range = function(start, stop, step) { 386 | var args = slice.call(arguments), 387 | solo = args.length <= 1, 388 | start = solo ? 0 : args[0], 389 | stop = solo ? args[0] : args[1], 390 | step = args[2] || 1, 391 | len = Math.max(Math.ceil((stop - start) / step), 0), 392 | idx = 0, 393 | range = new Array(len); 394 | while (idx < len) { 395 | range[idx++] = start; 396 | start += step; 397 | } 398 | return range; 399 | }; 400 | 401 | // Function (ahem) Functions 402 | // ------------------ 403 | 404 | // Create a function bound to a given object (assigning `this`, and arguments, 405 | // optionally). Binding with arguments is also known as `curry`. 406 | _.bind = function(func, obj) { 407 | var args = slice.call(arguments, 2); 408 | return function() { 409 | return func.apply(obj || {}, args.concat(slice.call(arguments))); 410 | }; 411 | }; 412 | 413 | // Bind all of an object's methods to that object. Useful for ensuring that 414 | // all callbacks defined on an object belong to it. 415 | _.bindAll = function(obj) { 416 | var funcs = slice.call(arguments, 1); 417 | if (funcs.length == 0) funcs = _.functions(obj); 418 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 419 | return obj; 420 | }; 421 | 422 | // Memoize an expensive function by storing its results. 423 | _.memoize = function(func, hasher) { 424 | var memo = {}; 425 | hasher = hasher || _.identity; 426 | return function() { 427 | var key = hasher.apply(this, arguments); 428 | return key in memo ? memo[key] : (memo[key] = func.apply(this, arguments)); 429 | }; 430 | }; 431 | 432 | // Delays a function for the given number of milliseconds, and then calls 433 | // it with the arguments supplied. 434 | _.delay = function(func, wait) { 435 | var args = slice.call(arguments, 2); 436 | return setTimeout(function(){ return func.apply(func, args); }, wait); 437 | }; 438 | 439 | // Defers a function, scheduling it to run after the current call stack has 440 | // cleared. 441 | _.defer = function(func) { 442 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 443 | }; 444 | 445 | // Internal function used to implement `_.throttle` and `_.debounce`. 446 | var limit = function(func, wait, debounce) { 447 | var timeout; 448 | return function() { 449 | var context = this, args = arguments; 450 | var throttler = function() { 451 | timeout = null; 452 | func.apply(context, args); 453 | }; 454 | if (debounce) clearTimeout(timeout); 455 | if (debounce || !timeout) timeout = setTimeout(throttler, wait); 456 | }; 457 | }; 458 | 459 | // Returns a function, that, when invoked, will only be triggered at most once 460 | // during a given window of time. 461 | _.throttle = function(func, wait) { 462 | return limit(func, wait, false); 463 | }; 464 | 465 | // Returns a function, that, as long as it continues to be invoked, will not 466 | // be triggered. The function will be called after it stops being called for 467 | // N milliseconds. 468 | _.debounce = function(func, wait) { 469 | return limit(func, wait, true); 470 | }; 471 | 472 | // Returns the first function passed as an argument to the second, 473 | // allowing you to adjust arguments, run code before and after, and 474 | // conditionally execute the original function. 475 | _.wrap = function(func, wrapper) { 476 | return function() { 477 | var args = [func].concat(slice.call(arguments)); 478 | return wrapper.apply(this, args); 479 | }; 480 | }; 481 | 482 | // Returns a function that is the composition of a list of functions, each 483 | // consuming the return value of the function that follows. 484 | _.compose = function() { 485 | var funcs = slice.call(arguments); 486 | return function() { 487 | var args = slice.call(arguments); 488 | for (var i=funcs.length-1; i >= 0; i--) { 489 | args = [funcs[i].apply(this, args)]; 490 | } 491 | return args[0]; 492 | }; 493 | }; 494 | 495 | // Object Functions 496 | // ---------------- 497 | 498 | // Retrieve the names of an object's properties. 499 | // Delegates to **ECMAScript 5**'s native `Object.keys` 500 | _.keys = nativeKeys || function(obj) { 501 | if (_.isArray(obj)) return _.range(0, obj.length); 502 | var keys = []; 503 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; 504 | return keys; 505 | }; 506 | 507 | // Retrieve the values of an object's properties. 508 | _.values = function(obj) { 509 | return _.map(obj, _.identity); 510 | }; 511 | 512 | // Return a sorted list of the function names available on the object. 513 | // Aliased as `methods` 514 | _.functions = _.methods = function(obj) { 515 | return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); 516 | }; 517 | 518 | // Extend a given object with all the properties in passed-in object(s). 519 | _.extend = function(obj) { 520 | each(slice.call(arguments, 1), function(source) { 521 | for (var prop in source) obj[prop] = source[prop]; 522 | }); 523 | return obj; 524 | }; 525 | 526 | // Create a (shallow-cloned) duplicate of an object. 527 | _.clone = function(obj) { 528 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 529 | }; 530 | 531 | // Invokes interceptor with the obj, and then returns obj. 532 | // The primary purpose of this method is to "tap into" a method chain, in 533 | // order to perform operations on intermediate results within the chain. 534 | _.tap = function(obj, interceptor) { 535 | interceptor(obj); 536 | return obj; 537 | }; 538 | 539 | // Perform a deep comparison to check if two objects are equal. 540 | _.isEqual = function(a, b) { 541 | // Check object identity. 542 | if (a === b) return true; 543 | // Different types? 544 | var atype = typeof(a), btype = typeof(b); 545 | if (atype != btype) return false; 546 | // Basic equality test (watch out for coercions). 547 | if (a == b) return true; 548 | // One is falsy and the other truthy. 549 | if ((!a && b) || (a && !b)) return false; 550 | // Unwrap any wrapped objects. 551 | if (a._chain) a = a._wrapped; 552 | if (b._chain) b = b._wrapped; 553 | // One of them implements an isEqual()? 554 | if (a.isEqual) return a.isEqual(b); 555 | // Check dates' integer values. 556 | if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); 557 | // Both are NaN? 558 | if (_.isNaN(a) && _.isNaN(b)) return false; 559 | // Compare regular expressions. 560 | if (_.isRegExp(a) && _.isRegExp(b)) 561 | return a.source === b.source && 562 | a.global === b.global && 563 | a.ignoreCase === b.ignoreCase && 564 | a.multiline === b.multiline; 565 | // If a is not an object by this point, we can't handle it. 566 | if (atype !== 'object') return false; 567 | // Check for different array lengths before comparing contents. 568 | if (a.length && (a.length !== b.length)) return false; 569 | // Nothing else worked, deep compare the contents. 570 | var aKeys = _.keys(a), bKeys = _.keys(b); 571 | // Different object sizes? 572 | if (aKeys.length != bKeys.length) return false; 573 | // Recursive comparison of contents. 574 | for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; 575 | return true; 576 | }; 577 | 578 | // Is a given array or object empty? 579 | _.isEmpty = function(obj) { 580 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 581 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; 582 | return true; 583 | }; 584 | 585 | // Is a given value a DOM element? 586 | _.isElement = function(obj) { 587 | return !!(obj && obj.nodeType == 1); 588 | }; 589 | 590 | // Is a given value an array? 591 | // Delegates to ECMA5's native Array.isArray 592 | _.isArray = nativeIsArray || function(obj) { 593 | return toString.call(obj) === '[object Array]'; 594 | }; 595 | 596 | // Is a given variable an arguments object? 597 | _.isArguments = function(obj) { 598 | return !!(obj && hasOwnProperty.call(obj, 'callee')); 599 | }; 600 | 601 | // Is a given value a function? 602 | _.isFunction = function(obj) { 603 | return !!(obj && obj.constructor && obj.call && obj.apply); 604 | }; 605 | 606 | // Is a given value a string? 607 | _.isString = function(obj) { 608 | return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); 609 | }; 610 | 611 | // Is a given value a number? 612 | _.isNumber = function(obj) { 613 | return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); 614 | }; 615 | 616 | // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript 617 | // that does not equal itself. 618 | _.isNaN = function(obj) { 619 | return obj !== obj; 620 | }; 621 | 622 | // Is a given value a boolean? 623 | _.isBoolean = function(obj) { 624 | return obj === true || obj === false; 625 | }; 626 | 627 | // Is a given value a date? 628 | _.isDate = function(obj) { 629 | return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); 630 | }; 631 | 632 | // Is the given value a regular expression? 633 | _.isRegExp = function(obj) { 634 | return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); 635 | }; 636 | 637 | // Is a given value equal to null? 638 | _.isNull = function(obj) { 639 | return obj === null; 640 | }; 641 | 642 | // Is a given variable undefined? 643 | _.isUndefined = function(obj) { 644 | return obj === void 0; 645 | }; 646 | 647 | // Utility Functions 648 | // ----------------- 649 | 650 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 651 | // previous owner. Returns a reference to the Underscore object. 652 | _.noConflict = function() { 653 | root._ = previousUnderscore; 654 | return this; 655 | }; 656 | 657 | // Keep the identity function around for default iterators. 658 | _.identity = function(value) { 659 | return value; 660 | }; 661 | 662 | // Run a function **n** times. 663 | _.times = function (n, iterator, context) { 664 | for (var i = 0; i < n; i++) iterator.call(context, i); 665 | }; 666 | 667 | // Add your own custom functions to the Underscore object, ensuring that 668 | // they're correctly added to the OOP wrapper as well. 669 | _.mixin = function(obj) { 670 | each(_.functions(obj), function(name){ 671 | addToWrapper(name, _[name] = obj[name]); 672 | }); 673 | }; 674 | 675 | // Generate a unique integer id (unique within the entire client session). 676 | // Useful for temporary DOM ids. 677 | var idCounter = 0; 678 | _.uniqueId = function(prefix) { 679 | var id = idCounter++; 680 | return prefix ? prefix + id : id; 681 | }; 682 | 683 | // By default, Underscore uses ERB-style template delimiters, change the 684 | // following template settings to use alternative delimiters. 685 | _.templateSettings = { 686 | evaluate : /<%([\s\S]+?)%>/g, 687 | interpolate : /<%=([\s\S]+?)%>/g 688 | }; 689 | 690 | // JavaScript micro-templating, similar to John Resig's implementation. 691 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 692 | // and correctly escapes quotes within interpolated code. 693 | _.template = function(str, data) { 694 | var c = _.templateSettings; 695 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 696 | 'with(obj||{}){__p.push(\'' + 697 | str.replace(/\\/g, '\\\\') 698 | .replace(/'/g, "\\'") 699 | .replace(c.interpolate, function(match, code) { 700 | return "'," + code.replace(/\\'/g, "'") + ",'"; 701 | }) 702 | .replace(c.evaluate || null, function(match, code) { 703 | return "');" + code.replace(/\\'/g, "'") 704 | .replace(/[\r\n\t]/g, ' ') + "__p.push('"; 705 | }) 706 | .replace(/\r/g, '\\r') 707 | .replace(/\n/g, '\\n') 708 | .replace(/\t/g, '\\t') 709 | + "');}return __p.join('');"; 710 | var func = new Function('obj', tmpl); 711 | return data ? func(data) : func; 712 | }; 713 | 714 | // The OOP Wrapper 715 | // --------------- 716 | 717 | // If Underscore is called as a function, it returns a wrapped object that 718 | // can be used OO-style. This wrapper holds altered versions of all the 719 | // underscore functions. Wrapped objects may be chained. 720 | var wrapper = function(obj) { this._wrapped = obj; }; 721 | 722 | // Expose `wrapper.prototype` as `_.prototype` 723 | _.prototype = wrapper.prototype; 724 | 725 | // Helper function to continue chaining intermediate results. 726 | var result = function(obj, chain) { 727 | return chain ? _(obj).chain() : obj; 728 | }; 729 | 730 | // A method to easily add functions to the OOP wrapper. 731 | var addToWrapper = function(name, func) { 732 | wrapper.prototype[name] = function() { 733 | var args = slice.call(arguments); 734 | unshift.call(args, this._wrapped); 735 | return result(func.apply(_, args), this._chain); 736 | }; 737 | }; 738 | 739 | // Add all of the Underscore functions to the wrapper object. 740 | _.mixin(_); 741 | 742 | // Add all mutator Array functions to the wrapper. 743 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 744 | var method = ArrayProto[name]; 745 | wrapper.prototype[name] = function() { 746 | method.apply(this._wrapped, arguments); 747 | return result(this._wrapped, this._chain); 748 | }; 749 | }); 750 | 751 | // Add all accessor Array functions to the wrapper. 752 | each(['concat', 'join', 'slice'], function(name) { 753 | var method = ArrayProto[name]; 754 | wrapper.prototype[name] = function() { 755 | return result(method.apply(this._wrapped, arguments), this._chain); 756 | }; 757 | }); 758 | 759 | // Start chaining a wrapped Underscore object. 760 | wrapper.prototype.chain = function() { 761 | this._chain = true; 762 | return this; 763 | }; 764 | 765 | // Extracts the result from a wrapped and chained object. 766 | wrapper.prototype.value = function() { 767 | return this._wrapped; 768 | }; 769 | 770 | })(); 771 | -------------------------------------------------------------------------------- /public/javascripts/vendor/backbone.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.3.3 2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Backbone may be freely distributed under the MIT license. 4 | // For all details and documentation: 5 | // http://documentcloud.github.com/backbone 6 | 7 | (function(){ 8 | 9 | // Initial Setup 10 | // ------------- 11 | 12 | // The top-level namespace. All public Backbone classes and modules will 13 | // be attached to this. Exported for both CommonJS and the browser. 14 | var Backbone; 15 | if (typeof exports !== 'undefined') { 16 | Backbone = exports; 17 | } else { 18 | Backbone = this.Backbone = {}; 19 | } 20 | 21 | // Current version of the library. Keep in sync with `package.json`. 22 | Backbone.VERSION = '0.3.3'; 23 | 24 | // Require Underscore, if we're on the server, and it's not already present. 25 | var _ = this._; 26 | if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._; 27 | 28 | // For Backbone's purposes, either jQuery or Zepto owns the `$` variable. 29 | var $ = this.jQuery || this.Zepto; 30 | 31 | // Turn on `emulateHTTP` to use support legacy HTTP servers. Setting this option will 32 | // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a 33 | // `X-Http-Method-Override` header. 34 | Backbone.emulateHTTP = false; 35 | 36 | // Turn on `emulateJSON` to support legacy servers that can't deal with direct 37 | // `application/json` requests ... will encode the body as 38 | // `application/x-www-form-urlencoded` instead and will send the model in a 39 | // form param named `model`. 40 | Backbone.emulateJSON = false; 41 | 42 | // Backbone.Events 43 | // ----------------- 44 | 45 | // A module that can be mixed in to *any object* in order to provide it with 46 | // custom events. You may `bind` or `unbind` a callback function to an event; 47 | // `trigger`-ing an event fires all callbacks in succession. 48 | // 49 | // var object = {}; 50 | // _.extend(object, Backbone.Events); 51 | // object.bind('expand', function(){ alert('expanded'); }); 52 | // object.trigger('expand'); 53 | // 54 | Backbone.Events = { 55 | 56 | // Bind an event, specified by a string name, `ev`, to a `callback` function. 57 | // Passing `"all"` will bind the callback to all events fired. 58 | bind : function(ev, callback) { 59 | var calls = this._callbacks || (this._callbacks = {}); 60 | var list = this._callbacks[ev] || (this._callbacks[ev] = []); 61 | list.push(callback); 62 | return this; 63 | }, 64 | 65 | // Remove one or many callbacks. If `callback` is null, removes all 66 | // callbacks for the event. If `ev` is null, removes all bound callbacks 67 | // for all events. 68 | unbind : function(ev, callback) { 69 | var calls; 70 | if (!ev) { 71 | this._callbacks = {}; 72 | } else if (calls = this._callbacks) { 73 | if (!callback) { 74 | calls[ev] = []; 75 | } else { 76 | var list = calls[ev]; 77 | if (!list) return this; 78 | for (var i = 0, l = list.length; i < l; i++) { 79 | if (callback === list[i]) { 80 | list.splice(i, 1); 81 | break; 82 | } 83 | } 84 | } 85 | } 86 | return this; 87 | }, 88 | 89 | // Trigger an event, firing all bound callbacks. Callbacks are passed the 90 | // same arguments as `trigger` is, apart from the event name. 91 | // Listening for `"all"` passes the true event name as the first argument. 92 | trigger : function(ev) { 93 | var list, calls, i, l; 94 | if (!(calls = this._callbacks)) return this; 95 | if (calls[ev]) { 96 | list = calls[ev].slice(0); 97 | for (i = 0, l = list.length; i < l; i++) { 98 | list[i].apply(this, Array.prototype.slice.call(arguments, 1)); 99 | } 100 | } 101 | if (calls['all']) { 102 | list = calls['all'].slice(0); 103 | for (i = 0, l = list.length; i < l; i++) { 104 | list[i].apply(this, arguments); 105 | } 106 | } 107 | return this; 108 | } 109 | 110 | }; 111 | 112 | // Backbone.Model 113 | // -------------- 114 | 115 | // Create a new model, with defined attributes. A client id (`cid`) 116 | // is automatically generated and assigned for you. 117 | Backbone.Model = function(attributes, options) { 118 | var defaults; 119 | attributes || (attributes = {}); 120 | if (defaults = this.defaults) { 121 | if (_.isFunction(defaults)) defaults = defaults(); 122 | attributes = _.extend({}, defaults, attributes); 123 | } 124 | this.attributes = {}; 125 | this._escapedAttributes = {}; 126 | this.cid = _.uniqueId('c'); 127 | this.set(attributes, {silent : true}); 128 | this._changed = false; 129 | this._previousAttributes = _.clone(this.attributes); 130 | if (options && options.collection) this.collection = options.collection; 131 | this.initialize(attributes, options); 132 | }; 133 | 134 | // Attach all inheritable methods to the Model prototype. 135 | _.extend(Backbone.Model.prototype, Backbone.Events, { 136 | 137 | // A snapshot of the model's previous attributes, taken immediately 138 | // after the last `"change"` event was fired. 139 | _previousAttributes : null, 140 | 141 | // Has the item been changed since the last `"change"` event? 142 | _changed : false, 143 | 144 | // The default name for the JSON `id` attribute is `"id"`. MongoDB and 145 | // CouchDB users may want to set this to `"_id"`. 146 | idAttribute : 'id', 147 | 148 | // Initialize is an empty function by default. Override it with your own 149 | // initialization logic. 150 | initialize : function(){}, 151 | 152 | // Return a copy of the model's `attributes` object. 153 | toJSON : function() { 154 | return _.clone(this.attributes); 155 | }, 156 | 157 | // Get the value of an attribute. 158 | get : function(attr) { 159 | return this.attributes[attr]; 160 | }, 161 | 162 | // Get the HTML-escaped value of an attribute. 163 | escape : function(attr) { 164 | var html; 165 | if (html = this._escapedAttributes[attr]) return html; 166 | var val = this.attributes[attr]; 167 | return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val); 168 | }, 169 | 170 | // Returns `true` if the attribute contains a value that is not null 171 | // or undefined. 172 | has : function(attr) { 173 | return this.attributes[attr] != null; 174 | }, 175 | 176 | // Set a hash of model attributes on the object, firing `"change"` unless you 177 | // choose to silence it. 178 | set : function(attrs, options) { 179 | 180 | // Extract attributes and options. 181 | options || (options = {}); 182 | if (!attrs) return this; 183 | if (attrs.attributes) attrs = attrs.attributes; 184 | var now = this.attributes, escaped = this._escapedAttributes; 185 | 186 | // Run validation. 187 | if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false; 188 | 189 | // Check for changes of `id`. 190 | if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; 191 | 192 | // Update attributes. 193 | for (var attr in attrs) { 194 | var val = attrs[attr]; 195 | if (!_.isEqual(now[attr], val)) { 196 | now[attr] = val; 197 | delete escaped[attr]; 198 | this._changed = true; 199 | if (!options.silent) this.trigger('change:' + attr, this, val, options); 200 | } 201 | } 202 | 203 | // Fire the `"change"` event, if the model has been changed. 204 | if (!options.silent && this._changed) this.change(options); 205 | return this; 206 | }, 207 | 208 | // Remove an attribute from the model, firing `"change"` unless you choose 209 | // to silence it. `unset` is a noop if the attribute doesn't exist. 210 | unset : function(attr, options) { 211 | if (!(attr in this.attributes)) return this; 212 | options || (options = {}); 213 | var value = this.attributes[attr]; 214 | 215 | // Run validation. 216 | var validObj = {}; 217 | validObj[attr] = void 0; 218 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; 219 | 220 | // Remove the attribute. 221 | delete this.attributes[attr]; 222 | delete this._escapedAttributes[attr]; 223 | if (attr == this.idAttribute) delete this.id; 224 | this._changed = true; 225 | if (!options.silent) { 226 | this.trigger('change:' + attr, this, void 0, options); 227 | this.change(options); 228 | } 229 | return this; 230 | }, 231 | 232 | // Clear all attributes on the model, firing `"change"` unless you choose 233 | // to silence it. 234 | clear : function(options) { 235 | options || (options = {}); 236 | var old = this.attributes; 237 | 238 | // Run validation. 239 | var validObj = {}; 240 | for (attr in old) validObj[attr] = void 0; 241 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; 242 | 243 | this.attributes = {}; 244 | this._escapedAttributes = {}; 245 | this._changed = true; 246 | if (!options.silent) { 247 | for (attr in old) { 248 | this.trigger('change:' + attr, this, void 0, options); 249 | } 250 | this.change(options); 251 | } 252 | return this; 253 | }, 254 | 255 | // Fetch the model from the server. If the server's representation of the 256 | // model differs from its current attributes, they will be overriden, 257 | // triggering a `"change"` event. 258 | fetch : function(options) { 259 | options || (options = {}); 260 | var model = this; 261 | var success = options.success; 262 | options.success = function(resp) { 263 | if (!model.set(model.parse(resp), options)) return false; 264 | if (success) success(model, resp); 265 | }; 266 | options.error = wrapError(options.error, model, options); 267 | (this.sync || Backbone.sync).call(this, 'read', this, options); 268 | return this; 269 | }, 270 | 271 | // Set a hash of model attributes, and sync the model to the server. 272 | // If the server returns an attributes hash that differs, the model's 273 | // state will be `set` again. 274 | save : function(attrs, options) { 275 | options || (options = {}); 276 | if (attrs && !this.set(attrs, options)) return false; 277 | var model = this; 278 | var success = options.success; 279 | options.success = function(resp) { 280 | if (!model.set(model.parse(resp), options)) return false; 281 | if (success) success(model, resp); 282 | }; 283 | options.error = wrapError(options.error, model, options); 284 | var method = this.isNew() ? 'create' : 'update'; 285 | (this.sync || Backbone.sync).call(this, method, this, options); 286 | return this; 287 | }, 288 | 289 | // Destroy this model on the server. Upon success, the model is removed 290 | // from its collection, if it has one. 291 | destroy : function(options) { 292 | options || (options = {}); 293 | var model = this; 294 | var success = options.success; 295 | options.success = function(resp) { 296 | model.trigger('destroy', model, model.collection, options); 297 | if (success) success(model, resp); 298 | }; 299 | options.error = wrapError(options.error, model, options); 300 | (this.sync || Backbone.sync).call(this, 'delete', this, options); 301 | return this; 302 | }, 303 | 304 | // Default URL for the model's representation on the server -- if you're 305 | // using Backbone's restful methods, override this to change the endpoint 306 | // that will be called. 307 | url : function() { 308 | var base = getUrl(this.collection) || this.urlRoot || urlError(); 309 | if (this.isNew()) return base; 310 | return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); 311 | }, 312 | 313 | // **parse** converts a response into the hash of attributes to be `set` on 314 | // the model. The default implementation is just to pass the response along. 315 | parse : function(resp) { 316 | return resp; 317 | }, 318 | 319 | // Create a new model with identical attributes to this one. 320 | clone : function() { 321 | return new this.constructor(this); 322 | }, 323 | 324 | // A model is new if it has never been saved to the server, and has a negative 325 | // ID. 326 | isNew : function() { 327 | return !this.id; 328 | }, 329 | 330 | // Call this method to manually fire a `change` event for this model. 331 | // Calling this will cause all objects observing the model to update. 332 | change : function(options) { 333 | this.trigger('change', this, options); 334 | this._previousAttributes = _.clone(this.attributes); 335 | this._changed = false; 336 | }, 337 | 338 | // Determine if the model has changed since the last `"change"` event. 339 | // If you specify an attribute name, determine if that attribute has changed. 340 | hasChanged : function(attr) { 341 | if (attr) return this._previousAttributes[attr] != this.attributes[attr]; 342 | return this._changed; 343 | }, 344 | 345 | // Return an object containing all the attributes that have changed, or false 346 | // if there are no changed attributes. Useful for determining what parts of a 347 | // view need to be updated and/or what attributes need to be persisted to 348 | // the server. 349 | changedAttributes : function(now) { 350 | now || (now = this.attributes); 351 | var old = this._previousAttributes; 352 | var changed = false; 353 | for (var attr in now) { 354 | if (!_.isEqual(old[attr], now[attr])) { 355 | changed = changed || {}; 356 | changed[attr] = now[attr]; 357 | } 358 | } 359 | return changed; 360 | }, 361 | 362 | // Get the previous value of an attribute, recorded at the time the last 363 | // `"change"` event was fired. 364 | previous : function(attr) { 365 | if (!attr || !this._previousAttributes) return null; 366 | return this._previousAttributes[attr]; 367 | }, 368 | 369 | // Get all of the attributes of the model at the time of the previous 370 | // `"change"` event. 371 | previousAttributes : function() { 372 | return _.clone(this._previousAttributes); 373 | }, 374 | 375 | // Run validation against a set of incoming attributes, returning `true` 376 | // if all is well. If a specific `error` callback has been passed, 377 | // call that instead of firing the general `"error"` event. 378 | _performValidation : function(attrs, options) { 379 | var error = this.validate(attrs); 380 | if (error) { 381 | if (options.error) { 382 | options.error(this, error); 383 | } else { 384 | this.trigger('error', this, error, options); 385 | } 386 | return false; 387 | } 388 | return true; 389 | } 390 | 391 | }); 392 | 393 | // Backbone.Collection 394 | // ------------------- 395 | 396 | // Provides a standard collection class for our sets of models, ordered 397 | // or unordered. If a `comparator` is specified, the Collection will maintain 398 | // its models in sort order, as they're added and removed. 399 | Backbone.Collection = function(models, options) { 400 | options || (options = {}); 401 | if (options.comparator) { 402 | this.comparator = options.comparator; 403 | delete options.comparator; 404 | } 405 | _.bindAll(this, '_onModelEvent', '_removeReference'); 406 | this._reset(); 407 | if (models) this.refresh(models, {silent: true}); 408 | this.initialize(models, options); 409 | }; 410 | 411 | // Define the Collection's inheritable methods. 412 | _.extend(Backbone.Collection.prototype, Backbone.Events, { 413 | 414 | // The default model for a collection is just a **Backbone.Model**. 415 | // This should be overridden in most cases. 416 | model : Backbone.Model, 417 | 418 | // Initialize is an empty function by default. Override it with your own 419 | // initialization logic. 420 | initialize : function(){}, 421 | 422 | // The JSON representation of a Collection is an array of the 423 | // models' attributes. 424 | toJSON : function() { 425 | return this.map(function(model){ return model.toJSON(); }); 426 | }, 427 | 428 | // Add a model, or list of models to the set. Pass **silent** to avoid 429 | // firing the `added` event for every new model. 430 | add : function(models, options) { 431 | if (_.isArray(models)) { 432 | for (var i = 0, l = models.length; i < l; i++) { 433 | this._add(models[i], options); 434 | } 435 | } else { 436 | this._add(models, options); 437 | } 438 | return this; 439 | }, 440 | 441 | // Remove a model, or a list of models from the set. Pass silent to avoid 442 | // firing the `removed` event for every model removed. 443 | remove : function(models, options) { 444 | if (_.isArray(models)) { 445 | for (var i = 0, l = models.length; i < l; i++) { 446 | this._remove(models[i], options); 447 | } 448 | } else { 449 | this._remove(models, options); 450 | } 451 | return this; 452 | }, 453 | 454 | // Get a model from the set by id. 455 | get : function(id) { 456 | if (id == null) return null; 457 | return this._byId[id.id != null ? id.id : id]; 458 | }, 459 | 460 | // Get a model from the set by client id. 461 | getByCid : function(cid) { 462 | return cid && this._byCid[cid.cid || cid]; 463 | }, 464 | 465 | // Get the model at the given index. 466 | at: function(index) { 467 | return this.models[index]; 468 | }, 469 | 470 | // Force the collection to re-sort itself. You don't need to call this under normal 471 | // circumstances, as the set will maintain sort order as each item is added. 472 | sort : function(options) { 473 | options || (options = {}); 474 | if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); 475 | this.models = this.sortBy(this.comparator); 476 | if (!options.silent) this.trigger('refresh', this, options); 477 | return this; 478 | }, 479 | 480 | // Pluck an attribute from each model in the collection. 481 | pluck : function(attr) { 482 | return _.map(this.models, function(model){ return model.get(attr); }); 483 | }, 484 | 485 | // When you have more items than you want to add or remove individually, 486 | // you can refresh the entire set with a new list of models, without firing 487 | // any `added` or `removed` events. Fires `refresh` when finished. 488 | refresh : function(models, options) { 489 | models || (models = []); 490 | options || (options = {}); 491 | this.each(this._removeReference); 492 | this._reset(); 493 | this.add(models, {silent: true}); 494 | if (!options.silent) this.trigger('refresh', this, options); 495 | return this; 496 | }, 497 | 498 | // Fetch the default set of models for this collection, refreshing the 499 | // collection when they arrive. If `add: true` is passed, appends the 500 | // models to the collection instead of refreshing. 501 | fetch : function(options) { 502 | options || (options = {}); 503 | var collection = this; 504 | var success = options.success; 505 | options.success = function(resp) { 506 | collection[options.add ? 'add' : 'refresh'](collection.parse(resp), options); 507 | if (success) success(collection, resp); 508 | }; 509 | options.error = wrapError(options.error, collection, options); 510 | (this.sync || Backbone.sync).call(this, 'read', this, options); 511 | return this; 512 | }, 513 | 514 | // Create a new instance of a model in this collection. After the model 515 | // has been created on the server, it will be added to the collection. 516 | create : function(model, options) { 517 | var coll = this; 518 | options || (options = {}); 519 | if (!(model instanceof Backbone.Model)) { 520 | var attrs = model; 521 | model = new this.model(null, {collection: coll}); 522 | if (!model.set(attrs)) return false; 523 | } else { 524 | model.collection = coll; 525 | } 526 | var success = options.success; 527 | options.success = function(nextModel, resp) { 528 | coll.add(nextModel); 529 | if (success) success(nextModel, resp); 530 | }; 531 | return model.save(null, options); 532 | }, 533 | 534 | // **parse** converts a response into a list of models to be added to the 535 | // collection. The default implementation is just to pass it through. 536 | parse : function(resp) { 537 | return resp; 538 | }, 539 | 540 | // Proxy to _'s chain. Can't be proxied the same way the rest of the 541 | // underscore methods are proxied because it relies on the underscore 542 | // constructor. 543 | chain: function () { 544 | return _(this.models).chain(); 545 | }, 546 | 547 | // Reset all internal state. Called when the collection is refreshed. 548 | _reset : function(options) { 549 | this.length = 0; 550 | this.models = []; 551 | this._byId = {}; 552 | this._byCid = {}; 553 | }, 554 | 555 | // Internal implementation of adding a single model to the set, updating 556 | // hash indexes for `id` and `cid` lookups. 557 | _add : function(model, options) { 558 | options || (options = {}); 559 | if (!(model instanceof Backbone.Model)) { 560 | model = new this.model(model, {collection: this}); 561 | } 562 | var already = this.getByCid(model); 563 | if (already) throw new Error(["Can't add the same model to a set twice", already.id]); 564 | this._byId[model.id] = model; 565 | this._byCid[model.cid] = model; 566 | if (!model.collection) { 567 | model.collection = this; 568 | } 569 | var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length; 570 | this.models.splice(index, 0, model); 571 | model.bind('all', this._onModelEvent); 572 | this.length++; 573 | if (!options.silent) model.trigger('add', model, this, options); 574 | return model; 575 | }, 576 | 577 | // Internal implementation of removing a single model from the set, updating 578 | // hash indexes for `id` and `cid` lookups. 579 | _remove : function(model, options) { 580 | options || (options = {}); 581 | model = this.getByCid(model) || this.get(model); 582 | if (!model) return null; 583 | delete this._byId[model.id]; 584 | delete this._byCid[model.cid]; 585 | this.models.splice(this.indexOf(model), 1); 586 | this.length--; 587 | if (!options.silent) model.trigger('remove', model, this, options); 588 | this._removeReference(model); 589 | return model; 590 | }, 591 | 592 | // Internal method to remove a model's ties to a collection. 593 | _removeReference : function(model) { 594 | if (this == model.collection) { 595 | delete model.collection; 596 | } 597 | model.unbind('all', this._onModelEvent); 598 | }, 599 | 600 | // Internal method called every time a model in the set fires an event. 601 | // Sets need to update their indexes when models change ids. All other 602 | // events simply proxy through. "add" and "remove" events that originate 603 | // in other collections are ignored. 604 | _onModelEvent : function(ev, model, collection, options) { 605 | if ((ev == 'add' || ev == 'remove') && collection != this) return; 606 | if (ev == 'destroy') { 607 | this._remove(model, options); 608 | } 609 | if (ev === 'change:' + model.idAttribute) { 610 | delete this._byId[model.previous(model.idAttribute)]; 611 | this._byId[model.id] = model; 612 | } 613 | this.trigger.apply(this, arguments); 614 | } 615 | 616 | }); 617 | 618 | // Underscore methods that we want to implement on the Collection. 619 | var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 620 | 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 621 | 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 622 | 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty']; 623 | 624 | // Mix in each Underscore method as a proxy to `Collection#models`. 625 | _.each(methods, function(method) { 626 | Backbone.Collection.prototype[method] = function() { 627 | return _[method].apply(_, [this.models].concat(_.toArray(arguments))); 628 | }; 629 | }); 630 | 631 | // Backbone.Controller 632 | // ------------------- 633 | 634 | // Controllers map faux-URLs to actions, and fire events when routes are 635 | // matched. Creating a new one sets its `routes` hash, if not set statically. 636 | Backbone.Controller = function(options) { 637 | options || (options = {}); 638 | if (options.routes) this.routes = options.routes; 639 | this._bindRoutes(); 640 | this.initialize(options); 641 | }; 642 | 643 | // Cached regular expressions for matching named param parts and splatted 644 | // parts of route strings. 645 | var namedParam = /:([\w\d]+)/g; 646 | var splatParam = /\*([\w\d]+)/g; 647 | var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; 648 | 649 | // Set up all inheritable **Backbone.Controller** properties and methods. 650 | _.extend(Backbone.Controller.prototype, Backbone.Events, { 651 | 652 | // Initialize is an empty function by default. Override it with your own 653 | // initialization logic. 654 | initialize : function(){}, 655 | 656 | // Manually bind a single named route to a callback. For example: 657 | // 658 | // this.route('search/:query/p:num', 'search', function(query, num) { 659 | // ... 660 | // }); 661 | // 662 | route : function(route, name, callback) { 663 | Backbone.history || (Backbone.history = new Backbone.History); 664 | if (!_.isRegExp(route)) route = this._routeToRegExp(route); 665 | Backbone.history.route(route, _.bind(function(fragment) { 666 | var args = this._extractParameters(route, fragment); 667 | callback.apply(this, args); 668 | this.trigger.apply(this, ['route:' + name].concat(args)); 669 | }, this)); 670 | }, 671 | 672 | // Simple proxy to `Backbone.history` to save a fragment into the history, 673 | // without triggering routes. 674 | saveLocation : function(fragment) { 675 | Backbone.history.saveLocation(fragment); 676 | }, 677 | 678 | // Bind all defined routes to `Backbone.history`. We have to reverse the 679 | // order of the routes here to support behavior where the most general 680 | // routes can be defined at the bottom of the route map. 681 | _bindRoutes : function() { 682 | if (!this.routes) return; 683 | var routes = []; 684 | for (var route in this.routes) { 685 | routes.unshift([route, this.routes[route]]); 686 | } 687 | for (var i = 0, l = routes.length; i < l; i++) { 688 | this.route(routes[i][0], routes[i][1], this[routes[i][1]]); 689 | } 690 | }, 691 | 692 | // Convert a route string into a regular expression, suitable for matching 693 | // against the current location fragment. 694 | _routeToRegExp : function(route) { 695 | route = route.replace(escapeRegExp, "\\$&") 696 | .replace(namedParam, "([^\/]*)") 697 | .replace(splatParam, "(.*?)"); 698 | return new RegExp('^' + route + '$'); 699 | }, 700 | 701 | // Given a route, and a URL fragment that it matches, return the array of 702 | // extracted parameters. 703 | _extractParameters : function(route, fragment) { 704 | return route.exec(fragment).slice(1); 705 | } 706 | 707 | }); 708 | 709 | // Backbone.History 710 | // ---------------- 711 | 712 | // Handles cross-browser history management, based on URL hashes. If the 713 | // browser does not support `onhashchange`, falls back to polling. 714 | Backbone.History = function() { 715 | this.handlers = []; 716 | this.fragment = this.getFragment(); 717 | _.bindAll(this, 'checkUrl'); 718 | }; 719 | 720 | // Cached regex for cleaning hashes. 721 | var hashStrip = /^#*/; 722 | 723 | // Has the history handling already been started? 724 | var historyStarted = false; 725 | 726 | // Set up all inheritable **Backbone.History** properties and methods. 727 | _.extend(Backbone.History.prototype, { 728 | 729 | // The default interval to poll for hash changes, if necessary, is 730 | // twenty times a second. 731 | interval: 50, 732 | 733 | // Get the cross-browser normalized URL fragment. 734 | getFragment : function(loc) { 735 | return (loc || window.location).hash.replace(hashStrip, ''); 736 | }, 737 | 738 | // Start the hash change handling, returning `true` if the current URL matches 739 | // an existing route, and `false` otherwise. 740 | start : function() { 741 | if (historyStarted) throw new Error("Backbone.history has already been started"); 742 | var docMode = document.documentMode; 743 | var oldIE = ($.browser.msie && (!docMode || docMode <= 7)); 744 | if (oldIE) { 745 | this.iframe = $('