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 = $('
').hide().appendTo('body')[0].contentWindow;
746 | }
747 | if ('onhashchange' in window && !oldIE) {
748 | $(window).bind('hashchange', this.checkUrl);
749 | } else {
750 | setInterval(this.checkUrl, this.interval);
751 | }
752 | historyStarted = true;
753 | return this.loadUrl();
754 | },
755 |
756 | // Add a route to be tested when the hash changes. Routes added later may
757 | // override previous routes.
758 | route : function(route, callback) {
759 | this.handlers.unshift({route : route, callback : callback});
760 | },
761 |
762 | // Checks the current URL to see if it has changed, and if it has,
763 | // calls `loadUrl`, normalizing across the hidden iframe.
764 | checkUrl : function() {
765 | var current = this.getFragment();
766 | if (current == this.fragment && this.iframe) {
767 | current = this.getFragment(this.iframe.location);
768 | }
769 | if (current == this.fragment ||
770 | current == decodeURIComponent(this.fragment)) return false;
771 | if (this.iframe) {
772 | window.location.hash = this.iframe.location.hash = current;
773 | }
774 | this.loadUrl();
775 | },
776 |
777 | // Attempt to load the current URL fragment. If a route succeeds with a
778 | // match, returns `true`. If no defined routes matches the fragment,
779 | // returns `false`.
780 | loadUrl : function() {
781 | var fragment = this.fragment = this.getFragment();
782 | var matched = _.any(this.handlers, function(handler) {
783 | if (handler.route.test(fragment)) {
784 | handler.callback(fragment);
785 | return true;
786 | }
787 | });
788 | return matched;
789 | },
790 |
791 | // Save a fragment into the hash history. You are responsible for properly
792 | // URL-encoding the fragment in advance. This does not trigger
793 | // a `hashchange` event.
794 | saveLocation : function(fragment) {
795 | fragment = (fragment || '').replace(hashStrip, '');
796 | if (this.fragment == fragment) return;
797 | window.location.hash = this.fragment = fragment;
798 | if (this.iframe && (fragment != this.getFragment(this.iframe.location))) {
799 | this.iframe.document.open().close();
800 | this.iframe.location.hash = fragment;
801 | }
802 | }
803 |
804 | });
805 |
806 | // Backbone.View
807 | // -------------
808 |
809 | // Creating a Backbone.View creates its initial element outside of the DOM,
810 | // if an existing element is not provided...
811 | Backbone.View = function(options) {
812 | this.cid = _.uniqueId('view');
813 | this._configure(options || {});
814 | this._ensureElement();
815 | this.delegateEvents();
816 | this.initialize(options);
817 | };
818 |
819 | // Element lookup, scoped to DOM elements within the current view.
820 | // This should be prefered to global lookups, if you're dealing with
821 | // a specific view.
822 | var selectorDelegate = function(selector) {
823 | return $(selector, this.el);
824 | };
825 |
826 | // Cached regex to split keys for `delegate`.
827 | var eventSplitter = /^(\w+)\s*(.*)$/;
828 |
829 | // List of view options to be merged as properties.
830 | var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
831 |
832 | // Set up all inheritable **Backbone.View** properties and methods.
833 | _.extend(Backbone.View.prototype, Backbone.Events, {
834 |
835 | // The default `tagName` of a View's element is `"div"`.
836 | tagName : 'div',
837 |
838 | // Attach the `selectorDelegate` function as the `$` property.
839 | $ : selectorDelegate,
840 |
841 | // Initialize is an empty function by default. Override it with your own
842 | // initialization logic.
843 | initialize : function(){},
844 |
845 | // **render** is the core function that your view should override, in order
846 | // to populate its element (`this.el`), with the appropriate HTML. The
847 | // convention is for **render** to always return `this`.
848 | render : function() {
849 | return this;
850 | },
851 |
852 | // Remove this view from the DOM. Note that the view isn't present in the
853 | // DOM by default, so calling this method may be a no-op.
854 | remove : function() {
855 | $(this.el).remove();
856 | return this;
857 | },
858 |
859 | // For small amounts of DOM Elements, where a full-blown template isn't
860 | // needed, use **make** to manufacture elements, one at a time.
861 | //
862 | // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
863 | //
864 | make : function(tagName, attributes, content) {
865 | var el = document.createElement(tagName);
866 | if (attributes) $(el).attr(attributes);
867 | if (content) $(el).html(content);
868 | return el;
869 | },
870 |
871 | // Set callbacks, where `this.callbacks` is a hash of
872 | //
873 | // *{"event selector": "callback"}*
874 | //
875 | // {
876 | // 'mousedown .title': 'edit',
877 | // 'click .button': 'save'
878 | // }
879 | //
880 | // pairs. Callbacks will be bound to the view, with `this` set properly.
881 | // Uses event delegation for efficiency.
882 | // Omitting the selector binds the event to `this.el`.
883 | // This only works for delegate-able events: not `focus`, `blur`, and
884 | // not `change`, `submit`, and `reset` in Internet Explorer.
885 | delegateEvents : function(events) {
886 | if (!(events || (events = this.events))) return;
887 | $(this.el).unbind('.delegateEvents' + this.cid);
888 | for (var key in events) {
889 | var methodName = events[key];
890 | var match = key.match(eventSplitter);
891 | var eventName = match[1], selector = match[2];
892 | var method = _.bind(this[methodName], this);
893 | eventName += '.delegateEvents' + this.cid;
894 | if (selector === '') {
895 | $(this.el).bind(eventName, method);
896 | } else {
897 | $(this.el).delegate(selector, eventName, method);
898 | }
899 | }
900 | },
901 |
902 | // Performs the initial configuration of a View with a set of options.
903 | // Keys with special meaning *(model, collection, id, className)*, are
904 | // attached directly to the view.
905 | _configure : function(options) {
906 | if (this.options) options = _.extend({}, this.options, options);
907 | for (var i = 0, l = viewOptions.length; i < l; i++) {
908 | var attr = viewOptions[i];
909 | if (options[attr]) this[attr] = options[attr];
910 | }
911 | this.options = options;
912 | },
913 |
914 | // Ensure that the View has a DOM element to render into.
915 | // If `this.el` is a string, pass it through `$()`, take the first
916 | // matching element, and re-assign it to `el`. Otherwise, create
917 | // an element from the `id`, `className` and `tagName` proeprties.
918 | _ensureElement : function() {
919 | if (!this.el) {
920 | var attrs = this.attributes || {};
921 | if (this.id) attrs.id = this.id;
922 | if (this.className) attrs['class'] = this.className;
923 | this.el = this.make(this.tagName, attrs);
924 | } else if (_.isString(this.el)) {
925 | this.el = $(this.el).get(0);
926 | }
927 | }
928 |
929 | });
930 |
931 | // The self-propagating extend function that Backbone classes use.
932 | var extend = function (protoProps, classProps) {
933 | var child = inherits(this, protoProps, classProps);
934 | child.extend = extend;
935 | return child;
936 | };
937 |
938 | // Set up inheritance for the model, collection, and view.
939 | Backbone.Model.extend = Backbone.Collection.extend =
940 | Backbone.Controller.extend = Backbone.View.extend = extend;
941 |
942 | // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
943 | var methodMap = {
944 | 'create': 'POST',
945 | 'update': 'PUT',
946 | 'delete': 'DELETE',
947 | 'read' : 'GET'
948 | };
949 |
950 | // Backbone.sync
951 | // -------------
952 |
953 | // Override this function to change the manner in which Backbone persists
954 | // models to the server. You will be passed the type of request, and the
955 | // model in question. By default, uses makes a RESTful Ajax request
956 | // to the model's `url()`. Some possible customizations could be:
957 | //
958 | // * Use `setTimeout` to batch rapid-fire updates into a single request.
959 | // * Send up the models as XML instead of JSON.
960 | // * Persist models via WebSockets instead of Ajax.
961 | //
962 | // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
963 | // as `POST`, with a `_method` parameter containing the true HTTP method,
964 | // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
965 | // `application/json` with the model in a param named `model`.
966 | // Useful when interfacing with server-side languages like **PHP** that make
967 | // it difficult to read the body of `PUT` requests.
968 | Backbone.sync = function(method, model, options) {
969 | var type = methodMap[method];
970 |
971 | // Default JSON-request options.
972 | var params = _.extend({
973 | type: type,
974 | contentType: 'application/json',
975 | dataType: 'json',
976 | processData: false
977 | }, options);
978 |
979 | // Ensure that we have a URL.
980 | if (!params.url) {
981 | params.url = getUrl(model) || urlError();
982 | }
983 |
984 | // Ensure that we have the appropriate request data.
985 | if (!params.data && model && (method == 'create' || method == 'update')) {
986 | params.data = JSON.stringify(model.toJSON());
987 | }
988 |
989 | // For older servers, emulate JSON by encoding the request into an HTML-form.
990 | if (Backbone.emulateJSON) {
991 | params.contentType = 'application/x-www-form-urlencoded';
992 | params.processData = true;
993 | params.data = params.data ? {model : params.data} : {};
994 | }
995 |
996 | // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
997 | // And an `X-HTTP-Method-Override` header.
998 | if (Backbone.emulateHTTP) {
999 | if (type === 'PUT' || type === 'DELETE') {
1000 | if (Backbone.emulateJSON) params.data._method = type;
1001 | params.type = 'POST';
1002 | params.beforeSend = function(xhr) {
1003 | xhr.setRequestHeader('X-HTTP-Method-Override', type);
1004 | };
1005 | }
1006 | }
1007 |
1008 | // Make the request.
1009 | $.ajax(params);
1010 | };
1011 |
1012 | // Helpers
1013 | // -------
1014 |
1015 | // Shared empty constructor function to aid in prototype-chain creation.
1016 | var ctor = function(){};
1017 |
1018 | // Helper function to correctly set up the prototype chain, for subclasses.
1019 | // Similar to `goog.inherits`, but uses a hash of prototype properties and
1020 | // class properties to be extended.
1021 | var inherits = function(parent, protoProps, staticProps) {
1022 | var child;
1023 |
1024 | // The constructor function for the new subclass is either defined by you
1025 | // (the "constructor" property in your `extend` definition), or defaulted
1026 | // by us to simply call `super()`.
1027 | if (protoProps && protoProps.hasOwnProperty('constructor')) {
1028 | child = protoProps.constructor;
1029 | } else {
1030 | child = function(){ return parent.apply(this, arguments); };
1031 | }
1032 |
1033 | // Inherit class (static) properties from parent.
1034 | _.extend(child, parent);
1035 |
1036 | // Set the prototype chain to inherit from `parent`, without calling
1037 | // `parent`'s constructor function.
1038 | ctor.prototype = parent.prototype;
1039 | child.prototype = new ctor();
1040 |
1041 | // Add prototype properties (instance properties) to the subclass,
1042 | // if supplied.
1043 | if (protoProps) _.extend(child.prototype, protoProps);
1044 |
1045 | // Add static properties to the constructor function, if supplied.
1046 | if (staticProps) _.extend(child, staticProps);
1047 |
1048 | // Correctly set child's `prototype.constructor`, for `instanceof`.
1049 | child.prototype.constructor = child;
1050 |
1051 | // Set a convenience property in case the parent's prototype is needed later.
1052 | child.__super__ = parent.prototype;
1053 |
1054 | return child;
1055 | };
1056 |
1057 | // Helper function to get a URL from a Model or Collection as a property
1058 | // or as a function.
1059 | var getUrl = function(object) {
1060 | if (!(object && object.url)) return null;
1061 | return _.isFunction(object.url) ? object.url() : object.url;
1062 | };
1063 |
1064 | // Throw an error when a URL is needed, and none is supplied.
1065 | var urlError = function() {
1066 | throw new Error("A 'url' property or function must be specified");
1067 | };
1068 |
1069 | // Wrap an optional error callback with a fallback error event.
1070 | var wrapError = function(onError, model, options) {
1071 | return function(resp) {
1072 | if (onError) {
1073 | onError(model, resp, options);
1074 | } else {
1075 | model.trigger('error', model, resp, options);
1076 | }
1077 | };
1078 | };
1079 |
1080 | // Helper function to escape a string for HTML rendering.
1081 | var escapeHTML = function(string) {
1082 | return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"');
1083 | };
1084 |
1085 | }).call(this);
1086 |
--------------------------------------------------------------------------------