[Ovirt-devel] [PATCH server 1/8] Update core app config for Rails 2.3.2

Joey Boggs jboggs at redhat.com
Mon Jul 20 15:56:25 UTC 2009


Jason Guiditta wrote:
> This patch updates app config and makes any
> required filename changes to support rails 2.3.2.
>
> Notable changes are:
> * application.rb -> application_controller.rb
> * cleanup of environment.rb
> * new config/initializers dir (this is where much of
>   the environment.rb stuff went)
> * gettext/rails -> gettext_rails
>
> Also note that applying this patch makes ovirt server
> no longer run on Fedora 10, as Rails 2.3.2 is not in that
> yum repo.  If you wish to run the app on F10 after this
> upgrade, you can probably still do so with:
> gem update rails
> but of course this has the potential to cause issues with
> yum and gem conflicting.
>
> Signed-off-by: Jason Guiditta <jason.guiditt at gmail.com>
> Signed-off-by: Jason Guiditta <jason.guiditta at gmail.com>
> ---
>  src/app/controllers/application.rb             |  200 --------
>  src/app/controllers/application_controller.rb  |  198 ++++++++
>  src/config.ru                                  |    7 +
>  src/config/boot.rb                             |   11 +-
>  src/config/environment.rb                      |   38 +--
>  src/config/initializers/backtrace_silencers.rb |    7 +
>  src/config/initializers/inflections.rb         |   10 +
>  src/config/initializers/mime_types.rb          |    5 +
>  src/config/initializers/new_rails_defaults.rb  |   19 +
>  src/config/initializers/session_store.rb       |   15 +
>  src/public/javascripts/controls.js             |  136 +++---
>  src/public/javascripts/dragdrop.js             |  175 ++++----
>  src/public/javascripts/effects.js              |  130 +++---
>  src/public/javascripts/prototype.js            |  629 ++++++++++++++----------
>  14 files changed, 861 insertions(+), 719 deletions(-)
>  delete mode 100644 src/app/controllers/application.rb
>  create mode 100644 src/app/controllers/application_controller.rb
>  create mode 100644 src/config.ru
>  create mode 100644 src/config/initializers/backtrace_silencers.rb
>  create mode 100644 src/config/initializers/inflections.rb
>  create mode 100644 src/config/initializers/mime_types.rb
>  create mode 100644 src/config/initializers/new_rails_defaults.rb
>  create mode 100644 src/config/initializers/session_store.rb
>
> diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb
> deleted file mode 100644
> index e50f71e..0000000
> --- a/src/app/controllers/application.rb
> +++ /dev/null
> @@ -1,200 +0,0 @@
> -#
> -# Copyright (C) 2008 Red Hat, Inc.
> -# Written by Scott Seago <sseago at redhat.com>
> -#
> -# This program is free software; you can redistribute it and/or modify
> -# it under the terms of the GNU General Public License as published by
> -# the Free Software Foundation; version 2 of the License.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program; if not, write to the Free Software
> -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
> -# MA  02110-1301, USA.  A copy of the GNU General Public License is
> -# also available at http://www.gnu.org/copyleft/gpl.html.
> -
> -# Filters added to this controller apply to all controllers in the application.
> -# Likewise, all the methods added will be available for all controllers.
> -
> -
> -class ApplicationController < ActionController::Base
> -  # FIXME: once all controller classes include this, remove here
> -  include ApplicationService
> -
> -  # Pick a unique cookie name to distinguish our session data from others'
> -  session :session_key => '_ovirt_session_id'
> -  init_gettext "ovirt"
> -  layout :choose_layout
> -
> -  before_filter :is_logged_in, :get_help_section
> -
> -  # General error handlers, must be in order from least specific
> -  # to most specific
> -  rescue_from Exception, :with => :handle_general_error
> -  rescue_from PermissionError, :with => :handle_perm_error
> -  rescue_from ActionError, :with => :handle_action_error
> -  rescue_from PartialSuccessError, :with => :handle_partial_success_error
> -
> -  def choose_layout
> -    if(params[:component_layout])
> -      return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux'
> -    end
> -    return 'redux'
> -  end
> -
> -  def is_logged_in
> -    redirect_to(:controller => "/login", :action => "login") unless get_login_user
> -  end
> -
> -  def get_help_section
> -      help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ])
> -      @help_section = help ? help.section : ""
> -      if @help_section.index('#')
> -        help_sections = @help_section.split('#')
> -        @help_section = help_sections[0]
> -        @anchor = help_sections[1]
> -      else
> -        @help_section = @help_section
> -        @anchor = ""
> -      end
> -  end
> -
> -  def get_login_user
> -    (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin"
> -  end
> -
> -  protected
> -  # permissions checking
> -
> -  def handle_perm_error(error)
> -    handle_error(:error => error, :status => :forbidden,
> -                 :title => "Access denied")
> -  end
> -
> -  def handle_partial_success_error(error)
> -    failures_arr = error.failures.collect do |resource, reason|
> -      if resource.respond_to?(:display_name)
> -        resource.display_name + ": " + reason
> -      else
> -        reason
> -      end
> -    end
> -    @successes = error.successes
> -    @failures = error.failures
> -    handle_error(:error => error, :status => :ok,
> -                 :message => error.message + ": " + failures_arr.join(", "),
> -                 :title => "Some actions failed")
> -  end
> -
> -  def handle_action_error(error)
> -    handle_error(:error => error, :status => :conflict,
> -                 :title => "Action Error")
> -  end
> -
> -  def handle_general_error(error)
> -    flash[:errmsg] = error.message
> -    handle_error(:error => error, :status => :internal_server_error,
> -                 :title => "Internal Server Error")
> -  end
> -
> -  def handle_error(hash)
> -    log_error(hash[:error]) if hash[:error]
> -    msg = hash[:message] || hash[:error].message
> -    title = hash[:title] || "Internal Server Error"
> -    status = hash[:status] || :internal_server_error
> -    respond_to do |format|
> -      format.html { html_error_page(title, msg) }
> -      format.json { render :json => json_error_hash(msg, status) }
> -      format.xml { render :xml => xml_errors(msg), :status => status }
> -    end
> -  end
> -
> -  def html_error_page(title, msg)
> -    @title = title
> -    @errmsg = msg
> -    @ajax = params[:ajax]
> -    @nolayout = params[:nolayout]
> -    if @layout
> -      render :layout => @layout
> -    elsif @ajax
> -      render :template => 'layouts/popup-error', :layout => 'tabs-and-content'
> -    elsif @nolayout
> -      render :template => 'layouts/popup-error', :layout => 'help-and-content'
> -    else
> -      render :template => 'layouts/popup-error', :layout => 'popup'
> -    end
> -  end
> -
> -  # don't define find_opts for array inputs
> -  def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id)
> -    page = params[:page].to_i
> -    paginate_opts = {:page => page,
> -                     :order => "#{params[:sortname]} #{params[:sortorder]}",
> -                     :per_page => params[:rp]}
> -    arg_list << find_opts.merge(paginate_opts)
> -    item_list = full_items.paginate(*arg_list)
> -    json_hash = {}
> -    json_hash[:page] = page
> -    json_hash[:total] = item_list.total_entries
> -    json_hash[:rows] = item_list.collect do |item|
> -      item_hash = {}
> -      item_hash[:id] = item.send(id_method)
> -      item_hash[:cell] = attributes.collect do |attr|
> -        if attr.is_a? Array
> -          value = item
> -          attr.each { |attr_item| value = (value.nil? ? nil : value.send(attr_item))}
> -          value
> -        else
> -          item.send(attr)
> -        end
> -      end
> -      item_hash
> -    end
> -    json_hash
> -  end
> -
> -  # json_list is a helper method used to format data for paginated flexigrid tables
> -  #
> -  # FIXME: what is the intent of this comment? don't define find_opts for array inputs
> -  def json_list(full_items, attributes, arg_list=[], find_opts={}, id_method=:id)
> -    render :json => json_hash(full_items, attributes, arg_list, find_opts, id_method).to_json
> -  end
> -
> -  private
> -  def json_error_hash(msg, status)
> -    json = {}
> -    json[:success] = (status == :ok)
> -    json.merge!(instance_errors)
> -    # There's a potential issue here: if we add :errors for an object
> -    # that the view won't generate inline error messages for, the user
> -    # won't get any indication what the error is. But if we set :alert
> -    # unconditionally, the user will get validation errors twice: once
> -    # inline in the form, and once in the flash
> -    json[:alert] = msg unless json[:errors]
> -    return json
> -  end
> -
> -  def xml_errors(msg)
> -    xml = {}
> -    xml[:message] = msg
> -    xml.merge!(instance_errors)
> -    return xml
> -  end
> -
> -  def instance_errors
> -    hash = {}
> -    instance_variables.each do |ivar|
> -      val = instance_variable_get(ivar)
> -      if val && val.respond_to?(:errors) && val.errors.size > 0
> -        hash[:object] = ivar[1, ivar.size]
> -        hash[:errors] ||= []
> -        hash[:errors] += val.errors.localize_error_messages.to_a
> -      end
> -    end
> -    return hash
> -  end
> -end
> diff --git a/src/app/controllers/application_controller.rb b/src/app/controllers/application_controller.rb
> new file mode 100644
> index 0000000..5ce625a
> --- /dev/null
> +++ b/src/app/controllers/application_controller.rb
> @@ -0,0 +1,198 @@
> +#
> +# Copyright (C) 2008 Red Hat, Inc.
> +# Written by Scott Seago <sseago at redhat.com>
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; version 2 of the License.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
> +# MA  02110-1301, USA.  A copy of the GNU General Public License is
> +# also available at http://www.gnu.org/copyleft/gpl.html.
> +
> +# Filters added to this controller apply to all controllers in the application.
> +# Likewise, all the methods added will be available for all controllers.
> +
> +
> +class ApplicationController < ActionController::Base
> +  # FIXME: once all controller classes include this, remove here
> +  include ApplicationService
> +
> +  init_gettext "ovirt"
> +  layout :choose_layout
> +
> +  before_filter :is_logged_in, :get_help_section
> +
> +  # General error handlers, must be in order from least specific
> +  # to most specific
> +  rescue_from Exception, :with => :handle_general_error
> +  rescue_from PermissionError, :with => :handle_perm_error
> +  rescue_from ActionError, :with => :handle_action_error
> +  rescue_from PartialSuccessError, :with => :handle_partial_success_error
> +
> +  def choose_layout
> +    if(params[:component_layout])
> +      return (ENV["RAILS_ENV"] != "production")?'components/' << params[:component_layout]:'redux'
> +    end
> +    return 'redux'
> +  end
> +
> +  def is_logged_in
> +    redirect_to(:controller => "/login", :action => "login") unless get_login_user
> +  end
> +
> +  def get_help_section
> +      help = HelpSection.find(:first, :conditions => [ "controller = ? AND action = ?", controller_name, action_name ])
> +      @help_section = help ? help.section : ""
> +      if @help_section.index('#')
> +        help_sections = @help_section.split('#')
> +        @help_section = help_sections[0]
> +        @anchor = help_sections[1]
> +      else
> +        @help_section = @help_section
> +        @anchor = ""
> +      end
> +  end
> +
> +  def get_login_user
> +    (ENV["RAILS_ENV"] == "production") ? session[:user] : "ovirtadmin"
> +  end
> +
> +  protected
> +  # permissions checking
> +
> +  def handle_perm_error(error)
> +    handle_error(:error => error, :status => :forbidden,
> +                 :title => "Access denied")
> +  end
> +
> +  def handle_partial_success_error(error)
> +    failures_arr = error.failures.collect do |resource, reason|
> +      if resource.respond_to?(:display_name)
> +        resource.display_name + ": " + reason
> +      else
> +        reason
> +      end
> +    end
> +    @successes = error.successes
> +    @failures = error.failures
> +    handle_error(:error => error, :status => :ok,
> +                 :message => error.message + ": " + failures_arr.join(", "),
> +                 :title => "Some actions failed")
> +  end
> +
> +  def handle_action_error(error)
> +    handle_error(:error => error, :status => :conflict,
> +                 :title => "Action Error")
> +  end
> +
> +  def handle_general_error(error)
> +    flash[:errmsg] = error.message
> +    handle_error(:error => error, :status => :internal_server_error,
> +                 :title => "Internal Server Error")
> +  end
> +
> +  def handle_error(hash)
> +    log_error(hash[:error]) if hash[:error]
> +    msg = hash[:message] || hash[:error].message
> +    title = hash[:title] || "Internal Server Error"
> +    status = hash[:status] || :internal_server_error
> +    respond_to do |format|
> +      format.html { html_error_page(title, msg) }
> +      format.json { render :json => json_error_hash(msg, status) }
> +      format.xml { render :xml => xml_errors(msg), :status => status }
> +    end
> +  end
> +
> +  def html_error_page(title, msg)
> +    @title = title
> +    @errmsg = msg
> +    @ajax = params[:ajax]
> +    @nolayout = params[:nolayout]
> +    if @layout
> +      render :layout => @layout
> +    elsif @ajax
> +      render :template => 'layouts/popup-error', :layout => 'tabs-and-content'
> +    elsif @nolayout
> +      render :template => 'layouts/popup-error', :layout => 'help-and-content'
> +    else
> +      render :template => 'layouts/popup-error', :layout => 'popup'
> +    end
> +  end
> +
> +  # don't define find_opts for array inputs
> +  def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id)
> +    page = params[:page].to_i
> +    paginate_opts = {:page => page,
> +                     :order => "#{params[:sortname]} #{params[:sortorder]}",
> +                     :per_page => params[:rp]}
> +    arg_list << find_opts.merge(paginate_opts)
> +    item_list = full_items.paginate(*arg_list)
> +    json_hash = {}
> +    json_hash[:page] = page
> +    json_hash[:total] = item_list.total_entries
> +    json_hash[:rows] = item_list.collect do |item|
> +      item_hash = {}
> +      item_hash[:id] = item.send(id_method)
> +      item_hash[:cell] = attributes.collect do |attr|
> +        if attr.is_a? Array
> +          value = item
> +          attr.each { |attr_item| value = (value.nil? ? nil : value.send(attr_item))}
> +          value
> +        else
> +          item.send(attr)
> +        end
> +      end
> +      item_hash
> +    end
> +    json_hash
> +  end
> +
> +  # json_list is a helper method used to format data for paginated flexigrid tables
> +  #
> +  # FIXME: what is the intent of this comment? don't define find_opts for array inputs
> +  def json_list(full_items, attributes, arg_list=[], find_opts={}, id_method=:id)
> +    render :json => json_hash(full_items, attributes, arg_list, find_opts, id_method).to_json
> +  end
> +
> +  private
> +  def json_error_hash(msg, status)
> +    json = {}
> +    json[:success] = (status == :ok)
> +    json.merge!(instance_errors)
> +    # There's a potential issue here: if we add :errors for an object
> +    # that the view won't generate inline error messages for, the user
> +    # won't get any indication what the error is. But if we set :alert
> +    # unconditionally, the user will get validation errors twice: once
> +    # inline in the form, and once in the flash
> +    json[:alert] = msg unless json[:errors]
> +    return json
> +  end
> +
> +  def xml_errors(msg)
> +    xml = {}
> +    xml[:message] = msg
> +    xml.merge!(instance_errors)
> +    return xml
> +  end
> +
> +  def instance_errors
> +    hash = {}
> +    instance_variables.each do |ivar|
> +      val = instance_variable_get(ivar)
> +      if val && val.respond_to?(:errors) && val.errors.size > 0
> +        hash[:object] = ivar[1, ivar.size]
> +        hash[:errors] ||= []
> +        hash[:errors] += val.errors.localize_error_messages.to_a
> +      end
> +    end
> +    return hash
> +  end
> +end
> diff --git a/src/config.ru b/src/config.ru
> new file mode 100644
> index 0000000..acbfe4e
> --- /dev/null
> +++ b/src/config.ru
> @@ -0,0 +1,7 @@
> +# Rack Dispatcher
> +
> +# Require your environment file to bootstrap Rails
> +require File.dirname(__FILE__) + '/config/environment'
> +
> +# Dispatch the request
> +run ActionController::Dispatcher.new
> diff --git a/src/config/boot.rb b/src/config/boot.rb
> index cd21fb9..0ad0f78 100644
> --- a/src/config/boot.rb
> +++ b/src/config/boot.rb
> @@ -44,6 +44,7 @@ module Rails
>      def load_initializer
>        require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
>        Rails::Initializer.run(:install_gem_spec_stubs)
> +      Rails::GemDependency.add_frozen_gem_path
>      end
>    end
>  
> @@ -67,7 +68,7 @@ module Rails
>  
>      class << self
>        def rubygems_version
> -        Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
> +        Gem::RubyGemsVersion rescue nil
>        end
>  
>        def gem_version
> @@ -82,14 +83,14 @@ module Rails
>  
>        def load_rubygems
>          require 'rubygems'
> -
> -        unless rubygems_version >= '0.9.4'
> -          $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
> +        min_version = '1.3.1'
> +        unless rubygems_version >= min_version
> +          $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
>            exit 1
>          end
>  
>        rescue LoadError
> -        $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
> +        $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
>          exit 1
>        end
>  
> diff --git a/src/config/environment.rb b/src/config/environment.rb
> index 98a2fbb..9d9a66d 100644
> --- a/src/config/environment.rb
> +++ b/src/config/environment.rb
> @@ -19,9 +19,8 @@
>  
>  # Be sure to restart your web server when you modify this file.
>  
> -# Uncomment below to force Rails into production mode when 
> -# you don't control web/app server and can't set it the proper way
> -# ENV['RAILS_ENV'] ||= 'production'
> +# Specifies gem version of Rails to use when vendor/rails is not present
> +RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION
>  
>  # Bootstrap the Rails environment, frameworks, and default configuration
>  require File.join(File.dirname(__FILE__), 'boot')
> @@ -42,7 +41,7 @@ Rails::Initializer.run do |config|
>    # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
>    # config.gem "aws-s3", :lib => "aws/s3"
>    config.gem "cobbler"
> -  config.gem "gettext", :lib => "gettext/rails"
> +  config.gem "gettext", :lib => "gettext_rails"
>  
>    # Only load the plugins named here, in the order given. By default, all plugins
>    # in vendor/plugins are loaded in alphabetical order.
> @@ -61,20 +60,6 @@ Rails::Initializer.run do |config|
>    # Run "rake -D time" for a list of tasks for finding time zone names. Uncomment to use default local time.
>    config.time_zone = 'UTC'
>  
> -  # Your secret key for verifying cookie session data integrity.
> -  # If you change this key, all old sessions will become invalid!
> -  # Make sure the secret is at least 30 characters and all random,
> -  # no regular words or you'll be exposed to dictionary attacks.
> -  config.action_controller.session = {
> -    :session_key => "_ovirt_session_id",
> -    :secret => "a covert ovirt phrase or some such"
> -  }
> -
> -  # Use the database for sessions instead of the cookie-based default,
> -  # which shouldn't be used to store highly confidential information
> -  # (create the session table with "rake db:sessions:create")
> -  config.action_controller.session_store = :active_record_store
> -
>    # Use SQL instead of Active Record's schema dumper when creating the test database.
>    # This is necessary if your schema can't be completely dumped by the schema dumper,
>    # like if you have constraints or database-specific column types
> @@ -83,17 +68,8 @@ Rails::Initializer.run do |config|
>    # Activate observers that should always be running
>    # config.active_record.observers = :cacher, :garbage_collector
>    config.active_record.observers = :host_observer, :vm_observer
> -end
>  
> -# Add new inflection rules using the following format
> -# (all these examples are active by default):
> -# Inflector.inflections do |inflect|
> -#   inflect.plural /^(ox)$/i, '\1en'
> -#   inflect.singular /^(ox)en/i, '\1'
> -#   inflect.irregular 'person', 'people'
> -#   inflect.uncountable %w( fish sheep )
> -# end
> -
> -# Add new mime types for use in respond_to blocks:
> -# Mime::Type.register "text/richtext", :rtf
> -# Mime::Type.register "application/x-mobile", :mobile
> +  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
> +  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
> +  # config.i18n.default_locale = :de
> +end
> diff --git a/src/config/initializers/backtrace_silencers.rb b/src/config/initializers/backtrace_silencers.rb
> new file mode 100644
> index 0000000..c2169ed
> --- /dev/null
> +++ b/src/config/initializers/backtrace_silencers.rb
> @@ -0,0 +1,7 @@
> +# Be sure to restart your server when you modify this file.
> +
> +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
> +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
> +
> +# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
> +# Rails.backtrace_cleaner.remove_silencers!
> \ No newline at end of file
> diff --git a/src/config/initializers/inflections.rb b/src/config/initializers/inflections.rb
> new file mode 100644
> index 0000000..9e8b013
> --- /dev/null
> +++ b/src/config/initializers/inflections.rb
> @@ -0,0 +1,10 @@
> +# Be sure to restart your server when you modify this file.
> +
> +# Add new inflection rules using the following format
> +# (all these examples are active by default):
> +# ActiveSupport::Inflector.inflections do |inflect|
> +#   inflect.plural /^(ox)$/i, '\1en'
> +#   inflect.singular /^(ox)en/i, '\1'
> +#   inflect.irregular 'person', 'people'
> +#   inflect.uncountable %w( fish sheep )
> +# end
> diff --git a/src/config/initializers/mime_types.rb b/src/config/initializers/mime_types.rb
> new file mode 100644
> index 0000000..72aca7e
> --- /dev/null
> +++ b/src/config/initializers/mime_types.rb
> @@ -0,0 +1,5 @@
> +# Be sure to restart your server when you modify this file.
> +
> +# Add new mime types for use in respond_to blocks:
> +# Mime::Type.register "text/richtext", :rtf
> +# Mime::Type.register_alias "text/html", :iphone
> diff --git a/src/config/initializers/new_rails_defaults.rb b/src/config/initializers/new_rails_defaults.rb
> new file mode 100644
> index 0000000..8ec3186
> --- /dev/null
> +++ b/src/config/initializers/new_rails_defaults.rb
> @@ -0,0 +1,19 @@
> +# Be sure to restart your server when you modify this file.
> +
> +# These settings change the behavior of Rails 2 apps and will be defaults
> +# for Rails 3. You can remove this initializer when Rails 3 is released.
> +
> +if defined?(ActiveRecord)
> +  # Include Active Record class name as root for JSON serialized output.
> +  ActiveRecord::Base.include_root_in_json = true
> +
> +  # Store the full class name (including module namespace) in STI type column.
> +  ActiveRecord::Base.store_full_sti_class = true
> +end
> +
> +# Use ISO 8601 format for JSON serialized times and dates.
> +ActiveSupport.use_standard_json_time_format = true
> +
> +# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
> +# if you're including raw json in an HTML page.
> +ActiveSupport.escape_html_entities_in_json = false
> \ No newline at end of file
> diff --git a/src/config/initializers/session_store.rb b/src/config/initializers/session_store.rb
> new file mode 100644
> index 0000000..64dbf51
> --- /dev/null
> +++ b/src/config/initializers/session_store.rb
> @@ -0,0 +1,15 @@
> +# Be sure to restart your server when you modify this file.
> +
> +# Your secret key for verifying cookie session data integrity.
> +# If you change this key, all old sessions will become invalid!
> +# Make sure the secret is at least 30 characters and all random,
> +# no regular words or you'll be exposed to dictionary attacks.
> +ActionController::Base.session = {
> +  :key         => '_rails23-app_session',
> +  :secret      => '41713a6b4a92b5b7af55314d2ef6fc499a177269ea91b9fdaa7d15c42e1234b70b32f52278ae26b774b38dbdfeb7d078585d10f643e81b6615d32410f192f1de'
> +}
> +
> +# Use the database for sessions instead of the cookie-based default,
> +# which shouldn't be used to store highly confidential information
> +# (create the session table with "rake db:sessions:create")
> +ActionController::Base.session_store = :active_record_store
> diff --git a/src/public/javascripts/controls.js b/src/public/javascripts/controls.js
> index 1de3b29..ca29aef 100644
> --- a/src/public/javascripts/controls.js
> +++ b/src/public/javascripts/controls.js
> @@ -1,22 +1,22 @@
>  // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
> -//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
> -//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
> +//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
> +//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
>  // Contributors:
>  //  Richard Livsey
>  //  Rahul Bhargava
>  //  Rob Wills
> -// 
> +//
>  // script.aculo.us is freely distributable under the terms of an MIT-style license.
>  // For details, see the script.aculo.us web site: http://script.aculo.us/
>  
> -// Autocompleter.Base handles all the autocompletion functionality 
> +// Autocompleter.Base handles all the autocompletion functionality
>  // that's independent of the data source for autocompletion. This
>  // includes drawing the autocompletion menu, observing keyboard
>  // and mouse events, and similar.
>  //
> -// Specific autocompleters need to provide, at the very least, 
> +// Specific autocompleters need to provide, at the very least,
>  // a getUpdatedChoices function that will be invoked every time
> -// the text inside the monitored textbox changes. This method 
> +// the text inside the monitored textbox changes. This method
>  // should get the text for which to provide autocompletion by
>  // invoking this.getToken(), NOT by directly accessing
>  // this.element.value. This is to allow incremental tokenized
> @@ -30,23 +30,23 @@
>  // will incrementally autocomplete with a comma as the token.
>  // Additionally, ',' in the above example can be replaced with
>  // a token array, e.g. { tokens: [',', '\n'] } which
> -// enables autocompletion on multiple tokens. This is most 
> -// useful when one of the tokens is \n (a newline), as it 
> +// enables autocompletion on multiple tokens. This is most
> +// useful when one of the tokens is \n (a newline), as it
>  // allows smart autocompletion after linebreaks.
>  
>  if(typeof Effect == 'undefined')
>    throw("controls.js requires including script.aculo.us' effects.js library");
>  
> -var Autocompleter = { }
> +var Autocompleter = { };
>  Autocompleter.Base = Class.create({
>    baseInitialize: function(element, update, options) {
> -    element          = $(element)
> +    element          = $(element);
>      this.element     = element;
> -    this.update      = $(update);  
> -    this.hasFocus    = false; 
> -    this.changed     = false; 
> -    this.active      = false; 
> -    this.index       = 0;     
> +    this.update      = $(update);
> +    this.hasFocus    = false;
> +    this.changed     = false;
> +    this.active      = false;
> +    this.index       = 0;
>      this.entryCount  = 0;
>      this.oldElementValue = this.element.value;
>  
> @@ -59,28 +59,28 @@ Autocompleter.Base = Class.create({
>      this.options.tokens       = this.options.tokens || [];
>      this.options.frequency    = this.options.frequency || 0.4;
>      this.options.minChars     = this.options.minChars || 1;
> -    this.options.onShow       = this.options.onShow || 
> -      function(element, update){ 
> +    this.options.onShow       = this.options.onShow ||
> +      function(element, update){
>          if(!update.style.position || update.style.position=='absolute') {
>            update.style.position = 'absolute';
>            Position.clone(element, update, {
> -            setHeight: false, 
> +            setHeight: false,
>              offsetTop: element.offsetHeight
>            });
>          }
>          Effect.Appear(update,{duration:0.15});
>        };
> -    this.options.onHide = this.options.onHide || 
> +    this.options.onHide = this.options.onHide ||
>        function(element, update){ new Effect.Fade(update,{duration:0.15}) };
>  
> -    if(typeof(this.options.tokens) == 'string') 
> +    if(typeof(this.options.tokens) == 'string')
>        this.options.tokens = new Array(this.options.tokens);
>      // Force carriage returns as token delimiters anyway
>      if (!this.options.tokens.include('\n'))
>        this.options.tokens.push('\n');
>  
>      this.observer = null;
> -    
> +
>      this.element.setAttribute('autocomplete','off');
>  
>      Element.hide(this.update);
> @@ -91,10 +91,10 @@ Autocompleter.Base = Class.create({
>  
>    show: function() {
>      if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
> -    if(!this.iefix && 
> +    if(!this.iefix &&
>        (Prototype.Browser.IE) &&
>        (Element.getStyle(this.update, 'position')=='absolute')) {
> -      new Insertion.After(this.update, 
> +      new Insertion.After(this.update,
>         '<iframe id="' + this.update.id + '_iefix" '+
>         'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
>         'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
> @@ -102,7 +102,7 @@ Autocompleter.Base = Class.create({
>      }
>      if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
>    },
> -  
> +
>    fixIEOverlapping: function() {
>      Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
>      this.iefix.style.zIndex = 1;
> @@ -150,15 +150,15 @@ Autocompleter.Base = Class.create({
>           Event.stop(event);
>           return;
>        }
> -     else 
> -       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
> +     else
> +       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
>           (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
>  
>      this.changed = true;
>      this.hasFocus = true;
>  
>      if(this.observer) clearTimeout(this.observer);
> -      this.observer = 
> +      this.observer =
>          setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
>    },
>  
> @@ -170,35 +170,35 @@ Autocompleter.Base = Class.create({
>  
>    onHover: function(event) {
>      var element = Event.findElement(event, 'LI');
> -    if(this.index != element.autocompleteIndex) 
> +    if(this.index != element.autocompleteIndex)
>      {
>          this.index = element.autocompleteIndex;
>          this.render();
>      }
>      Event.stop(event);
>    },
> -  
> +
>    onClick: function(event) {
>      var element = Event.findElement(event, 'LI');
>      this.index = element.autocompleteIndex;
>      this.selectEntry();
>      this.hide();
>    },
> -  
> +
>    onBlur: function(event) {
>      // needed to make click events working
>      setTimeout(this.hide.bind(this), 250);
>      this.hasFocus = false;
> -    this.active = false;     
> -  }, 
> -  
> +    this.active = false;
> +  },
> +
>    render: function() {
>      if(this.entryCount > 0) {
>        for (var i = 0; i < this.entryCount; i++)
> -        this.index==i ? 
> -          Element.addClassName(this.getEntry(i),"selected") : 
> +        this.index==i ?
> +          Element.addClassName(this.getEntry(i),"selected") :
>            Element.removeClassName(this.getEntry(i),"selected");
> -      if(this.hasFocus) { 
> +      if(this.hasFocus) {
>          this.show();
>          this.active = true;
>        }
> @@ -207,27 +207,27 @@ Autocompleter.Base = Class.create({
>        this.hide();
>      }
>    },
> -  
> +
>    markPrevious: function() {
> -    if(this.index > 0) this.index--
> +    if(this.index > 0) this.index--;
>        else this.index = this.entryCount-1;
>      this.getEntry(this.index).scrollIntoView(true);
>    },
> -  
> +
>    markNext: function() {
> -    if(this.index < this.entryCount-1) this.index++
> +    if(this.index < this.entryCount-1) this.index++;
>        else this.index = 0;
>      this.getEntry(this.index).scrollIntoView(false);
>    },
> -  
> +
>    getEntry: function(index) {
>      return this.update.firstChild.childNodes[index];
>    },
> -  
> +
>    getCurrentEntry: function() {
>      return this.getEntry(this.index);
>    },
> -  
> +
>    selectEntry: function() {
>      this.active = false;
>      this.updateElement(this.getCurrentEntry());
> @@ -244,7 +244,7 @@ Autocompleter.Base = Class.create({
>        if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
>      } else
>        value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
> -    
> +
>      var bounds = this.getTokenBounds();
>      if (bounds[0] != -1) {
>        var newValue = this.element.value.substr(0, bounds[0]);
> @@ -257,7 +257,7 @@ Autocompleter.Base = Class.create({
>      }
>      this.oldElementValue = this.element.value;
>      this.element.focus();
> -    
> +
>      if (this.options.afterUpdateElement)
>        this.options.afterUpdateElement(this.element, selectedElement);
>    },
> @@ -269,20 +269,20 @@ Autocompleter.Base = Class.create({
>        Element.cleanWhitespace(this.update.down());
>  
>        if(this.update.firstChild && this.update.down().childNodes) {
> -        this.entryCount = 
> +        this.entryCount =
>            this.update.down().childNodes.length;
>          for (var i = 0; i < this.entryCount; i++) {
>            var entry = this.getEntry(i);
>            entry.autocompleteIndex = i;
>            this.addObservers(entry);
>          }
> -      } else { 
> +      } else {
>          this.entryCount = 0;
>        }
>  
>        this.stopIndicator();
>        this.index = 0;
> -      
> +
>        if(this.entryCount==1 && this.options.autoSelect) {
>          this.selectEntry();
>          this.hide();
> @@ -298,7 +298,7 @@ Autocompleter.Base = Class.create({
>    },
>  
>    onObserverEvent: function() {
> -    this.changed = false;   
> +    this.changed = false;
>      this.tokenBounds = null;
>      if(this.getToken().length>=this.options.minChars) {
>        this.getUpdatedChoices();
> @@ -358,7 +358,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
>      this.options.parameters = this.options.callback ?
>        this.options.callback(this.element, entry) : entry;
>  
> -    if(this.options.defaultParams) 
> +    if(this.options.defaultParams)
>        this.options.parameters += '&' + this.options.defaultParams;
>  
>      new Ajax.Request(this.url, this.options);
> @@ -382,7 +382,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
>  // - choices - How many autocompletion choices to offer
>  //
>  // - partialSearch - If false, the autocompleter will match entered
> -//                    text only at the beginning of strings in the 
> +//                    text only at the beginning of strings in the
>  //                    autocomplete array. Defaults to true, which will
>  //                    match text at the beginning of any *word* in the
>  //                    strings in the autocomplete array. If you want to
> @@ -399,7 +399,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
>  // - ignoreCase - Whether to ignore case when autocompleting.
>  //                 Defaults to true.
>  //
> -// It's possible to pass in a custom function as the 'selector' 
> +// It's possible to pass in a custom function as the 'selector'
>  // option, if you prefer to write your own autocompletion logic.
>  // In that case, the other options above will not apply unless
>  // you support them.
> @@ -427,20 +427,20 @@ Autocompleter.Local = Class.create(Autocompleter.Base, {
>          var entry     = instance.getToken();
>          var count     = 0;
>  
> -        for (var i = 0; i < instance.options.array.length &&  
> -          ret.length < instance.options.choices ; i++) { 
> +        for (var i = 0; i < instance.options.array.length &&
> +          ret.length < instance.options.choices ; i++) {
>  
>            var elem = instance.options.array[i];
> -          var foundPos = instance.options.ignoreCase ? 
> -            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
> +          var foundPos = instance.options.ignoreCase ?
> +            elem.toLowerCase().indexOf(entry.toLowerCase()) :
>              elem.indexOf(entry);
>  
>            while (foundPos != -1) {
> -            if (foundPos == 0 && elem.length != entry.length) { 
> -              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
> +            if (foundPos == 0 && elem.length != entry.length) {
> +              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
>                  elem.substr(entry.length) + "</li>");
>                break;
> -            } else if (entry.length >= instance.options.partialChars && 
> +            } else if (entry.length >= instance.options.partialChars &&
>                instance.options.partialSearch && foundPos != -1) {
>                if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
>                  partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
> @@ -450,14 +450,14 @@ Autocompleter.Local = Class.create(Autocompleter.Base, {
>                }
>              }
>  
> -            foundPos = instance.options.ignoreCase ? 
> -              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
> +            foundPos = instance.options.ignoreCase ?
> +              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
>                elem.indexOf(entry, foundPos + 1);
>  
>            }
>          }
>          if (partial.length)
> -          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
> +          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
>          return "<ul>" + ret.join('') + "</ul>";
>        }
>      }, options || { });
> @@ -474,7 +474,7 @@ Field.scrollFreeActivate = function(field) {
>    setTimeout(function() {
>      Field.activate(field);
>    }, 1);
> -}
> +};
>  
>  Ajax.InPlaceEditor = Class.create({
>    initialize: function(element, url, options) {
> @@ -604,7 +604,7 @@ Ajax.InPlaceEditor = Class.create({
>      this.triggerCallback('onEnterHover');
>    },
>    getText: function() {
> -    return this.element.innerHTML;
> +    return this.element.innerHTML.unescapeHTML();
>    },
>    handleAJAXFailure: function(transport) {
>      this.triggerCallback('onFailure', transport);
> @@ -780,7 +780,7 @@ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
>        onSuccess: function(transport) {
>          var js = transport.responseText.strip();
>          if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
> -          throw 'Server returned an invalid collection representation.';
> +          throw('Server returned an invalid collection representation.');
>          this._collection = eval(js);
>          this.checkForExternalText();
>        }.bind(this),
> @@ -937,7 +937,7 @@ Ajax.InPlaceCollectionEditor.DefaultOptions = {
>    loadingCollectionText: 'Loading options...'
>  };
>  
> -// Delayed observer, like Form.Element.Observer, 
> +// Delayed observer, like Form.Element.Observer,
>  // but waits for delay after last key input
>  // Ideal for live-search fields
>  
> @@ -947,7 +947,7 @@ Form.Element.DelayedObserver = Class.create({
>      this.element   = $(element);
>      this.callback  = callback;
>      this.timer     = null;
> -    this.lastValue = $F(this.element); 
> +    this.lastValue = $F(this.element);
>      Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
>    },
>    delayedListener: function(event) {
> @@ -960,4 +960,4 @@ Form.Element.DelayedObserver = Class.create({
>      this.timer = null;
>      this.callback(this.element, $F(this.element));
>    }
> -});
> +});
> \ No newline at end of file
> diff --git a/src/public/javascripts/dragdrop.js b/src/public/javascripts/dragdrop.js
> index e2e7d4a..07229f9 100644
> --- a/src/public/javascripts/dragdrop.js
> +++ b/src/public/javascripts/dragdrop.js
> @@ -1,6 +1,6 @@
>  // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
> -//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi at oriontransfer.co.nz)
> -// 
> +//           (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi at oriontransfer.co.nz)
> +//
>  // script.aculo.us is freely distributable under the terms of an MIT-style license.
>  // For details, see the script.aculo.us web site: http://script.aculo.us/
>  
> @@ -32,7 +32,7 @@ var Droppables = {
>          options._containers.push($(containment));
>        }
>      }
> -    
> +
>      if(options.accept) options.accept = [options.accept].flatten();
>  
>      Element.makePositioned(element); // fix IE
> @@ -40,34 +40,34 @@ var Droppables = {
>  
>      this.drops.push(options);
>    },
> -  
> +
>    findDeepestChild: function(drops) {
>      deepest = drops[0];
> -      
> +
>      for (i = 1; i < drops.length; ++i)
>        if (Element.isParent(drops[i].element, deepest.element))
>          deepest = drops[i];
> -    
> +
>      return deepest;
>    },
>  
>    isContained: function(element, drop) {
>      var containmentNode;
>      if(drop.tree) {
> -      containmentNode = element.treeNode; 
> +      containmentNode = element.treeNode;
>      } else {
>        containmentNode = element.parentNode;
>      }
>      return drop._containers.detect(function(c) { return containmentNode == c });
>    },
> -  
> +
>    isAffected: function(point, element, drop) {
>      return (
>        (drop.element!=element) &&
>        ((!drop._containers) ||
>          this.isContained(element, drop)) &&
>        ((!drop.accept) ||
> -        (Element.classNames(element).detect( 
> +        (Element.classNames(element).detect(
>            function(v) { return drop.accept.include(v) } ) )) &&
>        Position.within(drop.element, point[0], point[1]) );
>    },
> @@ -87,12 +87,12 @@ var Droppables = {
>    show: function(point, element) {
>      if(!this.drops.length) return;
>      var drop, affected = [];
> -    
> +
>      this.drops.each( function(drop) {
>        if(Droppables.isAffected(point, element, drop))
>          affected.push(drop);
>      });
> -        
> +
>      if(affected.length>0)
>        drop = Droppables.findDeepestChild(affected);
>  
> @@ -101,7 +101,7 @@ var Droppables = {
>        Position.within(drop.element, point[0], point[1]);
>        if(drop.onHover)
>          drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
> -      
> +
>        if (drop != this.last_active) Droppables.activate(drop);
>      }
>    },
> @@ -121,25 +121,25 @@ var Droppables = {
>      if(this.last_active)
>        this.deactivate(this.last_active);
>    }
> -}
> +};
>  
>  var Draggables = {
>    drags: [],
>    observers: [],
> -  
> +
>    register: function(draggable) {
>      if(this.drags.length == 0) {
>        this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
>        this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
>        this.eventKeypress  = this.keyPress.bindAsEventListener(this);
> -      
> +
>        Event.observe(document, "mouseup", this.eventMouseUp);
>        Event.observe(document, "mousemove", this.eventMouseMove);
>        Event.observe(document, "keypress", this.eventKeypress);
>      }
>      this.drags.push(draggable);
>    },
> -  
> +
>    unregister: function(draggable) {
>      this.drags = this.drags.reject(function(d) { return d==draggable });
>      if(this.drags.length == 0) {
> @@ -148,24 +148,24 @@ var Draggables = {
>        Event.stopObserving(document, "keypress", this.eventKeypress);
>      }
>    },
> -  
> +
>    activate: function(draggable) {
> -    if(draggable.options.delay) { 
> -      this._timeout = setTimeout(function() { 
> -        Draggables._timeout = null; 
> -        window.focus(); 
> -        Draggables.activeDraggable = draggable; 
> -      }.bind(this), draggable.options.delay); 
> +    if(draggable.options.delay) {
> +      this._timeout = setTimeout(function() {
> +        Draggables._timeout = null;
> +        window.focus();
> +        Draggables.activeDraggable = draggable;
> +      }.bind(this), draggable.options.delay);
>      } else {
>        window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
>        this.activeDraggable = draggable;
>      }
>    },
> -  
> +
>    deactivate: function() {
>      this.activeDraggable = null;
>    },
> -  
> +
>    updateDrag: function(event) {
>      if(!this.activeDraggable) return;
>      var pointer = [Event.pointerX(event), Event.pointerY(event)];
> @@ -173,36 +173,36 @@ var Draggables = {
>      // the same coordinates, prevent needless redrawing (moz bug?)
>      if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
>      this._lastPointer = pointer;
> -    
> +
>      this.activeDraggable.updateDrag(event, pointer);
>    },
> -  
> +
>    endDrag: function(event) {
> -    if(this._timeout) { 
> -      clearTimeout(this._timeout); 
> -      this._timeout = null; 
> +    if(this._timeout) {
> +      clearTimeout(this._timeout);
> +      this._timeout = null;
>      }
>      if(!this.activeDraggable) return;
>      this._lastPointer = null;
>      this.activeDraggable.endDrag(event);
>      this.activeDraggable = null;
>    },
> -  
> +
>    keyPress: function(event) {
>      if(this.activeDraggable)
>        this.activeDraggable.keyPress(event);
>    },
> -  
> +
>    addObserver: function(observer) {
>      this.observers.push(observer);
>      this._cacheObserverCallbacks();
>    },
> -  
> +
>    removeObserver: function(element) {  // element instead of observer fixes mem leaks
>      this.observers = this.observers.reject( function(o) { return o.element==element });
>      this._cacheObserverCallbacks();
>    },
> -  
> +
>    notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
>      if(this[eventName+'Count'] > 0)
>        this.observers.each( function(o) {
> @@ -210,7 +210,7 @@ var Draggables = {
>        });
>      if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
>    },
> -  
> +
>    _cacheObserverCallbacks: function() {
>      ['onStart','onEnd','onDrag'].each( function(eventName) {
>        Draggables[eventName+'Count'] = Draggables.observers.select(
> @@ -218,7 +218,7 @@ var Draggables = {
>        ).length;
>      });
>    }
> -}
> +};
>  
>  /*--------------------------------------------------------------------------*/
>  
> @@ -234,12 +234,12 @@ var Draggable = Class.create({
>        },
>        endeffect: function(element) {
>          var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
> -        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
> +        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
>            queue: {scope:'_draggable', position:'end'},
> -          afterFinish: function(){ 
> -            Draggable._dragging[element] = false 
> +          afterFinish: function(){
> +            Draggable._dragging[element] = false
>            }
> -        }); 
> +        });
>        },
>        zindex: 1000,
>        revert: false,
> @@ -250,57 +250,57 @@ var Draggable = Class.create({
>        snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
>        delay: 0
>      };
> -    
> +
>      if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
>        Object.extend(defaults, {
>          starteffect: function(element) {
>            element._opacity = Element.getOpacity(element);
>            Draggable._dragging[element] = true;
> -          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
> +          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
>          }
>        });
> -    
> +
>      var options = Object.extend(defaults, arguments[1] || { });
>  
>      this.element = $(element);
> -    
> +
>      if(options.handle && Object.isString(options.handle))
>        this.handle = this.element.down('.'+options.handle, 0);
> -    
> +
>      if(!this.handle) this.handle = $(options.handle);
>      if(!this.handle) this.handle = this.element;
> -    
> +
>      if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
>        options.scroll = $(options.scroll);
>        this._isScrollChild = Element.childOf(this.element, options.scroll);
>      }
>  
> -    Element.makePositioned(this.element); // fix IE    
> +    Element.makePositioned(this.element); // fix IE
>  
>      this.options  = options;
> -    this.dragging = false;   
> +    this.dragging = false;
>  
>      this.eventMouseDown = this.initDrag.bindAsEventListener(this);
>      Event.observe(this.handle, "mousedown", this.eventMouseDown);
> -    
> +
>      Draggables.register(this);
>    },
> -  
> +
>    destroy: function() {
>      Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
>      Draggables.unregister(this);
>    },
> -  
> +
>    currentDelta: function() {
>      return([
>        parseInt(Element.getStyle(this.element,'left') || '0'),
>        parseInt(Element.getStyle(this.element,'top') || '0')]);
>    },
> -  
> +
>    initDrag: function(event) {
>      if(!Object.isUndefined(Draggable._dragging[this.element]) &&
>        Draggable._dragging[this.element]) return;
> -    if(Event.isLeftClick(event)) {    
> +    if(Event.isLeftClick(event)) {
>        // abort on form elements, fixes a Firefox issue
>        var src = Event.element(event);
>        if((tag_name = src.tagName.toUpperCase()) && (
> @@ -309,34 +309,34 @@ var Draggable = Class.create({
>          tag_name=='OPTION' ||
>          tag_name=='BUTTON' ||
>          tag_name=='TEXTAREA')) return;
> -        
> +
>        var pointer = [Event.pointerX(event), Event.pointerY(event)];
>        var pos     = Position.cumulativeOffset(this.element);
>        this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
> -      
> +
>        Draggables.activate(this);
>        Event.stop(event);
>      }
>    },
> -  
> +
>    startDrag: function(event) {
>      this.dragging = true;
>      if(!this.delta)
>        this.delta = this.currentDelta();
> -    
> +
>      if(this.options.zindex) {
>        this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
>        this.element.style.zIndex = this.options.zindex;
>      }
> -    
> +
>      if(this.options.ghosting) {
>        this._clone = this.element.cloneNode(true);
> -      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
> -      if (!this.element._originallyAbsolute)
> +      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
> +      if (!this._originallyAbsolute)
>          Position.absolutize(this.element);
>        this.element.parentNode.insertBefore(this._clone, this.element);
>      }
> -    
> +
>      if(this.options.scroll) {
>        if (this.options.scroll == window) {
>          var where = this._getWindowScroll(this.options.scroll);
> @@ -347,15 +347,15 @@ var Draggable = Class.create({
>          this.originalScrollTop = this.options.scroll.scrollTop;
>        }
>      }
> -    
> +
>      Draggables.notify('onStart', this, event);
> -        
> +
>      if(this.options.starteffect) this.options.starteffect(this.element);
>    },
> -  
> +
>    updateDrag: function(event, pointer) {
>      if(!this.dragging) this.startDrag(event);
> -    
> +
>      if(!this.options.quiet){
>        Position.prepare();
>        Droppables.show(pointer, this.element);
> @@ -403,9 +403,9 @@ var Draggable = Class.create({
>      }
>  
>      if(this.options.ghosting) {
> -      if (!this.element._originallyAbsolute)
> +      if (!this._originallyAbsolute)
>          Position.relativize(this.element);
> -      delete this.element._originallyAbsolute;
> +      delete this._originallyAbsolute;
>        Element.remove(this._clone);
>        this._clone = null;
>      }
> @@ -433,7 +433,7 @@ var Draggable = Class.create({
>      if(this.options.zindex)
>        this.element.style.zIndex = this.originalZ;
>  
> -    if(this.options.endeffect) 
> +    if(this.options.endeffect)
>        this.options.endeffect(this.element);
>  
>      Draggables.deactivate(this);
> @@ -468,8 +468,8 @@ var Draggable = Class.create({
>        pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
>      }
>  
> -    var p = [0,1].map(function(i){ 
> -      return (point[i]-pos[i]-this.offset[i]) 
> +    var p = [0,1].map(function(i){
> +      return (point[i]-pos[i]-this.offset[i])
>      }.bind(this));
>  
>      if(this.options.snap) {
> @@ -478,10 +478,10 @@ var Draggable = Class.create({
>        } else {
>        if(Object.isArray(this.options.snap)) {
>          p = p.map( function(v, i) {
> -          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
> +          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
>        } else {
>          p = p.map( function(v) {
> -          return (v/this.options.snap).round()*this.options.snap }.bind(this))
> +          return (v/this.options.snap).round()*this.options.snap }.bind(this));
>        }
>      }}
>  
> @@ -560,7 +560,7 @@ var Draggable = Class.create({
>          H = documentElement.clientHeight;
>        } else {
>          W = body.offsetWidth;
> -        H = body.offsetHeight
> +        H = body.offsetHeight;
>        }
>      }
>      return { top: T, left: L, width: W, height: H };
> @@ -591,9 +591,9 @@ var SortableObserver = Class.create({
>  
>  var Sortable = {
>    SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
> -  
> +
>    sortables: { },
> -  
> +
>    _findRootElement: function(element) {
>      while (element.tagName.toUpperCase() != "BODY") {
>        if(element.id && Sortable.sortables[element.id]) return element;
> @@ -608,7 +608,8 @@ var Sortable = {
>    },
>  
>    destroy: function(element){
> -    var s = Sortable.options(element);
> +    element = $(element);
> +    var s = Sortable.sortables[element.id];
>  
>      if(s) {
>        Draggables.removeObserver(s.element);
> @@ -689,14 +690,14 @@ var Sortable = {
>        tree:        options.tree,
>        hoverclass:  options.hoverclass,
>        onHover:     Sortable.onHover
> -    }
> +    };
>  
>      var options_for_tree = {
>        onHover:      Sortable.onEmptyHover,
>        overlap:      options.overlap,
>        containment:  options.containment,
>        hoverclass:   options.hoverclass
> -    }
> +    };
>  
>      // fix for gecko engine
>      Element.cleanWhitespace(element);
> @@ -832,7 +833,7 @@ var Sortable = {
>          Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
>        else
>          Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
> -    
> +
>      Sortable._marker.show();
>    },
>  
> @@ -851,11 +852,11 @@ var Sortable = {
>          children: [],
>          position: parent.children.length,
>          container: $(children[i]).down(options.treeTag)
> -      }
> +      };
>  
>        /* Get the element containing the children and recurse over it */
>        if (child.container)
> -        this._tree(child.container, options, child)
> +        this._tree(child.container, options, child);
>  
>        parent.children.push (child);
>      }
> @@ -880,7 +881,7 @@ var Sortable = {
>        children: [],
>        container: element,
>        position: 0
> -    }
> +    };
>  
>      return Sortable._tree(element, options, root);
>    },
> @@ -931,7 +932,7 @@ var Sortable = {
>  
>      if (options.tree) {
>        return Sortable.tree(element, arguments[1]).children.map( function (item) {
> -        return [name + Sortable._constructIndex(item) + "[id]=" + 
> +        return [name + Sortable._constructIndex(item) + "[id]=" +
>                  encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
>        }).flatten().join('&');
>      } else {
> @@ -940,14 +941,14 @@ var Sortable = {
>        }).join('&');
>      }
>    }
> -}
> +};
>  
>  // Returns true if child is contained within element
>  Element.isParent = function(child, element) {
>    if (!child.parentNode || child == element) return false;
>    if (child.parentNode == element) return true;
>    return Element.isParent(child.parentNode, element);
> -}
> +};
>  
>  Element.findChildren = function(element, only, recursive, tagName) {
>    if(!element.hasChildNodes()) return null;
> @@ -965,8 +966,8 @@ Element.findChildren = function(element, only, recursive, tagName) {
>    });
>  
>    return (elements.length>0 ? elements.flatten() : []);
> -}
> +};
>  
>  Element.offsetSize = function (element, type) {
>    return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
> -}
> +};
> \ No newline at end of file
> diff --git a/src/public/javascripts/effects.js b/src/public/javascripts/effects.js
> index b0f056b..5a639d2 100644
> --- a/src/public/javascripts/effects.js
> +++ b/src/public/javascripts/effects.js
> @@ -3,9 +3,9 @@
>  //  Justin Palmer (http://encytemedia.com/)
>  //  Mark Pilgrim (http://diveintomark.org/)
>  //  Martin Bialasinki
> -// 
> +//
>  // script.aculo.us is freely distributable under the terms of an MIT-style license.
> -// For details, see the script.aculo.us web site: http://script.aculo.us/ 
> +// For details, see the script.aculo.us web site: http://script.aculo.us/
>  
>  // converts rgb() and #xxx to #xxxxxx format,
>  // returns self (or first argument) if not convertable
> @@ -32,7 +32,7 @@ Element.collectTextNodes = function(element) {
>    }).flatten().join('');
>  };
>  
> -Element.collectTextNodesIgnoreClass = function(element, className) {  
> +Element.collectTextNodesIgnoreClass = function(element, className) {
>    return $A($(element).childNodes).collect( function(node) {
>      return (node.nodeType==3 ? node.nodeValue :
>        ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
> @@ -70,25 +70,20 @@ var Effect = {
>    Transitions: {
>      linear: Prototype.K,
>      sinoidal: function(pos) {
> -      return (-Math.cos(pos*Math.PI)/2) + 0.5;
> +      return (-Math.cos(pos*Math.PI)/2) + .5;
>      },
>      reverse: function(pos) {
>        return 1-pos;
>      },
>      flicker: function(pos) {
> -      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
> +      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
>        return pos > 1 ? 1 : pos;
>      },
>      wobble: function(pos) {
> -      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
> +      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
>      },
>      pulse: function(pos, pulses) {
> -      pulses = pulses || 5;
> -      return (
> -        ((pos % (1/pulses)) * pulses).round() == 0 ?
> -              ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
> -          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
> -        );
> +      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
>      },
>      spring: function(pos) {
>        return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
> @@ -223,7 +218,7 @@ Effect.Queues = {
>    instances: $H(),
>    get: function(queueName) {
>      if (!Object.isString(queueName)) return queueName;
> -    
> +
>      return this.instances.get(queueName) ||
>        this.instances.set(queueName, new Effect.ScopedQueue());
>    }
> @@ -249,18 +244,30 @@ Effect.Base = Class.create({
>      this.totalTime    = this.finishOn-this.startOn;
>      this.totalFrames  = this.options.fps*this.options.duration;
>  
> -    eval('this.render = function(pos){ '+
> -      'if (this.state=="idle"){this.state="running";'+
> -      codeForEvent(this.options,'beforeSetup')+
> -      (this.setup ? 'this.setup();':'')+
> -      codeForEvent(this.options,'afterSetup')+
> -      '};if (this.state=="running"){'+
> -      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
> -      'this.position=pos;'+
> -      codeForEvent(this.options,'beforeUpdate')+
> -      (this.update ? 'this.update(pos);':'')+
> -      codeForEvent(this.options,'afterUpdate')+
> -      '}}');
> +    this.render = (function() {
> +      function dispatch(effect, eventName) {
> +        if (effect.options[eventName + 'Internal'])
> +          effect.options[eventName + 'Internal'](effect);
> +        if (effect.options[eventName])
> +          effect.options[eventName](effect);
> +      }
> +
> +      return function(pos) {
> +        if (this.state === "idle") {
> +          this.state = "running";
> +          dispatch(this, 'beforeSetup');
> +          if (this.setup) this.setup();
> +          dispatch(this, 'afterSetup');
> +        }
> +        if (this.state === "running") {
> +          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
> +          this.position = pos;
> +          dispatch(this, 'beforeUpdate');
> +          if (this.update) this.update(pos);
> +          dispatch(this, 'afterUpdate');
> +        }
> +      };
> +    })();
>  
>      this.event('beforeStart');
>      if (!this.options.sync)
> @@ -392,7 +399,7 @@ Effect.Move = Class.create(Effect.Base, {
>  
>  // for backwards compatibility
>  Effect.MoveBy = function(element, toTop, toLeft) {
> -  return new Effect.Move(element, 
> +  return new Effect.Move(element,
>      Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
>  };
>  
> @@ -507,17 +514,16 @@ Effect.Highlight = Class.create(Effect.Base, {
>  
>  Effect.ScrollTo = function(element) {
>    var options = arguments[1] || { },
> -    scrollOffsets = document.viewport.getScrollOffsets(),
> -    elementOffsets = $(element).cumulativeOffset(),
> -    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
> +  scrollOffsets = document.viewport.getScrollOffsets(),
> +  elementOffsets = $(element).cumulativeOffset();
>  
>    if (options.offset) elementOffsets[1] += options.offset;
>  
>    return new Effect.Tween(null,
>      scrollOffsets.top,
> -    elementOffsets[1] > max ? max : elementOffsets[1],
> +    elementOffsets[1],
>      options,
> -    function(p){ scrollTo(scrollOffsets.left, p.round()) }
> +    function(p){ scrollTo(scrollOffsets.left, p.round()); }
>    );
>  };
>  
> @@ -554,7 +560,7 @@ Effect.Appear = function(element) {
>  
>  Effect.Puff = function(element) {
>    element = $(element);
> -  var oldStyle = { 
> +  var oldStyle = {
>      opacity: element.getInlineOpacity(),
>      position: element.getStyle('position'),
>      top:  element.style.top,
> @@ -563,12 +569,12 @@ Effect.Puff = function(element) {
>      height: element.style.height
>    };
>    return new Effect.Parallel(
> -   [ new Effect.Scale(element, 200, 
> +   [ new Effect.Scale(element, 200,
>        { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
>       new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
>       Object.extend({ duration: 1.0,
>        beforeSetupInternal: function(effect) {
> -        Position.absolutize(effect.effects[0].element)
> +        Position.absolutize(effect.effects[0].element);
>        },
>        afterFinishInternal: function(effect) {
>           effect.effects[0].element.hide().setStyle(oldStyle); }
> @@ -580,12 +586,12 @@ Effect.BlindUp = function(element) {
>    element = $(element);
>    element.makeClipping();
>    return new Effect.Scale(element, 0,
> -    Object.extend({ scaleContent: false, 
> +    Object.extend({ scaleContent: false,
>        scaleX: false,
>        restoreAfterFinish: true,
>        afterFinishInternal: function(effect) {
>          effect.element.hide().undoClipping();
> -      } 
> +      }
>      }, arguments[1] || { })
>    );
>  };
> @@ -619,13 +625,13 @@ Effect.SwitchOff = function(element) {
>        new Effect.Scale(effect.element, 1, {
>          duration: 0.3, scaleFromCenter: true,
>          scaleX: false, scaleContent: false, restoreAfterFinish: true,
> -        beforeSetup: function(effect) { 
> +        beforeSetup: function(effect) {
>            effect.element.makePositioned().makeClipping();
>          },
>          afterFinishInternal: function(effect) {
>            effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
>          }
> -      })
> +      });
>      }
>    }, arguments[1] || { }));
>  };
> @@ -646,7 +652,7 @@ Effect.DropOut = function(element) {
>          },
>          afterFinishInternal: function(effect) {
>            effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
> -        } 
> +        }
>        }, arguments[1] || { }));
>  };
>  
> @@ -674,7 +680,7 @@ Effect.Shake = function(element) {
>      new Effect.Move(effect.element,
>        { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
>          effect.element.undoPositioned().setStyle(oldStyle);
> -  }}) }}) }}) }}) }}) }});
> +  }}); }}); }}); }}); }}); }});
>  };
>  
>  Effect.SlideDown = function(element) {
> @@ -682,7 +688,7 @@ Effect.SlideDown = function(element) {
>    // SlideDown need to have the content of the element wrapped in a container element with fixed height!
>    var oldInnerBottom = element.down().getStyle('bottom');
>    var elementDimensions = element.getDimensions();
> -  return new Effect.Scale(element, 100, Object.extend({ 
> +  return new Effect.Scale(element, 100, Object.extend({
>      scaleContent: false,
>      scaleX: false,
>      scaleFrom: window.opera ? 0 : 1,
> @@ -742,7 +748,7 @@ Effect.Squish = function(element) {
>        effect.element.makeClipping();
>      },
>      afterFinishInternal: function(effect) {
> -      effect.element.hide().undoClipping(); 
> +      effect.element.hide().undoClipping();
>      }
>    });
>  };
> @@ -810,13 +816,13 @@ Effect.Grow = function(element) {
>              sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
>          ], Object.extend({
>               beforeSetup: function(effect) {
> -               effect.effects[0].element.setStyle({height: '0px'}).show(); 
> +               effect.effects[0].element.setStyle({height: '0px'}).show();
>               },
>               afterFinishInternal: function(effect) {
>                 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
>               }
>             }, options)
> -      )
> +      );
>      }
>    });
>  };
> @@ -838,7 +844,7 @@ Effect.Shrink = function(element) {
>  
>    var dims = element.getDimensions();
>    var moveX, moveY;
> -  
> +
>    switch (options.direction) {
>      case 'top-left':
>        moveX = moveY = 0;
> @@ -877,11 +883,13 @@ Effect.Shrink = function(element) {
>  
>  Effect.Pulsate = function(element) {
>    element = $(element);
> -  var options    = arguments[1] || { };
> -  var oldOpacity = element.getInlineOpacity();
> -  var transition = options.transition || Effect.Transitions.sinoidal;
> -  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
> -  reverser.bind(transition);
> +  var options    = arguments[1] || { },
> +    oldOpacity = element.getInlineOpacity(),
> +    transition = options.transition || Effect.Transitions.linear,
> +    reverser   = function(pos){
> +      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
> +    };
> +
>    return new Effect.Opacity(element,
>      Object.extend(Object.extend({  duration: 2.0, from: 0,
>        afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
> @@ -934,7 +942,7 @@ Effect.Morph = Class.create(Effect.Base, {
>            effect.transforms.each(function(transform) {
>              effect.element.style[transform.style] = '';
>            });
> -        }
> +        };
>        }
>      }
>      this.start(options);
> @@ -945,7 +953,7 @@ Effect.Morph = Class.create(Effect.Base, {
>        if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
>        color = color.parseColor();
>        return $R(0,2).map(function(i){
> -        return parseInt( color.slice(i*2+1,i*2+3), 16 )
> +        return parseInt( color.slice(i*2+1,i*2+3), 16 );
>        });
>      }
>      this.transforms = this.style.map(function(pair){
> @@ -978,7 +986,7 @@ Effect.Morph = Class.create(Effect.Base, {
>            transform.unit != 'color' &&
>            (isNaN(transform.originalValue) || isNaN(transform.targetValue))
>          )
> -      )
> +      );
>      });
>    },
>    update: function(position) {
> @@ -1074,14 +1082,14 @@ if (document.defaultView && document.defaultView.getComputedStyle) {
>    Element.getStyles = function(element) {
>      element = $(element);
>      var css = element.currentStyle, styles;
> -    styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) {
> -      hash.set(property, css[property]);
> -      return hash;
> +    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
> +      results[property] = css[property];
> +      return results;
>      });
> -    if (!styles.opacity) styles.set('opacity', element.getOpacity());
> +    if (!styles.opacity) styles.opacity = element.getOpacity();
>      return styles;
>    };
> -};
> +}
>  
>  Effect.Methods = {
>    morph: function(element, style) {
> @@ -1090,7 +1098,7 @@ Effect.Methods = {
>      return element;
>    },
>    visualEffect: function(element, effect, options) {
> -    element = $(element)
> +    element = $(element);
>      var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
>      new Effect[klass](element, options);
>      return element;
> @@ -1109,7 +1117,7 @@ $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
>        element = $(element);
>        Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
>        return element;
> -    }
> +    };
>    }
>  );
>  
> @@ -1117,4 +1125,4 @@ $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTex
>    function(f) { Effect.Methods[f] = Element[f]; }
>  );
>  
> -Element.addMethods(Effect.Methods);
> +Element.addMethods(Effect.Methods);
> \ No newline at end of file
> diff --git a/src/public/javascripts/prototype.js b/src/public/javascripts/prototype.js
> index 9f6c857..dfe8ab4 100644
> --- a/src/public/javascripts/prototype.js
> +++ b/src/public/javascripts/prototype.js
> @@ -1,5 +1,5 @@
> -/*  Prototype JavaScript framework, version 1.6.0.1
> - *  (c) 2005-2007 Sam Stephenson
> +/*  Prototype JavaScript framework, version 1.6.0.3
> + *  (c) 2005-2008 Sam Stephenson
>   *
>   *  Prototype is freely distributable under the terms of an MIT-style license.
>   *  For details, see the Prototype web site: http://www.prototypejs.org/
> @@ -7,23 +7,26 @@
>   *--------------------------------------------------------------------------*/
>  
>  var Prototype = {
> -  Version: '1.6.0.1',
> +  Version: '1.6.0.3',
>  
>    Browser: {
> -    IE:     !!(window.attachEvent && !window.opera),
> -    Opera:  !!window.opera,
> +    IE:     !!(window.attachEvent &&
> +      navigator.userAgent.indexOf('Opera') === -1),
> +    Opera:  navigator.userAgent.indexOf('Opera') > -1,
>      WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
> -    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
> +    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
> +      navigator.userAgent.indexOf('KHTML') === -1,
>      MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
>    },
>  
>    BrowserFeatures: {
>      XPath: !!document.evaluate,
> +    SelectorsAPI: !!document.querySelector,
>      ElementExtensions: !!window.HTMLElement,
>      SpecificElementExtensions:
> -      document.createElement('div').__proto__ &&
> -      document.createElement('div').__proto__ !==
> -        document.createElement('form').__proto__
> +      document.createElement('div')['__proto__'] &&
> +      document.createElement('div')['__proto__'] !==
> +        document.createElement('form')['__proto__']
>    },
>  
>    ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
> @@ -83,12 +86,13 @@ Class.Methods = {
>        var property = properties[i], value = source[property];
>        if (ancestor && Object.isFunction(value) &&
>            value.argumentNames().first() == "$super") {
> -        var method = value, value = Object.extend((function(m) {
> +        var method = value;
> +        value = (function(m) {
>            return function() { return ancestor[m].apply(this, arguments) };
> -        })(property).wrap(method), {
> -          valueOf:  function() { return method },
> -          toString: function() { return method.toString() }
> -        });
> +        })(property).wrap(method);
> +
> +        value.valueOf = method.valueOf.bind(method);
> +        value.toString = method.toString.bind(method);
>        }
>        this.prototype[property] = value;
>      }
> @@ -110,7 +114,7 @@ Object.extend(Object, {
>      try {
>        if (Object.isUndefined(object)) return 'undefined';
>        if (object === null) return 'null';
> -      return object.inspect ? object.inspect() : object.toString();
> +      return object.inspect ? object.inspect() : String(object);
>      } catch (e) {
>        if (e instanceof RangeError) return '...';
>        throw e;
> @@ -167,11 +171,12 @@ Object.extend(Object, {
>    },
>  
>    isElement: function(object) {
> -    return object && object.nodeType == 1;
> +    return !!(object && object.nodeType == 1);
>    },
>  
>    isArray: function(object) {
> -    return object && object.constructor === Array;
> +    return object != null && typeof object == "object" &&
> +      'splice' in object && 'join' in object;
>    },
>  
>    isHash: function(object) {
> @@ -197,7 +202,8 @@ Object.extend(Object, {
>  
>  Object.extend(Function.prototype, {
>    argumentNames: function() {
> -    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
> +    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
> +      .replace(/\s+/g, '').split(',');
>      return names.length == 1 && !names[0] ? [] : names;
>    },
>  
> @@ -231,6 +237,11 @@ Object.extend(Function.prototype, {
>      }, timeout);
>    },
>  
> +  defer: function() {
> +    var args = [0.01].concat($A(arguments));
> +    return this.delay.apply(this, args);
> +  },
> +
>    wrap: function(wrapper) {
>      var __method = this;
>      return function() {
> @@ -247,8 +258,6 @@ Object.extend(Function.prototype, {
>    }
>  });
>  
> -Function.prototype.defer = Function.prototype.delay.curry(0.01);
> -
>  Date.prototype.toJSON = function() {
>    return '"' + this.getUTCFullYear() + '-' +
>      (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
> @@ -529,7 +538,7 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto
>      return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
>    },
>    unescapeHTML: function() {
> -    return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
> +    return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
>    }
>  });
>  
> @@ -546,7 +555,7 @@ Object.extend(String.prototype.escapeHTML, {
>    text: document.createTextNode('')
>  });
>  
> -with (String.prototype.escapeHTML) div.appendChild(text);
> +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
>  
>  var Template = Class.create({
>    initialize: function(template, pattern) {
> @@ -578,7 +587,7 @@ var Template = Class.create({
>        }
>  
>        return before + String.interpret(ctx);
> -    }.bind(this));
> +    });
>    }
>  });
>  Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
> @@ -588,10 +597,9 @@ var $break = { };
>  var Enumerable = {
>    each: function(iterator, context) {
>      var index = 0;
> -    iterator = iterator.bind(context);
>      try {
>        this._each(function(value) {
> -        iterator(value, index++);
> +        iterator.call(context, value, index++);
>        });
>      } catch (e) {
>        if (e != $break) throw e;
> @@ -600,47 +608,46 @@ var Enumerable = {
>    },
>  
>    eachSlice: function(number, iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
>      var index = -number, slices = [], array = this.toArray();
> +    if (number < 1) return array;
>      while ((index += number) < array.length)
>        slices.push(array.slice(index, index+number));
>      return slices.collect(iterator, context);
>    },
>  
>    all: function(iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var result = true;
>      this.each(function(value, index) {
> -      result = result && !!iterator(value, index);
> +      result = result && !!iterator.call(context, value, index);
>        if (!result) throw $break;
>      });
>      return result;
>    },
>  
>    any: function(iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var result = false;
>      this.each(function(value, index) {
> -      if (result = !!iterator(value, index))
> +      if (result = !!iterator.call(context, value, index))
>          throw $break;
>      });
>      return result;
>    },
>  
>    collect: function(iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var results = [];
>      this.each(function(value, index) {
> -      results.push(iterator(value, index));
> +      results.push(iterator.call(context, value, index));
>      });
>      return results;
>    },
>  
>    detect: function(iterator, context) {
> -    iterator = iterator.bind(context);
>      var result;
>      this.each(function(value, index) {
> -      if (iterator(value, index)) {
> +      if (iterator.call(context, value, index)) {
>          result = value;
>          throw $break;
>        }
> @@ -649,17 +656,16 @@ var Enumerable = {
>    },
>  
>    findAll: function(iterator, context) {
> -    iterator = iterator.bind(context);
>      var results = [];
>      this.each(function(value, index) {
> -      if (iterator(value, index))
> +      if (iterator.call(context, value, index))
>          results.push(value);
>      });
>      return results;
>    },
>  
>    grep: function(filter, iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var results = [];
>  
>      if (Object.isString(filter))
> @@ -667,7 +673,7 @@ var Enumerable = {
>  
>      this.each(function(value, index) {
>        if (filter.match(value))
> -        results.push(iterator(value, index));
> +        results.push(iterator.call(context, value, index));
>      });
>      return results;
>    },
> @@ -695,9 +701,8 @@ var Enumerable = {
>    },
>  
>    inject: function(memo, iterator, context) {
> -    iterator = iterator.bind(context);
>      this.each(function(value, index) {
> -      memo = iterator(memo, value, index);
> +      memo = iterator.call(context, memo, value, index);
>      });
>      return memo;
>    },
> @@ -710,10 +715,10 @@ var Enumerable = {
>    },
>  
>    max: function(iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var result;
>      this.each(function(value, index) {
> -      value = iterator(value, index);
> +      value = iterator.call(context, value, index);
>        if (result == null || value >= result)
>          result = value;
>      });
> @@ -721,10 +726,10 @@ var Enumerable = {
>    },
>  
>    min: function(iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var result;
>      this.each(function(value, index) {
> -      value = iterator(value, index);
> +      value = iterator.call(context, value, index);
>        if (result == null || value < result)
>          result = value;
>      });
> @@ -732,10 +737,10 @@ var Enumerable = {
>    },
>  
>    partition: function(iterator, context) {
> -    iterator = iterator ? iterator.bind(context) : Prototype.K;
> +    iterator = iterator || Prototype.K;
>      var trues = [], falses = [];
>      this.each(function(value, index) {
> -      (iterator(value, index) ?
> +      (iterator.call(context, value, index) ?
>          trues : falses).push(value);
>      });
>      return [trues, falses];
> @@ -750,19 +755,20 @@ var Enumerable = {
>    },
>  
>    reject: function(iterator, context) {
> -    iterator = iterator.bind(context);
>      var results = [];
>      this.each(function(value, index) {
> -      if (!iterator(value, index))
> +      if (!iterator.call(context, value, index))
>          results.push(value);
>      });
>      return results;
>    },
>  
>    sortBy: function(iterator, context) {
> -    iterator = iterator.bind(context);
>      return this.map(function(value, index) {
> -      return {value: value, criteria: iterator(value, index)};
> +      return {
> +        value: value,
> +        criteria: iterator.call(context, value, index)
> +      };
>      }).sort(function(left, right) {
>        var a = left.criteria, b = right.criteria;
>        return a < b ? -1 : a > b ? 1 : 0;
> @@ -806,20 +812,24 @@ Object.extend(Enumerable, {
>  function $A(iterable) {
>    if (!iterable) return [];
>    if (iterable.toArray) return iterable.toArray();
> -  var length = iterable.length, results = new Array(length);
> +  var length = iterable.length || 0, results = new Array(length);
>    while (length--) results[length] = iterable[length];
>    return results;
>  }
>  
>  if (Prototype.Browser.WebKit) {
> -  function $A(iterable) {
> +  $A = function(iterable) {
>      if (!iterable) return [];
> -    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
> -        iterable.toArray) return iterable.toArray();
> -    var length = iterable.length, results = new Array(length);
> +    // In Safari, only use the `toArray` method if it's not a NodeList.
> +    // A NodeList is a function, has an function `item` property, and a numeric
> +    // `length` property. Adapted from Google Doctype.
> +    if (!(typeof iterable === 'function' && typeof iterable.length ===
> +        'number' && typeof iterable.item === 'function') && iterable.toArray)
> +      return iterable.toArray();
> +    var length = iterable.length || 0, results = new Array(length);
>      while (length--) results[length] = iterable[length];
>      return results;
> -  }
> +  };
>  }
>  
>  Array.from = $A;
> @@ -962,8 +972,8 @@ Object.extend(Number.prototype, {
>      return this + 1;
>    },
>  
> -  times: function(iterator) {
> -    $R(0, this, true).each(iterator);
> +  times: function(iterator, context) {
> +    $R(0, this, true).each(iterator, context);
>      return this;
>    },
>  
> @@ -1010,7 +1020,9 @@ var Hash = Class.create(Enumerable, (function() {
>      },
>  
>      get: function(key) {
> -      return this._object[key];
> +      // simulating poorly supported hasOwnProperty
> +      if (this._object[key] !== Object.prototype[key])
> +        return this._object[key];
>      },
>  
>      unset: function(key) {
> @@ -1050,14 +1062,14 @@ var Hash = Class.create(Enumerable, (function() {
>      },
>  
>      toQueryString: function() {
> -      return this.map(function(pair) {
> +      return this.inject([], function(results, pair) {
>          var key = encodeURIComponent(pair.key), values = pair.value;
>  
>          if (values && typeof values == 'object') {
>            if (Object.isArray(values))
> -            return values.map(toQueryPair.curry(key)).join('&');
> -        }
> -        return toQueryPair(key, values);
> +            return results.concat(values.map(toQueryPair.curry(key)));
> +        } else results.push(toQueryPair(key, values));
> +        return results;
>        }).join('&');
>      },
>  
> @@ -1298,7 +1310,7 @@ Ajax.Request = Class.create(Ajax.Base, {
>  
>        var contentType = response.getHeader('Content-type');
>        if (this.options.evalJS == 'force'
> -          || (this.options.evalJS && contentType
> +          || (this.options.evalJS && this.isSameOrigin() && contentType
>            && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
>          this.evalResponse();
>      }
> @@ -1316,9 +1328,18 @@ Ajax.Request = Class.create(Ajax.Base, {
>      }
>    },
>  
> +  isSameOrigin: function() {
> +    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
> +    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
> +      protocol: location.protocol,
> +      domain: document.domain,
> +      port: location.port ? ':' + location.port : ''
> +    }));
> +  },
> +
>    getHeader: function(name) {
>      try {
> -      return this.transport.getResponseHeader(name);
> +      return this.transport.getResponseHeader(name) || null;
>      } catch (e) { return null }
>    },
>  
> @@ -1391,7 +1412,8 @@ Ajax.Response = Class.create({
>      if (!json) return null;
>      json = decodeURIComponent(escape(json));
>      try {
> -      return json.evalJSON(this.request.options.sanitizeJSON);
> +      return json.evalJSON(this.request.options.sanitizeJSON ||
> +        !this.request.isSameOrigin());
>      } catch (e) {
>        this.request.dispatchException(e);
>      }
> @@ -1404,7 +1426,8 @@ Ajax.Response = Class.create({
>          this.responseText.blank())
>            return null;
>      try {
> -      return this.responseText.evalJSON(options.sanitizeJSON);
> +      return this.responseText.evalJSON(options.sanitizeJSON ||
> +        !this.request.isSameOrigin());
>      } catch (e) {
>        this.request.dispatchException(e);
>      }
> @@ -1546,6 +1569,7 @@ if (!Node.ELEMENT_NODE) {
>      return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
>    };
>    Object.extend(this.Element, element || { });
> +  if (element) this.Element.prototype = element.prototype;
>  }).call(window);
>  
>  Element.cache = { };
> @@ -1562,12 +1586,14 @@ Element.Methods = {
>    },
>  
>    hide: function(element) {
> -    $(element).style.display = 'none';
> +    element = $(element);
> +    element.style.display = 'none';
>      return element;
>    },
>  
>    show: function(element) {
> -    $(element).style.display = '';
> +    element = $(element);
> +    element.style.display = '';
>      return element;
>    },
>  
> @@ -1608,24 +1634,28 @@ Element.Methods = {
>          Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
>            insertions = {bottom:insertions};
>  
> -    var content, t, range;
> +    var content, insert, tagName, childNodes;
>  
> -    for (position in insertions) {
> +    for (var position in insertions) {
>        content  = insertions[position];
>        position = position.toLowerCase();
> -      t = Element._insertionTranslations[position];
> +      insert = Element._insertionTranslations[position];
>  
>        if (content && content.toElement) content = content.toElement();
>        if (Object.isElement(content)) {
> -        t.insert(element, content);
> +        insert(element, content);
>          continue;
>        }
>  
>        content = Object.toHTML(content);
>  
> -      range = element.ownerDocument.createRange();
> -      t.initializeRange(element, range);
> -      t.insert(element, range.createContextualFragment(content.stripScripts()));
> +      tagName = ((position == 'before' || position == 'after')
> +        ? element.parentNode : element).tagName.toUpperCase();
> +
> +      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
> +
> +      if (position == 'top' || position == 'after') childNodes.reverse();
> +      childNodes.each(insert.curry(element));
>  
>        content.evalScripts.bind(content).defer();
>      }
> @@ -1670,7 +1700,7 @@ Element.Methods = {
>    },
>  
>    descendants: function(element) {
> -    return $(element).getElementsBySelector("*");
> +    return $(element).select("*");
>    },
>  
>    firstDescendant: function(element) {
> @@ -1709,32 +1739,31 @@ Element.Methods = {
>      element = $(element);
>      if (arguments.length == 1) return $(element.parentNode);
>      var ancestors = element.ancestors();
> -    return expression ? Selector.findElement(ancestors, expression, index) :
> -      ancestors[index || 0];
> +    return Object.isNumber(expression) ? ancestors[expression] :
> +      Selector.findElement(ancestors, expression, index);
>    },
>  
>    down: function(element, expression, index) {
>      element = $(element);
>      if (arguments.length == 1) return element.firstDescendant();
> -    var descendants = element.descendants();
> -    return expression ? Selector.findElement(descendants, expression, index) :
> -      descendants[index || 0];
> +    return Object.isNumber(expression) ? element.descendants()[expression] :
> +      Element.select(element, expression)[index || 0];
>    },
>  
>    previous: function(element, expression, index) {
>      element = $(element);
>      if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
>      var previousSiblings = element.previousSiblings();
> -    return expression ? Selector.findElement(previousSiblings, expression, index) :
> -      previousSiblings[index || 0];
> +    return Object.isNumber(expression) ? previousSiblings[expression] :
> +      Selector.findElement(previousSiblings, expression, index);
>    },
>  
>    next: function(element, expression, index) {
>      element = $(element);
>      if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
>      var nextSiblings = element.nextSiblings();
> -    return expression ? Selector.findElement(nextSiblings, expression, index) :
> -      nextSiblings[index || 0];
> +    return Object.isNumber(expression) ? nextSiblings[expression] :
> +      Selector.findElement(nextSiblings, expression, index);
>    },
>  
>    select: function() {
> @@ -1848,23 +1877,16 @@ Element.Methods = {
>  
>    descendantOf: function(element, ancestor) {
>      element = $(element), ancestor = $(ancestor);
> -    var originalAncestor = ancestor;
>  
>      if (element.compareDocumentPosition)
>        return (element.compareDocumentPosition(ancestor) & 8) === 8;
>  
> -    if (element.sourceIndex && !Prototype.Browser.Opera) {
> -      var e = element.sourceIndex, a = ancestor.sourceIndex,
> -       nextAncestor = ancestor.nextSibling;
> -      if (!nextAncestor) {
> -        do { ancestor = ancestor.parentNode; }
> -        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
> -      }
> -      if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
> -    }
> +    if (ancestor.contains)
> +      return ancestor.contains(element) && ancestor !== element;
>  
>      while (element = element.parentNode)
> -      if (element == originalAncestor) return true;
> +      if (element == ancestor) return true;
> +
>      return false;
>    },
>  
> @@ -1879,7 +1901,7 @@ Element.Methods = {
>      element = $(element);
>      style = style == 'float' ? 'cssFloat' : style.camelize();
>      var value = element.style[style];
> -    if (!value) {
> +    if (!value || value == 'auto') {
>        var css = document.defaultView.getComputedStyle(element, null);
>        value = css ? css[style] : null;
>      }
> @@ -1918,7 +1940,7 @@ Element.Methods = {
>  
>    getDimensions: function(element) {
>      element = $(element);
> -    var display = $(element).getStyle('display');
> +    var display = element.getStyle('display');
>      if (display != 'none' && display != null) // Safari bug
>        return {width: element.offsetWidth, height: element.offsetHeight};
>  
> @@ -1947,7 +1969,7 @@ Element.Methods = {
>        element.style.position = 'relative';
>        // Opera returns the offset relative to the positioning context, when an
>        // element is position relative but top and left have not been defined
> -      if (window.opera) {
> +      if (Prototype.Browser.Opera) {
>          element.style.top = 0;
>          element.style.left = 0;
>        }
> @@ -2002,9 +2024,9 @@ Element.Methods = {
>        valueL += element.offsetLeft || 0;
>        element = element.offsetParent;
>        if (element) {
> -        if (element.tagName == 'BODY') break;
> +        if (element.tagName.toUpperCase() == 'BODY') break;
>          var p = Element.getStyle(element, 'position');
> -        if (p == 'relative' || p == 'absolute') break;
> +        if (p !== 'static') break;
>        }
>      } while (element);
>      return Element._returnOffset(valueL, valueT);
> @@ -2012,7 +2034,7 @@ Element.Methods = {
>  
>    absolutize: function(element) {
>      element = $(element);
> -    if (element.getStyle('position') == 'absolute') return;
> +    if (element.getStyle('position') == 'absolute') return element;
>      // Position.prepare(); // To be done manually by Scripty when it needs it.
>  
>      var offsets = element.positionedOffset();
> @@ -2036,7 +2058,7 @@ Element.Methods = {
>  
>    relativize: function(element) {
>      element = $(element);
> -    if (element.getStyle('position') == 'relative') return;
> +    if (element.getStyle('position') == 'relative') return element;
>      // Position.prepare(); // To be done manually by Scripty when it needs it.
>  
>      element.style.position = 'relative';
> @@ -2087,7 +2109,7 @@ Element.Methods = {
>  
>      element = forElement;
>      do {
> -      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
> +      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
>          valueT -= element.scrollTop  || 0;
>          valueL -= element.scrollLeft || 0;
>        }
> @@ -2153,46 +2175,6 @@ Element._attributeTranslations = {
>    }
>  };
>  
> -
> -if (!document.createRange || Prototype.Browser.Opera) {
> -  Element.Methods.insert = function(element, insertions) {
> -    element = $(element);
> -
> -    if (Object.isString(insertions) || Object.isNumber(insertions) ||
> -        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
> -          insertions = { bottom: insertions };
> -
> -    var t = Element._insertionTranslations, content, position, pos, tagName;
> -
> -    for (position in insertions) {
> -      content  = insertions[position];
> -      position = position.toLowerCase();
> -      pos      = t[position];
> -
> -      if (content && content.toElement) content = content.toElement();
> -      if (Object.isElement(content)) {
> -        pos.insert(element, content);
> -        continue;
> -      }
> -
> -      content = Object.toHTML(content);
> -      tagName = ((position == 'before' || position == 'after')
> -        ? element.parentNode : element).tagName.toUpperCase();
> -
> -      if (t.tags[tagName]) {
> -        var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
> -        if (position == 'top' || position == 'after') fragments.reverse();
> -        fragments.each(pos.insert.curry(element));
> -      }
> -      else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
> -
> -      content.evalScripts.bind(content).defer();
> -    }
> -
> -    return element;
> -  };
> -}
> -
>  if (Prototype.Browser.Opera) {
>    Element.Methods.getStyle = Element.Methods.getStyle.wrap(
>      function(proceed, element, style) {
> @@ -2237,12 +2219,36 @@ if (Prototype.Browser.Opera) {
>  }
>  
>  else if (Prototype.Browser.IE) {
> -  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
> +  // IE doesn't report offsets correctly for static elements, so we change them
> +  // to "relative" to get the values, then change them back.
> +  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
> +    function(proceed, element) {
> +      element = $(element);
> +      // IE throws an error if element is not in document
> +      try { element.offsetParent }
> +      catch(e) { return $(document.body) }
> +      var position = element.getStyle('position');
> +      if (position !== 'static') return proceed(element);
> +      element.setStyle({ position: 'relative' });
> +      var value = proceed(element);
> +      element.setStyle({ position: position });
> +      return value;
> +    }
> +  );
> +
> +  $w('positionedOffset viewportOffset').each(function(method) {
>      Element.Methods[method] = Element.Methods[method].wrap(
>        function(proceed, element) {
>          element = $(element);
> +        try { element.offsetParent }
> +        catch(e) { return Element._returnOffset(0,0) }
>          var position = element.getStyle('position');
> -        if (position != 'static') return proceed(element);
> +        if (position !== 'static') return proceed(element);
> +        // Trigger hasLayout on the offset parent so that IE6 reports
> +        // accurate offsetTop and offsetLeft values for position: fixed.
> +        var offsetParent = element.getOffsetParent();
> +        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
> +          offsetParent.setStyle({ zoom: 1 });
>          element.setStyle({ position: 'relative' });
>          var value = proceed(element);
>          element.setStyle({ position: position });
> @@ -2251,6 +2257,14 @@ else if (Prototype.Browser.IE) {
>      );
>    });
>  
> +  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
> +    function(proceed, element) {
> +      try { element.offsetParent }
> +      catch(e) { return Element._returnOffset(0,0) }
> +      return proceed(element);
> +    }
> +  );
> +
>    Element.Methods.getStyle = function(element, style) {
>      element = $(element);
>      style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
> @@ -2324,7 +2338,10 @@ else if (Prototype.Browser.IE) {
>    };
>  
>    Element._attributeTranslations.write = {
> -    names: Object.clone(Element._attributeTranslations.read.names),
> +    names: Object.extend({
> +      cellpadding: 'cellPadding',
> +      cellspacing: 'cellSpacing'
> +    }, Element._attributeTranslations.read.names),
>      values: {
>        checked: function(element, value) {
>          element.checked = !!value;
> @@ -2339,7 +2356,7 @@ else if (Prototype.Browser.IE) {
>    Element._attributeTranslations.has = {};
>  
>    $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
> -      'encType maxLength readOnly longDesc').each(function(attr) {
> +      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
>      Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
>      Element._attributeTranslations.has[attr.toLowerCase()] = attr;
>    });
> @@ -2392,7 +2409,7 @@ else if (Prototype.Browser.WebKit) {
>        (value < 0.00001) ? 0 : value;
>  
>      if (value == 1)
> -      if(element.tagName == 'IMG' && element.width) {
> +      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
>          element.width++; element.width--;
>        } else try {
>          var n = document.createTextNode(' ');
> @@ -2444,7 +2461,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
>    };
>  }
>  
> -if (document.createElement('div').outerHTML) {
> +if ('outerHTML' in document.createElement('div')) {
>    Element.Methods.replace = function(element, content) {
>      element = $(element);
>  
> @@ -2482,45 +2499,25 @@ Element._returnOffset = function(l, t) {
>  
>  Element._getContentFromAnonymousElement = function(tagName, html) {
>    var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
> -  div.innerHTML = t[0] + html + t[1];
> -  t[2].times(function() { div = div.firstChild });
> +  if (t) {
> +    div.innerHTML = t[0] + html + t[1];
> +    t[2].times(function() { div = div.firstChild });
> +  } else div.innerHTML = html;
>    return $A(div.childNodes);
>  };
>  
>  Element._insertionTranslations = {
> -  before: {
> -    adjacency: 'beforeBegin',
> -    insert: function(element, node) {
> -      element.parentNode.insertBefore(node, element);
> -    },
> -    initializeRange: function(element, range) {
> -      range.setStartBefore(element);
> -    }
> +  before: function(element, node) {
> +    element.parentNode.insertBefore(node, element);
>    },
> -  top: {
> -    adjacency: 'afterBegin',
> -    insert: function(element, node) {
> -      element.insertBefore(node, element.firstChild);
> -    },
> -    initializeRange: function(element, range) {
> -      range.selectNodeContents(element);
> -      range.collapse(true);
> -    }
> +  top: function(element, node) {
> +    element.insertBefore(node, element.firstChild);
>    },
> -  bottom: {
> -    adjacency: 'beforeEnd',
> -    insert: function(element, node) {
> -      element.appendChild(node);
> -    }
> +  bottom: function(element, node) {
> +    element.appendChild(node);
>    },
> -  after: {
> -    adjacency: 'afterEnd',
> -    insert: function(element, node) {
> -      element.parentNode.insertBefore(node, element.nextSibling);
> -    },
> -    initializeRange: function(element, range) {
> -      range.setStartAfter(element);
> -    }
> +  after: function(element, node) {
> +    element.parentNode.insertBefore(node, element.nextSibling);
>    },
>    tags: {
>      TABLE:  ['<table>',                '</table>',                   1],
> @@ -2532,7 +2529,6 @@ Element._insertionTranslations = {
>  };
>  
>  (function() {
> -  this.bottom.initializeRange = this.top.initializeRange;
>    Object.extend(this.tags, {
>      THEAD: this.tags.TBODY,
>      TFOOT: this.tags.TBODY,
> @@ -2544,7 +2540,7 @@ Element.Methods.Simulated = {
>    hasAttribute: function(element, attribute) {
>      attribute = Element._attributeTranslations.has[attribute] || attribute;
>      var node = $(element).getAttributeNode(attribute);
> -    return node && node.specified;
> +    return !!(node && node.specified);
>    }
>  };
>  
> @@ -2553,9 +2549,9 @@ Element.Methods.ByTag = { };
>  Object.extend(Element, Element.Methods);
>  
>  if (!Prototype.BrowserFeatures.ElementExtensions &&
> -    document.createElement('div').__proto__) {
> +    document.createElement('div')['__proto__']) {
>    window.HTMLElement = { };
> -  window.HTMLElement.prototype = document.createElement('div').__proto__;
> +  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
>    Prototype.BrowserFeatures.ElementExtensions = true;
>  }
>  
> @@ -2570,7 +2566,7 @@ Element.extend = (function() {
>          element.nodeType != 1 || element == window) return element;
>  
>      var methods = Object.clone(Methods),
> -      tagName = element.tagName, property, value;
> +      tagName = element.tagName.toUpperCase(), property, value;
>  
>      // extend methods for specific tags
>      if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
> @@ -2666,7 +2662,7 @@ Element.addMethods = function(methods) {
>      if (window[klass]) return window[klass];
>  
>      window[klass] = { };
> -    window[klass].prototype = document.createElement(tagName).__proto__;
> +    window[klass].prototype = document.createElement(tagName)['__proto__'];
>      return window[klass];
>    }
>  
> @@ -2692,12 +2688,18 @@ Element.addMethods = function(methods) {
>  
>  document.viewport = {
>    getDimensions: function() {
> -    var dimensions = { };
> -    var B = Prototype.Browser;
> +    var dimensions = { }, B = Prototype.Browser;
>      $w('width height').each(function(d) {
>        var D = d.capitalize();
> -      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
> -        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
> +      if (B.WebKit && !document.evaluate) {
> +        // Safari <3.0 needs self.innerWidth/Height
> +        dimensions[d] = self['inner' + D];
> +      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
> +        // Opera <9.5 needs document.body.clientWidth/Height
> +        dimensions[d] = document.body['client' + D]
> +      } else {
> +        dimensions[d] = document.documentElement['client' + D];
> +      }
>      });
>      return dimensions;
>    },
> @@ -2716,14 +2718,24 @@ document.viewport = {
>        window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
>    }
>  };
> -/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
> +/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
>   * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
>   * license.  Please see http://www.yui-ext.com/ for more information. */
>  
>  var Selector = Class.create({
>    initialize: function(expression) {
>      this.expression = expression.strip();
> -    this.compileMatcher();
> +
> +    if (this.shouldUseSelectorsAPI()) {
> +      this.mode = 'selectorsAPI';
> +    } else if (this.shouldUseXPath()) {
> +      this.mode = 'xpath';
> +      this.compileXPathMatcher();
> +    } else {
> +      this.mode = "normal";
> +      this.compileMatcher();
> +    }
> +
>    },
>  
>    shouldUseXPath: function() {
> @@ -2738,16 +2750,29 @@ var Selector = Class.create({
>  
>      // XPath can't do namespaced attributes, nor can it read
>      // the "checked" property from DOM nodes
> -    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
> +    if ((/(\[[\w-]*?:|:checked)/).test(e))
>        return false;
>  
>      return true;
>    },
>  
> -  compileMatcher: function() {
> -    if (this.shouldUseXPath())
> -      return this.compileXPathMatcher();
> +  shouldUseSelectorsAPI: function() {
> +    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
> +
> +    if (!Selector._div) Selector._div = new Element('div');
> +
> +    // Make sure the browser treats the selector as valid. Test on an
> +    // isolated element to minimize cost of this check.
> +    try {
> +      Selector._div.querySelector(this.expression);
> +    } catch(e) {
> +      return false;
> +    }
>  
> +    return true;
> +  },
> +
> +  compileMatcher: function() {
>      var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
>          c = Selector.criteria, le, p, m;
>  
> @@ -2765,7 +2790,7 @@ var Selector = Class.create({
>          p = ps[i];
>          if (m = e.match(p)) {
>            this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
> -             new Template(c[i]).evaluate(m));
> +            new Template(c[i]).evaluate(m));
>            e = e.replace(m[0], '');
>            break;
>          }
> @@ -2804,8 +2829,27 @@ var Selector = Class.create({
>  
>    findElements: function(root) {
>      root = root || document;
> -    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
> -    return this.matcher(root);
> +    var e = this.expression, results;
> +
> +    switch (this.mode) {
> +      case 'selectorsAPI':
> +        // querySelectorAll queries document-wide, then filters to descendants
> +        // of the context element. That's not what we want.
> +        // Add an explicit context to the selector if necessary.
> +        if (root !== document) {
> +          var oldId = root.id, id = $(root).identify();
> +          e = "#" + id + " " + e;
> +        }
> +
> +        results = $A(root.querySelectorAll(e)).map(Element.extend);
> +        root.id = oldId;
> +
> +        return results;
> +      case 'xpath':
> +        return document._getElementsByXPath(this.xpath, root);
> +      default:
> +       return this.matcher(root);
> +    }
>    },
>  
>    match: function(element) {
> @@ -2896,10 +2940,10 @@ Object.extend(Selector, {
>        'first-child': '[not(preceding-sibling::*)]',
>        'last-child':  '[not(following-sibling::*)]',
>        'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
> -      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
> +      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
>        'checked':     "[@checked]",
> -      'disabled':    "[@disabled]",
> -      'enabled':     "[not(@disabled)]",
> +      'disabled':    "[(@disabled) and (@type!='hidden')]",
> +      'enabled':     "[not(@disabled) and (@type!='hidden')]",
>        'not': function(m) {
>          var e = m[6], p = Selector.patterns,
>              x = Selector.xpath, le, v;
> @@ -2959,13 +3003,13 @@ Object.extend(Selector, {
>    },
>  
>    criteria: {
> -    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
> -    className:    'n = h.className(n, r, "#{1}", c); c = false;',
> -    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
> -    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
> +    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
> +    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
> +    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
> +    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
>      attr: function(m) {
>        m[3] = (m[5] || m[6]);
> -      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
> +      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
>      },
>      pseudo: function(m) {
>        if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
> @@ -2989,8 +3033,9 @@ Object.extend(Selector, {
>      tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
>      id:           /^#([\w\-\*]+)(\b|$)/,
>      className:    /^\.([\w\-\*]+)(\b|$)/,
> -    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
> -    attrPresence: /^\[([\w]+)\]/,
> +    pseudo:
> +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
> +    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
>      attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
>    },
>  
> @@ -3014,7 +3059,7 @@ Object.extend(Selector, {
>  
>      attr: function(element, matches) {
>        var nodeValue = Element.readAttribute(element, matches[1]);
> -      return Selector.operators[matches[2]](nodeValue, matches[3]);
> +      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
>      }
>    },
>  
> @@ -3029,14 +3074,15 @@ Object.extend(Selector, {
>  
>      // marks an array of nodes for counting
>      mark: function(nodes) {
> +      var _true = Prototype.emptyFunction;
>        for (var i = 0, node; node = nodes[i]; i++)
> -        node._counted = true;
> +        node._countedByPrototype = _true;
>        return nodes;
>      },
>  
>      unmark: function(nodes) {
>        for (var i = 0, node; node = nodes[i]; i++)
> -        node._counted = undefined;
> +        node._countedByPrototype = undefined;
>        return nodes;
>      },
>  
> @@ -3044,15 +3090,15 @@ Object.extend(Selector, {
>      // "ofType" flag indicates whether we're indexing for nth-of-type
>      // rather than nth-child
>      index: function(parentNode, reverse, ofType) {
> -      parentNode._counted = true;
> +      parentNode._countedByPrototype = Prototype.emptyFunction;
>        if (reverse) {
>          for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
>            var node = nodes[i];
> -          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
> +          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
>          }
>        } else {
>          for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
> -          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
> +          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
>        }
>      },
>  
> @@ -3061,8 +3107,8 @@ Object.extend(Selector, {
>        if (nodes.length == 0) return nodes;
>        var results = [], n;
>        for (var i = 0, l = nodes.length; i < l; i++)
> -        if (!(n = nodes[i])._counted) {
> -          n._counted = true;
> +        if (!(n = nodes[i])._countedByPrototype) {
> +          n._countedByPrototype = Prototype.emptyFunction;
>            results.push(Element.extend(n));
>          }
>        return Selector.handlers.unmark(results);
> @@ -3102,7 +3148,7 @@ Object.extend(Selector, {
>  
>      nextElementSibling: function(node) {
>        while (node = node.nextSibling)
> -	      if (node.nodeType == 1) return node;
> +        if (node.nodeType == 1) return node;
>        return null;
>      },
>  
> @@ -3114,7 +3160,7 @@ Object.extend(Selector, {
>  
>      // TOKEN FUNCTIONS
>      tagName: function(nodes, root, tagName, combinator) {
> -      tagName = tagName.toUpperCase();
> +      var uTagName = tagName.toUpperCase();
>        var results = [], h = Selector.handlers;
>        if (nodes) {
>          if (combinator) {
> @@ -3127,7 +3173,7 @@ Object.extend(Selector, {
>            if (tagName == "*") return nodes;
>          }
>          for (var i = 0, node; node = nodes[i]; i++)
> -          if (node.tagName.toUpperCase() == tagName) results.push(node);
> +          if (node.tagName.toUpperCase() === uTagName) results.push(node);
>          return results;
>        } else return root.getElementsByTagName(tagName);
>      },
> @@ -3174,16 +3220,18 @@ Object.extend(Selector, {
>        return results;
>      },
>  
> -    attrPresence: function(nodes, root, attr) {
> +    attrPresence: function(nodes, root, attr, combinator) {
>        if (!nodes) nodes = root.getElementsByTagName("*");
> +      if (nodes && combinator) nodes = this[combinator](nodes);
>        var results = [];
>        for (var i = 0, node; node = nodes[i]; i++)
>          if (Element.hasAttribute(node, attr)) results.push(node);
>        return results;
>      },
>  
> -    attr: function(nodes, root, attr, value, operator) {
> +    attr: function(nodes, root, attr, value, operator, combinator) {
>        if (!nodes) nodes = root.getElementsByTagName("*");
> +      if (nodes && combinator) nodes = this[combinator](nodes);
>        var handler = Selector.operators[operator], results = [];
>        for (var i = 0, node; node = nodes[i]; i++) {
>          var nodeValue = Element.readAttribute(node, attr);
> @@ -3262,7 +3310,7 @@ Object.extend(Selector, {
>        var h = Selector.handlers, results = [], indexed = [], m;
>        h.mark(nodes);
>        for (var i = 0, node; node = nodes[i]; i++) {
> -        if (!node.parentNode._counted) {
> +        if (!node.parentNode._countedByPrototype) {
>            h.index(node.parentNode, reverse, ofType);
>            indexed.push(node.parentNode);
>          }
> @@ -3289,7 +3337,7 @@ Object.extend(Selector, {
>      'empty': function(nodes, value, root) {
>        for (var i = 0, results = [], node; node = nodes[i]; i++) {
>          // IE treats comments as element nodes
> -        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
> +        if (node.tagName == '!' || node.firstChild) continue;
>          results.push(node);
>        }
>        return results;
> @@ -3300,14 +3348,15 @@ Object.extend(Selector, {
>        var exclusions = new Selector(selector).findElements(root);
>        h.mark(exclusions);
>        for (var i = 0, results = [], node; node = nodes[i]; i++)
> -        if (!node._counted) results.push(node);
> +        if (!node._countedByPrototype) results.push(node);
>        h.unmark(exclusions);
>        return results;
>      },
>  
>      'enabled': function(nodes, value, root) {
>        for (var i = 0, results = [], node; node = nodes[i]; i++)
> -        if (!node.disabled) results.push(node);
> +        if (!node.disabled && (!node.type || node.type !== 'hidden'))
> +          results.push(node);
>        return results;
>      },
>  
> @@ -3327,18 +3376,29 @@ Object.extend(Selector, {
>    operators: {
>      '=':  function(nv, v) { return nv == v; },
>      '!=': function(nv, v) { return nv != v; },
> -    '^=': function(nv, v) { return nv.startsWith(v); },
> +    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
> +    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
> +    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
>      '$=': function(nv, v) { return nv.endsWith(v); },
>      '*=': function(nv, v) { return nv.include(v); },
>      '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
> -    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
> +    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
> +     '-').include('-' + (v || "").toUpperCase() + '-'); }
> +  },
> +
> +  split: function(expression) {
> +    var expressions = [];
> +    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
> +      expressions.push(m[1].strip());
> +    });
> +    return expressions;
>    },
>  
>    matchElements: function(elements, expression) {
> -    var matches = new Selector(expression).findElements(), h = Selector.handlers;
> +    var matches = $$(expression), h = Selector.handlers;
>      h.mark(matches);
>      for (var i = 0, results = [], element; element = elements[i]; i++)
> -      if (element._counted) results.push(element);
> +      if (element._countedByPrototype) results.push(element);
>      h.unmark(matches);
>      return results;
>    },
> @@ -3351,11 +3411,7 @@ Object.extend(Selector, {
>    },
>  
>    findChildElements: function(element, expressions) {
> -    var exprs = expressions.join(',');
> -    expressions = [];
> -    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
> -      expressions.push(m[1].strip());
> -    });
> +    expressions = Selector.split(expressions.join(','));
>      var results = [], h = Selector.handlers;
>      for (var i = 0, l = expressions.length, selector; i < l; i++) {
>        selector = new Selector(expressions[i].strip());
> @@ -3366,13 +3422,22 @@ Object.extend(Selector, {
>  });
>  
>  if (Prototype.Browser.IE) {
> -  // IE returns comment nodes on getElementsByTagName("*").
> -  // Filter them out.
> -  Selector.handlers.concat = function(a, b) {
> -    for (var i = 0, node; node = b[i]; i++)
> -      if (node.tagName !== "!") a.push(node);
> -    return a;
> -  };
> +  Object.extend(Selector.handlers, {
> +    // IE returns comment nodes on getElementsByTagName("*").
> +    // Filter them out.
> +    concat: function(a, b) {
> +      for (var i = 0, node; node = b[i]; i++)
> +        if (node.tagName !== "!") a.push(node);
> +      return a;
> +    },
> +
> +    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
> +    unmark: function(nodes) {
> +      for (var i = 0, node; node = nodes[i]; i++)
> +        node.removeAttribute('_countedByPrototype');
> +      return nodes;
> +    }
> +  });
>  }
>  
>  function $$() {
> @@ -3392,7 +3457,7 @@ var Form = {
>      var data = elements.inject({ }, function(result, element) {
>        if (!element.disabled && element.name) {
>          key = element.name; value = $(element).getValue();
> -        if (value != null && (element.type != 'submit' || (!submitted &&
> +        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
>              submit !== false && (!submit || key == submit) && (submitted = true)))) {
>            if (key in result) {
>              // a key is already present; construct an array of values
> @@ -3553,7 +3618,6 @@ Form.Element.Methods = {
>  
>    disable: function(element) {
>      element = $(element);
> -    element.blur();
>      element.disabled = true;
>      return element;
>    },
> @@ -3593,22 +3657,22 @@ Form.Element.Serializers = {
>      else element.value = value;
>    },
>  
> -  select: function(element, index) {
> -    if (Object.isUndefined(index))
> +  select: function(element, value) {
> +    if (Object.isUndefined(value))
>        return this[element.type == 'select-one' ?
>          'selectOne' : 'selectMany'](element);
>      else {
> -      var opt, value, single = !Object.isArray(index);
> +      var opt, currentValue, single = !Object.isArray(value);
>        for (var i = 0, length = element.length; i < length; i++) {
>          opt = element.options[i];
> -        value = this.optionValue(opt);
> +        currentValue = this.optionValue(opt);
>          if (single) {
> -          if (value == index) {
> +          if (currentValue == value) {
>              opt.selected = true;
>              return;
>            }
>          }
> -        else opt.selected = index.include(value);
> +        else opt.selected = value.include(currentValue);
>        }
>      }
>    },
> @@ -3779,8 +3843,23 @@ Event.Methods = (function() {
>      isRightClick:  function(event) { return isButton(event, 2) },
>  
>      element: function(event) {
> -      var node = Event.extend(event).target;
> -      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
> +      event = Event.extend(event);
> +
> +      var node          = event.target,
> +          type          = event.type,
> +          currentTarget = event.currentTarget;
> +
> +      if (currentTarget && currentTarget.tagName) {
> +        // Firefox screws up the "click" event when moving between radio buttons
> +        // via arrow keys. It also screws up the "load" and "error" events on images,
> +        // reporting the document as the target instead of the original image.
> +        if (type === 'load' || type === 'error' ||
> +          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
> +            && currentTarget.type === 'radio'))
> +              node = currentTarget;
> +      }
> +      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
> +      return Element.extend(node);
>      },
>  
>      findElement: function(event, expression) {
> @@ -3791,11 +3870,15 @@ Event.Methods = (function() {
>      },
>  
>      pointer: function(event) {
> +      var docElement = document.documentElement,
> +      body = document.body || { scrollLeft: 0, scrollTop: 0 };
>        return {
>          x: event.pageX || (event.clientX +
> -          (document.documentElement.scrollLeft || document.body.scrollLeft)),
> +          (docElement.scrollLeft || body.scrollLeft) -
> +          (docElement.clientLeft || 0)),
>          y: event.pageY || (event.clientY +
> -          (document.documentElement.scrollTop || document.body.scrollTop))
> +          (docElement.scrollTop || body.scrollTop) -
> +          (docElement.clientTop || 0))
>        };
>      },
>  
> @@ -3840,7 +3923,7 @@ Event.extend = (function() {
>      };
>  
>    } else {
> -    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
> +    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
>      Object.extend(Event.prototype, methods);
>      return Prototype.K;
>    }
> @@ -3850,9 +3933,9 @@ Object.extend(Event, (function() {
>    var cache = Event.cache;
>  
>    function getEventID(element) {
> -    if (element._eventID) return element._eventID;
> +    if (element._prototypeEventID) return element._prototypeEventID[0];
>      arguments.callee.id = arguments.callee.id || 1;
> -    return element._eventID = ++arguments.callee.id;
> +    return element._prototypeEventID = [++arguments.callee.id];
>    }
>  
>    function getDOMEventName(eventName) {
> @@ -3880,7 +3963,7 @@ Object.extend(Event, (function() {
>            return false;
>  
>        Event.extend(event);
> -      handler.call(element, event)
> +      handler.call(element, event);
>      };
>  
>      wrapper.handler = handler;
> @@ -3905,10 +3988,20 @@ Object.extend(Event, (function() {
>          cache[id][eventName] = null;
>    }
>  
> +
> +  // Internet Explorer needs to remove event handlers on page unload
> +  // in order to avoid memory leaks.
>    if (window.attachEvent) {
>      window.attachEvent("onunload", destroyCache);
>    }
>  
> +  // Safari has a dummy event handler on page unload so that it won't
> +  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
> +  // object when page is returned to via the back button using its bfcache.
> +  if (Prototype.Browser.WebKit) {
> +    window.addEventListener('unload', Prototype.emptyFunction, false);
> +  }
> +
>    return {
>      observe: function(element, eventName, handler) {
>        element = $(element);
> @@ -3962,11 +4055,12 @@ Object.extend(Event, (function() {
>        if (element == document && document.createEvent && !element.dispatchEvent)
>          element = document.documentElement;
>  
> +      var event;
>        if (document.createEvent) {
> -        var event = document.createEvent("HTMLEvents");
> +        event = document.createEvent("HTMLEvents");
>          event.initEvent("dataavailable", true, true);
>        } else {
> -        var event = document.createEventObject();
> +        event = document.createEventObject();
>          event.eventType = "ondataavailable";
>        }
>  
> @@ -3995,20 +4089,21 @@ Element.addMethods({
>  Object.extend(document, {
>    fire:          Element.Methods.fire.methodize(),
>    observe:       Element.Methods.observe.methodize(),
> -  stopObserving: Element.Methods.stopObserving.methodize()
> +  stopObserving: Element.Methods.stopObserving.methodize(),
> +  loaded:        false
>  });
>  
>  (function() {
>    /* Support for the DOMContentLoaded event is based on work by Dan Webb,
>       Matthias Miller, Dean Edwards and John Resig. */
>  
> -  var timer, fired = false;
> +  var timer;
>  
>    function fireContentLoadedEvent() {
> -    if (fired) return;
> +    if (document.loaded) return;
>      if (timer) window.clearInterval(timer);
>      document.fire("dom:loaded");
> -    fired = true;
> +    document.loaded = true;
>    }
>  
>    if (document.addEventListener) {
>   
ACK




More information about the ovirt-devel mailing list