Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First release

  • Loading branch information...
commit 7a0f27402380ef69103f64fd6cab70df78969e00 0 parents
@timurvafin timurvafin authored
Showing with 9,936 additions and 0 deletions.
  1. +6 −0 .gitignore
  2. +30 −0 Gemfile
  3. +31 −0 README
  4. +7 −0 Rakefile
  5. +4 −0 app/controllers/application_controller.rb
  6. +7 −0 app/controllers/dashboard_controller.rb
  7. +10 −0 app/helpers/application_helper.rb
  8. +11 −0 app/models/user.rb
  9. +1 −0  app/views/dashboard/index.html.erb
  10. +11 −0 app/views/devise/confirmations/new.html.erb
  11. +5 −0 app/views/devise/mailer/confirmation_instructions.html.erb
  12. +8 −0 app/views/devise/mailer/reset_password_instructions.html.erb
  13. +7 −0 app/views/devise/mailer/unlock_instructions.html.erb
  14. +14 −0 app/views/devise/passwords/edit.html.erb
  15. +11 −0 app/views/devise/passwords/new.html.erb
  16. +17 −0 app/views/devise/registrations/edit.html.erb
  17. +16 −0 app/views/devise/registrations/new.html.erb
  18. +15 −0 app/views/devise/sessions/new.html.erb
  19. +19 −0 app/views/devise/shared/_links.erb
  20. +11 −0 app/views/devise/unlocks/new.html.erb
  21. +3 −0  app/views/layouts/_messages.html.erb
  22. +4 −0 app/views/layouts/_navigation.html.erb
  23. +24 −0 app/views/layouts/application.html.erb
  24. +4 −0 config.ru
  25. +46 −0 config/application.rb
  26. +13 −0 config/boot.rb
  27. +14 −0 config/config.yml
  28. +10 −0 config/cucumber.yml
  29. +39 −0 config/database.yml.example
  30. +5 −0 config/environment.rb
  31. +19 −0 config/environments/development.rb
  32. +46 −0 config/environments/production.rb
  33. +32 −0 config/environments/test.rb
  34. +7 −0 config/initializers/backtrace_silencers.rb
  35. +1 −0  config/initializers/config.rb
  36. +135 −0 config/initializers/devise.rb
  37. +54 −0 config/initializers/formtastic.rb
  38. +7 −0 config/initializers/generators.rb
  39. +10 −0 config/initializers/inflections.rb
  40. +1 −0  config/initializers/mailer.rb
  41. +5 −0 config/initializers/mime_types.rb
  42. +7 −0 config/initializers/secret_token.rb
  43. +8 −0 config/initializers/session_store.rb
  44. +1 −0  config/initializers/simple_navigation.rb
  45. +6 −0 config/initializers/time_formats.rb
  46. +5 −0 config/locales/en.yml
  47. +11 −0 config/navigations/main_navigation.rb
  48. +17 −0 config/navigations/user_navigation.rb
  49. +8 −0 config/routes.rb
  50. +19 −0 db/migrate/20100713113845_devise_create_users.rb
  51. +34 −0 db/schema.rb
  52. +7 −0 db/seeds.rb
  53. +2 −0  doc/README_FOR_APP
  54. +10 −0 features/devise.feature
  55. +16 −0 features/step_definitions/app_steps.rb
  56. +87 −0 features/step_definitions/pickle_steps.rb
  57. +219 −0 features/step_definitions/web_steps.rb
  58. +58 −0 features/support/env.rb
  59. +1 −0  features/support/factory_girl.rb
  60. +37 −0 features/support/paths.rb
  61. +6 −0 features/support/pickle.rb
  62. 0  lib/tasks/.gitkeep
  63. +53 −0 lib/tasks/cucumber.rake
  64. +26 −0 public/404.html
  65. +26 −0 public/422.html
  66. +26 −0 public/500.html
  67. 0  public/favicon.ico
  68. +85 −0 public/flutie/stylesheets/defaults.css
  69. +117 −0 public/flutie/stylesheets/forms.css
  70. +25 −0 public/flutie/stylesheets/lists.css
  71. +49 −0 public/flutie/stylesheets/reset.css
  72. 0  public/flutie/stylesheets/screen.css
  73. +27 −0 public/flutie/stylesheets/tables.css
  74. +90 −0 public/flutie/stylesheets/type.css
  75. BIN  public/images/rails.png
  76. +2 −0  public/javascripts/application.js
  77. +965 −0 public/javascripts/controls.js
  78. +974 −0 public/javascripts/dragdrop.js
  79. +1,123 −0 public/javascripts/effects.js
  80. +4,874 −0 public/javascripts/prototype.js
  81. +118 −0 public/javascripts/rails.js
  82. +5 −0 public/robots.txt
  83. 0  public/stylesheets/.gitkeep
  84. +33 −0 public/stylesheets/application.css
  85. +6 −0 script/rails
  86. +9 −0 spec/factories/user.rb
  87. +24 −0 spec/spec_helper.rb
  88. 0  vendor/plugins/.gitkeep
6 .gitignore
@@ -0,0 +1,6 @@
+.bundle
+db/*.sqlite3
+log/*.log
+tmp/**/*
+rerun.txt
+config/database.yml
30 Gemfile
@@ -0,0 +1,30 @@
+HEROKU = ENV['USER'].match(/^repo\d+/)
+
+source 'http://rubygems.org'
+
+gem 'mysql'
+gem 'rails', '3.0.0.beta4'
+gem 'sqlite3-ruby', :require => 'sqlite3'
+gem 'devise', '1.1.rc2'
+gem 'simple-navigation'
+gem 'formtastic', :git => "http://github.com/justinfrench/formtastic.git", :branch => "rails3"
+gem 'flutie'
+
+# Heroku hack b/c we don't want use these gem on heroku env
+unless HEROKU
+ group :test do
+ gem 'redgreen', '1.2.2'
+ gem 'rr', '0.10.9'
+ gem 'factory_girl_rails'
+ gem 'pickle', :git => 'git://github.com/codegram/pickle.git', :ref => '929ee633'
+
+ gem "rspec-rails", ">= 2.0.0.beta.17"
+ gem 'cucumber'
+ gem 'cucumber-rails'
+ gem 'capybara', '0.3.8'
+ gem 'autotest-rails'
+ gem 'autotest'
+
+ gem 'ruby-debug'
+ end
+end
31 README
@@ -0,0 +1,31 @@
+== Rails 3 based application
+
+== Implemented
+
+* Auth based on devise, checkout app/models/user.rb
+* Navigation based on simple navigation, checkout config/initializers/simple_navigation.rb and config/navigations
+* formstatic with flutie
+* application config, checkout config/config.yml
+* Default host for mailer from config, checkout config/initializers/mailer.rb
+* Added time formats, checkout config/initializers/time_formats.rb
+* Cucumber, rspec, factory girl, pickle, autotest
+
+== Quick start
+
+* fork repository
+* clone repository
+* tune config/config.yml
+* tune in the sources application name: config/application.rb, config/initializers/generators.rb
+* tune config/database.yml.example and copy it to the config/database.yml
+* bundle install
+* rake cucumber
+* for autotest run in the shell "export AUTOFEATURE=true" and than run autotest command
+* feel free to to use pull request or patch you application code with new features from skeleton usign fork queue github feature
+
+
+Thanks,
+Flatsoft
+
+
+
+
7 Rakefile
@@ -0,0 +1,7 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+require 'rake'
+
+Rails::Application.load_tasks
4 app/controllers/application_controller.rb
@@ -0,0 +1,4 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+ layout 'application'
+end
7 app/controllers/dashboard_controller.rb
@@ -0,0 +1,7 @@
+class DashboardController < ApplicationController
+ navigation :dashbaord
+ before_filter :authenticate_user!
+
+ def index
+ end
+end
10 app/helpers/application_helper.rb
@@ -0,0 +1,10 @@
+module ApplicationHelper
+ def title(page_title, show_title = true)
+ @show_title = show_title
+ content_for(:title) { page_title.to_s }
+ end
+
+ def show_title?
+ @show_title
+ end
+end
11 app/models/user.rb
@@ -0,0 +1,11 @@
+class User < ActiveRecord::Base
+ devise :database_authenticatable, :registerable,
+ :recoverable, :rememberable, :trackable, :validatable, :invitable
+
+ attr_accessible :full_name, :email, :password, :password_confirmation
+ validates :full_name, :presence => true
+
+ def full_name_with_email
+ "#{self[:full_name]} (#{email})"
+ end
+end
1  app/views/dashboard/index.html.erb
@@ -0,0 +1 @@
+<% title 'Dashboard' %>
11 app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,11 @@
+<% title 'Resend confirmation instructions' %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => confirmation_path(resource_name)) do |form| %>
+ <%= form.inputs do %>
+ <%= form.input :email, :required => true %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Resend confirmation instructions' %>
+ <% end %>
+<% end %>
5 app/views/devise/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,5 @@
+<p>Welcome <%= @resource.email %>!</p>
+
+<p>You can confirm your account through the link below:</p>
+
+<p><%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %></p>
8 app/views/devise/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
+<p>Hello <%= @resource.email %>!</p>
+
+<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
+
+<p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %></p>
+
+<p>If you didn't request this, please ignore this email.</p>
+<p>Your password won't change until you access the link above and create a new one.</p>
7 app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
+<p>Hello <%= @resource.email %>!</p>
+
+<p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p>
+
+<p>Click the link below to unlock your account:</p>
+
+<p><%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %></p>
14 app/views/devise/passwords/edit.html.erb
@@ -0,0 +1,14 @@
+<% title 'Change your password' %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |form| %>
+ <%= form.hidden_field :reset_password_token %>
+
+ <%= form.inputs do %>
+ <%= form.input :password, :required => true %>
+ <%= form.input :password_confirmation, :required => true %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Change my password' %>
+ <% end %>
+<% end %>
11 app/views/devise/passwords/new.html.erb
@@ -0,0 +1,11 @@
+<% title 'Forgot your password?' %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => password_path(resource_name)) do |form| %>
+ <%= form.inputs do %>
+ <%= form.input :email, :required => true %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Send me reset password instructions' %>
+ <% end %>
+<% end %>
17 app/views/devise/registrations/edit.html.erb
@@ -0,0 +1,17 @@
+<% title "Edit #{resource_name.to_s.humanize}" %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |form| %>
+ <%= form.inputs do %>
+ <%= form.input :full_name, :required => true %>
+ <%= form.input :email, :required => true %>
+ <%= form.input :password, :required => true, :hint => "leave blank if you don't want to change it" %>
+ <%= form.input :password_confirmation, :required => true %>
+ <%= form.input :current_password, :required => true, :hint => "we need your current password to confirm your changes" %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Update' %>
+ <% end %>
+<% end %>
+
+<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
16 app/views/devise/registrations/new.html.erb
@@ -0,0 +1,16 @@
+<% title 'Sing up' %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |form| %>
+ <%= form.inputs do %>
+ <%= form.input :full_name, :required => true %>
+ <%= form.input :email, :required => true %>
+ <%= form.input :password, :required => true %>
+ <%= form.input :password_confirmation, :required => true %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Sing up' %>
+ <% end %>
+<% end %>
+
+<%= render :partial => 'devise/shared/links' %>
15 app/views/devise/sessions/new.html.erb
@@ -0,0 +1,15 @@
+<% title 'Sign in' %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |form| %>
+ <%= form.inputs do %>
+ <%= form.input :email, :required => true %>
+ <%= form.input :password, :required => true %>
+ <%= form.input :remember_me, :as => :boolean if devise_mapping.rememberable? %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Sign in' %>
+ <% end %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
19 app/views/devise/shared/_links.erb
@@ -0,0 +1,19 @@
+<%- if controller_name != 'sessions' %>
+ <%= link_to "Sign in", new_session_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
+ <%= link_to "Sign up", new_registration_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
+<% end -%>
+
+<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
+<% end -%>
11 app/views/devise/unlocks/new.html.erb
@@ -0,0 +1,11 @@
+<% title 'Resend unlock instructions' %>
+
+<%= semantic_form_for(resource, :as => resource_name, :url => unlock_path(resource_name)) do |form| %>
+ <%= form.inputs do %>
+ <%= form.input :email, :required => true %>
+ <% end %>
+
+ <%= form.buttons do %>
+ <%= form.commit_button 'Resend unlock instructions' %>
+ <% end %>
+<% end %>
3  app/views/layouts/_messages.html.erb
@@ -0,0 +1,3 @@
+<% flash.each do |type, content| %>
+ <%= content_tag :div, content, :class => type %>
+<% end %>
4 app/views/layouts/_navigation.html.erb
@@ -0,0 +1,4 @@
+<div id="navigation">
+ <%= render_navigation(:context => :main) %>
+ <%= render_navigation(:context => :user) %>
+</div>
24 app/views/layouts/application.html.erb
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <%= stylesheet_link_tag :flutie, 'application' %>
+ <%= javascript_include_tag :defaults %>
+ <%= csrf_meta_tag %>
+ <%= yield(:head) %>
+ </head>
+ <body>
+ <div class="content">
+ <%= render 'layouts/navigation' %>
+ <hr />
+ <%= render 'layouts/messages' %>
+
+ <% if show_title? %>
+ <h1><%= yield(:title) %></h1>
+ <% end %>
+
+ <%= yield %>
+ </div>
+ </body>
+</html>
4 config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Features::Application
46 config/application.rb
@@ -0,0 +1,46 @@
+require File.expand_path('../boot', __FILE__)
+
+require 'rails/all'
+
+# If you have a Gemfile, require the gems listed there, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env) if defined?(Bundler)
+
+module Features
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Add additional load paths for your own custom dirs
+ # config.load_paths += %W( #{config.root}/extras )
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # Configure generators values. Many other options are available, be sure to check the documentation.
+ # config.generators do |g|
+ # g.orm :active_record
+ # g.template_engine :erb
+ # g.test_framework :test_unit, :fixture => true
+ # end
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password]
+ end
+end
13 config/boot.rb
@@ -0,0 +1,13 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+gemfile = File.expand_path('../../Gemfile', __FILE__)
+begin
+ ENV['BUNDLE_GEMFILE'] = gemfile
+ require 'bundler'
+ Bundler.setup
+rescue Bundler::GemNotFound => e
+ STDERR.puts e.message
+ STDERR.puts "Try running `bundle install`."
+ exit!
+end if File.exist?(gemfile)
14 config/config.yml
@@ -0,0 +1,14 @@
+defaults: &defaults
+ app_name: Features
+ noreply: noreply@features.flatsoft.com
+ host: localhost:3000
+
+development:
+ <<: *defaults
+
+test:
+ <<: *defaults
+
+production:
+ <<: *defaults
+ host: features.flatsoft.com
10 config/cucumber.yml
@@ -0,0 +1,10 @@
+<%
+rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
+rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
+std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip"
+%>
+default: <%= std_opts %> features
+wip: --tags @wip:3 --wip features
+rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
+autotest-all: --require features --require lib --format progress --color features
+autotest: --require features --require lib --format pretty --color features
39 config/database.yml.example
@@ -0,0 +1,39 @@
+# sqlite
+#
+defaults_developmet: &defaults_developmet
+ adapter: sqlite3
+ pool: 5
+ timeout: 5000
+
+development:
+ database: db/development.sqlite3
+ <<: *defaults_developmet
+
+test: &test
+ database: db/test.sqlite3
+ <<: *defaults_developmet
+
+test: &test
+ database: db/cucumber.sqlite3
+ <<: *defaults_developmet
+
+# mysql@locahost
+#
+#defaults_developmet: &defaults_developmet
+# adapter: mysql
+# encoding: utf8
+# host: localhost
+# username: root
+# password:
+#
+#development:
+# database: features_dev
+# <<: *defaults_developmet
+#
+#test:
+# database: features_test
+# <<: *defaults_developmet
+#
+#cucumber:
+# database: features_test
+# <<: *defaults_developmet
5 config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+Features::Application.initialize!
19 config/environments/development.rb
@@ -0,0 +1,19 @@
+Features::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the webserver when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_view.debug_rjs = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+end
46 config/environments/production.rb
@@ -0,0 +1,46 @@
+Features::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # The production environment is meant for finished, "live" apps.
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Specifies the header that your server uses for sending files
+ config.action_dispatch.x_sendfile_header = "X-Sendfile"
+
+ # For nginx:
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+
+ # If you have no front-end server that supports something like X-Sendfile,
+ # just comment this out and Rails will serve the files
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Disable Rails's static asset server
+ # In production, Apache or nginx will already do this
+ config.serve_static_assets = false
+
+ # Enable serving of images, stylesheets, and javascripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+end
32 config/environments/test.rb
@@ -0,0 +1,32 @@
+Features::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+end
7 config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
1  config/initializers/config.rb
@@ -0,0 +1 @@
+APP_CONFIG = YAML.load(File.read("#{Rails.root}/config/config.yml"))[Rails.env]
135 config/initializers/devise.rb
@@ -0,0 +1,135 @@
+# Use this hook to configure devise mailer, warden hooks and so forth. The first
+# four configuration values can also be set straight in your models.
+Devise.setup do |config|
+ # ==> Mailer Configuration
+ # Configure the e-mail address which will be shown in DeviseMailer.
+ config.mailer_sender = APP_CONFIG['noreply']
+
+ # Configure the class responsible to send e-mails.
+ # config.mailer = "Devise::Mailer"
+
+ # ==> ORM configuration
+ # Load and configure the ORM. Supports :active_record (default), :mongoid
+ # (bson_ext recommended) and :data_mapper (experimental).
+ require 'devise/orm/active_record'
+
+ # ==> Configuration for any authentication mechanism
+ # Configure which keys are used when authenticating an user. By default is
+ # just :email. You can configure it to use [:username, :subdomain], so for
+ # authenticating an user, both parameters are required. Remember that those
+ # parameters are used only when authenticating and not when retrieving from
+ # session. If you need permissions, you should implement that in a before filter.
+ config.authentication_keys = [ :email ]
+
+ # Tell if authentication through request.params is enabled. True by default.
+ # config.params_authenticatable = true
+
+ # Tell if authentication through HTTP Basic Auth is enabled. True by default.
+ # config.http_authenticatable = true
+
+ # The realm used in Http Basic Authentication
+ # config.http_authentication_realm = "Application"
+
+ # ==> Configuration for :database_authenticatable
+ # For bcrypt, this is the cost for hashing the password and defaults to 10. If
+ # using other encryptors, it sets how many times you want the password re-encrypted.
+ config.stretches = 10
+
+ # Define which will be the encryption algorithm. Devise also supports encryptors
+ # from others authentication tools as :clearance_sha1, :authlogic_sha512 (then
+ # you should set stretches above to 20 for default behavior) and :restful_authentication_sha1
+ # (then you should set stretches to 10, and copy REST_AUTH_SITE_KEY to pepper)
+ config.encryptor = :bcrypt
+
+ # Setup a pepper to generate the encrypted password.
+ config.pepper = "d77d65f2f8a2ee751d816683e05a8ea2167321958a1bb5e951022317eba0835b9f1229c3eecac185584af3b61262e390b195dee85610b672bcc1492fb9e2f816"
+
+ # ==> Configuration for :confirmable
+ # The time you want to give your user to confirm his account. During this time
+ # he will be able to access your application without confirming. Default is nil.
+ # When confirm_within is zero, the user won't be able to sign in without confirming.
+ # You can use this to let your user access some features of your application
+ # without confirming the account, but blocking it after a certain period
+ # (ie 2 days).
+ # config.confirm_within = 2.days
+
+ # ==> Configuration for :rememberable
+ # The time the user will be remembered without asking for credentials again.
+ # config.remember_for = 2.weeks
+
+ # ==> Configuration for :validatable
+ # Range for password length
+ # config.password_length = 6..20
+
+ # Regex to use to validate the email address
+ # config.email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
+
+ # ==> Configuration for :timeoutable
+ # The time you want to timeout the user session without activity. After this
+ # time the user will be asked for credentials again.
+ # config.timeout_in = 10.minutes
+
+ # ==> Configuration for :lockable
+ # Defines which strategy will be used to lock an account.
+ # :failed_attempts = Locks an account after a number of failed attempts to sign in.
+ # :none = No lock strategy. You should handle locking by yourself.
+ # config.lock_strategy = :failed_attempts
+
+ # Defines which strategy will be used to unlock an account.
+ # :email = Sends an unlock link to the user email
+ # :time = Re-enables login after a certain amount of time (see :unlock_in below)
+ # :both = Enables both strategies
+ # :none = No unlock strategy. You should handle unlocking by yourself.
+ # config.unlock_strategy = :both
+
+ # Number of authentication tries before locking an account if lock_strategy
+ # is failed attempts.
+ # config.maximum_attempts = 20
+
+ # Time interval to unlock the account if :time is enabled as unlock_strategy.
+ # config.unlock_in = 1.hour
+
+ # ==> Configuration for :token_authenticatable
+ # Defines name of the authentication token params key
+ # config.token_authentication_key = :auth_token
+
+ # ==> Scopes configuration
+ # Turn scoped views on. Before rendering "sessions/new", it will first check for
+ # "sessions/users/new". It's turned off by default because it's slower if you
+ # are using only default views.
+ # config.scoped_views = true
+
+ # By default, devise detects the role accessed based on the url. So whenever
+ # accessing "/users/sign_in", it knows you are accessing an User. This makes
+ # routes as "/sign_in" not possible, unless you tell Devise to use the default
+ # scope, setting true below.
+ # Note that devise does not generate default routes. You also have to
+ # specify them in config/routes.rb
+ config.use_default_scope = true
+
+ # Configure the default scope used by Devise. By default it's the first devise
+ # role declared in your routes.
+ config.default_scope = :user
+
+ # ==> Navigation configuration
+ # Lists the formats that should be treated as navigational. Formats like
+ # :html, should redirect to the sign in page when the user does not have
+ # access, but formats like :xml or :json, should return 401.
+ # If you have any extra navigational formats, like :iphone or :mobile, you
+ # should add them to the navigational formats lists. Default is [:html]
+ # config.navigational_formats = [:html, :iphone]
+
+ # ==> Warden configuration
+ # If you want to use other strategies, that are not (yet) supported by Devise,
+ # you can configure them inside the config.warden block. The example below
+ # allows you to setup OAuth, using http://github.com/roman/warden_oauth
+ #
+ # config.warden do |manager|
+ # manager.oauth(:twitter) do |twitter|
+ # twitter.consumer_secret = <YOUR CONSUMER SECRET>
+ # twitter.consumer_key = <YOUR CONSUMER KEY>
+ # twitter.options :site => 'http://twitter.com'
+ # end
+ # manager.default_strategies(:scope => :user).unshift :twitter_oauth
+ # end
+end
54 config/initializers/formtastic.rb
@@ -0,0 +1,54 @@
+# Set the default text field size when input is a string. Default is 50.
+# Formtastic::SemanticFormBuilder.default_text_field_size = 50
+
+# Set the default text area height when input is a text. Default is 20.
+# Formtastic::SemanticFormBuilder.default_text_area_height = 5
+
+# Should all fields be considered "required" by default?
+# Defaults to true, see ValidationReflection notes below.
+# Formtastic::SemanticFormBuilder.all_fields_required_by_default = true
+
+# Should select fields have a blank option/prompt by default?
+# Defaults to true.
+# Formtastic::SemanticFormBuilder.include_blank_for_select_by_default = true
+
+# Set the string that will be appended to the labels/fieldsets which are required
+# It accepts string or procs and the default is a localized version of
+# '<abbr title="required">*</abbr>'. In other words, if you configure formtastic.required
+# in your locale, it will replace the abbr title properly. But if you don't want to use
+# abbr tag, you can simply give a string as below
+# Formtastic::SemanticFormBuilder.required_string = "(required)"
+
+# Set the string that will be appended to the labels/fieldsets which are optional
+# Defaults to an empty string ("") and also accepts procs (see required_string above)
+# Formtastic::SemanticFormBuilder.optional_string = "(optional)"
+
+# Set the way inline errors will be displayed.
+# Defaults to :sentence, valid options are :sentence, :list and :none
+# Formtastic::SemanticFormBuilder.inline_errors = :sentence
+
+# Set the method to call on label text to transform or format it for human-friendly
+# reading when formtastic is used without object. Defaults to :humanize.
+# Formtastic::SemanticFormBuilder.label_str_method = :humanize
+
+# Set the array of methods to try calling on parent objects in :select and :radio inputs
+# for the text inside each @<option>@ tag or alongside each radio @<input>@. The first method
+# that is found on the object will be used.
+# Defaults to ["to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
+# Formtastic::SemanticFormBuilder.collection_label_methods = [
+# "to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
+
+# Formtastic by default renders inside li tags the input, hints and then
+# errors messages. Sometimes you want the hints to be rendered first than
+# the input, in the following order: hints, input and errors. You can
+# customize it doing just as below:
+# Formtastic::SemanticFormBuilder.inline_order = [:input, :hints, :errors]
+
+# Specifies if labels/hints for input fields automatically be looked up using I18n.
+# Default value: false. Overridden for specific fields by setting value to true,
+# i.e. :label => true, or :hint => true (or opposite depending on initialized value)
+# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
+
+# You can add custom inputs or override parts of Formtastic by subclassing SemanticFormBuilder and
+# specifying that class here. Defaults to SemanticFormBuilder.
+# Formtastic::SemanticFormHelper.builder = MyCustomBuilder
7 config/initializers/generators.rb
@@ -0,0 +1,7 @@
+Features::Application.configure do
+ config.generators do |g|
+ g.template_engine :erb
+ g.integration_tool :cucumber
+ g.test_framework :rspec, :fixture => true, :views => false
+ end
+end
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
1  config/initializers/mailer.rb
@@ -0,0 +1 @@
+ActionMailer::Base.default_url_options[:host] = APP_CONFIG['host']
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
7 config/initializers/secret_token.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+Rails.application.config.secret_token = 'cf1f1c746cbb4ed1c2894154fc7520cb922456005ccb8fdb758277b424a95af71d1f06cf1ff13618876152eaacd3d0681e9f4306dfffd1385991691434c324aa'
8 config/initializers/session_store.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+Rails.application.config.session_store :cookie_store, :key => '_features_session'
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rake db:sessions:create")
+# Rails.application.config.session_store :active_record_store
1  config/initializers/simple_navigation.rb
@@ -0,0 +1 @@
+SimpleNavigation.config_file_path = File.join(Rails.root, 'config', 'navigations')
6 config/initializers/time_formats.rb
@@ -0,0 +1,6 @@
+{
+ :short_date => "%x", # 04/13/10
+ :long_date => "%a, %b %d, %Y" # Tue, Apr 13, 2010
+}.each do |k, v|
+ Time::DATE_FORMATS.update(k => v)
+end
5 config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
11 config/navigations/main_navigation.rb
@@ -0,0 +1,11 @@
+SimpleNavigation::Configuration.run do |navigation|
+ navigation.autogenerate_item_ids = false
+
+ navigation.items do |primary|
+ primary.dom_class = 'main'
+
+ primary.with_options(:if => Proc.new { user_signed_in? }) do |signed_in_user|
+ signed_in_user.item :dashboard, 'Dashboard', root_path
+ end
+ end
+end
17 config/navigations/user_navigation.rb
@@ -0,0 +1,17 @@
+SimpleNavigation::Configuration.run do |navigation|
+ navigation.autogenerate_item_ids = false
+
+ navigation.items do |user|
+ user.dom_class = 'user'
+
+ user.with_options(:if => Proc.new { user_signed_in? }) do |signed_in_user|
+ signed_in_user.item :account, 'My Account', edit_user_registration_path
+ signed_in_user.item :sign_out, 'Sign out', destroy_user_session_path
+ end
+
+ user.with_options(:unless => Proc.new { user_signed_in? }) do |signed_out_user|
+ signed_out_user.item :sign_in, 'Sign in', new_user_session_path
+ signed_out_user.item :sign_up, 'Sign up', new_user_registration_path
+ end
+ end
+end
8 config/routes.rb
@@ -0,0 +1,8 @@
+Features::Application.routes.draw do
+ devise_for :users, :path_names => { :sign_in => 'login', :sign_out => 'logout' }
+ match 'login', :to => 'devise/sessions#new', :as => 'new_user_session'
+ match 'logout', :to => 'devise/sessions#destroy', :as => 'destroy_user_session'
+ match 'signup', :to => 'devise/registrations#new', :as => 'new_user_registration'
+
+ root :to => 'dashboard#index'
+end
19 db/migrate/20100713113845_devise_create_users.rb
@@ -0,0 +1,19 @@
+class DeviseCreateUsers < ActiveRecord::Migration
+ def self.up
+ create_table(:users) do |t|
+ t.database_authenticatable :null => false
+ t.recoverable
+ t.rememberable
+ t.trackable
+ t.timestamps
+ t.string :full_name
+ end
+
+ add_index :users, :email, :unique => true
+ add_index :users, :reset_password_token, :unique => true
+ end
+
+ def self.down
+ drop_table :users
+ end
+end
34 db/schema.rb
@@ -0,0 +1,34 @@
+# This file is auto-generated from the current state of the database. Instead of editing this file,
+# please use the migrations feature of Active Record to incrementally modify your database, and
+# then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your database schema. If you need
+# to create the application database on another system, you should be using db:schema:load, not running
+# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20100713113845) do
+
+ create_table "users", :force => true do |t|
+ t.string "email", :default => "", :null => false
+ t.string "encrypted_password", :limit => 128, :default => "", :null => false
+ t.string "password_salt", :default => "", :null => false
+ t.string "reset_password_token"
+ t.string "remember_token"
+ t.datetime "remember_created_at"
+ t.integer "sign_in_count", :default => 0
+ t.datetime "current_sign_in_at"
+ t.datetime "last_sign_in_at"
+ t.string "current_sign_in_ip"
+ t.string "last_sign_in_ip"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "full_name"
+ end
+
+ add_index "users", ["email"], :name => "index_users_on_email", :unique => true
+ add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
+
+end
7 db/seeds.rb
@@ -0,0 +1,7 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Mayor.create(:name => 'Daley', :city => cities.first)
2  doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
10 features/devise.feature
@@ -0,0 +1,10 @@
+Feature: Devise
+
+ Scenario: User sign in
+ Given a user: "current_user" exists with email: "me@timurv.ru", password: "123456"
+ When I go to the new user session page
+ And I fill in "user_email" with "me@timurv.ru"
+ And I fill in "user_password" with "123456"
+ And I press "Sign in"
+ Then I should be on the home page
+ And I should see "Signed in successfully."
16 features/step_definitions/app_steps.rb
@@ -0,0 +1,16 @@
+Given /^I am not authenticated$/ do
+ visit(destroy_user_session_path)
+end
+
+Given /^I am logged in as "(.*?)\/(.*?)"$/ do |email, password|
+ Given %{a user: "admin" exists with email: "#{email}", password: "#{password}", admin: true}
+ And %{I go to the new user session page}
+ And %{I fill in "user_email" with "#{email}"}
+ And %{I fill in "user_password" with "#{password}"}
+ And %{I press "Sign in"}
+end
+
+When /^I send "(GET|POST|PUT|DELETE)" request to (.+)$/ do |method, page_name|
+ Capybara.current_session.driver.process(method.downcase.to_sym, path_to(page_name))
+end
+
87 features/step_definitions/pickle_steps.rb
@@ -0,0 +1,87 @@
+# this file generated by script/generate pickle
+
+# create a model
+Given(/^#{capture_model} exists?(?: with #{capture_fields})?$/) do |name, fields|
+ create_model(name, fields)
+end
+
+# create n models
+Given(/^(\d+) #{capture_plural_factory} exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
+ count.to_i.times { create_model(plural_factory.singularize, fields) }
+end
+
+# create models from a table
+Given(/^the following #{capture_plural_factory} exists?:?$/) do |plural_factory, table|
+ create_models_from_table(plural_factory, table)
+end
+
+# find a model
+Then(/^#{capture_model} should exist(?: with #{capture_fields})?$/) do |name, fields|
+ find_model!(name, fields)
+end
+
+# not find a model
+Then(/^#{capture_model} should not exist(?: with #{capture_fields})?$/) do |name, fields|
+ find_model(name, fields).should be_nil
+end
+
+# find models with a table
+Then(/^the following #{capture_plural_factory} should exists?:?$/) do |plural_factory, table|
+ find_models_from_table(plural_factory, table).should_not be_any(&:nil?)
+end
+
+# find exactly n models
+Then(/^(\d+) #{capture_plural_factory} should exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
+ find_models(plural_factory.singularize, fields).size.should == count.to_i
+end
+
+# assert equality of models
+Then(/^#{capture_model} should be #{capture_model}$/) do |a, b|
+ model!(a).should == model!(b)
+end
+
+# assert model is in another model's has_many assoc
+Then(/^#{capture_model} should be (?:in|one of|amongst) #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
+ model!(owner).send(association).should include(model!(target))
+end
+
+# assert model is not in another model's has_many assoc
+Then(/^#{capture_model} should not be (?:in|one of|amongst) #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
+ model!(owner).send(association).should_not include(model!(target))
+end
+
+# assert model is another model's has_one/belongs_to assoc
+Then(/^#{capture_model} should be #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
+ model!(owner).send(association).should == model!(target)
+end
+
+# assert model is not another model's has_one/belongs_to assoc
+Then(/^#{capture_model} should not be #{capture_model}(?:'s)? (\w+)$/) do |target, owner, association|
+ model!(owner).send(association).should_not == model!(target)
+end
+
+# assert model.predicate?
+Then(/^#{capture_model} should (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
+ model!(name).should send("be_#{predicate.gsub(' ', '_')}")
+end
+
+# assert not model.predicate?
+Then(/^#{capture_model} should not (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
+ model!(name).should_not send("be_#{predicate.gsub(' ', '_')}")
+end
+
+# model.attribute.should eql(value)
+# model.attribute.should_not eql(value)
+Then(/^#{capture_model}'s (\w+) (should(?: not)?) be #{capture_value}$/) do |name, attribute, expectation, expected|
+ actual_value = model(name).send(attribute)
+ expectation = expectation.gsub(' ', '_')
+
+ case expected
+ when 'nil', 'true', 'false'
+ actual_value.send(expectation, send("be_#{expected}"))
+ when /^-?[0-9_]+$/
+ actual_value.send(expectation, eql(expected.to_i))
+ else
+ actual_value.to_s.send(expectation, eql(expected))
+ end
+end
219 features/step_definitions/web_steps.rb
@@ -0,0 +1,219 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+require 'uri'
+require 'cgi'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
+
+module WithinHelpers
+ def with_scope(locator)
+ locator ? within(locator) { yield } : yield
+ end
+end
+World(WithinHelpers)
+
+Given /^(?:|I )am on (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )go to (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
+ with_scope(selector) do
+ click_button(button)
+ end
+end
+
+When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
+ with_scope(selector) do
+ click_link(link)
+ end
+end
+
+When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector|
+ with_scope(selector) do
+ fill_in(field, :with => value)
+ end
+end
+
+When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
+ with_scope(selector) do
+ fill_in(field, :with => value)
+ end
+end
+
+# Use this to fill in an entire form with data from a table. Example:
+#
+# When I fill in the following:
+# | Account Number | 5002 |
+# | Expiry date | 2009-11-01 |
+# | Note | Nice guy |
+# | Wants Email? | |
+#
+# TODO: Add support for checkbox, select og option
+# based on naming conventions.
+#
+When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields|
+ with_scope(selector) do
+ fields.rows_hash.each do |name, value|
+ When %{I fill in "#{name}" with "#{value}"}
+ end
+ end
+end
+
+When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
+ with_scope(selector) do
+ select(value, :from => field)
+ end
+end
+
+When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ check(field)
+ end
+end
+
+When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ uncheck(field)
+ end
+end
+
+When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ choose(field)
+ end
+end
+
+When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"(?: within "([^"]*)")?$/ do |path, field, selector|
+ with_scope(selector) do
+ attach_file(field, path)
+ end
+end
+
+Then /^(?:|I )should see JSON:$/ do |expected_json|
+ require 'json'
+ expected = JSON.pretty_generate(JSON.parse(expected_json))
+ actual = JSON.pretty_generate(JSON.parse(response.body))
+ expected.should == actual
+end
+
+Then /^(?:|I )should see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_content(text)
+ else
+ assert page.has_content?(text)
+ end
+ end
+end
+
+Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
+ regexp = Regexp.new(regexp)
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_xpath('//*', :text => regexp)
+ else
+ assert page.has_xpath?('//*', :text => regexp)
+ end
+ end
+end
+
+Then /^(?:|I )should not see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_no_content(text)
+ else
+ assert page.has_no_content?(text)
+ end
+ end
+end
+
+Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
+ regexp = Regexp.new(regexp)
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_no_xpath('//*', :text => regexp)
+ else
+ assert page.has_no_xpath?('//*', :text => regexp)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value|
+ with_scope(selector) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should
+ field_value.should =~ /#{value}/
+ else
+ assert_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value|
+ with_scope(selector) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should_not
+ field_value.should_not =~ /#{value}/
+ else
+ assert_no_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector|
+ with_scope(selector) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_true
+ else
+ assert field_checked
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector|
+ with_scope(selector) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_false
+ else
+ assert !field_checked
+ end
+ end
+end
+
+Then /^(?:|I )should be on (.+)$/ do |page_name|
+ current_path = URI.parse(current_url).path
+ if current_path.respond_to? :should
+ current_path.should == path_to(page_name)
+ else
+ assert_equal path_to(page_name), current_path
+ end
+end
+
+Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
+ query = URI.parse(current_url).query
+ actual_params = query ? CGI.parse(query) : {}
+ expected_params = {}
+ expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
+
+ if actual_params.respond_to? :should
+ actual_params.should == expected_params
+ else
+ assert_equal expected_params, actual_params
+ end
+end
+
+Then /^show me the page$/ do
+ save_and_open_page
+end
58 features/support/env.rb
@@ -0,0 +1,58 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+ENV["RAILS_ENV"] ||= "test"
+require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
+
+require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
+require 'cucumber/rails/rspec'
+require 'cucumber/rails/world'
+require 'cucumber/rails/active_record'
+require 'cucumber/web/tableish'
+
+require 'capybara/rails'
+require 'capybara/cucumber'
+require 'capybara/session'
+require 'cucumber/rails/capybara_javascript_emulation' # Lets you click links with onclick javascript handlers without using @culerity or @javascript
+# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
+# order to ease the transition to Capybara we set the default here. If you'd
+# prefer to use XPath just remove this line and adjust any selectors in your
+# steps to use the XPath syntax.
+Capybara.default_selector = :css
+
+# If you set this to false, any error raised from within your app will bubble
+# up to your step definition and out to cucumber unless you catch it somewhere
+# on the way. You can make Rails rescue errors and render error pages on a
+# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag.
+#
+# If you set this to true, Rails will rescue all errors and render error
+# pages, more or less in the same way your application would behave in the
+# default production environment. It's not recommended to do this for all
+# of your scenarios, as this makes it hard to discover errors in your application.
+ActionController::Base.allow_rescue = false
+
+# If you set this to true, each scenario will run in a database transaction.
+# You can still turn off transactions on a per-scenario basis, simply tagging
+# a feature or scenario with the @no-txn tag. If you are using Capybara,
+# tagging with @culerity or @javascript will also turn transactions off.
+#
+# If you set this to false, transactions will be off for all scenarios,
+# regardless of whether you use @no-txn or not.
+#
+# Beware that turning transactions off will leave data in your database
+# after each scenario, which can lead to hard-to-debug failures in
+# subsequent scenarios. If you do this, we recommend you create a Before
+# block that will explicitly put your database in a known state.
+Cucumber::Rails::World.use_transactional_fixtures = true
+# How to clean your database when transactions are turned off. See
+# http://github.com/bmabey/database_cleaner for more info.
+if defined?(ActiveRecord::Base)
+ begin
+ require 'database_cleaner'
+ DatabaseCleaner.strategy = :truncation
+ rescue LoadError => ignore_if_database_cleaner_not_present
+ end
+end
1  features/support/factory_girl.rb
@@ -0,0 +1 @@
+require 'factory_girl'
37 features/support/paths.rb
@@ -0,0 +1,37 @@
+module NavigationHelpers
+ # Maps a name to a path. Used by the
+ #
+ # When /^I go to (.+)$/ do |page_name|
+ #
+ # step definition in web_steps.rb
+ #
+ def path_to(page_name)
+ case page_name
+
+ when /the home\s?page/
+ '/'
+ when /^the edit unit page with id: (\d+)$/i
+ edit_unit_path(:id => $1)
+ when /^the edit room page with id: (\d+)$/i
+ edit_room_path(:id => $1)
+ when /^the room page with id: (\d+)$/i
+ room_path(:id => $1)
+ when /^the user page with id: (\d+)$/i
+ user_path(:id => $1)
+ when /^the user units page with id: (\d+)$/i
+ user_units_path(:user_id => $1)
+
+ else
+ begin
+ page_name =~ /the (.*) page/
+ path_components = $1.split(/\s+/)
+ self.send(path_components.push('path').join('_').to_sym)
+ rescue Object => e
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
+ "Now, go and add a mapping in #{__FILE__}"
+ end
+ end
+ end
+end
+
+World(NavigationHelpers)
6 features/support/pickle.rb
@@ -0,0 +1,6 @@
+require 'pickle/world'
+
+Pickle.configure do |config|
+ config.adapters = [:factory_girl]
+ config.map 'I', 'myself', 'me', 'my', :to => 'user: "me"'
+end
0  lib/tasks/.gitkeep
No changes.
53 lib/tasks/cucumber.rake
@@ -0,0 +1,53 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
+
+vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
+$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
+
+begin
+ require 'cucumber/rake/task'
+
+ namespace :cucumber do
+ Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
+ t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'default'
+ end
+
+ Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'wip'
+ end
+
+ Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'rerun'
+ end
+
+ desc 'Run all features'
+ task :all => [:ok, :wip]
+ end
+ desc 'Alias for cucumber:ok'
+ task :cucumber => 'cucumber:ok'
+
+ task :default => :cucumber
+
+ task :features => :cucumber do
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
+ end
+rescue LoadError
+ desc 'cucumber rake task not available (cucumber not installed)'
+ task :cucumber do
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
+ end
+end
+
+end
26 public/404.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
26 public/422.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>
+ <p>Maybe you tried to change something you didn't have access to.</p>
+ </div>
+</body>
+</html>
26 public/500.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>We're sorry, but something went wrong (500)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/500.html -->
+ <div class="dialog">
+ <h1>We're sorry, but something went wrong.</h1>
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
+ </div>
+</body>
+</html>
0  public/favicon.ico
No changes.
85 public/flutie/stylesheets/defaults.css
@@ -0,0 +1,85 @@
+body {
+ color: #222;
+ font-size: 13px;
+ font-family: arial, "Helvetica Neue", "Lucida Grande", Helvetica, Arial, Verdana, sans-serif;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #111;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+/* Success, error & notice boxes for messages and errors. */
+div.error, div.alert, div.notice, div.success,
+#flash_failure, #flash_alert, #flash_success, #flash_notice {
+ padding: 0.8em;
+ margin-bottom: 1em;
+ border: 1px solid #ddd;
+ -moz-border-radius: 8px;
+ -webkit-border-radius: 8px;
+}
+div.error, div.alert,
+#flash_failure, #flash_alert {
+ background: #FBE3E4;
+ color: #D12F19;
+ border-color: #FBC2C4;
+}
+div.error a, div.alert a,
+#flash_failure a, #flash_alert a { color: #D12F19; }
+
+div.notice,
+#flash_notice {
+ background: #FFF6BF;
+ color: #817134;
+ border-color: #FFD324;
+}
+div.notice a,
+#flash_notice a { color: #817134; }
+
+div.success,
+#flash_success {
+ background: #E6EFC2;
+ color: #529214;
+ border-color: #C6D880;
+}
+div.success a,
+#flash_success a { color: #529214; }
+
+/* Misc classes and elements */
+
+/* Use a .box to create a padded box inside a column. */
+.box {
+ padding: 1.5em;
+ margin-bottom: 1.5em;
+ background: #eee;
+}
+
+/* Use this to create a horizontal ruler across a column. */
+hr {
+ background: #ddd;
+ color: #ddd;
+ clear: both;
+ float: none;
+ width: 100%;
+ height: 1px;
+ margin: 0 0 1.4em;
+ border: none;
+}
+hr.space {
+ background: #fff;
+ color: #fff;
+}
+
+/* Clearfix hack I love you */
+.clearfix:after {
+ content:".";
+ display:block;
+ height:0;
+ clear:both;
+ visibility:hidden;
+}
+
+.clearfix {display:inline-block;}
+/* Hide from IE Mac \*/
+.clearfix {display:block;}
+/* End hide from IE Mac */
117 public/flutie/stylesheets/forms.css
@@ -0,0 +1,117 @@
+/* Forms */
+
+input[type="submit"]::-moz-focus-inner {
+ border: none;
+ } /*removes dotted outline on submit buttons when clicking in firefox */
+
+ form ol {
+ list-style: none;
+ margin: 0 0 1em 0;
+ }
+ form ol ol { margin-left: 0; }
+ form ol li { margin: 0 0 1em 0; list-style-position: outside; }
+ form ol ol li { margin: 0 0 .25em 0; list-style-position: outside; } /*list-style-position fixes IE label margin bug*/
+
+ form ol li.error input { background: #FBE3E4; }
+ p.inline-errors { color: #D12F19; }
+ form ol li.file {
+ background: #e1e1e1;
+ border: 1px solid #c8c8c8;
+ padding: 10px;
+ }
+
+ form abbr { border-bottom: 0; }
+
+ label { display: block; }
+ .required label { font-weight: bold; }
+ .checkbox_field label, .radio_field label { font-weight: normal; }
+
+ a.cancel { color: #7d0d0d; }
+ .inline-hints {
+ font-size: 0.8em;
+ color: #666;
+ margin-bottom: 0.25em;
+ }
+
+/* Fieldsets */
+fieldset {
+ margin: 0 0 1.5em 0;
+ background: #f1f1f1;
+ padding: 1.5em 1.5em 1em 1.5em;
+ border: 1px solid #e3e3e3;
+}
+fieldset fieldset, fieldset fieldset fieldset {
+ padding: 0;
+ border: 0;
+}
+legend { font-weight: bold; }
+fieldset.buttons {
+ background: inherit;
+ border: 0;
+ padding: 0;
+}
+fieldset.buttons li { display: inline; }
+.radio fieldset {
+ padding: 0;
+ margin: 0;
+}
+
+/* Text fields */
+input[type="text"], input[type="password"] {
+ width: 300px;
+ padding: 3px 2px;
+ font-size: inherit;
+}
+input[disabled='disabled'] {
+ background-color: #fcfcfc;
+ cursor:default;
+}
+input[type="checkbox"] {
+ margin: 0 3px 0 0;
+ vertical-align: middle;
+ position: relative;
+ top: -2px;
+}
+input[type="radio"] {
+ margin: 0 3px 0 0;
+ vertical-align: middle;
+ position: relative;
+ top: -2px;
+}
+.check_boxes label {
+ vertical-align: middle;
+ padding: 0;
+ display: inline;
+}
+.radio label { padding: 0; }
+
+/* Textareas */
+textarea {
+ width: 440px;
+ height: 200px;
+ margin: 0 0.5em 0.5em 0;
+ padding: 5px;
+ font-size: inherit;
+}
+
+/* Select fields */
+fieldset .select select {
+ width:200px;
+ font-size: 0.9em;
+}
+optgroup { margin: 0 0 .5em 0; }
+
+/* Date & Time */
+form ol li.date ol li, form ol li.time ol li {
+ display: inline;
+}
+form ol li.datetime ol li {
+ display: inline-block;
+}
+form ol li.datetime select, form ol li.date select, form ol li.time select {
+ display: inline;
+ width: auto;
+}
+form ol li.date label, form ol li.time label {
+ display: none;
+}
25 public/flutie/stylesheets/lists.css
@@ -0,0 +1,25 @@
+/* Lists */
+
+ul, ol {
+ margin-bottom: 1.5em;
+ list-style-position: inside;
+}
+ul { list-style-type: disc; }
+ol { list-style-type: decimal; }
+
+dl {
+ margin-bottom: 1.5em;
+ line-height: 1.4;
+}
+dl dt {
+ font-weight: bold;
+ margin-top: 0.5em;
+}
+dl dd { margin-bottom: 0em;}
+dd { margin-left: 0.5em; }
+
+li { line-height: 1.4; }
+
+ol ol, ol ul, ul ul, ul ol {
+ margin-left: 1em;
+}
49 public/flutie/stylesheets/reset.css
@@ -0,0 +1,49 @@
+/* http://meyerweb.com/eric/tools/css/reset/ */
+/* v1.0 | 20080212 */
+
+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,
+font, 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 {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%;
+ vertical-align: baseline;
+ background: transparent;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
+}
+
+/* remember to highlight inserts somehow! */
+ins {
+ text-decoration: none;
+}
+del {
+ text-decoration: line-through;
+}
+
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
0  public/flutie/stylesheets/screen.css
No changes.
27 public/flutie/stylesheets/tables.css
@@ -0,0 +1,27 @@
+/* Tables */
+
+table {
+ margin-bottom: 2em;
+ width: 100%;
+}
+th {
+ border-bottom: 2px solid #ccc;
+ font-weight: bold;
+ text-align: left;
+}
+td { border-bottom: 1px solid #ddd; }
+caption, th, td {
+ padding: 4px 10px 4px 0;
+}
+caption {
+ background: #f1f1f1;
+ padding: 10px 0;
+ margin-bottom: 1em;
+}
+
+tr,td,th {
+ vertical-align: middle;
+}
+
+/* Use this if you use span-x classes on th/td. */
+table .last { padding-right: 0; }
90 public/flutie/stylesheets/type.css
@@ -0,0 +1,90 @@
+/* Headings */
+
+h1, h2, h3, h4, h5, h6 { font-weight: bold; }
+
+h1 {
+ font-size: 2.2em;
+ line-height: 1;
+ margin-bottom: 0.25em;
+}
+h2 {
+ font-size: 1.6em;
+ margin-bottom: 0.25em;
+ line-height: 1.1;
+}
+h3 {
+ font-size: 1.3em;
+ line-height: 1;
+ margin-bottom: 0.25em;
+}
+h4 {
+ font-size: 1.1em;
+ line-height: 1.25;
+ margin-bottom: 0.25em;
+}
+h5 {
+ font-size: 1em;
+ margin-bottom: 0.25em;
+}
+h6 {
+ font-size: 1em;
+ margin-bottom: 0.25em;
+}
+
+/* Text elements */
+p { margin-bottom: 0.5em; }
+p.last { margin-bottom: 0; }
+p img {
+ float: left;
+ margin: 1.5em 1.5em 1.5em 0;
+ padding: 0;
+}
+p img.top { margin-top: 0; } /* Use this if the image is at the top of the <p>. */
+img { margin: 0 0 1.5em; }
+
+abbr, acronym {
+ border-bottom: 1px dotted #666;
+ cursor: help;
+}
+address {
+ margin-top: 1.5em;
+ font-style: italic;
+}
+del { color:#666; }
+
+a, a:link {
+ color: #1a4882;
+ text-decoration: underline;
+}
+a:visited { color: #1a4882; }
+a:hover { color: #052246; }
+a:active, a:focus { color: #1a4882; }
+
+blockquote {
+ margin: 1.5em 0;
+ color: #666;
+ font-style: italic;
+ padding-left: 1em;
+ border-left: 4px solid #d1d1d1;
+}
+strong { font-weight: bold; }
+em, dfn { font-style: italic; }
+dfn { font-weight: bold; }
+pre, code {
+ margin: 1.5em 0;
+ white-space: pre;
+}
+pre, code, tt {
+ font: 1em 'andale mono', 'monotype.com', 'lucida console', monospace;
+ line-height: 1.5;
+}
+pre.code {
+ background: #000;
+ color: #fff;
+ padding: 20px;
+}
+tt {
+ display: block;
+ margin: 1.5em 0;
+ line-height: 1.5;
+}
BIN  public/images/rails.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2  public/javascripts/application.js
@@ -0,0 +1,2 @@
+// Place your application-specific JavaScript functions and classes here
+// This file is automatically included by javascript_include_tag :defaults
965 public/javascripts/controls.js
@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+ throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { };
+Autocompleter.Base = Class.create({
+ baseInitialize: function(element, update, options) {
+ element = $(element);
+ this.element = element;
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+ this.oldElementValue = this.element.value;
+
+ if(this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || { };
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {
+ setHeight: false,
+ offsetTop: element.offsetHeight
+ });
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if(typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+ // Force carriage returns as token delimiters anyway
+ if (!this.options.tokens.include('\n'))
+ this.options.tokens.push('\n');
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (Prototype.Browser.IE) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
+ },
+
+ hide: function() {
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+ if(this.iefix) Element.hide(this.iefix);
+ },
+
+ startIndicator: function() {
+ if(this.options.indicator) Element.show(this.options.indicator);
+ },
+
+ stopIndicator: function() {
+ if(this.options.indicator) Element.hide(this.options.indicator);
+ },
+
+ onKeyPress: function(event) {
+ if(this.active)
+ switch(event.keyCode) {
+ case Event.KEY_TAB:
+ case Event.KEY_RETURN:
+ this.selectEntry();
+ Event.stop(event);
+ case Event.KEY_ESC:
+ this.hide();
+ this.active = false;
+ Event.stop(event);
+ return;
+ case Event.KEY_LEFT:
+ case Event.KEY_RIGHT:
+ return;
+ case Event.KEY_UP:
+ this.markPrevious();
+ this.render();
+ Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+ this.changed = true;
+ this.hasFocus = true;
+
+ if(this.observer) clearTimeout(this.observer);
+ this.observer =
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+ },
+
+ activate: function() {
+ this.changed = false;
+ this.hasFocus = true;
+ this.getUpdatedChoices();
+ },
+
+ onHover: function(event) {
+ var element = Event.findElement(event, 'LI');
+ if(this.index != element.autocompleteIndex)
+ {
+ this.index = element.autocompleteIndex;
+ this.render();
+ }
+ Event.stop(event);
+ },
+
+ onClick: function(event) {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ },
+
+ onBlur: function(event) {
+ // needed to make click events working
+ setTimeout(this.hide.bind(this), 250);
+ this.hasFocus = false;
+ this.active = false;
+ },
+
+ render: function() {
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+ if(this.hasFocus) {
+ this.show();
+ this.active = true;
+ }
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ markPrevious: function() {
+ if(this.index > 0) this.index--;
+ else this.index = this.entryCount-1;
+ this.getEntry(this.index).scrollIntoView(true);
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++;
+ else this.index = 0;
+ this.getEntry(this.index).scrollIntoView(false);
+ },
+
+ getEntry: function(index) {
+ return this.update.firstChild.childNodes[index];
+ },
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
+ },
+
+ selectEntry: function() {
+ this.active = false;
+ this.updateElement(this.getCurrentEntry());
+ },
+
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ return;
+ }
+ var value = '';
+ if (this.options.select) {
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+ } else
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+ var bounds = this.getTokenBounds();
+ if (bounds[0] != -1) {
+ var newValue = this.element.value.substr(0, bounds[0]);
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+ } else {
+ this.element.value = value;
+ }
+ this.oldElementValue = this.element.value;
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
+ },
+
+ updateChoices: function(choices) {
+ if(!this.changed && this.hasFocus) {
+ this.update.innerHTML = choices;
+ Element.cleanWhitespace(this.update);
+ Element.cleanWhitespace(this.update.down());
+
+ if(this.update.firstChild && this.update.down().childNodes) {
+ this.entryCount =
+ this.update.down().childNodes.length;
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
+ entry.autocompleteIndex = i;
+ this.addObservers(entry);
+ }
+ } else {
+