Commit edb3508c621935dae072096dfcd11d7f82daf333

Authored by root
0 parents

initial checkin

  1 +== Welcome to Rails
  2 +
  3 +Rails is a web-application framework that includes everything needed to create
  4 +database-backed web applications according to the Model-View-Control pattern.
  5 +
  6 +This pattern splits the view (also called the presentation) into "dumb" templates
  7 +that are primarily responsible for inserting pre-built data in between HTML tags.
  8 +The model contains the "smart" domain objects (such as Account, Product, Person,
  9 +Post) that holds all the business logic and knows how to persist themselves to
  10 +a database. The controller handles the incoming requests (such as Save New Account,
  11 +Update Product, Show Post) by manipulating the model and directing data to the view.
  12 +
  13 +In Rails, the model is handled by what's called an object-relational mapping
  14 +layer entitled Active Record. This layer allows you to present the data from
  15 +database rows as objects and embellish these data objects with business logic
  16 +methods. You can read more about Active Record in
  17 +link:files/vendor/rails/activerecord/README.html.
  18 +
  19 +The controller and view are handled by the Action Pack, which handles both
  20 +layers by its two parts: Action View and Action Controller. These two layers
  21 +are bundled in a single package due to their heavy interdependence. This is
  22 +unlike the relationship between the Active Record and Action Pack that is much
  23 +more separate. Each of these packages can be used independently outside of
  24 +Rails. You can read more about Action Pack in
  25 +link:files/vendor/rails/actionpack/README.html.
  26 +
  27 +
  28 +== Getting Started
  29 +
  30 +1. At the command prompt, start a new Rails application using the <tt>rails</tt> command
  31 + and your application name. Ex: rails myapp
  32 +2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
  33 +3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!"
  34 +4. Follow the guidelines to start developing your application
  35 +
  36 +
  37 +== Web Servers
  38 +
  39 +By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails
  40 +with a variety of other web servers.
  41 +
  42 +Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is
  43 +suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
  44 +getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
  45 +More info at: http://mongrel.rubyforge.org
  46 +
  47 +Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or
  48 +Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use
  49 +FCGI or proxy to a pack of Mongrels/Thin/Ebb servers.
  50 +
  51 +== Apache .htaccess example for FCGI/CGI
  52 +
  53 +# General Apache options
  54 +AddHandler fastcgi-script .fcgi
  55 +AddHandler cgi-script .cgi
  56 +Options +FollowSymLinks +ExecCGI
  57 +
  58 +# If you don't want Rails to look in certain directories,
  59 +# use the following rewrite rules so that Apache won't rewrite certain requests
  60 +#
  61 +# Example:
  62 +# RewriteCond %{REQUEST_URI} ^/notrails.*
  63 +# RewriteRule .* - [L]
  64 +
  65 +# Redirect all requests not available on the filesystem to Rails
  66 +# By default the cgi dispatcher is used which is very slow
  67 +#
  68 +# For better performance replace the dispatcher with the fastcgi one
  69 +#
  70 +# Example:
  71 +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
  72 +RewriteEngine On
  73 +
  74 +# If your Rails application is accessed via an Alias directive,
  75 +# then you MUST also set the RewriteBase in this htaccess file.
  76 +#
  77 +# Example:
  78 +# Alias /myrailsapp /path/to/myrailsapp/public
  79 +# RewriteBase /myrailsapp
  80 +
  81 +RewriteRule ^$ index.html [QSA]
  82 +RewriteRule ^([^.]+)$ $1.html [QSA]
  83 +RewriteCond %{REQUEST_FILENAME} !-f
  84 +RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
  85 +
  86 +# In case Rails experiences terminal errors
  87 +# Instead of displaying this message you can supply a file here which will be rendered instead
  88 +#
  89 +# Example:
  90 +# ErrorDocument 500 /500.html
  91 +
  92 +ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
  93 +
  94 +
  95 +== Debugging Rails
  96 +
  97 +Sometimes your application goes wrong. Fortunately there are a lot of tools that
  98 +will help you debug it and get it back on the rails.
  99 +
  100 +First area to check is the application log files. Have "tail -f" commands running
  101 +on the server.log and development.log. Rails will automatically display debugging
  102 +and runtime information to these files. Debugging info will also be shown in the
  103 +browser on requests from 127.0.0.1.
  104 +
  105 +You can also log your own messages directly into the log file from your code using
  106 +the Ruby logger class from inside your controllers. Example:
  107 +
  108 + class WeblogController < ActionController::Base
  109 + def destroy
  110 + @weblog = Weblog.find(params[:id])
  111 + @weblog.destroy
  112 + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
  113 + end
  114 + end
  115 +
  116 +The result will be a message in your log file along the lines of:
  117 +
  118 + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1
  119 +
  120 +More information on how to use the logger is at http://www.ruby-doc.org/core/
  121 +
  122 +Also, Ruby documentation can be found at http://www.ruby-lang.org/ including:
  123 +
  124 +* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/
  125 +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
  126 +
  127 +These two online (and free) books will bring you up to speed on the Ruby language
  128 +and also on programming in general.
  129 +
  130 +
  131 +== Debugger
  132 +
  133 +Debugger support is available through the debugger command when you start your Mongrel or
  134 +Webrick server with --debugger. This means that you can break out of execution at any point
  135 +in the code, investigate and change the model, AND then resume execution!
  136 +You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'
  137 +Example:
  138 +
  139 + class WeblogController < ActionController::Base
  140 + def index
  141 + @posts = Post.find(:all)
  142 + debugger
  143 + end
  144 + end
  145 +
  146 +So the controller will accept the action, run the first line, then present you
  147 +with a IRB prompt in the server window. Here you can do things like:
  148 +
  149 + >> @posts.inspect
  150 + => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
  151 + #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
  152 + >> @posts.first.title = "hello from a debugger"
  153 + => "hello from a debugger"
  154 +
  155 +...and even better is that you can examine how your runtime objects actually work:
  156 +
  157 + >> f = @posts.first
  158 + => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
  159 + >> f.
  160 + Display all 152 possibilities? (y or n)
  161 +
  162 +Finally, when you're ready to resume execution, you enter "cont"
  163 +
  164 +
  165 +== Console
  166 +
  167 +You can interact with the domain model by starting the console through <tt>script/console</tt>.
  168 +Here you'll have all parts of the application configured, just like it is when the
  169 +application is running. You can inspect domain models, change values, and save to the
  170 +database. Starting the script without arguments will launch it in the development environment.
  171 +Passing an argument will specify a different environment, like <tt>script/console production</tt>.
  172 +
  173 +To reload your controllers and models after launching the console run <tt>reload!</tt>
  174 +
  175 +== dbconsole
  176 +
  177 +You can go to the command line of your database directly through <tt>script/dbconsole</tt>.
  178 +You would be connected to the database with the credentials defined in database.yml.
  179 +Starting the script without arguments will connect you to the development database. Passing an
  180 +argument will connect you to a different database, like <tt>script/dbconsole production</tt>.
  181 +Currently works for mysql, postgresql and sqlite.
  182 +
  183 +== Description of Contents
  184 +
  185 +app
  186 + Holds all the code that's specific to this particular application.
  187 +
  188 +app/controllers
  189 + Holds controllers that should be named like weblogs_controller.rb for
  190 + automated URL mapping. All controllers should descend from ApplicationController
  191 + which itself descends from ActionController::Base.
  192 +
  193 +app/models
  194 + Holds models that should be named like post.rb.
  195 + Most models will descend from ActiveRecord::Base.
  196 +
  197 +app/views
  198 + Holds the template files for the view that should be named like
  199 + weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby
  200 + syntax.
  201 +
  202 +app/views/layouts
  203 + Holds the template files for layouts to be used with views. This models the common
  204 + header/footer method of wrapping views. In your views, define a layout using the
  205 + <tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,
  206 + call <% yield %> to render the view using this layout.
  207 +
  208 +app/helpers
  209 + Holds view helpers that should be named like weblogs_helper.rb. These are generated
  210 + for you automatically when using script/generate for controllers. Helpers can be used to
  211 + wrap functionality for your views into methods.
  212 +
  213 +config
  214 + Configuration files for the Rails environment, the routing map, the database, and other dependencies.
  215 +
  216 +db
  217 + Contains the database schema in schema.rb. db/migrate contains all
  218 + the sequence of Migrations for your schema.
  219 +
  220 +doc
  221 + This directory is where your application documentation will be stored when generated
  222 + using <tt>rake doc:app</tt>
  223 +
  224 +lib
  225 + Application specific libraries. Basically, any kind of custom code that doesn't
  226 + belong under controllers, models, or helpers. This directory is in the load path.
  227 +
  228 +public
  229 + The directory available for the web server. Contains subdirectories for images, stylesheets,
  230 + and javascripts. Also contains the dispatchers and the default HTML files. This should be
  231 + set as the DOCUMENT_ROOT of your web server.
  232 +
  233 +script
  234 + Helper scripts for automation and generation.
  235 +
  236 +test
  237 + Unit and functional tests along with fixtures. When using the script/generate scripts, template
  238 + test files will be generated for you and placed in this directory.
  239 +
  240 +vendor
  241 + External libraries that the application depends on. Also includes the plugins subdirectory.
  242 + If the app has frozen rails, those gems also go here, under vendor/rails/.
  243 + This directory is in the load path.
  1 +# Add your own tasks in files placed in lib/tasks ending in .rake,
  2 +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
  3 +
  4 +require(File.join(File.dirname(__FILE__), 'config', 'boot'))
  5 +
  6 +require 'rake'
  7 +require 'rake/testtask'
  8 +require 'rake/rdoctask'
  9 +
  10 +require 'tasks/rails'
  1 +# Filters added to this controller apply to all controllers in the application.
  2 +# Likewise, all the methods added will be available for all controllers.
  3 +
  4 +class ApplicationController < ActionController::Base
  5 + helper :all # include all helpers, all the time
  6 + protect_from_forgery # See ActionController::RequestForgeryProtection for details
  7 +
  8 + # Scrub sensitive parameters from your log
  9 + # filter_parameter_logging :password
  10 +end
  1 +class WelcomeController < ApplicationController
  2 + def index
  3 + end
  4 +
  5 +end
  1 +# Methods added to this helper will be available to all templates in the application.
  2 +module ApplicationHelper
  3 +end
  1 +module WelcomeHelper
  2 +end
  1 +<h1>(WWW) Weird Web Workers</h1>
  2 +<img src="weird.png" />
  3 +<p>Welcome to the freshly new started Weird Web Workers.</p>
  4 +<p>Right now, you have been to early, this actually just started.</p>
  5 +<p>We hope you visit us again later and watch us evolving.</p>
  6 +<br />
  7 +<p>Contact us via: <a href="mailto:webmaster@weird-web-workers.org">webmaster@weird-web-workers.org</a>
  8 +<br />
  9 +<p>Last changed: Tue Apr 19 08:26:35 CEST 2011</p>
  1 +# Rack Dispatcher
  2 +
  3 +# Require your environment file to bootstrap Rails
  4 +require File.dirname(__FILE__) + '/config/environment'
  5 +
  6 +# Dispatch the request
  7 +run ActionController::Dispatcher.new
  1 +# Don't change this file!
  2 +# Configure your app in config/environment.rb and config/environments/*.rb
  3 +
  4 +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
  5 +
  6 +module Rails
  7 + class << self
  8 + def boot!
  9 + unless booted?
  10 + preinitialize
  11 + pick_boot.run
  12 + end
  13 + end
  14 +
  15 + def booted?
  16 + defined? Rails::Initializer
  17 + end
  18 +
  19 + def pick_boot
  20 + (vendor_rails? ? VendorBoot : GemBoot).new
  21 + end
  22 +
  23 + def vendor_rails?
  24 + File.exist?("#{RAILS_ROOT}/vendor/rails")
  25 + end
  26 +
  27 + def preinitialize
  28 + load(preinitializer_path) if File.exist?(preinitializer_path)
  29 + end
  30 +
  31 + def preinitializer_path
  32 + "#{RAILS_ROOT}/config/preinitializer.rb"
  33 + end
  34 + end
  35 +
  36 + class Boot
  37 + def run
  38 + load_initializer
  39 + Rails::Initializer.run(:set_load_path)
  40 + end
  41 + end
  42 +
  43 + class VendorBoot < Boot
  44 + def load_initializer
  45 + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
  46 + Rails::Initializer.run(:install_gem_spec_stubs)
  47 + Rails::GemDependency.add_frozen_gem_path
  48 + end
  49 + end
  50 +
  51 + class GemBoot < Boot
  52 + def load_initializer
  53 + self.class.load_rubygems
  54 + load_rails_gem
  55 + require 'initializer'
  56 + end
  57 +
  58 + def load_rails_gem
  59 + if version = self.class.gem_version
  60 + gem 'rails', version
  61 + else
  62 + gem 'rails'
  63 + end
  64 + rescue Gem::LoadError => load_error
  65 + if load_error.message =~ /Could not find RubyGem rails/
  66 + STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
  67 + exit 1
  68 + else
  69 + raise
  70 + end
  71 + end
  72 +
  73 + class << self
  74 + def rubygems_version
  75 + Gem::RubyGemsVersion rescue nil
  76 + end
  77 +
  78 + def gem_version
  79 + if defined? RAILS_GEM_VERSION
  80 + RAILS_GEM_VERSION
  81 + elsif ENV.include?('RAILS_GEM_VERSION')
  82 + ENV['RAILS_GEM_VERSION']
  83 + else
  84 + parse_gem_version(read_environment_rb)
  85 + end
  86 + end
  87 +
  88 + def load_rubygems
  89 + min_version = '1.3.2'
  90 + require 'rubygems'
  91 + unless rubygems_version >= min_version
  92 + $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
  93 + exit 1
  94 + end
  95 +
  96 + rescue LoadError
  97 + $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
  98 + exit 1
  99 + end
  100 +
  101 + def parse_gem_version(text)
  102 + $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
  103 + end
  104 +
  105 + private
  106 + def read_environment_rb
  107 + File.read("#{RAILS_ROOT}/config/environment.rb")
  108 + end
  109 + end
  110 + end
  111 +end
  112 +
  113 +# All that for this:
  114 +Rails.boot!
  1 +# SQLite version 3.x
  2 +# gem install sqlite3-ruby (not necessary on OS X Leopard)
  3 +development:
  4 + adapter: sqlite3
  5 + database: db/development.sqlite3
  6 + pool: 5
  7 + timeout: 5000
  8 +
  9 +# Warning: The database defined as "test" will be erased and
  10 +# re-generated from your development database when you run "rake".
  11 +# Do not set this db to the same as development or production.
  12 +test:
  13 + adapter: sqlite3
  14 + database: db/test.sqlite3
  15 + pool: 5
  16 + timeout: 5000
  17 +
  18 +production:
  19 + adapter: sqlite3
  20 + database: db/production.sqlite3
  21 + pool: 5
  22 + timeout: 5000
  1 +# Be sure to restart your server when you modify this file
  2 +
  3 +# Specifies gem version of Rails to use when vendor/rails is not present
  4 +RAILS_GEM_VERSION = '2.3.11' unless defined? RAILS_GEM_VERSION
  5 +
  6 +# Bootstrap the Rails environment, frameworks, and default configuration
  7 +require File.join(File.dirname(__FILE__), 'boot')
  8 +
  9 +Rails::Initializer.run do |config|
  10 + # Settings in config/environments/* take precedence over those specified here.
  11 + # Application configuration should go into files in config/initializers
  12 + # -- all .rb files in that directory are automatically loaded.
  13 +
  14 + # Add additional load paths for your own custom dirs
  15 + # config.autoload_paths += %W( #{RAILS_ROOT}/extras )
  16 +
  17 + # Specify gems that this application depends on and have them installed with rake gems:install
  18 + # config.gem "bj"
  19 + # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
  20 + # config.gem "sqlite3-ruby", :lib => "sqlite3"
  21 + # config.gem "aws-s3", :lib => "aws/s3"
  22 +
  23 + # Only load the plugins named here, in the order given (default is alphabetical).
  24 + # :all can be used as a placeholder for all plugins not explicitly named
  25 + # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
  26 +
  27 + # Skip frameworks you're not going to use. To use Rails without a database,
  28 + # you must remove the Active Record framework.
  29 + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
  30 +
  31 + # Activate observers that should always be running
  32 + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
  33 +
  34 + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
  35 + # Run "rake -D time" for a list of tasks for finding time zone names.
  36 + config.time_zone = 'UTC'
  37 +
  38 + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
  39 + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
  40 + # config.i18n.default_locale = :de
  41 +end
  1 +# Settings specified here will take precedence over those in config/environment.rb
  2 +
  3 +# In the development environment your application's code is reloaded on
  4 +# every request. This slows down response time but is perfect for development
  5 +# since you don't have to restart the webserver when you make code changes.
  6 +config.cache_classes = false
  7 +
  8 +# Log error messages when you accidentally call methods on nil.
  9 +config.whiny_nils = true
  10 +
  11 +# Show full error reports and disable caching
  12 +config.action_controller.consider_all_requests_local = true
  13 +config.action_view.debug_rjs = true
  14 +config.action_controller.perform_caching = false
  15 +
  16 +# Don't care if the mailer can't send
  17 +config.action_mailer.raise_delivery_errors = false
  1 +# Settings specified here will take precedence over those in config/environment.rb
  2 +
  3 +# The production environment is meant for finished, "live" apps.
  4 +# Code is not reloaded between requests
  5 +config.cache_classes = true
  6 +
  7 +# Full error reports are disabled and caching is turned on
  8 +config.action_controller.consider_all_requests_local = false
  9 +config.action_controller.perform_caching = true
  10 +config.action_view.cache_template_loading = true
  11 +
  12 +# See everything in the log (default is :info)
  13 +# config.log_level = :debug
  14 +
  15 +# Use a different logger for distributed setups
  16 +# config.logger = SyslogLogger.new
  17 +
  18 +# Use a different cache store in production
  19 +# config.cache_store = :mem_cache_store
  20 +
  21 +# Enable serving of images, stylesheets, and javascripts from an asset server
  22 +# config.action_controller.asset_host = "http://assets.example.com"
  23 +
  24 +# Disable delivery errors, bad email addresses will be ignored
  25 +# config.action_mailer.raise_delivery_errors = false
  26 +
  27 +# Enable threaded mode
  28 +# config.threadsafe!
  1 +# Settings specified here will take precedence over those in config/environment.rb
  2 +
  3 +# The test environment is used exclusively to run your application's
  4 +# test suite. You never need to work with it otherwise. Remember that
  5 +# your test database is "scratch space" for the test suite and is wiped
  6 +# and recreated between test runs. Don't rely on the data there!
  7 +config.cache_classes = true
  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.action_controller.consider_all_requests_local = true
  14 +config.action_controller.perform_caching = false
  15 +config.action_view.cache_template_loading = true
  16 +
  17 +# Disable request forgery protection in test environment
  18 +config.action_controller.allow_forgery_protection = false
  19 +
  20 +# Tell Action Mailer not to deliver emails to the real world.
  21 +# The :test delivery method accumulates sent emails in the
  22 +# ActionMailer::Base.deliveries array.
  23 +config.action_mailer.delivery_method = :test
  24 +
  25 +# Use SQL instead of Active Record's schema dumper when creating the test database.
  26 +# This is necessary if your schema can't be completely dumped by the schema dumper,
  27 +# like if you have constraints or database-specific column types
  28 +# config.active_record.schema_format = :sql
  1 +# Be sure to restart your server when you modify this file.
  2 +
  3 +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
  4 +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
  5 +
  6 +# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
  7 +# Rails.backtrace_cleaner.remove_silencers!
  1 +# Be sure to restart your server when you modify this file.
  2 +
  3 +# Your secret key for verifying the integrity of signed cookies.
  4 +# If you change this key, all old signed cookies will become invalid!
  5 +# Make sure the secret is at least 30 characters and all random,
  6 +# no regular words or you'll be exposed to dictionary attacks.
  7 +ActionController::Base.cookie_verifier_secret = '84f6816372e7c509df89e71bee301eeba1d2632fe2ec41c9850c094c7cdf2118c15a7523ece77e707c9c4a0fbff3575b470df6f547f77c9bfe54355198d8d1cc';
  1 +# Be sure to restart your server when you modify this file.
  2 +
  3 +# Add new inflection rules using the following format
  4 +# (all these examples are active by default):
  5 +# ActiveSupport::Inflector.inflections do |inflect|
  6 +# inflect.plural /^(ox)$/i, '\1en'
  7 +# inflect.singular /^(ox)en/i, '\1'
  8 +# inflect.irregular 'person', 'people'
  9 +# inflect.uncountable %w( fish sheep )
  10 +# end
  1 +# Be sure to restart your server when you modify this file.
  2 +
  3 +# Add new mime types for use in respond_to blocks:
  4 +# Mime::Type.register "text/richtext", :rtf
  5 +# Mime::Type.register_alias "text/html", :iphone
  1 +# Be sure to restart your server when you modify this file.
  2 +
  3 +# These settings change the behavior of Rails 2 apps and will be defaults
  4 +# for Rails 3. You can remove this initializer when Rails 3 is released.
  5 +
  6 +if defined?(ActiveRecord)
  7 + # Include Active Record class name as root for JSON serialized output.
  8 + ActiveRecord::Base.include_root_in_json = true
  9 +
  10 + # Store the full class name (including module namespace) in STI type column.
  11 + ActiveRecord::Base.store_full_sti_class = true
  12 +end
  13 +
  14 +ActionController::Routing.generate_best_match = false
  15 +
  16 +# Use ISO 8601 format for JSON serialized times and dates.
  17 +ActiveSupport.use_standard_json_time_format = true
  18 +
  19 +# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
  20 +# if you're including raw json in an HTML page.
  21 +ActiveSupport.escape_html_entities_in_json = false
  1 +# Be sure to restart your server when you modify this file.
  2 +
  3 +# Your secret key for verifying cookie session data integrity.
  4 +# If you change this key, all old sessions will become invalid!
  5 +# Make sure the secret is at least 30 characters and all random,
  6 +# no regular words or you'll be exposed to dictionary attacks.
  7 +ActionController::Base.session = {
  8 + :key => '_weird-web-workers_session',
  9 + :secret => '473daf0543d67e15b642475bc0c8ac4266c9051c482651f23fd808f8d07f296329a92237bde0e82726b9e6827929a951deaa4900db6196042ef0a41de476471d'
  10 +}
  11 +
  12 +# Use the database for sessions instead of the cookie-based default,
  13 +# which shouldn't be used to store highly confidential information
  14 +# (create the session table with "rake db:sessions:create")
  15 +# ActionController::Base.session_store = :active_record_store
  1 +# Sample localization file for English. Add more files in this directory for other locales.
  2 +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
  3 +
  4 +en:
  5 + hello: "Hello world"
  1 +ActionController::Routing::Routes.draw do |map|
  2 + # The priority is based upon order of creation: first created -> highest priority.
  3 +
  4 + # Sample of regular route:
  5 + # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
  6 + # Keep in mind you can assign values other than :controller and :action
  7 +
  8 + # Sample of named route:
  9 + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
  10 + # This route can be invoked with purchase_url(:id => product.id)
  11 +
  12 + # Sample resource route (maps HTTP verbs to controller actions automatically):
  13 + # map.resources :products
  14 +
  15 + # Sample resource route with options:
  16 + # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
  17 +
  18 + # Sample resource route with sub-resources:
  19 + # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
  20 +
  21 + # Sample resource route with more complex sub-resources
  22 + # map.resources :products do |products|
  23 + # products.resources :comments
  24 + # products.resources :sales, :collection => { :recent => :get }
  25 + # end
  26 +
  27 + # Sample resource route within a namespace:
  28 + # map.namespace :admin do |admin|
  29 + # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
  30 + # admin.resources :products
  31 + # end
  32 +
  33 + # You can have the root of your site routed with map.root -- just remember to delete public/index.html.
  34 + map.root :controller => "welcome"
  35 +
  36 + # See how all your routes lay out with "rake routes"
  37 +
  38 + # Install the default routes as the lowest priority.
  39 + # Note: These default routes make all actions in every controller accessible via GET requests. You should
  40 + # consider removing or commenting them out if you're using named routes and resources.
  41 + map.connect ':controller/:action/:id'
  42 + map.connect ':controller/:action/:id.:format'
  43 +end
No preview for this file type
  1 +# This file is auto-generated from the current state of the database. Instead of editing this file,
  2 +# please use the migrations feature of Active Record to incrementally modify your database, and
  3 +# then regenerate this schema definition.
  4 +#
  5 +# Note that this schema.rb definition is the authoritative source for your database schema. If you need
  6 +# to create the application database on another system, you should be using db:schema:load, not running
  7 +# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
  8 +# you'll amass, the slower it'll run and the greater likelihood for issues).
  9 +#
  10 +# It's strongly recommended to check this file into your version control system.
  11 +
  12 +ActiveRecord::Schema.define(:version => 0) do
  13 +
  14 +end
  1 +# This file should contain all the record creation needed to seed the database with its default values.
  2 +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
  3 +#
  4 +# Examples:
  5 +#
  6 +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
  7 +# Major.create(:name => 'Daley', :city => cities.first)
  1 +Use this README file to introduce your application and point to useful places in the API for learning more.
  2 +Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
  1 +/!\ FAILSAFE /!\ Tue Apr 19 08:05:46 +0200 2011
  2 + Status: 500 Internal Server Error
  3 + unable to open database file
  4 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/sqlite3_adapter.rb:13:in `initialize'
  5 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/sqlite3_adapter.rb:13:in `new'
  6 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/sqlite3_adapter.rb:13:in `sqlite3_connection'
  7 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:223:in `send'
  8 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:223:in `new_connection'
  9 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:245:in `checkout_new_connection'
  10 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:188:in `checkout'
  11 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:184:in `loop'
  12 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:184:in `checkout'
  13 + /usr/lib64/rubyee/1.8/monitor.rb:242:in `synchronize'
  14 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:183:in `checkout'
  15 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:98:in `connection'
  16 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:326:in `retrieve_connection'
  17 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_specification.rb:123:in `retrieve_connection'
  18 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_specification.rb:115:in `connection'
  19 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/query_cache.rb:9:in `cache'
  20 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/query_cache.rb:28:in `call'
  21 + /usr/lib64/rubyee/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/connection_pool.rb:361:in `call'
  22 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/string_coercion.rb:25:in `call'
  23 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/head.rb:9:in `call'
  24 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/methodoverride.rb:24:in `call'
  25 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/params_parser.rb:15:in `call'
  26 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/session/cookie_store.rb:99:in `call'
  27 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/failsafe.rb:26:in `call'
  28 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/lock.rb:11:in `call'
  29 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/lock.rb:11:in `synchronize'
  30 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/lock.rb:11:in `call'
  31 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/dispatcher.rb:114:in `call'
  32 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/reloader.rb:34:in `run'
  33 + /usr/lib64/rubyee/gems/1.8/gems/actionpack-2.3.11/lib/action_controller/dispatcher.rb:108:in `call'
  34 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/content_length.rb:13:in `call'
  35 + /usr/lib64/rubyee/gems/1.8/gems/rack-1.1.2/lib/rack/handler/fastcgi.rb:57:in `serve'
  36 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:103:in `process_request'
  37 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:153:in `with_signal_handler'
  38 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:101:in `process_request'
  39 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:78:in `process_each_request'
  40 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:77:in `each'
  41 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:77:in `process_each_request'
  42 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:76:in `catch'
  43 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:76:in `process_each_request'
  44 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:51:in `process!'
  45 + /usr/lib64/rubyee/gems/1.8/gems/rails-2.3.11/lib/fcgi_handler.rb:23:in `process!'
  46 + /var/www/weird-web-workers/public/dispatch.fcgi:24
  47 + SQL (0.8ms)  SELECT name
  48 + FROM sqlite_master
  49 + WHERE type = 'table' AND NOT name = 'sqlite_sequence'
  50 +
  51 + SQL (0.1ms) select sqlite_version(*)
  52 + SQL (99.0ms) CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL) 
  53 + SQL (0.1ms) PRAGMA index_list("schema_migrations")
  54 + SQL (77.5ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
  55 + SQL (0.2ms)  SELECT name
  56 + FROM sqlite_master
  57 + WHERE type = 'table' AND NOT name = 'sqlite_sequence'
  58 +
  59 + SQL (0.2ms)  SELECT name
  60 + FROM sqlite_master
  61 + WHERE type = 'table' AND NOT name = 'sqlite_sequence'
  62 +
  63 + SQL (0.1ms) SELECT version FROM schema_migrations
  64 + SQL (0.1ms)  SELECT name
  65 + FROM sqlite_master
  66 + WHERE type = 'table' AND NOT name = 'sqlite_sequence'
  67 +
  68 +
  69 +
  70 +Processing Rails::InfoController#properties (for 85.182.241.135 at 2011-04-19 08:06:14) [GET]
  71 + SQL (1.0ms)  SELECT name
  72 + FROM sqlite_master
  73 + WHERE type = 'table' AND NOT name = 'sqlite_sequence'
  74 +
  75 + SQL (0.2ms) SELECT version FROM schema_migrations
  76 +Completed in 90ms (View: 3, DB: 1) | 200 OK [http://www.weird-web-workers.org/rails/info/properties]
  77 +
  78 +
  79 +Processing ApplicationController#index (for 85.182.241.135 at 2011-04-19 08:09:42) [GET]
  80 +
  81 +ActionController::RoutingError (No route matches "/welcome" with {:method=>:get}):
  82 + public/dispatch.fcgi:24
  83 +
  84 +Rendering rescues/layout (not_found)
  85 +
  86 +
  87 +Processing ApplicationController#index (for 85.182.241.135 at 2011-04-19 08:13:01) [GET]
  88 +
  89 +ActionController::RoutingError (No route matches "/welcome" with {:method=>:get}):
  90 + public/dispatch.fcgi:24
  91 +
  92 +Rendering rescues/layout (not_found)
  93 +
  94 +
  95 +Processing ApplicationController#index (for 85.182.241.135 at 2011-04-19 08:14:59) [GET]
  96 +
  97 +NameError (uninitialized constant WelcomeController):
  98 + public/dispatch.fcgi:24
  99 +
  100 +Rendered rescues/_trace (23.3ms)
  101 +Rendered rescues/_request_and_response (1.5ms)
  102 +Rendering rescues/layout (internal_server_error)
  103 +
  104 +
  105 +Processing ApplicationController#index (for 85.182.241.135 at 2011-04-19 08:15:58) [GET]
  106 +
  107 +NameError (uninitialized constant WelcomeController):
  108 + public/dispatch.fcgi:24
  109 +
  110 +Rendered rescues/_trace (23.2ms)
  111 +Rendered rescues/_request_and_response (1.4ms)
  112 +Rendering rescues/layout (internal_server_error)
  113 +
  114 +
  115 +Processing WelcomesController#index (for 85.182.241.135 at 2011-04-19 08:17:33) [GET]
  116 + Welcome Load (0.0ms) SQLite3::SQLException: no such table: welcomes: SELECT * FROM "welcomes" 
  117 +
  118 +ActiveRecord::StatementInvalid (SQLite3::SQLException: no such table: welcomes: SELECT * FROM "welcomes" ):
  119 + app/controllers/welcomes_controller.rb:5:in `index'
  120 + public/dispatch.fcgi:24
  121 +
  122 +Rendered rescues/_trace (33.5ms)
  123 +Rendered rescues/_request_and_response (0.5ms)
  124 +Rendering rescues/layout (internal_server_error)
  125 +
  126 +
  127 +Processing WelcomeController#index (for 85.182.241.135 at 2011-04-19 08:19:35) [GET]
  128 +Rendering welcome/index
  129 +Completed in 14ms (View: 12, DB: 0) | 200 OK [http://www.weird-web-workers.org/]
  130 +
  131 +
  132 +Processing WelcomeController#index (for 85.182.241.135 at 2011-04-19 08:26:51) [GET]
  133 +Rendering welcome/index
  134 +Completed in 12ms (View: 10, DB: 0) | 200 OK [http://www.weird-web-workers.org/]
  135 +
  136 +
  137 +Processing WelcomeController#index (for 85.182.241.135 at 2011-04-19 08:27:13) [GET]
  138 +Rendering welcome/index
  139 +Completed in 12ms (View: 10, DB: 0) | 200 OK [http://www.weird-web-workers.org/]
  140 +
  141 +
  142 +Processing WelcomeController#index (for 85.182.241.135 at 2011-04-19 08:27:39) [GET]
  143 +Rendering welcome/index
  144 +Completed in 12ms (View: 10, DB: 0) | 200 OK [http://www.weird-web-workers.org/]
  145 +
  146 +
  147 +Processing WelcomeController#index (for 85.182.241.135 at 2011-04-20 11:26:49) [GET]
  148 +Rendering welcome/index
  149 +Completed in 98ms (View: 96, DB: 0) | 200 OK [http://weird-web-workers.org/]
  150 +
  151 +
  152 +Processing WelcomeController#index (for 66.249.17.127 at 2011-04-20 11:27:42) [GET]
  153 +Rendering welcome/index
  154 +Completed in 3ms (View: 1, DB: 0) | 200 OK [http://weird-web-workers.org/]
  155 +
  156 +
  157 +Processing WelcomeController#index (for 85.182.241.135 at 2011-04-20 12:26:32) [GET]
  158 +Rendering welcome/index
  159 +Completed in 2ms (View: 1, DB: 0) | 200 OK [http://weird-web-workers.org/]
  160 +
  161 +
  162 +Processing WelcomeController#index (for 85.182.241.134 at 2011-04-20 12:26:45) [GET]
  163 +Rendering welcome/index
  164 +Completed in 2ms (View: 1, DB: 0) | 200 OK [http://weird-web-workers.org/]
  1 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  2 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3 +
  4 +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  5 +
  6 +<head>
  7 + <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  8 + <title>The page you were looking for doesn't exist (404)</title>
  9 + <style type="text/css">
  10 + body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
  11 + div.dialog {
  12 + width: 25em;
  13 + padding: 0 4em;
  14 + margin: 4em auto 0 auto;
  15 + border: 1px solid #ccc;
  16 + border-right-color: #999;
  17 + border-bottom-color: #999;
  18 + }
  19 + h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
  20 + </style>
  21 +</head>
  22 +
  23 +<body>
  24 + <!-- This file lives in public/404.html -->
  25 + <div class="dialog">
  26 + <h1>The page you were looking for doesn't exist.</h1>
  27 + <p>You may have mistyped the address or the page may have moved.</p>
  28 + </div>
  29 +</body>
  30 +</html>
  1 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  2 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3 +
  4 +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  5 +
  6 +<head>
  7 + <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  8 + <title>The change you wanted was rejected (422)</title>
  9 + <style type="text/css">
  10 + body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
  11 + div.dialog {
  12 + width: 25em;
  13 + padding: 0 4em;
  14 + margin: 4em auto 0 auto;
  15 + border: 1px solid #ccc;
  16 + border-right-color: #999;
  17 + border-bottom-color: #999;
  18 + }
  19 + h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
  20 + </style>
  21 +</head>
  22 +
  23 +<body>
  24 + <!-- This file lives in public/422.html -->
  25 + <div class="dialog">
  26 + <h1>The change you wanted was rejected.</h1>
  27 + <p>Maybe you tried to change something you didn't have access to.</p>
  28 + </div>
  29 +</body>
  30 +</html>
  1 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  2 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3 +
  4 +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  5 +
  6 +<head>
  7 + <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  8 + <title>We're sorry, but something went wrong (500)</title>
  9 + <style type="text/css">
  10 + body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
  11 + div.dialog {
  12 + width: 25em;
  13 + padding: 0 4em;
  14 + margin: 4em auto 0 auto;
  15 + border: 1px solid #ccc;
  16 + border-right-color: #999;
  17 + border-bottom-color: #999;
  18 + }
  19 + h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
  20 + </style>
  21 +</head>
  22 +
  23 +<body>
  24 + <!-- This file lives in public/500.html -->
  25 + <div class="dialog">
  26 + <h1>We're sorry, but something went wrong.</h1>
  27 + <p>We've been notified about this issue and we'll take a look at it shortly.</p>
  28 + </div>
  29 +</body>
  30 +</html>
  1 +#!/usr/bin/env ruby
  2 +
  3 +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
  4 +
  5 +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
  6 +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
  7 +require "dispatcher"
  8 +
  9 +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
  10 +Dispatcher.dispatch
  1 +#!/usr/bin/env ruby
  2 +#
  3 +# You may specify the path to the FastCGI crash log (a log of unhandled
  4 +# exceptions which forced the FastCGI instance to exit, great for debugging)
  5 +# and the number of requests to process before running garbage collection.
  6 +#
  7 +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
  8 +# and the GC period is nil (turned off). A reasonable number of requests
  9 +# could range from 10-100 depending on the memory footprint of your app.
  10 +#
  11 +# Example:
  12 +# # Default log path, normal GC behavior.
  13 +# RailsFCGIHandler.process!
  14 +#
  15 +# # Default log path, 50 requests between GC.
  16 +# RailsFCGIHandler.process! nil, 50
  17 +#
  18 +# # Custom log path, normal GC behavior.
  19 +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
  20 +#
  21 +require File.dirname(__FILE__) + "/../config/environment"
  22 +require 'fcgi_handler'
  23 +
  24 +RailsFCGIHandler.process! nil, 50
  25 +
  1 +#!/usr/bin/env ruby
  2 +
  3 +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
  4 +
  5 +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
  6 +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
  7 +require "dispatcher"
  8 +
  9 +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
  10 +Dispatcher.dispatch
  1 +// Place your application-specific JavaScript functions and classes here
  2 +// This file is automatically included by javascript_include_tag :defaults
  1 +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2 +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
  3 +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
  4 +// Contributors:
  5 +// Richard Livsey
  6 +// Rahul Bhargava
  7 +// Rob Wills
  8 +//
  9 +// script.aculo.us is freely distributable under the terms of an MIT-style license.
  10 +// For details, see the script.aculo.us web site: http://script.aculo.us/
  11 +
  12 +// Autocompleter.Base handles all the autocompletion functionality
  13 +// that's independent of the data source for autocompletion. This
  14 +// includes drawing the autocompletion menu, observing keyboard
  15 +// and mouse events, and similar.
  16 +//
  17 +// Specific autocompleters need to provide, at the very least,
  18 +// a getUpdatedChoices function that will be invoked every time
  19 +// the text inside the monitored textbox changes. This method
  20 +// should get the text for which to provide autocompletion by
  21 +// invoking this.getToken(), NOT by directly accessing
  22 +// this.element.value. This is to allow incremental tokenized
  23 +// autocompletion. Specific auto-completion logic (AJAX, etc)
  24 +// belongs in getUpdatedChoices.
  25 +//
  26 +// Tokenized incremental autocompletion is enabled automatically
  27 +// when an autocompleter is instantiated with the 'tokens' option
  28 +// in the options parameter, e.g.:
  29 +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
  30 +// will incrementally autocomplete with a comma as the token.
  31 +// Additionally, ',' in the above example can be replaced with
  32 +// a token array, e.g. { tokens: [',', '\n'] } which
  33 +// enables autocompletion on multiple tokens. This is most
  34 +// useful when one of the tokens is \n (a newline), as it
  35 +// allows smart autocompletion after linebreaks.
  36 +
  37 +if(typeof Effect == 'undefined')
  38 + throw("controls.js requires including script.aculo.us' effects.js library");
  39 +
  40 +var Autocompleter = { };
  41 +Autocompleter.Base = Class.create({
  42 + baseInitialize: function(element, update, options) {
  43 + element = $(element);
  44 + this.element = element;
  45 + this.update = $(update);
  46 + this.hasFocus = false;
  47 + this.changed = false;
  48 + this.active = false;
  49 + this.index = 0;
  50 + this.entryCount = 0;
  51 + this.oldElementValue = this.element.value;
  52 +
  53 + if(this.setOptions)
  54 + this.setOptions(options);
  55 + else
  56 + this.options = options || { };
  57 +
  58 + this.options.paramName = this.options.paramName || this.element.name;
  59 + this.options.tokens = this.options.tokens || [];
  60 + this.options.frequency = this.options.frequency || 0.4;
  61 + this.options.minChars = this.options.minChars || 1;
  62 + this.options.onShow = this.options.onShow ||
  63 + function(element, update){
  64 + if(!update.style.position || update.style.position=='absolute') {
  65 + update.style.position = 'absolute';
  66 + Position.clone(element, update, {
  67 + setHeight: false,
  68 + offsetTop: element.offsetHeight
  69 + });
  70 + }
  71 + Effect.Appear(update,{duration:0.15});
  72 + };
  73 + this.options.onHide = this.options.onHide ||
  74 + function(element, update){ new Effect.Fade(update,{duration:0.15}) };
  75 +
  76 + if(typeof(this.options.tokens) == 'string')
  77 + this.options.tokens = new Array(this.options.tokens);
  78 + // Force carriage returns as token delimiters anyway
  79 + if (!this.options.tokens.include('\n'))
  80 + this.options.tokens.push('\n');
  81 +
  82 + this.observer = null;
  83 +
  84 + this.element.setAttribute('autocomplete','off');
  85 +
  86 + Element.hide(this.update);
  87 +
  88 + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
  89 + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  90 + },
  91 +
  92 + show: function() {
  93 + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
  94 + if(!this.iefix &&
  95 + (Prototype.Browser.IE) &&
  96 + (Element.getStyle(this.update, 'position')=='absolute')) {
  97 + new Insertion.After(this.update,
  98 + '<iframe id="' + this.update.id + '_iefix" '+
  99 + 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
  100 + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  101 + this.iefix = $(this.update.id+'_iefix');
  102 + }
  103 + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  104 + },
  105 +
  106 + fixIEOverlapping: function() {
  107 + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
  108 + this.iefix.style.zIndex = 1;
  109 + this.update.style.zIndex = 2;
  110 + Element.show(this.iefix);
  111 + },
  112 +
  113 + hide: function() {
  114 + this.stopIndicator();
  115 + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
  116 + if(this.iefix) Element.hide(this.iefix);
  117 + },
  118 +
  119 + startIndicator: function() {
  120 + if(this.options.indicator) Element.show(this.options.indicator);
  121 + },
  122 +
  123 + stopIndicator: function() {
  124 + if(this.options.indicator) Element.hide(this.options.indicator);
  125 + },
  126 +
  127 + onKeyPress: function(event) {
  128 + if(this.active)
  129 + switch(event.keyCode) {
  130 + case Event.KEY_TAB:
  131 + case Event.KEY_RETURN:
  132 + this.selectEntry();
  133 + Event.stop(event);
  134 + case Event.KEY_ESC:
  135 + this.hide();
  136 + this.active = false;
  137 + Event.stop(event);
  138 + return;
  139 + case Event.KEY_LEFT:
  140 + case Event.KEY_RIGHT:
  141 + return;
  142 + case Event.KEY_UP:
  143 + this.markPrevious();
  144 + this.render();
  145 + Event.stop(event);
  146 + return;
  147 + case Event.KEY_DOWN:
  148 + this.markNext();
  149 + this.render();
  150 + Event.stop(event);
  151 + return;
  152 + }
  153 + else
  154 + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
  155 + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
  156 +
  157 + this.changed = true;
  158 + this.hasFocus = true;
  159 +
  160 + if(this.observer) clearTimeout(this.observer);
  161 + this.observer =
  162 + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  163 + },
  164 +
  165 + activate: function() {
  166 + this.changed = false;
  167 + this.hasFocus = true;
  168 + this.getUpdatedChoices();
  169 + },
  170 +
  171 + onHover: function(event) {
  172 + var element = Event.findElement(event, 'LI');
  173 + if(this.index != element.autocompleteIndex)
  174 + {
  175 + this.index = element.autocompleteIndex;
  176 + this.render();
  177 + }
  178 + Event.stop(event);
  179 + },
  180 +
  181 + onClick: function(event) {
  182 + var element = Event.findElement(event, 'LI');
  183 + this.index = element.autocompleteIndex;
  184 + this.selectEntry();
  185 + this.hide();
  186 + },
  187 +
  188 + onBlur: function(event) {
  189 + // needed to make click events working
  190 + setTimeout(this.hide.bind(this), 250);
  191 + this.hasFocus = false;
  192 + this.active = false;
  193 + },
  194 +
  195 + render: function() {
  196 + if(this.entryCount > 0) {
  197 + for (var i = 0; i < this.entryCount; i++)
  198 + this.index==i ?
  199 + Element.addClassName(this.getEntry(i),"selected") :
  200 + Element.removeClassName(this.getEntry(i),"selected");
  201 + if(this.hasFocus) {
  202 + this.show();
  203 + this.active = true;
  204 + }
  205 + } else {
  206 + this.active = false;
  207 + this.hide();
  208 + }
  209 + },
  210 +
  211 + markPrevious: function() {
  212 + if(this.index > 0) this.index--;
  213 + else this.index = this.entryCount-1;
  214 + this.getEntry(this.index).scrollIntoView(true);
  215 + },
  216 +
  217 + markNext: function() {
  218 + if(this.index < this.entryCount-1) this.index++;
  219 + else this.index = 0;
  220 + this.getEntry(this.index).scrollIntoView(false);
  221 + },
  222 +
  223 + getEntry: function(index) {
  224 + return this.update.firstChild.childNodes[index];
  225 + },
  226 +
  227 + getCurrentEntry: function() {
  228 + return this.getEntry(this.index);
  229 + },
  230 +
  231 + selectEntry: function() {
  232 + this.active = false;
  233 + this.updateElement(this.getCurrentEntry());
  234 + },
  235 +
  236 + updateElement: function(selectedElement) {
  237 + if (this.options.updateElement) {
  238 + this.options.updateElement(selectedElement);
  239 + return;
  240 + }
  241 + var value = '';
  242 + if (this.options.select) {
  243 + var nodes = $(selectedElement).select('.' + this.options.select) || [];
  244 + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
  245 + } else
  246 + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
  247 +
  248 + var bounds = this.getTokenBounds();
  249 + if (bounds[0] != -1) {
  250 + var newValue = this.element.value.substr(0, bounds[0]);
  251 + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
  252 + if (whitespace)
  253 + newValue += whitespace[0];
  254 + this.element.value = newValue + value + this.element.value.substr(bounds[1]);
  255 + } else {
  256 + this.element.value = value;
  257 + }
  258 + this.oldElementValue = this.element.value;
  259 + this.element.focus();
  260 +
  261 + if (this.options.afterUpdateElement)
  262 + this.options.afterUpdateElement(this.element, selectedElement);
  263 + },
  264 +
  265 + updateChoices: function(choices) {
  266 + if(!this.changed && this.hasFocus) {
  267 + this.update.innerHTML = choices;
  268 + Element.cleanWhitespace(this.update);
  269 + Element.cleanWhitespace(this.update.down());
  270 +
  271 + if(this.update.firstChild && this.update.down().childNodes) {
  272 + this.entryCount =
  273 + this.update.down().childNodes.length;
  274 + for (var i = 0; i < this.entryCount; i++) {
  275 + var entry = this.getEntry(i);
  276 + entry.autocompleteIndex = i;
  277 + this.addObservers(entry);
  278 + }
  279 + } else {
  280 + this.entryCount = 0;
  281 + }
  282 +
  283 + this.stopIndicator();
  284 + this.index = 0;
  285 +
  286 + if(this.entryCount==1 && this.options.autoSelect) {
  287 + this.selectEntry();
  288 + this.hide();
  289 + } else {
  290 + this.render();
  291 + }
  292 + }
  293 + },
  294 +
  295 + addObservers: function(element) {
  296 + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
  297 + Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  298 + },
  299 +
  300 + onObserverEvent: function() {
  301 + this.changed = false;
  302 + this.tokenBounds = null;
  303 + if(this.getToken().length>=this.options.minChars) {
  304 + this.getUpdatedChoices();
  305 + } else {
  306 + this.active = false;
  307 + this.hide();
  308 + }
  309 + this.oldElementValue = this.element.value;
  310 + },
  311 +
  312 + getToken: function() {
  313 + var bounds = this.getTokenBounds();
  314 + return this.element.value.substring(bounds[0], bounds[1]).strip();
  315 + },
  316 +
  317 + getTokenBounds: function() {
  318 + if (null != this.tokenBounds) return this.tokenBounds;
  319 + var value = this.element.value;
  320 + if (value.strip().empty()) return [-1, 0];
  321 + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
  322 + var offset = (diff == this.oldElementValue.length ? 1 : 0);
  323 + var prevTokenPos = -1, nextTokenPos = value.length;
  324 + var tp;
  325 + for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
  326 + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
  327 + if (tp > prevTokenPos) prevTokenPos = tp;
  328 + tp = value.indexOf(this.options.tokens[index], diff + offset);
  329 + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
  330 + }
  331 + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  332 + }
  333 +});
  334 +
  335 +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  336 + var boundary = Math.min(newS.length, oldS.length);
  337 + for (var index = 0; index < boundary; ++index)
  338 + if (newS[index] != oldS[index])
  339 + return index;
  340 + return boundary;
  341 +};
  342 +
  343 +Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  344 + initialize: function(element, update, url, options) {
  345 + this.baseInitialize(element, update, options);
  346 + this.options.asynchronous = true;
  347 + this.options.onComplete = this.onComplete.bind(this);
  348 + this.options.defaultParams = this.options.parameters || null;
  349 + this.url = url;
  350 + },
  351 +
  352 + getUpdatedChoices: function() {
  353 + this.startIndicator();
  354 +
  355 + var entry = encodeURIComponent(this.options.paramName) + '=' +
  356 + encodeURIComponent(this.getToken());
  357 +
  358 + this.options.parameters = this.options.callback ?
  359 + this.options.callback(this.element, entry) : entry;
  360 +
  361 + if(this.options.defaultParams)
  362 + this.options.parameters += '&' + this.options.defaultParams;
  363 +
  364 + new Ajax.Request(this.url, this.options);
  365 + },
  366 +
  367 + onComplete: function(request) {
  368 + this.updateChoices(request.responseText);
  369 + }
  370 +});
  371 +
  372 +// The local array autocompleter. Used when you'd prefer to
  373 +// inject an array of autocompletion options into the page, rather
  374 +// than sending out Ajax queries, which can be quite slow sometimes.
  375 +//
  376 +// The constructor takes four parameters. The first two are, as usual,
  377 +// the id of the monitored textbox, and id of the autocompletion menu.
  378 +// The third is the array you want to autocomplete from, and the fourth
  379 +// is the options block.
  380 +//
  381 +// Extra local autocompletion options:
  382 +// - choices - How many autocompletion choices to offer
  383 +//
  384 +// - partialSearch - If false, the autocompleter will match entered
  385 +// text only at the beginning of strings in the
  386 +// autocomplete array. Defaults to true, which will
  387 +// match text at the beginning of any *word* in the
  388 +// strings in the autocomplete array. If you want to
  389 +// search anywhere in the string, additionally set
  390 +// the option fullSearch to true (default: off).
  391 +//
  392 +// - fullSsearch - Search anywhere in autocomplete array strings.
  393 +//
  394 +// - partialChars - How many characters to enter before triggering
  395 +// a partial match (unlike minChars, which defines
  396 +// how many characters are required to do any match
  397 +// at all). Defaults to 2.
  398 +//
  399 +// - ignoreCase - Whether to ignore case when autocompleting.
  400 +// Defaults to true.
  401 +//
  402 +// It's possible to pass in a custom function as the 'selector'
  403 +// option, if you prefer to write your own autocompletion logic.
  404 +// In that case, the other options above will not apply unless
  405 +// you support them.
  406 +
  407 +Autocompleter.Local = Class.create(Autocompleter.Base, {
  408 + initialize: function(element, update, array, options) {
  409 + this.baseInitialize(element, update, options);
  410 + this.options.array = array;
  411 + },
  412 +
  413 + getUpdatedChoices: function() {
  414 + this.updateChoices(this.options.selector(this));
  415 + },
  416 +
  417 + setOptions: function(options) {
  418 + this.options = Object.extend({
  419 + choices: 10,
  420 + partialSearch: true,
  421 + partialChars: 2,
  422 + ignoreCase: true,
  423 + fullSearch: false,
  424 + selector: function(instance) {
  425 + var ret = []; // Beginning matches
  426 + var partial = []; // Inside matches
  427 + var entry = instance.getToken();
  428 + var count = 0;
  429 +
  430 + for (var i = 0; i < instance.options.array.length &&
  431 + ret.length < instance.options.choices ; i++) {
  432 +
  433 + var elem = instance.options.array[i];
  434 + var foundPos = instance.options.ignoreCase ?
  435 + elem.toLowerCase().indexOf(entry.toLowerCase()) :
  436 + elem.indexOf(entry);
  437 +
  438 + while (foundPos != -1) {
  439 + if (foundPos == 0 && elem.length != entry.length) {
  440 + ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
  441 + elem.substr(entry.length) + "</li>");
  442 + break;
  443 + } else if (entry.length >= instance.options.partialChars &&
  444 + instance.options.partialSearch && foundPos != -1) {
  445 + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
  446 + partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
  447 + elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
  448 + foundPos + entry.length) + "</li>");
  449 + break;
  450 + }
  451 + }
  452 +
  453 + foundPos = instance.options.ignoreCase ?
  454 + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
  455 + elem.indexOf(entry, foundPos + 1);
  456 +
  457 + }
  458 + }
  459 + if (partial.length)
  460 + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
  461 + return "<ul>" + ret.join('') + "</ul>";
  462 + }
  463 + }, options || { });
  464 + }
  465 +});
  466 +
  467 +// AJAX in-place editor and collection editor
  468 +// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
  469 +
  470 +// Use this if you notice weird scrolling problems on some browsers,
  471 +// the DOM might be a bit confused when this gets called so do this
  472 +// waits 1 ms (with setTimeout) until it does the activation
  473 +Field.scrollFreeActivate = function(field) {
  474 + setTimeout(function() {
  475 + Field.activate(field);
  476 + }, 1);
  477 +};
  478 +
  479 +Ajax.InPlaceEditor = Class.create({
  480 + initialize: function(element, url, options) {
  481 + this.url = url;
  482 + this.element = element = $(element);
  483 + this.prepareOptions();
  484 + this._controls = { };
  485 + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
  486 + Object.extend(this.options, options || { });
  487 + if (!this.options.formId && this.element.id) {
  488 + this.options.formId = this.element.id + '-inplaceeditor';
  489 + if ($(this.options.formId))
  490 + this.options.formId = '';
  491 + }
  492 + if (this.options.externalControl)
  493 + this.options.externalControl = $(this.options.externalControl);
  494 + if (!this.options.externalControl)
  495 + this.options.externalControlOnly = false;
  496 + this._originalBackground = this.element.getStyle('background-color') || 'transparent';
  497 + this.element.title = this.options.clickToEditText;
  498 + this._boundCancelHandler = this.handleFormCancellation.bind(this);
  499 + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
  500 + this._boundFailureHandler = this.handleAJAXFailure.bind(this);
  501 + this._boundSubmitHandler = this.handleFormSubmission.bind(this);
  502 + this._boundWrapperHandler = this.wrapUp.bind(this);
  503 + this.registerListeners();
  504 + },
  505 + checkForEscapeOrReturn: function(e) {
  506 + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
  507 + if (Event.KEY_ESC == e.keyCode)
  508 + this.handleFormCancellation(e);
  509 + else if (Event.KEY_RETURN == e.keyCode)
  510 + this.handleFormSubmission(e);
  511 + },
  512 + createControl: function(mode, handler, extraClasses) {
  513 + var control = this.options[mode + 'Control'];
  514 + var text = this.options[mode + 'Text'];
  515 + if ('button' == control) {
  516 + var btn = document.createElement('input');
  517 + btn.type = 'submit';
  518 + btn.value = text;
  519 + btn.className = 'editor_' + mode + '_button';
  520 + if ('cancel' == mode)
  521 + btn.onclick = this._boundCancelHandler;
  522 + this._form.appendChild(btn);
  523 + this._controls[mode] = btn;
  524 + } else if ('link' == control) {
  525 + var link = document.createElement('a');
  526 + link.href = '#';
  527 + link.appendChild(document.createTextNode(text));
  528 + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
  529 + link.className = 'editor_' + mode + '_link';
  530 + if (extraClasses)
  531 + link.className += ' ' + extraClasses;
  532 + this._form.appendChild(link);
  533 + this._controls[mode] = link;
  534 + }
  535 + },
  536 + createEditField: function() {
  537 + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
  538 + var fld;
  539 + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
  540 + fld = document.createElement('input');
  541 + fld.type = 'text';
  542 + var size = this.options.size || this.options.cols || 0;
  543 + if (0 < size) fld.size = size;
  544 + } else {
  545 + fld = document.createElement('textarea');
  546 + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
  547 + fld.cols = this.options.cols || 40;
  548 + }
  549 + fld.name = this.options.paramName;
  550 + fld.value = text; // No HTML breaks conversion anymore
  551 + fld.className = 'editor_field';
  552 + if (this.options.submitOnBlur)
  553 + fld.onblur = this._boundSubmitHandler;
  554 + this._controls.editor = fld;
  555 + if (this.options.loadTextURL)
  556 + this.loadExternalText();
  557 + this._form.appendChild(this._controls.editor);
  558 + },
  559 + createForm: function() {
  560 + var ipe = this;
  561 + function addText(mode, condition) {
  562 + var text = ipe.options['text' + mode + 'Controls'];
  563 + if (!text || condition === false) return;
  564 + ipe._form.appendChild(document.createTextNode(text));
  565 + };
  566 + this._form = $(document.createElement('form'));
  567 + this._form.id = this.options.formId;
  568 + this._form.addClassName(this.options.formClassName);
  569 + this._form.onsubmit = this._boundSubmitHandler;
  570 + this.createEditField();
  571 + if ('textarea' == this._controls.editor.tagName.toLowerCase())
  572 + this._form.appendChild(document.createElement('br'));
  573 + if (this.options.onFormCustomization)
  574 + this.options.onFormCustomization(this, this._form);
  575 + addText('Before', this.options.okControl || this.options.cancelControl);
  576 + this.createControl('ok', this._boundSubmitHandler);
  577 + addText('Between', this.options.okControl && this.options.cancelControl);
  578 + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
  579 + addText('After', this.options.okControl || this.options.cancelControl);
  580 + },
  581 + destroy: function() {
  582 + if (this._oldInnerHTML)
  583 + this.element.innerHTML = this._oldInnerHTML;
  584 + this.leaveEditMode();
  585 + this.unregisterListeners();
  586 + },
  587 + enterEditMode: function(e) {
  588 + if (this._saving || this._editing) return;
  589 + this._editing = true;
  590 + this.triggerCallback('onEnterEditMode');
  591 + if (this.options.externalControl)
  592 + this.options.externalControl.hide();
  593 + this.element.hide();
  594 + this.createForm();
  595 + this.element.parentNode.insertBefore(this._form, this.element);
  596 + if (!this.options.loadTextURL)
  597 + this.postProcessEditField();
  598 + if (e) Event.stop(e);
  599 + },
  600 + enterHover: function(e) {
  601 + if (this.options.hoverClassName)
  602 + this.element.addClassName(this.options.hoverClassName);
  603 + if (this._saving) return;
  604 + this.triggerCallback('onEnterHover');
  605 + },
  606 + getText: function() {
  607 + return this.element.innerHTML.unescapeHTML();
  608 + },
  609 + handleAJAXFailure: function(transport) {
  610 + this.triggerCallback('onFailure', transport);
  611 + if (this._oldInnerHTML) {
  612 + this.element.innerHTML = this._oldInnerHTML;
  613 + this._oldInnerHTML = null;
  614 + }
  615 + },
  616 + handleFormCancellation: function(e) {
  617 + this.wrapUp();
  618 + if (e) Event.stop(e);
  619 + },
  620 + handleFormSubmission: function(e) {
  621 + var form = this._form;
  622 + var value = $F(this._controls.editor);
  623 + this.prepareSubmission();
  624 + var params = this.options.callback(form, value) || '';
  625 + if (Object.isString(params))
  626 + params = params.toQueryParams();
  627 + params.editorId = this.element.id;
  628 + if (this.options.htmlResponse) {
  629 + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
  630 + Object.extend(options, {
  631 + parameters: params,
  632 + onComplete: this._boundWrapperHandler,
  633 + onFailure: this._boundFailureHandler
  634 + });
  635 + new Ajax.Updater({ success: this.element }, this.url, options);
  636 + } else {
  637 + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  638 + Object.extend(options, {
  639 + parameters: params,
  640 + onComplete: this._boundWrapperHandler,
  641 + onFailure: this._boundFailureHandler
  642 + });
  643 + new Ajax.Request(this.url, options);
  644 + }
  645 + if (e) Event.stop(e);
  646 + },
  647 + leaveEditMode: function() {
  648 + this.element.removeClassName(this.options.savingClassName);
  649 + this.removeForm();
  650 + this.leaveHover();
  651 + this.element.style.backgroundColor = this._originalBackground;
  652 + this.element.show();
  653 + if (this.options.externalControl)
  654 + this.options.externalControl.show();
  655 + this._saving = false;
  656 + this._editing = false;
  657 + this._oldInnerHTML = null;
  658 + this.triggerCallback('onLeaveEditMode');
  659 + },
  660 + leaveHover: function(e) {
  661 + if (this.options.hoverClassName)
  662 + this.element.removeClassName(this.options.hoverClassName);
  663 + if (this._saving) return;
  664 + this.triggerCallback('onLeaveHover');
  665 + },
  666 + loadExternalText: function() {
  667 + this._form.addClassName(this.options.loadingClassName);
  668 + this._controls.editor.disabled = true;
  669 + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  670 + Object.extend(options, {
  671 + parameters: 'editorId=' + encodeURIComponent(this.element.id),
  672 + onComplete: Prototype.emptyFunction,
  673 + onSuccess: function(transport) {
  674 + this._form.removeClassName(this.options.loadingClassName);
  675 + var text = transport.responseText;
  676 + if (this.options.stripLoadedTextTags)
  677 + text = text.stripTags();
  678 + this._controls.editor.value = text;
  679 + this._controls.editor.disabled = false;
  680 + this.postProcessEditField();
  681 + }.bind(this),
  682 + onFailure: this._boundFailureHandler
  683 + });
  684 + new Ajax.Request(this.options.loadTextURL, options);
  685 + },
  686 + postProcessEditField: function() {
  687 + var fpc = this.options.fieldPostCreation;
  688 + if (fpc)
  689 + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
  690 + },
  691 + prepareOptions: function() {
  692 + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
  693 + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
  694 + [this._extraDefaultOptions].flatten().compact().each(function(defs) {
  695 + Object.extend(this.options, defs);
  696 + }.bind(this));
  697 + },
  698 + prepareSubmission: function() {
  699 + this._saving = true;
  700 + this.removeForm();
  701 + this.leaveHover();
  702 + this.showSaving();
  703 + },
  704 + registerListeners: function() {
  705 + this._listeners = { };
  706 + var listener;
  707 + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
  708 + listener = this[pair.value].bind(this);
  709 + this._listeners[pair.key] = listener;
  710 + if (!this.options.externalControlOnly)
  711 + this.element.observe(pair.key, listener);
  712 + if (this.options.externalControl)
  713 + this.options.externalControl.observe(pair.key, listener);
  714 + }.bind(this));
  715 + },
  716 + removeForm: function() {
  717 + if (!this._form) return;
  718 + this._form.remove();
  719 + this._form = null;
  720 + this._controls = { };
  721 + },
  722 + showSaving: function() {
  723 + this._oldInnerHTML = this.element.innerHTML;
  724 + this.element.innerHTML = this.options.savingText;
  725 + this.element.addClassName(this.options.savingClassName);
  726 + this.element.style.backgroundColor = this._originalBackground;
  727 + this.element.show();
  728 + },
  729 + triggerCallback: function(cbName, arg) {
  730 + if ('function' == typeof this.options[cbName]) {
  731 + this.options[cbName](this, arg);
  732 + }
  733 + },
  734 + unregisterListeners: function() {
  735 + $H(this._listeners).each(function(pair) {
  736 + if (!this.options.externalControlOnly)
  737 + this.element.stopObserving(pair.key, pair.value);
  738 + if (this.options.externalControl)
  739 + this.options.externalControl.stopObserving(pair.key, pair.value);
  740 + }.bind(this));
  741 + },
  742 + wrapUp: function(transport) {
  743 + this.leaveEditMode();
  744 + // Can't use triggerCallback due to backward compatibility: requires
  745 + // binding + direct element
  746 + this._boundComplete(transport, this.element);
  747 + }
  748 +});
  749 +
  750 +Object.extend(Ajax.InPlaceEditor.prototype, {
  751 + dispose: Ajax.InPlaceEditor.prototype.destroy
  752 +});
  753 +
  754 +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
  755 + initialize: function($super, element, url, options) {
  756 + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
  757 + $super(element, url, options);
  758 + },
  759 +
  760 + createEditField: function() {
  761 + var list = document.createElement('select');
  762 + list.name = this.options.paramName;
  763 + list.size = 1;
  764 + this._controls.editor = list;
  765 + this._collection = this.options.collection || [];
  766 + if (this.options.loadCollectionURL)
  767 + this.loadCollection();
  768 + else
  769 + this.checkForExternalText();
  770 + this._form.appendChild(this._controls.editor);
  771 + },
  772 +
  773 + loadCollection: function() {
  774 + this._form.addClassName(this.options.loadingClassName);
  775 + this.showLoadingText(this.options.loadingCollectionText);
  776 + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  777 + Object.extend(options, {
  778 + parameters: 'editorId=' + encodeURIComponent(this.element.id),
  779 + onComplete: Prototype.emptyFunction,
  780 + onSuccess: function(transport) {
  781 + var js = transport.responseText.strip();
  782 + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
  783 + throw('Server returned an invalid collection representation.');
  784 + this._collection = eval(js);
  785 + this.checkForExternalText();
  786 + }.bind(this),
  787 + onFailure: this.onFailure
  788 + });
  789 + new Ajax.Request(this.options.loadCollectionURL, options);
  790 + },
  791 +
  792 + showLoadingText: function(text) {
  793 + this._controls.editor.disabled = true;
  794 + var tempOption = this._controls.editor.firstChild;
  795 + if (!tempOption) {
  796 + tempOption = document.createElement('option');
  797 + tempOption.value = '';
  798 + this._controls.editor.appendChild(tempOption);
  799 + tempOption.selected = true;
  800 + }
  801 + tempOption.update((text || '').stripScripts().stripTags());
  802 + },
  803 +
  804 + checkForExternalText: function() {
  805 + this._text = this.getText();
  806 + if (this.options.loadTextURL)
  807 + this.loadExternalText();
  808 + else
  809 + this.buildOptionList();
  810 + },
  811 +
  812 + loadExternalText: function() {
  813 + this.showLoadingText(this.options.loadingText);
  814 + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  815 + Object.extend(options, {
  816 + parameters: 'editorId=' + encodeURIComponent(this.element.id),
  817 + onComplete: Prototype.emptyFunction,
  818 + onSuccess: function(transport) {
  819 + this._text = transport.responseText.strip();
  820 + this.buildOptionList();
  821 + }.bind(this),
  822 + onFailure: this.onFailure
  823 + });
  824 + new Ajax.Request(this.options.loadTextURL, options);
  825 + },
  826 +
  827 + buildOptionList: function() {
  828 + this._form.removeClassName(this.options.loadingClassName);
  829 + this._collection = this._collection.map(function(entry) {
  830 + return 2 === entry.length ? entry : [entry, entry].flatten();
  831 + });
  832 + var marker = ('value' in this.options) ? this.options.value : this._text;
  833 + var textFound = this._collection.any(function(entry) {
  834 + return entry[0] == marker;
  835 + }.bind(this));
  836 + this._controls.editor.update('');
  837 + var option;
  838 + this._collection.each(function(entry, index) {
  839 + option = document.createElement('option');
  840 + option.value = entry[0];
  841 + option.selected = textFound ? entry[0] == marker : 0 == index;
  842 + option.appendChild(document.createTextNode(entry[1]));
  843 + this._controls.editor.appendChild(option);
  844 + }.bind(this));
  845 + this._controls.editor.disabled = false;
  846 + Field.scrollFreeActivate(this._controls.editor);
  847 + }
  848 +});
  849 +
  850 +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
  851 +//**** This only exists for a while, in order to let ****
  852 +//**** users adapt to the new API. Read up on the new ****
  853 +//**** API and convert your code to it ASAP! ****
  854 +
  855 +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  856 + if (!options) return;
  857 + function fallback(name, expr) {
  858 + if (name in options || expr === undefined) return;
  859 + options[name] = expr;
  860 + };
  861 + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
  862 + options.cancelLink == options.cancelButton == false ? false : undefined)));
  863 + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
  864 + options.okLink == options.okButton == false ? false : undefined)));
  865 + fallback('highlightColor', options.highlightcolor);
  866 + fallback('highlightEndColor', options.highlightendcolor);
  867 +};
  868 +
  869 +Object.extend(Ajax.InPlaceEditor, {
  870 + DefaultOptions: {
  871 + ajaxOptions: { },
  872 + autoRows: 3, // Use when multi-line w/ rows == 1
  873 + cancelControl: 'link', // 'link'|'button'|false
  874 + cancelText: 'cancel',
  875 + clickToEditText: 'Click to edit',
  876 + externalControl: null, // id|elt
  877 + externalControlOnly: false,
  878 + fieldPostCreation: 'activate', // 'activate'|'focus'|false
  879 + formClassName: 'inplaceeditor-form',
  880 + formId: null, // id|elt
  881 + highlightColor: '#ffff99',
  882 + highlightEndColor: '#ffffff',
  883 + hoverClassName: '',
  884 + htmlResponse: true,
  885 + loadingClassName: 'inplaceeditor-loading',
  886 + loadingText: 'Loading...',
  887 + okControl: 'button', // 'link'|'button'|false
  888 + okText: 'ok',
  889 + paramName: 'value',
  890 + rows: 1, // If 1 and multi-line, uses autoRows
  891 + savingClassName: 'inplaceeditor-saving',
  892 + savingText: 'Saving...',
  893 + size: 0,
  894 + stripLoadedTextTags: false,
  895 + submitOnBlur: false,
  896 + textAfterControls: '',
  897 + textBeforeControls: '',
  898 + textBetweenControls: ''
  899 + },
  900 + DefaultCallbacks: {
  901 + callback: function(form) {
  902 + return Form.serialize(form);
  903 + },
  904 + onComplete: function(transport, element) {
  905 + // For backward compatibility, this one is bound to the IPE, and passes
  906 + // the element directly. It was too often customized, so we don't break it.
  907 + new Effect.Highlight(element, {
  908 + startcolor: this.options.highlightColor, keepBackgroundImage: true });
  909 + },
  910 + onEnterEditMode: null,
  911 + onEnterHover: function(ipe) {
  912 + ipe.element.style.backgroundColor = ipe.options.highlightColor;
  913 + if (ipe._effect)
  914 + ipe._effect.cancel();
  915 + },
  916 + onFailure: function(transport, ipe) {
  917 + alert('Error communication with the server: ' + transport.responseText.stripTags());
  918 + },
  919 + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
  920 + onLeaveEditMode: null,
  921 + onLeaveHover: function(ipe) {
  922 + ipe._effect = new Effect.Highlight(ipe.element, {
  923 + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
  924 + restorecolor: ipe._originalBackground, keepBackgroundImage: true
  925 + });
  926 + }
  927 + },
  928 + Listeners: {
  929 + click: 'enterEditMode',
  930 + keydown: 'checkForEscapeOrReturn',
  931 + mouseover: 'enterHover',
  932 + mouseout: 'leaveHover'
  933 + }
  934 +});
  935 +
  936 +Ajax.InPlaceCollectionEditor.DefaultOptions = {
  937 + loadingCollectionText: 'Loading options...'
  938 +};
  939 +
  940 +// Delayed observer, like Form.Element.Observer,
  941 +// but waits for delay after last key input
  942 +// Ideal for live-search fields
  943 +
  944 +Form.Element.DelayedObserver = Class.create({
  945 + initialize: function(element, delay, callback) {
  946 + this.delay = delay || 0.5;
  947 + this.element = $(element);
  948 + this.callback = callback;
  949 + this.timer = null;
  950 + this.lastValue = $F(this.element);
  951 + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  952 + },
  953 + delayedListener: function(event) {
  954 + if(this.lastValue == $F(this.element)) return;
  955 + if(this.timer) clearTimeout(this.timer);
  956 + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
  957 + this.lastValue = $F(this.element);
  958 + },
  959 + onTimerEvent: function() {
  960 + this.timer = null;
  961 + this.callback(this.element, $F(this.element));
  962 + }
  963 +});
  1 +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2 +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
  3 +//
  4 +// script.aculo.us is freely distributable under the terms of an MIT-style license.
  5 +// For details, see the script.aculo.us web site: http://script.aculo.us/
  6 +
  7 +if(Object.isUndefined(Effect))
  8 + throw("dragdrop.js requires including script.aculo.us' effects.js library");
  9 +
  10 +var Droppables = {
  11 + drops: [],
  12 +
  13 + remove: function(element) {
  14 + this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  15 + },
  16 +
  17 + add: function(element) {
  18 + element = $(element);
  19 + var options = Object.extend({
  20 + greedy: true,
  21 + hoverclass: null,
  22 + tree: false
  23 + }, arguments[1] || { });
  24 +
  25 + // cache containers
  26 + if(options.containment) {
  27 + options._containers = [];
  28 + var containment = options.containment;
  29 + if(Object.isArray(containment)) {
  30 + containment.each( function(c) { options._containers.push($(c)) });
  31 + } else {
  32 + options._containers.push($(containment));
  33 + }
  34 + }
  35 +
  36 + if(options.accept) options.accept = [options.accept].flatten();
  37 +
  38 + Element.makePositioned(element); // fix IE
  39 + options.element = element;
  40 +
  41 + this.drops.push(options);
  42 + },
  43 +
  44 + findDeepestChild: function(drops) {
  45 + deepest = drops[0];
  46 +
  47 + for (i = 1; i < drops.length; ++i)
  48 + if (Element.isParent(drops[i].element, deepest.element))
  49 + deepest = drops[i];
  50 +
  51 + return deepest;
  52 + },
  53 +
  54 + isContained: function(element, drop) {
  55 + var containmentNode;
  56 + if(drop.tree) {
  57 + containmentNode = element.treeNode;
  58 + } else {
  59 + containmentNode = element.parentNode;
  60 + }
  61 + return drop._containers.detect(function(c) { return containmentNode == c });
  62 + },
  63 +
  64 + isAffected: function(point, element, drop) {
  65 + return (
  66 + (drop.element!=element) &&
  67 + ((!drop._containers) ||
  68 + this.isContained(element, drop)) &&
  69 + ((!drop.accept) ||
  70 + (Element.classNames(element).detect(
  71 + function(v) { return drop.accept.include(v) } ) )) &&
  72 + Position.within(drop.element, point[0], point[1]) );
  73 + },
  74 +
  75 + deactivate: function(drop) {
  76 + if(drop.hoverclass)
  77 + Element.removeClassName(drop.element, drop.hoverclass);
  78 + this.last_active = null;
  79 + },
  80 +
  81 + activate: function(drop) {
  82 + if(drop.hoverclass)
  83 + Element.addClassName(drop.element, drop.hoverclass);
  84 + this.last_active = drop;
  85 + },
  86 +
  87 + show: function(point, element) {
  88 + if(!this.drops.length) return;
  89 + var drop, affected = [];
  90 +
  91 + this.drops.each( function(drop) {
  92 + if(Droppables.isAffected(point, element, drop))
  93 + affected.push(drop);
  94 + });
  95 +
  96 + if(affected.length>0)
  97 + drop = Droppables.findDeepestChild(affected);
  98 +
  99 + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
  100 + if (drop) {
  101 + Position.within(drop.element, point[0], point[1]);
  102 + if(drop.onHover)
  103 + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
  104 +
  105 + if (drop != this.last_active) Droppables.activate(drop);
  106 + }
  107 + },
  108 +
  109 + fire: function(event, element) {
  110 + if(!this.last_active) return;
  111 + Position.prepare();
  112 +
  113 + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
  114 + if (this.last_active.onDrop) {
  115 + this.last_active.onDrop(element, this.last_active.element, event);
  116 + return true;
  117 + }
  118 + },
  119 +
  120 + reset: function() {
  121 + if(this.last_active)
  122 + this.deactivate(this.last_active);
  123 + }
  124 +};
  125 +
  126 +var Draggables = {
  127 + drags: [],
  128 + observers: [],
  129 +
  130 + register: function(draggable) {
  131 + if(this.drags.length == 0) {
  132 + this.eventMouseUp = this.endDrag.bindAsEventListener(this);
  133 + this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
  134 + this.eventKeypress = this.keyPress.bindAsEventListener(this);
  135 +
  136 + Event.observe(document, "mouseup", this.eventMouseUp);
  137 + Event.observe(document, "mousemove", this.eventMouseMove);
  138 + Event.observe(document, "keypress", this.eventKeypress);
  139 + }
  140 + this.drags.push(draggable);
  141 + },
  142 +
  143 + unregister: function(draggable) {
  144 + this.drags = this.drags.reject(function(d) { return d==draggable });
  145 + if(this.drags.length == 0) {
  146 + Event.stopObserving(document, "mouseup", this.eventMouseUp);
  147 + Event.stopObserving(document, "mousemove", this.eventMouseMove);
  148 + Event.stopObserving(document, "keypress", this.eventKeypress);
  149 + }
  150 + },
  151 +
  152 + activate: function(draggable) {
  153 + if(draggable.options.delay) {
  154 + this._timeout = setTimeout(function() {
  155 + Draggables._timeout = null;
  156 + window.focus();
  157 + Draggables.activeDraggable = draggable;
  158 + }.bind(this), draggable.options.delay);
  159 + } else {
  160 + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
  161 + this.activeDraggable = draggable;
  162 + }
  163 + },
  164 +
  165 + deactivate: function() {
  166 + this.activeDraggable = null;
  167 + },
  168 +
  169 + updateDrag: function(event) {
  170 + if(!this.activeDraggable) return;
  171 + var pointer = [Event.pointerX(event), Event.pointerY(event)];
  172 + // Mozilla-based browsers fire successive mousemove events with
  173 + // the same coordinates, prevent needless redrawing (moz bug?)
  174 + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
  175 + this._lastPointer = pointer;
  176 +
  177 + this.activeDraggable.updateDrag(event, pointer);
  178 + },
  179 +
  180 + endDrag: function(event) {
  181 + if(this._timeout) {
  182 + clearTimeout(this._timeout);
  183 + this._timeout = null;
  184 + }
  185 + if(!this.activeDraggable) return;
  186 + this._lastPointer = null;
  187 + this.activeDraggable.endDrag(event);
  188 + this.activeDraggable = null;
  189 + },
  190 +
  191 + keyPress: function(event) {
  192 + if(this.activeDraggable)
  193 + this.activeDraggable.keyPress(event);
  194 + },
  195 +
  196 + addObserver: function(observer) {
  197 + this.observers.push(observer);
  198 + this._cacheObserverCallbacks();
  199 + },
  200 +
  201 + removeObserver: function(element) { // element instead of observer fixes mem leaks
  202 + this.observers = this.observers.reject( function(o) { return o.element==element });
  203 + this._cacheObserverCallbacks();
  204 + },
  205 +
  206 + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
  207 + if(this[eventName+'Count'] > 0)
  208 + this.observers.each( function(o) {
  209 + if(o[eventName]) o[eventName](eventName, draggable, event);
  210 + });
  211 + if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  212 + },
  213 +
  214 + _cacheObserverCallbacks: function() {
  215 + ['onStart','onEnd','onDrag'].each( function(eventName) {
  216 + Draggables[eventName+'Count'] = Draggables.observers.select(
  217 + function(o) { return o[eventName]; }
  218 + ).length;
  219 + });
  220 + }
  221 +};
  222 +
  223 +/*--------------------------------------------------------------------------*/
  224 +
  225 +var Draggable = Class.create({
  226 + initialize: function(element) {
  227 + var defaults = {
  228 + handle: false,
  229 + reverteffect: function(element, top_offset, left_offset) {
  230 + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
  231 + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
  232 + queue: {scope:'_draggable', position:'end'}
  233 + });
  234 + },
  235 + endeffect: function(element) {
  236 + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
  237 + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
  238 + queue: {scope:'_draggable', position:'end'},
  239 + afterFinish: function(){
  240 + Draggable._dragging[element] = false
  241 + }
  242 + });
  243 + },
  244 + zindex: 1000,
  245 + revert: false,
  246 + quiet: false,
  247 + scroll: false,
  248 + scrollSensitivity: 20,
  249 + scrollSpeed: 15,
  250 + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
  251 + delay: 0
  252 + };
  253 +
  254 + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
  255 + Object.extend(defaults, {
  256 + starteffect: function(element) {
  257 + element._opacity = Element.getOpacity(element);
  258 + Draggable._dragging[element] = true;
  259 + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
  260 + }
  261 + });
  262 +
  263 + var options = Object.extend(defaults, arguments[1] || { });
  264 +
  265 + this.element = $(element);
  266 +
  267 + if(options.handle && Object.isString(options.handle))
  268 + this.handle = this.element.down('.'+options.handle, 0);
  269 +
  270 + if(!this.handle) this.handle = $(options.handle);
  271 + if(!this.handle) this.handle = this.element;
  272 +
  273 + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
  274 + options.scroll = $(options.scroll);
  275 + this._isScrollChild = Element.childOf(this.element, options.scroll);
  276 + }
  277 +
  278 + Element.makePositioned(this.element); // fix IE
  279 +
  280 + this.options = options;
  281 + this.dragging = false;
  282 +
  283 + this.eventMouseDown = this.initDrag.bindAsEventListener(this);
  284 + Event.observe(this.handle, "mousedown", this.eventMouseDown);
  285 +
  286 + Draggables.register(this);
  287 + },
  288 +
  289 + destroy: function() {
  290 + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
  291 + Draggables.unregister(this);
  292 + },
  293 +
  294 + currentDelta: function() {
  295 + return([
  296 + parseInt(Element.getStyle(this.element,'left') || '0'),
  297 + parseInt(Element.getStyle(this.element,'top') || '0')]);
  298 + },
  299 +
  300 + initDrag: function(event) {
  301 + if(!Object.isUndefined(Draggable._dragging[this.element]) &&
  302 + Draggable._dragging[this.element]) return;
  303 + if(Event.isLeftClick(event)) {
  304 + // abort on form elements, fixes a Firefox issue
  305 + var src = Event.element(event);
  306 + if((tag_name = src.tagName.toUpperCase()) && (
  307 + tag_name=='INPUT' ||
  308 + tag_name=='SELECT' ||
  309 + tag_name=='OPTION' ||
  310 + tag_name=='BUTTON' ||
  311 + tag_name=='TEXTAREA')) return;
  312 +
  313 + var pointer = [Event.pointerX(event), Event.pointerY(event)];
  314 + var pos = Position.cumulativeOffset(this.element);
  315 + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
  316 +
  317 + Draggables.activate(this);
  318 + Event.stop(event);
  319 + }
  320 + },
  321 +
  322 + startDrag: function(event) {
  323 + this.dragging = true;
  324 + if(!this.delta)
  325 + this.delta = this.currentDelta();
  326 +
  327 + if(this.options.zindex) {
  328 + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
  329 + this.element.style.zIndex = this.options.zindex;
  330 + }
  331 +
  332 + if(this.options.ghosting) {
  333 + this._clone = this.element.cloneNode(true);
  334 + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
  335 + if (!this._originallyAbsolute)
  336 + Position.absolutize(this.element);
  337 + this.element.parentNode.insertBefore(this._clone, this.element);
  338 + }
  339 +
  340 + if(this.options.scroll) {
  341 + if (this.options.scroll == window) {
  342 + var where = this._getWindowScroll(this.options.scroll);
  343 + this.originalScrollLeft = where.left;
  344 + this.originalScrollTop = where.top;
  345 + } else {
  346 + this.originalScrollLeft = this.options.scroll.scrollLeft;
  347 + this.originalScrollTop = this.options.scroll.scrollTop;
  348 + }
  349 + }
  350 +
  351 + Draggables.notify('onStart', this, event);
  352 +
  353 + if(this.options.starteffect) this.options.starteffect(this.element);
  354 + },
  355 +
  356 + updateDrag: function(event, pointer) {
  357 + if(!this.dragging) this.startDrag(event);
  358 +
  359 + if(!this.options.quiet){
  360 + Position.prepare();
  361 + Droppables.show(pointer, this.element);
  362 + }
  363 +
  364 + Draggables.notify('onDrag', this, event);
  365 +
  366 + this.draw(pointer);
  367 + if(this.options.change) this.options.change(this);
  368 +
  369 + if(this.options.scroll) {
  370 + this.stopScrolling();
  371 +
  372 + var p;
  373 + if (this.options.scroll == window) {
  374 + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
  375 + } else {
  376 + p = Position.page(this.options.scroll);
  377 + p[0] += this.options.scroll.scrollLeft + Position.deltaX;
  378 + p[1] += this.options.scroll.scrollTop + Position.deltaY;
  379 + p.push(p[0]+this.options.scroll.offsetWidth);
  380 + p.push(p[1]+this.options.scroll.offsetHeight);
  381 + }
  382 + var speed = [0,0];
  383 + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
  384 + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
  385 + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
  386 + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
  387 + this.startScrolling(speed);
  388 + }
  389 +
  390 + // fix AppleWebKit rendering
  391 + if(Prototype.Browser.WebKit) window.scrollBy(0,0);
  392 +
  393 + Event.stop(event);
  394 + },
  395 +
  396 + finishDrag: function(event, success) {
  397 + this.dragging = false;
  398 +
  399 + if(this.options.quiet){
  400 + Position.prepare();
  401 + var pointer = [Event.pointerX(event), Event.pointerY(event)];
  402 + Droppables.show(pointer, this.element);
  403 + }
  404 +
  405 + if(this.options.ghosting) {
  406 + if (!this._originallyAbsolute)
  407 + Position.relativize(this.element);
  408 + delete this._originallyAbsolute;
  409 + Element.remove(this._clone);
  410 + this._clone = null;
  411 + }
  412 +
  413 + var dropped = false;
  414 + if(success) {
  415 + dropped = Droppables.fire(event, this.element);
  416 + if (!dropped) dropped = false;
  417 + }
  418 + if(dropped && this.options.onDropped) this.options.onDropped(this.element);
  419 + Draggables.notify('onEnd', this, event);
  420 +
  421 + var revert = this.options.revert;
  422 + if(revert && Object.isFunction(revert)) revert = revert(this.element);
  423 +
  424 + var d = this.currentDelta();
  425 + if(revert && this.options.reverteffect) {
  426 + if (dropped == 0 || revert != 'failure')
  427 + this.options.reverteffect(this.element,
  428 + d[1]-this.delta[1], d[0]-this.delta[0]);
  429 + } else {
  430 + this.delta = d;
  431 + }
  432 +
  433 + if(this.options.zindex)
  434 + this.element.style.zIndex = this.originalZ;
  435 +
  436 + if(this.options.endeffect)
  437 + this.options.endeffect(this.element);
  438 +
  439 + Draggables.deactivate(this);
  440 + Droppables.reset();
  441 + },
  442 +
  443 + keyPress: function(event) {
  444 + if(event.keyCode!=Event.KEY_ESC) return;
  445 + this.finishDrag(event, false);
  446 + Event.stop(event);
  447 + },
  448 +
  449 + endDrag: function(event) {
  450 + if(!this.dragging) return;
  451 + this.stopScrolling();
  452 + this.finishDrag(event, true);
  453 + Event.stop(event);
  454 + },
  455 +
  456 + draw: function(point) {
  457 + var pos = Position.cumulativeOffset(this.element);
  458 + if(this.options.ghosting) {
  459 + var r = Position.realOffset(this.element);
  460 + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
  461 + }
  462 +
  463 + var d = this.currentDelta();
  464 + pos[0] -= d[0]; pos[1] -= d[1];
  465 +
  466 + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
  467 + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
  468 + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
  469 + }
  470 +
  471 + var p = [0,1].map(function(i){
  472 + return (point[i]-pos[i]-this.offset[i])
  473 + }.bind(this));
  474 +
  475 + if(this.options.snap) {
  476 + if(Object.isFunction(this.options.snap)) {
  477 + p = this.options.snap(p[0],p[1],this);
  478 + } else {
  479 + if(Object.isArray(this.options.snap)) {
  480 + p = p.map( function(v, i) {
  481 + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
  482 + } else {
  483 + p = p.map( function(v) {
  484 + return (v/this.options.snap).round()*this.options.snap }.bind(this));
  485 + }
  486 + }}
  487 +
  488 + var style = this.element.style;
  489 + if((!this.options.constraint) || (this.options.constraint=='horizontal'))
  490 + style.left = p[0] + "px";
  491 + if((!this.options.constraint) || (this.options.constraint=='vertical'))
  492 + style.top = p[1] + "px";
  493 +
  494 + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  495 + },
  496 +
  497 + stopScrolling: function() {
  498 + if(this.scrollInterval) {
  499 + clearInterval(this.scrollInterval);
  500 + this.scrollInterval = null;
  501 + Draggables._lastScrollPointer = null;
  502 + }
  503 + },
  504 +
  505 + startScrolling: function(speed) {
  506 + if(!(speed[0] || speed[1])) return;
  507 + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
  508 + this.lastScrolled = new Date();
  509 + this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  510 + },
  511 +
  512 + scroll: function() {
  513 + var current = new Date();
  514 + var delta = current - this.lastScrolled;
  515 + this.lastScrolled = current;
  516 + if(this.options.scroll == window) {
  517 + with (this._getWindowScroll(this.options.scroll)) {
  518 + if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
  519 + var d = delta / 1000;
  520 + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
  521 + }
  522 + }
  523 + } else {
  524 + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
  525 + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
  526 + }
  527 +
  528 + Position.prepare();
  529 + Droppables.show(Draggables._lastPointer, this.element);
  530 + Draggables.notify('onDrag', this);
  531 + if (this._isScrollChild) {
  532 + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
  533 + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
  534 + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
  535 + if (Draggables._lastScrollPointer[0] < 0)
  536 + Draggables._lastScrollPointer[0] = 0;
  537 + if (Draggables._lastScrollPointer[1] < 0)
  538 + Draggables._lastScrollPointer[1] = 0;
  539 + this.draw(Draggables._lastScrollPointer);
  540 + }
  541 +
  542 + if(this.options.change) this.options.change(this);
  543 + },
  544 +
  545 + _getWindowScroll: function(w) {
  546 + var T, L, W, H;
  547 + with (w.document) {
  548 + if (w.document.documentElement && documentElement.scrollTop) {
  549 + T = documentElement.scrollTop;
  550 + L = documentElement.scrollLeft;
  551 + } else if (w.document.body) {
  552 + T = body.scrollTop;
  553 + L = body.scrollLeft;
  554 + }
  555 + if (w.innerWidth) {
  556 + W = w.innerWidth;
  557 + H = w.innerHeight;
  558 + } else if (w.document.documentElement && documentElement.clientWidth) {
  559 + W = documentElement.clientWidth;
  560 + H = documentElement.clientHeight;
  561 + } else {
  562 + W = body.offsetWidth;
  563 + H = body.offsetHeight;
  564 + }
  565 + }
  566 + return { top: T, left: L, width: W, height: H };
  567 + }
  568 +});
  569 +
  570 +Draggable._dragging = { };
  571 +
  572 +/*--------------------------------------------------------------------------*/
  573 +
  574 +var SortableObserver = Class.create({
  575 + initialize: function(element, observer) {
  576 + this.element = $(element);
  577 + this.observer = observer;
  578 + this.lastValue = Sortable.serialize(this.element);
  579 + },
  580 +
  581 + onStart: function() {
  582 + this.lastValue = Sortable.serialize(this.element);
  583 + },
  584 +
  585 + onEnd: function() {
  586 + Sortable.unmark();
  587 + if(this.lastValue != Sortable.serialize(this.element))
  588 + this.observer(this.element)
  589 + }
  590 +});
  591 +
  592 +var Sortable = {
  593 + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  594 +
  595 + sortables: { },
  596 +
  597 + _findRootElement: function(element) {
  598 + while (element.tagName.toUpperCase() != "BODY") {
  599 + if(element.id && Sortable.sortables[element.id]) return element;
  600 + element = element.parentNode;
  601 + }
  602 + },
  603 +
  604 + options: function(element) {
  605 + element = Sortable._findRootElement($(element));
  606 + if(!element) return;
  607 + return Sortable.sortables[element.id];
  608 + },
  609 +
  610 + destroy: function(element){
  611 + element = $(element);
  612 + var s = Sortable.sortables[element.id];
  613 +
  614 + if(s) {
  615 + Draggables.removeObserver(s.element);
  616 + s.droppables.each(function(d){ Droppables.remove(d) });
  617 + s.draggables.invoke('destroy');
  618 +
  619 + delete Sortable.sortables[s.element.id];
  620 + }
  621 + },
  622 +
  623 + create: function(element) {
  624 + element = $(element);
  625 + var options = Object.extend({
  626 + element: element,
  627 + tag: 'li', // assumes li children, override with tag: 'tagname'
  628 + dropOnEmpty: false,
  629 + tree: false,
  630 + treeTag: 'ul',
  631 + overlap: 'vertical', // one of 'vertical', 'horizontal'
  632 + constraint: 'vertical', // one of 'vertical', 'horizontal', false
  633 + containment: element, // also takes array of elements (or id's); or false
  634 + handle: false, // or a CSS class
  635 + only: false,
  636 + delay: 0,
  637 + hoverclass: null,
  638 + ghosting: false,
  639 + quiet: false,
  640 + scroll: false,
  641 + scrollSensitivity: 20,
  642 + scrollSpeed: 15,
  643 + format: this.SERIALIZE_RULE,
  644 +
  645 + // these take arrays of elements or ids and can be
  646 + // used for better initialization performance
  647 + elements: false,
  648 + handles: false,
  649 +
  650 + onChange: Prototype.emptyFunction,
  651 + onUpdate: Prototype.emptyFunction
  652 + }, arguments[1] || { });
  653 +
  654 + // clear any old sortable with same element
  655 + this.destroy(element);
  656 +
  657 + // build options for the draggables
  658 + var options_for_draggable = {
  659 + revert: true,
  660 + quiet: options.quiet,
  661 + scroll: options.scroll,
  662 + scrollSpeed: options.scrollSpeed,
  663 + scrollSensitivity: options.scrollSensitivity,
  664 + delay: options.delay,
  665 + ghosting: options.ghosting,
  666 + constraint: options.constraint,
  667 + handle: options.handle };
  668 +
  669 + if(options.starteffect)
  670 + options_for_draggable.starteffect = options.starteffect;
  671 +
  672 + if(options.reverteffect)
  673 + options_for_draggable.reverteffect = options.reverteffect;
  674 + else
  675 + if(options.ghosting) options_for_draggable.reverteffect = function(element) {
  676 + element.style.top = 0;
  677 + element.style.left = 0;
  678 + };
  679 +
  680 + if(options.endeffect)
  681 + options_for_draggable.endeffect = options.endeffect;
  682 +
  683 + if(options.zindex)
  684 + options_for_draggable.zindex = options.zindex;
  685 +
  686 + // build options for the droppables
  687 + var options_for_droppable = {
  688 + overlap: options.overlap,
  689 + containment: options.containment,
  690 + tree: options.tree,
  691 + hoverclass: options.hoverclass,
  692 + onHover: Sortable.onHover
  693 + };
  694 +
  695 + var options_for_tree = {
  696 + onHover: Sortable.onEmptyHover,
  697 + overlap: options.overlap,
  698 + containment: options.containment,
  699 + hoverclass: options.hoverclass
  700 + };
  701 +
  702 + // fix for gecko engine
  703 + Element.cleanWhitespace(element);
  704 +
  705 + options.draggables = [];
  706 + options.droppables = [];
  707 +
  708 + // drop on empty handling
  709 + if(options.dropOnEmpty || options.tree) {
  710 + Droppables.add(element, options_for_tree);
  711 + options.droppables.push(element);
  712 + }
  713 +
  714 + (options.elements || this.findElements(element, options) || []).each( function(e,i) {
  715 + var handle = options.handles ? $(options.handles[i]) :
  716 + (options.handle ? $(e).select('.' + options.handle)[0] : e);
  717 + options.draggables.push(
  718 + new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
  719 + Droppables.add(e, options_for_droppable);
  720 + if(options.tree) e.treeNode = element;
  721 + options.droppables.push(e);
  722 + });
  723 +
  724 + if(options.tree) {
  725 + (Sortable.findTreeElements(element, options) || []).each( function(e) {
  726 + Droppables.add(e, options_for_tree);
  727 + e.treeNode = element;
  728 + options.droppables.push(e);
  729 + });
  730 + }
  731 +
  732 + // keep reference
  733 + this.sortables[element.id] = options;
  734 +
  735 + // for onupdate
  736 + Draggables.addObserver(new SortableObserver(element, options.onUpdate));
  737 +
  738 + },
  739 +
  740 + // return all suitable-for-sortable elements in a guaranteed order
  741 + findElements: function(element, options) {
  742 + return Element.findChildren(
  743 + element, options.only, options.tree ? true : false, options.tag);
  744 + },
  745 +
  746 + findTreeElements: function(element, options) {
  747 + return Element.findChildren(
  748 + element, options.only, options.tree ? true : false, options.treeTag);
  749 + },
  750 +
  751 + onHover: function(element, dropon, overlap) {
  752 + if(Element.isParent(dropon, element)) return;
  753 +
  754 + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
  755 + return;
  756 + } else if(overlap>0.5) {
  757 + Sortable.mark(dropon, 'before');
  758 + if(dropon.previousSibling != element) {
  759 + var oldParentNode = element.parentNode;
  760 + element.style.visibility = "hidden"; // fix gecko rendering
  761 + dropon.parentNode.insertBefore(element, dropon);
  762 + if(dropon.parentNode!=oldParentNode)
  763 + Sortable.options(oldParentNode).onChange(element);
  764 + Sortable.options(dropon.parentNode).onChange(element);
  765 + }
  766 + } else {
  767 + Sortable.mark(dropon, 'after');
  768 + var nextElement = dropon.nextSibling || null;
  769 + if(nextElement != element) {
  770 + var oldParentNode = element.parentNode;
  771 + element.style.visibility = "hidden"; // fix gecko rendering
  772 + dropon.parentNode.insertBefore(element, nextElement);
  773 + if(dropon.parentNode!=oldParentNode)
  774 + Sortable.options(oldParentNode).onChange(element);
  775 + Sortable.options(dropon.parentNode).onChange(element);
  776 + }
  777 + }
  778 + },
  779 +
  780 + onEmptyHover: function(element, dropon, overlap) {
  781 + var oldParentNode = element.parentNode;
  782 + var droponOptions = Sortable.options(dropon);
  783 +
  784 + if(!Element.isParent(dropon, element)) {
  785 + var index;
  786 +
  787 + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
  788 + var child = null;
  789 +
  790 + if(children) {
  791 + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
  792 +
  793 + for (index = 0; index < children.length; index += 1) {
  794 + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
  795 + offset -= Element.offsetSize (children[index], droponOptions.overlap);
  796 + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
  797 + child = index + 1 < children.length ? children[index + 1] : null;
  798 + break;
  799 + } else {
  800 + child = children[index];
  801 + break;
  802 + }
  803 + }
  804 + }
  805 +
  806 + dropon.insertBefore(element, child);
  807 +
  808 + Sortable.options(oldParentNode).onChange(element);
  809 + droponOptions.onChange(element);
  810 + }
  811 + },
  812 +
  813 + unmark: function() {
  814 + if(Sortable._marker) Sortable._marker.hide();
  815 + },
  816 +
  817 + mark: function(dropon, position) {
  818 + // mark on ghosting only
  819 + var sortable = Sortable.options(dropon.parentNode);
  820 + if(sortable && !sortable.ghosting) return;
  821 +
  822 + if(!Sortable._marker) {
  823 + Sortable._marker =
  824 + ($('dropmarker') || Element.extend(document.createElement('DIV'))).
  825 + hide().addClassName('dropmarker').setStyle({position:'absolute'});
  826 + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
  827 + }
  828 + var offsets = Position.cumulativeOffset(dropon);
  829 + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
  830 +
  831 + if(position=='after')
  832 + if(sortable.overlap == 'horizontal')
  833 + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
  834 + else
  835 + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
  836 +
  837 + Sortable._marker.show();
  838 + },
  839 +
  840 + _tree: function(element, options, parent) {
  841 + var children = Sortable.findElements(element, options) || [];
  842 +
  843 + for (var i = 0; i < children.length; ++i) {
  844 + var match = children[i].id.match(options.format);
  845 +
  846 + if (!match) continue;
  847 +
  848 + var child = {
  849 + id: encodeURIComponent(match ? match[1] : null),
  850 + element: element,
  851 + parent: parent,
  852 + children: [],
  853 + position: parent.children.length,
  854 + container: $(children[i]).down(options.treeTag)
  855 + };
  856 +
  857 + /* Get the element containing the children and recurse over it */
  858 + if (child.container)
  859 + this._tree(child.container, options, child);
  860 +
  861 + parent.children.push (child);
  862 + }
  863 +
  864 + return parent;
  865 + },
  866 +
  867 + tree: function(element) {
  868 + element = $(element);
  869 + var sortableOptions = this.options(element);
  870 + var options = Object.extend({
  871 + tag: sortableOptions.tag,
  872 + treeTag: sortableOptions.treeTag,
  873 + only: sortableOptions.only,
  874 + name: element.id,
  875 + format: sortableOptions.format
  876 + }, arguments[1] || { });
  877 +
  878 + var root = {
  879 + id: null,
  880 + parent: null,
  881 + children: [],
  882 + container: element,
  883 + position: 0
  884 + };
  885 +
  886 + return Sortable._tree(element, options, root);
  887 + },
  888 +
  889 + /* Construct a [i] index for a particular node */
  890 + _constructIndex: function(node) {
  891 + var index = '';
  892 + do {
  893 + if (node.id) index = '[' + node.position + ']' + index;
  894 + } while ((node = node.parent) != null);
  895 + return index;
  896 + },
  897 +
  898 + sequence: function(element) {
  899 + element = $(element);
  900 + var options = Object.extend(this.options(element), arguments[1] || { });
  901 +
  902 + return $(this.findElements(element, options) || []).map( function(item) {
  903 + return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
  904 + });
  905 + },
  906 +
  907 + setSequence: function(element, new_sequence) {
  908 + element = $(element);
  909 + var options = Object.extend(this.options(element), arguments[2] || { });
  910 +
  911 + var nodeMap = { };
  912 + this.findElements(element, options).each( function(n) {
  913 + if (n.id.match(options.format))
  914 + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
  915 + n.parentNode.removeChild(n);
  916 + });
  917 +
  918 + new_sequence.each(function(ident) {
  919 + var n = nodeMap[ident];
  920 + if (n) {
  921 + n[1].appendChild(n[0]);
  922 + delete nodeMap[ident];
  923 + }
  924 + });
  925 + },
  926 +
  927 + serialize: function(element) {
  928 + element = $(element);
  929 + var options = Object.extend(Sortable.options(element), arguments[1] || { });
  930 + var name = encodeURIComponent(
  931 + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
  932 +
  933 + if (options.tree) {
  934 + return Sortable.tree(element, arguments[1]).children.map( function (item) {
  935 + return [name + Sortable._constructIndex(item) + "[id]=" +
  936 + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
  937 + }).flatten().join('&');
  938 + } else {
  939 + return Sortable.sequence(element, arguments[1]).map( function(item) {
  940 + return name + "[]=" + encodeURIComponent(item);
  941 + }).join('&');
  942 + }
  943 + }
  944 +};
  945 +
  946 +// Returns true if child is contained within element
  947 +Element.isParent = function(child, element) {
  948 + if (!child.parentNode || child == element) return false;
  949 + if (child.parentNode == element) return true;
  950 + return Element.isParent(child.parentNode, element);
  951 +};
  952 +
  953 +Element.findChildren = function(element, only, recursive, tagName) {
  954 + if(!element.hasChildNodes()) return null;
  955 + tagName = tagName.toUpperCase();
  956 + if(only) only = [only].flatten();
  957 + var elements = [];
  958 + $A(element.childNodes).each( function(e) {
  959 + if(e.tagName && e.tagName.toUpperCase()==tagName &&
  960 + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
  961 + elements.push(e);
  962 + if(recursive) {
  963 + var grandchildren = Element.findChildren(e, only, recursive, tagName);
  964 + if(grandchildren) elements.push(grandchildren);
  965 + }
  966 + });
  967 +
  968 + return (elements.length>0 ? elements.flatten() : []);
  969 +};
  970 +
  971 +Element.offsetSize = function (element, type) {
  972 + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
  973 +};
  1 +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2 +// Contributors:
  3 +// Justin Palmer (http://encytemedia.com/)
  4 +// Mark Pilgrim (http://diveintomark.org/)
  5 +// Martin Bialasinki
  6 +//
  7 +// script.aculo.us is freely distributable under the terms of an MIT-style license.
  8 +// For details, see the script.aculo.us web site: http://script.aculo.us/
  9 +
  10 +// converts rgb() and #xxx to #xxxxxx format,
  11 +// returns self (or first argument) if not convertable
  12 +String.prototype.parseColor = function() {
  13 + var color = '#';
  14 + if (this.slice(0,4) == 'rgb(') {
  15 + var cols = this.slice(4,this.length-1).split(',');
  16 + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  17 + } else {
  18 + if (this.slice(0,1) == '#') {
  19 + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
  20 + if (this.length==7) color = this.toLowerCase();
  21 + }
  22 + }
  23 + return (color.length==7 ? color : (arguments[0] || this));
  24 +};
  25 +
  26 +/*--------------------------------------------------------------------------*/
  27 +
  28 +Element.collectTextNodes = function(element) {
  29 + return $A($(element).childNodes).collect( function(node) {
  30 + return (node.nodeType==3 ? node.nodeValue :
  31 + (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  32 + }).flatten().join('');
  33 +};
  34 +
  35 +Element.collectTextNodesIgnoreClass = function(element, className) {
  36 + return $A($(element).childNodes).collect( function(node) {
  37 + return (node.nodeType==3 ? node.nodeValue :
  38 + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
  39 + Element.collectTextNodesIgnoreClass(node, className) : ''));
  40 + }).flatten().join('');
  41 +};
  42 +
  43 +Element.setContentZoom = function(element, percent) {
  44 + element = $(element);
  45 + element.setStyle({fontSize: (percent/100) + 'em'});
  46 + if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  47 + return element;
  48 +};
  49 +
  50 +Element.getInlineOpacity = function(element){
  51 + return $(element).style.opacity || '';
  52 +};
  53 +
  54 +Element.forceRerendering = function(element) {
  55 + try {
  56 + element = $(element);
  57 + var n = document.createTextNode(' ');
  58 + element.appendChild(n);
  59 + element.removeChild(n);
  60 + } catch(e) { }
  61 +};
  62 +
  63 +/*--------------------------------------------------------------------------*/
  64 +
  65 +var Effect = {
  66 + _elementDoesNotExistError: {
  67 + name: 'ElementDoesNotExistError',
  68 + message: 'The specified DOM element does not exist, but is required for this effect to operate'
  69 + },
  70 + Transitions: {
  71 + linear: Prototype.K,
  72 + sinoidal: function(pos) {
  73 + return (-Math.cos(pos*Math.PI)/2) + .5;
  74 + },
  75 + reverse: function(pos) {
  76 + return 1-pos;
  77 + },
  78 + flicker: function(pos) {
  79 + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
  80 + return pos > 1 ? 1 : pos;
  81 + },
  82 + wobble: function(pos) {
  83 + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
  84 + },
  85 + pulse: function(pos, pulses) {
  86 + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
  87 + },
  88 + spring: function(pos) {
  89 + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
  90 + },
  91 + none: function(pos) {
  92 + return 0;
  93 + },
  94 + full: function(pos) {
  95 + return 1;
  96 + }
  97 + },
  98 + DefaultOptions: {
  99 + duration: 1.0, // seconds
  100 + fps: 100, // 100= assume 66fps max.
  101 + sync: false, // true for combining
  102 + from: 0.0,
  103 + to: 1.0,
  104 + delay: 0.0,
  105 + queue: 'parallel'
  106 + },
  107 + tagifyText: function(element) {
  108 + var tagifyStyle = 'position:relative';
  109 + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
  110 +
  111 + element = $(element);
  112 + $A(element.childNodes).each( function(child) {
  113 + if (child.nodeType==3) {
  114 + child.nodeValue.toArray().each( function(character) {
  115 + element.insertBefore(
  116 + new Element('span', {style: tagifyStyle}).update(
  117 + character == ' ' ? String.fromCharCode(160) : character),
  118 + child);
  119 + });
  120 + Element.remove(child);
  121 + }
  122 + });
  123 + },
  124 + multiple: function(element, effect) {
  125 + var elements;
  126 + if (((typeof element == 'object') ||
  127 + Object.isFunction(element)) &&
  128 + (element.length))
  129 + elements = element;
  130 + else
  131 + elements = $(element).childNodes;
  132 +
  133 + var options = Object.extend({
  134 + speed: 0.1,
  135 + delay: 0.0
  136 + }, arguments[2] || { });
  137 + var masterDelay = options.delay;
  138 +
  139 + $A(elements).each( function(element, index) {
  140 + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
  141 + });
  142 + },
  143 + PAIRS: {
  144 + 'slide': ['SlideDown','SlideUp'],
  145 + 'blind': ['BlindDown','BlindUp'],
  146 + 'appear': ['Appear','Fade']
  147 + },
  148 + toggle: function(element, effect) {
  149 + element = $(element);
  150 + effect = (effect || 'appear').toLowerCase();
  151 + var options = Object.extend({
  152 + queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
  153 + }, arguments[2] || { });
  154 + Effect[element.visible() ?
  155 + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  156 + }
  157 +};
  158 +
  159 +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
  160 +
  161 +/* ------------- core effects ------------- */
  162 +
  163 +Effect.ScopedQueue = Class.create(Enumerable, {
  164 + initialize: function() {
  165 + this.effects = [];
  166 + this.interval = null;
  167 + },
  168 + _each: function(iterator) {
  169 + this.effects._each(iterator);
  170 + },
  171 + add: function(effect) {
  172 + var timestamp = new Date().getTime();
  173 +
  174 + var position = Object.isString(effect.options.queue) ?
  175 + effect.options.queue : effect.options.queue.position;
  176 +
  177 + switch(position) {
  178 + case 'front':
  179 + // move unstarted effects after this effect
  180 + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
  181 + e.startOn += effect.finishOn;
  182 + e.finishOn += effect.finishOn;
  183 + });
  184 + break;
  185 + case 'with-last':
  186 + timestamp = this.effects.pluck('startOn').max() || timestamp;
  187 + break;
  188 + case 'end':
  189 + // start effect after last queued effect has finished
  190 + timestamp = this.effects.pluck('finishOn').max() || timestamp;
  191 + break;
  192 + }
  193 +
  194 + effect.startOn += timestamp;
  195 + effect.finishOn += timestamp;
  196 +
  197 + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
  198 + this.effects.push(effect);
  199 +
  200 + if (!this.interval)
  201 + this.interval = setInterval(this.loop.bind(this), 15);
  202 + },
  203 + remove: function(effect) {
  204 + this.effects = this.effects.reject(function(e) { return e==effect });
  205 + if (this.effects.length == 0) {
  206 + clearInterval(this.interval);
  207 + this.interval = null;
  208 + }
  209 + },
  210 + loop: function() {
  211 + var timePos = new Date().getTime();
  212 + for(var i=0, len=this.effects.length;i<len;i++)
  213 + this.effects[i] && this.effects[i].loop(timePos);
  214 + }
  215 +});
  216 +
  217 +Effect.Queues = {
  218 + instances: $H(),
  219 + get: function(queueName) {
  220 + if (!Object.isString(queueName)) return queueName;
  221 +
  222 + return this.instances.get(queueName) ||
  223 + this.instances.set(queueName, new Effect.ScopedQueue());
  224 + }
  225 +};
  226 +Effect.Queue = Effect.Queues.get('global');
  227 +
  228 +Effect.Base = Class.create({
  229 + position: null,
  230 + start: function(options) {
  231 + function codeForEvent(options,eventName){
  232 + return (
  233 + (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
  234 + (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
  235 + );
  236 + }
  237 + if (options && options.transition === false) options.transition = Effect.Transitions.linear;
  238 + this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
  239 + this.currentFrame = 0;
  240 + this.state = 'idle';
  241 + this.startOn = this.options.delay*1000;
  242 + this.finishOn = this.startOn+(this.options.duration*1000);
  243 + this.fromToDelta = this.options.to-this.options.from;
  244 + this.totalTime = this.finishOn-this.startOn;
  245 + this.totalFrames = this.options.fps*this.options.duration;
  246 +
  247 + this.render = (function() {
  248 + function dispatch(effect, eventName) {
  249 + if (effect.options[eventName + 'Internal'])
  250 + effect.options[eventName + 'Internal'](effect);
  251 + if (effect.options[eventName])
  252 + effect.options[eventName](effect);
  253 + }
  254 +
  255 + return function(pos) {
  256 + if (this.state === "idle") {
  257 + this.state = "running";
  258 + dispatch(this, 'beforeSetup');
  259 + if (this.setup) this.setup();
  260 + dispatch(this, 'afterSetup');
  261 + }
  262 + if (this.state === "running") {
  263 + pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
  264 + this.position = pos;
  265 + dispatch(this, 'beforeUpdate');
  266 + if (this.update) this.update(pos);
  267 + dispatch(this, 'afterUpdate');
  268 + }
  269 + };
  270 + })();
  271 +
  272 + this.event('beforeStart');
  273 + if (!this.options.sync)
  274 + Effect.Queues.get(Object.isString(this.options.queue) ?
  275 + 'global' : this.options.queue.scope).add(this);
  276 + },
  277 + loop: function(timePos) {
  278 + if (timePos >= this.startOn) {
  279 + if (timePos >= this.finishOn) {
  280 + this.render(1.0);
  281 + this.cancel();
  282 + this.event('beforeFinish');
  283 + if (this.finish) this.finish();
  284 + this.event('afterFinish');
  285 + return;
  286 + }
  287 + var pos = (timePos - this.startOn) / this.totalTime,
  288 + frame = (pos * this.totalFrames).round();
  289 + if (frame > this.currentFrame) {
  290 + this.render(pos);
  291 + this.currentFrame = frame;
  292 + }
  293 + }
  294 + },
  295 + cancel: function() {
  296 + if (!this.options.sync)
  297 + Effect.Queues.get(Object.isString(this.options.queue) ?
  298 + 'global' : this.options.queue.scope).remove(this);
  299 + this.state = 'finished';
  300 + },
  301 + event: function(eventName) {
  302 + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
  303 + if (this.options[eventName]) this.options[eventName](this);
  304 + },
  305 + inspect: function() {
  306 + var data = $H();
  307 + for(property in this)
  308 + if (!Object.isFunction(this[property])) data.set(property, this[property]);
  309 + return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  310 + }
  311 +});
  312 +
  313 +Effect.Parallel = Class.create(Effect.Base, {
  314 + initialize: function(effects) {
  315 + this.effects = effects || [];
  316 + this.start(arguments[1]);
  317 + },
  318 + update: function(position) {
  319 + this.effects.invoke('render', position);
  320 + },
  321 + finish: function(position) {
  322 + this.effects.each( function(effect) {
  323 + effect.render(1.0);
  324 + effect.cancel();
  325 + effect.event('beforeFinish');
  326 + if (effect.finish) effect.finish(position);
  327 + effect.event('afterFinish');
  328 + });
  329 + }
  330 +});
  331 +
  332 +Effect.Tween = Class.create(Effect.Base, {
  333 + initialize: function(object, from, to) {
  334 + object = Object.isString(object) ? $(object) : object;
  335 + var args = $A(arguments), method = args.last(),
  336 + options = args.length == 5 ? args[3] : null;
  337 + this.method = Object.isFunction(method) ? method.bind(object) :
  338 + Object.isFunction(object[method]) ? object[method].bind(object) :
  339 + function(value) { object[method] = value };
  340 + this.start(Object.extend({ from: from, to: to }, options || { }));
  341 + },
  342 + update: function(position) {
  343 + this.method(position);
  344 + }
  345 +});
  346 +
  347 +Effect.Event = Class.create(Effect.Base, {
  348 + initialize: function() {
  349 + this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  350 + },
  351 + update: Prototype.emptyFunction
  352 +});
  353 +
  354 +Effect.Opacity = Class.create(Effect.Base, {
  355 + initialize: function(element) {
  356 + this.element = $(element);
  357 + if (!this.element) throw(Effect._elementDoesNotExistError);
  358 + // make this work on IE on elements without 'layout'
  359 + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  360 + this.element.setStyle({zoom: 1});
  361 + var options = Object.extend({
  362 + from: this.element.getOpacity() || 0.0,
  363 + to: 1.0
  364 + }, arguments[1] || { });
  365 + this.start(options);
  366 + },
  367 + update: function(position) {
  368 + this.element.setOpacity(position);
  369 + }
  370 +});
  371 +
  372 +Effect.Move = Class.create(Effect.Base, {
  373 + initialize: function(element) {
  374 + this.element = $(element);
  375 + if (!this.element) throw(Effect._elementDoesNotExistError);
  376 + var options = Object.extend({
  377 + x: 0,
  378 + y: 0,
  379 + mode: 'relative'
  380 + }, arguments[1] || { });
  381 + this.start(options);
  382 + },
  383 + setup: function() {
  384 + this.element.makePositioned();
  385 + this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
  386 + this.originalTop = parseFloat(this.element.getStyle('top') || '0');
  387 + if (this.options.mode == 'absolute') {
  388 + this.options.x = this.options.x - this.originalLeft;
  389 + this.options.y = this.options.y - this.originalTop;
  390 + }
  391 + },
  392 + update: function(position) {
  393 + this.element.setStyle({
  394 + left: (this.options.x * position + this.originalLeft).round() + 'px',
  395 + top: (this.options.y * position + this.originalTop).round() + 'px'
  396 + });
  397 + }
  398 +});
  399 +
  400 +// for backwards compatibility
  401 +Effect.MoveBy = function(element, toTop, toLeft) {
  402 + return new Effect.Move(element,
  403 + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
  404 +};
  405 +
  406 +Effect.Scale = Class.create(Effect.Base, {
  407 + initialize: function(element, percent) {
  408 + this.element = $(element);
  409 + if (!this.element) throw(Effect._elementDoesNotExistError);
  410 + var options = Object.extend({
  411 + scaleX: true,
  412 + scaleY: true,
  413 + scaleContent: true,
  414 + scaleFromCenter: false,
  415 + scaleMode: 'box', // 'box' or 'contents' or { } with provided values
  416 + scaleFrom: 100.0,
  417 + scaleTo: percent
  418 + }, arguments[2] || { });
  419 + this.start(options);
  420 + },
  421 + setup: function() {
  422 + this.restoreAfterFinish = this.options.restoreAfterFinish || false;
  423 + this.elementPositioning = this.element.getStyle('position');
  424 +
  425 + this.originalStyle = { };
  426 + ['top','left','width','height','fontSize'].each( function(k) {
  427 + this.originalStyle[k] = this.element.style[k];
  428 + }.bind(this));
  429 +
  430 + this.originalTop = this.element.offsetTop;
  431 + this.originalLeft = this.element.offsetLeft;
  432 +
  433 + var fontSize = this.element.getStyle('font-size') || '100%';
  434 + ['em','px','%','pt'].each( function(fontSizeType) {
  435 + if (fontSize.indexOf(fontSizeType)>0) {
  436 + this.fontSize = parseFloat(fontSize);
  437 + this.fontSizeType = fontSizeType;
  438 + }
  439 + }.bind(this));
  440 +
  441 + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
  442 +
  443 + this.dims = null;
  444 + if (this.options.scaleMode=='box')
  445 + this.dims = [this.element.offsetHeight, this.element.offsetWidth];
  446 + if (/^content/.test(this.options.scaleMode))
  447 + this.dims = [this.element.scrollHeight, this.element.scrollWidth];
  448 + if (!this.dims)
  449 + this.dims = [this.options.scaleMode.originalHeight,
  450 + this.options.scaleMode.originalWidth];
  451 + },
  452 + update: function(position) {
  453 + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
  454 + if (this.options.scaleContent && this.fontSize)
  455 + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
  456 + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  457 + },
  458 + finish: function(position) {
  459 + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  460 + },
  461 + setDimensions: function(height, width) {
  462 + var d = { };
  463 + if (this.options.scaleX) d.width = width.round() + 'px';
  464 + if (this.options.scaleY) d.height = height.round() + 'px';
  465 + if (this.options.scaleFromCenter) {
  466 + var topd = (height - this.dims[0])/2;
  467 + var leftd = (width - this.dims[1])/2;
  468 + if (this.elementPositioning == 'absolute') {
  469 + if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
  470 + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
  471 + } else {
  472 + if (this.options.scaleY) d.top = -topd + 'px';
  473 + if (this.options.scaleX) d.left = -leftd + 'px';
  474 + }
  475 + }
  476 + this.element.setStyle(d);
  477 + }
  478 +});
  479 +
  480 +Effect.Highlight = Class.create(Effect.Base, {
  481 + initialize: function(element) {
  482 + this.element = $(element);
  483 + if (!this.element) throw(Effect._elementDoesNotExistError);
  484 + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
  485 + this.start(options);
  486 + },
  487 + setup: function() {
  488 + // Prevent executing on elements not in the layout flow
  489 + if (this.element.getStyle('display')=='none') { this.cancel(); return; }
  490 + // Disable background image during the effect
  491 + this.oldStyle = { };
  492 + if (!this.options.keepBackgroundImage) {
  493 + this.oldStyle.backgroundImage = this.element.getStyle('background-image');
  494 + this.element.setStyle({backgroundImage: 'none'});
  495 + }
  496 + if (!this.options.endcolor)
  497 + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
  498 + if (!this.options.restorecolor)
  499 + this.options.restorecolor = this.element.getStyle('background-color');
  500 + // init color calculations
  501 + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
  502 + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  503 + },
  504 + update: function(position) {
  505 + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
  506 + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  507 + },
  508 + finish: function() {
  509 + this.element.setStyle(Object.extend(this.oldStyle, {
  510 + backgroundColor: this.options.restorecolor
  511 + }));
  512 + }
  513 +});
  514 +
  515 +Effect.ScrollTo = function(element) {
  516 + var options = arguments[1] || { },
  517 + scrollOffsets = document.viewport.getScrollOffsets(),
  518 + elementOffsets = $(element).cumulativeOffset();
  519 +
  520 + if (options.offset) elementOffsets[1] += options.offset;
  521 +
  522 + return new Effect.Tween(null,
  523 + scrollOffsets.top,
  524 + elementOffsets[1],
  525 + options,
  526 + function(p){ scrollTo(scrollOffsets.left, p.round()); }
  527 + );
  528 +};
  529 +
  530 +/* ------------- combination effects ------------- */
  531 +
  532 +Effect.Fade = function(element) {
  533 + element = $(element);
  534 + var oldOpacity = element.getInlineOpacity();
  535 + var options = Object.extend({
  536 + from: element.getOpacity() || 1.0,
  537 + to: 0.0,
  538 + afterFinishInternal: function(effect) {
  539 + if (effect.options.to!=0) return;
  540 + effect.element.hide().setStyle({opacity: oldOpacity});
  541 + }
  542 + }, arguments[1] || { });
  543 + return new Effect.Opacity(element,options);
  544 +};
  545 +
  546 +Effect.Appear = function(element) {
  547 + element = $(element);
  548 + var options = Object.extend({
  549 + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  550 + to: 1.0,
  551 + // force Safari to render floated elements properly
  552 + afterFinishInternal: function(effect) {
  553 + effect.element.forceRerendering();
  554 + },
  555 + beforeSetup: function(effect) {
  556 + effect.element.setOpacity(effect.options.from).show();
  557 + }}, arguments[1] || { });
  558 + return new Effect.Opacity(element,options);
  559 +};
  560 +
  561 +Effect.Puff = function(element) {
  562 + element = $(element);
  563 + var oldStyle = {
  564 + opacity: element.getInlineOpacity(),
  565 + position: element.getStyle('position'),
  566 + top: element.style.top,
  567 + left: element.style.left,
  568 + width: element.style.width,
  569 + height: element.style.height
  570 + };
  571 + return new Effect.Parallel(
  572 + [ new Effect.Scale(element, 200,
  573 + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
  574 + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
  575 + Object.extend({ duration: 1.0,
  576 + beforeSetupInternal: function(effect) {
  577 + Position.absolutize(effect.effects[0].element);
  578 + },
  579 + afterFinishInternal: function(effect) {
  580 + effect.effects[0].element.hide().setStyle(oldStyle); }
  581 + }, arguments[1] || { })
  582 + );
  583 +};
  584 +
  585 +Effect.BlindUp = function(element) {
  586 + element = $(element);
  587 + element.makeClipping();
  588 + return new Effect.Scale(element, 0,
  589 + Object.extend({ scaleContent: false,
  590 + scaleX: false,
  591 + restoreAfterFinish: true,
  592 + afterFinishInternal: function(effect) {
  593 + effect.element.hide().undoClipping();
  594 + }
  595 + }, arguments[1] || { })
  596 + );
  597 +};
  598 +
  599 +Effect.BlindDown = function(element) {
  600 + element = $(element);
  601 + var elementDimensions = element.getDimensions();
  602 + return new Effect.Scale(element, 100, Object.extend({
  603 + scaleContent: false,
  604 + scaleX: false,
  605 + scaleFrom: 0,
  606 + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  607 + restoreAfterFinish: true,
  608 + afterSetup: function(effect) {
  609 + effect.element.makeClipping().setStyle({height: '0px'}).show();
  610 + },
  611 + afterFinishInternal: function(effect) {
  612 + effect.element.undoClipping();
  613 + }
  614 + }, arguments[1] || { }));
  615 +};
  616 +
  617 +Effect.SwitchOff = function(element) {
  618 + element = $(element);
  619 + var oldOpacity = element.getInlineOpacity();
  620 + return new Effect.Appear(element, Object.extend({
  621 + duration: 0.4,
  622 + from: 0,
  623 + transition: Effect.Transitions.flicker,
  624 + afterFinishInternal: function(effect) {
  625 + new Effect.Scale(effect.element, 1, {
  626 + duration: 0.3, scaleFromCenter: true,
  627 + scaleX: false, scaleContent: false, restoreAfterFinish: true,
  628 + beforeSetup: function(effect) {
  629 + effect.element.makePositioned().makeClipping();
  630 + },
  631 + afterFinishInternal: function(effect) {
  632 + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
  633 + }
  634 + });
  635 + }
  636 + }, arguments[1] || { }));
  637 +};
  638 +
  639 +Effect.DropOut = function(element) {
  640 + element = $(element);
  641 + var oldStyle = {
  642 + top: element.getStyle('top'),
  643 + left: element.getStyle('left'),
  644 + opacity: element.getInlineOpacity() };
  645 + return new Effect.Parallel(
  646 + [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
  647 + new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
  648 + Object.extend(
  649 + { duration: 0.5,
  650 + beforeSetup: function(effect) {
  651 + effect.effects[0].element.makePositioned();
  652 + },
  653 + afterFinishInternal: function(effect) {
  654 + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
  655 + }
  656 + }, arguments[1] || { }));
  657 +};
  658 +
  659 +Effect.Shake = function(element) {
  660 + element = $(element);
  661 + var options = Object.extend({
  662 + distance: 20,
  663 + duration: 0.5
  664 + }, arguments[1] || {});
  665 + var distance = parseFloat(options.distance);
  666 + var split = parseFloat(options.duration) / 10.0;
  667 + var oldStyle = {
  668 + top: element.getStyle('top'),
  669 + left: element.getStyle('left') };
  670 + return new Effect.Move(element,
  671 + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  672 + new Effect.Move(effect.element,
  673 + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  674 + new Effect.Move(effect.element,
  675 + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  676 + new Effect.Move(effect.element,
  677 + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  678 + new Effect.Move(effect.element,
  679 + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  680 + new Effect.Move(effect.element,
  681 + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  682 + effect.element.undoPositioned().setStyle(oldStyle);
  683 + }}); }}); }}); }}); }}); }});
  684 +};
  685 +
  686 +Effect.SlideDown = function(element) {
  687 + element = $(element).cleanWhitespace();
  688 + // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  689 + var oldInnerBottom = element.down().getStyle('bottom');
  690 + var elementDimensions = element.getDimensions();
  691 + return new Effect.Scale(element, 100, Object.extend({
  692 + scaleContent: false,
  693 + scaleX: false,
  694 + scaleFrom: window.opera ? 0 : 1,
  695 + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  696 + restoreAfterFinish: true,
  697 + afterSetup: function(effect) {
  698 + effect.element.makePositioned();
  699 + effect.element.down().makePositioned();
  700 + if (window.opera) effect.element.setStyle({top: ''});
  701 + effect.element.makeClipping().setStyle({height: '0px'}).show();
  702 + },
  703 + afterUpdateInternal: function(effect) {
  704 + effect.element.down().setStyle({bottom:
  705 + (effect.dims[0] - effect.element.clientHeight) + 'px' });
  706 + },
  707 + afterFinishInternal: function(effect) {
  708 + effect.element.undoClipping().undoPositioned();
  709 + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
  710 + }, arguments[1] || { })
  711 + );
  712 +};
  713 +
  714 +Effect.SlideUp = function(element) {
  715 + element = $(element).cleanWhitespace();
  716 + var oldInnerBottom = element.down().getStyle('bottom');
  717 + var elementDimensions = element.getDimensions();
  718 + return new Effect.Scale(element, window.opera ? 0 : 1,
  719 + Object.extend({ scaleContent: false,
  720 + scaleX: false,
  721 + scaleMode: 'box',
  722 + scaleFrom: 100,
  723 + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  724 + restoreAfterFinish: true,
  725 + afterSetup: function(effect) {
  726 + effect.element.makePositioned();
  727 + effect.element.down().makePositioned();
  728 + if (window.opera) effect.element.setStyle({top: ''});
  729 + effect.element.makeClipping().show();
  730 + },
  731 + afterUpdateInternal: function(effect) {
  732 + effect.element.down().setStyle({bottom:
  733 + (effect.dims[0] - effect.element.clientHeight) + 'px' });
  734 + },
  735 + afterFinishInternal: function(effect) {
  736 + effect.element.hide().undoClipping().undoPositioned();
  737 + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
  738 + }
  739 + }, arguments[1] || { })
  740 + );
  741 +};
  742 +
  743 +// Bug in opera makes the TD containing this element expand for a instance after finish
  744 +Effect.Squish = function(element) {
  745 + return new Effect.Scale(element, window.opera ? 1 : 0, {
  746 + restoreAfterFinish: true,
  747 + beforeSetup: function(effect) {
  748 + effect.element.makeClipping();
  749 + },
  750 + afterFinishInternal: function(effect) {
  751 + effect.element.hide().undoClipping();
  752 + }
  753 + });
  754 +};
  755 +
  756 +Effect.Grow = function(element) {
  757 + element = $(element);
  758 + var options = Object.extend({
  759 + direction: 'center',
  760 + moveTransition: Effect.Transitions.sinoidal,
  761 + scaleTransition: Effect.Transitions.sinoidal,
  762 + opacityTransition: Effect.Transitions.full
  763 + }, arguments[1] || { });
  764 + var oldStyle = {
  765 + top: element.style.top,
  766 + left: element.style.left,
  767 + height: element.style.height,
  768 + width: element.style.width,
  769 + opacity: element.getInlineOpacity() };
  770 +
  771 + var dims = element.getDimensions();
  772 + var initialMoveX, initialMoveY;
  773 + var moveX, moveY;
  774 +
  775 + switch (options.direction) {
  776 + case 'top-left':
  777 + initialMoveX = initialMoveY = moveX = moveY = 0;
  778 + break;
  779 + case 'top-right':
  780 + initialMoveX = dims.width;
  781 + initialMoveY = moveY = 0;
  782 + moveX = -dims.width;
  783 + break;
  784 + case 'bottom-left':
  785 + initialMoveX = moveX = 0;
  786 + initialMoveY = dims.height;
  787 + moveY = -dims.height;
  788 + break;
  789 + case 'bottom-right':
  790 + initialMoveX = dims.width;
  791 + initialMoveY = dims.height;
  792 + moveX = -dims.width;
  793 + moveY = -dims.height;
  794 + break;
  795 + case 'center':
  796 + initialMoveX = dims.width / 2;
  797 + initialMoveY = dims.height / 2;
  798 + moveX = -dims.width / 2;
  799 + moveY = -dims.height / 2;
  800 + break;
  801 + }
  802 +
  803 + return new Effect.Move(element, {
  804 + x: initialMoveX,
  805 + y: initialMoveY,
  806 + duration: 0.01,
  807 + beforeSetup: function(effect) {
  808 + effect.element.hide().makeClipping().makePositioned();
  809 + },
  810 + afterFinishInternal: function(effect) {
  811 + new Effect.Parallel(
  812 + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
  813 + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
  814 + new Effect.Scale(effect.element, 100, {
  815 + scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
  816 + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
  817 + ], Object.extend({
  818 + beforeSetup: function(effect) {
  819 + effect.effects[0].element.setStyle({height: '0px'}).show();
  820 + },
  821 + afterFinishInternal: function(effect) {
  822 + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
  823 + }
  824 + }, options)
  825 + );
  826 + }
  827 + });
  828 +};
  829 +
  830 +Effect.Shrink = function(element) {
  831 + element = $(element);
  832 + var options = Object.extend({
  833 + direction: 'center',
  834 + moveTransition: Effect.Transitions.sinoidal,
  835 + scaleTransition: Effect.Transitions.sinoidal,
  836 + opacityTransition: Effect.Transitions.none
  837 + }, arguments[1] || { });
  838 + var oldStyle = {
  839 + top: element.style.top,
  840 + left: element.style.left,
  841 + height: element.style.height,
  842 + width: element.style.width,
  843 + opacity: element.getInlineOpacity() };
  844 +
  845 + var dims = element.getDimensions();
  846 + var moveX, moveY;
  847 +
  848 + switch (options.direction) {
  849 + case 'top-left':
  850 + moveX = moveY = 0;
  851 + break;
  852 + case 'top-right':
  853 + moveX = dims.width;
  854 + moveY = 0;
  855 + break;
  856 + case 'bottom-left':
  857 + moveX = 0;
  858 + moveY = dims.height;
  859 + break;
  860 + case 'bottom-right':
  861 + moveX = dims.width;
  862 + moveY = dims.height;
  863 + break;
  864 + case 'center':
  865 + moveX = dims.width / 2;
  866 + moveY = dims.height / 2;
  867 + break;
  868 + }
  869 +
  870 + return new Effect.Parallel(
  871 + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
  872 + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
  873 + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
  874 + ], Object.extend({
  875 + beforeStartInternal: function(effect) {
  876 + effect.effects[0].element.makePositioned().makeClipping();
  877 + },
  878 + afterFinishInternal: function(effect) {
  879 + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
  880 + }, options)
  881 + );
  882 +};
  883 +
  884 +Effect.Pulsate = function(element) {
  885 + element = $(element);
  886 + var options = arguments[1] || { },
  887 + oldOpacity = element.getInlineOpacity(),
  888 + transition = options.transition || Effect.Transitions.linear,
  889 + reverser = function(pos){
  890 + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
  891 + };
  892 +
  893 + return new Effect.Opacity(element,
  894 + Object.extend(Object.extend({ duration: 2.0, from: 0,
  895 + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
  896 + }, options), {transition: reverser}));
  897 +};
  898 +
  899 +Effect.Fold = function(element) {
  900 + element = $(element);
  901 + var oldStyle = {
  902 + top: element.style.top,
  903 + left: element.style.left,
  904 + width: element.style.width,
  905 + height: element.style.height };
  906 + element.makeClipping();
  907 + return new Effect.Scale(element, 5, Object.extend({
  908 + scaleContent: false,
  909 + scaleX: false,
  910 + afterFinishInternal: function(effect) {
  911 + new Effect.Scale(element, 1, {
  912 + scaleContent: false,
  913 + scaleY: false,
  914 + afterFinishInternal: function(effect) {
  915 + effect.element.hide().undoClipping().setStyle(oldStyle);
  916 + } });
  917 + }}, arguments[1] || { }));
  918 +};
  919 +
  920 +Effect.Morph = Class.create(Effect.Base, {
  921 + initialize: function(element) {
  922 + this.element = $(element);
  923 + if (!this.element) throw(Effect._elementDoesNotExistError);
  924 + var options = Object.extend({
  925 + style: { }
  926 + }, arguments[1] || { });
  927 +
  928 + if (!Object.isString(options.style)) this.style = $H(options.style);
  929 + else {
  930 + if (options.style.include(':'))
  931 + this.style = options.style.parseStyle();
  932 + else {
  933 + this.element.addClassName(options.style);
  934 + this.style = $H(this.element.getStyles());
  935 + this.element.removeClassName(options.style);
  936 + var css = this.element.getStyles();
  937 + this.style = this.style.reject(function(style) {
  938 + return style.value == css[style.key];
  939 + });
  940 + options.afterFinishInternal = function(effect) {
  941 + effect.element.addClassName(effect.options.style);
  942 + effect.transforms.each(function(transform) {
  943 + effect.element.style[transform.style] = '';
  944 + });
  945 + };
  946 + }
  947 + }
  948 + this.start(options);
  949 + },
  950 +
  951 + setup: function(){
  952 + function parseColor(color){
  953 + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
  954 + color = color.parseColor();
  955 + return $R(0,2).map(function(i){
  956 + return parseInt( color.slice(i*2+1,i*2+3), 16 );
  957 + });
  958 + }
  959 + this.transforms = this.style.map(function(pair){
  960 + var property = pair[0], value = pair[1], unit = null;
  961 +
  962 + if (value.parseColor('#zzzzzz') != '#zzzzzz') {
  963 + value = value.parseColor();
  964 + unit = 'color';
  965 + } else if (property == 'opacity') {
  966 + value = parseFloat(value);
  967 + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  968 + this.element.setStyle({zoom: 1});
  969 + } else if (Element.CSS_LENGTH.test(value)) {
  970 + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
  971 + value = parseFloat(components[1]);
  972 + unit = (components.length == 3) ? components[2] : null;
  973 + }
  974 +
  975 + var originalValue = this.element.getStyle(property);
  976 + return {
  977 + style: property.camelize(),
  978 + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
  979 + targetValue: unit=='color' ? parseColor(value) : value,
  980 + unit: unit
  981 + };
  982 + }.bind(this)).reject(function(transform){
  983 + return (
  984 + (transform.originalValue == transform.targetValue) ||
  985 + (
  986 + transform.unit != 'color' &&
  987 + (isNaN(transform.originalValue) || isNaN(transform.targetValue))
  988 + )
  989 + );
  990 + });
  991 + },
  992 + update: function(position) {
  993 + var style = { }, transform, i = this.transforms.length;
  994 + while(i--)
  995 + style[(transform = this.transforms[i]).style] =
  996 + transform.unit=='color' ? '#'+
  997 + (Math.round(transform.originalValue[0]+
  998 + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
  999 + (Math.round(transform.originalValue[1]+
  1000 + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
  1001 + (Math.round(transform.originalValue[2]+
  1002 + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
  1003 + (transform.originalValue +
  1004 + (transform.targetValue - transform.originalValue) * position).toFixed(3) +
  1005 + (transform.unit === null ? '' : transform.unit);
  1006 + this.element.setStyle(style, true);
  1007 + }
  1008 +});
  1009 +
  1010 +Effect.Transform = Class.create({
  1011 + initialize: function(tracks){
  1012 + this.tracks = [];
  1013 + this.options = arguments[1] || { };
  1014 + this.addTracks(tracks);
  1015 + },
  1016 + addTracks: function(tracks){
  1017 + tracks.each(function(track){
  1018 + track = $H(track);
  1019 + var data = track.values().first();
  1020 + this.tracks.push($H({
  1021 + ids: track.keys().first(),
  1022 + effect: Effect.Morph,
  1023 + options: { style: data }
  1024 + }));
  1025 + }.bind(this));
  1026 + return this;
  1027 + },
  1028 + play: function(){
  1029 + return new Effect.Parallel(
  1030 + this.tracks.map(function(track){
  1031 + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
  1032 + var elements = [$(ids) || $$(ids)].flatten();
  1033 + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
  1034 + }).flatten(),
  1035 + this.options
  1036 + );
  1037 + }
  1038 +});
  1039 +
  1040 +Element.CSS_PROPERTIES = $w(
  1041 + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  1042 + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  1043 + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  1044 + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  1045 + 'fontSize fontWeight height left letterSpacing lineHeight ' +
  1046 + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  1047 + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  1048 + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  1049 + 'right textIndent top width wordSpacing zIndex');
  1050 +
  1051 +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
  1052 +
  1053 +String.__parseStyleElement = document.createElement('div');
  1054 +String.prototype.parseStyle = function(){
  1055 + var style, styleRules = $H();
  1056 + if (Prototype.Browser.WebKit)
  1057 + style = new Element('div',{style:this}).style;
  1058 + else {
  1059 + String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
  1060 + style = String.__parseStyleElement.childNodes[0].style;
  1061 + }
  1062 +
  1063 + Element.CSS_PROPERTIES.each(function(property){
  1064 + if (style[property]) styleRules.set(property, style[property]);
  1065 + });
  1066 +
  1067 + if (Prototype.Browser.IE && this.include('opacity'))
  1068 + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
  1069 +
  1070 + return styleRules;
  1071 +};
  1072 +
  1073 +if (document.defaultView && document.defaultView.getComputedStyle) {
  1074 + Element.getStyles = function(element) {
  1075 + var css = document.defaultView.getComputedStyle($(element), null);
  1076 + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
  1077 + styles[property] = css[property];
  1078 + return styles;
  1079 + });
  1080 + };
  1081 +} else {
  1082 + Element.getStyles = function(element) {
  1083 + element = $(element);
  1084 + var css = element.currentStyle, styles;
  1085 + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
  1086 + results[property] = css[property];
  1087 + return results;
  1088 + });
  1089 + if (!styles.opacity) styles.opacity = element.getOpacity();
  1090 + return styles;
  1091 + };
  1092 +}
  1093 +
  1094 +Effect.Methods = {
  1095 + morph: function(element, style) {
  1096 + element = $(element);
  1097 + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
  1098 + return element;
  1099 + },
  1100 + visualEffect: function(element, effect, options) {
  1101 + element = $(element);
  1102 + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
  1103 + new Effect[klass](element, options);
  1104 + return element;
  1105 + },
  1106 + highlight: function(element, options) {
  1107 + element = $(element);
  1108 + new Effect.Highlight(element, options);
  1109 + return element;
  1110 + }
  1111 +};
  1112 +
  1113 +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  1114 + 'pulsate shake puff squish switchOff dropOut').each(
  1115 + function(effect) {
  1116 + Effect.Methods[effect] = function(element, options){
  1117 + element = $(element);
  1118 + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
  1119 + return element;
  1120 + };
  1121 + }
  1122 +);
  1123 +
  1124 +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  1125 + function(f) { Effect.Methods[f] = Element[f]; }
  1126 +);
  1127 +
  1128 +Element.addMethods(Effect.Methods);
This diff could not be displayed because it is too large.
  1 +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
  2 +#
  3 +# To ban all spiders from the entire site uncomment the next two lines:
  4 +# User-Agent: *
  5 +# Disallow: /
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
  4 +require 'commands/about'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/console'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/dbconsole'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/destroy'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/generate'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../../config/boot', __FILE__)
  3 +require 'commands/performance/benchmarker'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../../config/boot', __FILE__)
  3 +require 'commands/performance/profiler'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/plugin'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/runner'
  1 +#!/usr/bin/env ruby
  2 +require File.expand_path('../../config/boot', __FILE__)
  3 +require 'commands/server'
  1 +require 'test_helper'
  2 +
  3 +class WelcomeControllerTest < ActionController::TestCase
  4 + # Replace this with your real tests.
  5 + test "the truth" do
  6 + assert true
  7 + end
  8 +end
  1 +require 'test_helper'
  2 +require 'performance_test_help'
  3 +
  4 +# Profiling results for each test method are written to tmp/performance.
  5 +class BrowsingTest < ActionController::PerformanceTest
  6 + def test_homepage
  7 + get '/'
  8 + end
  9 +end
  1 +ENV["RAILS_ENV"] = "test"
  2 +require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
  3 +require 'test_help'
  4 +
  5 +class ActiveSupport::TestCase
  6 + # Transactional fixtures accelerate your tests by wrapping each test method
  7 + # in a transaction that's rolled back on completion. This ensures that the
  8 + # test database remains unchanged so your fixtures don't have to be reloaded
  9 + # between every test method. Fewer database queries means faster tests.
  10 + #
  11 + # Read Mike Clark's excellent walkthrough at
  12 + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
  13 + #
  14 + # Every Active Record database supports transactions except MyISAM tables
  15 + # in MySQL. Turn off transactional fixtures in this case; however, if you
  16 + # don't care one way or the other, switching from MyISAM to InnoDB tables
  17 + # is recommended.
  18 + #
  19 + # The only drawback to using transactional fixtures is when you actually
  20 + # need to test transactions. Since your test is bracketed by a transaction,
  21 + # any transactions started in your code will be automatically rolled back.
  22 + self.use_transactional_fixtures = true
  23 +
  24 + # Instantiated fixtures are slow, but give you @david where otherwise you
  25 + # would need people(:david). If you don't want to migrate your existing
  26 + # test cases which use the @david style and don't mind the speed hit (each
  27 + # instantiated fixtures translates to a database query per test method),
  28 + # then set this back to true.
  29 + self.use_instantiated_fixtures = false
  30 +
  31 + # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
  32 + #
  33 + # Note: You'll currently still have to declare fixtures explicitly in integration tests
  34 + # -- they do not yet inherit this setting
  35 + fixtures :all
  36 +
  37 + # Add more helper methods to be used by all tests here...
  38 +end
  1 +require 'test_helper'
  2 +
  3 +class WelcomeHelperTest < ActionView::TestCase
  4 +end
Please register or login to post a comment