Commit d50eca43ad0bfc5ef3c36ca398876f5e166ade73

Authored by AntonioTerceiro
0 parents

initial import



git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1 3f533792-8f58-4932-b0fe-aaf55b0a4547
README 0 → 100644
  1 +++ a/README
... ... @@ -0,0 +1,183 @@
  1 +== Welcome to Rails
  2 +
  3 +Rails is a web-application and persistence framework that includes everything
  4 +needed to create database-backed web-applications according to the
  5 +Model-View-Control pattern of separation. This pattern splits the view (also
  6 +called the presentation) into "dumb" templates that are primarily responsible
  7 +for inserting pre-built data in between HTML tags. The model contains the
  8 +"smart" domain objects (such as Account, Product, Person, Post) that holds all
  9 +the business logic and knows how to persist themselves to a database. The
  10 +controller handles the incoming requests (such as Save New Account, Update
  11 +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. Start the web server: <tt>ruby script/server</tt> (run with --help for options)
  31 +2. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!"
  32 +3. Follow the guidelines to start developing your application
  33 +
  34 +
  35 +== Web servers
  36 +
  37 +Rails uses the built-in web server in Ruby called WEBrick by default, so you don't
  38 +have to install or configure anything to play around.
  39 +
  40 +If you have lighttpd installed, though, it'll be used instead when running script/server.
  41 +It's considerably faster than WEBrick and suited for production use, but requires additional
  42 +installation and currently only works well on OS X/Unix (Windows users are encouraged
  43 +to start with WEBrick). We recommend version 1.4.11 and higher. You can download it from
  44 +http://www.lighttpd.net.
  45 +
  46 +If you want something that's halfway between WEBrick and lighttpd, we heartily recommend
  47 +Mongrel. It's a Ruby-based web server with a C-component (so it requires compilation) that
  48 +also works very well with Windows. See more at http://mongrel.rubyforge.org/.
  49 +
  50 +But of course its also possible to run Rails with the premiere open source web server Apache.
  51 +To get decent performance, though, you'll need to install FastCGI. For Apache 1.3, you want
  52 +to use mod_fastcgi. For Apache 2.0+, you want to use mod_fcgid.
  53 +
  54 +See http://wiki.rubyonrails.com/rails/pages/FastCGI for more information on FastCGI.
  55 +
  56 +== Example for Apache conf
  57 +
  58 + <VirtualHost *:80>
  59 + ServerName rails
  60 + DocumentRoot /path/application/public/
  61 + ErrorLog /path/application/log/server.log
  62 +
  63 + <Directory /path/application/public/>
  64 + Options ExecCGI FollowSymLinks
  65 + AllowOverride all
  66 + Allow from all
  67 + Order allow,deny
  68 + </Directory>
  69 + </VirtualHost>
  70 +
  71 +NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI
  72 +should be on and ".cgi" should respond. All requests from 127.0.0.1 go
  73 +through CGI, so no Apache restart is necessary for changes. All other requests
  74 +go through FCGI (or mod_ruby), which requires a restart to show changes.
  75 +
  76 +
  77 +== Debugging Rails
  78 +
  79 +Have "tail -f" commands running on both the server.log, production.log, and
  80 +test.log files. Rails will automatically display debugging and runtime
  81 +information to these files. Debugging info will also be shown in the browser
  82 +on requests from 127.0.0.1.
  83 +
  84 +
  85 +== Breakpoints
  86 +
  87 +Breakpoint support is available through the script/breakpointer client. This
  88 +means that you can break out of execution at any point in the code, investigate
  89 +and change the model, AND then resume execution! Example:
  90 +
  91 + class WeblogController < ActionController::Base
  92 + def index
  93 + @posts = Post.find_all
  94 + breakpoint "Breaking out from the list"
  95 + end
  96 + end
  97 +
  98 +So the controller will accept the action, run the first line, then present you
  99 +with a IRB prompt in the breakpointer window. Here you can do things like:
  100 +
  101 +Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
  102 +
  103 + >> @posts.inspect
  104 + => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
  105 + #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
  106 + >> @posts.first.title = "hello from a breakpoint"
  107 + => "hello from a breakpoint"
  108 +
  109 +...and even better is that you can examine how your runtime objects actually work:
  110 +
  111 + >> f = @posts.first
  112 + => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
  113 + >> f.
  114 + Display all 152 possibilities? (y or n)
  115 +
  116 +Finally, when you're ready to resume execution, you press CTRL-D
  117 +
  118 +
  119 +== Console
  120 +
  121 +You can interact with the domain model by starting the console through script/console.
  122 +Here you'll have all parts of the application configured, just like it is when the
  123 +application is running. You can inspect domain models, change values, and save to the
  124 +database. Starting the script without arguments will launch it in the development environment.
  125 +Passing an argument will specify a different environment, like <tt>script/console production</tt>.
  126 +
  127 +To reload your controllers and models after launching the console run <tt>reload!</tt>
  128 +
  129 +
  130 +
  131 +== Description of contents
  132 +
  133 +app
  134 + Holds all the code that's specific to this particular application.
  135 +
  136 +app/controllers
  137 + Holds controllers that should be named like weblog_controller.rb for
  138 + automated URL mapping. All controllers should descend from
  139 + ActionController::Base.
  140 +
  141 +app/models
  142 + Holds models that should be named like post.rb.
  143 + Most models will descend from ActiveRecord::Base.
  144 +
  145 +app/views
  146 + Holds the template files for the view that should be named like
  147 + weblog/index.rhtml for the WeblogController#index action. All views use eRuby
  148 + syntax. This directory can also be used to keep stylesheets, images, and so on
  149 + that can be symlinked to public.
  150 +
  151 +app/helpers
  152 + Holds view helpers that should be named like weblog_helper.rb.
  153 +
  154 +app/apis
  155 + Holds API classes for web services.
  156 +
  157 +config
  158 + Configuration files for the Rails environment, the routing map, the database, and other dependencies.
  159 +
  160 +components
  161 + Self-contained mini-applications that can bundle together controllers, models, and views.
  162 +
  163 +db
  164 + Contains the database schema in schema.rb. db/migrate contains all
  165 + the sequence of Migrations for your schema.
  166 +
  167 +lib
  168 + Application specific libraries. Basically, any kind of custom code that doesn't
  169 + belong under controllers, models, or helpers. This directory is in the load path.
  170 +
  171 +public
  172 + The directory available for the web server. Contains subdirectories for images, stylesheets,
  173 + and javascripts. Also contains the dispatchers and the default HTML files.
  174 +
  175 +script
  176 + Helper scripts for automation and generation.
  177 +
  178 +test
  179 + Unit and functional tests along with fixtures.
  180 +
  181 +vendor
  182 + External libraries that the application depends on. Also includes the plugins subdirectory.
  183 + This directory is in the load path.
... ...
Rakefile 0 → 100644
  1 +++ a/Rakefile
... ... @@ -0,0 +1,10 @@
  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'
... ...
app/controllers/application.rb 0 → 100644
  1 +++ a/app/controllers/application.rb
... ... @@ -0,0 +1,4 @@
  1 +# Filters added to this controller will be run for all controllers in the application.
  2 +# Likewise, all the methods added will be available for all controllers.
  3 +class ApplicationController < ActionController::Base
  4 +end
0 5 \ No newline at end of file
... ...
app/helpers/application_helper.rb 0 → 100644
  1 +++ a/app/helpers/application_helper.rb
... ... @@ -0,0 +1,3 @@
  1 +# Methods added to this helper will be available to all templates in the application.
  2 +module ApplicationHelper
  3 +end
... ...
config/boot.rb 0 → 100644
  1 +++ a/config/boot.rb
... ... @@ -0,0 +1,44 @@
  1 +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
  2 +
  3 +unless defined?(RAILS_ROOT)
  4 + root_path = File.join(File.dirname(__FILE__), '..')
  5 +
  6 + unless RUBY_PLATFORM =~ /mswin32/
  7 + require 'pathname'
  8 + root_path = Pathname.new(root_path).cleanpath(true).to_s
  9 + end
  10 +
  11 + RAILS_ROOT = root_path
  12 +end
  13 +
  14 +unless defined?(Rails::Initializer)
  15 + if File.directory?("#{RAILS_ROOT}/vendor/rails")
  16 + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
  17 + else
  18 + require 'rubygems'
  19 +
  20 + environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join
  21 + environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/
  22 + rails_gem_version = $1
  23 +
  24 + if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version
  25 + rails_gem = Gem.cache.search('rails', "=#{version}").first
  26 +
  27 + if rails_gem
  28 + require_gem "rails", "=#{version}"
  29 + require rails_gem.full_gem_path + '/lib/initializer'
  30 + else
  31 + STDERR.puts %(Cannot find gem for Rails =#{version}:
  32 + Install the missing gem with 'gem install -v=#{version} rails', or
  33 + change environment.rb to define RAILS_GEM_VERSION with your desired version.
  34 + )
  35 + exit 1
  36 + end
  37 + else
  38 + require_gem "rails"
  39 + require 'initializer'
  40 + end
  41 + end
  42 +
  43 + Rails::Initializer.run(:set_load_path)
  44 +end
0 45 \ No newline at end of file
... ...
config/database.yml 0 → 100644
  1 +++ a/config/database.yml
... ... @@ -0,0 +1,35 @@
  1 +# MySQL (default setup). Versions 4.1 and 5.0 are recommended.
  2 +#
  3 +# Install the MySQL driver:
  4 +# gem install mysql
  5 +# On MacOS X:
  6 +# gem install mysql -- --include=/usr/local/lib
  7 +# On Windows:
  8 +# There is no gem for Windows. Install mysql.so from RubyForApache.
  9 +# http://rubyforge.org/projects/rubyforapache
  10 +#
  11 +# And be sure to use new-style password hashing:
  12 +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
  13 +development:
  14 + adapter: mysql
  15 + database: anhetegua_development
  16 + username: root
  17 + password:
  18 + host: localhost
  19 +
  20 +# Warning: The database defined as 'test' will be erased and
  21 +# re-generated from your development database when you run 'rake'.
  22 +# Do not set this db to the same as development or production.
  23 +test:
  24 + adapter: mysql
  25 + database: anhetegua_test
  26 + username: root
  27 + password:
  28 + host: localhost
  29 +
  30 +production:
  31 + adapter: mysql
  32 + database: anhetegua_production
  33 + username: root
  34 + password:
  35 + host: localhost
... ...
config/environment.rb 0 → 100644
  1 +++ a/config/environment.rb
... ... @@ -0,0 +1,53 @@
  1 +# Be sure to restart your web server when you modify this file.
  2 +
  3 +# Uncomment below to force Rails into production mode when
  4 +# you don't control web/app server and can't set it the proper way
  5 +# ENV['RAILS_ENV'] ||= 'production'
  6 +
  7 +# Specifies gem version of Rails to use when vendor/rails is not present
  8 +RAILS_GEM_VERSION = '1.1.6'
  9 +
  10 +# Bootstrap the Rails environment, frameworks, and default configuration
  11 +require File.join(File.dirname(__FILE__), 'boot')
  12 +
  13 +Rails::Initializer.run do |config|
  14 + # Settings in config/environments/* take precedence those specified here
  15 +
  16 + # Skip frameworks you're not going to use (only works if using vendor/rails)
  17 + # config.frameworks -= [ :action_web_service, :action_mailer ]
  18 +
  19 + # Add additional load paths for your own custom dirs
  20 + # config.load_paths += %W( #{RAILS_ROOT}/extras )
  21 +
  22 + # Force all environments to use the same logger level
  23 + # (by default production uses :info, the others :debug)
  24 + # config.log_level = :debug
  25 +
  26 + # Use the database for sessions instead of the file system
  27 + # (create the session table with 'rake db:sessions:create')
  28 + # config.action_controller.session_store = :active_record_store
  29 +
  30 + # Use SQL instead of Active Record's schema dumper when creating the test database.
  31 + # This is necessary if your schema can't be completely dumped by the schema dumper,
  32 + # like if you have constraints or database-specific column types
  33 + # config.active_record.schema_format = :sql
  34 +
  35 + # Activate observers that should always be running
  36 + # config.active_record.observers = :cacher, :garbage_collector
  37 +
  38 + # Make Active Record use UTC-base instead of local time
  39 + # config.active_record.default_timezone = :utc
  40 +
  41 + # See Rails::Configuration for more options
  42 +end
  43 +
  44 +# Add new inflection rules using the following format
  45 +# (all these examples are active by default):
  46 +# Inflector.inflections do |inflect|
  47 +# inflect.plural /^(ox)$/i, '\1en'
  48 +# inflect.singular /^(ox)en/i, '\1'
  49 +# inflect.irregular 'person', 'people'
  50 +# inflect.uncountable %w( fish sheep )
  51 +# end
  52 +
  53 +# Include your application configuration below
0 54 \ No newline at end of file
... ...
config/environments/development.rb 0 → 100644
  1 +++ a/config/environments/development.rb
... ... @@ -0,0 +1,21 @@
  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 +# Enable the breakpoint server that script/breakpointer connects to
  12 +config.breakpoint_server = true
  13 +
  14 +# Show full error reports and disable caching
  15 +config.action_controller.consider_all_requests_local = true
  16 +config.action_controller.perform_caching = false
  17 +config.action_view.cache_template_extensions = false
  18 +config.action_view.debug_rjs = true
  19 +
  20 +# Don't care if the mailer can't send
  21 +config.action_mailer.raise_delivery_errors = false
... ...
config/environments/production.rb 0 → 100644
  1 +++ a/config/environments/production.rb
... ... @@ -0,0 +1,18 @@
  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 +# Use a different logger for distributed setups
  8 +# config.logger = SyslogLogger.new
  9 +
  10 +# Full error reports are disabled and caching is turned on
  11 +config.action_controller.consider_all_requests_local = false
  12 +config.action_controller.perform_caching = true
  13 +
  14 +# Enable serving of images, stylesheets, and javascripts from an asset server
  15 +# config.action_controller.asset_host = "http://assets.example.com"
  16 +
  17 +# Disable delivery errors if you bad email addresses should just be ignored
  18 +# config.action_mailer.raise_delivery_errors = false
... ...
config/environments/test.rb 0 → 100644
  1 +++ a/config/environments/test.rb
... ... @@ -0,0 +1,19 @@
  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 +
  16 +# Tell ActionMailer not to deliver emails to the real world.
  17 +# The :test delivery method accumulates sent emails in the
  18 +# ActionMailer::Base.deliveries array.
  19 +config.action_mailer.delivery_method = :test
0 20 \ No newline at end of file
... ...
config/routes.rb 0 → 100644
  1 +++ a/config/routes.rb
... ... @@ -0,0 +1,22 @@
  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 + # You can have the root of your site routed by hooking up ''
  13 + # -- just remember to delete public/index.html.
  14 + # map.connect '', :controller => "welcome"
  15 +
  16 + # Allow downloading Web Service WSDL as a file with an extension
  17 + # instead of a file named 'wsdl'
  18 + map.connect ':controller/service.wsdl', :action => 'wsdl'
  19 +
  20 + # Install the default route as the lowest priority.
  21 + map.connect ':controller/:action/:id'
  22 +end
... ...
doc/README_FOR_APP 0 → 100644
  1 +++ a/doc/README_FOR_APP
... ... @@ -0,0 +1,2 @@
  1 +Use this README file to introduce your application and point to useful places in the API for learning more.
  2 +Run "rake appdoc" to generate API documentation for your models and controllers.
0 3 \ No newline at end of file
... ...
doc/api 0 → 120000
  1 +++ a/doc/api
... ... @@ -0,0 +1 @@
  1 +/usr/share/doc/rails/html
0 2 \ No newline at end of file
... ...
log/development.log 0 → 100644
  1 +++ a/log/development.log
... ...
log/production.log 0 → 100644
  1 +++ a/log/production.log
... ...
log/server.log 0 → 100644
  1 +++ a/log/server.log
... ...
log/test.log 0 → 100644
  1 +++ a/log/test.log
... ...
public/.htaccess 0 → 100644
  1 +++ a/public/.htaccess
... ... @@ -0,0 +1,40 @@
  1 +# General Apache options
  2 +AddHandler fastcgi-script .fcgi
  3 +AddHandler cgi-script .cgi
  4 +Options +FollowSymLinks +ExecCGI
  5 +
  6 +# If you don't want Rails to look in certain directories,
  7 +# use the following rewrite rules so that Apache won't rewrite certain requests
  8 +#
  9 +# Example:
  10 +# RewriteCond %{REQUEST_URI} ^/notrails.*
  11 +# RewriteRule .* - [L]
  12 +
  13 +# Redirect all requests not available on the filesystem to Rails
  14 +# By default the cgi dispatcher is used which is very slow
  15 +#
  16 +# For better performance replace the dispatcher with the fastcgi one
  17 +#
  18 +# Example:
  19 +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
  20 +RewriteEngine On
  21 +
  22 +# If your Rails application is accessed via an Alias directive,
  23 +# then you MUST also set the RewriteBase in this htaccess file.
  24 +#
  25 +# Example:
  26 +# Alias /myrailsapp /path/to/myrailsapp/public
  27 +# RewriteBase /myrailsapp
  28 +
  29 +RewriteRule ^$ index.html [QSA]
  30 +RewriteRule ^([^.]+)$ $1.html [QSA]
  31 +RewriteCond %{REQUEST_FILENAME} !-f
  32 +RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
  33 +
  34 +# In case Rails experiences terminal errors
  35 +# Instead of displaying this message you can supply a file here which will be rendered instead
  36 +#
  37 +# Example:
  38 +# ErrorDocument 500 /500.html
  39 +
  40 +ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
0 41 \ No newline at end of file
... ...
public/404.html 0 → 100644
  1 +++ a/public/404.html
... ... @@ -0,0 +1,8 @@
  1 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  2 + "http://www.w3.org/TR/html4/loose.dtd">
  3 +<html>
  4 +<body>
  5 + <h1>File not found</h1>
  6 + <p>Change this error message for pages not found in public/404.html</p>
  7 +</body>
  8 +</html>
0 9 \ No newline at end of file
... ...
public/500.html 0 → 100644
  1 +++ a/public/500.html
... ... @@ -0,0 +1,8 @@
  1 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  2 + "http://www.w3.org/TR/html4/loose.dtd">
  3 +<html>
  4 +<body>
  5 + <h1>Application error</h1>
  6 + <p>Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html</p>
  7 +</body>
  8 +</html>
0 9 \ No newline at end of file
... ...
public/dispatch.cgi 0 → 100755
  1 +++ a/public/dispatch.cgi
... ... @@ -0,0 +1,12 @@
  1 +#!/usr/bin/ruby1.8
  2 +
  3 +#!/usr/local/bin/ruby
  4 +
  5 +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
  6 +
  7 +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
  8 +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
  9 +require "dispatcher"
  10 +
  11 +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
  12 +Dispatcher.dispatch
0 13 \ No newline at end of file
... ...
public/dispatch.fcgi 0 → 100755
  1 +++ a/public/dispatch.fcgi
... ... @@ -0,0 +1,26 @@
  1 +#!/usr/bin/ruby1.8
  2 +
  3 +#!/usr/local/bin/ruby
  4 +#
  5 +# You may specify the path to the FastCGI crash log (a log of unhandled
  6 +# exceptions which forced the FastCGI instance to exit, great for debugging)
  7 +# and the number of requests to process before running garbage collection.
  8 +#
  9 +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
  10 +# and the GC period is nil (turned off). A reasonable number of requests
  11 +# could range from 10-100 depending on the memory footprint of your app.
  12 +#
  13 +# Example:
  14 +# # Default log path, normal GC behavior.
  15 +# RailsFCGIHandler.process!
  16 +#
  17 +# # Default log path, 50 requests between GC.
  18 +# RailsFCGIHandler.process! nil, 50
  19 +#
  20 +# # Custom log path, normal GC behavior.
  21 +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
  22 +#
  23 +require File.dirname(__FILE__) + "/../config/environment"
  24 +require 'fcgi_handler'
  25 +
  26 +RailsFCGIHandler.process!
... ...
public/dispatch.rb 0 → 100755
  1 +++ a/public/dispatch.rb
... ... @@ -0,0 +1,12 @@
  1 +#!/usr/bin/ruby1.8
  2 +
  3 +#!/usr/local/bin/ruby
  4 +
  5 +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
  6 +
  7 +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
  8 +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
  9 +require "dispatcher"
  10 +
  11 +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
  12 +Dispatcher.dispatch
0 13 \ No newline at end of file
... ...
public/favicon.ico 0 → 100644
  1 +++ a/public/favicon.ico
... ...
public/images/rails.png 0 → 100644

1.75 KB

public/index.html 0 → 100644
  1 +++ a/public/index.html
... ... @@ -0,0 +1,277 @@
  1 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  2 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  3 +<html>
  4 + <head>
  5 + <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  6 + <title>Ruby on Rails: Welcome aboard</title>
  7 + <style type="text/css" media="screen">
  8 + body {
  9 + margin: 0;
  10 + margin-bottom: 25px;
  11 + padding: 0;
  12 + background-color: #f0f0f0;
  13 + font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
  14 + font-size: 13px;
  15 + color: #333;
  16 + }
  17 +
  18 + h1 {
  19 + font-size: 28px;
  20 + color: #000;
  21 + }
  22 +
  23 + a {color: #03c}
  24 + a:hover {
  25 + background-color: #03c;
  26 + color: white;
  27 + text-decoration: none;
  28 + }
  29 +
  30 +
  31 + #page {
  32 + background-color: #f0f0f0;
  33 + width: 750px;
  34 + margin: 0;
  35 + margin-left: auto;
  36 + margin-right: auto;
  37 + }
  38 +
  39 + #content {
  40 + float: left;
  41 + background-color: white;
  42 + border: 3px solid #aaa;
  43 + border-top: none;
  44 + padding: 25px;
  45 + width: 500px;
  46 + }
  47 +
  48 + #sidebar {
  49 + float: right;
  50 + width: 175px;
  51 + }
  52 +
  53 + #footer {
  54 + clear: both;
  55 + }
  56 +
  57 +
  58 + #header, #about, #getting-started {
  59 + padding-left: 75px;
  60 + padding-right: 30px;
  61 + }
  62 +
  63 +
  64 + #header {
  65 + background-image: url("images/rails.png");
  66 + background-repeat: no-repeat;
  67 + background-position: top left;
  68 + height: 64px;
  69 + }
  70 + #header h1, #header h2 {margin: 0}
  71 + #header h2 {
  72 + color: #888;
  73 + font-weight: normal;
  74 + font-size: 16px;
  75 + }
  76 +
  77 +
  78 + #about h3 {
  79 + margin: 0;
  80 + margin-bottom: 10px;
  81 + font-size: 14px;
  82 + }
  83 +
  84 + #about-content {
  85 + background-color: #ffd;
  86 + border: 1px solid #fc0;
  87 + margin-left: -11px;
  88 + }
  89 + #about-content table {
  90 + margin-top: 10px;
  91 + margin-bottom: 10px;
  92 + font-size: 11px;
  93 + border-collapse: collapse;
  94 + }
  95 + #about-content td {
  96 + padding: 10px;
  97 + padding-top: 3px;
  98 + padding-bottom: 3px;
  99 + }
  100 + #about-content td.name {color: #555}
  101 + #about-content td.value {color: #000}
  102 +
  103 + #about-content.failure {
  104 + background-color: #fcc;
  105 + border: 1px solid #f00;
  106 + }
  107 + #about-content.failure p {
  108 + margin: 0;
  109 + padding: 10px;
  110 + }
  111 +
  112 +
  113 + #getting-started {
  114 + border-top: 1px solid #ccc;
  115 + margin-top: 25px;
  116 + padding-top: 15px;
  117 + }
  118 + #getting-started h1 {
  119 + margin: 0;
  120 + font-size: 20px;
  121 + }
  122 + #getting-started h2 {
  123 + margin: 0;
  124 + font-size: 14px;
  125 + font-weight: normal;
  126 + color: #333;
  127 + margin-bottom: 25px;
  128 + }
  129 + #getting-started ol {
  130 + margin-left: 0;
  131 + padding-left: 0;
  132 + }
  133 + #getting-started li {
  134 + font-size: 18px;
  135 + color: #888;
  136 + margin-bottom: 25px;
  137 + }
  138 + #getting-started li h2 {
  139 + margin: 0;
  140 + font-weight: normal;
  141 + font-size: 18px;
  142 + color: #333;
  143 + }
  144 + #getting-started li p {
  145 + color: #555;
  146 + font-size: 13px;
  147 + }
  148 +
  149 +
  150 + #search {
  151 + margin: 0;
  152 + padding-top: 10px;
  153 + padding-bottom: 10px;
  154 + font-size: 11px;
  155 + }
  156 + #search input {
  157 + font-size: 11px;
  158 + margin: 2px;
  159 + }
  160 + #search-text {width: 170px}
  161 +
  162 +
  163 + #sidebar ul {
  164 + margin-left: 0;
  165 + padding-left: 0;
  166 + }
  167 + #sidebar ul h3 {
  168 + margin-top: 25px;
  169 + font-size: 16px;
  170 + padding-bottom: 10px;
  171 + border-bottom: 1px solid #ccc;
  172 + }
  173 + #sidebar li {
  174 + list-style-type: none;
  175 + }
  176 + #sidebar ul.links li {
  177 + margin-bottom: 5px;
  178 + }
  179 +
  180 + </style>
  181 + <script type="text/javascript" src="javascripts/prototype.js"></script>
  182 + <script type="text/javascript" src="javascripts/effects.js"></script>
  183 + <script type="text/javascript">
  184 + function about() {
  185 + if (Element.empty('about-content')) {
  186 + new Ajax.Updater('about-content', 'rails/info/properties', {
  187 + method: 'get',
  188 + onFailure: function() {Element.classNames('about-content').add('failure')},
  189 + onComplete: function() {new Effect.BlindDown('about-content', {duration: 0.25})}
  190 + });
  191 + } else {
  192 + new Effect[Element.visible('about-content') ?
  193 + 'BlindUp' : 'BlindDown']('about-content', {duration: 0.25});
  194 + }
  195 + }
  196 +
  197 + window.onload = function() {
  198 + $('search-text').value = '';
  199 + $('search').onsubmit = function() {
  200 + $('search-text').value = 'site:rubyonrails.org ' + $F('search-text');
  201 + }
  202 + }
  203 + </script>
  204 + </head>
  205 + <body>
  206 + <div id="page">
  207 + <div id="sidebar">
  208 + <ul id="sidebar-items">
  209 + <li>
  210 + <form id="search" action="http://www.google.com/search" method="get">
  211 + <input type="hidden" name="hl" value="en" />
  212 + <input type="text" id="search-text" name="q" value="site:rubyonrails.org " />
  213 + <input type="submit" value="Search" /> the Rails site
  214 + </form>
  215 + </li>
  216 +
  217 + <li>
  218 + <h3>Join the community</h3>
  219 + <ul class="links">
  220 + <li><a href="http://www.rubyonrails.org/">Ruby on Rails</a></li>
  221 + <li><a href="http://weblog.rubyonrails.org/">Official weblog</a></li>
  222 + <li><a href="http://lists.rubyonrails.org/">Mailing lists</a></li>
  223 + <li><a href="http://wiki.rubyonrails.org/rails/pages/IRC">IRC channel</a></li>
  224 + <li><a href="http://wiki.rubyonrails.org/">Wiki</a></li>
  225 + <li><a href="http://dev.rubyonrails.org/">Bug tracker</a></li>
  226 + </ul>
  227 + </li>
  228 +
  229 + <li>
  230 + <h3>Browse the documentation</h3>
  231 + <ul class="links">
  232 + <li><a href="http://api.rubyonrails.org/">Rails API</a></li>
  233 + <li><a href="http://stdlib.rubyonrails.org/">Ruby standard library</a></li>
  234 + <li><a href="http://corelib.rubyonrails.org/">Ruby core</a></li>
  235 + </ul>
  236 + </li>
  237 + </ul>
  238 + </div>
  239 +
  240 + <div id="content">
  241 + <div id="header">
  242 + <h1>Welcome aboard</h1>
  243 + <h2>You&rsquo;re riding the Rails!</h2>
  244 + </div>
  245 +
  246 + <div id="about">
  247 + <h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
  248 + <div id="about-content" style="display: none"></div>
  249 + </div>
  250 +
  251 + <div id="getting-started">
  252 + <h1>Getting started</h1>
  253 + <h2>Here&rsquo;s how to get rolling:</h2>
  254 +
  255 + <ol>
  256 + <li>
  257 + <h2>Create your databases and edit <tt>config/database.yml</tt></h2>
  258 + <p>Rails needs to know your login and password.</p>
  259 + </li>
  260 +
  261 + <li>
  262 + <h2>Use <tt>script/generate</tt> to create your models and controllers</h2>
  263 + <p>To see all available options, run it without parameters.</p>
  264 + </li>
  265 +
  266 + <li>
  267 + <h2>Set up a default route and remove or rename this file</h2>
  268 + <p>Routes are setup in config/routes.rb.</p>
  269 + </li>
  270 + </ol>
  271 + </div>
  272 + </div>
  273 +
  274 + <div id="footer">&nbsp;</div>
  275 + </div>
  276 + </body>
  277 +</html>
0 278 \ No newline at end of file
... ...
public/javascripts/application.js 0 → 100644
  1 +++ a/public/javascripts/application.js
... ... @@ -0,0 +1,2 @@
  1 +// Place your application-specific JavaScript functions and classes here
  2 +// This file is automatically included by javascript_include_tag :defaults
... ...
public/javascripts/controls.js 0 → 100644
  1 +++ a/public/javascripts/controls.js
... ... @@ -0,0 +1,815 @@
  1 +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2 +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
  3 +// (c) 2005 Jon Tirsen (http://www.tirsen.com)
  4 +// Contributors:
  5 +// Richard Livsey
  6 +// Rahul Bhargava
  7 +// Rob Wills
  8 +//
  9 +// See scriptaculous.js for full license.
  10 +
  11 +// Autocompleter.Base handles all the autocompletion functionality
  12 +// that's independent of the data source for autocompletion. This
  13 +// includes drawing the autocompletion menu, observing keyboard
  14 +// and mouse events, and similar.
  15 +//
  16 +// Specific autocompleters need to provide, at the very least,
  17 +// a getUpdatedChoices function that will be invoked every time
  18 +// the text inside the monitored textbox changes. This method
  19 +// should get the text for which to provide autocompletion by
  20 +// invoking this.getToken(), NOT by directly accessing
  21 +// this.element.value. This is to allow incremental tokenized
  22 +// autocompletion. Specific auto-completion logic (AJAX, etc)
  23 +// belongs in getUpdatedChoices.
  24 +//
  25 +// Tokenized incremental autocompletion is enabled automatically
  26 +// when an autocompleter is instantiated with the 'tokens' option
  27 +// in the options parameter, e.g.:
  28 +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
  29 +// will incrementally autocomplete with a comma as the token.
  30 +// Additionally, ',' in the above example can be replaced with
  31 +// a token array, e.g. { tokens: [',', '\n'] } which
  32 +// enables autocompletion on multiple tokens. This is most
  33 +// useful when one of the tokens is \n (a newline), as it
  34 +// allows smart autocompletion after linebreaks.
  35 +
  36 +var Autocompleter = {}
  37 +Autocompleter.Base = function() {};
  38 +Autocompleter.Base.prototype = {
  39 + baseInitialize: function(element, update, options) {
  40 + this.element = $(element);
  41 + this.update = $(update);
  42 + this.hasFocus = false;
  43 + this.changed = false;
  44 + this.active = false;
  45 + this.index = 0;
  46 + this.entryCount = 0;
  47 +
  48 + if (this.setOptions)
  49 + this.setOptions(options);
  50 + else
  51 + this.options = options || {};
  52 +
  53 + this.options.paramName = this.options.paramName || this.element.name;
  54 + this.options.tokens = this.options.tokens || [];
  55 + this.options.frequency = this.options.frequency || 0.4;
  56 + this.options.minChars = this.options.minChars || 1;
  57 + this.options.onShow = this.options.onShow ||
  58 + function(element, update){
  59 + if(!update.style.position || update.style.position=='absolute') {
  60 + update.style.position = 'absolute';
  61 + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
  62 + }
  63 + Effect.Appear(update,{duration:0.15});
  64 + };
  65 + this.options.onHide = this.options.onHide ||
  66 + function(element, update){ new Effect.Fade(update,{duration:0.15}) };
  67 +
  68 + if (typeof(this.options.tokens) == 'string')
  69 + this.options.tokens = new Array(this.options.tokens);
  70 +
  71 + this.observer = null;
  72 +
  73 + this.element.setAttribute('autocomplete','off');
  74 +
  75 + Element.hide(this.update);
  76 +
  77 + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
  78 + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  79 + },
  80 +
  81 + show: function() {
  82 + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
  83 + if(!this.iefix &&
  84 + (navigator.appVersion.indexOf('MSIE')>0) &&
  85 + (navigator.userAgent.indexOf('Opera')<0) &&
  86 + (Element.getStyle(this.update, 'position')=='absolute')) {
  87 + new Insertion.After(this.update,
  88 + '<iframe id="' + this.update.id + '_iefix" '+
  89 + 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
  90 + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  91 + this.iefix = $(this.update.id+'_iefix');
  92 + }
  93 + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  94 + },
  95 +
  96 + fixIEOverlapping: function() {
  97 + Position.clone(this.update, this.iefix);
  98 + this.iefix.style.zIndex = 1;
  99 + this.update.style.zIndex = 2;
  100 + Element.show(this.iefix);
  101 + },
  102 +
  103 + hide: function() {
  104 + this.stopIndicator();
  105 + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
  106 + if(this.iefix) Element.hide(this.iefix);
  107 + },
  108 +
  109 + startIndicator: function() {
  110 + if(this.options.indicator) Element.show(this.options.indicator);
  111 + },
  112 +
  113 + stopIndicator: function() {
  114 + if(this.options.indicator) Element.hide(this.options.indicator);
  115 + },
  116 +
  117 + onKeyPress: function(event) {
  118 + if(this.active)
  119 + switch(event.keyCode) {
  120 + case Event.KEY_TAB:
  121 + case Event.KEY_RETURN:
  122 + this.selectEntry();
  123 + Event.stop(event);
  124 + case Event.KEY_ESC:
  125 + this.hide();
  126 + this.active = false;
  127 + Event.stop(event);
  128 + return;
  129 + case Event.KEY_LEFT:
  130 + case Event.KEY_RIGHT:
  131 + return;
  132 + case Event.KEY_UP:
  133 + this.markPrevious();
  134 + this.render();
  135 + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
  136 + return;
  137 + case Event.KEY_DOWN:
  138 + this.markNext();
  139 + this.render();
  140 + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
  141 + return;
  142 + }
  143 + else
  144 + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
  145 + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
  146 +
  147 + this.changed = true;
  148 + this.hasFocus = true;
  149 +
  150 + if(this.observer) clearTimeout(this.observer);
  151 + this.observer =
  152 + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  153 + },
  154 +
  155 + activate: function() {
  156 + this.changed = false;
  157 + this.hasFocus = true;
  158 + this.getUpdatedChoices();
  159 + },
  160 +
  161 + onHover: function(event) {
  162 + var element = Event.findElement(event, 'LI');
  163 + if(this.index != element.autocompleteIndex)
  164 + {
  165 + this.index = element.autocompleteIndex;
  166 + this.render();
  167 + }
  168 + Event.stop(event);
  169 + },
  170 +
  171 + onClick: function(event) {
  172 + var element = Event.findElement(event, 'LI');
  173 + this.index = element.autocompleteIndex;
  174 + this.selectEntry();
  175 + this.hide();
  176 + },
  177 +
  178 + onBlur: function(event) {
  179 + // needed to make click events working
  180 + setTimeout(this.hide.bind(this), 250);
  181 + this.hasFocus = false;
  182 + this.active = false;
  183 + },
  184 +
  185 + render: function() {
  186 + if(this.entryCount > 0) {
  187 + for (var i = 0; i < this.entryCount; i++)
  188 + this.index==i ?
  189 + Element.addClassName(this.getEntry(i),"selected") :
  190 + Element.removeClassName(this.getEntry(i),"selected");
  191 +
  192 + if(this.hasFocus) {
  193 + this.show();
  194 + this.active = true;
  195 + }
  196 + } else {
  197 + this.active = false;
  198 + this.hide();
  199 + }
  200 + },
  201 +
  202 + markPrevious: function() {
  203 + if(this.index > 0) this.index--
  204 + else this.index = this.entryCount-1;
  205 + },
  206 +
  207 + markNext: function() {
  208 + if(this.index < this.entryCount-1) this.index++
  209 + else this.index = 0;
  210 + },
  211 +
  212 + getEntry: function(index) {
  213 + return this.update.firstChild.childNodes[index];
  214 + },
  215 +
  216 + getCurrentEntry: function() {
  217 + return this.getEntry(this.index);
  218 + },
  219 +
  220 + selectEntry: function() {
  221 + this.active = false;
  222 + this.updateElement(this.getCurrentEntry());
  223 + },
  224 +
  225 + updateElement: function(selectedElement) {
  226 + if (this.options.updateElement) {
  227 + this.options.updateElement(selectedElement);
  228 + return;
  229 + }
  230 + var value = '';
  231 + if (this.options.select) {
  232 + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
  233 + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
  234 + } else
  235 + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
  236 +
  237 + var lastTokenPos = this.findLastToken();
  238 + if (lastTokenPos != -1) {
  239 + var newValue = this.element.value.substr(0, lastTokenPos + 1);
  240 + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
  241 + if (whitespace)
  242 + newValue += whitespace[0];
  243 + this.element.value = newValue + value;
  244 + } else {
  245 + this.element.value = value;
  246 + }
  247 + this.element.focus();
  248 +
  249 + if (this.options.afterUpdateElement)
  250 + this.options.afterUpdateElement(this.element, selectedElement);
  251 + },
  252 +
  253 + updateChoices: function(choices) {
  254 + if(!this.changed && this.hasFocus) {
  255 + this.update.innerHTML = choices;
  256 + Element.cleanWhitespace(this.update);
  257 + Element.cleanWhitespace(this.update.firstChild);
  258 +
  259 + if(this.update.firstChild && this.update.firstChild.childNodes) {
  260 + this.entryCount =
  261 + this.update.firstChild.childNodes.length;
  262 + for (var i = 0; i < this.entryCount; i++) {
  263 + var entry = this.getEntry(i);
  264 + entry.autocompleteIndex = i;
  265 + this.addObservers(entry);
  266 + }
  267 + } else {
  268 + this.entryCount = 0;
  269 + }
  270 +
  271 + this.stopIndicator();
  272 +
  273 + this.index = 0;
  274 + this.render();
  275 + }
  276 + },
  277 +
  278 + addObservers: function(element) {
  279 + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
  280 + Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  281 + },
  282 +
  283 + onObserverEvent: function() {
  284 + this.changed = false;
  285 + if(this.getToken().length>=this.options.minChars) {
  286 + this.startIndicator();
  287 + this.getUpdatedChoices();
  288 + } else {
  289 + this.active = false;
  290 + this.hide();
  291 + }
  292 + },
  293 +
  294 + getToken: function() {
  295 + var tokenPos = this.findLastToken();
  296 + if (tokenPos != -1)
  297 + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
  298 + else
  299 + var ret = this.element.value;
  300 +
  301 + return /\n/.test(ret) ? '' : ret;
  302 + },
  303 +
  304 + findLastToken: function() {
  305 + var lastTokenPos = -1;
  306 +
  307 + for (var i=0; i<this.options.tokens.length; i++) {
  308 + var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
  309 + if (thisTokenPos > lastTokenPos)
  310 + lastTokenPos = thisTokenPos;
  311 + }
  312 + return lastTokenPos;
  313 + }
  314 +}
  315 +
  316 +Ajax.Autocompleter = Class.create();
  317 +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  318 + initialize: function(element, update, url, options) {
  319 + this.baseInitialize(element, update, options);
  320 + this.options.asynchronous = true;
  321 + this.options.onComplete = this.onComplete.bind(this);
  322 + this.options.defaultParams = this.options.parameters || null;
  323 + this.url = url;
  324 + },
  325 +
  326 + getUpdatedChoices: function() {
  327 + entry = encodeURIComponent(this.options.paramName) + '=' +
  328 + encodeURIComponent(this.getToken());
  329 +
  330 + this.options.parameters = this.options.callback ?
  331 + this.options.callback(this.element, entry) : entry;
  332 +
  333 + if(this.options.defaultParams)
  334 + this.options.parameters += '&' + this.options.defaultParams;
  335 +
  336 + new Ajax.Request(this.url, this.options);
  337 + },
  338 +
  339 + onComplete: function(request) {
  340 + this.updateChoices(request.responseText);
  341 + }
  342 +
  343 +});
  344 +
  345 +// The local array autocompleter. Used when you'd prefer to
  346 +// inject an array of autocompletion options into the page, rather
  347 +// than sending out Ajax queries, which can be quite slow sometimes.
  348 +//
  349 +// The constructor takes four parameters. The first two are, as usual,
  350 +// the id of the monitored textbox, and id of the autocompletion menu.
  351 +// The third is the array you want to autocomplete from, and the fourth
  352 +// is the options block.
  353 +//
  354 +// Extra local autocompletion options:
  355 +// - choices - How many autocompletion choices to offer
  356 +//
  357 +// - partialSearch - If false, the autocompleter will match entered
  358 +// text only at the beginning of strings in the
  359 +// autocomplete array. Defaults to true, which will
  360 +// match text at the beginning of any *word* in the
  361 +// strings in the autocomplete array. If you want to
  362 +// search anywhere in the string, additionally set
  363 +// the option fullSearch to true (default: off).
  364 +//
  365 +// - fullSsearch - Search anywhere in autocomplete array strings.
  366 +//
  367 +// - partialChars - How many characters to enter before triggering
  368 +// a partial match (unlike minChars, which defines
  369 +// how many characters are required to do any match
  370 +// at all). Defaults to 2.
  371 +//
  372 +// - ignoreCase - Whether to ignore case when autocompleting.
  373 +// Defaults to true.
  374 +//
  375 +// It's possible to pass in a custom function as the 'selector'
  376 +// option, if you prefer to write your own autocompletion logic.
  377 +// In that case, the other options above will not apply unless
  378 +// you support them.
  379 +
  380 +Autocompleter.Local = Class.create();
  381 +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  382 + initialize: function(element, update, array, options) {
  383 + this.baseInitialize(element, update, options);
  384 + this.options.array = array;
  385 + },
  386 +
  387 + getUpdatedChoices: function() {
  388 + this.updateChoices(this.options.selector(this));
  389 + },
  390 +
  391 + setOptions: function(options) {
  392 + this.options = Object.extend({
  393 + choices: 10,
  394 + partialSearch: true,
  395 + partialChars: 2,
  396 + ignoreCase: true,
  397 + fullSearch: false,
  398 + selector: function(instance) {
  399 + var ret = []; // Beginning matches
  400 + var partial = []; // Inside matches
  401 + var entry = instance.getToken();
  402 + var count = 0;
  403 +
  404 + for (var i = 0; i < instance.options.array.length &&
  405 + ret.length < instance.options.choices ; i++) {
  406 +
  407 + var elem = instance.options.array[i];
  408 + var foundPos = instance.options.ignoreCase ?
  409 + elem.toLowerCase().indexOf(entry.toLowerCase()) :
  410 + elem.indexOf(entry);
  411 +
  412 + while (foundPos != -1) {
  413 + if (foundPos == 0 && elem.length != entry.length) {
  414 + ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
  415 + elem.substr(entry.length) + "</li>");
  416 + break;
  417 + } else if (entry.length >= instance.options.partialChars &&
  418 + instance.options.partialSearch && foundPos != -1) {
  419 + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
  420 + partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
  421 + elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
  422 + foundPos + entry.length) + "</li>");
  423 + break;
  424 + }
  425 + }
  426 +
  427 + foundPos = instance.options.ignoreCase ?
  428 + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
  429 + elem.indexOf(entry, foundPos + 1);
  430 +
  431 + }
  432 + }
  433 + if (partial.length)
  434 + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
  435 + return "<ul>" + ret.join('') + "</ul>";
  436 + }
  437 + }, options || {});
  438 + }
  439 +});
  440 +
  441 +// AJAX in-place editor
  442 +//
  443 +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
  444 +
  445 +// Use this if you notice weird scrolling problems on some browsers,
  446 +// the DOM might be a bit confused when this gets called so do this
  447 +// waits 1 ms (with setTimeout) until it does the activation
  448 +Field.scrollFreeActivate = function(field) {
  449 + setTimeout(function() {
  450 + Field.activate(field);
  451 + }, 1);
  452 +}
  453 +
  454 +Ajax.InPlaceEditor = Class.create();
  455 +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
  456 +Ajax.InPlaceEditor.prototype = {
  457 + initialize: function(element, url, options) {
  458 + this.url = url;
  459 + this.element = $(element);
  460 +
  461 + this.options = Object.extend({
  462 + okButton: true,
  463 + okText: "ok",
  464 + cancelLink: true,
  465 + cancelText: "cancel",
  466 + savingText: "Saving...",
  467 + clickToEditText: "Click to edit",
  468 + okText: "ok",
  469 + rows: 1,
  470 + onComplete: function(transport, element) {
  471 + new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
  472 + },
  473 + onFailure: function(transport) {
  474 + alert("Error communicating with the server: " + transport.responseText.stripTags());
  475 + },
  476 + callback: function(form) {
  477 + return Form.serialize(form);
  478 + },
  479 + handleLineBreaks: true,
  480 + loadingText: 'Loading...',
  481 + savingClassName: 'inplaceeditor-saving',
  482 + loadingClassName: 'inplaceeditor-loading',
  483 + formClassName: 'inplaceeditor-form',
  484 + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
  485 + highlightendcolor: "#FFFFFF",
  486 + externalControl: null,
  487 + submitOnBlur: false,
  488 + ajaxOptions: {},
  489 + evalScripts: false
  490 + }, options || {});
  491 +
  492 + if(!this.options.formId && this.element.id) {
  493 + this.options.formId = this.element.id + "-inplaceeditor";
  494 + if ($(this.options.formId)) {
  495 + // there's already a form with that name, don't specify an id
  496 + this.options.formId = null;
  497 + }
  498 + }
  499 +
  500 + if (this.options.externalControl) {
  501 + this.options.externalControl = $(this.options.externalControl);
  502 + }
  503 +
  504 + this.originalBackground = Element.getStyle(this.element, 'background-color');
  505 + if (!this.originalBackground) {
  506 + this.originalBackground = "transparent";
  507 + }
  508 +
  509 + this.element.title = this.options.clickToEditText;
  510 +
  511 + this.onclickListener = this.enterEditMode.bindAsEventListener(this);
  512 + this.mouseoverListener = this.enterHover.bindAsEventListener(this);
  513 + this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
  514 + Event.observe(this.element, 'click', this.onclickListener);
  515 + Event.observe(this.element, 'mouseover', this.mouseoverListener);
  516 + Event.observe(this.element, 'mouseout', this.mouseoutListener);
  517 + if (this.options.externalControl) {
  518 + Event.observe(this.options.externalControl, 'click', this.onclickListener);
  519 + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
  520 + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
  521 + }
  522 + },
  523 + enterEditMode: function(evt) {
  524 + if (this.saving) return;
  525 + if (this.editing) return;
  526 + this.editing = true;
  527 + this.onEnterEditMode();
  528 + if (this.options.externalControl) {
  529 + Element.hide(this.options.externalControl);
  530 + }
  531 + Element.hide(this.element);
  532 + this.createForm();
  533 + this.element.parentNode.insertBefore(this.form, this.element);
  534 + Field.scrollFreeActivate(this.editField);
  535 + // stop the event to avoid a page refresh in Safari
  536 + if (evt) {
  537 + Event.stop(evt);
  538 + }
  539 + return false;
  540 + },
  541 + createForm: function() {
  542 + this.form = document.createElement("form");
  543 + this.form.id = this.options.formId;
  544 + Element.addClassName(this.form, this.options.formClassName)
  545 + this.form.onsubmit = this.onSubmit.bind(this);
  546 +
  547 + this.createEditField();
  548 +
  549 + if (this.options.textarea) {
  550 + var br = document.createElement("br");
  551 + this.form.appendChild(br);
  552 + }
  553 +
  554 + if (this.options.okButton) {
  555 + okButton = document.createElement("input");
  556 + okButton.type = "submit";
  557 + okButton.value = this.options.okText;
  558 + okButton.className = 'editor_ok_button';
  559 + this.form.appendChild(okButton);
  560 + }
  561 +
  562 + if (this.options.cancelLink) {
  563 + cancelLink = document.createElement("a");
  564 + cancelLink.href = "#";
  565 + cancelLink.appendChild(document.createTextNode(this.options.cancelText));
  566 + cancelLink.onclick = this.onclickCancel.bind(this);
  567 + cancelLink.className = 'editor_cancel';
  568 + this.form.appendChild(cancelLink);
  569 + }
  570 + },
  571 + hasHTMLLineBreaks: function(string) {
  572 + if (!this.options.handleLineBreaks) return false;
  573 + return string.match(/<br/i) || string.match(/<p>/i);
  574 + },
  575 + convertHTMLLineBreaks: function(string) {
  576 + return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  577 + },
  578 + createEditField: function() {
  579 + var text;
  580 + if(this.options.loadTextURL) {
  581 + text = this.options.loadingText;
  582 + } else {
  583 + text = this.getText();
  584 + }
  585 +
  586 + var obj = this;
  587 +
  588 + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
  589 + this.options.textarea = false;
  590 + var textField = document.createElement("input");
  591 + textField.obj = this;
  592 + textField.type = "text";
  593 + textField.name = "value";
  594 + textField.value = text;
  595 + textField.style.backgroundColor = this.options.highlightcolor;
  596 + textField.className = 'editor_field';
  597 + var size = this.options.size || this.options.cols || 0;
  598 + if (size != 0) textField.size = size;
  599 + if (this.options.submitOnBlur)
  600 + textField.onblur = this.onSubmit.bind(this);
  601 + this.editField = textField;
  602 + } else {
  603 + this.options.textarea = true;
  604 + var textArea = document.createElement("textarea");
  605 + textArea.obj = this;
  606 + textArea.name = "value";
  607 + textArea.value = this.convertHTMLLineBreaks(text);
  608 + textArea.rows = this.options.rows;
  609 + textArea.cols = this.options.cols || 40;
  610 + textArea.className = 'editor_field';
  611 + if (this.options.submitOnBlur)
  612 + textArea.onblur = this.onSubmit.bind(this);
  613 + this.editField = textArea;
  614 + }
  615 +
  616 + if(this.options.loadTextURL) {
  617 + this.loadExternalText();
  618 + }
  619 + this.form.appendChild(this.editField);
  620 + },
  621 + getText: function() {
  622 + return this.element.innerHTML;
  623 + },
  624 + loadExternalText: function() {
  625 + Element.addClassName(this.form, this.options.loadingClassName);
  626 + this.editField.disabled = true;
  627 + new Ajax.Request(
  628 + this.options.loadTextURL,
  629 + Object.extend({
  630 + asynchronous: true,
  631 + onComplete: this.onLoadedExternalText.bind(this)
  632 + }, this.options.ajaxOptions)
  633 + );
  634 + },
  635 + onLoadedExternalText: function(transport) {
  636 + Element.removeClassName(this.form, this.options.loadingClassName);
  637 + this.editField.disabled = false;
  638 + this.editField.value = transport.responseText.stripTags();
  639 + },
  640 + onclickCancel: function() {
  641 + this.onComplete();
  642 + this.leaveEditMode();
  643 + return false;
  644 + },
  645 + onFailure: function(transport) {
  646 + this.options.onFailure(transport);
  647 + if (this.oldInnerHTML) {
  648 + this.element.innerHTML = this.oldInnerHTML;
  649 + this.oldInnerHTML = null;
  650 + }
  651 + return false;
  652 + },
  653 + onSubmit: function() {
  654 + // onLoading resets these so we need to save them away for the Ajax call
  655 + var form = this.form;
  656 + var value = this.editField.value;
  657 +
  658 + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
  659 + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
  660 + // to be displayed indefinitely
  661 + this.onLoading();
  662 +
  663 + if (this.options.evalScripts) {
  664 + new Ajax.Request(
  665 + this.url, Object.extend({
  666 + parameters: this.options.callback(form, value),
  667 + onComplete: this.onComplete.bind(this),
  668 + onFailure: this.onFailure.bind(this),
  669 + asynchronous:true,
  670 + evalScripts:true
  671 + }, this.options.ajaxOptions));
  672 + } else {
  673 + new Ajax.Updater(
  674 + { success: this.element,
  675 + // don't update on failure (this could be an option)
  676 + failure: null },
  677 + this.url, Object.extend({
  678 + parameters: this.options.callback(form, value),
  679 + onComplete: this.onComplete.bind(this),
  680 + onFailure: this.onFailure.bind(this)
  681 + }, this.options.ajaxOptions));
  682 + }
  683 + // stop the event to avoid a page refresh in Safari
  684 + if (arguments.length > 1) {
  685 + Event.stop(arguments[0]);
  686 + }
  687 + return false;
  688 + },
  689 + onLoading: function() {
  690 + this.saving = true;
  691 + this.removeForm();
  692 + this.leaveHover();
  693 + this.showSaving();
  694 + },
  695 + showSaving: function() {
  696 + this.oldInnerHTML = this.element.innerHTML;
  697 + this.element.innerHTML = this.options.savingText;
  698 + Element.addClassName(this.element, this.options.savingClassName);
  699 + this.element.style.backgroundColor = this.originalBackground;
  700 + Element.show(this.element);
  701 + },
  702 + removeForm: function() {
  703 + if(this.form) {
  704 + if (this.form.parentNode) Element.remove(this.form);
  705 + this.form = null;
  706 + }
  707 + },
  708 + enterHover: function() {
  709 + if (this.saving) return;
  710 + this.element.style.backgroundColor = this.options.highlightcolor;
  711 + if (this.effect) {
  712 + this.effect.cancel();
  713 + }
  714 + Element.addClassName(this.element, this.options.hoverClassName)
  715 + },
  716 + leaveHover: function() {
  717 + if (this.options.backgroundColor) {
  718 + this.element.style.backgroundColor = this.oldBackground;
  719 + }
  720 + Element.removeClassName(this.element, this.options.hoverClassName)
  721 + if (this.saving) return;
  722 + this.effect = new Effect.Highlight(this.element, {
  723 + startcolor: this.options.highlightcolor,
  724 + endcolor: this.options.highlightendcolor,
  725 + restorecolor: this.originalBackground
  726 + });
  727 + },
  728 + leaveEditMode: function() {
  729 + Element.removeClassName(this.element, this.options.savingClassName);
  730 + this.removeForm();
  731 + this.leaveHover();
  732 + this.element.style.backgroundColor = this.originalBackground;
  733 + Element.show(this.element);
  734 + if (this.options.externalControl) {
  735 + Element.show(this.options.externalControl);
  736 + }
  737 + this.editing = false;
  738 + this.saving = false;
  739 + this.oldInnerHTML = null;
  740 + this.onLeaveEditMode();
  741 + },
  742 + onComplete: function(transport) {
  743 + this.leaveEditMode();
  744 + this.options.onComplete.bind(this)(transport, this.element);
  745 + },
  746 + onEnterEditMode: function() {},
  747 + onLeaveEditMode: function() {},
  748 + dispose: function() {
  749 + if (this.oldInnerHTML) {
  750 + this.element.innerHTML = this.oldInnerHTML;
  751 + }
  752 + this.leaveEditMode();
  753 + Event.stopObserving(this.element, 'click', this.onclickListener);
  754 + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
  755 + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
  756 + if (this.options.externalControl) {
  757 + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
  758 + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
  759 + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
  760 + }
  761 + }
  762 +};
  763 +
  764 +Ajax.InPlaceCollectionEditor = Class.create();
  765 +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
  766 +Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  767 + createEditField: function() {
  768 + if (!this.cached_selectTag) {
  769 + var selectTag = document.createElement("select");
  770 + var collection = this.options.collection || [];
  771 + var optionTag;
  772 + collection.each(function(e,i) {
  773 + optionTag = document.createElement("option");
  774 + optionTag.value = (e instanceof Array) ? e[0] : e;
  775 + if(this.options.value==optionTag.value) optionTag.selected = true;
  776 + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
  777 + selectTag.appendChild(optionTag);
  778 + }.bind(this));
  779 + this.cached_selectTag = selectTag;
  780 + }
  781 +
  782 + this.editField = this.cached_selectTag;
  783 + if(this.options.loadTextURL) this.loadExternalText();
  784 + this.form.appendChild(this.editField);
  785 + this.options.callback = function(form, value) {
  786 + return "value=" + encodeURIComponent(value);
  787 + }
  788 + }
  789 +});
  790 +
  791 +// Delayed observer, like Form.Element.Observer,
  792 +// but waits for delay after last key input
  793 +// Ideal for live-search fields
  794 +
  795 +Form.Element.DelayedObserver = Class.create();
  796 +Form.Element.DelayedObserver.prototype = {
  797 + initialize: function(element, delay, callback) {
  798 + this.delay = delay || 0.5;
  799 + this.element = $(element);
  800 + this.callback = callback;
  801 + this.timer = null;
  802 + this.lastValue = $F(this.element);
  803 + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  804 + },
  805 + delayedListener: function(event) {
  806 + if(this.lastValue == $F(this.element)) return;
  807 + if(this.timer) clearTimeout(this.timer);
  808 + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
  809 + this.lastValue = $F(this.element);
  810 + },
  811 + onTimerEvent: function() {
  812 + this.timer = null;
  813 + this.callback(this.element, $F(this.element));
  814 + }
  815 +};
... ...
public/javascripts/dragdrop.js 0 → 100644
  1 +++ a/public/javascripts/dragdrop.js
... ... @@ -0,0 +1,913 @@
  1 +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2 +// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
  3 +//
  4 +// See scriptaculous.js for full license.
  5 +
  6 +/*--------------------------------------------------------------------------*/
  7 +
  8 +var Droppables = {
  9 + drops: [],
  10 +
  11 + remove: function(element) {
  12 + this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  13 + },
  14 +
  15 + add: function(element) {
  16 + element = $(element);
  17 + var options = Object.extend({
  18 + greedy: true,
  19 + hoverclass: null,
  20 + tree: false
  21 + }, arguments[1] || {});
  22 +
  23 + // cache containers
  24 + if(options.containment) {
  25 + options._containers = [];
  26 + var containment = options.containment;
  27 + if((typeof containment == 'object') &&
  28 + (containment.constructor == Array)) {
  29 + containment.each( function(c) { options._containers.push($(c)) });
  30 + } else {
  31 + options._containers.push($(containment));
  32 + }
  33 + }
  34 +
  35 + if(options.accept) options.accept = [options.accept].flatten();
  36 +
  37 + Element.makePositioned(element); // fix IE
  38 + options.element = element;
  39 +
  40 + this.drops.push(options);
  41 + },
  42 +
  43 + findDeepestChild: function(drops) {
  44 + deepest = drops[0];
  45 +
  46 + for (i = 1; i < drops.length; ++i)
  47 + if (Element.isParent(drops[i].element, deepest.element))
  48 + deepest = drops[i];
  49 +
  50 + return deepest;
  51 + },
  52 +
  53 + isContained: function(element, drop) {
  54 + var containmentNode;
  55 + if(drop.tree) {
  56 + containmentNode = element.treeNode;
  57 + } else {
  58 + containmentNode = element.parentNode;
  59 + }
  60 + return drop._containers.detect(function(c) { return containmentNode == c });
  61 + },
  62 +
  63 + isAffected: function(point, element, drop) {
  64 + return (
  65 + (drop.element!=element) &&
  66 + ((!drop._containers) ||
  67 + this.isContained(element, drop)) &&
  68 + ((!drop.accept) ||
  69 + (Element.classNames(element).detect(
  70 + function(v) { return drop.accept.include(v) } ) )) &&
  71 + Position.within(drop.element, point[0], point[1]) );
  72 + },
  73 +
  74 + deactivate: function(drop) {
  75 + if(drop.hoverclass)
  76 + Element.removeClassName(drop.element, drop.hoverclass);
  77 + this.last_active = null;
  78 + },
  79 +
  80 + activate: function(drop) {
  81 + if(drop.hoverclass)
  82 + Element.addClassName(drop.element, drop.hoverclass);
  83 + this.last_active = drop;
  84 + },
  85 +
  86 + show: function(point, element) {
  87 + if(!this.drops.length) return;
  88 + var affected = [];
  89 +
  90 + if(this.last_active) this.deactivate(this.last_active);
  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 + Position.within(drop.element, point[0], point[1]);
  99 + if(drop.onHover)
  100 + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
  101 +
  102 + Droppables.activate(drop);
  103 + }
  104 + },
  105 +
  106 + fire: function(event, element) {
  107 + if(!this.last_active) return;
  108 + Position.prepare();
  109 +
  110 + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
  111 + if (this.last_active.onDrop)
  112 + this.last_active.onDrop(element, this.last_active.element, event);
  113 + },
  114 +
  115 + reset: function() {
  116 + if(this.last_active)
  117 + this.deactivate(this.last_active);
  118 + }
  119 +}
  120 +
  121 +var Draggables = {
  122 + drags: [],
  123 + observers: [],
  124 +
  125 + register: function(draggable) {
  126 + if(this.drags.length == 0) {
  127 + this.eventMouseUp = this.endDrag.bindAsEventListener(this);
  128 + this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
  129 + this.eventKeypress = this.keyPress.bindAsEventListener(this);
  130 +
  131 + Event.observe(document, "mouseup", this.eventMouseUp);
  132 + Event.observe(document, "mousemove", this.eventMouseMove);
  133 + Event.observe(document, "keypress", this.eventKeypress);
  134 + }
  135 + this.drags.push(draggable);
  136 + },
  137 +
  138 + unregister: function(draggable) {
  139 + this.drags = this.drags.reject(function(d) { return d==draggable });
  140 + if(this.drags.length == 0) {
  141 + Event.stopObserving(document, "mouseup", this.eventMouseUp);
  142 + Event.stopObserving(document, "mousemove", this.eventMouseMove);
  143 + Event.stopObserving(document, "keypress", this.eventKeypress);
  144 + }
  145 + },
  146 +
  147 + activate: function(draggable) {
  148 + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
  149 + this.activeDraggable = draggable;
  150 + },
  151 +
  152 + deactivate: function() {
  153 + this.activeDraggable = null;
  154 + },
  155 +
  156 + updateDrag: function(event) {
  157 + if(!this.activeDraggable) return;
  158 + var pointer = [Event.pointerX(event), Event.pointerY(event)];
  159 + // Mozilla-based browsers fire successive mousemove events with
  160 + // the same coordinates, prevent needless redrawing (moz bug?)
  161 + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
  162 + this._lastPointer = pointer;
  163 + this.activeDraggable.updateDrag(event, pointer);
  164 + },
  165 +
  166 + endDrag: function(event) {
  167 + if(!this.activeDraggable) return;
  168 + this._lastPointer = null;
  169 + this.activeDraggable.endDrag(event);
  170 + this.activeDraggable = null;
  171 + },
  172 +
  173 + keyPress: function(event) {
  174 + if(this.activeDraggable)
  175 + this.activeDraggable.keyPress(event);
  176 + },
  177 +
  178 + addObserver: function(observer) {
  179 + this.observers.push(observer);
  180 + this._cacheObserverCallbacks();
  181 + },
  182 +
  183 + removeObserver: function(element) { // element instead of observer fixes mem leaks
  184 + this.observers = this.observers.reject( function(o) { return o.element==element });
  185 + this._cacheObserverCallbacks();
  186 + },
  187 +
  188 + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
  189 + if(this[eventName+'Count'] > 0)
  190 + this.observers.each( function(o) {
  191 + if(o[eventName]) o[eventName](eventName, draggable, event);
  192 + });
  193 + },
  194 +
  195 + _cacheObserverCallbacks: function() {
  196 + ['onStart','onEnd','onDrag'].each( function(eventName) {
  197 + Draggables[eventName+'Count'] = Draggables.observers.select(
  198 + function(o) { return o[eventName]; }
  199 + ).length;
  200 + });
  201 + }
  202 +}
  203 +
  204 +/*--------------------------------------------------------------------------*/
  205 +
  206 +var Draggable = Class.create();
  207 +Draggable.prototype = {
  208 + initialize: function(element) {
  209 + var options = Object.extend({
  210 + handle: false,
  211 + starteffect: function(element) {
  212 + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
  213 + },
  214 + reverteffect: function(element, top_offset, left_offset) {
  215 + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
  216 + element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
  217 + },
  218 + endeffect: function(element) {
  219 + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
  220 + },
  221 + zindex: 1000,
  222 + revert: false,
  223 + scroll: false,
  224 + scrollSensitivity: 20,
  225 + scrollSpeed: 15,
  226 + snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
  227 + }, arguments[1] || {});
  228 +
  229 + this.element = $(element);
  230 +
  231 + if(options.handle && (typeof options.handle == 'string')) {
  232 + var h = Element.childrenWithClassName(this.element, options.handle, true);
  233 + if(h.length>0) this.handle = h[0];
  234 + }
  235 + if(!this.handle) this.handle = $(options.handle);
  236 + if(!this.handle) this.handle = this.element;
  237 +
  238 + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
  239 + options.scroll = $(options.scroll);
  240 +
  241 + Element.makePositioned(this.element); // fix IE
  242 +
  243 + this.delta = this.currentDelta();
  244 + this.options = options;
  245 + this.dragging = false;
  246 +
  247 + this.eventMouseDown = this.initDrag.bindAsEventListener(this);
  248 + Event.observe(this.handle, "mousedown", this.eventMouseDown);
  249 +
  250 + Draggables.register(this);
  251 + },
  252 +
  253 + destroy: function() {
  254 + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
  255 + Draggables.unregister(this);
  256 + },
  257 +
  258 + currentDelta: function() {
  259 + return([
  260 + parseInt(Element.getStyle(this.element,'left') || '0'),
  261 + parseInt(Element.getStyle(this.element,'top') || '0')]);
  262 + },
  263 +
  264 + initDrag: function(event) {
  265 + if(Event.isLeftClick(event)) {
  266 + // abort on form elements, fixes a Firefox issue
  267 + var src = Event.element(event);
  268 + if(src.tagName && (
  269 + src.tagName=='INPUT' ||
  270 + src.tagName=='SELECT' ||
  271 + src.tagName=='OPTION' ||
  272 + src.tagName=='BUTTON' ||
  273 + src.tagName=='TEXTAREA')) return;
  274 +
  275 + if(this.element._revert) {
  276 + this.element._revert.cancel();
  277 + this.element._revert = null;
  278 + }
  279 +
  280 + var pointer = [Event.pointerX(event), Event.pointerY(event)];
  281 + var pos = Position.cumulativeOffset(this.element);
  282 + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
  283 +
  284 + Draggables.activate(this);
  285 + Event.stop(event);
  286 + }
  287 + },
  288 +
  289 + startDrag: function(event) {
  290 + this.dragging = true;
  291 +
  292 + if(this.options.zindex) {
  293 + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
  294 + this.element.style.zIndex = this.options.zindex;
  295 + }
  296 +
  297 + if(this.options.ghosting) {
  298 + this._clone = this.element.cloneNode(true);
  299 + Position.absolutize(this.element);
  300 + this.element.parentNode.insertBefore(this._clone, this.element);
  301 + }
  302 +
  303 + if(this.options.scroll) {
  304 + if (this.options.scroll == window) {
  305 + var where = this._getWindowScroll(this.options.scroll);
  306 + this.originalScrollLeft = where.left;
  307 + this.originalScrollTop = where.top;
  308 + } else {
  309 + this.originalScrollLeft = this.options.scroll.scrollLeft;
  310 + this.originalScrollTop = this.options.scroll.scrollTop;
  311 + }
  312 + }
  313 +
  314 + Draggables.notify('onStart', this, event);
  315 + if(this.options.starteffect) this.options.starteffect(this.element);
  316 + },
  317 +
  318 + updateDrag: function(event, pointer) {
  319 + if(!this.dragging) this.startDrag(event);
  320 + Position.prepare();
  321 + Droppables.show(pointer, this.element);
  322 + Draggables.notify('onDrag', this, event);
  323 + this.draw(pointer);
  324 + if(this.options.change) this.options.change(this);
  325 +
  326 + if(this.options.scroll) {
  327 + this.stopScrolling();
  328 +
  329 + var p;
  330 + if (this.options.scroll == window) {
  331 + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
  332 + } else {
  333 + p = Position.page(this.options.scroll);
  334 + p[0] += this.options.scroll.scrollLeft;
  335 + p[1] += this.options.scroll.scrollTop;
  336 + p.push(p[0]+this.options.scroll.offsetWidth);
  337 + p.push(p[1]+this.options.scroll.offsetHeight);
  338 + }
  339 + var speed = [0,0];
  340 + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
  341 + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
  342 + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
  343 + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
  344 + this.startScrolling(speed);
  345 + }
  346 +
  347 + // fix AppleWebKit rendering
  348 + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
  349 +
  350 + Event.stop(event);
  351 + },
  352 +
  353 + finishDrag: function(event, success) {
  354 + this.dragging = false;
  355 +
  356 + if(this.options.ghosting) {
  357 + Position.relativize(this.element);
  358 + Element.remove(this._clone);
  359 + this._clone = null;
  360 + }
  361 +
  362 + if(success) Droppables.fire(event, this.element);
  363 + Draggables.notify('onEnd', this, event);
  364 +
  365 + var revert = this.options.revert;
  366 + if(revert && typeof revert == 'function') revert = revert(this.element);
  367 +
  368 + var d = this.currentDelta();
  369 + if(revert && this.options.reverteffect) {
  370 + this.options.reverteffect(this.element,
  371 + d[1]-this.delta[1], d[0]-this.delta[0]);
  372 + } else {
  373 + this.delta = d;
  374 + }
  375 +
  376 + if(this.options.zindex)
  377 + this.element.style.zIndex = this.originalZ;
  378 +
  379 + if(this.options.endeffect)
  380 + this.options.endeffect(this.element);
  381 +
  382 + Draggables.deactivate(this);
  383 + Droppables.reset();
  384 + },
  385 +
  386 + keyPress: function(event) {
  387 + if(event.keyCode!=Event.KEY_ESC) return;
  388 + this.finishDrag(event, false);
  389 + Event.stop(event);
  390 + },
  391 +
  392 + endDrag: function(event) {
  393 + if(!this.dragging) return;
  394 + this.stopScrolling();
  395 + this.finishDrag(event, true);
  396 + Event.stop(event);
  397 + },
  398 +
  399 + draw: function(point) {
  400 + var pos = Position.cumulativeOffset(this.element);
  401 + var d = this.currentDelta();
  402 + pos[0] -= d[0]; pos[1] -= d[1];
  403 +
  404 + if(this.options.scroll && (this.options.scroll != window)) {
  405 + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
  406 + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
  407 + }
  408 +
  409 + var p = [0,1].map(function(i){
  410 + return (point[i]-pos[i]-this.offset[i])
  411 + }.bind(this));
  412 +
  413 + if(this.options.snap) {
  414 + if(typeof this.options.snap == 'function') {
  415 + p = this.options.snap(p[0],p[1]);
  416 + } else {
  417 + if(this.options.snap instanceof Array) {
  418 + p = p.map( function(v, i) {
  419 + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
  420 + } else {
  421 + p = p.map( function(v) {
  422 + return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
  423 + }
  424 + }}
  425 +
  426 + var style = this.element.style;
  427 + if((!this.options.constraint) || (this.options.constraint=='horizontal'))
  428 + style.left = p[0] + "px";
  429 + if((!this.options.constraint) || (this.options.constraint=='vertical'))
  430 + style.top = p[1] + "px";
  431 + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  432 + },
  433 +
  434 + stopScrolling: function() {
  435 + if(this.scrollInterval) {
  436 + clearInterval(this.scrollInterval);
  437 + this.scrollInterval = null;
  438 + Draggables._lastScrollPointer = null;
  439 + }
  440 + },
  441 +
  442 + startScrolling: function(speed) {
  443 + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
  444 + this.lastScrolled = new Date();
  445 + this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  446 + },
  447 +
  448 + scroll: function() {
  449 + var current = new Date();
  450 + var delta = current - this.lastScrolled;
  451 + this.lastScrolled = current;
  452 + if(this.options.scroll == window) {
  453 + with (this._getWindowScroll(this.options.scroll)) {
  454 + if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
  455 + var d = delta / 1000;
  456 + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
  457 + }
  458 + }
  459 + } else {
  460 + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
  461 + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
  462 + }
  463 +
  464 + Position.prepare();
  465 + Droppables.show(Draggables._lastPointer, this.element);
  466 + Draggables.notify('onDrag', this);
  467 + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
  468 + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
  469 + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
  470 + if (Draggables._lastScrollPointer[0] < 0)
  471 + Draggables._lastScrollPointer[0] = 0;
  472 + if (Draggables._lastScrollPointer[1] < 0)
  473 + Draggables._lastScrollPointer[1] = 0;
  474 + this.draw(Draggables._lastScrollPointer);
  475 +
  476 + if(this.options.change) this.options.change(this);
  477 + },
  478 +
  479 + _getWindowScroll: function(w) {
  480 + var T, L, W, H;
  481 + with (w.document) {
  482 + if (w.document.documentElement && documentElement.scrollTop) {
  483 + T = documentElement.scrollTop;
  484 + L = documentElement.scrollLeft;
  485 + } else if (w.document.body) {
  486 + T = body.scrollTop;
  487 + L = body.scrollLeft;
  488 + }
  489 + if (w.innerWidth) {
  490 + W = w.innerWidth;
  491 + H = w.innerHeight;
  492 + } else if (w.document.documentElement && documentElement.clientWidth) {
  493 + W = documentElement.clientWidth;
  494 + H = documentElement.clientHeight;
  495 + } else {
  496 + W = body.offsetWidth;
  497 + H = body.offsetHeight
  498 + }
  499 + }
  500 + return { top: T, left: L, width: W, height: H };
  501 + }
  502 +}
  503 +
  504 +/*--------------------------------------------------------------------------*/
  505 +
  506 +var SortableObserver = Class.create();
  507 +SortableObserver.prototype = {
  508 + initialize: function(element, observer) {
  509 + this.element = $(element);
  510 + this.observer = observer;
  511 + this.lastValue = Sortable.serialize(this.element);
  512 + },
  513 +
  514 + onStart: function() {
  515 + this.lastValue = Sortable.serialize(this.element);
  516 + },
  517 +
  518 + onEnd: function() {
  519 + Sortable.unmark();
  520 + if(this.lastValue != Sortable.serialize(this.element))
  521 + this.observer(this.element)
  522 + }
  523 +}
  524 +
  525 +var Sortable = {
  526 + sortables: {},
  527 +
  528 + _findRootElement: function(element) {
  529 + while (element.tagName != "BODY") {
  530 + if(element.id && Sortable.sortables[element.id]) return element;
  531 + element = element.parentNode;
  532 + }
  533 + },
  534 +
  535 + options: function(element) {
  536 + element = Sortable._findRootElement($(element));
  537 + if(!element) return;
  538 + return Sortable.sortables[element.id];
  539 + },
  540 +
  541 + destroy: function(element){
  542 + var s = Sortable.options(element);
  543 +
  544 + if(s) {
  545 + Draggables.removeObserver(s.element);
  546 + s.droppables.each(function(d){ Droppables.remove(d) });
  547 + s.draggables.invoke('destroy');
  548 +
  549 + delete Sortable.sortables[s.element.id];
  550 + }
  551 + },
  552 +
  553 + create: function(element) {
  554 + element = $(element);
  555 + var options = Object.extend({
  556 + element: element,
  557 + tag: 'li', // assumes li children, override with tag: 'tagname'
  558 + dropOnEmpty: false,
  559 + tree: false,
  560 + treeTag: 'ul',
  561 + overlap: 'vertical', // one of 'vertical', 'horizontal'
  562 + constraint: 'vertical', // one of 'vertical', 'horizontal', false
  563 + containment: element, // also takes array of elements (or id's); or false
  564 + handle: false, // or a CSS class
  565 + only: false,
  566 + hoverclass: null,
  567 + ghosting: false,
  568 + scroll: false,
  569 + scrollSensitivity: 20,
  570 + scrollSpeed: 15,
  571 + format: /^[^_]*_(.*)$/,
  572 + onChange: Prototype.emptyFunction,
  573 + onUpdate: Prototype.emptyFunction
  574 + }, arguments[1] || {});
  575 +
  576 + // clear any old sortable with same element
  577 + this.destroy(element);
  578 +
  579 + // build options for the draggables
  580 + var options_for_draggable = {
  581 + revert: true,
  582 + scroll: options.scroll,
  583 + scrollSpeed: options.scrollSpeed,
  584 + scrollSensitivity: options.scrollSensitivity,
  585 + ghosting: options.ghosting,
  586 + constraint: options.constraint,
  587 + handle: options.handle };
  588 +
  589 + if(options.starteffect)
  590 + options_for_draggable.starteffect = options.starteffect;
  591 +
  592 + if(options.reverteffect)
  593 + options_for_draggable.reverteffect = options.reverteffect;
  594 + else
  595 + if(options.ghosting) options_for_draggable.reverteffect = function(element) {
  596 + element.style.top = 0;
  597 + element.style.left = 0;
  598 + };
  599 +
  600 + if(options.endeffect)
  601 + options_for_draggable.endeffect = options.endeffect;
  602 +
  603 + if(options.zindex)
  604 + options_for_draggable.zindex = options.zindex;
  605 +
  606 + // build options for the droppables
  607 + var options_for_droppable = {
  608 + overlap: options.overlap,
  609 + containment: options.containment,
  610 + tree: options.tree,
  611 + hoverclass: options.hoverclass,
  612 + onHover: Sortable.onHover
  613 + //greedy: !options.dropOnEmpty
  614 + }
  615 +
  616 + var options_for_tree = {
  617 + onHover: Sortable.onEmptyHover,
  618 + overlap: options.overlap,
  619 + containment: options.containment,
  620 + hoverclass: options.hoverclass
  621 + }
  622 +
  623 + // fix for gecko engine
  624 + Element.cleanWhitespace(element);
  625 +
  626 + options.draggables = [];
  627 + options.droppables = [];
  628 +
  629 + // drop on empty handling
  630 + if(options.dropOnEmpty || options.tree) {
  631 + Droppables.add(element, options_for_tree);
  632 + options.droppables.push(element);
  633 + }
  634 +
  635 + (this.findElements(element, options) || []).each( function(e) {
  636 + // handles are per-draggable
  637 + var handle = options.handle ?
  638 + Element.childrenWithClassName(e, options.handle)[0] : e;
  639 + options.draggables.push(
  640 + new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
  641 + Droppables.add(e, options_for_droppable);
  642 + if(options.tree) e.treeNode = element;
  643 + options.droppables.push(e);
  644 + });
  645 +
  646 + if(options.tree) {
  647 + (Sortable.findTreeElements(element, options) || []).each( function(e) {
  648 + Droppables.add(e, options_for_tree);
  649 + e.treeNode = element;
  650 + options.droppables.push(e);
  651 + });
  652 + }
  653 +
  654 + // keep reference
  655 + this.sortables[element.id] = options;
  656 +
  657 + // for onupdate
  658 + Draggables.addObserver(new SortableObserver(element, options.onUpdate));
  659 +
  660 + },
  661 +
  662 + // return all suitable-for-sortable elements in a guaranteed order
  663 + findElements: function(element, options) {
  664 + return Element.findChildren(
  665 + element, options.only, options.tree ? true : false, options.tag);
  666 + },
  667 +
  668 + findTreeElements: function(element, options) {
  669 + return Element.findChildren(
  670 + element, options.only, options.tree ? true : false, options.treeTag);
  671 + },
  672 +
  673 + onHover: function(element, dropon, overlap) {
  674 + if(Element.isParent(dropon, element)) return;
  675 +
  676 + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
  677 + return;
  678 + } else if(overlap>0.5) {
  679 + Sortable.mark(dropon, 'before');
  680 + if(dropon.previousSibling != element) {
  681 + var oldParentNode = element.parentNode;
  682 + element.style.visibility = "hidden"; // fix gecko rendering
  683 + dropon.parentNode.insertBefore(element, dropon);
  684 + if(dropon.parentNode!=oldParentNode)
  685 + Sortable.options(oldParentNode).onChange(element);
  686 + Sortable.options(dropon.parentNode).onChange(element);
  687 + }
  688 + } else {
  689 + Sortable.mark(dropon, 'after');
  690 + var nextElement = dropon.nextSibling || null;
  691 + if(nextElement != element) {
  692 + var oldParentNode = element.parentNode;
  693 + element.style.visibility = "hidden"; // fix gecko rendering
  694 + dropon.parentNode.insertBefore(element, nextElement);
  695 + if(dropon.parentNode!=oldParentNode)
  696 + Sortable.options(oldParentNode).onChange(element);
  697 + Sortable.options(dropon.parentNode).onChange(element);
  698 + }
  699 + }
  700 + },
  701 +
  702 + onEmptyHover: function(element, dropon, overlap) {
  703 + var oldParentNode = element.parentNode;
  704 + var droponOptions = Sortable.options(dropon);
  705 +
  706 + if(!Element.isParent(dropon, element)) {
  707 + var index;
  708 +
  709 + var children = Sortable.findElements(dropon, {tag: droponOptions.tag});
  710 + var child = null;
  711 +
  712 + if(children) {
  713 + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
  714 +
  715 + for (index = 0; index < children.length; index += 1) {
  716 + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
  717 + offset -= Element.offsetSize (children[index], droponOptions.overlap);
  718 + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
  719 + child = index + 1 < children.length ? children[index + 1] : null;
  720 + break;
  721 + } else {
  722 + child = children[index];
  723 + break;
  724 + }
  725 + }
  726 + }
  727 +
  728 + dropon.insertBefore(element, child);
  729 +
  730 + Sortable.options(oldParentNode).onChange(element);
  731 + droponOptions.onChange(element);
  732 + }
  733 + },
  734 +
  735 + unmark: function() {
  736 + if(Sortable._marker) Element.hide(Sortable._marker);
  737 + },
  738 +
  739 + mark: function(dropon, position) {
  740 + // mark on ghosting only
  741 + var sortable = Sortable.options(dropon.parentNode);
  742 + if(sortable && !sortable.ghosting) return;
  743 +
  744 + if(!Sortable._marker) {
  745 + Sortable._marker = $('dropmarker') || document.createElement('DIV');
  746 + Element.hide(Sortable._marker);
  747 + Element.addClassName(Sortable._marker, 'dropmarker');
  748 + Sortable._marker.style.position = 'absolute';
  749 + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
  750 + }
  751 + var offsets = Position.cumulativeOffset(dropon);
  752 + Sortable._marker.style.left = offsets[0] + 'px';
  753 + Sortable._marker.style.top = offsets[1] + 'px';
  754 +
  755 + if(position=='after')
  756 + if(sortable.overlap == 'horizontal')
  757 + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
  758 + else
  759 + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
  760 +
  761 + Element.show(Sortable._marker);
  762 + },
  763 +
  764 + _tree: function(element, options, parent) {
  765 + var children = Sortable.findElements(element, options) || [];
  766 +
  767 + for (var i = 0; i < children.length; ++i) {
  768 + var match = children[i].id.match(options.format);
  769 +
  770 + if (!match) continue;
  771 +
  772 + var child = {
  773 + id: encodeURIComponent(match ? match[1] : null),
  774 + element: element,
  775 + parent: parent,
  776 + children: new Array,
  777 + position: parent.children.length,
  778 + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
  779 + }
  780 +
  781 + /* Get the element containing the children and recurse over it */
  782 + if (child.container)
  783 + this._tree(child.container, options, child)
  784 +
  785 + parent.children.push (child);
  786 + }
  787 +
  788 + return parent;
  789 + },
  790 +
  791 + /* Finds the first element of the given tag type within a parent element.
  792 + Used for finding the first LI[ST] within a L[IST]I[TEM].*/
  793 + _findChildrenElement: function (element, containerTag) {
  794 + if (element && element.hasChildNodes)
  795 + for (var i = 0; i < element.childNodes.length; ++i)
  796 + if (element.childNodes[i].tagName == containerTag)
  797 + return element.childNodes[i];
  798 +
  799 + return null;
  800 + },
  801 +
  802 + tree: function(element) {
  803 + element = $(element);
  804 + var sortableOptions = this.options(element);
  805 + var options = Object.extend({
  806 + tag: sortableOptions.tag,
  807 + treeTag: sortableOptions.treeTag,
  808 + only: sortableOptions.only,
  809 + name: element.id,
  810 + format: sortableOptions.format
  811 + }, arguments[1] || {});
  812 +
  813 + var root = {
  814 + id: null,
  815 + parent: null,
  816 + children: new Array,
  817 + container: element,
  818 + position: 0
  819 + }
  820 +
  821 + return Sortable._tree (element, options, root);
  822 + },
  823 +
  824 + /* Construct a [i] index for a particular node */
  825 + _constructIndex: function(node) {
  826 + var index = '';
  827 + do {
  828 + if (node.id) index = '[' + node.position + ']' + index;
  829 + } while ((node = node.parent) != null);
  830 + return index;
  831 + },
  832 +
  833 + sequence: function(element) {
  834 + element = $(element);
  835 + var options = Object.extend(this.options(element), arguments[1] || {});
  836 +
  837 + return $(this.findElements(element, options) || []).map( function(item) {
  838 + return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
  839 + });
  840 + },
  841 +
  842 + setSequence: function(element, new_sequence) {
  843 + element = $(element);
  844 + var options = Object.extend(this.options(element), arguments[2] || {});
  845 +
  846 + var nodeMap = {};
  847 + this.findElements(element, options).each( function(n) {
  848 + if (n.id.match(options.format))
  849 + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
  850 + n.parentNode.removeChild(n);
  851 + });
  852 +
  853 + new_sequence.each(function(ident) {
  854 + var n = nodeMap[ident];
  855 + if (n) {
  856 + n[1].appendChild(n[0]);
  857 + delete nodeMap[ident];
  858 + }
  859 + });
  860 + },
  861 +
  862 + serialize: function(element) {
  863 + element = $(element);
  864 + var options = Object.extend(Sortable.options(element), arguments[1] || {});
  865 + var name = encodeURIComponent(
  866 + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
  867 +
  868 + if (options.tree) {
  869 + return Sortable.tree(element, arguments[1]).children.map( function (item) {
  870 + return [name + Sortable._constructIndex(item) + "=" +
  871 + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
  872 + }).flatten().join('&');
  873 + } else {
  874 + return Sortable.sequence(element, arguments[1]).map( function(item) {
  875 + return name + "[]=" + encodeURIComponent(item);
  876 + }).join('&');
  877 + }
  878 + }
  879 +}
  880 +
  881 +/* Returns true if child is contained within element */
  882 +Element.isParent = function(child, element) {
  883 + if (!child.parentNode || child == element) return false;
  884 +
  885 + if (child.parentNode == element) return true;
  886 +
  887 + return Element.isParent(child.parentNode, element);
  888 +}
  889 +
  890 +Element.findChildren = function(element, only, recursive, tagName) {
  891 + if(!element.hasChildNodes()) return null;
  892 + tagName = tagName.toUpperCase();
  893 + if(only) only = [only].flatten();
  894 + var elements = [];
  895 + $A(element.childNodes).each( function(e) {
  896 + if(e.tagName && e.tagName.toUpperCase()==tagName &&
  897 + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
  898 + elements.push(e);
  899 + if(recursive) {
  900 + var grandchildren = Element.findChildren(e, only, recursive, tagName);
  901 + if(grandchildren) elements.push(grandchildren);
  902 + }
  903 + });
  904 +
  905 + return (elements.length>0 ? elements.flatten() : []);
  906 +}
  907 +
  908 +Element.offsetSize = function (element, type) {
  909 + if (type == 'vertical' || type == 'height')
  910 + return element.offsetHeight;
  911 + else
  912 + return element.offsetWidth;
  913 +}
0 914 \ No newline at end of file
... ...
public/javascripts/effects.js 0 → 100644
  1 +++ a/public/javascripts/effects.js
... ... @@ -0,0 +1,958 @@
  1 +// Copyright (c) 2005 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 +// See scriptaculous.js for full license.
  8 +
  9 +// converts rgb() and #xxx to #xxxxxx format,
  10 +// returns self (or first argument) if not convertable
  11 +String.prototype.parseColor = function() {
  12 + var color = '#';
  13 + if(this.slice(0,4) == 'rgb(') {
  14 + var cols = this.slice(4,this.length-1).split(',');
  15 + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  16 + } else {
  17 + if(this.slice(0,1) == '#') {
  18 + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
  19 + if(this.length==7) color = this.toLowerCase();
  20 + }
  21 + }
  22 + return(color.length==7 ? color : (arguments[0] || this));
  23 +}
  24 +
  25 +/*--------------------------------------------------------------------------*/
  26 +
  27 +Element.collectTextNodes = function(element) {
  28 + return $A($(element).childNodes).collect( function(node) {
  29 + return (node.nodeType==3 ? node.nodeValue :
  30 + (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  31 + }).flatten().join('');
  32 +}
  33 +
  34 +Element.collectTextNodesIgnoreClass = function(element, className) {
  35 + return $A($(element).childNodes).collect( function(node) {
  36 + return (node.nodeType==3 ? node.nodeValue :
  37 + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
  38 + Element.collectTextNodesIgnoreClass(node, className) : ''));
  39 + }).flatten().join('');
  40 +}
  41 +
  42 +Element.setContentZoom = function(element, percent) {
  43 + element = $(element);
  44 + Element.setStyle(element, {fontSize: (percent/100) + 'em'});
  45 + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
  46 +}
  47 +
  48 +Element.getOpacity = function(element){
  49 + var opacity;
  50 + if (opacity = Element.getStyle(element, 'opacity'))
  51 + return parseFloat(opacity);
  52 + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
  53 + if(opacity[1]) return parseFloat(opacity[1]) / 100;
  54 + return 1.0;
  55 +}
  56 +
  57 +Element.setOpacity = function(element, value){
  58 + element= $(element);
  59 + if (value == 1){
  60 + Element.setStyle(element, { opacity:
  61 + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
  62 + 0.999999 : null });
  63 + if(/MSIE/.test(navigator.userAgent))
  64 + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
  65 + } else {
  66 + if(value < 0.00001) value = 0;
  67 + Element.setStyle(element, {opacity: value});
  68 + if(/MSIE/.test(navigator.userAgent))
  69 + Element.setStyle(element,
  70 + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
  71 + 'alpha(opacity='+value*100+')' });
  72 + }
  73 +}
  74 +
  75 +Element.getInlineOpacity = function(element){
  76 + return $(element).style.opacity || '';
  77 +}
  78 +
  79 +Element.childrenWithClassName = function(element, className, findFirst) {
  80 + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
  81 + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
  82 + return (c.className && c.className.match(classNameRegExp));
  83 + });
  84 + if(!results) results = [];
  85 + return results;
  86 +}
  87 +
  88 +Element.forceRerendering = function(element) {
  89 + try {
  90 + element = $(element);
  91 + var n = document.createTextNode(' ');
  92 + element.appendChild(n);
  93 + element.removeChild(n);
  94 + } catch(e) { }
  95 +};
  96 +
  97 +/*--------------------------------------------------------------------------*/
  98 +
  99 +Array.prototype.call = function() {
  100 + var args = arguments;
  101 + this.each(function(f){ f.apply(this, args) });
  102 +}
  103 +
  104 +/*--------------------------------------------------------------------------*/
  105 +
  106 +var Effect = {
  107 + tagifyText: function(element) {
  108 + var tagifyStyle = 'position:relative';
  109 + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
  110 + element = $(element);
  111 + $A(element.childNodes).each( function(child) {
  112 + if(child.nodeType==3) {
  113 + child.nodeValue.toArray().each( function(character) {
  114 + element.insertBefore(
  115 + Builder.node('span',{style: tagifyStyle},
  116 + character == ' ' ? String.fromCharCode(160) : character),
  117 + child);
  118 + });
  119 + Element.remove(child);
  120 + }
  121 + });
  122 + },
  123 + multiple: function(element, effect) {
  124 + var elements;
  125 + if(((typeof element == 'object') ||
  126 + (typeof element == 'function')) &&
  127 + (element.length))
  128 + elements = element;
  129 + else
  130 + elements = $(element).childNodes;
  131 +
  132 + var options = Object.extend({
  133 + speed: 0.1,
  134 + delay: 0.0
  135 + }, arguments[2] || {});
  136 + var masterDelay = options.delay;
  137 +
  138 + $A(elements).each( function(element, index) {
  139 + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
  140 + });
  141 + },
  142 + PAIRS: {
  143 + 'slide': ['SlideDown','SlideUp'],
  144 + 'blind': ['BlindDown','BlindUp'],
  145 + 'appear': ['Appear','Fade']
  146 + },
  147 + toggle: function(element, effect) {
  148 + element = $(element);
  149 + effect = (effect || 'appear').toLowerCase();
  150 + var options = Object.extend({
  151 + queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
  152 + }, arguments[2] || {});
  153 + Effect[element.visible() ?
  154 + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  155 + }
  156 +};
  157 +
  158 +var Effect2 = Effect; // deprecated
  159 +
  160 +/* ------------- transitions ------------- */
  161 +
  162 +Effect.Transitions = {}
  163 +
  164 +Effect.Transitions.linear = function(pos) {
  165 + return pos;
  166 +}
  167 +Effect.Transitions.sinoidal = function(pos) {
  168 + return (-Math.cos(pos*Math.PI)/2) + 0.5;
  169 +}
  170 +Effect.Transitions.reverse = function(pos) {
  171 + return 1-pos;
  172 +}
  173 +Effect.Transitions.flicker = function(pos) {
  174 + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
  175 +}
  176 +Effect.Transitions.wobble = function(pos) {
  177 + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
  178 +}
  179 +Effect.Transitions.pulse = function(pos) {
  180 + return (Math.floor(pos*10) % 2 == 0 ?
  181 + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
  182 +}
  183 +Effect.Transitions.none = function(pos) {
  184 + return 0;
  185 +}
  186 +Effect.Transitions.full = function(pos) {
  187 + return 1;
  188 +}
  189 +
  190 +/* ------------- core effects ------------- */
  191 +
  192 +Effect.ScopedQueue = Class.create();
  193 +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
  194 + initialize: function() {
  195 + this.effects = [];
  196 + this.interval = null;
  197 + },
  198 + _each: function(iterator) {
  199 + this.effects._each(iterator);
  200 + },
  201 + add: function(effect) {
  202 + var timestamp = new Date().getTime();
  203 +
  204 + var position = (typeof effect.options.queue == 'string') ?
  205 + effect.options.queue : effect.options.queue.position;
  206 +
  207 + switch(position) {
  208 + case 'front':
  209 + // move unstarted effects after this effect
  210 + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
  211 + e.startOn += effect.finishOn;
  212 + e.finishOn += effect.finishOn;
  213 + });
  214 + break;
  215 + case 'end':
  216 + // start effect after last queued effect has finished
  217 + timestamp = this.effects.pluck('finishOn').max() || timestamp;
  218 + break;
  219 + }
  220 +
  221 + effect.startOn += timestamp;
  222 + effect.finishOn += timestamp;
  223 +
  224 + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
  225 + this.effects.push(effect);
  226 +
  227 + if(!this.interval)
  228 + this.interval = setInterval(this.loop.bind(this), 40);
  229 + },
  230 + remove: function(effect) {
  231 + this.effects = this.effects.reject(function(e) { return e==effect });
  232 + if(this.effects.length == 0) {
  233 + clearInterval(this.interval);
  234 + this.interval = null;
  235 + }
  236 + },
  237 + loop: function() {
  238 + var timePos = new Date().getTime();
  239 + this.effects.invoke('loop', timePos);
  240 + }
  241 +});
  242 +
  243 +Effect.Queues = {
  244 + instances: $H(),
  245 + get: function(queueName) {
  246 + if(typeof queueName != 'string') return queueName;
  247 +
  248 + if(!this.instances[queueName])
  249 + this.instances[queueName] = new Effect.ScopedQueue();
  250 +
  251 + return this.instances[queueName];
  252 + }
  253 +}
  254 +Effect.Queue = Effect.Queues.get('global');
  255 +
  256 +Effect.DefaultOptions = {
  257 + transition: Effect.Transitions.sinoidal,
  258 + duration: 1.0, // seconds
  259 + fps: 25.0, // max. 25fps due to Effect.Queue implementation
  260 + sync: false, // true for combining
  261 + from: 0.0,
  262 + to: 1.0,
  263 + delay: 0.0,
  264 + queue: 'parallel'
  265 +}
  266 +
  267 +Effect.Base = function() {};
  268 +Effect.Base.prototype = {
  269 + position: null,
  270 + start: function(options) {
  271 + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
  272 + this.currentFrame = 0;
  273 + this.state = 'idle';
  274 + this.startOn = this.options.delay*1000;
  275 + this.finishOn = this.startOn + (this.options.duration*1000);
  276 + this.event('beforeStart');
  277 + if(!this.options.sync)
  278 + Effect.Queues.get(typeof this.options.queue == 'string' ?
  279 + 'global' : this.options.queue.scope).add(this);
  280 + },
  281 + loop: function(timePos) {
  282 + if(timePos >= this.startOn) {
  283 + if(timePos >= this.finishOn) {
  284 + this.render(1.0);
  285 + this.cancel();
  286 + this.event('beforeFinish');
  287 + if(this.finish) this.finish();
  288 + this.event('afterFinish');
  289 + return;
  290 + }
  291 + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
  292 + var frame = Math.round(pos * this.options.fps * this.options.duration);
  293 + if(frame > this.currentFrame) {
  294 + this.render(pos);
  295 + this.currentFrame = frame;
  296 + }
  297 + }
  298 + },
  299 + render: function(pos) {
  300 + if(this.state == 'idle') {
  301 + this.state = 'running';
  302 + this.event('beforeSetup');
  303 + if(this.setup) this.setup();
  304 + this.event('afterSetup');
  305 + }
  306 + if(this.state == 'running') {
  307 + if(this.options.transition) pos = this.options.transition(pos);
  308 + pos *= (this.options.to-this.options.from);
  309 + pos += this.options.from;
  310 + this.position = pos;
  311 + this.event('beforeUpdate');
  312 + if(this.update) this.update(pos);
  313 + this.event('afterUpdate');
  314 + }
  315 + },
  316 + cancel: function() {
  317 + if(!this.options.sync)
  318 + Effect.Queues.get(typeof this.options.queue == 'string' ?
  319 + 'global' : this.options.queue.scope).remove(this);
  320 + this.state = 'finished';
  321 + },
  322 + event: function(eventName) {
  323 + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
  324 + if(this.options[eventName]) this.options[eventName](this);
  325 + },
  326 + inspect: function() {
  327 + return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
  328 + }
  329 +}
  330 +
  331 +Effect.Parallel = Class.create();
  332 +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
  333 + initialize: function(effects) {
  334 + this.effects = effects || [];
  335 + this.start(arguments[1]);
  336 + },
  337 + update: function(position) {
  338 + this.effects.invoke('render', position);
  339 + },
  340 + finish: function(position) {
  341 + this.effects.each( function(effect) {
  342 + effect.render(1.0);
  343 + effect.cancel();
  344 + effect.event('beforeFinish');
  345 + if(effect.finish) effect.finish(position);
  346 + effect.event('afterFinish');
  347 + });
  348 + }
  349 +});
  350 +
  351 +Effect.Opacity = Class.create();
  352 +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
  353 + initialize: function(element) {
  354 + this.element = $(element);
  355 + // make this work on IE on elements without 'layout'
  356 + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
  357 + this.element.setStyle({zoom: 1});
  358 + var options = Object.extend({
  359 + from: this.element.getOpacity() || 0.0,
  360 + to: 1.0
  361 + }, arguments[1] || {});
  362 + this.start(options);
  363 + },
  364 + update: function(position) {
  365 + this.element.setOpacity(position);
  366 + }
  367 +});
  368 +
  369 +Effect.Move = Class.create();
  370 +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
  371 + initialize: function(element) {
  372 + this.element = $(element);
  373 + var options = Object.extend({
  374 + x: 0,
  375 + y: 0,
  376 + mode: 'relative'
  377 + }, arguments[1] || {});
  378 + this.start(options);
  379 + },
  380 + setup: function() {
  381 + // Bug in Opera: Opera returns the "real" position of a static element or
  382 + // relative element that does not have top/left explicitly set.
  383 + // ==> Always set top and left for position relative elements in your stylesheets
  384 + // (to 0 if you do not need them)
  385 + this.element.makePositioned();
  386 + this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
  387 + this.originalTop = parseFloat(this.element.getStyle('top') || '0');
  388 + if(this.options.mode == 'absolute') {
  389 + // absolute movement, so we need to calc deltaX and deltaY
  390 + this.options.x = this.options.x - this.originalLeft;
  391 + this.options.y = this.options.y - this.originalTop;
  392 + }
  393 + },
  394 + update: function(position) {
  395 + this.element.setStyle({
  396 + left: this.options.x * position + this.originalLeft + 'px',
  397 + top: this.options.y * position + this.originalTop + 'px'
  398 + });
  399 + }
  400 +});
  401 +
  402 +// for backwards compatibility
  403 +Effect.MoveBy = function(element, toTop, toLeft) {
  404 + return new Effect.Move(element,
  405 + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
  406 +};
  407 +
  408 +Effect.Scale = Class.create();
  409 +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
  410 + initialize: function(element, percent) {
  411 + this.element = $(element)
  412 + var options = Object.extend({
  413 + scaleX: true,
  414 + scaleY: true,
  415 + scaleContent: true,
  416 + scaleFromCenter: false,
  417 + scaleMode: 'box', // 'box' or 'contents' or {} with provided values
  418 + scaleFrom: 100.0,
  419 + scaleTo: percent
  420 + }, arguments[2] || {});
  421 + this.start(options);
  422 + },
  423 + setup: function() {
  424 + this.restoreAfterFinish = this.options.restoreAfterFinish || false;
  425 + this.elementPositioning = this.element.getStyle('position');
  426 +
  427 + this.originalStyle = {};
  428 + ['top','left','width','height','fontSize'].each( function(k) {
  429 + this.originalStyle[k] = this.element.style[k];
  430 + }.bind(this));
  431 +
  432 + this.originalTop = this.element.offsetTop;
  433 + this.originalLeft = this.element.offsetLeft;
  434 +
  435 + var fontSize = this.element.getStyle('font-size') || '100%';
  436 + ['em','px','%'].each( function(fontSizeType) {
  437 + if(fontSize.indexOf(fontSizeType)>0) {
  438 + this.fontSize = parseFloat(fontSize);
  439 + this.fontSizeType = fontSizeType;
  440 + }
  441 + }.bind(this));
  442 +
  443 + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
  444 +
  445 + this.dims = null;
  446 + if(this.options.scaleMode=='box')
  447 + this.dims = [this.element.offsetHeight, this.element.offsetWidth];
  448 + if(/^content/.test(this.options.scaleMode))
  449 + this.dims = [this.element.scrollHeight, this.element.scrollWidth];
  450 + if(!this.dims)
  451 + this.dims = [this.options.scaleMode.originalHeight,
  452 + this.options.scaleMode.originalWidth];
  453 + },
  454 + update: function(position) {
  455 + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
  456 + if(this.options.scaleContent && this.fontSize)
  457 + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
  458 + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  459 + },
  460 + finish: function(position) {
  461 + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  462 + },
  463 + setDimensions: function(height, width) {
  464 + var d = {};
  465 + if(this.options.scaleX) d.width = width + 'px';
  466 + if(this.options.scaleY) d.height = height + 'px';
  467 + if(this.options.scaleFromCenter) {
  468 + var topd = (height - this.dims[0])/2;
  469 + var leftd = (width - this.dims[1])/2;
  470 + if(this.elementPositioning == 'absolute') {
  471 + if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
  472 + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
  473 + } else {
  474 + if(this.options.scaleY) d.top = -topd + 'px';
  475 + if(this.options.scaleX) d.left = -leftd + 'px';
  476 + }
  477 + }
  478 + this.element.setStyle(d);
  479 + }
  480 +});
  481 +
  482 +Effect.Highlight = Class.create();
  483 +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
  484 + initialize: function(element) {
  485 + this.element = $(element);
  486 + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
  487 + this.start(options);
  488 + },
  489 + setup: function() {
  490 + // Prevent executing on elements not in the layout flow
  491 + if(this.element.getStyle('display')=='none') { this.cancel(); return; }
  492 + // Disable background image during the effect
  493 + this.oldStyle = {
  494 + backgroundImage: this.element.getStyle('background-image') };
  495 + this.element.setStyle({backgroundImage: 'none'});
  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+(Math.round(this._base[i]+(this._delta[i]*position)).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 = Class.create();
  516 +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
  517 + initialize: function(element) {
  518 + this.element = $(element);
  519 + this.start(arguments[1] || {});
  520 + },
  521 + setup: function() {
  522 + Position.prepare();
  523 + var offsets = Position.cumulativeOffset(this.element);
  524 + if(this.options.offset) offsets[1] += this.options.offset;
  525 + var max = window.innerHeight ?
  526 + window.height - window.innerHeight :
  527 + document.body.scrollHeight -
  528 + (document.documentElement.clientHeight ?
  529 + document.documentElement.clientHeight : document.body.clientHeight);
  530 + this.scrollStart = Position.deltaY;
  531 + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
  532 + },
  533 + update: function(position) {
  534 + Position.prepare();
  535 + window.scrollTo(Position.deltaX,
  536 + this.scrollStart + (position*this.delta));
  537 + }
  538 +});
  539 +
  540 +/* ------------- combination effects ------------- */
  541 +
  542 +Effect.Fade = function(element) {
  543 + element = $(element);
  544 + var oldOpacity = element.getInlineOpacity();
  545 + var options = Object.extend({
  546 + from: element.getOpacity() || 1.0,
  547 + to: 0.0,
  548 + afterFinishInternal: function(effect) {
  549 + if(effect.options.to!=0) return;
  550 + effect.element.hide();
  551 + effect.element.setStyle({opacity: oldOpacity});
  552 + }}, arguments[1] || {});
  553 + return new Effect.Opacity(element,options);
  554 +}
  555 +
  556 +Effect.Appear = function(element) {
  557 + element = $(element);
  558 + var options = Object.extend({
  559 + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  560 + to: 1.0,
  561 + // force Safari to render floated elements properly
  562 + afterFinishInternal: function(effect) {
  563 + effect.element.forceRerendering();
  564 + },
  565 + beforeSetup: function(effect) {
  566 + effect.element.setOpacity(effect.options.from);
  567 + effect.element.show();
  568 + }}, arguments[1] || {});
  569 + return new Effect.Opacity(element,options);
  570 +}
  571 +
  572 +Effect.Puff = function(element) {
  573 + element = $(element);
  574 + var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
  575 + return new Effect.Parallel(
  576 + [ new Effect.Scale(element, 200,
  577 + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
  578 + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
  579 + Object.extend({ duration: 1.0,
  580 + beforeSetupInternal: function(effect) {
  581 + effect.effects[0].element.setStyle({position: 'absolute'}); },
  582 + afterFinishInternal: function(effect) {
  583 + effect.effects[0].element.hide();
  584 + effect.effects[0].element.setStyle(oldStyle); }
  585 + }, arguments[1] || {})
  586 + );
  587 +}
  588 +
  589 +Effect.BlindUp = function(element) {
  590 + element = $(element);
  591 + element.makeClipping();
  592 + return new Effect.Scale(element, 0,
  593 + Object.extend({ scaleContent: false,
  594 + scaleX: false,
  595 + restoreAfterFinish: true,
  596 + afterFinishInternal: function(effect) {
  597 + effect.element.hide();
  598 + effect.element.undoClipping();
  599 + }
  600 + }, arguments[1] || {})
  601 + );
  602 +}
  603 +
  604 +Effect.BlindDown = function(element) {
  605 + element = $(element);
  606 + var elementDimensions = element.getDimensions();
  607 + return new Effect.Scale(element, 100,
  608 + Object.extend({ scaleContent: false,
  609 + scaleX: false,
  610 + scaleFrom: 0,
  611 + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  612 + restoreAfterFinish: true,
  613 + afterSetup: function(effect) {
  614 + effect.element.makeClipping();
  615 + effect.element.setStyle({height: '0px'});
  616 + effect.element.show();
  617 + },
  618 + afterFinishInternal: function(effect) {
  619 + effect.element.undoClipping();
  620 + }
  621 + }, arguments[1] || {})
  622 + );
  623 +}
  624 +
  625 +Effect.SwitchOff = function(element) {
  626 + element = $(element);
  627 + var oldOpacity = element.getInlineOpacity();
  628 + return new Effect.Appear(element, {
  629 + duration: 0.4,
  630 + from: 0,
  631 + transition: Effect.Transitions.flicker,
  632 + afterFinishInternal: function(effect) {
  633 + new Effect.Scale(effect.element, 1, {
  634 + duration: 0.3, scaleFromCenter: true,
  635 + scaleX: false, scaleContent: false, restoreAfterFinish: true,
  636 + beforeSetup: function(effect) {
  637 + effect.element.makePositioned();
  638 + effect.element.makeClipping();
  639 + },
  640 + afterFinishInternal: function(effect) {
  641 + effect.element.hide();
  642 + effect.element.undoClipping();
  643 + effect.element.undoPositioned();
  644 + effect.element.setStyle({opacity: oldOpacity});
  645 + }
  646 + })
  647 + }
  648 + });
  649 +}
  650 +
  651 +Effect.DropOut = function(element) {
  652 + element = $(element);
  653 + var oldStyle = {
  654 + top: element.getStyle('top'),
  655 + left: element.getStyle('left'),
  656 + opacity: element.getInlineOpacity() };
  657 + return new Effect.Parallel(
  658 + [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
  659 + new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
  660 + Object.extend(
  661 + { duration: 0.5,
  662 + beforeSetup: function(effect) {
  663 + effect.effects[0].element.makePositioned();
  664 + },
  665 + afterFinishInternal: function(effect) {
  666 + effect.effects[0].element.hide();
  667 + effect.effects[0].element.undoPositioned();
  668 + effect.effects[0].element.setStyle(oldStyle);
  669 + }
  670 + }, arguments[1] || {}));
  671 +}
  672 +
  673 +Effect.Shake = function(element) {
  674 + element = $(element);
  675 + var oldStyle = {
  676 + top: element.getStyle('top'),
  677 + left: element.getStyle('left') };
  678 + return new Effect.Move(element,
  679 + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
  680 + new Effect.Move(effect.element,
  681 + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
  682 + new Effect.Move(effect.element,
  683 + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
  684 + new Effect.Move(effect.element,
  685 + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
  686 + new Effect.Move(effect.element,
  687 + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
  688 + new Effect.Move(effect.element,
  689 + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
  690 + effect.element.undoPositioned();
  691 + effect.element.setStyle(oldStyle);
  692 + }}) }}) }}) }}) }}) }});
  693 +}
  694 +
  695 +Effect.SlideDown = function(element) {
  696 + element = $(element);
  697 + element.cleanWhitespace();
  698 + // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  699 + var oldInnerBottom = $(element.firstChild).getStyle('bottom');
  700 + var elementDimensions = element.getDimensions();
  701 + return new Effect.Scale(element, 100, Object.extend({
  702 + scaleContent: false,
  703 + scaleX: false,
  704 + scaleFrom: 0,
  705 + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  706 + restoreAfterFinish: true,
  707 + afterSetup: function(effect) {
  708 + effect.element.makePositioned();
  709 + effect.element.firstChild.makePositioned();
  710 + if(window.opera) effect.element.setStyle({top: ''});
  711 + effect.element.makeClipping();
  712 + effect.element.setStyle({height: '0px'});
  713 + effect.element.show(); },
  714 + afterUpdateInternal: function(effect) {
  715 + effect.element.firstChild.setStyle({bottom:
  716 + (effect.dims[0] - effect.element.clientHeight) + 'px' });
  717 + },
  718 + afterFinishInternal: function(effect) {
  719 + effect.element.undoClipping();
  720 + // IE will crash if child is undoPositioned first
  721 + if(/MSIE/.test(navigator.userAgent)){
  722 + effect.element.undoPositioned();
  723 + effect.element.firstChild.undoPositioned();
  724 + }else{
  725 + effect.element.firstChild.undoPositioned();
  726 + effect.element.undoPositioned();
  727 + }
  728 + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
  729 + }, arguments[1] || {})
  730 + );
  731 +}
  732 +
  733 +Effect.SlideUp = function(element) {
  734 + element = $(element);
  735 + element.cleanWhitespace();
  736 + var oldInnerBottom = $(element.firstChild).getStyle('bottom');
  737 + return new Effect.Scale(element, 0,
  738 + Object.extend({ scaleContent: false,
  739 + scaleX: false,
  740 + scaleMode: 'box',
  741 + scaleFrom: 100,
  742 + restoreAfterFinish: true,
  743 + beforeStartInternal: function(effect) {
  744 + effect.element.makePositioned();
  745 + effect.element.firstChild.makePositioned();
  746 + if(window.opera) effect.element.setStyle({top: ''});
  747 + effect.element.makeClipping();
  748 + effect.element.show(); },
  749 + afterUpdateInternal: function(effect) {
  750 + effect.element.firstChild.setStyle({bottom:
  751 + (effect.dims[0] - effect.element.clientHeight) + 'px' }); },
  752 + afterFinishInternal: function(effect) {
  753 + effect.element.hide();
  754 + effect.element.undoClipping();
  755 + effect.element.firstChild.undoPositioned();
  756 + effect.element.undoPositioned();
  757 + effect.element.setStyle({bottom: oldInnerBottom}); }
  758 + }, arguments[1] || {})
  759 + );
  760 +}
  761 +
  762 +// Bug in opera makes the TD containing this element expand for a instance after finish
  763 +Effect.Squish = function(element) {
  764 + return new Effect.Scale(element, window.opera ? 1 : 0,
  765 + { restoreAfterFinish: true,
  766 + beforeSetup: function(effect) {
  767 + effect.element.makeClipping(effect.element); },
  768 + afterFinishInternal: function(effect) {
  769 + effect.element.hide(effect.element);
  770 + effect.element.undoClipping(effect.element); }
  771 + });
  772 +}
  773 +
  774 +Effect.Grow = function(element) {
  775 + element = $(element);
  776 + var options = Object.extend({
  777 + direction: 'center',
  778 + moveTransition: Effect.Transitions.sinoidal,
  779 + scaleTransition: Effect.Transitions.sinoidal,
  780 + opacityTransition: Effect.Transitions.full
  781 + }, arguments[1] || {});
  782 + var oldStyle = {
  783 + top: element.style.top,
  784 + left: element.style.left,
  785 + height: element.style.height,
  786 + width: element.style.width,
  787 + opacity: element.getInlineOpacity() };
  788 +
  789 + var dims = element.getDimensions();
  790 + var initialMoveX, initialMoveY;
  791 + var moveX, moveY;
  792 +
  793 + switch (options.direction) {
  794 + case 'top-left':
  795 + initialMoveX = initialMoveY = moveX = moveY = 0;
  796 + break;
  797 + case 'top-right':
  798 + initialMoveX = dims.width;
  799 + initialMoveY = moveY = 0;
  800 + moveX = -dims.width;
  801 + break;
  802 + case 'bottom-left':
  803 + initialMoveX = moveX = 0;
  804 + initialMoveY = dims.height;
  805 + moveY = -dims.height;
  806 + break;
  807 + case 'bottom-right':
  808 + initialMoveX = dims.width;
  809 + initialMoveY = dims.height;
  810 + moveX = -dims.width;
  811 + moveY = -dims.height;
  812 + break;
  813 + case 'center':
  814 + initialMoveX = dims.width / 2;
  815 + initialMoveY = dims.height / 2;
  816 + moveX = -dims.width / 2;
  817 + moveY = -dims.height / 2;
  818 + break;
  819 + }
  820 +
  821 + return new Effect.Move(element, {
  822 + x: initialMoveX,
  823 + y: initialMoveY,
  824 + duration: 0.01,
  825 + beforeSetup: function(effect) {
  826 + effect.element.hide();
  827 + effect.element.makeClipping();
  828 + effect.element.makePositioned();
  829 + },
  830 + afterFinishInternal: function(effect) {
  831 + new Effect.Parallel(
  832 + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
  833 + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
  834 + new Effect.Scale(effect.element, 100, {
  835 + scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
  836 + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
  837 + ], Object.extend({
  838 + beforeSetup: function(effect) {
  839 + effect.effects[0].element.setStyle({height: '0px'});
  840 + effect.effects[0].element.show();
  841 + },
  842 + afterFinishInternal: function(effect) {
  843 + effect.effects[0].element.undoClipping();
  844 + effect.effects[0].element.undoPositioned();
  845 + effect.effects[0].element.setStyle(oldStyle);
  846 + }
  847 + }, options)
  848 + )
  849 + }
  850 + });
  851 +}
  852 +
  853 +Effect.Shrink = function(element) {
  854 + element = $(element);
  855 + var options = Object.extend({
  856 + direction: 'center',
  857 + moveTransition: Effect.Transitions.sinoidal,
  858 + scaleTransition: Effect.Transitions.sinoidal,
  859 + opacityTransition: Effect.Transitions.none
  860 + }, arguments[1] || {});
  861 + var oldStyle = {
  862 + top: element.style.top,
  863 + left: element.style.left,
  864 + height: element.style.height,
  865 + width: element.style.width,
  866 + opacity: element.getInlineOpacity() };
  867 +
  868 + var dims = element.getDimensions();
  869 + var moveX, moveY;
  870 +
  871 + switch (options.direction) {
  872 + case 'top-left':
  873 + moveX = moveY = 0;
  874 + break;
  875 + case 'top-right':
  876 + moveX = dims.width;
  877 + moveY = 0;
  878 + break;
  879 + case 'bottom-left':
  880 + moveX = 0;
  881 + moveY = dims.height;
  882 + break;
  883 + case 'bottom-right':
  884 + moveX = dims.width;
  885 + moveY = dims.height;
  886 + break;
  887 + case 'center':
  888 + moveX = dims.width / 2;
  889 + moveY = dims.height / 2;
  890 + break;
  891 + }
  892 +
  893 + return new Effect.Parallel(
  894 + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
  895 + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
  896 + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
  897 + ], Object.extend({
  898 + beforeStartInternal: function(effect) {
  899 + effect.effects[0].element.makePositioned();
  900 + effect.effects[0].element.makeClipping(); },
  901 + afterFinishInternal: function(effect) {
  902 + effect.effects[0].element.hide();
  903 + effect.effects[0].element.undoClipping();
  904 + effect.effects[0].element.undoPositioned();
  905 + effect.effects[0].element.setStyle(oldStyle); }
  906 + }, options)
  907 + );
  908 +}
  909 +
  910 +Effect.Pulsate = function(element) {
  911 + element = $(element);
  912 + var options = arguments[1] || {};
  913 + var oldOpacity = element.getInlineOpacity();
  914 + var transition = options.transition || Effect.Transitions.sinoidal;
  915 + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
  916 + reverser.bind(transition);
  917 + return new Effect.Opacity(element,
  918 + Object.extend(Object.extend({ duration: 3.0, from: 0,
  919 + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
  920 + }, options), {transition: reverser}));
  921 +}
  922 +
  923 +Effect.Fold = function(element) {
  924 + element = $(element);
  925 + var oldStyle = {
  926 + top: element.style.top,
  927 + left: element.style.left,
  928 + width: element.style.width,
  929 + height: element.style.height };
  930 + Element.makeClipping(element);
  931 + return new Effect.Scale(element, 5, Object.extend({
  932 + scaleContent: false,
  933 + scaleX: false,
  934 + afterFinishInternal: function(effect) {
  935 + new Effect.Scale(element, 1, {
  936 + scaleContent: false,
  937 + scaleY: false,
  938 + afterFinishInternal: function(effect) {
  939 + effect.element.hide();
  940 + effect.element.undoClipping();
  941 + effect.element.setStyle(oldStyle);
  942 + } });
  943 + }}, arguments[1] || {}));
  944 +};
  945 +
  946 +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
  947 + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
  948 + function(f) { Element.Methods[f] = Element[f]; }
  949 +);
  950 +
  951 +Element.Methods.visualEffect = function(element, effect, options) {
  952 + s = effect.gsub(/_/, '-').camelize();
  953 + effect_class = s.charAt(0).toUpperCase() + s.substring(1);
  954 + new Effect[effect_class](element, options);
  955 + return $(element);
  956 +};
  957 +
  958 +Element.addMethods();
0 959 \ No newline at end of file
... ...
public/javascripts/prototype.js 0 → 100644
  1 +++ a/public/javascripts/prototype.js
... ... @@ -0,0 +1,2006 @@
  1 +/* Prototype JavaScript framework, version 1.5.0_rc0
  2 + * (c) 2005 Sam Stephenson <sam@conio.net>
  3 + *
  4 + * Prototype is freely distributable under the terms of an MIT-style license.
  5 + * For details, see the Prototype web site: http://prototype.conio.net/
  6 + *
  7 +/*--------------------------------------------------------------------------*/
  8 +
  9 +var Prototype = {
  10 + Version: '1.5.0_rc0',
  11 + ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
  12 +
  13 + emptyFunction: function() {},
  14 + K: function(x) {return x}
  15 +}
  16 +
  17 +var Class = {
  18 + create: function() {
  19 + return function() {
  20 + this.initialize.apply(this, arguments);
  21 + }
  22 + }
  23 +}
  24 +
  25 +var Abstract = new Object();
  26 +
  27 +Object.extend = function(destination, source) {
  28 + for (var property in source) {
  29 + destination[property] = source[property];
  30 + }
  31 + return destination;
  32 +}
  33 +
  34 +Object.inspect = function(object) {
  35 + try {
  36 + if (object == undefined) return 'undefined';
  37 + if (object == null) return 'null';
  38 + return object.inspect ? object.inspect() : object.toString();
  39 + } catch (e) {
  40 + if (e instanceof RangeError) return '...';
  41 + throw e;
  42 + }
  43 +}
  44 +
  45 +Function.prototype.bind = function() {
  46 + var __method = this, args = $A(arguments), object = args.shift();
  47 + return function() {
  48 + return __method.apply(object, args.concat($A(arguments)));
  49 + }
  50 +}
  51 +
  52 +Function.prototype.bindAsEventListener = function(object) {
  53 + var __method = this;
  54 + return function(event) {
  55 + return __method.call(object, event || window.event);
  56 + }
  57 +}
  58 +
  59 +Object.extend(Number.prototype, {
  60 + toColorPart: function() {
  61 + var digits = this.toString(16);
  62 + if (this < 16) return '0' + digits;
  63 + return digits;
  64 + },
  65 +
  66 + succ: function() {
  67 + return this + 1;
  68 + },
  69 +
  70 + times: function(iterator) {
  71 + $R(0, this, true).each(iterator);
  72 + return this;
  73 + }
  74 +});
  75 +
  76 +var Try = {
  77 + these: function() {
  78 + var returnValue;
  79 +
  80 + for (var i = 0; i < arguments.length; i++) {
  81 + var lambda = arguments[i];
  82 + try {
  83 + returnValue = lambda();
  84 + break;
  85 + } catch (e) {}
  86 + }
  87 +
  88 + return returnValue;
  89 + }
  90 +}
  91 +
  92 +/*--------------------------------------------------------------------------*/
  93 +
  94 +var PeriodicalExecuter = Class.create();
  95 +PeriodicalExecuter.prototype = {
  96 + initialize: function(callback, frequency) {
  97 + this.callback = callback;
  98 + this.frequency = frequency;
  99 + this.currentlyExecuting = false;
  100 +
  101 + this.registerCallback();
  102 + },
  103 +
  104 + registerCallback: function() {
  105 + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  106 + },
  107 +
  108 + onTimerEvent: function() {
  109 + if (!this.currentlyExecuting) {
  110 + try {
  111 + this.currentlyExecuting = true;
  112 + this.callback();
  113 + } finally {
  114 + this.currentlyExecuting = false;
  115 + }
  116 + }
  117 + }
  118 +}
  119 +Object.extend(String.prototype, {
  120 + gsub: function(pattern, replacement) {
  121 + var result = '', source = this, match;
  122 + replacement = arguments.callee.prepareReplacement(replacement);
  123 +
  124 + while (source.length > 0) {
  125 + if (match = source.match(pattern)) {
  126 + result += source.slice(0, match.index);
  127 + result += (replacement(match) || '').toString();
  128 + source = source.slice(match.index + match[0].length);
  129 + } else {
  130 + result += source, source = '';
  131 + }
  132 + }
  133 + return result;
  134 + },
  135 +
  136 + sub: function(pattern, replacement, count) {
  137 + replacement = this.gsub.prepareReplacement(replacement);
  138 + count = count === undefined ? 1 : count;
  139 +
  140 + return this.gsub(pattern, function(match) {
  141 + if (--count < 0) return match[0];
  142 + return replacement(match);
  143 + });
  144 + },
  145 +
  146 + scan: function(pattern, iterator) {
  147 + this.gsub(pattern, iterator);
  148 + return this;
  149 + },
  150 +
  151 + truncate: function(length, truncation) {
  152 + length = length || 30;
  153 + truncation = truncation === undefined ? '...' : truncation;
  154 + return this.length > length ?
  155 + this.slice(0, length - truncation.length) + truncation : this;
  156 + },
  157 +
  158 + strip: function() {
  159 + return this.replace(/^\s+/, '').replace(/\s+$/, '');
  160 + },
  161 +
  162 + stripTags: function() {
  163 + return this.replace(/<\/?[^>]+>/gi, '');
  164 + },
  165 +
  166 + stripScripts: function() {
  167 + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  168 + },
  169 +
  170 + extractScripts: function() {
  171 + var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
  172 + var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
  173 + return (this.match(matchAll) || []).map(function(scriptTag) {
  174 + return (scriptTag.match(matchOne) || ['', ''])[1];
  175 + });
  176 + },
  177 +
  178 + evalScripts: function() {
  179 + return this.extractScripts().map(function(script) { return eval(script) });
  180 + },
  181 +
  182 + escapeHTML: function() {
  183 + var div = document.createElement('div');
  184 + var text = document.createTextNode(this);
  185 + div.appendChild(text);
  186 + return div.innerHTML;
  187 + },
  188 +
  189 + unescapeHTML: function() {
  190 + var div = document.createElement('div');
  191 + div.innerHTML = this.stripTags();
  192 + return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
  193 + },
  194 +
  195 + toQueryParams: function() {
  196 + var pairs = this.match(/^\??(.*)$/)[1].split('&');
  197 + return pairs.inject({}, function(params, pairString) {
  198 + var pair = pairString.split('=');
  199 + params[pair[0]] = pair[1];
  200 + return params;
  201 + });
  202 + },
  203 +
  204 + toArray: function() {
  205 + return this.split('');
  206 + },
  207 +
  208 + camelize: function() {
  209 + var oStringList = this.split('-');
  210 + if (oStringList.length == 1) return oStringList[0];
  211 +
  212 + var camelizedString = this.indexOf('-') == 0
  213 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
  214 + : oStringList[0];
  215 +
  216 + for (var i = 1, len = oStringList.length; i < len; i++) {
  217 + var s = oStringList[i];
  218 + camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
  219 + }
  220 +
  221 + return camelizedString;
  222 + },
  223 +
  224 + inspect: function() {
  225 + return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'";
  226 + }
  227 +});
  228 +
  229 +String.prototype.gsub.prepareReplacement = function(replacement) {
  230 + if (typeof replacement == 'function') return replacement;
  231 + var template = new Template(replacement);
  232 + return function(match) { return template.evaluate(match) };
  233 +}
  234 +
  235 +String.prototype.parseQuery = String.prototype.toQueryParams;
  236 +
  237 +var Template = Class.create();
  238 +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
  239 +Template.prototype = {
  240 + initialize: function(template, pattern) {
  241 + this.template = template.toString();
  242 + this.pattern = pattern || Template.Pattern;
  243 + },
  244 +
  245 + evaluate: function(object) {
  246 + return this.template.gsub(this.pattern, function(match) {
  247 + var before = match[1];
  248 + if (before == '\\') return match[2];
  249 + return before + (object[match[3]] || '').toString();
  250 + });
  251 + }
  252 +}
  253 +
  254 +var $break = new Object();
  255 +var $continue = new Object();
  256 +
  257 +var Enumerable = {
  258 + each: function(iterator) {
  259 + var index = 0;
  260 + try {
  261 + this._each(function(value) {
  262 + try {
  263 + iterator(value, index++);
  264 + } catch (e) {
  265 + if (e != $continue) throw e;
  266 + }
  267 + });
  268 + } catch (e) {
  269 + if (e != $break) throw e;
  270 + }
  271 + },
  272 +
  273 + all: function(iterator) {
  274 + var result = true;
  275 + this.each(function(value, index) {
  276 + result = result && !!(iterator || Prototype.K)(value, index);
  277 + if (!result) throw $break;
  278 + });
  279 + return result;
  280 + },
  281 +
  282 + any: function(iterator) {
  283 + var result = true;
  284 + this.each(function(value, index) {
  285 + if (result = !!(iterator || Prototype.K)(value, index))
  286 + throw $break;
  287 + });
  288 + return result;
  289 + },
  290 +
  291 + collect: function(iterator) {
  292 + var results = [];
  293 + this.each(function(value, index) {
  294 + results.push(iterator(value, index));
  295 + });
  296 + return results;
  297 + },
  298 +
  299 + detect: function (iterator) {
  300 + var result;
  301 + this.each(function(value, index) {
  302 + if (iterator(value, index)) {
  303 + result = value;
  304 + throw $break;
  305 + }
  306 + });
  307 + return result;
  308 + },
  309 +
  310 + findAll: function(iterator) {
  311 + var results = [];
  312 + this.each(function(value, index) {
  313 + if (iterator(value, index))
  314 + results.push(value);
  315 + });
  316 + return results;
  317 + },
  318 +
  319 + grep: function(pattern, iterator) {
  320 + var results = [];
  321 + this.each(function(value, index) {
  322 + var stringValue = value.toString();
  323 + if (stringValue.match(pattern))
  324 + results.push((iterator || Prototype.K)(value, index));
  325 + })
  326 + return results;
  327 + },
  328 +
  329 + include: function(object) {
  330 + var found = false;
  331 + this.each(function(value) {
  332 + if (value == object) {
  333 + found = true;
  334 + throw $break;
  335 + }
  336 + });
  337 + return found;
  338 + },
  339 +
  340 + inject: function(memo, iterator) {
  341 + this.each(function(value, index) {
  342 + memo = iterator(memo, value, index);
  343 + });
  344 + return memo;
  345 + },
  346 +
  347 + invoke: function(method) {
  348 + var args = $A(arguments).slice(1);
  349 + return this.collect(function(value) {
  350 + return value[method].apply(value, args);
  351 + });
  352 + },
  353 +
  354 + max: function(iterator) {
  355 + var result;
  356 + this.each(function(value, index) {
  357 + value = (iterator || Prototype.K)(value, index);
  358 + if (result == undefined || value >= result)
  359 + result = value;
  360 + });
  361 + return result;
  362 + },
  363 +
  364 + min: function(iterator) {
  365 + var result;
  366 + this.each(function(value, index) {
  367 + value = (iterator || Prototype.K)(value, index);
  368 + if (result == undefined || value < result)
  369 + result = value;
  370 + });
  371 + return result;
  372 + },
  373 +
  374 + partition: function(iterator) {
  375 + var trues = [], falses = [];
  376 + this.each(function(value, index) {
  377 + ((iterator || Prototype.K)(value, index) ?
  378 + trues : falses).push(value);
  379 + });
  380 + return [trues, falses];
  381 + },
  382 +
  383 + pluck: function(property) {
  384 + var results = [];
  385 + this.each(function(value, index) {
  386 + results.push(value[property]);
  387 + });
  388 + return results;
  389 + },
  390 +
  391 + reject: function(iterator) {
  392 + var results = [];
  393 + this.each(function(value, index) {
  394 + if (!iterator(value, index))
  395 + results.push(value);
  396 + });
  397 + return results;
  398 + },
  399 +
  400 + sortBy: function(iterator) {
  401 + return this.collect(function(value, index) {
  402 + return {value: value, criteria: iterator(value, index)};
  403 + }).sort(function(left, right) {
  404 + var a = left.criteria, b = right.criteria;
  405 + return a < b ? -1 : a > b ? 1 : 0;
  406 + }).pluck('value');
  407 + },
  408 +
  409 + toArray: function() {
  410 + return this.collect(Prototype.K);
  411 + },
  412 +
  413 + zip: function() {
  414 + var iterator = Prototype.K, args = $A(arguments);
  415 + if (typeof args.last() == 'function')
  416 + iterator = args.pop();
  417 +
  418 + var collections = [this].concat(args).map($A);
  419 + return this.map(function(value, index) {
  420 + return iterator(collections.pluck(index));
  421 + });
  422 + },
  423 +
  424 + inspect: function() {
  425 + return '#<Enumerable:' + this.toArray().inspect() + '>';
  426 + }
  427 +}
  428 +
  429 +Object.extend(Enumerable, {
  430 + map: Enumerable.collect,
  431 + find: Enumerable.detect,
  432 + select: Enumerable.findAll,
  433 + member: Enumerable.include,
  434 + entries: Enumerable.toArray
  435 +});
  436 +var $A = Array.from = function(iterable) {
  437 + if (!iterable) return [];
  438 + if (iterable.toArray) {
  439 + return iterable.toArray();
  440 + } else {
  441 + var results = [];
  442 + for (var i = 0; i < iterable.length; i++)
  443 + results.push(iterable[i]);
  444 + return results;
  445 + }
  446 +}
  447 +
  448 +Object.extend(Array.prototype, Enumerable);
  449 +
  450 +if (!Array.prototype._reverse)
  451 + Array.prototype._reverse = Array.prototype.reverse;
  452 +
  453 +Object.extend(Array.prototype, {
  454 + _each: function(iterator) {
  455 + for (var i = 0; i < this.length; i++)
  456 + iterator(this[i]);
  457 + },
  458 +
  459 + clear: function() {
  460 + this.length = 0;
  461 + return this;
  462 + },
  463 +
  464 + first: function() {
  465 + return this[0];
  466 + },
  467 +
  468 + last: function() {
  469 + return this[this.length - 1];
  470 + },
  471 +
  472 + compact: function() {
  473 + return this.select(function(value) {
  474 + return value != undefined || value != null;
  475 + });
  476 + },
  477 +
  478 + flatten: function() {
  479 + return this.inject([], function(array, value) {
  480 + return array.concat(value && value.constructor == Array ?
  481 + value.flatten() : [value]);
  482 + });
  483 + },
  484 +
  485 + without: function() {
  486 + var values = $A(arguments);
  487 + return this.select(function(value) {
  488 + return !values.include(value);
  489 + });
  490 + },
  491 +
  492 + indexOf: function(object) {
  493 + for (var i = 0; i < this.length; i++)
  494 + if (this[i] == object) return i;
  495 + return -1;
  496 + },
  497 +
  498 + reverse: function(inline) {
  499 + return (inline !== false ? this : this.toArray())._reverse();
  500 + },
  501 +
  502 + inspect: function() {
  503 + return '[' + this.map(Object.inspect).join(', ') + ']';
  504 + }
  505 +});
  506 +var Hash = {
  507 + _each: function(iterator) {
  508 + for (var key in this) {
  509 + var value = this[key];
  510 + if (typeof value == 'function') continue;
  511 +
  512 + var pair = [key, value];
  513 + pair.key = key;
  514 + pair.value = value;
  515 + iterator(pair);
  516 + }
  517 + },
  518 +
  519 + keys: function() {
  520 + return this.pluck('key');
  521 + },
  522 +
  523 + values: function() {
  524 + return this.pluck('value');
  525 + },
  526 +
  527 + merge: function(hash) {
  528 + return $H(hash).inject($H(this), function(mergedHash, pair) {
  529 + mergedHash[pair.key] = pair.value;
  530 + return mergedHash;
  531 + });
  532 + },
  533 +
  534 + toQueryString: function() {
  535 + return this.map(function(pair) {
  536 + return pair.map(encodeURIComponent).join('=');
  537 + }).join('&');
  538 + },
  539 +
  540 + inspect: function() {
  541 + return '#<Hash:{' + this.map(function(pair) {
  542 + return pair.map(Object.inspect).join(': ');
  543 + }).join(', ') + '}>';
  544 + }
  545 +}
  546 +
  547 +function $H(object) {
  548 + var hash = Object.extend({}, object || {});
  549 + Object.extend(hash, Enumerable);
  550 + Object.extend(hash, Hash);
  551 + return hash;
  552 +}
  553 +ObjectRange = Class.create();
  554 +Object.extend(ObjectRange.prototype, Enumerable);
  555 +Object.extend(ObjectRange.prototype, {
  556 + initialize: function(start, end, exclusive) {
  557 + this.start = start;
  558 + this.end = end;
  559 + this.exclusive = exclusive;
  560 + },
  561 +
  562 + _each: function(iterator) {
  563 + var value = this.start;
  564 + do {
  565 + iterator(value);
  566 + value = value.succ();
  567 + } while (this.include(value));
  568 + },
  569 +
  570 + include: function(value) {
  571 + if (value < this.start)
  572 + return false;
  573 + if (this.exclusive)
  574 + return value < this.end;
  575 + return value <= this.end;
  576 + }
  577 +});
  578 +
  579 +var $R = function(start, end, exclusive) {
  580 + return new ObjectRange(start, end, exclusive);
  581 +}
  582 +
  583 +var Ajax = {
  584 + getTransport: function() {
  585 + return Try.these(
  586 + function() {return new XMLHttpRequest()},
  587 + function() {return new ActiveXObject('Msxml2.XMLHTTP')},
  588 + function() {return new ActiveXObject('Microsoft.XMLHTTP')}
  589 + ) || false;
  590 + },
  591 +
  592 + activeRequestCount: 0
  593 +}
  594 +
  595 +Ajax.Responders = {
  596 + responders: [],
  597 +
  598 + _each: function(iterator) {
  599 + this.responders._each(iterator);
  600 + },
  601 +
  602 + register: function(responderToAdd) {
  603 + if (!this.include(responderToAdd))
  604 + this.responders.push(responderToAdd);
  605 + },
  606 +
  607 + unregister: function(responderToRemove) {
  608 + this.responders = this.responders.without(responderToRemove);
  609 + },
  610 +
  611 + dispatch: function(callback, request, transport, json) {
  612 + this.each(function(responder) {
  613 + if (responder[callback] && typeof responder[callback] == 'function') {
  614 + try {
  615 + responder[callback].apply(responder, [request, transport, json]);
  616 + } catch (e) {}
  617 + }
  618 + });
  619 + }
  620 +};
  621 +
  622 +Object.extend(Ajax.Responders, Enumerable);
  623 +
  624 +Ajax.Responders.register({
  625 + onCreate: function() {
  626 + Ajax.activeRequestCount++;
  627 + },
  628 +
  629 + onComplete: function() {
  630 + Ajax.activeRequestCount--;
  631 + }
  632 +});
  633 +
  634 +Ajax.Base = function() {};
  635 +Ajax.Base.prototype = {
  636 + setOptions: function(options) {
  637 + this.options = {
  638 + method: 'post',
  639 + asynchronous: true,
  640 + contentType: 'application/x-www-form-urlencoded',
  641 + parameters: ''
  642 + }
  643 + Object.extend(this.options, options || {});
  644 + },
  645 +
  646 + responseIsSuccess: function() {
  647 + return this.transport.status == undefined
  648 + || this.transport.status == 0
  649 + || (this.transport.status >= 200 && this.transport.status < 300);
  650 + },
  651 +
  652 + responseIsFailure: function() {
  653 + return !this.responseIsSuccess();
  654 + }
  655 +}
  656 +
  657 +Ajax.Request = Class.create();
  658 +Ajax.Request.Events =
  659 + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
  660 +
  661 +Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  662 + initialize: function(url, options) {
  663 + this.transport = Ajax.getTransport();
  664 + this.setOptions(options);
  665 + this.request(url);
  666 + },
  667 +
  668 + request: function(url) {
  669 + var parameters = this.options.parameters || '';
  670 + if (parameters.length > 0) parameters += '&_=';
  671 +
  672 + try {
  673 + this.url = url;
  674 + if (this.options.method == 'get' && parameters.length > 0)
  675 + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
  676 +
  677 + Ajax.Responders.dispatch('onCreate', this, this.transport);
  678 +
  679 + this.transport.open(this.options.method, this.url,
  680 + this.options.asynchronous);
  681 +
  682 + if (this.options.asynchronous) {
  683 + this.transport.onreadystatechange = this.onStateChange.bind(this);
  684 + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
  685 + }
  686 +
  687 + this.setRequestHeaders();
  688 +
  689 + var body = this.options.postBody ? this.options.postBody : parameters;
  690 + this.transport.send(this.options.method == 'post' ? body : null);
  691 +
  692 + } catch (e) {
  693 + this.dispatchException(e);
  694 + }
  695 + },
  696 +
  697 + setRequestHeaders: function() {
  698 + var requestHeaders =
  699 + ['X-Requested-With', 'XMLHttpRequest',
  700 + 'X-Prototype-Version', Prototype.Version,
  701 + 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
  702 +
  703 + if (this.options.method == 'post') {
  704 + requestHeaders.push('Content-type', this.options.contentType);
  705 +
  706 + /* Force "Connection: close" for Mozilla browsers to work around
  707 + * a bug where XMLHttpReqeuest sends an incorrect Content-length
  708 + * header. See Mozilla Bugzilla #246651.
  709 + */
  710 + if (this.transport.overrideMimeType)
  711 + requestHeaders.push('Connection', 'close');
  712 + }
  713 +
  714 + if (this.options.requestHeaders)
  715 + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
  716 +
  717 + for (var i = 0; i < requestHeaders.length; i += 2)
  718 + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  719 + },
  720 +
  721 + onStateChange: function() {
  722 + var readyState = this.transport.readyState;
  723 + if (readyState != 1)
  724 + this.respondToReadyState(this.transport.readyState);
  725 + },
  726 +
  727 + header: function(name) {
  728 + try {
  729 + return this.transport.getResponseHeader(name);
  730 + } catch (e) {}
  731 + },
  732 +
  733 + evalJSON: function() {
  734 + try {
  735 + return eval('(' + this.header('X-JSON') + ')');
  736 + } catch (e) {}
  737 + },
  738 +
  739 + evalResponse: function() {
  740 + try {
  741 + return eval(this.transport.responseText);
  742 + } catch (e) {
  743 + this.dispatchException(e);
  744 + }
  745 + },
  746 +
  747 + respondToReadyState: function(readyState) {
  748 + var event = Ajax.Request.Events[readyState];
  749 + var transport = this.transport, json = this.evalJSON();
  750 +
  751 + if (event == 'Complete') {
  752 + try {
  753 + (this.options['on' + this.transport.status]
  754 + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
  755 + || Prototype.emptyFunction)(transport, json);
  756 + } catch (e) {
  757 + this.dispatchException(e);
  758 + }
  759 +
  760 + if ((this.header('Content-type') || '').match(/^text\/javascript/i))
  761 + this.evalResponse();
  762 + }
  763 +
  764 + try {
  765 + (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
  766 + Ajax.Responders.dispatch('on' + event, this, transport, json);
  767 + } catch (e) {
  768 + this.dispatchException(e);
  769 + }
  770 +
  771 + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
  772 + if (event == 'Complete')
  773 + this.transport.onreadystatechange = Prototype.emptyFunction;
  774 + },
  775 +
  776 + dispatchException: function(exception) {
  777 + (this.options.onException || Prototype.emptyFunction)(this, exception);
  778 + Ajax.Responders.dispatch('onException', this, exception);
  779 + }
  780 +});
  781 +
  782 +Ajax.Updater = Class.create();
  783 +
  784 +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  785 + initialize: function(container, url, options) {
  786 + this.containers = {
  787 + success: container.success ? $(container.success) : $(container),
  788 + failure: container.failure ? $(container.failure) :
  789 + (container.success ? null : $(container))
  790 + }
  791 +
  792 + this.transport = Ajax.getTransport();
  793 + this.setOptions(options);
  794 +
  795 + var onComplete = this.options.onComplete || Prototype.emptyFunction;
  796 + this.options.onComplete = (function(transport, object) {
  797 + this.updateContent();
  798 + onComplete(transport, object);
  799 + }).bind(this);
  800 +
  801 + this.request(url);
  802 + },
  803 +
  804 + updateContent: function() {
  805 + var receiver = this.responseIsSuccess() ?
  806 + this.containers.success : this.containers.failure;
  807 + var response = this.transport.responseText;
  808 +
  809 + if (!this.options.evalScripts)
  810 + response = response.stripScripts();
  811 +
  812 + if (receiver) {
  813 + if (this.options.insertion) {
  814 + new this.options.insertion(receiver, response);
  815 + } else {
  816 + Element.update(receiver, response);
  817 + }
  818 + }
  819 +
  820 + if (this.responseIsSuccess()) {
  821 + if (this.onComplete)
  822 + setTimeout(this.onComplete.bind(this), 10);
  823 + }
  824 + }
  825 +});
  826 +
  827 +Ajax.PeriodicalUpdater = Class.create();
  828 +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  829 + initialize: function(container, url, options) {
  830 + this.setOptions(options);
  831 + this.onComplete = this.options.onComplete;
  832 +
  833 + this.frequency = (this.options.frequency || 2);
  834 + this.decay = (this.options.decay || 1);
  835 +
  836 + this.updater = {};
  837 + this.container = container;
  838 + this.url = url;
  839 +
  840 + this.start();
  841 + },
  842 +
  843 + start: function() {
  844 + this.options.onComplete = this.updateComplete.bind(this);
  845 + this.onTimerEvent();
  846 + },
  847 +
  848 + stop: function() {
  849 + this.updater.onComplete = undefined;
  850 + clearTimeout(this.timer);
  851 + (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  852 + },
  853 +
  854 + updateComplete: function(request) {
  855 + if (this.options.decay) {
  856 + this.decay = (request.responseText == this.lastText ?
  857 + this.decay * this.options.decay : 1);
  858 +
  859 + this.lastText = request.responseText;
  860 + }
  861 + this.timer = setTimeout(this.onTimerEvent.bind(this),
  862 + this.decay * this.frequency * 1000);
  863 + },
  864 +
  865 + onTimerEvent: function() {
  866 + this.updater = new Ajax.Updater(this.container, this.url, this.options);
  867 + }
  868 +});
  869 +function $() {
  870 + var results = [], element;
  871 + for (var i = 0; i < arguments.length; i++) {
  872 + element = arguments[i];
  873 + if (typeof element == 'string')
  874 + element = document.getElementById(element);
  875 + results.push(Element.extend(element));
  876 + }
  877 + return results.length < 2 ? results[0] : results;
  878 +}
  879 +
  880 +document.getElementsByClassName = function(className, parentElement) {
  881 + var children = ($(parentElement) || document.body).getElementsByTagName('*');
  882 + return $A(children).inject([], function(elements, child) {
  883 + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
  884 + elements.push(Element.extend(child));
  885 + return elements;
  886 + });
  887 +}
  888 +
  889 +/*--------------------------------------------------------------------------*/
  890 +
  891 +if (!window.Element)
  892 + var Element = new Object();
  893 +
  894 +Element.extend = function(element) {
  895 + if (!element) return;
  896 + if (_nativeExtensions) return element;
  897 +
  898 + if (!element._extended && element.tagName && element != window) {
  899 + var methods = Element.Methods, cache = Element.extend.cache;
  900 + for (property in methods) {
  901 + var value = methods[property];
  902 + if (typeof value == 'function')
  903 + element[property] = cache.findOrStore(value);
  904 + }
  905 + }
  906 +
  907 + element._extended = true;
  908 + return element;
  909 +}
  910 +
  911 +Element.extend.cache = {
  912 + findOrStore: function(value) {
  913 + return this[value] = this[value] || function() {
  914 + return value.apply(null, [this].concat($A(arguments)));
  915 + }
  916 + }
  917 +}
  918 +
  919 +Element.Methods = {
  920 + visible: function(element) {
  921 + return $(element).style.display != 'none';
  922 + },
  923 +
  924 + toggle: function() {
  925 + for (var i = 0; i < arguments.length; i++) {
  926 + var element = $(arguments[i]);
  927 + Element[Element.visible(element) ? 'hide' : 'show'](element);
  928 + }
  929 + },
  930 +
  931 + hide: function() {
  932 + for (var i = 0; i < arguments.length; i++) {
  933 + var element = $(arguments[i]);
  934 + element.style.display = 'none';
  935 + }
  936 + },
  937 +
  938 + show: function() {
  939 + for (var i = 0; i < arguments.length; i++) {
  940 + var element = $(arguments[i]);
  941 + element.style.display = '';
  942 + }
  943 + },
  944 +
  945 + remove: function(element) {
  946 + element = $(element);
  947 + element.parentNode.removeChild(element);
  948 + },
  949 +
  950 + update: function(element, html) {
  951 + $(element).innerHTML = html.stripScripts();
  952 + setTimeout(function() {html.evalScripts()}, 10);
  953 + },
  954 +
  955 + replace: function(element, html) {
  956 + element = $(element);
  957 + if (element.outerHTML) {
  958 + element.outerHTML = html.stripScripts();
  959 + } else {
  960 + var range = element.ownerDocument.createRange();
  961 + range.selectNodeContents(element);
  962 + element.parentNode.replaceChild(
  963 + range.createContextualFragment(html.stripScripts()), element);
  964 + }
  965 + setTimeout(function() {html.evalScripts()}, 10);
  966 + },
  967 +
  968 + getHeight: function(element) {
  969 + element = $(element);
  970 + return element.offsetHeight;
  971 + },
  972 +
  973 + classNames: function(element) {
  974 + return new Element.ClassNames(element);
  975 + },
  976 +
  977 + hasClassName: function(element, className) {
  978 + if (!(element = $(element))) return;
  979 + return Element.classNames(element).include(className);
  980 + },
  981 +
  982 + addClassName: function(element, className) {
  983 + if (!(element = $(element))) return;
  984 + return Element.classNames(element).add(className);
  985 + },
  986 +
  987 + removeClassName: function(element, className) {
  988 + if (!(element = $(element))) return;
  989 + return Element.classNames(element).remove(className);
  990 + },
  991 +
  992 + // removes whitespace-only text node children
  993 + cleanWhitespace: function(element) {
  994 + element = $(element);
  995 + for (var i = 0; i < element.childNodes.length; i++) {
  996 + var node = element.childNodes[i];
  997 + if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
  998 + Element.remove(node);
  999 + }
  1000 + },
  1001 +
  1002 + empty: function(element) {
  1003 + return $(element).innerHTML.match(/^\s*$/);
  1004 + },
  1005 +
  1006 + childOf: function(element, ancestor) {
  1007 + element = $(element), ancestor = $(ancestor);
  1008 + while (element = element.parentNode)
  1009 + if (element == ancestor) return true;
  1010 + return false;
  1011 + },
  1012 +
  1013 + scrollTo: function(element) {
  1014 + element = $(element);
  1015 + var x = element.x ? element.x : element.offsetLeft,
  1016 + y = element.y ? element.y : element.offsetTop;
  1017 + window.scrollTo(x, y);
  1018 + },
  1019 +
  1020 + getStyle: function(element, style) {
  1021 + element = $(element);
  1022 + var value = element.style[style.camelize()];
  1023 + if (!value) {
  1024 + if (document.defaultView && document.defaultView.getComputedStyle) {
  1025 + var css = document.defaultView.getComputedStyle(element, null);
  1026 + value = css ? css.getPropertyValue(style) : null;
  1027 + } else if (element.currentStyle) {
  1028 + value = element.currentStyle[style.camelize()];
  1029 + }
  1030 + }
  1031 +
  1032 + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
  1033 + if (Element.getStyle(element, 'position') == 'static') value = 'auto';
  1034 +
  1035 + return value == 'auto' ? null : value;
  1036 + },
  1037 +
  1038 + setStyle: function(element, style) {
  1039 + element = $(element);
  1040 + for (var name in style)
  1041 + element.style[name.camelize()] = style[name];
  1042 + },
  1043 +
  1044 + getDimensions: function(element) {
  1045 + element = $(element);
  1046 + if (Element.getStyle(element, 'display') != 'none')
  1047 + return {width: element.offsetWidth, height: element.offsetHeight};
  1048 +
  1049 + // All *Width and *Height properties give 0 on elements with display none,
  1050 + // so enable the element temporarily
  1051 + var els = element.style;
  1052 + var originalVisibility = els.visibility;
  1053 + var originalPosition = els.position;
  1054 + els.visibility = 'hidden';
  1055 + els.position = 'absolute';
  1056 + els.display = '';
  1057 + var originalWidth = element.clientWidth;
  1058 + var originalHeight = element.clientHeight;
  1059 + els.display = 'none';
  1060 + els.position = originalPosition;
  1061 + els.visibility = originalVisibility;
  1062 + return {width: originalWidth, height: originalHeight};
  1063 + },
  1064 +
  1065 + makePositioned: function(element) {
  1066 + element = $(element);
  1067 + var pos = Element.getStyle(element, 'position');
  1068 + if (pos == 'static' || !pos) {
  1069 + element._madePositioned = true;
  1070 + element.style.position = 'relative';
  1071 + // Opera returns the offset relative to the positioning context, when an
  1072 + // element is position relative but top and left have not been defined
  1073 + if (window.opera) {
  1074 + element.style.top = 0;
  1075 + element.style.left = 0;
  1076 + }
  1077 + }
  1078 + },
  1079 +
  1080 + undoPositioned: function(element) {
  1081 + element = $(element);
  1082 + if (element._madePositioned) {
  1083 + element._madePositioned = undefined;
  1084 + element.style.position =
  1085 + element.style.top =
  1086 + element.style.left =
  1087 + element.style.bottom =
  1088 + element.style.right = '';
  1089 + }
  1090 + },
  1091 +
  1092 + makeClipping: function(element) {
  1093 + element = $(element);
  1094 + if (element._overflow) return;
  1095 + element._overflow = element.style.overflow;
  1096 + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
  1097 + element.style.overflow = 'hidden';
  1098 + },
  1099 +
  1100 + undoClipping: function(element) {
  1101 + element = $(element);
  1102 + if (element._overflow) return;
  1103 + element.style.overflow = element._overflow;
  1104 + element._overflow = undefined;
  1105 + }
  1106 +}
  1107 +
  1108 +Object.extend(Element, Element.Methods);
  1109 +
  1110 +var _nativeExtensions = false;
  1111 +
  1112 +if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  1113 + var HTMLElement = {}
  1114 + HTMLElement.prototype = document.createElement('div').__proto__;
  1115 +}
  1116 +
  1117 +Element.addMethods = function(methods) {
  1118 + Object.extend(Element.Methods, methods || {});
  1119 +
  1120 + if(typeof HTMLElement != 'undefined') {
  1121 + var methods = Element.Methods, cache = Element.extend.cache;
  1122 + for (property in methods) {
  1123 + var value = methods[property];
  1124 + if (typeof value == 'function')
  1125 + HTMLElement.prototype[property] = cache.findOrStore(value);
  1126 + }
  1127 + _nativeExtensions = true;
  1128 + }
  1129 +}
  1130 +
  1131 +Element.addMethods();
  1132 +
  1133 +var Toggle = new Object();
  1134 +Toggle.display = Element.toggle;
  1135 +
  1136 +/*--------------------------------------------------------------------------*/
  1137 +
  1138 +Abstract.Insertion = function(adjacency) {
  1139 + this.adjacency = adjacency;
  1140 +}
  1141 +
  1142 +Abstract.Insertion.prototype = {
  1143 + initialize: function(element, content) {
  1144 + this.element = $(element);
  1145 + this.content = content.stripScripts();
  1146 +
  1147 + if (this.adjacency && this.element.insertAdjacentHTML) {
  1148 + try {
  1149 + this.element.insertAdjacentHTML(this.adjacency, this.content);
  1150 + } catch (e) {
  1151 + var tagName = this.element.tagName.toLowerCase();
  1152 + if (tagName == 'tbody' || tagName == 'tr') {
  1153 + this.insertContent(this.contentFromAnonymousTable());
  1154 + } else {
  1155 + throw e;
  1156 + }
  1157 + }
  1158 + } else {
  1159 + this.range = this.element.ownerDocument.createRange();
  1160 + if (this.initializeRange) this.initializeRange();
  1161 + this.insertContent([this.range.createContextualFragment(this.content)]);
  1162 + }
  1163 +
  1164 + setTimeout(function() {content.evalScripts()}, 10);
  1165 + },
  1166 +
  1167 + contentFromAnonymousTable: function() {
  1168 + var div = document.createElement('div');
  1169 + div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
  1170 + return $A(div.childNodes[0].childNodes[0].childNodes);
  1171 + }
  1172 +}
  1173 +
  1174 +var Insertion = new Object();
  1175 +
  1176 +Insertion.Before = Class.create();
  1177 +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  1178 + initializeRange: function() {
  1179 + this.range.setStartBefore(this.element);
  1180 + },
  1181 +
  1182 + insertContent: function(fragments) {
  1183 + fragments.each((function(fragment) {
  1184 + this.element.parentNode.insertBefore(fragment, this.element);
  1185 + }).bind(this));
  1186 + }
  1187 +});
  1188 +
  1189 +Insertion.Top = Class.create();
  1190 +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  1191 + initializeRange: function() {
  1192 + this.range.selectNodeContents(this.element);
  1193 + this.range.collapse(true);
  1194 + },
  1195 +
  1196 + insertContent: function(fragments) {
  1197 + fragments.reverse(false).each((function(fragment) {
  1198 + this.element.insertBefore(fragment, this.element.firstChild);
  1199 + }).bind(this));
  1200 + }
  1201 +});
  1202 +
  1203 +Insertion.Bottom = Class.create();
  1204 +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  1205 + initializeRange: function() {
  1206 + this.range.selectNodeContents(this.element);
  1207 + this.range.collapse(this.element);
  1208 + },
  1209 +
  1210 + insertContent: function(fragments) {
  1211 + fragments.each((function(fragment) {
  1212 + this.element.appendChild(fragment);
  1213 + }).bind(this));
  1214 + }
  1215 +});
  1216 +
  1217 +Insertion.After = Class.create();
  1218 +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  1219 + initializeRange: function() {
  1220 + this.range.setStartAfter(this.element);
  1221 + },
  1222 +
  1223 + insertContent: function(fragments) {
  1224 + fragments.each((function(fragment) {
  1225 + this.element.parentNode.insertBefore(fragment,
  1226 + this.element.nextSibling);
  1227 + }).bind(this));
  1228 + }
  1229 +});
  1230 +
  1231 +/*--------------------------------------------------------------------------*/
  1232 +
  1233 +Element.ClassNames = Class.create();
  1234 +Element.ClassNames.prototype = {
  1235 + initialize: function(element) {
  1236 + this.element = $(element);
  1237 + },
  1238 +
  1239 + _each: function(iterator) {
  1240 + this.element.className.split(/\s+/).select(function(name) {
  1241 + return name.length > 0;
  1242 + })._each(iterator);
  1243 + },
  1244 +
  1245 + set: function(className) {
  1246 + this.element.className = className;
  1247 + },
  1248 +
  1249 + add: function(classNameToAdd) {
  1250 + if (this.include(classNameToAdd)) return;
  1251 + this.set(this.toArray().concat(classNameToAdd).join(' '));
  1252 + },
  1253 +
  1254 + remove: function(classNameToRemove) {
  1255 + if (!this.include(classNameToRemove)) return;
  1256 + this.set(this.select(function(className) {
  1257 + return className != classNameToRemove;
  1258 + }).join(' '));
  1259 + },
  1260 +
  1261 + toString: function() {
  1262 + return this.toArray().join(' ');
  1263 + }
  1264 +}
  1265 +
  1266 +Object.extend(Element.ClassNames.prototype, Enumerable);
  1267 +var Selector = Class.create();
  1268 +Selector.prototype = {
  1269 + initialize: function(expression) {
  1270 + this.params = {classNames: []};
  1271 + this.expression = expression.toString().strip();
  1272 + this.parseExpression();
  1273 + this.compileMatcher();
  1274 + },
  1275 +
  1276 + parseExpression: function() {
  1277 + function abort(message) { throw 'Parse error in selector: ' + message; }
  1278 +
  1279 + if (this.expression == '') abort('empty expression');
  1280 +
  1281 + var params = this.params, expr = this.expression, match, modifier, clause, rest;
  1282 + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
  1283 + params.attributes = params.attributes || [];
  1284 + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
  1285 + expr = match[1];
  1286 + }
  1287 +
  1288 + if (expr == '*') return this.params.wildcard = true;
  1289 +
  1290 + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
  1291 + modifier = match[1], clause = match[2], rest = match[3];
  1292 + switch (modifier) {
  1293 + case '#': params.id = clause; break;
  1294 + case '.': params.classNames.push(clause); break;
  1295 + case '':
  1296 + case undefined: params.tagName = clause.toUpperCase(); break;
  1297 + default: abort(expr.inspect());
  1298 + }
  1299 + expr = rest;
  1300 + }
  1301 +
  1302 + if (expr.length > 0) abort(expr.inspect());
  1303 + },
  1304 +
  1305 + buildMatchExpression: function() {
  1306 + var params = this.params, conditions = [], clause;
  1307 +
  1308 + if (params.wildcard)
  1309 + conditions.push('true');
  1310 + if (clause = params.id)
  1311 + conditions.push('element.id == ' + clause.inspect());
  1312 + if (clause = params.tagName)
  1313 + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
  1314 + if ((clause = params.classNames).length > 0)
  1315 + for (var i = 0; i < clause.length; i++)
  1316 + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
  1317 + if (clause = params.attributes) {
  1318 + clause.each(function(attribute) {
  1319 + var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
  1320 + var splitValueBy = function(delimiter) {
  1321 + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
  1322 + }
  1323 +
  1324 + switch (attribute.operator) {
  1325 + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
  1326 + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
  1327 + case '|=': conditions.push(
  1328 + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
  1329 + ); break;
  1330 + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
  1331 + case '':
  1332 + case undefined: conditions.push(value + ' != null'); break;
  1333 + default: throw 'Unknown operator ' + attribute.operator + ' in selector';
  1334 + }
  1335 + });
  1336 + }
  1337 +
  1338 + return conditions.join(' && ');
  1339 + },
  1340 +
  1341 + compileMatcher: function() {
  1342 + this.match = new Function('element', 'if (!element.tagName) return false; \
  1343 + return ' + this.buildMatchExpression());
  1344 + },
  1345 +
  1346 + findElements: function(scope) {
  1347 + var element;
  1348 +
  1349 + if (element = $(this.params.id))
  1350 + if (this.match(element))
  1351 + if (!scope || Element.childOf(element, scope))
  1352 + return [element];
  1353 +
  1354 + scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
  1355 +
  1356 + var results = [];
  1357 + for (var i = 0; i < scope.length; i++)
  1358 + if (this.match(element = scope[i]))
  1359 + results.push(Element.extend(element));
  1360 +
  1361 + return results;
  1362 + },
  1363 +
  1364 + toString: function() {
  1365 + return this.expression;
  1366 + }
  1367 +}
  1368 +
  1369 +function $$() {
  1370 + return $A(arguments).map(function(expression) {
  1371 + return expression.strip().split(/\s+/).inject([null], function(results, expr) {
  1372 + var selector = new Selector(expr);
  1373 + return results.map(selector.findElements.bind(selector)).flatten();
  1374 + });
  1375 + }).flatten();
  1376 +}
  1377 +var Field = {
  1378 + clear: function() {
  1379 + for (var i = 0; i < arguments.length; i++)
  1380 + $(arguments[i]).value = '';
  1381 + },
  1382 +
  1383 + focus: function(element) {
  1384 + $(element).focus();
  1385 + },
  1386 +
  1387 + present: function() {
  1388 + for (var i = 0; i < arguments.length; i++)
  1389 + if ($(arguments[i]).value == '') return false;
  1390 + return true;
  1391 + },
  1392 +
  1393 + select: function(element) {
  1394 + $(element).select();
  1395 + },
  1396 +
  1397 + activate: function(element) {
  1398 + element = $(element);
  1399 + element.focus();
  1400 + if (element.select)
  1401 + element.select();
  1402 + }
  1403 +}
  1404 +
  1405 +/*--------------------------------------------------------------------------*/
  1406 +
  1407 +var Form = {
  1408 + serialize: function(form) {
  1409 + var elements = Form.getElements($(form));
  1410 + var queryComponents = new Array();
  1411 +
  1412 + for (var i = 0; i < elements.length; i++) {
  1413 + var queryComponent = Form.Element.serialize(elements[i]);
  1414 + if (queryComponent)
  1415 + queryComponents.push(queryComponent);
  1416 + }
  1417 +
  1418 + return queryComponents.join('&');
  1419 + },
  1420 +
  1421 + getElements: function(form) {
  1422 + form = $(form);
  1423 + var elements = new Array();
  1424 +
  1425 + for (var tagName in Form.Element.Serializers) {
  1426 + var tagElements = form.getElementsByTagName(tagName);
  1427 + for (var j = 0; j < tagElements.length; j++)
  1428 + elements.push(tagElements[j]);
  1429 + }
  1430 + return elements;
  1431 + },
  1432 +
  1433 + getInputs: function(form, typeName, name) {
  1434 + form = $(form);
  1435 + var inputs = form.getElementsByTagName('input');
  1436 +
  1437 + if (!typeName && !name)
  1438 + return inputs;
  1439 +
  1440 + var matchingInputs = new Array();
  1441 + for (var i = 0; i < inputs.length; i++) {
  1442 + var input = inputs[i];
  1443 + if ((typeName && input.type != typeName) ||
  1444 + (name && input.name != name))
  1445 + continue;
  1446 + matchingInputs.push(input);
  1447 + }
  1448 +
  1449 + return matchingInputs;
  1450 + },
  1451 +
  1452 + disable: function(form) {
  1453 + var elements = Form.getElements(form);
  1454 + for (var i = 0; i < elements.length; i++) {
  1455 + var element = elements[i];
  1456 + element.blur();
  1457 + element.disabled = 'true';
  1458 + }
  1459 + },
  1460 +
  1461 + enable: function(form) {
  1462 + var elements = Form.getElements(form);
  1463 + for (var i = 0; i < elements.length; i++) {
  1464 + var element = elements[i];
  1465 + element.disabled = '';
  1466 + }
  1467 + },
  1468 +
  1469 + findFirstElement: function(form) {
  1470 + return Form.getElements(form).find(function(element) {
  1471 + return element.type != 'hidden' && !element.disabled &&
  1472 + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
  1473 + });
  1474 + },
  1475 +
  1476 + focusFirstElement: function(form) {
  1477 + Field.activate(Form.findFirstElement(form));
  1478 + },
  1479 +
  1480 + reset: function(form) {
  1481 + $(form).reset();
  1482 + }
  1483 +}
  1484 +
  1485 +Form.Element = {
  1486 + serialize: function(element) {
  1487 + element = $(element);
  1488 + var method = element.tagName.toLowerCase();
  1489 + var parameter = Form.Element.Serializers[method](element);
  1490 +
  1491 + if (parameter) {
  1492 + var key = encodeURIComponent(parameter[0]);
  1493 + if (key.length == 0) return;
  1494 +
  1495 + if (parameter[1].constructor != Array)
  1496 + parameter[1] = [parameter[1]];
  1497 +
  1498 + return parameter[1].map(function(value) {
  1499 + return key + '=' + encodeURIComponent(value);
  1500 + }).join('&');
  1501 + }
  1502 + },
  1503 +
  1504 + getValue: function(element) {
  1505 + element = $(element);
  1506 + var method = element.tagName.toLowerCase();
  1507 + var parameter = Form.Element.Serializers[method](element);
  1508 +
  1509 + if (parameter)
  1510 + return parameter[1];
  1511 + }
  1512 +}
  1513 +
  1514 +Form.Element.Serializers = {
  1515 + input: function(element) {
  1516 + switch (element.type.toLowerCase()) {
  1517 + case 'submit':
  1518 + case 'hidden':
  1519 + case 'password':
  1520 + case 'text':
  1521 + return Form.Element.Serializers.textarea(element);
  1522 + case 'checkbox':
  1523 + case 'radio':
  1524 + return Form.Element.Serializers.inputSelector(element);
  1525 + }
  1526 + return false;
  1527 + },
  1528 +
  1529 + inputSelector: function(element) {
  1530 + if (element.checked)
  1531 + return [element.name, element.value];
  1532 + },
  1533 +
  1534 + textarea: function(element) {
  1535 + return [element.name, element.value];
  1536 + },
  1537 +
  1538 + select: function(element) {
  1539 + return Form.Element.Serializers[element.type == 'select-one' ?
  1540 + 'selectOne' : 'selectMany'](element);
  1541 + },
  1542 +
  1543 + selectOne: function(element) {
  1544 + var value = '', opt, index = element.selectedIndex;
  1545 + if (index >= 0) {
  1546 + opt = element.options[index];
  1547 + value = opt.value || opt.text;
  1548 + }
  1549 + return [element.name, value];
  1550 + },
  1551 +
  1552 + selectMany: function(element) {
  1553 + var value = [];
  1554 + for (var i = 0; i < element.length; i++) {
  1555 + var opt = element.options[i];
  1556 + if (opt.selected)
  1557 + value.push(opt.value || opt.text);
  1558 + }
  1559 + return [element.name, value];
  1560 + }
  1561 +}
  1562 +
  1563 +/*--------------------------------------------------------------------------*/
  1564 +
  1565 +var $F = Form.Element.getValue;
  1566 +
  1567 +/*--------------------------------------------------------------------------*/
  1568 +
  1569 +Abstract.TimedObserver = function() {}
  1570 +Abstract.TimedObserver.prototype = {
  1571 + initialize: function(element, frequency, callback) {
  1572 + this.frequency = frequency;
  1573 + this.element = $(element);
  1574 + this.callback = callback;
  1575 +
  1576 + this.lastValue = this.getValue();
  1577 + this.registerCallback();
  1578 + },
  1579 +
  1580 + registerCallback: function() {
  1581 + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  1582 + },
  1583 +
  1584 + onTimerEvent: function() {
  1585 + var value = this.getValue();
  1586 + if (this.lastValue != value) {
  1587 + this.callback(this.element, value);
  1588 + this.lastValue = value;
  1589 + }
  1590 + }
  1591 +}
  1592 +
  1593 +Form.Element.Observer = Class.create();
  1594 +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  1595 + getValue: function() {
  1596 + return Form.Element.getValue(this.element);
  1597 + }
  1598 +});
  1599 +
  1600 +Form.Observer = Class.create();
  1601 +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  1602 + getValue: function() {
  1603 + return Form.serialize(this.element);
  1604 + }
  1605 +});
  1606 +
  1607 +/*--------------------------------------------------------------------------*/
  1608 +
  1609 +Abstract.EventObserver = function() {}
  1610 +Abstract.EventObserver.prototype = {
  1611 + initialize: function(element, callback) {
  1612 + this.element = $(element);
  1613 + this.callback = callback;
  1614 +
  1615 + this.lastValue = this.getValue();
  1616 + if (this.element.tagName.toLowerCase() == 'form')
  1617 + this.registerFormCallbacks();
  1618 + else
  1619 + this.registerCallback(this.element);
  1620 + },
  1621 +
  1622 + onElementEvent: function() {
  1623 + var value = this.getValue();
  1624 + if (this.lastValue != value) {
  1625 + this.callback(this.element, value);
  1626 + this.lastValue = value;
  1627 + }
  1628 + },
  1629 +
  1630 + registerFormCallbacks: function() {
  1631 + var elements = Form.getElements(this.element);
  1632 + for (var i = 0; i < elements.length; i++)
  1633 + this.registerCallback(elements[i]);
  1634 + },
  1635 +
  1636 + registerCallback: function(element) {
  1637 + if (element.type) {
  1638 + switch (element.type.toLowerCase()) {
  1639 + case 'checkbox':
  1640 + case 'radio':
  1641 + Event.observe(element, 'click', this.onElementEvent.bind(this));
  1642 + break;
  1643 + case 'password':
  1644 + case 'text':
  1645 + case 'textarea':
  1646 + case 'select-one':
  1647 + case 'select-multiple':
  1648 + Event.observe(element, 'change', this.onElementEvent.bind(this));
  1649 + break;
  1650 + }
  1651 + }
  1652 + }
  1653 +}
  1654 +
  1655 +Form.Element.EventObserver = Class.create();
  1656 +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  1657 + getValue: function() {
  1658 + return Form.Element.getValue(this.element);
  1659 + }
  1660 +});
  1661 +
  1662 +Form.EventObserver = Class.create();
  1663 +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  1664 + getValue: function() {
  1665 + return Form.serialize(this.element);
  1666 + }
  1667 +});
  1668 +if (!window.Event) {
  1669 + var Event = new Object();
  1670 +}
  1671 +
  1672 +Object.extend(Event, {
  1673 + KEY_BACKSPACE: 8,
  1674 + KEY_TAB: 9,
  1675 + KEY_RETURN: 13,
  1676 + KEY_ESC: 27,
  1677 + KEY_LEFT: 37,
  1678 + KEY_UP: 38,
  1679 + KEY_RIGHT: 39,
  1680 + KEY_DOWN: 40,
  1681 + KEY_DELETE: 46,
  1682 +
  1683 + element: function(event) {
  1684 + return event.target || event.srcElement;
  1685 + },
  1686 +
  1687 + isLeftClick: function(event) {
  1688 + return (((event.which) && (event.which == 1)) ||
  1689 + ((event.button) && (event.button == 1)));
  1690 + },
  1691 +
  1692 + pointerX: function(event) {
  1693 + return event.pageX || (event.clientX +
  1694 + (document.documentElement.scrollLeft || document.body.scrollLeft));
  1695 + },
  1696 +
  1697 + pointerY: function(event) {
  1698 + return event.pageY || (event.clientY +
  1699 + (document.documentElement.scrollTop || document.body.scrollTop));
  1700 + },
  1701 +
  1702 + stop: function(event) {
  1703 + if (event.preventDefault) {
  1704 + event.preventDefault();
  1705 + event.stopPropagation();
  1706 + } else {
  1707 + event.returnValue = false;
  1708 + event.cancelBubble = true;
  1709 + }
  1710 + },
  1711 +
  1712 + // find the first node with the given tagName, starting from the
  1713 + // node the event was triggered on; traverses the DOM upwards
  1714 + findElement: function(event, tagName) {
  1715 + var element = Event.element(event);
  1716 + while (element.parentNode && (!element.tagName ||
  1717 + (element.tagName.toUpperCase() != tagName.toUpperCase())))
  1718 + element = element.parentNode;
  1719 + return element;
  1720 + },
  1721 +
  1722 + observers: false,
  1723 +
  1724 + _observeAndCache: function(element, name, observer, useCapture) {
  1725 + if (!this.observers) this.observers = [];
  1726 + if (element.addEventListener) {
  1727 + this.observers.push([element, name, observer, useCapture]);
  1728 + element.addEventListener(name, observer, useCapture);
  1729 + } else if (element.attachEvent) {
  1730 + this.observers.push([element, name, observer, useCapture]);
  1731 + element.attachEvent('on' + name, observer);
  1732 + }
  1733 + },
  1734 +
  1735 + unloadCache: function() {
  1736 + if (!Event.observers) return;
  1737 + for (var i = 0; i < Event.observers.length; i++) {
  1738 + Event.stopObserving.apply(this, Event.observers[i]);
  1739 + Event.observers[i][0] = null;
  1740 + }
  1741 + Event.observers = false;
  1742 + },
  1743 +
  1744 + observe: function(element, name, observer, useCapture) {
  1745 + var element = $(element);
  1746 + useCapture = useCapture || false;
  1747 +
  1748 + if (name == 'keypress' &&
  1749 + (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  1750 + || element.attachEvent))
  1751 + name = 'keydown';
  1752 +
  1753 + this._observeAndCache(element, name, observer, useCapture);
  1754 + },
  1755 +
  1756 + stopObserving: function(element, name, observer, useCapture) {
  1757 + var element = $(element);
  1758 + useCapture = useCapture || false;
  1759 +
  1760 + if (name == 'keypress' &&
  1761 + (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  1762 + || element.detachEvent))
  1763 + name = 'keydown';
  1764 +
  1765 + if (element.removeEventListener) {
  1766 + element.removeEventListener(name, observer, useCapture);
  1767 + } else if (element.detachEvent) {
  1768 + element.detachEvent('on' + name, observer);
  1769 + }
  1770 + }
  1771 +});
  1772 +
  1773 +/* prevent memory leaks in IE */
  1774 +if (navigator.appVersion.match(/\bMSIE\b/))
  1775 + Event.observe(window, 'unload', Event.unloadCache, false);
  1776 +var Position = {
  1777 + // set to true if needed, warning: firefox performance problems
  1778 + // NOT neeeded for page scrolling, only if draggable contained in
  1779 + // scrollable elements
  1780 + includeScrollOffsets: false,
  1781 +
  1782 + // must be called before calling withinIncludingScrolloffset, every time the
  1783 + // page is scrolled
  1784 + prepare: function() {
  1785 + this.deltaX = window.pageXOffset
  1786 + || document.documentElement.scrollLeft
  1787 + || document.body.scrollLeft
  1788 + || 0;
  1789 + this.deltaY = window.pageYOffset
  1790 + || document.documentElement.scrollTop
  1791 + || document.body.scrollTop
  1792 + || 0;
  1793 + },
  1794 +
  1795 + realOffset: function(element) {
  1796 + var valueT = 0, valueL = 0;
  1797 + do {
  1798 + valueT += element.scrollTop || 0;
  1799 + valueL += element.scrollLeft || 0;
  1800 + element = element.parentNode;
  1801 + } while (element);
  1802 + return [valueL, valueT];
  1803 + },
  1804 +
  1805 + cumulativeOffset: function(element) {
  1806 + var valueT = 0, valueL = 0;
  1807 + do {
  1808 + valueT += element.offsetTop || 0;
  1809 + valueL += element.offsetLeft || 0;
  1810 + element = element.offsetParent;
  1811 + } while (element);
  1812 + return [valueL, valueT];
  1813 + },
  1814 +
  1815 + positionedOffset: function(element) {
  1816 + var valueT = 0, valueL = 0;
  1817 + do {
  1818 + valueT += element.offsetTop || 0;
  1819 + valueL += element.offsetLeft || 0;
  1820 + element = element.offsetParent;
  1821 + if (element) {
  1822 + p = Element.getStyle(element, 'position');
  1823 + if (p == 'relative' || p == 'absolute') break;
  1824 + }
  1825 + } while (element);
  1826 + return [valueL, valueT];
  1827 + },
  1828 +
  1829 + offsetParent: function(element) {
  1830 + if (element.offsetParent) return element.offsetParent;
  1831 + if (element == document.body) return element;
  1832 +
  1833 + while ((element = element.parentNode) && element != document.body)
  1834 + if (Element.getStyle(element, 'position') != 'static')
  1835 + return element;
  1836 +
  1837 + return document.body;
  1838 + },
  1839 +
  1840 + // caches x/y coordinate pair to use with overlap
  1841 + within: function(element, x, y) {
  1842 + if (this.includeScrollOffsets)
  1843 + return this.withinIncludingScrolloffsets(element, x, y);
  1844 + this.xcomp = x;
  1845 + this.ycomp = y;
  1846 + this.offset = this.cumulativeOffset(element);
  1847 +
  1848 + return (y >= this.offset[1] &&
  1849 + y < this.offset[1] + element.offsetHeight &&
  1850 + x >= this.offset[0] &&
  1851 + x < this.offset[0] + element.offsetWidth);
  1852 + },
  1853 +
  1854 + withinIncludingScrolloffsets: function(element, x, y) {
  1855 + var offsetcache = this.realOffset(element);
  1856 +
  1857 + this.xcomp = x + offsetcache[0] - this.deltaX;
  1858 + this.ycomp = y + offsetcache[1] - this.deltaY;
  1859 + this.offset = this.cumulativeOffset(element);
  1860 +
  1861 + return (this.ycomp >= this.offset[1] &&
  1862 + this.ycomp < this.offset[1] + element.offsetHeight &&
  1863 + this.xcomp >= this.offset[0] &&
  1864 + this.xcomp < this.offset[0] + element.offsetWidth);
  1865 + },
  1866 +
  1867 + // within must be called directly before
  1868 + overlap: function(mode, element) {
  1869 + if (!mode) return 0;
  1870 + if (mode == 'vertical')
  1871 + return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
  1872 + element.offsetHeight;
  1873 + if (mode == 'horizontal')
  1874 + return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
  1875 + element.offsetWidth;
  1876 + },
  1877 +
  1878 + clone: function(source, target) {
  1879 + source = $(source);
  1880 + target = $(target);
  1881 + target.style.position = 'absolute';
  1882 + var offsets = this.cumulativeOffset(source);
  1883 + target.style.top = offsets[1] + 'px';
  1884 + target.style.left = offsets[0] + 'px';
  1885 + target.style.width = source.offsetWidth + 'px';
  1886 + target.style.height = source.offsetHeight + 'px';
  1887 + },
  1888 +
  1889 + page: function(forElement) {
  1890 + var valueT = 0, valueL = 0;
  1891 +
  1892 + var element = forElement;
  1893 + do {
  1894 + valueT += element.offsetTop || 0;
  1895 + valueL += element.offsetLeft || 0;
  1896 +
  1897 + // Safari fix
  1898 + if (element.offsetParent==document.body)
  1899 + if (Element.getStyle(element,'position')=='absolute') break;
  1900 +
  1901 + } while (element = element.offsetParent);
  1902 +
  1903 + element = forElement;
  1904 + do {
  1905 + valueT -= element.scrollTop || 0;
  1906 + valueL -= element.scrollLeft || 0;
  1907 + } while (element = element.parentNode);
  1908 +
  1909 + return [valueL, valueT];
  1910 + },
  1911 +
  1912 + clone: function(source, target) {
  1913 + var options = Object.extend({
  1914 + setLeft: true,
  1915 + setTop: true,
  1916 + setWidth: true,
  1917 + setHeight: true,
  1918 + offsetTop: 0,
  1919 + offsetLeft: 0
  1920 + }, arguments[2] || {})
  1921 +
  1922 + // find page position of source
  1923 + source = $(source);
  1924 + var p = Position.page(source);
  1925 +
  1926 + // find coordinate system to use
  1927 + target = $(target);
  1928 + var delta = [0, 0];
  1929 + var parent = null;
  1930 + // delta [0,0] will do fine with position: fixed elements,
  1931 + // position:absolute needs offsetParent deltas
  1932 + if (Element.getStyle(target,'position') == 'absolute') {
  1933 + parent = Position.offsetParent(target);
  1934 + delta = Position.page(parent);
  1935 + }
  1936 +
  1937 + // correct by body offsets (fixes Safari)
  1938 + if (parent == document.body) {
  1939 + delta[0] -= document.body.offsetLeft;
  1940 + delta[1] -= document.body.offsetTop;
  1941 + }
  1942 +
  1943 + // set position
  1944 + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
  1945 + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
  1946 + if(options.setWidth) target.style.width = source.offsetWidth + 'px';
  1947 + if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  1948 + },
  1949 +
  1950 + absolutize: function(element) {
  1951 + element = $(element);
  1952 + if (element.style.position == 'absolute') return;
  1953 + Position.prepare();
  1954 +
  1955 + var offsets = Position.positionedOffset(element);
  1956 + var top = offsets[1];
  1957 + var left = offsets[0];
  1958 + var width = element.clientWidth;
  1959 + var height = element.clientHeight;
  1960 +
  1961 + element._originalLeft = left - parseFloat(element.style.left || 0);
  1962 + element._originalTop = top - parseFloat(element.style.top || 0);
  1963 + element._originalWidth = element.style.width;
  1964 + element._originalHeight = element.style.height;
  1965 +
  1966 + element.style.position = 'absolute';
  1967 + element.style.top = top + 'px';;
  1968 + element.style.left = left + 'px';;
  1969 + element.style.width = width + 'px';;
  1970 + element.style.height = height + 'px';;
  1971 + },
  1972 +
  1973 + relativize: function(element) {
  1974 + element = $(element);
  1975 + if (element.style.position == 'relative') return;
  1976 + Position.prepare();
  1977 +
  1978 + element.style.position = 'relative';
  1979 + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
  1980 + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
  1981 +
  1982 + element.style.top = top + 'px';
  1983 + element.style.left = left + 'px';
  1984 + element.style.height = element._originalHeight;
  1985 + element.style.width = element._originalWidth;
  1986 + }
  1987 +}
  1988 +
  1989 +// Safari returns margins on body which is incorrect if the child is absolutely
  1990 +// positioned. For performance reasons, redefine Position.cumulativeOffset for
  1991 +// KHTML/WebKit only.
  1992 +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  1993 + Position.cumulativeOffset = function(element) {
  1994 + var valueT = 0, valueL = 0;
  1995 + do {
  1996 + valueT += element.offsetTop || 0;
  1997 + valueL += element.offsetLeft || 0;
  1998 + if (element.offsetParent == document.body)
  1999 + if (Element.getStyle(element, 'position') == 'absolute') break;
  2000 +
  2001 + element = element.offsetParent;
  2002 + } while (element);
  2003 +
  2004 + return [valueL, valueT];
  2005 + }
  2006 +}
0 2007 \ No newline at end of file
... ...
public/robots.txt 0 → 100644
  1 +++ a/public/robots.txt
... ... @@ -0,0 +1 @@
  1 +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
0 2 \ No newline at end of file
... ...
script/about 0 → 100755
  1 +++ a/script/about
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/about'
0 4 \ No newline at end of file
... ...
script/breakpointer 0 → 100755
  1 +++ a/script/breakpointer
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/breakpointer'
0 4 \ No newline at end of file
... ...
script/console 0 → 100755
  1 +++ a/script/console
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/console'
0 4 \ No newline at end of file
... ...
script/destroy 0 → 100755
  1 +++ a/script/destroy
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/destroy'
0 4 \ No newline at end of file
... ...
script/generate 0 → 100755
  1 +++ a/script/generate
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/generate'
0 4 \ No newline at end of file
... ...
script/performance/benchmarker 0 → 100755
  1 +++ a/script/performance/benchmarker
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../../config/boot'
  3 +require 'commands/performance/benchmarker'
... ...
script/performance/profiler 0 → 100755
  1 +++ a/script/performance/profiler
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../../config/boot'
  3 +require 'commands/performance/profiler'
... ...
script/plugin 0 → 100755
  1 +++ a/script/plugin
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/plugin'
0 4 \ No newline at end of file
... ...
script/process/reaper 0 → 100755
  1 +++ a/script/process/reaper
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../../config/boot'
  3 +require 'commands/process/reaper'
... ...
script/process/spawner 0 → 100755
  1 +++ a/script/process/spawner
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../../config/boot'
  3 +require 'commands/process/spawner'
... ...
script/runner 0 → 100755
  1 +++ a/script/runner
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/runner'
0 4 \ No newline at end of file
... ...
script/server 0 → 100755
  1 +++ a/script/server
... ... @@ -0,0 +1,3 @@
  1 +#!/usr/bin/env ruby
  2 +require File.dirname(__FILE__) + '/../config/boot'
  3 +require 'commands/server'
0 4 \ No newline at end of file
... ...
test/test_helper.rb 0 → 100644
  1 +++ a/test/test_helper.rb
... ... @@ -0,0 +1,28 @@
  1 +ENV["RAILS_ENV"] = "test"
  2 +require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
  3 +require 'test_help'
  4 +
  5 +class Test::Unit::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 + self.use_transactional_fixtures = true
  19 +
  20 + # Instantiated fixtures are slow, but give you @david where otherwise you
  21 + # would need people(:david). If you don't want to migrate your existing
  22 + # test cases which use the @david style and don't mind the speed hit (each
  23 + # instantiated fixtures translates to a database query per test method),
  24 + # then set this back to true.
  25 + self.use_instantiated_fixtures = false
  26 +
  27 + # Add more helper methods to be used by all tests here...
  28 +end
... ...
vendor/actionmailer 0 → 120000
  1 +++ a/vendor/actionmailer
... ... @@ -0,0 +1 @@
  1 +/usr/share/rails/actionmailer
0 2 \ No newline at end of file
... ...
vendor/actionpack 0 → 120000
  1 +++ a/vendor/actionpack
... ... @@ -0,0 +1 @@
  1 +/usr/share/rails/actionpack
0 2 \ No newline at end of file
... ...
vendor/actionwebservice 0 → 120000
  1 +++ a/vendor/actionwebservice
... ... @@ -0,0 +1 @@
  1 +/usr/share/rails/actionwebservice
0 2 \ No newline at end of file
... ...
vendor/activerecord 0 → 120000
  1 +++ a/vendor/activerecord
... ... @@ -0,0 +1 @@
  1 +/usr/share/rails/activerecord
0 2 \ No newline at end of file
... ...
vendor/activesupport 0 → 120000
  1 +++ a/vendor/activesupport
... ... @@ -0,0 +1 @@
  1 +/usr/share/rails/activesupport
0 2 \ No newline at end of file
... ...
vendor/rails 0 → 120000
No preview for this file type
vendor/railties 0 → 120000
  1 +++ a/vendor/railties
... ... @@ -0,0 +1 @@
  1 +/usr/share/rails/railties
0 2 \ No newline at end of file
... ...