[
  {
    "path": ".document",
    "content": "README.rdoc\nlib/**/*.rb\nbin/*\nfeatures/**/*.feature\nLICENSE\n"
  },
  {
    "path": ".gitignore",
    "content": "## MAC OS\n.DS_Store\n\n## TEXTMATE\n*.tmproj\ntmtags\n\n## EMACS\n*~\n\\#*\n.\\#*\n\n## VIM\n*.swp\n\n## PROJECT::GENERAL\ncoverage\nrdoc\npkg\n\n## PROJECT::SPECIFIC\n.bundle\n.idea\nbin\n\n# RAILS\nlog\n\n"
  },
  {
    "path": ".rspec",
    "content": "--colour\n\n"
  },
  {
    "path": ".rvmrc",
    "content": "#!/usr/bin/env bash\n\n# This is an RVM Project .rvmrc file, used to automatically load the ruby\n# development environment upon cd'ing into the directory\n\n# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,\n# Only full ruby name is supported here, for short names use:\n#     echo \"rvm use 2.0.0\" > .rvmrc\nenvironment_id=\"ruby-2.0.0-p353@analytical\"\n\n# Uncomment the following lines if you want to verify rvm version per project\n# rvmrc_rvm_version=\"1.24.7 (master)\" # 1.10.1 seems like a safe start\n# eval \"$(echo ${rvm_version}.${rvmrc_rvm_version} | __rvm_awk -F. '{print \"[[ \"$1*65536+$2*256+$3\" -ge \"$4*65536+$5*256+$6\" ]]\"}' )\" || {\n#   echo \"This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading.\"\n#   return 1\n# }\n\n# First we attempt to load the desired environment directly from the environment\n# file. This is very fast and efficient compared to running through the entire\n# CLI and selector. If you want feedback on which environment was used then\n# insert the word 'use' after --create as this triggers verbose mode.\nif [[ -d \"${rvm_path:-$HOME/.rvm}/environments\"\n  && -s \"${rvm_path:-$HOME/.rvm}/environments/$environment_id\" ]]\nthen\n  \\. \"${rvm_path:-$HOME/.rvm}/environments/$environment_id\"\n  for __hook in \"${rvm_path:-$HOME/.rvm}/hooks/after_use\"*\n  do\n    if [[ -f \"${__hook}\" && -x \"${__hook}\" && -s \"${__hook}\" ]]\n    then \\. \"${__hook}\" || true\n    fi\n  done\n  unset __hook\n  if (( ${rvm_use_flag:=1} >= 2 )) # display only when forced\n  then\n    if [[ $- == *i* ]] # check for interactive shells\n    then printf \"%b\" \"Using: $(tput setaf 2 2>/dev/null)$GEM_HOME$(tput sgr0 2>/dev/null)\n\" # show the user the ruby and gemset they are using in green\n    else printf \"%b\" \"Using: $GEM_HOME\n\" # don't use colors in non-interactive shells\n    fi\n  fi\nelse\n  # If the environment file has not yet been created, use the RVM CLI to select.\n  rvm --create  \"$environment_id\" || {\n    echo \"Failed to create RVM environment '${environment_id}'.\"\n    return 1\n  }\nfi\n\n# If you use bundler, this might be useful to you:\n# if [[ -s Gemfile ]] && {\n#   ! builtin command -v bundle >/dev/null ||\n#   builtin command -v bundle | GREP_OPTIONS=\"\" \\grep $rvm_path/bin/bundle >/dev/null\n# }\n# then\n#   printf \"%b\" \"The rubygem 'bundler' is not installed. Installing it now.\\n\"\n#   gem install bundler\n# fi\n# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null\n# then\n#   bundle install | GREP_OPTIONS=\"\" \\grep -vE '^Using|Your bundle is complete'\n# fi\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Analytical Changelog\n\n## 3.0.12\n\n* Fixed version checking so it works with Rails 3.2\n* Fixed example configuration in README\n* Enhanced google module\n\n## 3.0.9\n\n* Fixed bug that was causing problems in Rails 2.3 & 3.0  (Issue #19)\n* Cleaned up gemspec, Rakefile, added Changelog (Issue #26)\n* Added example apps for rails 2.3, 3.0, 3.1.\n\n## 3.0.8\n\n* Pulled in changes to update MixPanel event tracking (#25)\n\n## 3.0.7\n\n* Fixed broken clicky tests (#20)\n* \"Not all options are a hash and not all options are modules.\" (#21 and e20b256c8b)\n\n## 3.0.2\n\n* Updated Clicky so use async loading\n\n## 3.0.0\n\n*Significant update (tried to maintain backward compatibility)*\n\n* Support filtering modules, per-request (see README)\n* Added javascript helpers, enabled by default (see README)\n\n## 2.11.0\n\n*Significant update (tried to maintain backward compatibility)*\n\n* Modified analytical.yml structure to support environment-specific settings (d5e893c289)\n\n\n\n"
  },
  {
    "path": "Gemfile",
    "content": "source :rubygems\n\ngroup :development, :test do\n  gem 'rails', '3.0.9'\n  gem 'activesupport'\n  gem 'activemodel'\n  gem 'rspec-rails', '~> 2.11'\n  gem 'jeweler'\n  gem 'diff-lcs'\n  gem 'guard'\n  gem 'guard-rspec'\n  gem 'rb-fsevent', \">= 0.9.2\"\n  gem 'growl'\nend\n"
  },
  {
    "path": "Guardfile",
    "content": "# A sample Guardfile\n# More info at http://github.com/guard/guard#readme\n\nguard 'rspec', :version => 1 do\n  watch(/^spec\\/(.*)_spec.rb/)\n  watch(/^spec\\/spec_helper.rb/)                        { \"spec\" }\n  watch(/^lib\\/(.*)\\.rb/)                               { |m| \"spec/lib/#{m[1]}_spec.rb\" }\nend\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009 Joshua Krall\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.rdoc",
    "content": "= Analytical\n\nGem for managing multiple analytics services in your rails app.\n\n{<img src=\"https://secure.travis-ci.org/jkrall/analytical.png\" />}[http://travis-ci.org/jkrall/analytical]\n\nService implementations include:\n* Google Analytics\n* Google Adwords (conversion tracking)\n* Clicky[http://getclicky.com]\n* KISSMetrics[http://kissmetrics.com]\n* Hubspot[http://hubspot.com]\n* CrazyEgg[http://www.crazyegg.com]\n* Chartbeat[http://chartbeat.com]\n* {ComScore Direct}[http://direct.comscore.com]\n* Optimizely[http://www.optimizely.com]\n* Loopfuse\n* Clicktale\n* Google Website Optimizer\n* Performancing\n* Quantcast[http://www.quantcast.com]\n* MixPanel[http://www.mixpanel.com]\n* Gauges[http://get.gaug.es]\n* Segment.io[http://segment.io]\n* Mouseflow[http://mouseflow.com]\n\n== Usage\n\nAdd the following to your controllers:\n\n    analytical\n\nAdd a configuration file (config/analytical.yml) to declare your API keys, like so:\n\n   production:\n     google:\n       key: UA-5555555-5\n     clicky:\n       key: 55555\n   development:\n   test:\n\nYou can add different configurations for different environments.\n\nBy default, all the declared analytics modules are loaded. You can override this behavior in the controller.\n\n    analytical :modules=>[:console, :google, :clicky]\n\nThen, in your template files, you'll want to add the analytical helper methods for initializing the tracking javascript:\n\n    <!DOCTYPE html>\n    <html>\n      <head>\n        <%= raw analytical.head_prepend_javascript %>\n        <title>Example</title>\n        <%= stylesheet_link_tag :all %>\n        <%= javascript_include_tag :defaults %>\n        <%= csrf_meta_tag %>\n        <% analytical.identify '5', :email=>'josh@transfs.com' %>\n        <%= raw analytical.head_append_javascript %>\n      </head>\n      <body>\n        <%= raw analytical.body_prepend_javascript %>\n        <%= yield %>\n        <%= raw analytical.body_append_javascript %>\n      </body>\n    </html>\n\nNote the example above also includes an identify() command that will apply to every page.  More likely, you'll want to make this identify() command conditional so that it only applies when you have a logged-in user:\n\n    <% analytical.identify(current_user.id, :email=>current_user.email) if current_user %>\n\nYou can sprinkle the track() and event() tracking methods throughout your views & controllers as needed.\n\n    analytical.track '/a/really/nice/url'\n    analytical.event 'Some Awesome Event', :with=>:data\n\nIt's possible to conditionally disable analytics by specifying a `disable_if` method.\n\n    analytical :disable_if => lambda{ |controller| controller.i_can_haz_tracking? }\n\nAnalytical also provides the ability to filter your active modules dynamically:\n\n    analytical :filter_modules => lambda{ |controller, modules|\n      controller.use_google_analytics? ? modules : modules - [:google]\n    }\n\nThis can be useful for enabling the console logger optionally in your app, based on a request parameter:\n\n    :filter_modules => lambda { |controller, modules|\n      controller.use_console_logger? ? [:console] : modules\n    }\n\nYou can also configure modules in the controller by passing a hash to the :modules option\n\n    analytical :modules => { :google => { key: 'UA-5555555-5' } }\n\nFinally it is also possible to dynamically specify what modules to use and their configurations, which can come in handy if your site uses multi-tenancy, where every tenant might not wish to use the same modules or configurations for each module.\n\n    analytical :modules => lambda{ |controller| controller.analytical_modules }\n\nIn this case we would then implement a method in the controller that could look like this:\n\n    def analytical_modules\n      case current_tenant.name.parameterize\n      when \"site-1\"\n        {\n          :google => { key: 'UA-5555555-5' }\n        }\n      when \"site-2\"\n        {\n          :google => { key: 'UA-1111111-1' }\n        }\n      end\n    end\n    hide_action :analytical_modules\n\n== Adding new modules\n\nNew modules should be fairly easy to add.  Follow the structure that I've used in the Clicky, Google, and KISSMetrics modules... and you should be fine.  All modules should include the Analytical::Base::Api module, so that they have some default behavior for methods that they don't support or need.\n\n== Session-based command queues\n\nBy default, any Analytical commands that are queued in a controller that subsequently redirects won't be emitted to the client.  This is because the redirect triggers a new request, and everything is cleared out at the beginning of each request.\n\nHowever, if you would like to be able to queue commands between requests... there's a new option that supports this behavior:\n\n    analytical :modules=>[:console, :google], :use_session_store=>true\n\nThis will store the queued commands in the user session, clearing them out when they are emitted to the client, but allowing you to make calls like:\n\n    analytical.track 'something'\n\n... in your controller.  After a redirect, the corresponding track() call will be emitted in the next request made by the client.\nNOTE:  This is new and somewhat experimental, and could cause problems if you store large amounts of data in the session.  (there is a 4k limit to session data)\n\n== Javascript tracking/event commands\n\nSometimes you want to trigger an analytics track/event via javascript.  Analytical now provides a solution for this by default.  Appended to your <head> is a simple javascript object that will contain \"instantaneous\" versions of the tracking commands for each of your modules.\n\nYou call these analytical commands like this:\n\n    <script type=\"text/javascript\">Analytical.track('/some/url');</script>\n    <script type=\"text/javascript\">Analytical.event('A javascript event', {with: 'data'});</script>\n\nWhen you call these commands, it will pass the track/event name & data on to each of the modules.  (Take a look at the simple javascript helpers in your <head> for more information.)\n\nTo disable this functionality, use\n\n    analytical :javascript_helpers => false\n\n\n== Note on Patches/Pull Requests\n\nI would be extremely happy to accept contributions to this project!  If you think something should work differently, send me a message and I'd love to discuss your ideas.  Even better:\n\n* Fork the project.\n* Make your feature addition or bug fix.\n* Add specs for it. This is important so I don't break it in a future version unintentionally.\n* Commit, do not mess with rakefile, version, or history.\n  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)\n* Send me a pull request. Bonus points for topic branches.\n\n== Current Contributors\n\nThese fine folks have contributed patches and new features to this project:\n\n* {Adam Anderson}[http://github.com/scudco]\n* {Bryan Liles}[http://github.com/bryanl]\n* {Nathan Phelps}[http://github.com/nwp]\n* {Adam Anderson}[http://github.com/scudco]\n* {Kevin Menard}[http://github.com/nirvdrum]\n* {Ablyamitov Ablyamit}[http://github.com/unlimit]\n* {Kurt Werle}[http://github.com/kwerle]\n* {Olivier Lauzon}[http://github.com/olauzon]\n* {Chris Ricca}[https://github.com/ChrisRicca]\n* {Rajiv Aaron Manglani}[https://github.com/rajiv]\n* {Thomas Dippel}[https://github.com/dipth]\n\nThanks guys!\n\n\n== Copyright\n\nCopyright (c) 2010 Joshua Krall. See LICENSE for details.\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'rubygems'\nrequire 'rake'\n\nbegin\n  require 'jeweler'\n  Jeweler::Tasks.new do |gem|\n    gem.name = \"analytical\"\n    gem.summary = %Q{Gem for managing multiple analytics services in your rails app.}\n    gem.description = %Q{Gem for managing multiple analytics services in your rails app.}\n    gem.email = \"josh@feefighters.com\"\n    gem.homepage = \"http://github.com/jkrall/analytical\"\n    gem.authors = [\"Joshua Krall\", \"Nathan Phelps\", \"Adam Anderson\", \"Kevin Menard\", \"Ablyamitov Ablyamit\", \"Kurt Werle\", \"Olivier Lauzon\", \"Daniel Doubrovkine\"]\n    gem.files = Dir['lib/**/*'] + Dir['app/**/*'] + Dir['rails/**/*']\n    # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings\n  end\n  Jeweler::GemcutterTasks.new\nrescue LoadError\n  puts \"Jeweler (or a dependency) not available. Install it with: gem install jeweler\"\nend\n\nrequire 'rspec/core/rake_task'\nRSpec::Core::RakeTask.new(:spec)\nRSpec::Core::RakeTask.new(:rcov)\n\ntask :default => :spec\n\nrequire 'rdoc/task'\nRake::RDocTask.new do |rdoc|\n  version = File.exist?('VERSION') ? File.read('VERSION') : \"\"\n\n  rdoc.rdoc_dir = 'rdoc'\n  rdoc.title = \"analytical #{version}\"\n  rdoc.rdoc_files.include('README*')\n  rdoc.rdoc_files.include('lib/**/*.rb')\nend\n"
  },
  {
    "path": "VERSION",
    "content": "3.0.12"
  },
  {
    "path": "analytical.gemspec",
    "content": "# Generated by jeweler\n# DO NOT EDIT THIS FILE DIRECTLY\n# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'\n# -*- encoding: utf-8 -*-\n\nGem::Specification.new do |s|\n  s.name = %q{analytical}\n  s.version = \"3.0.12\"\n\n  s.required_rubygems_version = Gem::Requirement.new(\">= 0\") if s.respond_to? :required_rubygems_version=\n  s.authors = [\"Joshua Krall\", \"Nathan Phelps\", \"Adam Anderson\", \"Kevin Menard\", \"Ablyamitov Ablyamit\", \"Kurt Werle\", \"Olivier Lauzon\", \"Daniel Doubrovkine\"]\n  s.date = %q{2012-01-24}\n  s.description = %q{Gem for managing multiple analytics services in your rails app.}\n  s.email = %q{josh@feefighters.com}\n  s.extra_rdoc_files = [\n    \"LICENSE\",\n    \"README.rdoc\"\n  ]\n  s.files = [\n    \"app/views/application/_analytical_javascript.html.erb\",\n    \"lib/analytical.rb\",\n    \"lib/analytical/api.rb\",\n    \"lib/analytical/bot_detector.rb\",\n    \"lib/analytical/command_store.rb\",\n    \"lib/analytical/modules/adroll.rb\",\n    \"lib/analytical/modules/adwords.rb\",\n    \"lib/analytical/modules/base.rb\",\n    \"lib/analytical/modules/chartbeat.rb\",\n    \"lib/analytical/modules/click_tale.rb\",\n    \"lib/analytical/modules/clicky.rb\",\n    \"lib/analytical/modules/comscore.rb\",\n    \"lib/analytical/modules/console.rb\",\n    \"lib/analytical/modules/crazy_egg.rb\",\n    \"lib/analytical/modules/google.rb\",\n    \"lib/analytical/modules/google_optimizer.rb\",\n    \"lib/analytical/modules/hubspot.rb\",\n    \"lib/analytical/modules/kiss_metrics.rb\",\n    \"lib/analytical/modules/loopfuse.rb\",\n    \"lib/analytical/modules/microsoft_ads.rb\",\n    \"lib/analytical/modules/mixpanel.rb\",\n    \"lib/analytical/modules/optimizely.rb\",\n    \"lib/analytical/modules/performancing.rb\",\n    \"lib/analytical/modules/quantcast.rb\",\n    \"lib/analytical/modules/reinvigorate.rb\",\n    \"lib/analytical/rails/engine.rb\",\n    \"lib/analytical/session_command_store.rb\",\n    \"rails/init.rb\"\n  ]\n  s.homepage = %q{http://github.com/jkrall/analytical}\n  s.require_paths = [\"lib\"]\n  s.rubygems_version = %q{1.6.2}\n  s.summary = %q{Gem for managing multiple analytics services in your rails app.}\n\n  if s.respond_to? :specification_version then\n    s.specification_version = 3\n\n    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then\n      s.add_development_dependency(%q<rails>, [\"= 3.0.9\"])\n      s.add_development_dependency(%q<activesupport>, [\">= 0\"])\n      s.add_development_dependency(%q<activemodel>, [\">= 0\"])\n      s.add_development_dependency(%q<rspec>, [\"= 2.6.0\"])\n      s.add_development_dependency(%q<rspec-core>, [\"= 2.6.4\"])\n      s.add_development_dependency(%q<rspec-expectations>, [\"= 2.6.0\"])\n      s.add_development_dependency(%q<rspec-mocks>, [\"= 2.6.0\"])\n      s.add_development_dependency(%q<rspec-rails>, [\"= 2.6.0\"])\n      s.add_development_dependency(%q<jeweler>, [\">= 0\"])\n      s.add_development_dependency(%q<diff-lcs>, [\">= 0\"])\n      s.add_development_dependency(%q<guard>, [\">= 0\"])\n      s.add_development_dependency(%q<guard-rspec>, [\">= 0\"])\n      s.add_development_dependency(%q<rb-fsevent>, [\">= 0\"])\n      s.add_development_dependency(%q<growl>, [\">= 0\"])\n    else\n      s.add_dependency(%q<rails>, [\"= 3.0.9\"])\n      s.add_dependency(%q<activesupport>, [\">= 0\"])\n      s.add_dependency(%q<activemodel>, [\">= 0\"])\n      s.add_dependency(%q<rspec>, [\"= 2.6.0\"])\n      s.add_dependency(%q<rspec-core>, [\"= 2.6.4\"])\n      s.add_dependency(%q<rspec-expectations>, [\"= 2.6.0\"])\n      s.add_dependency(%q<rspec-mocks>, [\"= 2.6.0\"])\n      s.add_dependency(%q<rspec-rails>, [\"= 2.6.0\"])\n      s.add_dependency(%q<jeweler>, [\">= 0\"])\n      s.add_dependency(%q<diff-lcs>, [\">= 0\"])\n      s.add_dependency(%q<guard>, [\">= 0\"])\n      s.add_dependency(%q<guard-rspec>, [\">= 0\"])\n      s.add_dependency(%q<rb-fsevent>, [\">= 0\"])\n      s.add_dependency(%q<growl>, [\">= 0\"])\n    end\n  else\n    s.add_dependency(%q<rails>, [\"= 3.0.9\"])\n    s.add_dependency(%q<activesupport>, [\">= 0\"])\n    s.add_dependency(%q<activemodel>, [\">= 0\"])\n    s.add_dependency(%q<rspec>, [\"= 2.6.0\"])\n    s.add_dependency(%q<rspec-core>, [\"= 2.6.4\"])\n    s.add_dependency(%q<rspec-expectations>, [\"= 2.6.0\"])\n    s.add_dependency(%q<rspec-mocks>, [\"= 2.6.0\"])\n    s.add_dependency(%q<rspec-rails>, [\"= 2.6.0\"])\n    s.add_dependency(%q<jeweler>, [\">= 0\"])\n    s.add_dependency(%q<diff-lcs>, [\">= 0\"])\n    s.add_dependency(%q<guard>, [\">= 0\"])\n    s.add_dependency(%q<guard-rspec>, [\">= 0\"])\n    s.add_dependency(%q<rb-fsevent>, [\">= 0\"])\n    s.add_dependency(%q<growl>, [\">= 0\"])\n  end\nend\n\n"
  },
  {
    "path": "app/views/application/_analytical_javascript.html.erb",
    "content": "<script type=\"text/javascript\">\nvar Analytical = {\n  track: function(page) {\n<%= raw analytical.now.track('__PAGE__').gsub(/\"__PAGE__\"/,'page') %>\n  },\n  event: function(name, data) {\n    if (typeof data === 'undefined') { data = {}; }\n<%= raw analytical.now.event('__EVENT__', {}).gsub(/\"__EVENT__\"/,'name').gsub(/\"?\\{\\}\"?/,'data') %>\n  }\n};\n</script>\n"
  },
  {
    "path": "example/.gitignore",
    "content": ".bundle\ndb/*.sqlite3\nlog/*.log\ntmp/**/*\n"
  },
  {
    "path": "example/rails2.3/Gemfile",
    "content": "source 'http://rubygems.org'\n\ngem 'rails', '~> 2.3.0'\n\n# Bundle edge Rails instead:\n# gem 'rails', :git => 'git://github.com/rails/rails.git'\n\ngem 'sqlite3-ruby', :require => 'sqlite3'\ngem 'analytical', :path => '../..'\n\n# Use unicorn as the web server\n# gem 'unicorn'\n\n# Deploy with Capistrano\n# gem 'capistrano'\n\n# Bundle the extra gems:\n# gem 'bj'\n# gem 'nokogiri', '1.4.1'\n# gem 'sqlite3-ruby', :require => 'sqlite3'\n# gem 'aws-s3', :require => 'aws/s3'\n\n# Bundle gems for certain environments:\n# gem 'rspec', :group => :test\n# group :test do\n#   gem 'webrat'\n# end\n"
  },
  {
    "path": "example/rails2.3/README",
    "content": "== Welcome to Rails\n\nRails is a web-application framework that includes everything needed to create \ndatabase-backed web applications according to the Model-View-Control pattern. \n\nThis pattern splits the view (also called the presentation) into \"dumb\" templates\nthat are primarily responsible for inserting pre-built data in between HTML tags.\nThe model contains the \"smart\" domain objects (such as Account, Product, Person,\nPost) that holds all the business logic and knows how to persist themselves to\na database. The controller handles the incoming requests (such as Save New Account,\nUpdate Product, Show Post) by manipulating the model and directing data to the view.\n\nIn Rails, the model is handled by what's called an object-relational mapping\nlayer entitled Active Record. This layer allows you to present the data from\ndatabase rows as objects and embellish these data objects with business logic\nmethods. You can read more about Active Record in\nlink:files/vendor/rails/activerecord/README.html.\n\nThe controller and view are handled by the Action Pack, which handles both\nlayers by its two parts: Action View and Action Controller. These two layers\nare bundled in a single package due to their heavy interdependence. This is\nunlike the relationship between the Active Record and Action Pack that is much\nmore separate. Each of these packages can be used independently outside of\nRails.  You can read more about Action Pack in\nlink:files/vendor/rails/actionpack/README.html.\n\n\n== Getting Started\n\n1. At the command prompt, start a new Rails application using the <tt>rails</tt> command\n   and your application name. Ex: rails myapp\n2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)\n3. Go to http://localhost:3000/ and get \"Welcome aboard: You're riding the Rails!\"\n4. Follow the guidelines to start developing your application\n\n\n== Web Servers\n\nBy default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails\nwith a variety of other web servers.\n\nMongrel is a Ruby-based webserver with a C component (which requires compilation) that is\nsuitable for development and deployment of Rails applications. If you have Ruby Gems installed,\ngetting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.\nMore info at: http://mongrel.rubyforge.org\n\nSay other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or\nLighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use\nFCGI or proxy to a pack of Mongrels/Thin/Ebb servers.\n\n== Apache .htaccess example for FCGI/CGI\n\n# General Apache options\nAddHandler fastcgi-script .fcgi\nAddHandler cgi-script .cgi\nOptions +FollowSymLinks +ExecCGI\n\n# If you don't want Rails to look in certain directories,\n# use the following rewrite rules so that Apache won't rewrite certain requests\n# \n# Example:\n#   RewriteCond %{REQUEST_URI} ^/notrails.*\n#   RewriteRule .* - [L]\n\n# Redirect all requests not available on the filesystem to Rails\n# By default the cgi dispatcher is used which is very slow\n# \n# For better performance replace the dispatcher with the fastcgi one\n#\n# Example:\n#   RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]\nRewriteEngine On\n\n# If your Rails application is accessed via an Alias directive,\n# then you MUST also set the RewriteBase in this htaccess file.\n#\n# Example:\n#   Alias /myrailsapp /path/to/myrailsapp/public\n#   RewriteBase /myrailsapp\n\nRewriteRule ^$ index.html [QSA]\nRewriteRule ^([^.]+)$ $1.html [QSA]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule ^(.*)$ dispatch.cgi [QSA,L]\n\n# In case Rails experiences terminal errors\n# Instead of displaying this message you can supply a file here which will be rendered instead\n# \n# Example:\n#   ErrorDocument 500 /500.html\n\nErrorDocument 500 \"<h2>Application error</h2>Rails application failed to start properly\"\n\n\n== Debugging Rails\n\nSometimes your application goes wrong.  Fortunately there are a lot of tools that\nwill help you debug it and get it back on the rails.\n\nFirst area to check is the application log files.  Have \"tail -f\" commands running\non the server.log and development.log. Rails will automatically display debugging\nand runtime information to these files. Debugging info will also be shown in the\nbrowser on requests from 127.0.0.1.\n\nYou can also log your own messages directly into the log file from your code using\nthe Ruby logger class from inside your controllers. Example:\n\n  class WeblogController < ActionController::Base\n    def destroy\n      @weblog = Weblog.find(params[:id])\n      @weblog.destroy\n      logger.info(\"#{Time.now} Destroyed Weblog ID ##{@weblog.id}!\")\n    end\n  end\n\nThe result will be a message in your log file along the lines of:\n\n  Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1\n\nMore information on how to use the logger is at http://www.ruby-doc.org/core/\n\nAlso, Ruby documentation can be found at http://www.ruby-lang.org/ including:\n\n* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/\n* Learn to Program: http://pine.fm/LearnToProgram/  (a beginners guide)\n\nThese two online (and free) books will bring you up to speed on the Ruby language\nand also on programming in general.\n\n\n== Debugger\n\nDebugger support is available through the debugger command when you start your Mongrel or\nWebrick server with --debugger. This means that you can break out of execution at any point\nin the code, investigate and change the model, AND then resume execution! \nYou need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'\nExample:\n\n  class WeblogController < ActionController::Base\n    def index\n      @posts = Post.find(:all)\n      debugger\n    end\n  end\n\nSo the controller will accept the action, run the first line, then present you\nwith a IRB prompt in the server window. Here you can do things like:\n\n  >> @posts.inspect\n  => \"[#<Post:0x14a6be8 @attributes={\\\"title\\\"=>nil, \\\"body\\\"=>nil, \\\"id\\\"=>\\\"1\\\"}>,\n       #<Post:0x14a6620 @attributes={\\\"title\\\"=>\\\"Rails you know!\\\", \\\"body\\\"=>\\\"Only ten..\\\", \\\"id\\\"=>\\\"2\\\"}>]\"\n  >> @posts.first.title = \"hello from a debugger\"\n  => \"hello from a debugger\"\n\n...and even better is that you can examine how your runtime objects actually work:\n\n  >> f = @posts.first\n  => #<Post:0x13630c4 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>\n  >> f.\n  Display all 152 possibilities? (y or n)\n\nFinally, when you're ready to resume execution, you enter \"cont\"\n\n\n== Console\n\nYou can interact with the domain model by starting the console through <tt>script/console</tt>.\nHere you'll have all parts of the application configured, just like it is when the\napplication is running. You can inspect domain models, change values, and save to the\ndatabase. Starting the script without arguments will launch it in the development environment.\nPassing an argument will specify a different environment, like <tt>script/console production</tt>.\n\nTo reload your controllers and models after launching the console run <tt>reload!</tt>\n\n== dbconsole\n\nYou can go to the command line of your database directly through <tt>script/dbconsole</tt>.\nYou would be connected to the database with the credentials defined in database.yml.\nStarting the script without arguments will connect you to the development database. Passing an\nargument will connect you to a different database, like <tt>script/dbconsole production</tt>.\nCurrently works for mysql, postgresql and sqlite.\n\n== Description of Contents\n\napp\n  Holds all the code that's specific to this particular application.\n\napp/controllers\n  Holds controllers that should be named like weblogs_controller.rb for\n  automated URL mapping. All controllers should descend from ApplicationController\n  which itself descends from ActionController::Base.\n\napp/models\n  Holds models that should be named like post.rb.\n  Most models will descend from ActiveRecord::Base.\n\napp/views\n  Holds the template files for the view that should be named like\n  weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby\n  syntax.\n\napp/views/layouts\n  Holds the template files for layouts to be used with views. This models the common\n  header/footer method of wrapping views. In your views, define a layout using the\n  <tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,\n  call <% yield %> to render the view using this layout.\n\napp/helpers\n  Holds view helpers that should be named like weblogs_helper.rb. These are generated\n  for you automatically when using script/generate for controllers. Helpers can be used to\n  wrap functionality for your views into methods.\n\nconfig\n  Configuration files for the Rails environment, the routing map, the database, and other dependencies.\n\ndb\n  Contains the database schema in schema.rb.  db/migrate contains all\n  the sequence of Migrations for your schema.\n\ndoc\n  This directory is where your application documentation will be stored when generated\n  using <tt>rake doc:app</tt>\n\nlib\n  Application specific libraries. Basically, any kind of custom code that doesn't\n  belong under controllers, models, or helpers. This directory is in the load path.\n\npublic\n  The directory available for the web server. Contains subdirectories for images, stylesheets,\n  and javascripts. Also contains the dispatchers and the default HTML files. This should be\n  set as the DOCUMENT_ROOT of your web server.\n\nscript\n  Helper scripts for automation and generation.\n\ntest\n  Unit and functional tests along with fixtures. When using the script/generate scripts, template\n  test files will be generated for you and placed in this directory.\n\nvendor\n  External libraries that the application depends on. Also includes the plugins subdirectory.\n  If the app has frozen rails, those gems also go here, under vendor/rails/.\n  This directory is in the load path.\n"
  },
  {
    "path": "example/rails2.3/Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire(File.join(File.dirname(__FILE__), 'config', 'boot'))\n\nrequire 'rake'\nrequire 'rake/testtask'\nrequire 'rake/rdoctask'\n\nrequire 'tasks/rails'\n"
  },
  {
    "path": "example/rails2.3/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  helper :all\n  protect_from_forgery\n\n  analytical :modules=>[:console, :google, :clicky], :disable_if=>lambda{|controller| false}, :use_session_store=>true\nend\n"
  },
  {
    "path": "example/rails2.3/app/controllers/page_controller.rb",
    "content": "class PageController < ApplicationController\n  def index\n  end\n\n  def test_a\n  end\n\n  def test_b\n    analytical.clicky.track('track link in A')\n    analytical.track 'track in controller'\n  end\n\n  def test_c\n    analytical.track 'track in controller that redirects'\n    redirect_to root_path\n  end\n\nend\n"
  },
  {
    "path": "example/rails2.3/app/helpers/application_helper.rb",
    "content": "# Methods added to this helper will be available to all templates in the application.\nmodule ApplicationHelper\nend\n"
  },
  {
    "path": "example/rails2.3/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Example</title>\n  <%= stylesheet_link_tag :all %>\n  <%= javascript_include_tag :defaults %>\n\n\t<% analytical.identify '5', :email=>'josh@feefighters.com' %>\n\t<%= analytical.head_javascript %>\n</head>\n<body>\n\t<%= analytical.body_prepend_javascript %>\n\t<%= yield %>\n\t<%= analytical.body_append_javascript %>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails2.3/app/views/page/index.html.erb",
    "content": "<h1>Page#index</h1>\n<p>\n\t<%= link_to 'A', '/page/test_a' %><br />\n\t<%= link_to 'B', '/page/test_b' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n</p>"
  },
  {
    "path": "example/rails2.3/app/views/page/test_a.html.erb",
    "content": "<% analytical.track 'track in view A' %>\n<% analytical.event 'Something Important', {:some=>'data'} %>\n<h1>Page#test_a</h1>\n<p>\n\t<%= link_to 'B', '/page/test_b' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n\t<%= link_to 'index', '/' %><br />\n\t<a href='#' onclick=\"<%= analytical.now.track('track link in A') %>; return false;\">click me to track all modules</a><br />\n</p>\n\n\n"
  },
  {
    "path": "example/rails2.3/app/views/page/test_b.html.erb",
    "content": "<h1>Page#test_b</h1>\n<p>\n\t<%= link_to 'A', '/page/test_a' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n\t<%= link_to 'index', '/' %><br />\n\t<a href='#' onclick=\"<%= analytical.console.track('track link in A') %>; return false;\">track console only</a><br />\n</p>\n"
  },
  {
    "path": "example/rails2.3/config/boot.rb",
    "content": "# Don't change this file!\n# Configure your app in config/environment.rb and config/environments/*.rb\n\nRAILS_ROOT = \"#{File.dirname(__FILE__)}/..\" unless defined?(RAILS_ROOT)\n\nmodule Rails\n  class << self\n    def boot!\n      unless booted?\n        preinitialize\n        pick_boot.run\n      end\n    end\n\n    def booted?\n      defined? Rails::Initializer\n    end\n\n    def pick_boot\n      (vendor_rails? ? VendorBoot : GemBoot).new\n    end\n\n    def vendor_rails?\n      File.exist?(\"#{RAILS_ROOT}/vendor/rails\")\n    end\n\n    def preinitialize\n      load(preinitializer_path) if File.exist?(preinitializer_path)\n    end\n\n    def preinitializer_path\n      \"#{RAILS_ROOT}/config/preinitializer.rb\"\n    end\n  end\n\n  class Boot\n    def run\n      load_initializer\n      Rails::Initializer.run(:set_load_path)\n    end\n  end\n\n  class VendorBoot < Boot\n    def load_initializer\n      require \"#{RAILS_ROOT}/vendor/rails/railties/lib/initializer\"\n      Rails::Initializer.run(:install_gem_spec_stubs)\n      Rails::GemDependency.add_frozen_gem_path\n    end\n  end\n\n  class GemBoot < Boot\n    def load_initializer\n      self.class.load_rubygems\n      load_rails_gem\n      require 'initializer'\n    end\n\n    def load_rails_gem\n      if version = self.class.gem_version\n        gem 'rails', version\n      else\n        gem 'rails'\n      end\n    rescue Gem::LoadError => load_error\n      if load_error.message =~ /Could not find RubyGem rails/\n        STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)\n        exit 1\n      else\n        raise\n      end\n    end\n\n    class << self\n      def rubygems_version\n        Gem::RubyGemsVersion rescue nil\n      end\n\n      def gem_version\n        if defined? RAILS_GEM_VERSION\n          RAILS_GEM_VERSION\n        elsif ENV.include?('RAILS_GEM_VERSION')\n          ENV['RAILS_GEM_VERSION']\n        else\n          parse_gem_version(read_environment_rb)\n        end\n      end\n\n      def load_rubygems\n        min_version = '1.3.2'\n        require 'rubygems'\n        unless rubygems_version >= min_version\n          $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)\n          exit 1\n        end\n\n      rescue LoadError\n        $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)\n        exit 1\n      end\n\n      def parse_gem_version(text)\n        $1 if text =~ /^[^#]*RAILS_GEM_VERSION\\s*=\\s*[\"']([!~<>=]*\\s*[\\d.]+)[\"']/\n      end\n\n      private\n        def read_environment_rb\n          File.read(\"#{RAILS_ROOT}/config/environment.rb\")\n        end\n    end\n  end\nend\n\n# All that for this:\nRails.boot!\n"
  },
  {
    "path": "example/rails2.3/config/database.yml",
    "content": "# SQLite version 3.x\n#   gem install sqlite3-ruby (not necessary on OS X Leopard)\ndevelopment:\n  adapter: sqlite3\n  database: db/development.sqlite3\n  pool: 5\n  timeout: 5000\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  adapter: sqlite3\n  database: db/test.sqlite3\n  pool: 5\n  timeout: 5000\n\nproduction:\n  adapter: sqlite3\n  database: db/production.sqlite3\n  pool: 5\n  timeout: 5000\n"
  },
  {
    "path": "example/rails2.3/config/environment.rb",
    "content": "# Be sure to restart your server when you modify this file\n\n# Specifies gem version of Rails to use when vendor/rails is not present\nRAILS_GEM_VERSION = '2.3.14' unless defined? RAILS_GEM_VERSION\n\n# Bootstrap the Rails environment, frameworks, and default configuration\nrequire File.join(File.dirname(__FILE__), 'boot')\n\nRails::Initializer.run do |config|\n  # Settings in config/environments/* take precedence over those specified here.\n  # Application configuration should go into files in config/initializers\n  # -- all .rb files in that directory are automatically loaded.\n\n  # Add additional load paths for your own custom dirs\n  # config.autoload_paths += %W( #{RAILS_ROOT}/extras )\n\n  # Specify gems that this application depends on and have them installed with rake gems:install\n  # config.gem \"bj\"\n  # config.gem \"hpricot\", :version => '0.6', :source => \"http://code.whytheluckystiff.net\"\n  # config.gem \"sqlite3-ruby\", :lib => \"sqlite3\"\n  # config.gem \"aws-s3\", :lib => \"aws/s3\"\n\n  # Only load the plugins named here, in the order given (default is alphabetical).\n  # :all can be used as a placeholder for all plugins not explicitly named\n  # config.plugins = [ :exception_notification, :ssl_requirement, :all ]\n\n  # Skip frameworks you're not going to use. To use Rails without a database,\n  # you must remove the Active Record framework.\n  # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]\n\n  # Activate observers that should always be running\n  # config.active_record.observers = :cacher, :garbage_collector, :forum_observer\n\n  # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.\n  # Run \"rake -D time\" for a list of tasks for finding time zone names.\n  config.time_zone = 'UTC'\n\n  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.\n  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]\n  # config.i18n.default_locale = :de\nend"
  },
  {
    "path": "example/rails2.3/config/environments/development.rb",
    "content": "# Settings specified here will take precedence over those in config/environment.rb\n\n# In the development environment your application's code is reloaded on\n# every request.  This slows down response time but is perfect for development\n# since you don't have to restart the webserver when you make code changes.\nconfig.cache_classes = false\n\n# Log error messages when you accidentally call methods on nil.\nconfig.whiny_nils = true\n\n# Show full error reports and disable caching\nconfig.action_controller.consider_all_requests_local = true\nconfig.action_view.debug_rjs                         = true\nconfig.action_controller.perform_caching             = false\n\n# Don't care if the mailer can't send\nconfig.action_mailer.raise_delivery_errors = false"
  },
  {
    "path": "example/rails2.3/config/environments/production.rb",
    "content": "# Settings specified here will take precedence over those in config/environment.rb\n\n# The production environment is meant for finished, \"live\" apps.\n# Code is not reloaded between requests\nconfig.cache_classes = true\n\n# Full error reports are disabled and caching is turned on\nconfig.action_controller.consider_all_requests_local = false\nconfig.action_controller.perform_caching             = true\nconfig.action_view.cache_template_loading            = true\n\n# See everything in the log (default is :info)\n# config.log_level = :debug\n\n# Use a different logger for distributed setups\n# config.logger = SyslogLogger.new\n\n# Use a different cache store in production\n# config.cache_store = :mem_cache_store\n\n# Enable serving of images, stylesheets, and javascripts from an asset server\n# config.action_controller.asset_host = \"http://assets.example.com\"\n\n# Disable delivery errors, bad email addresses will be ignored\n# config.action_mailer.raise_delivery_errors = false\n\n# Enable threaded mode\n# config.threadsafe!"
  },
  {
    "path": "example/rails2.3/config/environments/test.rb",
    "content": "# Settings specified here will take precedence over those in config/environment.rb\n\n# The test environment is used exclusively to run your application's\n# test suite.  You never need to work with it otherwise.  Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs.  Don't rely on the data there!\nconfig.cache_classes = true\n\n# Log error messages when you accidentally call methods on nil.\nconfig.whiny_nils = true\n\n# Show full error reports and disable caching\nconfig.action_controller.consider_all_requests_local = true\nconfig.action_controller.perform_caching             = false\nconfig.action_view.cache_template_loading            = true\n\n# Disable request forgery protection in test environment\nconfig.action_controller.allow_forgery_protection    = false\n\n# Tell Action Mailer not to deliver emails to the real world.\n# The :test delivery method accumulates sent emails in the\n# ActionMailer::Base.deliveries array.\nconfig.action_mailer.delivery_method = :test\n\n# Use SQL instead of Active Record's schema dumper when creating the test database.\n# This is necessary if your schema can't be completely dumped by the schema dumper,\n# like if you have constraints or database-specific column types\n# config.active_record.schema_format = :sql"
  },
  {
    "path": "example/rails2.3/config/initializers/backtrace_silencers.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }\n\n# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.\n# Rails.backtrace_cleaner.remove_silencers!"
  },
  {
    "path": "example/rails2.3/config/initializers/cookie_verification_secret.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Your secret key for verifying the integrity of signed cookies.\n# If you change this key, all old signed cookies will become invalid!\n# Make sure the secret is at least 30 characters and all random, \n# no regular words or you'll be exposed to dictionary attacks.\nActionController::Base.cookie_verifier_secret = '9f8775eb823fb91b025741105b96651584bd80dca1c1b53e93ddb30e0c093bf3a2fed5326d90f516903dc2b32dc29872b2e380e706cdff5972d6820a7ce3f1fb';\n"
  },
  {
    "path": "example/rails2.3/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format \n# (all these examples are active by default):\n# ActiveSupport::Inflector.inflections do |inflect|\n#   inflect.plural /^(ox)$/i, '\\1en'\n#   inflect.singular /^(ox)en/i, '\\1'\n#   inflect.irregular 'person', 'people'\n#   inflect.uncountable %w( fish sheep )\n# end\n"
  },
  {
    "path": "example/rails2.3/config/initializers/mime_types.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\n# Mime::Type.register_alias \"text/html\", :iphone\n"
  },
  {
    "path": "example/rails2.3/config/initializers/new_rails_defaults.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# These settings change the behavior of Rails 2 apps and will be defaults\n# for Rails 3. You can remove this initializer when Rails 3 is released.\n\nif defined?(ActiveRecord)\n  # Include Active Record class name as root for JSON serialized output.\n  ActiveRecord::Base.include_root_in_json = true\n\n  # Store the full class name (including module namespace) in STI type column.\n  ActiveRecord::Base.store_full_sti_class = true\nend\n\nActionController::Routing.generate_best_match = false\n\n# Use ISO 8601 format for JSON serialized times and dates.\nActiveSupport.use_standard_json_time_format = true\n\n# Don't escape HTML entities in JSON, leave that for the #json_escape helper.\n# if you're including raw json in an HTML page.\nActiveSupport.escape_html_entities_in_json = false"
  },
  {
    "path": "example/rails2.3/config/initializers/session_store.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Your secret key for verifying cookie session data integrity.\n# If you change this key, all old sessions will become invalid!\n# Make sure the secret is at least 30 characters and all random, \n# no regular words or you'll be exposed to dictionary attacks.\nActionController::Base.session = {\n  :key         => '_example_session',\n  :secret      => '857713e3e2406c40a81798264d0523a082862e124f5b7998ff6c8af1358f58b54e86f0a0793d252d596c9cbbba2c38fd7c08c475977684aaeb58f75872c51850'\n}\n\n# Use the database for sessions instead of the cookie-based default,\n# which shouldn't be used to store highly confidential information\n# (create the session table with \"rake db:sessions:create\")\n# ActionController::Base.session_store = :active_record_store\n"
  },
  {
    "path": "example/rails2.3/config/locales/en.yml",
    "content": "# Sample localization file for English. Add more files in this directory for other locales.\n# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.\n\nen:\n  hello: \"Hello world\""
  },
  {
    "path": "example/rails2.3/config/routes.rb",
    "content": "ActionController::Routing::Routes.draw do |map|\n  # The priority is based upon order of creation: first created -> highest priority.\n\n  # Sample of regular route:\n  #   map.connect 'products/:id', :controller => 'catalog', :action => 'view'\n  # Keep in mind you can assign values other than :controller and :action\n\n  # Sample of named route:\n  #   map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'\n  # This route can be invoked with purchase_url(:id => product.id)\n\n  # Sample resource route (maps HTTP verbs to controller actions automatically):\n  #   map.resources :products\n\n  # Sample resource route with options:\n  #   map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }\n\n  # Sample resource route with sub-resources:\n  #   map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller\n  \n  # Sample resource route with more complex sub-resources\n  #   map.resources :products do |products|\n  #     products.resources :comments\n  #     products.resources :sales, :collection => { :recent => :get }\n  #   end\n\n  # Sample resource route within a namespace:\n  #   map.namespace :admin do |admin|\n  #     # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)\n  #     admin.resources :products\n  #   end\n\n  # You can have the root of your site routed with map.root -- just remember to delete public/index.html.\n  map.root :controller => \"page\"\n\n  # See how all your routes lay out with \"rake routes\"\n\n  # Install the default routes as the lowest priority.\n  # Note: These default routes make all actions in every controller accessible via GET requests. You should\n  # consider removing or commenting them out if you're using named routes and resources.\n  map.connect ':controller/:action/:id'\n  map.connect ':controller/:action/:id.:format'\nend\n"
  },
  {
    "path": "example/rails2.3/db/development.sqlite3",
    "content": ""
  },
  {
    "path": "example/rails2.3/db/seeds.rb",
    "content": "# This file should contain all the record creation needed to seed the database with its default values.\n# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).\n#\n# Examples:\n#   \n#   cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])\n#   Major.create(:name => 'Daley', :city => cities.first)\n"
  },
  {
    "path": "example/rails2.3/doc/README_FOR_APP",
    "content": "Use this README file to introduce your application and point to useful places in the API for learning more.\nRun \"rake doc:app\" to generate API documentation for your models, controllers, helpers, and libraries.\n"
  },
  {
    "path": "example/rails2.3/public/404.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n       \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n\n<head>\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n  <title>The page you were looking for doesn't exist (404)</title>\n\t<style type=\"text/css\">\n\t\tbody { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n\t\tdiv.dialog {\n\t\t\twidth: 25em;\n\t\t\tpadding: 0 4em;\n\t\t\tmargin: 4em auto 0 auto;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-right-color: #999;\n\t\t\tborder-bottom-color: #999;\n\t\t}\n\t\th1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n\t</style>\n</head>\n\n<body>\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <h1>The page you were looking for doesn't exist.</h1>\n    <p>You may have mistyped the address or the page may have moved.</p>\n  </div>\n</body>\n</html>"
  },
  {
    "path": "example/rails2.3/public/422.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n       \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n\n<head>\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n  <title>The change you wanted was rejected (422)</title>\n\t<style type=\"text/css\">\n\t\tbody { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n\t\tdiv.dialog {\n\t\t\twidth: 25em;\n\t\t\tpadding: 0 4em;\n\t\t\tmargin: 4em auto 0 auto;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-right-color: #999;\n\t\t\tborder-bottom-color: #999;\n\t\t}\n\t\th1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n\t</style>\n</head>\n\n<body>\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <h1>The change you wanted was rejected.</h1>\n    <p>Maybe you tried to change something you didn't have access to.</p>\n  </div>\n</body>\n</html>"
  },
  {
    "path": "example/rails2.3/public/500.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n       \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n\n<head>\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n  <title>We're sorry, but something went wrong (500)</title>\n\t<style type=\"text/css\">\n\t\tbody { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n\t\tdiv.dialog {\n\t\t\twidth: 25em;\n\t\t\tpadding: 0 4em;\n\t\t\tmargin: 4em auto 0 auto;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-right-color: #999;\n\t\t\tborder-bottom-color: #999;\n\t\t}\n\t\th1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n\t</style>\n</head>\n\n<body>\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <h1>We're sorry, but something went wrong.</h1>\n    <p>We've been notified about this issue and we'll take a look at it shortly.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails2.3/public/javascripts/application.js",
    "content": "// Place your application-specific JavaScript functions and classes here\n// This file is automatically included by javascript_include_tag :defaults\n"
  },
  {
    "path": "example/rails2.3/public/javascripts/controls.js",
    "content": "// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)\n//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)\n// Contributors:\n//  Richard Livsey\n//  Rahul Bhargava\n//  Rob Wills\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\n// Autocompleter.Base handles all the autocompletion functionality\n// that's independent of the data source for autocompletion. This\n// includes drawing the autocompletion menu, observing keyboard\n// and mouse events, and similar.\n//\n// Specific autocompleters need to provide, at the very least,\n// a getUpdatedChoices function that will be invoked every time\n// the text inside the monitored textbox changes. This method\n// should get the text for which to provide autocompletion by\n// invoking this.getToken(), NOT by directly accessing\n// this.element.value. This is to allow incremental tokenized\n// autocompletion. Specific auto-completion logic (AJAX, etc)\n// belongs in getUpdatedChoices.\n//\n// Tokenized incremental autocompletion is enabled automatically\n// when an autocompleter is instantiated with the 'tokens' option\n// in the options parameter, e.g.:\n// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });\n// will incrementally autocomplete with a comma as the token.\n// Additionally, ',' in the above example can be replaced with\n// a token array, e.g. { tokens: [',', '\\n'] } which\n// enables autocompletion on multiple tokens. This is most\n// useful when one of the tokens is \\n (a newline), as it\n// allows smart autocompletion after linebreaks.\n\nif(typeof Effect == 'undefined')\n  throw(\"controls.js requires including script.aculo.us' effects.js library\");\n\nvar Autocompleter = { };\nAutocompleter.Base = Class.create({\n  baseInitialize: function(element, update, options) {\n    element          = $(element);\n    this.element     = element;\n    this.update      = $(update);\n    this.hasFocus    = false;\n    this.changed     = false;\n    this.active      = false;\n    this.index       = 0;\n    this.entryCount  = 0;\n    this.oldElementValue = this.element.value;\n\n    if(this.setOptions)\n      this.setOptions(options);\n    else\n      this.options = options || { };\n\n    this.options.paramName    = this.options.paramName || this.element.name;\n    this.options.tokens       = this.options.tokens || [];\n    this.options.frequency    = this.options.frequency || 0.4;\n    this.options.minChars     = this.options.minChars || 1;\n    this.options.onShow       = this.options.onShow ||\n      function(element, update){\n        if(!update.style.position || update.style.position=='absolute') {\n          update.style.position = 'absolute';\n          Position.clone(element, update, {\n            setHeight: false,\n            offsetTop: element.offsetHeight\n          });\n        }\n        Effect.Appear(update,{duration:0.15});\n      };\n    this.options.onHide = this.options.onHide ||\n      function(element, update){ new Effect.Fade(update,{duration:0.15}) };\n\n    if(typeof(this.options.tokens) == 'string')\n      this.options.tokens = new Array(this.options.tokens);\n    // Force carriage returns as token delimiters anyway\n    if (!this.options.tokens.include('\\n'))\n      this.options.tokens.push('\\n');\n\n    this.observer = null;\n\n    this.element.setAttribute('autocomplete','off');\n\n    Element.hide(this.update);\n\n    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));\n    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));\n  },\n\n  show: function() {\n    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);\n    if(!this.iefix &&\n      (Prototype.Browser.IE) &&\n      (Element.getStyle(this.update, 'position')=='absolute')) {\n      new Insertion.After(this.update,\n       '<iframe id=\"' + this.update.id + '_iefix\" '+\n       'style=\"display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);\" ' +\n       'src=\"javascript:false;\" frameborder=\"0\" scrolling=\"no\"></iframe>');\n      this.iefix = $(this.update.id+'_iefix');\n    }\n    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);\n  },\n\n  fixIEOverlapping: function() {\n    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});\n    this.iefix.style.zIndex = 1;\n    this.update.style.zIndex = 2;\n    Element.show(this.iefix);\n  },\n\n  hide: function() {\n    this.stopIndicator();\n    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);\n    if(this.iefix) Element.hide(this.iefix);\n  },\n\n  startIndicator: function() {\n    if(this.options.indicator) Element.show(this.options.indicator);\n  },\n\n  stopIndicator: function() {\n    if(this.options.indicator) Element.hide(this.options.indicator);\n  },\n\n  onKeyPress: function(event) {\n    if(this.active)\n      switch(event.keyCode) {\n       case Event.KEY_TAB:\n       case Event.KEY_RETURN:\n         this.selectEntry();\n         Event.stop(event);\n       case Event.KEY_ESC:\n         this.hide();\n         this.active = false;\n         Event.stop(event);\n         return;\n       case Event.KEY_LEFT:\n       case Event.KEY_RIGHT:\n         return;\n       case Event.KEY_UP:\n         this.markPrevious();\n         this.render();\n         Event.stop(event);\n         return;\n       case Event.KEY_DOWN:\n         this.markNext();\n         this.render();\n         Event.stop(event);\n         return;\n      }\n     else\n       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||\n         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;\n\n    this.changed = true;\n    this.hasFocus = true;\n\n    if(this.observer) clearTimeout(this.observer);\n      this.observer =\n        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);\n  },\n\n  activate: function() {\n    this.changed = false;\n    this.hasFocus = true;\n    this.getUpdatedChoices();\n  },\n\n  onHover: function(event) {\n    var element = Event.findElement(event, 'LI');\n    if(this.index != element.autocompleteIndex)\n    {\n        this.index = element.autocompleteIndex;\n        this.render();\n    }\n    Event.stop(event);\n  },\n\n  onClick: function(event) {\n    var element = Event.findElement(event, 'LI');\n    this.index = element.autocompleteIndex;\n    this.selectEntry();\n    this.hide();\n  },\n\n  onBlur: function(event) {\n    // needed to make click events working\n    setTimeout(this.hide.bind(this), 250);\n    this.hasFocus = false;\n    this.active = false;\n  },\n\n  render: function() {\n    if(this.entryCount > 0) {\n      for (var i = 0; i < this.entryCount; i++)\n        this.index==i ?\n          Element.addClassName(this.getEntry(i),\"selected\") :\n          Element.removeClassName(this.getEntry(i),\"selected\");\n      if(this.hasFocus) {\n        this.show();\n        this.active = true;\n      }\n    } else {\n      this.active = false;\n      this.hide();\n    }\n  },\n\n  markPrevious: function() {\n    if(this.index > 0) this.index--;\n      else this.index = this.entryCount-1;\n    this.getEntry(this.index).scrollIntoView(true);\n  },\n\n  markNext: function() {\n    if(this.index < this.entryCount-1) this.index++;\n      else this.index = 0;\n    this.getEntry(this.index).scrollIntoView(false);\n  },\n\n  getEntry: function(index) {\n    return this.update.firstChild.childNodes[index];\n  },\n\n  getCurrentEntry: function() {\n    return this.getEntry(this.index);\n  },\n\n  selectEntry: function() {\n    this.active = false;\n    this.updateElement(this.getCurrentEntry());\n  },\n\n  updateElement: function(selectedElement) {\n    if (this.options.updateElement) {\n      this.options.updateElement(selectedElement);\n      return;\n    }\n    var value = '';\n    if (this.options.select) {\n      var nodes = $(selectedElement).select('.' + this.options.select) || [];\n      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);\n    } else\n      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');\n\n    var bounds = this.getTokenBounds();\n    if (bounds[0] != -1) {\n      var newValue = this.element.value.substr(0, bounds[0]);\n      var whitespace = this.element.value.substr(bounds[0]).match(/^\\s+/);\n      if (whitespace)\n        newValue += whitespace[0];\n      this.element.value = newValue + value + this.element.value.substr(bounds[1]);\n    } else {\n      this.element.value = value;\n    }\n    this.oldElementValue = this.element.value;\n    this.element.focus();\n\n    if (this.options.afterUpdateElement)\n      this.options.afterUpdateElement(this.element, selectedElement);\n  },\n\n  updateChoices: function(choices) {\n    if(!this.changed && this.hasFocus) {\n      this.update.innerHTML = choices;\n      Element.cleanWhitespace(this.update);\n      Element.cleanWhitespace(this.update.down());\n\n      if(this.update.firstChild && this.update.down().childNodes) {\n        this.entryCount =\n          this.update.down().childNodes.length;\n        for (var i = 0; i < this.entryCount; i++) {\n          var entry = this.getEntry(i);\n          entry.autocompleteIndex = i;\n          this.addObservers(entry);\n        }\n      } else {\n        this.entryCount = 0;\n      }\n\n      this.stopIndicator();\n      this.index = 0;\n\n      if(this.entryCount==1 && this.options.autoSelect) {\n        this.selectEntry();\n        this.hide();\n      } else {\n        this.render();\n      }\n    }\n  },\n\n  addObservers: function(element) {\n    Event.observe(element, \"mouseover\", this.onHover.bindAsEventListener(this));\n    Event.observe(element, \"click\", this.onClick.bindAsEventListener(this));\n  },\n\n  onObserverEvent: function() {\n    this.changed = false;\n    this.tokenBounds = null;\n    if(this.getToken().length>=this.options.minChars) {\n      this.getUpdatedChoices();\n    } else {\n      this.active = false;\n      this.hide();\n    }\n    this.oldElementValue = this.element.value;\n  },\n\n  getToken: function() {\n    var bounds = this.getTokenBounds();\n    return this.element.value.substring(bounds[0], bounds[1]).strip();\n  },\n\n  getTokenBounds: function() {\n    if (null != this.tokenBounds) return this.tokenBounds;\n    var value = this.element.value;\n    if (value.strip().empty()) return [-1, 0];\n    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);\n    var offset = (diff == this.oldElementValue.length ? 1 : 0);\n    var prevTokenPos = -1, nextTokenPos = value.length;\n    var tp;\n    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {\n      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);\n      if (tp > prevTokenPos) prevTokenPos = tp;\n      tp = value.indexOf(this.options.tokens[index], diff + offset);\n      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;\n    }\n    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);\n  }\n});\n\nAutocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {\n  var boundary = Math.min(newS.length, oldS.length);\n  for (var index = 0; index < boundary; ++index)\n    if (newS[index] != oldS[index])\n      return index;\n  return boundary;\n};\n\nAjax.Autocompleter = Class.create(Autocompleter.Base, {\n  initialize: function(element, update, url, options) {\n    this.baseInitialize(element, update, options);\n    this.options.asynchronous  = true;\n    this.options.onComplete    = this.onComplete.bind(this);\n    this.options.defaultParams = this.options.parameters || null;\n    this.url                   = url;\n  },\n\n  getUpdatedChoices: function() {\n    this.startIndicator();\n\n    var entry = encodeURIComponent(this.options.paramName) + '=' +\n      encodeURIComponent(this.getToken());\n\n    this.options.parameters = this.options.callback ?\n      this.options.callback(this.element, entry) : entry;\n\n    if(this.options.defaultParams)\n      this.options.parameters += '&' + this.options.defaultParams;\n\n    new Ajax.Request(this.url, this.options);\n  },\n\n  onComplete: function(request) {\n    this.updateChoices(request.responseText);\n  }\n});\n\n// The local array autocompleter. Used when you'd prefer to\n// inject an array of autocompletion options into the page, rather\n// than sending out Ajax queries, which can be quite slow sometimes.\n//\n// The constructor takes four parameters. The first two are, as usual,\n// the id of the monitored textbox, and id of the autocompletion menu.\n// The third is the array you want to autocomplete from, and the fourth\n// is the options block.\n//\n// Extra local autocompletion options:\n// - choices - How many autocompletion choices to offer\n//\n// - partialSearch - If false, the autocompleter will match entered\n//                    text only at the beginning of strings in the\n//                    autocomplete array. Defaults to true, which will\n//                    match text at the beginning of any *word* in the\n//                    strings in the autocomplete array. If you want to\n//                    search anywhere in the string, additionally set\n//                    the option fullSearch to true (default: off).\n//\n// - fullSsearch - Search anywhere in autocomplete array strings.\n//\n// - partialChars - How many characters to enter before triggering\n//                   a partial match (unlike minChars, which defines\n//                   how many characters are required to do any match\n//                   at all). Defaults to 2.\n//\n// - ignoreCase - Whether to ignore case when autocompleting.\n//                 Defaults to true.\n//\n// It's possible to pass in a custom function as the 'selector'\n// option, if you prefer to write your own autocompletion logic.\n// In that case, the other options above will not apply unless\n// you support them.\n\nAutocompleter.Local = Class.create(Autocompleter.Base, {\n  initialize: function(element, update, array, options) {\n    this.baseInitialize(element, update, options);\n    this.options.array = array;\n  },\n\n  getUpdatedChoices: function() {\n    this.updateChoices(this.options.selector(this));\n  },\n\n  setOptions: function(options) {\n    this.options = Object.extend({\n      choices: 10,\n      partialSearch: true,\n      partialChars: 2,\n      ignoreCase: true,\n      fullSearch: false,\n      selector: function(instance) {\n        var ret       = []; // Beginning matches\n        var partial   = []; // Inside matches\n        var entry     = instance.getToken();\n        var count     = 0;\n\n        for (var i = 0; i < instance.options.array.length &&\n          ret.length < instance.options.choices ; i++) {\n\n          var elem = instance.options.array[i];\n          var foundPos = instance.options.ignoreCase ?\n            elem.toLowerCase().indexOf(entry.toLowerCase()) :\n            elem.indexOf(entry);\n\n          while (foundPos != -1) {\n            if (foundPos == 0 && elem.length != entry.length) {\n              ret.push(\"<li><strong>\" + elem.substr(0, entry.length) + \"</strong>\" +\n                elem.substr(entry.length) + \"</li>\");\n              break;\n            } else if (entry.length >= instance.options.partialChars &&\n              instance.options.partialSearch && foundPos != -1) {\n              if (instance.options.fullSearch || /\\s/.test(elem.substr(foundPos-1,1))) {\n                partial.push(\"<li>\" + elem.substr(0, foundPos) + \"<strong>\" +\n                  elem.substr(foundPos, entry.length) + \"</strong>\" + elem.substr(\n                  foundPos + entry.length) + \"</li>\");\n                break;\n              }\n            }\n\n            foundPos = instance.options.ignoreCase ?\n              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :\n              elem.indexOf(entry, foundPos + 1);\n\n          }\n        }\n        if (partial.length)\n          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));\n        return \"<ul>\" + ret.join('') + \"</ul>\";\n      }\n    }, options || { });\n  }\n});\n\n// AJAX in-place editor and collection editor\n// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).\n\n// Use this if you notice weird scrolling problems on some browsers,\n// the DOM might be a bit confused when this gets called so do this\n// waits 1 ms (with setTimeout) until it does the activation\nField.scrollFreeActivate = function(field) {\n  setTimeout(function() {\n    Field.activate(field);\n  }, 1);\n};\n\nAjax.InPlaceEditor = Class.create({\n  initialize: function(element, url, options) {\n    this.url = url;\n    this.element = element = $(element);\n    this.prepareOptions();\n    this._controls = { };\n    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!\n    Object.extend(this.options, options || { });\n    if (!this.options.formId && this.element.id) {\n      this.options.formId = this.element.id + '-inplaceeditor';\n      if ($(this.options.formId))\n        this.options.formId = '';\n    }\n    if (this.options.externalControl)\n      this.options.externalControl = $(this.options.externalControl);\n    if (!this.options.externalControl)\n      this.options.externalControlOnly = false;\n    this._originalBackground = this.element.getStyle('background-color') || 'transparent';\n    this.element.title = this.options.clickToEditText;\n    this._boundCancelHandler = this.handleFormCancellation.bind(this);\n    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);\n    this._boundFailureHandler = this.handleAJAXFailure.bind(this);\n    this._boundSubmitHandler = this.handleFormSubmission.bind(this);\n    this._boundWrapperHandler = this.wrapUp.bind(this);\n    this.registerListeners();\n  },\n  checkForEscapeOrReturn: function(e) {\n    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;\n    if (Event.KEY_ESC == e.keyCode)\n      this.handleFormCancellation(e);\n    else if (Event.KEY_RETURN == e.keyCode)\n      this.handleFormSubmission(e);\n  },\n  createControl: function(mode, handler, extraClasses) {\n    var control = this.options[mode + 'Control'];\n    var text = this.options[mode + 'Text'];\n    if ('button' == control) {\n      var btn = document.createElement('input');\n      btn.type = 'submit';\n      btn.value = text;\n      btn.className = 'editor_' + mode + '_button';\n      if ('cancel' == mode)\n        btn.onclick = this._boundCancelHandler;\n      this._form.appendChild(btn);\n      this._controls[mode] = btn;\n    } else if ('link' == control) {\n      var link = document.createElement('a');\n      link.href = '#';\n      link.appendChild(document.createTextNode(text));\n      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;\n      link.className = 'editor_' + mode + '_link';\n      if (extraClasses)\n        link.className += ' ' + extraClasses;\n      this._form.appendChild(link);\n      this._controls[mode] = link;\n    }\n  },\n  createEditField: function() {\n    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());\n    var fld;\n    if (1 >= this.options.rows && !/\\r|\\n/.test(this.getText())) {\n      fld = document.createElement('input');\n      fld.type = 'text';\n      var size = this.options.size || this.options.cols || 0;\n      if (0 < size) fld.size = size;\n    } else {\n      fld = document.createElement('textarea');\n      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);\n      fld.cols = this.options.cols || 40;\n    }\n    fld.name = this.options.paramName;\n    fld.value = text; // No HTML breaks conversion anymore\n    fld.className = 'editor_field';\n    if (this.options.submitOnBlur)\n      fld.onblur = this._boundSubmitHandler;\n    this._controls.editor = fld;\n    if (this.options.loadTextURL)\n      this.loadExternalText();\n    this._form.appendChild(this._controls.editor);\n  },\n  createForm: function() {\n    var ipe = this;\n    function addText(mode, condition) {\n      var text = ipe.options['text' + mode + 'Controls'];\n      if (!text || condition === false) return;\n      ipe._form.appendChild(document.createTextNode(text));\n    };\n    this._form = $(document.createElement('form'));\n    this._form.id = this.options.formId;\n    this._form.addClassName(this.options.formClassName);\n    this._form.onsubmit = this._boundSubmitHandler;\n    this.createEditField();\n    if ('textarea' == this._controls.editor.tagName.toLowerCase())\n      this._form.appendChild(document.createElement('br'));\n    if (this.options.onFormCustomization)\n      this.options.onFormCustomization(this, this._form);\n    addText('Before', this.options.okControl || this.options.cancelControl);\n    this.createControl('ok', this._boundSubmitHandler);\n    addText('Between', this.options.okControl && this.options.cancelControl);\n    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');\n    addText('After', this.options.okControl || this.options.cancelControl);\n  },\n  destroy: function() {\n    if (this._oldInnerHTML)\n      this.element.innerHTML = this._oldInnerHTML;\n    this.leaveEditMode();\n    this.unregisterListeners();\n  },\n  enterEditMode: function(e) {\n    if (this._saving || this._editing) return;\n    this._editing = true;\n    this.triggerCallback('onEnterEditMode');\n    if (this.options.externalControl)\n      this.options.externalControl.hide();\n    this.element.hide();\n    this.createForm();\n    this.element.parentNode.insertBefore(this._form, this.element);\n    if (!this.options.loadTextURL)\n      this.postProcessEditField();\n    if (e) Event.stop(e);\n  },\n  enterHover: function(e) {\n    if (this.options.hoverClassName)\n      this.element.addClassName(this.options.hoverClassName);\n    if (this._saving) return;\n    this.triggerCallback('onEnterHover');\n  },\n  getText: function() {\n    return this.element.innerHTML.unescapeHTML();\n  },\n  handleAJAXFailure: function(transport) {\n    this.triggerCallback('onFailure', transport);\n    if (this._oldInnerHTML) {\n      this.element.innerHTML = this._oldInnerHTML;\n      this._oldInnerHTML = null;\n    }\n  },\n  handleFormCancellation: function(e) {\n    this.wrapUp();\n    if (e) Event.stop(e);\n  },\n  handleFormSubmission: function(e) {\n    var form = this._form;\n    var value = $F(this._controls.editor);\n    this.prepareSubmission();\n    var params = this.options.callback(form, value) || '';\n    if (Object.isString(params))\n      params = params.toQueryParams();\n    params.editorId = this.element.id;\n    if (this.options.htmlResponse) {\n      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);\n      Object.extend(options, {\n        parameters: params,\n        onComplete: this._boundWrapperHandler,\n        onFailure: this._boundFailureHandler\n      });\n      new Ajax.Updater({ success: this.element }, this.url, options);\n    } else {\n      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n      Object.extend(options, {\n        parameters: params,\n        onComplete: this._boundWrapperHandler,\n        onFailure: this._boundFailureHandler\n      });\n      new Ajax.Request(this.url, options);\n    }\n    if (e) Event.stop(e);\n  },\n  leaveEditMode: function() {\n    this.element.removeClassName(this.options.savingClassName);\n    this.removeForm();\n    this.leaveHover();\n    this.element.style.backgroundColor = this._originalBackground;\n    this.element.show();\n    if (this.options.externalControl)\n      this.options.externalControl.show();\n    this._saving = false;\n    this._editing = false;\n    this._oldInnerHTML = null;\n    this.triggerCallback('onLeaveEditMode');\n  },\n  leaveHover: function(e) {\n    if (this.options.hoverClassName)\n      this.element.removeClassName(this.options.hoverClassName);\n    if (this._saving) return;\n    this.triggerCallback('onLeaveHover');\n  },\n  loadExternalText: function() {\n    this._form.addClassName(this.options.loadingClassName);\n    this._controls.editor.disabled = true;\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        this._form.removeClassName(this.options.loadingClassName);\n        var text = transport.responseText;\n        if (this.options.stripLoadedTextTags)\n          text = text.stripTags();\n        this._controls.editor.value = text;\n        this._controls.editor.disabled = false;\n        this.postProcessEditField();\n      }.bind(this),\n      onFailure: this._boundFailureHandler\n    });\n    new Ajax.Request(this.options.loadTextURL, options);\n  },\n  postProcessEditField: function() {\n    var fpc = this.options.fieldPostCreation;\n    if (fpc)\n      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();\n  },\n  prepareOptions: function() {\n    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);\n    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);\n    [this._extraDefaultOptions].flatten().compact().each(function(defs) {\n      Object.extend(this.options, defs);\n    }.bind(this));\n  },\n  prepareSubmission: function() {\n    this._saving = true;\n    this.removeForm();\n    this.leaveHover();\n    this.showSaving();\n  },\n  registerListeners: function() {\n    this._listeners = { };\n    var listener;\n    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {\n      listener = this[pair.value].bind(this);\n      this._listeners[pair.key] = listener;\n      if (!this.options.externalControlOnly)\n        this.element.observe(pair.key, listener);\n      if (this.options.externalControl)\n        this.options.externalControl.observe(pair.key, listener);\n    }.bind(this));\n  },\n  removeForm: function() {\n    if (!this._form) return;\n    this._form.remove();\n    this._form = null;\n    this._controls = { };\n  },\n  showSaving: function() {\n    this._oldInnerHTML = this.element.innerHTML;\n    this.element.innerHTML = this.options.savingText;\n    this.element.addClassName(this.options.savingClassName);\n    this.element.style.backgroundColor = this._originalBackground;\n    this.element.show();\n  },\n  triggerCallback: function(cbName, arg) {\n    if ('function' == typeof this.options[cbName]) {\n      this.options[cbName](this, arg);\n    }\n  },\n  unregisterListeners: function() {\n    $H(this._listeners).each(function(pair) {\n      if (!this.options.externalControlOnly)\n        this.element.stopObserving(pair.key, pair.value);\n      if (this.options.externalControl)\n        this.options.externalControl.stopObserving(pair.key, pair.value);\n    }.bind(this));\n  },\n  wrapUp: function(transport) {\n    this.leaveEditMode();\n    // Can't use triggerCallback due to backward compatibility: requires\n    // binding + direct element\n    this._boundComplete(transport, this.element);\n  }\n});\n\nObject.extend(Ajax.InPlaceEditor.prototype, {\n  dispose: Ajax.InPlaceEditor.prototype.destroy\n});\n\nAjax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {\n  initialize: function($super, element, url, options) {\n    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;\n    $super(element, url, options);\n  },\n\n  createEditField: function() {\n    var list = document.createElement('select');\n    list.name = this.options.paramName;\n    list.size = 1;\n    this._controls.editor = list;\n    this._collection = this.options.collection || [];\n    if (this.options.loadCollectionURL)\n      this.loadCollection();\n    else\n      this.checkForExternalText();\n    this._form.appendChild(this._controls.editor);\n  },\n\n  loadCollection: function() {\n    this._form.addClassName(this.options.loadingClassName);\n    this.showLoadingText(this.options.loadingCollectionText);\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        var js = transport.responseText.strip();\n        if (!/^\\[.*\\]$/.test(js)) // TODO: improve sanity check\n          throw('Server returned an invalid collection representation.');\n        this._collection = eval(js);\n        this.checkForExternalText();\n      }.bind(this),\n      onFailure: this.onFailure\n    });\n    new Ajax.Request(this.options.loadCollectionURL, options);\n  },\n\n  showLoadingText: function(text) {\n    this._controls.editor.disabled = true;\n    var tempOption = this._controls.editor.firstChild;\n    if (!tempOption) {\n      tempOption = document.createElement('option');\n      tempOption.value = '';\n      this._controls.editor.appendChild(tempOption);\n      tempOption.selected = true;\n    }\n    tempOption.update((text || '').stripScripts().stripTags());\n  },\n\n  checkForExternalText: function() {\n    this._text = this.getText();\n    if (this.options.loadTextURL)\n      this.loadExternalText();\n    else\n      this.buildOptionList();\n  },\n\n  loadExternalText: function() {\n    this.showLoadingText(this.options.loadingText);\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        this._text = transport.responseText.strip();\n        this.buildOptionList();\n      }.bind(this),\n      onFailure: this.onFailure\n    });\n    new Ajax.Request(this.options.loadTextURL, options);\n  },\n\n  buildOptionList: function() {\n    this._form.removeClassName(this.options.loadingClassName);\n    this._collection = this._collection.map(function(entry) {\n      return 2 === entry.length ? entry : [entry, entry].flatten();\n    });\n    var marker = ('value' in this.options) ? this.options.value : this._text;\n    var textFound = this._collection.any(function(entry) {\n      return entry[0] == marker;\n    }.bind(this));\n    this._controls.editor.update('');\n    var option;\n    this._collection.each(function(entry, index) {\n      option = document.createElement('option');\n      option.value = entry[0];\n      option.selected = textFound ? entry[0] == marker : 0 == index;\n      option.appendChild(document.createTextNode(entry[1]));\n      this._controls.editor.appendChild(option);\n    }.bind(this));\n    this._controls.editor.disabled = false;\n    Field.scrollFreeActivate(this._controls.editor);\n  }\n});\n\n//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****\n//**** This only  exists for a while,  in order to  let ****\n//**** users adapt to  the new API.  Read up on the new ****\n//**** API and convert your code to it ASAP!            ****\n\nAjax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {\n  if (!options) return;\n  function fallback(name, expr) {\n    if (name in options || expr === undefined) return;\n    options[name] = expr;\n  };\n  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :\n    options.cancelLink == options.cancelButton == false ? false : undefined)));\n  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :\n    options.okLink == options.okButton == false ? false : undefined)));\n  fallback('highlightColor', options.highlightcolor);\n  fallback('highlightEndColor', options.highlightendcolor);\n};\n\nObject.extend(Ajax.InPlaceEditor, {\n  DefaultOptions: {\n    ajaxOptions: { },\n    autoRows: 3,                                // Use when multi-line w/ rows == 1\n    cancelControl: 'link',                      // 'link'|'button'|false\n    cancelText: 'cancel',\n    clickToEditText: 'Click to edit',\n    externalControl: null,                      // id|elt\n    externalControlOnly: false,\n    fieldPostCreation: 'activate',              // 'activate'|'focus'|false\n    formClassName: 'inplaceeditor-form',\n    formId: null,                               // id|elt\n    highlightColor: '#ffff99',\n    highlightEndColor: '#ffffff',\n    hoverClassName: '',\n    htmlResponse: true,\n    loadingClassName: 'inplaceeditor-loading',\n    loadingText: 'Loading...',\n    okControl: 'button',                        // 'link'|'button'|false\n    okText: 'ok',\n    paramName: 'value',\n    rows: 1,                                    // If 1 and multi-line, uses autoRows\n    savingClassName: 'inplaceeditor-saving',\n    savingText: 'Saving...',\n    size: 0,\n    stripLoadedTextTags: false,\n    submitOnBlur: false,\n    textAfterControls: '',\n    textBeforeControls: '',\n    textBetweenControls: ''\n  },\n  DefaultCallbacks: {\n    callback: function(form) {\n      return Form.serialize(form);\n    },\n    onComplete: function(transport, element) {\n      // For backward compatibility, this one is bound to the IPE, and passes\n      // the element directly.  It was too often customized, so we don't break it.\n      new Effect.Highlight(element, {\n        startcolor: this.options.highlightColor, keepBackgroundImage: true });\n    },\n    onEnterEditMode: null,\n    onEnterHover: function(ipe) {\n      ipe.element.style.backgroundColor = ipe.options.highlightColor;\n      if (ipe._effect)\n        ipe._effect.cancel();\n    },\n    onFailure: function(transport, ipe) {\n      alert('Error communication with the server: ' + transport.responseText.stripTags());\n    },\n    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.\n    onLeaveEditMode: null,\n    onLeaveHover: function(ipe) {\n      ipe._effect = new Effect.Highlight(ipe.element, {\n        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,\n        restorecolor: ipe._originalBackground, keepBackgroundImage: true\n      });\n    }\n  },\n  Listeners: {\n    click: 'enterEditMode',\n    keydown: 'checkForEscapeOrReturn',\n    mouseover: 'enterHover',\n    mouseout: 'leaveHover'\n  }\n});\n\nAjax.InPlaceCollectionEditor.DefaultOptions = {\n  loadingCollectionText: 'Loading options...'\n};\n\n// Delayed observer, like Form.Element.Observer,\n// but waits for delay after last key input\n// Ideal for live-search fields\n\nForm.Element.DelayedObserver = Class.create({\n  initialize: function(element, delay, callback) {\n    this.delay     = delay || 0.5;\n    this.element   = $(element);\n    this.callback  = callback;\n    this.timer     = null;\n    this.lastValue = $F(this.element);\n    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));\n  },\n  delayedListener: function(event) {\n    if(this.lastValue == $F(this.element)) return;\n    if(this.timer) clearTimeout(this.timer);\n    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);\n    this.lastValue = $F(this.element);\n  },\n  onTimerEvent: function() {\n    this.timer = null;\n    this.callback(this.element, $F(this.element));\n  }\n});"
  },
  {
    "path": "example/rails2.3/public/javascripts/dragdrop.js",
    "content": "// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n//           (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\nif(Object.isUndefined(Effect))\n  throw(\"dragdrop.js requires including script.aculo.us' effects.js library\");\n\nvar Droppables = {\n  drops: [],\n\n  remove: function(element) {\n    this.drops = this.drops.reject(function(d) { return d.element==$(element) });\n  },\n\n  add: function(element) {\n    element = $(element);\n    var options = Object.extend({\n      greedy:     true,\n      hoverclass: null,\n      tree:       false\n    }, arguments[1] || { });\n\n    // cache containers\n    if(options.containment) {\n      options._containers = [];\n      var containment = options.containment;\n      if(Object.isArray(containment)) {\n        containment.each( function(c) { options._containers.push($(c)) });\n      } else {\n        options._containers.push($(containment));\n      }\n    }\n\n    if(options.accept) options.accept = [options.accept].flatten();\n\n    Element.makePositioned(element); // fix IE\n    options.element = element;\n\n    this.drops.push(options);\n  },\n\n  findDeepestChild: function(drops) {\n    deepest = drops[0];\n\n    for (i = 1; i < drops.length; ++i)\n      if (Element.isParent(drops[i].element, deepest.element))\n        deepest = drops[i];\n\n    return deepest;\n  },\n\n  isContained: function(element, drop) {\n    var containmentNode;\n    if(drop.tree) {\n      containmentNode = element.treeNode;\n    } else {\n      containmentNode = element.parentNode;\n    }\n    return drop._containers.detect(function(c) { return containmentNode == c });\n  },\n\n  isAffected: function(point, element, drop) {\n    return (\n      (drop.element!=element) &&\n      ((!drop._containers) ||\n        this.isContained(element, drop)) &&\n      ((!drop.accept) ||\n        (Element.classNames(element).detect(\n          function(v) { return drop.accept.include(v) } ) )) &&\n      Position.within(drop.element, point[0], point[1]) );\n  },\n\n  deactivate: function(drop) {\n    if(drop.hoverclass)\n      Element.removeClassName(drop.element, drop.hoverclass);\n    this.last_active = null;\n  },\n\n  activate: function(drop) {\n    if(drop.hoverclass)\n      Element.addClassName(drop.element, drop.hoverclass);\n    this.last_active = drop;\n  },\n\n  show: function(point, element) {\n    if(!this.drops.length) return;\n    var drop, affected = [];\n\n    this.drops.each( function(drop) {\n      if(Droppables.isAffected(point, element, drop))\n        affected.push(drop);\n    });\n\n    if(affected.length>0)\n      drop = Droppables.findDeepestChild(affected);\n\n    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);\n    if (drop) {\n      Position.within(drop.element, point[0], point[1]);\n      if(drop.onHover)\n        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));\n\n      if (drop != this.last_active) Droppables.activate(drop);\n    }\n  },\n\n  fire: function(event, element) {\n    if(!this.last_active) return;\n    Position.prepare();\n\n    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))\n      if (this.last_active.onDrop) {\n        this.last_active.onDrop(element, this.last_active.element, event);\n        return true;\n      }\n  },\n\n  reset: function() {\n    if(this.last_active)\n      this.deactivate(this.last_active);\n  }\n};\n\nvar Draggables = {\n  drags: [],\n  observers: [],\n\n  register: function(draggable) {\n    if(this.drags.length == 0) {\n      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);\n      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);\n      this.eventKeypress  = this.keyPress.bindAsEventListener(this);\n\n      Event.observe(document, \"mouseup\", this.eventMouseUp);\n      Event.observe(document, \"mousemove\", this.eventMouseMove);\n      Event.observe(document, \"keypress\", this.eventKeypress);\n    }\n    this.drags.push(draggable);\n  },\n\n  unregister: function(draggable) {\n    this.drags = this.drags.reject(function(d) { return d==draggable });\n    if(this.drags.length == 0) {\n      Event.stopObserving(document, \"mouseup\", this.eventMouseUp);\n      Event.stopObserving(document, \"mousemove\", this.eventMouseMove);\n      Event.stopObserving(document, \"keypress\", this.eventKeypress);\n    }\n  },\n\n  activate: function(draggable) {\n    if(draggable.options.delay) {\n      this._timeout = setTimeout(function() {\n        Draggables._timeout = null;\n        window.focus();\n        Draggables.activeDraggable = draggable;\n      }.bind(this), draggable.options.delay);\n    } else {\n      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari\n      this.activeDraggable = draggable;\n    }\n  },\n\n  deactivate: function() {\n    this.activeDraggable = null;\n  },\n\n  updateDrag: function(event) {\n    if(!this.activeDraggable) return;\n    var pointer = [Event.pointerX(event), Event.pointerY(event)];\n    // Mozilla-based browsers fire successive mousemove events with\n    // the same coordinates, prevent needless redrawing (moz bug?)\n    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;\n    this._lastPointer = pointer;\n\n    this.activeDraggable.updateDrag(event, pointer);\n  },\n\n  endDrag: function(event) {\n    if(this._timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    }\n    if(!this.activeDraggable) return;\n    this._lastPointer = null;\n    this.activeDraggable.endDrag(event);\n    this.activeDraggable = null;\n  },\n\n  keyPress: function(event) {\n    if(this.activeDraggable)\n      this.activeDraggable.keyPress(event);\n  },\n\n  addObserver: function(observer) {\n    this.observers.push(observer);\n    this._cacheObserverCallbacks();\n  },\n\n  removeObserver: function(element) {  // element instead of observer fixes mem leaks\n    this.observers = this.observers.reject( function(o) { return o.element==element });\n    this._cacheObserverCallbacks();\n  },\n\n  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'\n    if(this[eventName+'Count'] > 0)\n      this.observers.each( function(o) {\n        if(o[eventName]) o[eventName](eventName, draggable, event);\n      });\n    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);\n  },\n\n  _cacheObserverCallbacks: function() {\n    ['onStart','onEnd','onDrag'].each( function(eventName) {\n      Draggables[eventName+'Count'] = Draggables.observers.select(\n        function(o) { return o[eventName]; }\n      ).length;\n    });\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Draggable = Class.create({\n  initialize: function(element) {\n    var defaults = {\n      handle: false,\n      reverteffect: function(element, top_offset, left_offset) {\n        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;\n        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,\n          queue: {scope:'_draggable', position:'end'}\n        });\n      },\n      endeffect: function(element) {\n        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;\n        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,\n          queue: {scope:'_draggable', position:'end'},\n          afterFinish: function(){\n            Draggable._dragging[element] = false\n          }\n        });\n      },\n      zindex: 1000,\n      revert: false,\n      quiet: false,\n      scroll: false,\n      scrollSensitivity: 20,\n      scrollSpeed: 15,\n      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }\n      delay: 0\n    };\n\n    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))\n      Object.extend(defaults, {\n        starteffect: function(element) {\n          element._opacity = Element.getOpacity(element);\n          Draggable._dragging[element] = true;\n          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});\n        }\n      });\n\n    var options = Object.extend(defaults, arguments[1] || { });\n\n    this.element = $(element);\n\n    if(options.handle && Object.isString(options.handle))\n      this.handle = this.element.down('.'+options.handle, 0);\n\n    if(!this.handle) this.handle = $(options.handle);\n    if(!this.handle) this.handle = this.element;\n\n    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {\n      options.scroll = $(options.scroll);\n      this._isScrollChild = Element.childOf(this.element, options.scroll);\n    }\n\n    Element.makePositioned(this.element); // fix IE\n\n    this.options  = options;\n    this.dragging = false;\n\n    this.eventMouseDown = this.initDrag.bindAsEventListener(this);\n    Event.observe(this.handle, \"mousedown\", this.eventMouseDown);\n\n    Draggables.register(this);\n  },\n\n  destroy: function() {\n    Event.stopObserving(this.handle, \"mousedown\", this.eventMouseDown);\n    Draggables.unregister(this);\n  },\n\n  currentDelta: function() {\n    return([\n      parseInt(Element.getStyle(this.element,'left') || '0'),\n      parseInt(Element.getStyle(this.element,'top') || '0')]);\n  },\n\n  initDrag: function(event) {\n    if(!Object.isUndefined(Draggable._dragging[this.element]) &&\n      Draggable._dragging[this.element]) return;\n    if(Event.isLeftClick(event)) {\n      // abort on form elements, fixes a Firefox issue\n      var src = Event.element(event);\n      if((tag_name = src.tagName.toUpperCase()) && (\n        tag_name=='INPUT' ||\n        tag_name=='SELECT' ||\n        tag_name=='OPTION' ||\n        tag_name=='BUTTON' ||\n        tag_name=='TEXTAREA')) return;\n\n      var pointer = [Event.pointerX(event), Event.pointerY(event)];\n      var pos     = Position.cumulativeOffset(this.element);\n      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });\n\n      Draggables.activate(this);\n      Event.stop(event);\n    }\n  },\n\n  startDrag: function(event) {\n    this.dragging = true;\n    if(!this.delta)\n      this.delta = this.currentDelta();\n\n    if(this.options.zindex) {\n      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);\n      this.element.style.zIndex = this.options.zindex;\n    }\n\n    if(this.options.ghosting) {\n      this._clone = this.element.cloneNode(true);\n      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');\n      if (!this._originallyAbsolute)\n        Position.absolutize(this.element);\n      this.element.parentNode.insertBefore(this._clone, this.element);\n    }\n\n    if(this.options.scroll) {\n      if (this.options.scroll == window) {\n        var where = this._getWindowScroll(this.options.scroll);\n        this.originalScrollLeft = where.left;\n        this.originalScrollTop = where.top;\n      } else {\n        this.originalScrollLeft = this.options.scroll.scrollLeft;\n        this.originalScrollTop = this.options.scroll.scrollTop;\n      }\n    }\n\n    Draggables.notify('onStart', this, event);\n\n    if(this.options.starteffect) this.options.starteffect(this.element);\n  },\n\n  updateDrag: function(event, pointer) {\n    if(!this.dragging) this.startDrag(event);\n\n    if(!this.options.quiet){\n      Position.prepare();\n      Droppables.show(pointer, this.element);\n    }\n\n    Draggables.notify('onDrag', this, event);\n\n    this.draw(pointer);\n    if(this.options.change) this.options.change(this);\n\n    if(this.options.scroll) {\n      this.stopScrolling();\n\n      var p;\n      if (this.options.scroll == window) {\n        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }\n      } else {\n        p = Position.page(this.options.scroll);\n        p[0] += this.options.scroll.scrollLeft + Position.deltaX;\n        p[1] += this.options.scroll.scrollTop + Position.deltaY;\n        p.push(p[0]+this.options.scroll.offsetWidth);\n        p.push(p[1]+this.options.scroll.offsetHeight);\n      }\n      var speed = [0,0];\n      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);\n      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);\n      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);\n      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);\n      this.startScrolling(speed);\n    }\n\n    // fix AppleWebKit rendering\n    if(Prototype.Browser.WebKit) window.scrollBy(0,0);\n\n    Event.stop(event);\n  },\n\n  finishDrag: function(event, success) {\n    this.dragging = false;\n\n    if(this.options.quiet){\n      Position.prepare();\n      var pointer = [Event.pointerX(event), Event.pointerY(event)];\n      Droppables.show(pointer, this.element);\n    }\n\n    if(this.options.ghosting) {\n      if (!this._originallyAbsolute)\n        Position.relativize(this.element);\n      delete this._originallyAbsolute;\n      Element.remove(this._clone);\n      this._clone = null;\n    }\n\n    var dropped = false;\n    if(success) {\n      dropped = Droppables.fire(event, this.element);\n      if (!dropped) dropped = false;\n    }\n    if(dropped && this.options.onDropped) this.options.onDropped(this.element);\n    Draggables.notify('onEnd', this, event);\n\n    var revert = this.options.revert;\n    if(revert && Object.isFunction(revert)) revert = revert(this.element);\n\n    var d = this.currentDelta();\n    if(revert && this.options.reverteffect) {\n      if (dropped == 0 || revert != 'failure')\n        this.options.reverteffect(this.element,\n          d[1]-this.delta[1], d[0]-this.delta[0]);\n    } else {\n      this.delta = d;\n    }\n\n    if(this.options.zindex)\n      this.element.style.zIndex = this.originalZ;\n\n    if(this.options.endeffect)\n      this.options.endeffect(this.element);\n\n    Draggables.deactivate(this);\n    Droppables.reset();\n  },\n\n  keyPress: function(event) {\n    if(event.keyCode!=Event.KEY_ESC) return;\n    this.finishDrag(event, false);\n    Event.stop(event);\n  },\n\n  endDrag: function(event) {\n    if(!this.dragging) return;\n    this.stopScrolling();\n    this.finishDrag(event, true);\n    Event.stop(event);\n  },\n\n  draw: function(point) {\n    var pos = Position.cumulativeOffset(this.element);\n    if(this.options.ghosting) {\n      var r   = Position.realOffset(this.element);\n      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;\n    }\n\n    var d = this.currentDelta();\n    pos[0] -= d[0]; pos[1] -= d[1];\n\n    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {\n      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;\n      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;\n    }\n\n    var p = [0,1].map(function(i){\n      return (point[i]-pos[i]-this.offset[i])\n    }.bind(this));\n\n    if(this.options.snap) {\n      if(Object.isFunction(this.options.snap)) {\n        p = this.options.snap(p[0],p[1],this);\n      } else {\n      if(Object.isArray(this.options.snap)) {\n        p = p.map( function(v, i) {\n          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));\n      } else {\n        p = p.map( function(v) {\n          return (v/this.options.snap).round()*this.options.snap }.bind(this));\n      }\n    }}\n\n    var style = this.element.style;\n    if((!this.options.constraint) || (this.options.constraint=='horizontal'))\n      style.left = p[0] + \"px\";\n    if((!this.options.constraint) || (this.options.constraint=='vertical'))\n      style.top  = p[1] + \"px\";\n\n    if(style.visibility==\"hidden\") style.visibility = \"\"; // fix gecko rendering\n  },\n\n  stopScrolling: function() {\n    if(this.scrollInterval) {\n      clearInterval(this.scrollInterval);\n      this.scrollInterval = null;\n      Draggables._lastScrollPointer = null;\n    }\n  },\n\n  startScrolling: function(speed) {\n    if(!(speed[0] || speed[1])) return;\n    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];\n    this.lastScrolled = new Date();\n    this.scrollInterval = setInterval(this.scroll.bind(this), 10);\n  },\n\n  scroll: function() {\n    var current = new Date();\n    var delta = current - this.lastScrolled;\n    this.lastScrolled = current;\n    if(this.options.scroll == window) {\n      with (this._getWindowScroll(this.options.scroll)) {\n        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {\n          var d = delta / 1000;\n          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );\n        }\n      }\n    } else {\n      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;\n      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;\n    }\n\n    Position.prepare();\n    Droppables.show(Draggables._lastPointer, this.element);\n    Draggables.notify('onDrag', this);\n    if (this._isScrollChild) {\n      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);\n      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;\n      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;\n      if (Draggables._lastScrollPointer[0] < 0)\n        Draggables._lastScrollPointer[0] = 0;\n      if (Draggables._lastScrollPointer[1] < 0)\n        Draggables._lastScrollPointer[1] = 0;\n      this.draw(Draggables._lastScrollPointer);\n    }\n\n    if(this.options.change) this.options.change(this);\n  },\n\n  _getWindowScroll: function(w) {\n    var T, L, W, H;\n    with (w.document) {\n      if (w.document.documentElement && documentElement.scrollTop) {\n        T = documentElement.scrollTop;\n        L = documentElement.scrollLeft;\n      } else if (w.document.body) {\n        T = body.scrollTop;\n        L = body.scrollLeft;\n      }\n      if (w.innerWidth) {\n        W = w.innerWidth;\n        H = w.innerHeight;\n      } else if (w.document.documentElement && documentElement.clientWidth) {\n        W = documentElement.clientWidth;\n        H = documentElement.clientHeight;\n      } else {\n        W = body.offsetWidth;\n        H = body.offsetHeight;\n      }\n    }\n    return { top: T, left: L, width: W, height: H };\n  }\n});\n\nDraggable._dragging = { };\n\n/*--------------------------------------------------------------------------*/\n\nvar SortableObserver = Class.create({\n  initialize: function(element, observer) {\n    this.element   = $(element);\n    this.observer  = observer;\n    this.lastValue = Sortable.serialize(this.element);\n  },\n\n  onStart: function() {\n    this.lastValue = Sortable.serialize(this.element);\n  },\n\n  onEnd: function() {\n    Sortable.unmark();\n    if(this.lastValue != Sortable.serialize(this.element))\n      this.observer(this.element)\n  }\n});\n\nvar Sortable = {\n  SERIALIZE_RULE: /^[^_\\-](?:[A-Za-z0-9\\-\\_]*)[_](.*)$/,\n\n  sortables: { },\n\n  _findRootElement: function(element) {\n    while (element.tagName.toUpperCase() != \"BODY\") {\n      if(element.id && Sortable.sortables[element.id]) return element;\n      element = element.parentNode;\n    }\n  },\n\n  options: function(element) {\n    element = Sortable._findRootElement($(element));\n    if(!element) return;\n    return Sortable.sortables[element.id];\n  },\n\n  destroy: function(element){\n    element = $(element);\n    var s = Sortable.sortables[element.id];\n\n    if(s) {\n      Draggables.removeObserver(s.element);\n      s.droppables.each(function(d){ Droppables.remove(d) });\n      s.draggables.invoke('destroy');\n\n      delete Sortable.sortables[s.element.id];\n    }\n  },\n\n  create: function(element) {\n    element = $(element);\n    var options = Object.extend({\n      element:     element,\n      tag:         'li',       // assumes li children, override with tag: 'tagname'\n      dropOnEmpty: false,\n      tree:        false,\n      treeTag:     'ul',\n      overlap:     'vertical', // one of 'vertical', 'horizontal'\n      constraint:  'vertical', // one of 'vertical', 'horizontal', false\n      containment: element,    // also takes array of elements (or id's); or false\n      handle:      false,      // or a CSS class\n      only:        false,\n      delay:       0,\n      hoverclass:  null,\n      ghosting:    false,\n      quiet:       false,\n      scroll:      false,\n      scrollSensitivity: 20,\n      scrollSpeed: 15,\n      format:      this.SERIALIZE_RULE,\n\n      // these take arrays of elements or ids and can be\n      // used for better initialization performance\n      elements:    false,\n      handles:     false,\n\n      onChange:    Prototype.emptyFunction,\n      onUpdate:    Prototype.emptyFunction\n    }, arguments[1] || { });\n\n    // clear any old sortable with same element\n    this.destroy(element);\n\n    // build options for the draggables\n    var options_for_draggable = {\n      revert:      true,\n      quiet:       options.quiet,\n      scroll:      options.scroll,\n      scrollSpeed: options.scrollSpeed,\n      scrollSensitivity: options.scrollSensitivity,\n      delay:       options.delay,\n      ghosting:    options.ghosting,\n      constraint:  options.constraint,\n      handle:      options.handle };\n\n    if(options.starteffect)\n      options_for_draggable.starteffect = options.starteffect;\n\n    if(options.reverteffect)\n      options_for_draggable.reverteffect = options.reverteffect;\n    else\n      if(options.ghosting) options_for_draggable.reverteffect = function(element) {\n        element.style.top  = 0;\n        element.style.left = 0;\n      };\n\n    if(options.endeffect)\n      options_for_draggable.endeffect = options.endeffect;\n\n    if(options.zindex)\n      options_for_draggable.zindex = options.zindex;\n\n    // build options for the droppables\n    var options_for_droppable = {\n      overlap:     options.overlap,\n      containment: options.containment,\n      tree:        options.tree,\n      hoverclass:  options.hoverclass,\n      onHover:     Sortable.onHover\n    };\n\n    var options_for_tree = {\n      onHover:      Sortable.onEmptyHover,\n      overlap:      options.overlap,\n      containment:  options.containment,\n      hoverclass:   options.hoverclass\n    };\n\n    // fix for gecko engine\n    Element.cleanWhitespace(element);\n\n    options.draggables = [];\n    options.droppables = [];\n\n    // drop on empty handling\n    if(options.dropOnEmpty || options.tree) {\n      Droppables.add(element, options_for_tree);\n      options.droppables.push(element);\n    }\n\n    (options.elements || this.findElements(element, options) || []).each( function(e,i) {\n      var handle = options.handles ? $(options.handles[i]) :\n        (options.handle ? $(e).select('.' + options.handle)[0] : e);\n      options.draggables.push(\n        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));\n      Droppables.add(e, options_for_droppable);\n      if(options.tree) e.treeNode = element;\n      options.droppables.push(e);\n    });\n\n    if(options.tree) {\n      (Sortable.findTreeElements(element, options) || []).each( function(e) {\n        Droppables.add(e, options_for_tree);\n        e.treeNode = element;\n        options.droppables.push(e);\n      });\n    }\n\n    // keep reference\n    this.sortables[element.id] = options;\n\n    // for onupdate\n    Draggables.addObserver(new SortableObserver(element, options.onUpdate));\n\n  },\n\n  // return all suitable-for-sortable elements in a guaranteed order\n  findElements: function(element, options) {\n    return Element.findChildren(\n      element, options.only, options.tree ? true : false, options.tag);\n  },\n\n  findTreeElements: function(element, options) {\n    return Element.findChildren(\n      element, options.only, options.tree ? true : false, options.treeTag);\n  },\n\n  onHover: function(element, dropon, overlap) {\n    if(Element.isParent(dropon, element)) return;\n\n    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {\n      return;\n    } else if(overlap>0.5) {\n      Sortable.mark(dropon, 'before');\n      if(dropon.previousSibling != element) {\n        var oldParentNode = element.parentNode;\n        element.style.visibility = \"hidden\"; // fix gecko rendering\n        dropon.parentNode.insertBefore(element, dropon);\n        if(dropon.parentNode!=oldParentNode)\n          Sortable.options(oldParentNode).onChange(element);\n        Sortable.options(dropon.parentNode).onChange(element);\n      }\n    } else {\n      Sortable.mark(dropon, 'after');\n      var nextElement = dropon.nextSibling || null;\n      if(nextElement != element) {\n        var oldParentNode = element.parentNode;\n        element.style.visibility = \"hidden\"; // fix gecko rendering\n        dropon.parentNode.insertBefore(element, nextElement);\n        if(dropon.parentNode!=oldParentNode)\n          Sortable.options(oldParentNode).onChange(element);\n        Sortable.options(dropon.parentNode).onChange(element);\n      }\n    }\n  },\n\n  onEmptyHover: function(element, dropon, overlap) {\n    var oldParentNode = element.parentNode;\n    var droponOptions = Sortable.options(dropon);\n\n    if(!Element.isParent(dropon, element)) {\n      var index;\n\n      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});\n      var child = null;\n\n      if(children) {\n        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);\n\n        for (index = 0; index < children.length; index += 1) {\n          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {\n            offset -= Element.offsetSize (children[index], droponOptions.overlap);\n          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {\n            child = index + 1 < children.length ? children[index + 1] : null;\n            break;\n          } else {\n            child = children[index];\n            break;\n          }\n        }\n      }\n\n      dropon.insertBefore(element, child);\n\n      Sortable.options(oldParentNode).onChange(element);\n      droponOptions.onChange(element);\n    }\n  },\n\n  unmark: function() {\n    if(Sortable._marker) Sortable._marker.hide();\n  },\n\n  mark: function(dropon, position) {\n    // mark on ghosting only\n    var sortable = Sortable.options(dropon.parentNode);\n    if(sortable && !sortable.ghosting) return;\n\n    if(!Sortable._marker) {\n      Sortable._marker =\n        ($('dropmarker') || Element.extend(document.createElement('DIV'))).\n          hide().addClassName('dropmarker').setStyle({position:'absolute'});\n      document.getElementsByTagName(\"body\").item(0).appendChild(Sortable._marker);\n    }\n    var offsets = Position.cumulativeOffset(dropon);\n    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});\n\n    if(position=='after')\n      if(sortable.overlap == 'horizontal')\n        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});\n      else\n        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});\n\n    Sortable._marker.show();\n  },\n\n  _tree: function(element, options, parent) {\n    var children = Sortable.findElements(element, options) || [];\n\n    for (var i = 0; i < children.length; ++i) {\n      var match = children[i].id.match(options.format);\n\n      if (!match) continue;\n\n      var child = {\n        id: encodeURIComponent(match ? match[1] : null),\n        element: element,\n        parent: parent,\n        children: [],\n        position: parent.children.length,\n        container: $(children[i]).down(options.treeTag)\n      };\n\n      /* Get the element containing the children and recurse over it */\n      if (child.container)\n        this._tree(child.container, options, child);\n\n      parent.children.push (child);\n    }\n\n    return parent;\n  },\n\n  tree: function(element) {\n    element = $(element);\n    var sortableOptions = this.options(element);\n    var options = Object.extend({\n      tag: sortableOptions.tag,\n      treeTag: sortableOptions.treeTag,\n      only: sortableOptions.only,\n      name: element.id,\n      format: sortableOptions.format\n    }, arguments[1] || { });\n\n    var root = {\n      id: null,\n      parent: null,\n      children: [],\n      container: element,\n      position: 0\n    };\n\n    return Sortable._tree(element, options, root);\n  },\n\n  /* Construct a [i] index for a particular node */\n  _constructIndex: function(node) {\n    var index = '';\n    do {\n      if (node.id) index = '[' + node.position + ']' + index;\n    } while ((node = node.parent) != null);\n    return index;\n  },\n\n  sequence: function(element) {\n    element = $(element);\n    var options = Object.extend(this.options(element), arguments[1] || { });\n\n    return $(this.findElements(element, options) || []).map( function(item) {\n      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';\n    });\n  },\n\n  setSequence: function(element, new_sequence) {\n    element = $(element);\n    var options = Object.extend(this.options(element), arguments[2] || { });\n\n    var nodeMap = { };\n    this.findElements(element, options).each( function(n) {\n        if (n.id.match(options.format))\n            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];\n        n.parentNode.removeChild(n);\n    });\n\n    new_sequence.each(function(ident) {\n      var n = nodeMap[ident];\n      if (n) {\n        n[1].appendChild(n[0]);\n        delete nodeMap[ident];\n      }\n    });\n  },\n\n  serialize: function(element) {\n    element = $(element);\n    var options = Object.extend(Sortable.options(element), arguments[1] || { });\n    var name = encodeURIComponent(\n      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);\n\n    if (options.tree) {\n      return Sortable.tree(element, arguments[1]).children.map( function (item) {\n        return [name + Sortable._constructIndex(item) + \"[id]=\" +\n                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));\n      }).flatten().join('&');\n    } else {\n      return Sortable.sequence(element, arguments[1]).map( function(item) {\n        return name + \"[]=\" + encodeURIComponent(item);\n      }).join('&');\n    }\n  }\n};\n\n// Returns true if child is contained within element\nElement.isParent = function(child, element) {\n  if (!child.parentNode || child == element) return false;\n  if (child.parentNode == element) return true;\n  return Element.isParent(child.parentNode, element);\n};\n\nElement.findChildren = function(element, only, recursive, tagName) {\n  if(!element.hasChildNodes()) return null;\n  tagName = tagName.toUpperCase();\n  if(only) only = [only].flatten();\n  var elements = [];\n  $A(element.childNodes).each( function(e) {\n    if(e.tagName && e.tagName.toUpperCase()==tagName &&\n      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))\n        elements.push(e);\n    if(recursive) {\n      var grandchildren = Element.findChildren(e, only, recursive, tagName);\n      if(grandchildren) elements.push(grandchildren);\n    }\n  });\n\n  return (elements.length>0 ? elements.flatten() : []);\n};\n\nElement.offsetSize = function (element, type) {\n  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];\n};"
  },
  {
    "path": "example/rails2.3/public/javascripts/effects.js",
    "content": "// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n// Contributors:\n//  Justin Palmer (http://encytemedia.com/)\n//  Mark Pilgrim (http://diveintomark.org/)\n//  Martin Bialasinki\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\n// converts rgb() and #xxx to #xxxxxx format,\n// returns self (or first argument) if not convertable\nString.prototype.parseColor = function() {\n  var color = '#';\n  if (this.slice(0,4) == 'rgb(') {\n    var cols = this.slice(4,this.length-1).split(',');\n    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);\n  } else {\n    if (this.slice(0,1) == '#') {\n      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();\n      if (this.length==7) color = this.toLowerCase();\n    }\n  }\n  return (color.length==7 ? color : (arguments[0] || this));\n};\n\n/*--------------------------------------------------------------------------*/\n\nElement.collectTextNodes = function(element) {\n  return $A($(element).childNodes).collect( function(node) {\n    return (node.nodeType==3 ? node.nodeValue :\n      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));\n  }).flatten().join('');\n};\n\nElement.collectTextNodesIgnoreClass = function(element, className) {\n  return $A($(element).childNodes).collect( function(node) {\n    return (node.nodeType==3 ? node.nodeValue :\n      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?\n        Element.collectTextNodesIgnoreClass(node, className) : ''));\n  }).flatten().join('');\n};\n\nElement.setContentZoom = function(element, percent) {\n  element = $(element);\n  element.setStyle({fontSize: (percent/100) + 'em'});\n  if (Prototype.Browser.WebKit) window.scrollBy(0,0);\n  return element;\n};\n\nElement.getInlineOpacity = function(element){\n  return $(element).style.opacity || '';\n};\n\nElement.forceRerendering = function(element) {\n  try {\n    element = $(element);\n    var n = document.createTextNode(' ');\n    element.appendChild(n);\n    element.removeChild(n);\n  } catch(e) { }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Effect = {\n  _elementDoesNotExistError: {\n    name: 'ElementDoesNotExistError',\n    message: 'The specified DOM element does not exist, but is required for this effect to operate'\n  },\n  Transitions: {\n    linear: Prototype.K,\n    sinoidal: function(pos) {\n      return (-Math.cos(pos*Math.PI)/2) + .5;\n    },\n    reverse: function(pos) {\n      return 1-pos;\n    },\n    flicker: function(pos) {\n      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;\n      return pos > 1 ? 1 : pos;\n    },\n    wobble: function(pos) {\n      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;\n    },\n    pulse: function(pos, pulses) {\n      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;\n    },\n    spring: function(pos) {\n      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));\n    },\n    none: function(pos) {\n      return 0;\n    },\n    full: function(pos) {\n      return 1;\n    }\n  },\n  DefaultOptions: {\n    duration:   1.0,   // seconds\n    fps:        100,   // 100= assume 66fps max.\n    sync:       false, // true for combining\n    from:       0.0,\n    to:         1.0,\n    delay:      0.0,\n    queue:      'parallel'\n  },\n  tagifyText: function(element) {\n    var tagifyStyle = 'position:relative';\n    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';\n\n    element = $(element);\n    $A(element.childNodes).each( function(child) {\n      if (child.nodeType==3) {\n        child.nodeValue.toArray().each( function(character) {\n          element.insertBefore(\n            new Element('span', {style: tagifyStyle}).update(\n              character == ' ' ? String.fromCharCode(160) : character),\n              child);\n        });\n        Element.remove(child);\n      }\n    });\n  },\n  multiple: function(element, effect) {\n    var elements;\n    if (((typeof element == 'object') ||\n        Object.isFunction(element)) &&\n       (element.length))\n      elements = element;\n    else\n      elements = $(element).childNodes;\n\n    var options = Object.extend({\n      speed: 0.1,\n      delay: 0.0\n    }, arguments[2] || { });\n    var masterDelay = options.delay;\n\n    $A(elements).each( function(element, index) {\n      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));\n    });\n  },\n  PAIRS: {\n    'slide':  ['SlideDown','SlideUp'],\n    'blind':  ['BlindDown','BlindUp'],\n    'appear': ['Appear','Fade']\n  },\n  toggle: function(element, effect) {\n    element = $(element);\n    effect = (effect || 'appear').toLowerCase();\n    var options = Object.extend({\n      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }\n    }, arguments[2] || { });\n    Effect[element.visible() ?\n      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);\n  }\n};\n\nEffect.DefaultOptions.transition = Effect.Transitions.sinoidal;\n\n/* ------------- core effects ------------- */\n\nEffect.ScopedQueue = Class.create(Enumerable, {\n  initialize: function() {\n    this.effects  = [];\n    this.interval = null;\n  },\n  _each: function(iterator) {\n    this.effects._each(iterator);\n  },\n  add: function(effect) {\n    var timestamp = new Date().getTime();\n\n    var position = Object.isString(effect.options.queue) ?\n      effect.options.queue : effect.options.queue.position;\n\n    switch(position) {\n      case 'front':\n        // move unstarted effects after this effect\n        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {\n            e.startOn  += effect.finishOn;\n            e.finishOn += effect.finishOn;\n          });\n        break;\n      case 'with-last':\n        timestamp = this.effects.pluck('startOn').max() || timestamp;\n        break;\n      case 'end':\n        // start effect after last queued effect has finished\n        timestamp = this.effects.pluck('finishOn').max() || timestamp;\n        break;\n    }\n\n    effect.startOn  += timestamp;\n    effect.finishOn += timestamp;\n\n    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))\n      this.effects.push(effect);\n\n    if (!this.interval)\n      this.interval = setInterval(this.loop.bind(this), 15);\n  },\n  remove: function(effect) {\n    this.effects = this.effects.reject(function(e) { return e==effect });\n    if (this.effects.length == 0) {\n      clearInterval(this.interval);\n      this.interval = null;\n    }\n  },\n  loop: function() {\n    var timePos = new Date().getTime();\n    for(var i=0, len=this.effects.length;i<len;i++)\n      this.effects[i] && this.effects[i].loop(timePos);\n  }\n});\n\nEffect.Queues = {\n  instances: $H(),\n  get: function(queueName) {\n    if (!Object.isString(queueName)) return queueName;\n\n    return this.instances.get(queueName) ||\n      this.instances.set(queueName, new Effect.ScopedQueue());\n  }\n};\nEffect.Queue = Effect.Queues.get('global');\n\nEffect.Base = Class.create({\n  position: null,\n  start: function(options) {\n    function codeForEvent(options,eventName){\n      return (\n        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +\n        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')\n      );\n    }\n    if (options && options.transition === false) options.transition = Effect.Transitions.linear;\n    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });\n    this.currentFrame = 0;\n    this.state        = 'idle';\n    this.startOn      = this.options.delay*1000;\n    this.finishOn     = this.startOn+(this.options.duration*1000);\n    this.fromToDelta  = this.options.to-this.options.from;\n    this.totalTime    = this.finishOn-this.startOn;\n    this.totalFrames  = this.options.fps*this.options.duration;\n\n    this.render = (function() {\n      function dispatch(effect, eventName) {\n        if (effect.options[eventName + 'Internal'])\n          effect.options[eventName + 'Internal'](effect);\n        if (effect.options[eventName])\n          effect.options[eventName](effect);\n      }\n\n      return function(pos) {\n        if (this.state === \"idle\") {\n          this.state = \"running\";\n          dispatch(this, 'beforeSetup');\n          if (this.setup) this.setup();\n          dispatch(this, 'afterSetup');\n        }\n        if (this.state === \"running\") {\n          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;\n          this.position = pos;\n          dispatch(this, 'beforeUpdate');\n          if (this.update) this.update(pos);\n          dispatch(this, 'afterUpdate');\n        }\n      };\n    })();\n\n    this.event('beforeStart');\n    if (!this.options.sync)\n      Effect.Queues.get(Object.isString(this.options.queue) ?\n        'global' : this.options.queue.scope).add(this);\n  },\n  loop: function(timePos) {\n    if (timePos >= this.startOn) {\n      if (timePos >= this.finishOn) {\n        this.render(1.0);\n        this.cancel();\n        this.event('beforeFinish');\n        if (this.finish) this.finish();\n        this.event('afterFinish');\n        return;\n      }\n      var pos   = (timePos - this.startOn) / this.totalTime,\n          frame = (pos * this.totalFrames).round();\n      if (frame > this.currentFrame) {\n        this.render(pos);\n        this.currentFrame = frame;\n      }\n    }\n  },\n  cancel: function() {\n    if (!this.options.sync)\n      Effect.Queues.get(Object.isString(this.options.queue) ?\n        'global' : this.options.queue.scope).remove(this);\n    this.state = 'finished';\n  },\n  event: function(eventName) {\n    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);\n    if (this.options[eventName]) this.options[eventName](this);\n  },\n  inspect: function() {\n    var data = $H();\n    for(property in this)\n      if (!Object.isFunction(this[property])) data.set(property, this[property]);\n    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';\n  }\n});\n\nEffect.Parallel = Class.create(Effect.Base, {\n  initialize: function(effects) {\n    this.effects = effects || [];\n    this.start(arguments[1]);\n  },\n  update: function(position) {\n    this.effects.invoke('render', position);\n  },\n  finish: function(position) {\n    this.effects.each( function(effect) {\n      effect.render(1.0);\n      effect.cancel();\n      effect.event('beforeFinish');\n      if (effect.finish) effect.finish(position);\n      effect.event('afterFinish');\n    });\n  }\n});\n\nEffect.Tween = Class.create(Effect.Base, {\n  initialize: function(object, from, to) {\n    object = Object.isString(object) ? $(object) : object;\n    var args = $A(arguments), method = args.last(),\n      options = args.length == 5 ? args[3] : null;\n    this.method = Object.isFunction(method) ? method.bind(object) :\n      Object.isFunction(object[method]) ? object[method].bind(object) :\n      function(value) { object[method] = value };\n    this.start(Object.extend({ from: from, to: to }, options || { }));\n  },\n  update: function(position) {\n    this.method(position);\n  }\n});\n\nEffect.Event = Class.create(Effect.Base, {\n  initialize: function() {\n    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));\n  },\n  update: Prototype.emptyFunction\n});\n\nEffect.Opacity = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    // make this work on IE on elements without 'layout'\n    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))\n      this.element.setStyle({zoom: 1});\n    var options = Object.extend({\n      from: this.element.getOpacity() || 0.0,\n      to:   1.0\n    }, arguments[1] || { });\n    this.start(options);\n  },\n  update: function(position) {\n    this.element.setOpacity(position);\n  }\n});\n\nEffect.Move = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      x:    0,\n      y:    0,\n      mode: 'relative'\n    }, arguments[1] || { });\n    this.start(options);\n  },\n  setup: function() {\n    this.element.makePositioned();\n    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');\n    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');\n    if (this.options.mode == 'absolute') {\n      this.options.x = this.options.x - this.originalLeft;\n      this.options.y = this.options.y - this.originalTop;\n    }\n  },\n  update: function(position) {\n    this.element.setStyle({\n      left: (this.options.x  * position + this.originalLeft).round() + 'px',\n      top:  (this.options.y  * position + this.originalTop).round()  + 'px'\n    });\n  }\n});\n\n// for backwards compatibility\nEffect.MoveBy = function(element, toTop, toLeft) {\n  return new Effect.Move(element,\n    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));\n};\n\nEffect.Scale = Class.create(Effect.Base, {\n  initialize: function(element, percent) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      scaleX: true,\n      scaleY: true,\n      scaleContent: true,\n      scaleFromCenter: false,\n      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values\n      scaleFrom: 100.0,\n      scaleTo:   percent\n    }, arguments[2] || { });\n    this.start(options);\n  },\n  setup: function() {\n    this.restoreAfterFinish = this.options.restoreAfterFinish || false;\n    this.elementPositioning = this.element.getStyle('position');\n\n    this.originalStyle = { };\n    ['top','left','width','height','fontSize'].each( function(k) {\n      this.originalStyle[k] = this.element.style[k];\n    }.bind(this));\n\n    this.originalTop  = this.element.offsetTop;\n    this.originalLeft = this.element.offsetLeft;\n\n    var fontSize = this.element.getStyle('font-size') || '100%';\n    ['em','px','%','pt'].each( function(fontSizeType) {\n      if (fontSize.indexOf(fontSizeType)>0) {\n        this.fontSize     = parseFloat(fontSize);\n        this.fontSizeType = fontSizeType;\n      }\n    }.bind(this));\n\n    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;\n\n    this.dims = null;\n    if (this.options.scaleMode=='box')\n      this.dims = [this.element.offsetHeight, this.element.offsetWidth];\n    if (/^content/.test(this.options.scaleMode))\n      this.dims = [this.element.scrollHeight, this.element.scrollWidth];\n    if (!this.dims)\n      this.dims = [this.options.scaleMode.originalHeight,\n                   this.options.scaleMode.originalWidth];\n  },\n  update: function(position) {\n    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);\n    if (this.options.scaleContent && this.fontSize)\n      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });\n    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);\n  },\n  finish: function(position) {\n    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);\n  },\n  setDimensions: function(height, width) {\n    var d = { };\n    if (this.options.scaleX) d.width = width.round() + 'px';\n    if (this.options.scaleY) d.height = height.round() + 'px';\n    if (this.options.scaleFromCenter) {\n      var topd  = (height - this.dims[0])/2;\n      var leftd = (width  - this.dims[1])/2;\n      if (this.elementPositioning == 'absolute') {\n        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';\n        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';\n      } else {\n        if (this.options.scaleY) d.top = -topd + 'px';\n        if (this.options.scaleX) d.left = -leftd + 'px';\n      }\n    }\n    this.element.setStyle(d);\n  }\n});\n\nEffect.Highlight = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });\n    this.start(options);\n  },\n  setup: function() {\n    // Prevent executing on elements not in the layout flow\n    if (this.element.getStyle('display')=='none') { this.cancel(); return; }\n    // Disable background image during the effect\n    this.oldStyle = { };\n    if (!this.options.keepBackgroundImage) {\n      this.oldStyle.backgroundImage = this.element.getStyle('background-image');\n      this.element.setStyle({backgroundImage: 'none'});\n    }\n    if (!this.options.endcolor)\n      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');\n    if (!this.options.restorecolor)\n      this.options.restorecolor = this.element.getStyle('background-color');\n    // init color calculations\n    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));\n    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));\n  },\n  update: function(position) {\n    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){\n      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });\n  },\n  finish: function() {\n    this.element.setStyle(Object.extend(this.oldStyle, {\n      backgroundColor: this.options.restorecolor\n    }));\n  }\n});\n\nEffect.ScrollTo = function(element) {\n  var options = arguments[1] || { },\n  scrollOffsets = document.viewport.getScrollOffsets(),\n  elementOffsets = $(element).cumulativeOffset();\n\n  if (options.offset) elementOffsets[1] += options.offset;\n\n  return new Effect.Tween(null,\n    scrollOffsets.top,\n    elementOffsets[1],\n    options,\n    function(p){ scrollTo(scrollOffsets.left, p.round()); }\n  );\n};\n\n/* ------------- combination effects ------------- */\n\nEffect.Fade = function(element) {\n  element = $(element);\n  var oldOpacity = element.getInlineOpacity();\n  var options = Object.extend({\n    from: element.getOpacity() || 1.0,\n    to:   0.0,\n    afterFinishInternal: function(effect) {\n      if (effect.options.to!=0) return;\n      effect.element.hide().setStyle({opacity: oldOpacity});\n    }\n  }, arguments[1] || { });\n  return new Effect.Opacity(element,options);\n};\n\nEffect.Appear = function(element) {\n  element = $(element);\n  var options = Object.extend({\n  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),\n  to:   1.0,\n  // force Safari to render floated elements properly\n  afterFinishInternal: function(effect) {\n    effect.element.forceRerendering();\n  },\n  beforeSetup: function(effect) {\n    effect.element.setOpacity(effect.options.from).show();\n  }}, arguments[1] || { });\n  return new Effect.Opacity(element,options);\n};\n\nEffect.Puff = function(element) {\n  element = $(element);\n  var oldStyle = {\n    opacity: element.getInlineOpacity(),\n    position: element.getStyle('position'),\n    top:  element.style.top,\n    left: element.style.left,\n    width: element.style.width,\n    height: element.style.height\n  };\n  return new Effect.Parallel(\n   [ new Effect.Scale(element, 200,\n      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),\n     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],\n     Object.extend({ duration: 1.0,\n      beforeSetupInternal: function(effect) {\n        Position.absolutize(effect.effects[0].element);\n      },\n      afterFinishInternal: function(effect) {\n         effect.effects[0].element.hide().setStyle(oldStyle); }\n     }, arguments[1] || { })\n   );\n};\n\nEffect.BlindUp = function(element) {\n  element = $(element);\n  element.makeClipping();\n  return new Effect.Scale(element, 0,\n    Object.extend({ scaleContent: false,\n      scaleX: false,\n      restoreAfterFinish: true,\n      afterFinishInternal: function(effect) {\n        effect.element.hide().undoClipping();\n      }\n    }, arguments[1] || { })\n  );\n};\n\nEffect.BlindDown = function(element) {\n  element = $(element);\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, 100, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    scaleFrom: 0,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makeClipping().setStyle({height: '0px'}).show();\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.undoClipping();\n    }\n  }, arguments[1] || { }));\n};\n\nEffect.SwitchOff = function(element) {\n  element = $(element);\n  var oldOpacity = element.getInlineOpacity();\n  return new Effect.Appear(element, Object.extend({\n    duration: 0.4,\n    from: 0,\n    transition: Effect.Transitions.flicker,\n    afterFinishInternal: function(effect) {\n      new Effect.Scale(effect.element, 1, {\n        duration: 0.3, scaleFromCenter: true,\n        scaleX: false, scaleContent: false, restoreAfterFinish: true,\n        beforeSetup: function(effect) {\n          effect.element.makePositioned().makeClipping();\n        },\n        afterFinishInternal: function(effect) {\n          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});\n        }\n      });\n    }\n  }, arguments[1] || { }));\n};\n\nEffect.DropOut = function(element) {\n  element = $(element);\n  var oldStyle = {\n    top: element.getStyle('top'),\n    left: element.getStyle('left'),\n    opacity: element.getInlineOpacity() };\n  return new Effect.Parallel(\n    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),\n      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],\n    Object.extend(\n      { duration: 0.5,\n        beforeSetup: function(effect) {\n          effect.effects[0].element.makePositioned();\n        },\n        afterFinishInternal: function(effect) {\n          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);\n        }\n      }, arguments[1] || { }));\n};\n\nEffect.Shake = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    distance: 20,\n    duration: 0.5\n  }, arguments[1] || {});\n  var distance = parseFloat(options.distance);\n  var split = parseFloat(options.duration) / 10.0;\n  var oldStyle = {\n    top: element.getStyle('top'),\n    left: element.getStyle('left') };\n    return new Effect.Move(element,\n      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {\n        effect.element.undoPositioned().setStyle(oldStyle);\n  }}); }}); }}); }}); }}); }});\n};\n\nEffect.SlideDown = function(element) {\n  element = $(element).cleanWhitespace();\n  // SlideDown need to have the content of the element wrapped in a container element with fixed height!\n  var oldInnerBottom = element.down().getStyle('bottom');\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, 100, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    scaleFrom: window.opera ? 0 : 1,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makePositioned();\n      effect.element.down().makePositioned();\n      if (window.opera) effect.element.setStyle({top: ''});\n      effect.element.makeClipping().setStyle({height: '0px'}).show();\n    },\n    afterUpdateInternal: function(effect) {\n      effect.element.down().setStyle({bottom:\n        (effect.dims[0] - effect.element.clientHeight) + 'px' });\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.undoClipping().undoPositioned();\n      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }\n    }, arguments[1] || { })\n  );\n};\n\nEffect.SlideUp = function(element) {\n  element = $(element).cleanWhitespace();\n  var oldInnerBottom = element.down().getStyle('bottom');\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, window.opera ? 0 : 1,\n   Object.extend({ scaleContent: false,\n    scaleX: false,\n    scaleMode: 'box',\n    scaleFrom: 100,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makePositioned();\n      effect.element.down().makePositioned();\n      if (window.opera) effect.element.setStyle({top: ''});\n      effect.element.makeClipping().show();\n    },\n    afterUpdateInternal: function(effect) {\n      effect.element.down().setStyle({bottom:\n        (effect.dims[0] - effect.element.clientHeight) + 'px' });\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.hide().undoClipping().undoPositioned();\n      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});\n    }\n   }, arguments[1] || { })\n  );\n};\n\n// Bug in opera makes the TD containing this element expand for a instance after finish\nEffect.Squish = function(element) {\n  return new Effect.Scale(element, window.opera ? 1 : 0, {\n    restoreAfterFinish: true,\n    beforeSetup: function(effect) {\n      effect.element.makeClipping();\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.hide().undoClipping();\n    }\n  });\n};\n\nEffect.Grow = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    direction: 'center',\n    moveTransition: Effect.Transitions.sinoidal,\n    scaleTransition: Effect.Transitions.sinoidal,\n    opacityTransition: Effect.Transitions.full\n  }, arguments[1] || { });\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    height: element.style.height,\n    width: element.style.width,\n    opacity: element.getInlineOpacity() };\n\n  var dims = element.getDimensions();\n  var initialMoveX, initialMoveY;\n  var moveX, moveY;\n\n  switch (options.direction) {\n    case 'top-left':\n      initialMoveX = initialMoveY = moveX = moveY = 0;\n      break;\n    case 'top-right':\n      initialMoveX = dims.width;\n      initialMoveY = moveY = 0;\n      moveX = -dims.width;\n      break;\n    case 'bottom-left':\n      initialMoveX = moveX = 0;\n      initialMoveY = dims.height;\n      moveY = -dims.height;\n      break;\n    case 'bottom-right':\n      initialMoveX = dims.width;\n      initialMoveY = dims.height;\n      moveX = -dims.width;\n      moveY = -dims.height;\n      break;\n    case 'center':\n      initialMoveX = dims.width / 2;\n      initialMoveY = dims.height / 2;\n      moveX = -dims.width / 2;\n      moveY = -dims.height / 2;\n      break;\n  }\n\n  return new Effect.Move(element, {\n    x: initialMoveX,\n    y: initialMoveY,\n    duration: 0.01,\n    beforeSetup: function(effect) {\n      effect.element.hide().makeClipping().makePositioned();\n    },\n    afterFinishInternal: function(effect) {\n      new Effect.Parallel(\n        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),\n          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),\n          new Effect.Scale(effect.element, 100, {\n            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },\n            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})\n        ], Object.extend({\n             beforeSetup: function(effect) {\n               effect.effects[0].element.setStyle({height: '0px'}).show();\n             },\n             afterFinishInternal: function(effect) {\n               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);\n             }\n           }, options)\n      );\n    }\n  });\n};\n\nEffect.Shrink = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    direction: 'center',\n    moveTransition: Effect.Transitions.sinoidal,\n    scaleTransition: Effect.Transitions.sinoidal,\n    opacityTransition: Effect.Transitions.none\n  }, arguments[1] || { });\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    height: element.style.height,\n    width: element.style.width,\n    opacity: element.getInlineOpacity() };\n\n  var dims = element.getDimensions();\n  var moveX, moveY;\n\n  switch (options.direction) {\n    case 'top-left':\n      moveX = moveY = 0;\n      break;\n    case 'top-right':\n      moveX = dims.width;\n      moveY = 0;\n      break;\n    case 'bottom-left':\n      moveX = 0;\n      moveY = dims.height;\n      break;\n    case 'bottom-right':\n      moveX = dims.width;\n      moveY = dims.height;\n      break;\n    case 'center':\n      moveX = dims.width / 2;\n      moveY = dims.height / 2;\n      break;\n  }\n\n  return new Effect.Parallel(\n    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),\n      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),\n      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })\n    ], Object.extend({\n         beforeStartInternal: function(effect) {\n           effect.effects[0].element.makePositioned().makeClipping();\n         },\n         afterFinishInternal: function(effect) {\n           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }\n       }, options)\n  );\n};\n\nEffect.Pulsate = function(element) {\n  element = $(element);\n  var options    = arguments[1] || { },\n    oldOpacity = element.getInlineOpacity(),\n    transition = options.transition || Effect.Transitions.linear,\n    reverser   = function(pos){\n      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);\n    };\n\n  return new Effect.Opacity(element,\n    Object.extend(Object.extend({  duration: 2.0, from: 0,\n      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }\n    }, options), {transition: reverser}));\n};\n\nEffect.Fold = function(element) {\n  element = $(element);\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    width: element.style.width,\n    height: element.style.height };\n  element.makeClipping();\n  return new Effect.Scale(element, 5, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    afterFinishInternal: function(effect) {\n    new Effect.Scale(element, 1, {\n      scaleContent: false,\n      scaleY: false,\n      afterFinishInternal: function(effect) {\n        effect.element.hide().undoClipping().setStyle(oldStyle);\n      } });\n  }}, arguments[1] || { }));\n};\n\nEffect.Morph = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      style: { }\n    }, arguments[1] || { });\n\n    if (!Object.isString(options.style)) this.style = $H(options.style);\n    else {\n      if (options.style.include(':'))\n        this.style = options.style.parseStyle();\n      else {\n        this.element.addClassName(options.style);\n        this.style = $H(this.element.getStyles());\n        this.element.removeClassName(options.style);\n        var css = this.element.getStyles();\n        this.style = this.style.reject(function(style) {\n          return style.value == css[style.key];\n        });\n        options.afterFinishInternal = function(effect) {\n          effect.element.addClassName(effect.options.style);\n          effect.transforms.each(function(transform) {\n            effect.element.style[transform.style] = '';\n          });\n        };\n      }\n    }\n    this.start(options);\n  },\n\n  setup: function(){\n    function parseColor(color){\n      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';\n      color = color.parseColor();\n      return $R(0,2).map(function(i){\n        return parseInt( color.slice(i*2+1,i*2+3), 16 );\n      });\n    }\n    this.transforms = this.style.map(function(pair){\n      var property = pair[0], value = pair[1], unit = null;\n\n      if (value.parseColor('#zzzzzz') != '#zzzzzz') {\n        value = value.parseColor();\n        unit  = 'color';\n      } else if (property == 'opacity') {\n        value = parseFloat(value);\n        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))\n          this.element.setStyle({zoom: 1});\n      } else if (Element.CSS_LENGTH.test(value)) {\n          var components = value.match(/^([\\+\\-]?[0-9\\.]+)(.*)$/);\n          value = parseFloat(components[1]);\n          unit = (components.length == 3) ? components[2] : null;\n      }\n\n      var originalValue = this.element.getStyle(property);\n      return {\n        style: property.camelize(),\n        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),\n        targetValue: unit=='color' ? parseColor(value) : value,\n        unit: unit\n      };\n    }.bind(this)).reject(function(transform){\n      return (\n        (transform.originalValue == transform.targetValue) ||\n        (\n          transform.unit != 'color' &&\n          (isNaN(transform.originalValue) || isNaN(transform.targetValue))\n        )\n      );\n    });\n  },\n  update: function(position) {\n    var style = { }, transform, i = this.transforms.length;\n    while(i--)\n      style[(transform = this.transforms[i]).style] =\n        transform.unit=='color' ? '#'+\n          (Math.round(transform.originalValue[0]+\n            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +\n          (Math.round(transform.originalValue[1]+\n            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +\n          (Math.round(transform.originalValue[2]+\n            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :\n        (transform.originalValue +\n          (transform.targetValue - transform.originalValue) * position).toFixed(3) +\n            (transform.unit === null ? '' : transform.unit);\n    this.element.setStyle(style, true);\n  }\n});\n\nEffect.Transform = Class.create({\n  initialize: function(tracks){\n    this.tracks  = [];\n    this.options = arguments[1] || { };\n    this.addTracks(tracks);\n  },\n  addTracks: function(tracks){\n    tracks.each(function(track){\n      track = $H(track);\n      var data = track.values().first();\n      this.tracks.push($H({\n        ids:     track.keys().first(),\n        effect:  Effect.Morph,\n        options: { style: data }\n      }));\n    }.bind(this));\n    return this;\n  },\n  play: function(){\n    return new Effect.Parallel(\n      this.tracks.map(function(track){\n        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');\n        var elements = [$(ids) || $$(ids)].flatten();\n        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });\n      }).flatten(),\n      this.options\n    );\n  }\n});\n\nElement.CSS_PROPERTIES = $w(\n  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +\n  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +\n  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +\n  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +\n  'fontSize fontWeight height left letterSpacing lineHeight ' +\n  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+\n  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +\n  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +\n  'right textIndent top width wordSpacing zIndex');\n\nElement.CSS_LENGTH = /^(([\\+\\-]?[0-9\\.]+)(em|ex|px|in|cm|mm|pt|pc|\\%))|0$/;\n\nString.__parseStyleElement = document.createElement('div');\nString.prototype.parseStyle = function(){\n  var style, styleRules = $H();\n  if (Prototype.Browser.WebKit)\n    style = new Element('div',{style:this}).style;\n  else {\n    String.__parseStyleElement.innerHTML = '<div style=\"' + this + '\"></div>';\n    style = String.__parseStyleElement.childNodes[0].style;\n  }\n\n  Element.CSS_PROPERTIES.each(function(property){\n    if (style[property]) styleRules.set(property, style[property]);\n  });\n\n  if (Prototype.Browser.IE && this.include('opacity'))\n    styleRules.set('opacity', this.match(/opacity:\\s*((?:0|1)?(?:\\.\\d*)?)/)[1]);\n\n  return styleRules;\n};\n\nif (document.defaultView && document.defaultView.getComputedStyle) {\n  Element.getStyles = function(element) {\n    var css = document.defaultView.getComputedStyle($(element), null);\n    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {\n      styles[property] = css[property];\n      return styles;\n    });\n  };\n} else {\n  Element.getStyles = function(element) {\n    element = $(element);\n    var css = element.currentStyle, styles;\n    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {\n      results[property] = css[property];\n      return results;\n    });\n    if (!styles.opacity) styles.opacity = element.getOpacity();\n    return styles;\n  };\n}\n\nEffect.Methods = {\n  morph: function(element, style) {\n    element = $(element);\n    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));\n    return element;\n  },\n  visualEffect: function(element, effect, options) {\n    element = $(element);\n    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);\n    new Effect[klass](element, options);\n    return element;\n  },\n  highlight: function(element, options) {\n    element = $(element);\n    new Effect.Highlight(element, options);\n    return element;\n  }\n};\n\n$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+\n  'pulsate shake puff squish switchOff dropOut').each(\n  function(effect) {\n    Effect.Methods[effect] = function(element, options){\n      element = $(element);\n      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);\n      return element;\n    };\n  }\n);\n\n$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(\n  function(f) { Effect.Methods[f] = Element[f]; }\n);\n\nElement.addMethods(Effect.Methods);"
  },
  {
    "path": "example/rails2.3/public/javascripts/prototype.js",
    "content": "/*  Prototype JavaScript framework, version 1.6.0.3\n *  (c) 2005-2008 Sam Stephenson\n *\n *  Prototype is freely distributable under the terms of an MIT-style license.\n *  For details, see the Prototype web site: http://www.prototypejs.org/\n *\n *--------------------------------------------------------------------------*/\n\nvar Prototype = {\n  Version: '1.6.0.3',\n\n  Browser: {\n    IE:     !!(window.attachEvent &&\n      navigator.userAgent.indexOf('Opera') === -1),\n    Opera:  navigator.userAgent.indexOf('Opera') > -1,\n    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,\n    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&\n      navigator.userAgent.indexOf('KHTML') === -1,\n    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)\n  },\n\n  BrowserFeatures: {\n    XPath: !!document.evaluate,\n    SelectorsAPI: !!document.querySelector,\n    ElementExtensions: !!window.HTMLElement,\n    SpecificElementExtensions:\n      document.createElement('div')['__proto__'] &&\n      document.createElement('div')['__proto__'] !==\n        document.createElement('form')['__proto__']\n  },\n\n  ScriptFragment: '<script[^>]*>([\\\\S\\\\s]*?)<\\/script>',\n  JSONFilter: /^\\/\\*-secure-([\\s\\S]*)\\*\\/\\s*$/,\n\n  emptyFunction: function() { },\n  K: function(x) { return x }\n};\n\nif (Prototype.Browser.MobileSafari)\n  Prototype.BrowserFeatures.SpecificElementExtensions = false;\n\n\n/* Based on Alex Arnell's inheritance implementation. */\nvar Class = {\n  create: function() {\n    var parent = null, properties = $A(arguments);\n    if (Object.isFunction(properties[0]))\n      parent = properties.shift();\n\n    function klass() {\n      this.initialize.apply(this, arguments);\n    }\n\n    Object.extend(klass, Class.Methods);\n    klass.superclass = parent;\n    klass.subclasses = [];\n\n    if (parent) {\n      var subclass = function() { };\n      subclass.prototype = parent.prototype;\n      klass.prototype = new subclass;\n      parent.subclasses.push(klass);\n    }\n\n    for (var i = 0; i < properties.length; i++)\n      klass.addMethods(properties[i]);\n\n    if (!klass.prototype.initialize)\n      klass.prototype.initialize = Prototype.emptyFunction;\n\n    klass.prototype.constructor = klass;\n\n    return klass;\n  }\n};\n\nClass.Methods = {\n  addMethods: function(source) {\n    var ancestor   = this.superclass && this.superclass.prototype;\n    var properties = Object.keys(source);\n\n    if (!Object.keys({ toString: true }).length)\n      properties.push(\"toString\", \"valueOf\");\n\n    for (var i = 0, length = properties.length; i < length; i++) {\n      var property = properties[i], value = source[property];\n      if (ancestor && Object.isFunction(value) &&\n          value.argumentNames().first() == \"$super\") {\n        var method = value;\n        value = (function(m) {\n          return function() { return ancestor[m].apply(this, arguments) };\n        })(property).wrap(method);\n\n        value.valueOf = method.valueOf.bind(method);\n        value.toString = method.toString.bind(method);\n      }\n      this.prototype[property] = value;\n    }\n\n    return this;\n  }\n};\n\nvar Abstract = { };\n\nObject.extend = function(destination, source) {\n  for (var property in source)\n    destination[property] = source[property];\n  return destination;\n};\n\nObject.extend(Object, {\n  inspect: function(object) {\n    try {\n      if (Object.isUndefined(object)) return 'undefined';\n      if (object === null) return 'null';\n      return object.inspect ? object.inspect() : String(object);\n    } catch (e) {\n      if (e instanceof RangeError) return '...';\n      throw e;\n    }\n  },\n\n  toJSON: function(object) {\n    var type = typeof object;\n    switch (type) {\n      case 'undefined':\n      case 'function':\n      case 'unknown': return;\n      case 'boolean': return object.toString();\n    }\n\n    if (object === null) return 'null';\n    if (object.toJSON) return object.toJSON();\n    if (Object.isElement(object)) return;\n\n    var results = [];\n    for (var property in object) {\n      var value = Object.toJSON(object[property]);\n      if (!Object.isUndefined(value))\n        results.push(property.toJSON() + ': ' + value);\n    }\n\n    return '{' + results.join(', ') + '}';\n  },\n\n  toQueryString: function(object) {\n    return $H(object).toQueryString();\n  },\n\n  toHTML: function(object) {\n    return object && object.toHTML ? object.toHTML() : String.interpret(object);\n  },\n\n  keys: function(object) {\n    var keys = [];\n    for (var property in object)\n      keys.push(property);\n    return keys;\n  },\n\n  values: function(object) {\n    var values = [];\n    for (var property in object)\n      values.push(object[property]);\n    return values;\n  },\n\n  clone: function(object) {\n    return Object.extend({ }, object);\n  },\n\n  isElement: function(object) {\n    return !!(object && object.nodeType == 1);\n  },\n\n  isArray: function(object) {\n    return object != null && typeof object == \"object\" &&\n      'splice' in object && 'join' in object;\n  },\n\n  isHash: function(object) {\n    return object instanceof Hash;\n  },\n\n  isFunction: function(object) {\n    return typeof object == \"function\";\n  },\n\n  isString: function(object) {\n    return typeof object == \"string\";\n  },\n\n  isNumber: function(object) {\n    return typeof object == \"number\";\n  },\n\n  isUndefined: function(object) {\n    return typeof object == \"undefined\";\n  }\n});\n\nObject.extend(Function.prototype, {\n  argumentNames: function() {\n    var names = this.toString().match(/^[\\s\\(]*function[^(]*\\(([^\\)]*)\\)/)[1]\n      .replace(/\\s+/g, '').split(',');\n    return names.length == 1 && !names[0] ? [] : names;\n  },\n\n  bind: function() {\n    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;\n    var __method = this, args = $A(arguments), object = args.shift();\n    return function() {\n      return __method.apply(object, args.concat($A(arguments)));\n    }\n  },\n\n  bindAsEventListener: function() {\n    var __method = this, args = $A(arguments), object = args.shift();\n    return function(event) {\n      return __method.apply(object, [event || window.event].concat(args));\n    }\n  },\n\n  curry: function() {\n    if (!arguments.length) return this;\n    var __method = this, args = $A(arguments);\n    return function() {\n      return __method.apply(this, args.concat($A(arguments)));\n    }\n  },\n\n  delay: function() {\n    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;\n    return window.setTimeout(function() {\n      return __method.apply(__method, args);\n    }, timeout);\n  },\n\n  defer: function() {\n    var args = [0.01].concat($A(arguments));\n    return this.delay.apply(this, args);\n  },\n\n  wrap: function(wrapper) {\n    var __method = this;\n    return function() {\n      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));\n    }\n  },\n\n  methodize: function() {\n    if (this._methodized) return this._methodized;\n    var __method = this;\n    return this._methodized = function() {\n      return __method.apply(null, [this].concat($A(arguments)));\n    };\n  }\n});\n\nDate.prototype.toJSON = function() {\n  return '\"' + this.getUTCFullYear() + '-' +\n    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +\n    this.getUTCDate().toPaddedString(2) + 'T' +\n    this.getUTCHours().toPaddedString(2) + ':' +\n    this.getUTCMinutes().toPaddedString(2) + ':' +\n    this.getUTCSeconds().toPaddedString(2) + 'Z\"';\n};\n\nvar Try = {\n  these: function() {\n    var returnValue;\n\n    for (var i = 0, length = arguments.length; i < length; i++) {\n      var lambda = arguments[i];\n      try {\n        returnValue = lambda();\n        break;\n      } catch (e) { }\n    }\n\n    return returnValue;\n  }\n};\n\nRegExp.prototype.match = RegExp.prototype.test;\n\nRegExp.escape = function(str) {\n  return String(str).replace(/([.*+?^=!:${}()|[\\]\\/\\\\])/g, '\\\\$1');\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar PeriodicalExecuter = Class.create({\n  initialize: function(callback, frequency) {\n    this.callback = callback;\n    this.frequency = frequency;\n    this.currentlyExecuting = false;\n\n    this.registerCallback();\n  },\n\n  registerCallback: function() {\n    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);\n  },\n\n  execute: function() {\n    this.callback(this);\n  },\n\n  stop: function() {\n    if (!this.timer) return;\n    clearInterval(this.timer);\n    this.timer = null;\n  },\n\n  onTimerEvent: function() {\n    if (!this.currentlyExecuting) {\n      try {\n        this.currentlyExecuting = true;\n        this.execute();\n      } finally {\n        this.currentlyExecuting = false;\n      }\n    }\n  }\n});\nObject.extend(String, {\n  interpret: function(value) {\n    return value == null ? '' : String(value);\n  },\n  specialChar: {\n    '\\b': '\\\\b',\n    '\\t': '\\\\t',\n    '\\n': '\\\\n',\n    '\\f': '\\\\f',\n    '\\r': '\\\\r',\n    '\\\\': '\\\\\\\\'\n  }\n});\n\nObject.extend(String.prototype, {\n  gsub: function(pattern, replacement) {\n    var result = '', source = this, match;\n    replacement = arguments.callee.prepareReplacement(replacement);\n\n    while (source.length > 0) {\n      if (match = source.match(pattern)) {\n        result += source.slice(0, match.index);\n        result += String.interpret(replacement(match));\n        source  = source.slice(match.index + match[0].length);\n      } else {\n        result += source, source = '';\n      }\n    }\n    return result;\n  },\n\n  sub: function(pattern, replacement, count) {\n    replacement = this.gsub.prepareReplacement(replacement);\n    count = Object.isUndefined(count) ? 1 : count;\n\n    return this.gsub(pattern, function(match) {\n      if (--count < 0) return match[0];\n      return replacement(match);\n    });\n  },\n\n  scan: function(pattern, iterator) {\n    this.gsub(pattern, iterator);\n    return String(this);\n  },\n\n  truncate: function(length, truncation) {\n    length = length || 30;\n    truncation = Object.isUndefined(truncation) ? '...' : truncation;\n    return this.length > length ?\n      this.slice(0, length - truncation.length) + truncation : String(this);\n  },\n\n  strip: function() {\n    return this.replace(/^\\s+/, '').replace(/\\s+$/, '');\n  },\n\n  stripTags: function() {\n    return this.replace(/<\\/?[^>]+>/gi, '');\n  },\n\n  stripScripts: function() {\n    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');\n  },\n\n  extractScripts: function() {\n    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');\n    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');\n    return (this.match(matchAll) || []).map(function(scriptTag) {\n      return (scriptTag.match(matchOne) || ['', ''])[1];\n    });\n  },\n\n  evalScripts: function() {\n    return this.extractScripts().map(function(script) { return eval(script) });\n  },\n\n  escapeHTML: function() {\n    var self = arguments.callee;\n    self.text.data = this;\n    return self.div.innerHTML;\n  },\n\n  unescapeHTML: function() {\n    var div = new Element('div');\n    div.innerHTML = this.stripTags();\n    return div.childNodes[0] ? (div.childNodes.length > 1 ?\n      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :\n      div.childNodes[0].nodeValue) : '';\n  },\n\n  toQueryParams: function(separator) {\n    var match = this.strip().match(/([^?#]*)(#.*)?$/);\n    if (!match) return { };\n\n    return match[1].split(separator || '&').inject({ }, function(hash, pair) {\n      if ((pair = pair.split('='))[0]) {\n        var key = decodeURIComponent(pair.shift());\n        var value = pair.length > 1 ? pair.join('=') : pair[0];\n        if (value != undefined) value = decodeURIComponent(value);\n\n        if (key in hash) {\n          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];\n          hash[key].push(value);\n        }\n        else hash[key] = value;\n      }\n      return hash;\n    });\n  },\n\n  toArray: function() {\n    return this.split('');\n  },\n\n  succ: function() {\n    return this.slice(0, this.length - 1) +\n      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);\n  },\n\n  times: function(count) {\n    return count < 1 ? '' : new Array(count + 1).join(this);\n  },\n\n  camelize: function() {\n    var parts = this.split('-'), len = parts.length;\n    if (len == 1) return parts[0];\n\n    var camelized = this.charAt(0) == '-'\n      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)\n      : parts[0];\n\n    for (var i = 1; i < len; i++)\n      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);\n\n    return camelized;\n  },\n\n  capitalize: function() {\n    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();\n  },\n\n  underscore: function() {\n    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();\n  },\n\n  dasherize: function() {\n    return this.gsub(/_/,'-');\n  },\n\n  inspect: function(useDoubleQuotes) {\n    var escapedString = this.gsub(/[\\x00-\\x1f\\\\]/, function(match) {\n      var character = String.specialChar[match[0]];\n      return character ? character : '\\\\u00' + match[0].charCodeAt().toPaddedString(2, 16);\n    });\n    if (useDoubleQuotes) return '\"' + escapedString.replace(/\"/g, '\\\\\"') + '\"';\n    return \"'\" + escapedString.replace(/'/g, '\\\\\\'') + \"'\";\n  },\n\n  toJSON: function() {\n    return this.inspect(true);\n  },\n\n  unfilterJSON: function(filter) {\n    return this.sub(filter || Prototype.JSONFilter, '#{1}');\n  },\n\n  isJSON: function() {\n    var str = this;\n    if (str.blank()) return false;\n    str = this.replace(/\\\\./g, '@').replace(/\"[^\"\\\\\\n\\r]*\"/g, '');\n    return (/^[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]*$/).test(str);\n  },\n\n  evalJSON: function(sanitize) {\n    var json = this.unfilterJSON();\n    try {\n      if (!sanitize || json.isJSON()) return eval('(' + json + ')');\n    } catch (e) { }\n    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());\n  },\n\n  include: function(pattern) {\n    return this.indexOf(pattern) > -1;\n  },\n\n  startsWith: function(pattern) {\n    return this.indexOf(pattern) === 0;\n  },\n\n  endsWith: function(pattern) {\n    var d = this.length - pattern.length;\n    return d >= 0 && this.lastIndexOf(pattern) === d;\n  },\n\n  empty: function() {\n    return this == '';\n  },\n\n  blank: function() {\n    return /^\\s*$/.test(this);\n  },\n\n  interpolate: function(object, pattern) {\n    return new Template(this, pattern).evaluate(object);\n  }\n});\n\nif (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {\n  escapeHTML: function() {\n    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n  },\n  unescapeHTML: function() {\n    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');\n  }\n});\n\nString.prototype.gsub.prepareReplacement = function(replacement) {\n  if (Object.isFunction(replacement)) return replacement;\n  var template = new Template(replacement);\n  return function(match) { return template.evaluate(match) };\n};\n\nString.prototype.parseQuery = String.prototype.toQueryParams;\n\nObject.extend(String.prototype.escapeHTML, {\n  div:  document.createElement('div'),\n  text: document.createTextNode('')\n});\n\nString.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);\n\nvar Template = Class.create({\n  initialize: function(template, pattern) {\n    this.template = template.toString();\n    this.pattern = pattern || Template.Pattern;\n  },\n\n  evaluate: function(object) {\n    if (Object.isFunction(object.toTemplateReplacements))\n      object = object.toTemplateReplacements();\n\n    return this.template.gsub(this.pattern, function(match) {\n      if (object == null) return '';\n\n      var before = match[1] || '';\n      if (before == '\\\\') return match[2];\n\n      var ctx = object, expr = match[3];\n      var pattern = /^([^.[]+|\\[((?:.*?[^\\\\])?)\\])(\\.|\\[|$)/;\n      match = pattern.exec(expr);\n      if (match == null) return before;\n\n      while (match != null) {\n        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\\\\\]', ']') : match[1];\n        ctx = ctx[comp];\n        if (null == ctx || '' == match[3]) break;\n        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);\n        match = pattern.exec(expr);\n      }\n\n      return before + String.interpret(ctx);\n    });\n  }\n});\nTemplate.Pattern = /(^|.|\\r|\\n)(#\\{(.*?)\\})/;\n\nvar $break = { };\n\nvar Enumerable = {\n  each: function(iterator, context) {\n    var index = 0;\n    try {\n      this._each(function(value) {\n        iterator.call(context, value, index++);\n      });\n    } catch (e) {\n      if (e != $break) throw e;\n    }\n    return this;\n  },\n\n  eachSlice: function(number, iterator, context) {\n    var index = -number, slices = [], array = this.toArray();\n    if (number < 1) return array;\n    while ((index += number) < array.length)\n      slices.push(array.slice(index, index+number));\n    return slices.collect(iterator, context);\n  },\n\n  all: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result = true;\n    this.each(function(value, index) {\n      result = result && !!iterator.call(context, value, index);\n      if (!result) throw $break;\n    });\n    return result;\n  },\n\n  any: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result = false;\n    this.each(function(value, index) {\n      if (result = !!iterator.call(context, value, index))\n        throw $break;\n    });\n    return result;\n  },\n\n  collect: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var results = [];\n    this.each(function(value, index) {\n      results.push(iterator.call(context, value, index));\n    });\n    return results;\n  },\n\n  detect: function(iterator, context) {\n    var result;\n    this.each(function(value, index) {\n      if (iterator.call(context, value, index)) {\n        result = value;\n        throw $break;\n      }\n    });\n    return result;\n  },\n\n  findAll: function(iterator, context) {\n    var results = [];\n    this.each(function(value, index) {\n      if (iterator.call(context, value, index))\n        results.push(value);\n    });\n    return results;\n  },\n\n  grep: function(filter, iterator, context) {\n    iterator = iterator || Prototype.K;\n    var results = [];\n\n    if (Object.isString(filter))\n      filter = new RegExp(filter);\n\n    this.each(function(value, index) {\n      if (filter.match(value))\n        results.push(iterator.call(context, value, index));\n    });\n    return results;\n  },\n\n  include: function(object) {\n    if (Object.isFunction(this.indexOf))\n      if (this.indexOf(object) != -1) return true;\n\n    var found = false;\n    this.each(function(value) {\n      if (value == object) {\n        found = true;\n        throw $break;\n      }\n    });\n    return found;\n  },\n\n  inGroupsOf: function(number, fillWith) {\n    fillWith = Object.isUndefined(fillWith) ? null : fillWith;\n    return this.eachSlice(number, function(slice) {\n      while(slice.length < number) slice.push(fillWith);\n      return slice;\n    });\n  },\n\n  inject: function(memo, iterator, context) {\n    this.each(function(value, index) {\n      memo = iterator.call(context, memo, value, index);\n    });\n    return memo;\n  },\n\n  invoke: function(method) {\n    var args = $A(arguments).slice(1);\n    return this.map(function(value) {\n      return value[method].apply(value, args);\n    });\n  },\n\n  max: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result;\n    this.each(function(value, index) {\n      value = iterator.call(context, value, index);\n      if (result == null || value >= result)\n        result = value;\n    });\n    return result;\n  },\n\n  min: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result;\n    this.each(function(value, index) {\n      value = iterator.call(context, value, index);\n      if (result == null || value < result)\n        result = value;\n    });\n    return result;\n  },\n\n  partition: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var trues = [], falses = [];\n    this.each(function(value, index) {\n      (iterator.call(context, value, index) ?\n        trues : falses).push(value);\n    });\n    return [trues, falses];\n  },\n\n  pluck: function(property) {\n    var results = [];\n    this.each(function(value) {\n      results.push(value[property]);\n    });\n    return results;\n  },\n\n  reject: function(iterator, context) {\n    var results = [];\n    this.each(function(value, index) {\n      if (!iterator.call(context, value, index))\n        results.push(value);\n    });\n    return results;\n  },\n\n  sortBy: function(iterator, context) {\n    return this.map(function(value, index) {\n      return {\n        value: value,\n        criteria: iterator.call(context, value, index)\n      };\n    }).sort(function(left, right) {\n      var a = left.criteria, b = right.criteria;\n      return a < b ? -1 : a > b ? 1 : 0;\n    }).pluck('value');\n  },\n\n  toArray: function() {\n    return this.map();\n  },\n\n  zip: function() {\n    var iterator = Prototype.K, args = $A(arguments);\n    if (Object.isFunction(args.last()))\n      iterator = args.pop();\n\n    var collections = [this].concat(args).map($A);\n    return this.map(function(value, index) {\n      return iterator(collections.pluck(index));\n    });\n  },\n\n  size: function() {\n    return this.toArray().length;\n  },\n\n  inspect: function() {\n    return '#<Enumerable:' + this.toArray().inspect() + '>';\n  }\n};\n\nObject.extend(Enumerable, {\n  map:     Enumerable.collect,\n  find:    Enumerable.detect,\n  select:  Enumerable.findAll,\n  filter:  Enumerable.findAll,\n  member:  Enumerable.include,\n  entries: Enumerable.toArray,\n  every:   Enumerable.all,\n  some:    Enumerable.any\n});\nfunction $A(iterable) {\n  if (!iterable) return [];\n  if (iterable.toArray) return iterable.toArray();\n  var length = iterable.length || 0, results = new Array(length);\n  while (length--) results[length] = iterable[length];\n  return results;\n}\n\nif (Prototype.Browser.WebKit) {\n  $A = function(iterable) {\n    if (!iterable) return [];\n    // In Safari, only use the `toArray` method if it's not a NodeList.\n    // A NodeList is a function, has an function `item` property, and a numeric\n    // `length` property. Adapted from Google Doctype.\n    if (!(typeof iterable === 'function' && typeof iterable.length ===\n        'number' && typeof iterable.item === 'function') && iterable.toArray)\n      return iterable.toArray();\n    var length = iterable.length || 0, results = new Array(length);\n    while (length--) results[length] = iterable[length];\n    return results;\n  };\n}\n\nArray.from = $A;\n\nObject.extend(Array.prototype, Enumerable);\n\nif (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;\n\nObject.extend(Array.prototype, {\n  _each: function(iterator) {\n    for (var i = 0, length = this.length; i < length; i++)\n      iterator(this[i]);\n  },\n\n  clear: function() {\n    this.length = 0;\n    return this;\n  },\n\n  first: function() {\n    return this[0];\n  },\n\n  last: function() {\n    return this[this.length - 1];\n  },\n\n  compact: function() {\n    return this.select(function(value) {\n      return value != null;\n    });\n  },\n\n  flatten: function() {\n    return this.inject([], function(array, value) {\n      return array.concat(Object.isArray(value) ?\n        value.flatten() : [value]);\n    });\n  },\n\n  without: function() {\n    var values = $A(arguments);\n    return this.select(function(value) {\n      return !values.include(value);\n    });\n  },\n\n  reverse: function(inline) {\n    return (inline !== false ? this : this.toArray())._reverse();\n  },\n\n  reduce: function() {\n    return this.length > 1 ? this : this[0];\n  },\n\n  uniq: function(sorted) {\n    return this.inject([], function(array, value, index) {\n      if (0 == index || (sorted ? array.last() != value : !array.include(value)))\n        array.push(value);\n      return array;\n    });\n  },\n\n  intersect: function(array) {\n    return this.uniq().findAll(function(item) {\n      return array.detect(function(value) { return item === value });\n    });\n  },\n\n  clone: function() {\n    return [].concat(this);\n  },\n\n  size: function() {\n    return this.length;\n  },\n\n  inspect: function() {\n    return '[' + this.map(Object.inspect).join(', ') + ']';\n  },\n\n  toJSON: function() {\n    var results = [];\n    this.each(function(object) {\n      var value = Object.toJSON(object);\n      if (!Object.isUndefined(value)) results.push(value);\n    });\n    return '[' + results.join(', ') + ']';\n  }\n});\n\n// use native browser JS 1.6 implementation if available\nif (Object.isFunction(Array.prototype.forEach))\n  Array.prototype._each = Array.prototype.forEach;\n\nif (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {\n  i || (i = 0);\n  var length = this.length;\n  if (i < 0) i = length + i;\n  for (; i < length; i++)\n    if (this[i] === item) return i;\n  return -1;\n};\n\nif (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {\n  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;\n  var n = this.slice(0, i).reverse().indexOf(item);\n  return (n < 0) ? n : i - n - 1;\n};\n\nArray.prototype.toArray = Array.prototype.clone;\n\nfunction $w(string) {\n  if (!Object.isString(string)) return [];\n  string = string.strip();\n  return string ? string.split(/\\s+/) : [];\n}\n\nif (Prototype.Browser.Opera){\n  Array.prototype.concat = function() {\n    var array = [];\n    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);\n    for (var i = 0, length = arguments.length; i < length; i++) {\n      if (Object.isArray(arguments[i])) {\n        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)\n          array.push(arguments[i][j]);\n      } else {\n        array.push(arguments[i]);\n      }\n    }\n    return array;\n  };\n}\nObject.extend(Number.prototype, {\n  toColorPart: function() {\n    return this.toPaddedString(2, 16);\n  },\n\n  succ: function() {\n    return this + 1;\n  },\n\n  times: function(iterator, context) {\n    $R(0, this, true).each(iterator, context);\n    return this;\n  },\n\n  toPaddedString: function(length, radix) {\n    var string = this.toString(radix || 10);\n    return '0'.times(length - string.length) + string;\n  },\n\n  toJSON: function() {\n    return isFinite(this) ? this.toString() : 'null';\n  }\n});\n\n$w('abs round ceil floor').each(function(method){\n  Number.prototype[method] = Math[method].methodize();\n});\nfunction $H(object) {\n  return new Hash(object);\n};\n\nvar Hash = Class.create(Enumerable, (function() {\n\n  function toQueryPair(key, value) {\n    if (Object.isUndefined(value)) return key;\n    return key + '=' + encodeURIComponent(String.interpret(value));\n  }\n\n  return {\n    initialize: function(object) {\n      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);\n    },\n\n    _each: function(iterator) {\n      for (var key in this._object) {\n        var value = this._object[key], pair = [key, value];\n        pair.key = key;\n        pair.value = value;\n        iterator(pair);\n      }\n    },\n\n    set: function(key, value) {\n      return this._object[key] = value;\n    },\n\n    get: function(key) {\n      // simulating poorly supported hasOwnProperty\n      if (this._object[key] !== Object.prototype[key])\n        return this._object[key];\n    },\n\n    unset: function(key) {\n      var value = this._object[key];\n      delete this._object[key];\n      return value;\n    },\n\n    toObject: function() {\n      return Object.clone(this._object);\n    },\n\n    keys: function() {\n      return this.pluck('key');\n    },\n\n    values: function() {\n      return this.pluck('value');\n    },\n\n    index: function(value) {\n      var match = this.detect(function(pair) {\n        return pair.value === value;\n      });\n      return match && match.key;\n    },\n\n    merge: function(object) {\n      return this.clone().update(object);\n    },\n\n    update: function(object) {\n      return new Hash(object).inject(this, function(result, pair) {\n        result.set(pair.key, pair.value);\n        return result;\n      });\n    },\n\n    toQueryString: function() {\n      return this.inject([], function(results, pair) {\n        var key = encodeURIComponent(pair.key), values = pair.value;\n\n        if (values && typeof values == 'object') {\n          if (Object.isArray(values))\n            return results.concat(values.map(toQueryPair.curry(key)));\n        } else results.push(toQueryPair(key, values));\n        return results;\n      }).join('&');\n    },\n\n    inspect: function() {\n      return '#<Hash:{' + this.map(function(pair) {\n        return pair.map(Object.inspect).join(': ');\n      }).join(', ') + '}>';\n    },\n\n    toJSON: function() {\n      return Object.toJSON(this.toObject());\n    },\n\n    clone: function() {\n      return new Hash(this);\n    }\n  }\n})());\n\nHash.prototype.toTemplateReplacements = Hash.prototype.toObject;\nHash.from = $H;\nvar ObjectRange = Class.create(Enumerable, {\n  initialize: function(start, end, exclusive) {\n    this.start = start;\n    this.end = end;\n    this.exclusive = exclusive;\n  },\n\n  _each: function(iterator) {\n    var value = this.start;\n    while (this.include(value)) {\n      iterator(value);\n      value = value.succ();\n    }\n  },\n\n  include: function(value) {\n    if (value < this.start)\n      return false;\n    if (this.exclusive)\n      return value < this.end;\n    return value <= this.end;\n  }\n});\n\nvar $R = function(start, end, exclusive) {\n  return new ObjectRange(start, end, exclusive);\n};\n\nvar Ajax = {\n  getTransport: function() {\n    return Try.these(\n      function() {return new XMLHttpRequest()},\n      function() {return new ActiveXObject('Msxml2.XMLHTTP')},\n      function() {return new ActiveXObject('Microsoft.XMLHTTP')}\n    ) || false;\n  },\n\n  activeRequestCount: 0\n};\n\nAjax.Responders = {\n  responders: [],\n\n  _each: function(iterator) {\n    this.responders._each(iterator);\n  },\n\n  register: function(responder) {\n    if (!this.include(responder))\n      this.responders.push(responder);\n  },\n\n  unregister: function(responder) {\n    this.responders = this.responders.without(responder);\n  },\n\n  dispatch: function(callback, request, transport, json) {\n    this.each(function(responder) {\n      if (Object.isFunction(responder[callback])) {\n        try {\n          responder[callback].apply(responder, [request, transport, json]);\n        } catch (e) { }\n      }\n    });\n  }\n};\n\nObject.extend(Ajax.Responders, Enumerable);\n\nAjax.Responders.register({\n  onCreate:   function() { Ajax.activeRequestCount++ },\n  onComplete: function() { Ajax.activeRequestCount-- }\n});\n\nAjax.Base = Class.create({\n  initialize: function(options) {\n    this.options = {\n      method:       'post',\n      asynchronous: true,\n      contentType:  'application/x-www-form-urlencoded',\n      encoding:     'UTF-8',\n      parameters:   '',\n      evalJSON:     true,\n      evalJS:       true\n    };\n    Object.extend(this.options, options || { });\n\n    this.options.method = this.options.method.toLowerCase();\n\n    if (Object.isString(this.options.parameters))\n      this.options.parameters = this.options.parameters.toQueryParams();\n    else if (Object.isHash(this.options.parameters))\n      this.options.parameters = this.options.parameters.toObject();\n  }\n});\n\nAjax.Request = Class.create(Ajax.Base, {\n  _complete: false,\n\n  initialize: function($super, url, options) {\n    $super(options);\n    this.transport = Ajax.getTransport();\n    this.request(url);\n  },\n\n  request: function(url) {\n    this.url = url;\n    this.method = this.options.method;\n    var params = Object.clone(this.options.parameters);\n\n    if (!['get', 'post'].include(this.method)) {\n      // simulate other verbs over post\n      params['_method'] = this.method;\n      this.method = 'post';\n    }\n\n    this.parameters = params;\n\n    if (params = Object.toQueryString(params)) {\n      // when GET, append parameters to URL\n      if (this.method == 'get')\n        this.url += (this.url.include('?') ? '&' : '?') + params;\n      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))\n        params += '&_=';\n    }\n\n    try {\n      var response = new Ajax.Response(this);\n      if (this.options.onCreate) this.options.onCreate(response);\n      Ajax.Responders.dispatch('onCreate', this, response);\n\n      this.transport.open(this.method.toUpperCase(), this.url,\n        this.options.asynchronous);\n\n      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);\n\n      this.transport.onreadystatechange = this.onStateChange.bind(this);\n      this.setRequestHeaders();\n\n      this.body = this.method == 'post' ? (this.options.postBody || params) : null;\n      this.transport.send(this.body);\n\n      /* Force Firefox to handle ready state 4 for synchronous requests */\n      if (!this.options.asynchronous && this.transport.overrideMimeType)\n        this.onStateChange();\n\n    }\n    catch (e) {\n      this.dispatchException(e);\n    }\n  },\n\n  onStateChange: function() {\n    var readyState = this.transport.readyState;\n    if (readyState > 1 && !((readyState == 4) && this._complete))\n      this.respondToReadyState(this.transport.readyState);\n  },\n\n  setRequestHeaders: function() {\n    var headers = {\n      'X-Requested-With': 'XMLHttpRequest',\n      'X-Prototype-Version': Prototype.Version,\n      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'\n    };\n\n    if (this.method == 'post') {\n      headers['Content-type'] = this.options.contentType +\n        (this.options.encoding ? '; charset=' + this.options.encoding : '');\n\n      /* Force \"Connection: close\" for older Mozilla browsers to work\n       * around a bug where XMLHttpRequest sends an incorrect\n       * Content-length header. See Mozilla Bugzilla #246651.\n       */\n      if (this.transport.overrideMimeType &&\n          (navigator.userAgent.match(/Gecko\\/(\\d{4})/) || [0,2005])[1] < 2005)\n            headers['Connection'] = 'close';\n    }\n\n    // user-defined headers\n    if (typeof this.options.requestHeaders == 'object') {\n      var extras = this.options.requestHeaders;\n\n      if (Object.isFunction(extras.push))\n        for (var i = 0, length = extras.length; i < length; i += 2)\n          headers[extras[i]] = extras[i+1];\n      else\n        $H(extras).each(function(pair) { headers[pair.key] = pair.value });\n    }\n\n    for (var name in headers)\n      this.transport.setRequestHeader(name, headers[name]);\n  },\n\n  success: function() {\n    var status = this.getStatus();\n    return !status || (status >= 200 && status < 300);\n  },\n\n  getStatus: function() {\n    try {\n      return this.transport.status || 0;\n    } catch (e) { return 0 }\n  },\n\n  respondToReadyState: function(readyState) {\n    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);\n\n    if (state == 'Complete') {\n      try {\n        this._complete = true;\n        (this.options['on' + response.status]\n         || this.options['on' + (this.success() ? 'Success' : 'Failure')]\n         || Prototype.emptyFunction)(response, response.headerJSON);\n      } catch (e) {\n        this.dispatchException(e);\n      }\n\n      var contentType = response.getHeader('Content-type');\n      if (this.options.evalJS == 'force'\n          || (this.options.evalJS && this.isSameOrigin() && contentType\n          && contentType.match(/^\\s*(text|application)\\/(x-)?(java|ecma)script(;.*)?\\s*$/i)))\n        this.evalResponse();\n    }\n\n    try {\n      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);\n      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);\n    } catch (e) {\n      this.dispatchException(e);\n    }\n\n    if (state == 'Complete') {\n      // avoid memory leak in MSIE: clean up\n      this.transport.onreadystatechange = Prototype.emptyFunction;\n    }\n  },\n\n  isSameOrigin: function() {\n    var m = this.url.match(/^\\s*https?:\\/\\/[^\\/]*/);\n    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({\n      protocol: location.protocol,\n      domain: document.domain,\n      port: location.port ? ':' + location.port : ''\n    }));\n  },\n\n  getHeader: function(name) {\n    try {\n      return this.transport.getResponseHeader(name) || null;\n    } catch (e) { return null }\n  },\n\n  evalResponse: function() {\n    try {\n      return eval((this.transport.responseText || '').unfilterJSON());\n    } catch (e) {\n      this.dispatchException(e);\n    }\n  },\n\n  dispatchException: function(exception) {\n    (this.options.onException || Prototype.emptyFunction)(this, exception);\n    Ajax.Responders.dispatch('onException', this, exception);\n  }\n});\n\nAjax.Request.Events =\n  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];\n\nAjax.Response = Class.create({\n  initialize: function(request){\n    this.request = request;\n    var transport  = this.transport  = request.transport,\n        readyState = this.readyState = transport.readyState;\n\n    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {\n      this.status       = this.getStatus();\n      this.statusText   = this.getStatusText();\n      this.responseText = String.interpret(transport.responseText);\n      this.headerJSON   = this._getHeaderJSON();\n    }\n\n    if(readyState == 4) {\n      var xml = transport.responseXML;\n      this.responseXML  = Object.isUndefined(xml) ? null : xml;\n      this.responseJSON = this._getResponseJSON();\n    }\n  },\n\n  status:      0,\n  statusText: '',\n\n  getStatus: Ajax.Request.prototype.getStatus,\n\n  getStatusText: function() {\n    try {\n      return this.transport.statusText || '';\n    } catch (e) { return '' }\n  },\n\n  getHeader: Ajax.Request.prototype.getHeader,\n\n  getAllHeaders: function() {\n    try {\n      return this.getAllResponseHeaders();\n    } catch (e) { return null }\n  },\n\n  getResponseHeader: function(name) {\n    return this.transport.getResponseHeader(name);\n  },\n\n  getAllResponseHeaders: function() {\n    return this.transport.getAllResponseHeaders();\n  },\n\n  _getHeaderJSON: function() {\n    var json = this.getHeader('X-JSON');\n    if (!json) return null;\n    json = decodeURIComponent(escape(json));\n    try {\n      return json.evalJSON(this.request.options.sanitizeJSON ||\n        !this.request.isSameOrigin());\n    } catch (e) {\n      this.request.dispatchException(e);\n    }\n  },\n\n  _getResponseJSON: function() {\n    var options = this.request.options;\n    if (!options.evalJSON || (options.evalJSON != 'force' &&\n      !(this.getHeader('Content-type') || '').include('application/json')) ||\n        this.responseText.blank())\n          return null;\n    try {\n      return this.responseText.evalJSON(options.sanitizeJSON ||\n        !this.request.isSameOrigin());\n    } catch (e) {\n      this.request.dispatchException(e);\n    }\n  }\n});\n\nAjax.Updater = Class.create(Ajax.Request, {\n  initialize: function($super, container, url, options) {\n    this.container = {\n      success: (container.success || container),\n      failure: (container.failure || (container.success ? null : container))\n    };\n\n    options = Object.clone(options);\n    var onComplete = options.onComplete;\n    options.onComplete = (function(response, json) {\n      this.updateContent(response.responseText);\n      if (Object.isFunction(onComplete)) onComplete(response, json);\n    }).bind(this);\n\n    $super(url, options);\n  },\n\n  updateContent: function(responseText) {\n    var receiver = this.container[this.success() ? 'success' : 'failure'],\n        options = this.options;\n\n    if (!options.evalScripts) responseText = responseText.stripScripts();\n\n    if (receiver = $(receiver)) {\n      if (options.insertion) {\n        if (Object.isString(options.insertion)) {\n          var insertion = { }; insertion[options.insertion] = responseText;\n          receiver.insert(insertion);\n        }\n        else options.insertion(receiver, responseText);\n      }\n      else receiver.update(responseText);\n    }\n  }\n});\n\nAjax.PeriodicalUpdater = Class.create(Ajax.Base, {\n  initialize: function($super, container, url, options) {\n    $super(options);\n    this.onComplete = this.options.onComplete;\n\n    this.frequency = (this.options.frequency || 2);\n    this.decay = (this.options.decay || 1);\n\n    this.updater = { };\n    this.container = container;\n    this.url = url;\n\n    this.start();\n  },\n\n  start: function() {\n    this.options.onComplete = this.updateComplete.bind(this);\n    this.onTimerEvent();\n  },\n\n  stop: function() {\n    this.updater.options.onComplete = undefined;\n    clearTimeout(this.timer);\n    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);\n  },\n\n  updateComplete: function(response) {\n    if (this.options.decay) {\n      this.decay = (response.responseText == this.lastText ?\n        this.decay * this.options.decay : 1);\n\n      this.lastText = response.responseText;\n    }\n    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);\n  },\n\n  onTimerEvent: function() {\n    this.updater = new Ajax.Updater(this.container, this.url, this.options);\n  }\n});\nfunction $(element) {\n  if (arguments.length > 1) {\n    for (var i = 0, elements = [], length = arguments.length; i < length; i++)\n      elements.push($(arguments[i]));\n    return elements;\n  }\n  if (Object.isString(element))\n    element = document.getElementById(element);\n  return Element.extend(element);\n}\n\nif (Prototype.BrowserFeatures.XPath) {\n  document._getElementsByXPath = function(expression, parentElement) {\n    var results = [];\n    var query = document.evaluate(expression, $(parentElement) || document,\n      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n    for (var i = 0, length = query.snapshotLength; i < length; i++)\n      results.push(Element.extend(query.snapshotItem(i)));\n    return results;\n  };\n}\n\n/*--------------------------------------------------------------------------*/\n\nif (!window.Node) var Node = { };\n\nif (!Node.ELEMENT_NODE) {\n  // DOM level 2 ECMAScript Language Binding\n  Object.extend(Node, {\n    ELEMENT_NODE: 1,\n    ATTRIBUTE_NODE: 2,\n    TEXT_NODE: 3,\n    CDATA_SECTION_NODE: 4,\n    ENTITY_REFERENCE_NODE: 5,\n    ENTITY_NODE: 6,\n    PROCESSING_INSTRUCTION_NODE: 7,\n    COMMENT_NODE: 8,\n    DOCUMENT_NODE: 9,\n    DOCUMENT_TYPE_NODE: 10,\n    DOCUMENT_FRAGMENT_NODE: 11,\n    NOTATION_NODE: 12\n  });\n}\n\n(function() {\n  var element = this.Element;\n  this.Element = function(tagName, attributes) {\n    attributes = attributes || { };\n    tagName = tagName.toLowerCase();\n    var cache = Element.cache;\n    if (Prototype.Browser.IE && attributes.name) {\n      tagName = '<' + tagName + ' name=\"' + attributes.name + '\">';\n      delete attributes.name;\n      return Element.writeAttribute(document.createElement(tagName), attributes);\n    }\n    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));\n    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);\n  };\n  Object.extend(this.Element, element || { });\n  if (element) this.Element.prototype = element.prototype;\n}).call(window);\n\nElement.cache = { };\n\nElement.Methods = {\n  visible: function(element) {\n    return $(element).style.display != 'none';\n  },\n\n  toggle: function(element) {\n    element = $(element);\n    Element[Element.visible(element) ? 'hide' : 'show'](element);\n    return element;\n  },\n\n  hide: function(element) {\n    element = $(element);\n    element.style.display = 'none';\n    return element;\n  },\n\n  show: function(element) {\n    element = $(element);\n    element.style.display = '';\n    return element;\n  },\n\n  remove: function(element) {\n    element = $(element);\n    element.parentNode.removeChild(element);\n    return element;\n  },\n\n  update: function(element, content) {\n    element = $(element);\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) return element.update().insert(content);\n    content = Object.toHTML(content);\n    element.innerHTML = content.stripScripts();\n    content.evalScripts.bind(content).defer();\n    return element;\n  },\n\n  replace: function(element, content) {\n    element = $(element);\n    if (content && content.toElement) content = content.toElement();\n    else if (!Object.isElement(content)) {\n      content = Object.toHTML(content);\n      var range = element.ownerDocument.createRange();\n      range.selectNode(element);\n      content.evalScripts.bind(content).defer();\n      content = range.createContextualFragment(content.stripScripts());\n    }\n    element.parentNode.replaceChild(content, element);\n    return element;\n  },\n\n  insert: function(element, insertions) {\n    element = $(element);\n\n    if (Object.isString(insertions) || Object.isNumber(insertions) ||\n        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))\n          insertions = {bottom:insertions};\n\n    var content, insert, tagName, childNodes;\n\n    for (var position in insertions) {\n      content  = insertions[position];\n      position = position.toLowerCase();\n      insert = Element._insertionTranslations[position];\n\n      if (content && content.toElement) content = content.toElement();\n      if (Object.isElement(content)) {\n        insert(element, content);\n        continue;\n      }\n\n      content = Object.toHTML(content);\n\n      tagName = ((position == 'before' || position == 'after')\n        ? element.parentNode : element).tagName.toUpperCase();\n\n      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());\n\n      if (position == 'top' || position == 'after') childNodes.reverse();\n      childNodes.each(insert.curry(element));\n\n      content.evalScripts.bind(content).defer();\n    }\n\n    return element;\n  },\n\n  wrap: function(element, wrapper, attributes) {\n    element = $(element);\n    if (Object.isElement(wrapper))\n      $(wrapper).writeAttribute(attributes || { });\n    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);\n    else wrapper = new Element('div', wrapper);\n    if (element.parentNode)\n      element.parentNode.replaceChild(wrapper, element);\n    wrapper.appendChild(element);\n    return wrapper;\n  },\n\n  inspect: function(element) {\n    element = $(element);\n    var result = '<' + element.tagName.toLowerCase();\n    $H({'id': 'id', 'className': 'class'}).each(function(pair) {\n      var property = pair.first(), attribute = pair.last();\n      var value = (element[property] || '').toString();\n      if (value) result += ' ' + attribute + '=' + value.inspect(true);\n    });\n    return result + '>';\n  },\n\n  recursivelyCollect: function(element, property) {\n    element = $(element);\n    var elements = [];\n    while (element = element[property])\n      if (element.nodeType == 1)\n        elements.push(Element.extend(element));\n    return elements;\n  },\n\n  ancestors: function(element) {\n    return $(element).recursivelyCollect('parentNode');\n  },\n\n  descendants: function(element) {\n    return $(element).select(\"*\");\n  },\n\n  firstDescendant: function(element) {\n    element = $(element).firstChild;\n    while (element && element.nodeType != 1) element = element.nextSibling;\n    return $(element);\n  },\n\n  immediateDescendants: function(element) {\n    if (!(element = $(element).firstChild)) return [];\n    while (element && element.nodeType != 1) element = element.nextSibling;\n    if (element) return [element].concat($(element).nextSiblings());\n    return [];\n  },\n\n  previousSiblings: function(element) {\n    return $(element).recursivelyCollect('previousSibling');\n  },\n\n  nextSiblings: function(element) {\n    return $(element).recursivelyCollect('nextSibling');\n  },\n\n  siblings: function(element) {\n    element = $(element);\n    return element.previousSiblings().reverse().concat(element.nextSiblings());\n  },\n\n  match: function(element, selector) {\n    if (Object.isString(selector))\n      selector = new Selector(selector);\n    return selector.match($(element));\n  },\n\n  up: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(element.parentNode);\n    var ancestors = element.ancestors();\n    return Object.isNumber(expression) ? ancestors[expression] :\n      Selector.findElement(ancestors, expression, index);\n  },\n\n  down: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return element.firstDescendant();\n    return Object.isNumber(expression) ? element.descendants()[expression] :\n      Element.select(element, expression)[index || 0];\n  },\n\n  previous: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));\n    var previousSiblings = element.previousSiblings();\n    return Object.isNumber(expression) ? previousSiblings[expression] :\n      Selector.findElement(previousSiblings, expression, index);\n  },\n\n  next: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));\n    var nextSiblings = element.nextSiblings();\n    return Object.isNumber(expression) ? nextSiblings[expression] :\n      Selector.findElement(nextSiblings, expression, index);\n  },\n\n  select: function() {\n    var args = $A(arguments), element = $(args.shift());\n    return Selector.findChildElements(element, args);\n  },\n\n  adjacent: function() {\n    var args = $A(arguments), element = $(args.shift());\n    return Selector.findChildElements(element.parentNode, args).without(element);\n  },\n\n  identify: function(element) {\n    element = $(element);\n    var id = element.readAttribute('id'), self = arguments.callee;\n    if (id) return id;\n    do { id = 'anonymous_element_' + self.counter++ } while ($(id));\n    element.writeAttribute('id', id);\n    return id;\n  },\n\n  readAttribute: function(element, name) {\n    element = $(element);\n    if (Prototype.Browser.IE) {\n      var t = Element._attributeTranslations.read;\n      if (t.values[name]) return t.values[name](element, name);\n      if (t.names[name]) name = t.names[name];\n      if (name.include(':')) {\n        return (!element.attributes || !element.attributes[name]) ? null :\n         element.attributes[name].value;\n      }\n    }\n    return element.getAttribute(name);\n  },\n\n  writeAttribute: function(element, name, value) {\n    element = $(element);\n    var attributes = { }, t = Element._attributeTranslations.write;\n\n    if (typeof name == 'object') attributes = name;\n    else attributes[name] = Object.isUndefined(value) ? true : value;\n\n    for (var attr in attributes) {\n      name = t.names[attr] || attr;\n      value = attributes[attr];\n      if (t.values[attr]) name = t.values[attr](element, value);\n      if (value === false || value === null)\n        element.removeAttribute(name);\n      else if (value === true)\n        element.setAttribute(name, name);\n      else element.setAttribute(name, value);\n    }\n    return element;\n  },\n\n  getHeight: function(element) {\n    return $(element).getDimensions().height;\n  },\n\n  getWidth: function(element) {\n    return $(element).getDimensions().width;\n  },\n\n  classNames: function(element) {\n    return new Element.ClassNames(element);\n  },\n\n  hasClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    var elementClassName = element.className;\n    return (elementClassName.length > 0 && (elementClassName == className ||\n      new RegExp(\"(^|\\\\s)\" + className + \"(\\\\s|$)\").test(elementClassName)));\n  },\n\n  addClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    if (!element.hasClassName(className))\n      element.className += (element.className ? ' ' : '') + className;\n    return element;\n  },\n\n  removeClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    element.className = element.className.replace(\n      new RegExp(\"(^|\\\\s+)\" + className + \"(\\\\s+|$)\"), ' ').strip();\n    return element;\n  },\n\n  toggleClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    return element[element.hasClassName(className) ?\n      'removeClassName' : 'addClassName'](className);\n  },\n\n  // removes whitespace-only text node children\n  cleanWhitespace: function(element) {\n    element = $(element);\n    var node = element.firstChild;\n    while (node) {\n      var nextNode = node.nextSibling;\n      if (node.nodeType == 3 && !/\\S/.test(node.nodeValue))\n        element.removeChild(node);\n      node = nextNode;\n    }\n    return element;\n  },\n\n  empty: function(element) {\n    return $(element).innerHTML.blank();\n  },\n\n  descendantOf: function(element, ancestor) {\n    element = $(element), ancestor = $(ancestor);\n\n    if (element.compareDocumentPosition)\n      return (element.compareDocumentPosition(ancestor) & 8) === 8;\n\n    if (ancestor.contains)\n      return ancestor.contains(element) && ancestor !== element;\n\n    while (element = element.parentNode)\n      if (element == ancestor) return true;\n\n    return false;\n  },\n\n  scrollTo: function(element) {\n    element = $(element);\n    var pos = element.cumulativeOffset();\n    window.scrollTo(pos[0], pos[1]);\n    return element;\n  },\n\n  getStyle: function(element, style) {\n    element = $(element);\n    style = style == 'float' ? 'cssFloat' : style.camelize();\n    var value = element.style[style];\n    if (!value || value == 'auto') {\n      var css = document.defaultView.getComputedStyle(element, null);\n      value = css ? css[style] : null;\n    }\n    if (style == 'opacity') return value ? parseFloat(value) : 1.0;\n    return value == 'auto' ? null : value;\n  },\n\n  getOpacity: function(element) {\n    return $(element).getStyle('opacity');\n  },\n\n  setStyle: function(element, styles) {\n    element = $(element);\n    var elementStyle = element.style, match;\n    if (Object.isString(styles)) {\n      element.style.cssText += ';' + styles;\n      return styles.include('opacity') ?\n        element.setOpacity(styles.match(/opacity:\\s*(\\d?\\.?\\d*)/)[1]) : element;\n    }\n    for (var property in styles)\n      if (property == 'opacity') element.setOpacity(styles[property]);\n      else\n        elementStyle[(property == 'float' || property == 'cssFloat') ?\n          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :\n            property] = styles[property];\n\n    return element;\n  },\n\n  setOpacity: function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1 || value === '') ? '' :\n      (value < 0.00001) ? 0 : value;\n    return element;\n  },\n\n  getDimensions: function(element) {\n    element = $(element);\n    var display = element.getStyle('display');\n    if (display != 'none' && display != null) // Safari bug\n      return {width: element.offsetWidth, height: element.offsetHeight};\n\n    // All *Width and *Height properties give 0 on elements with display none,\n    // so enable the element temporarily\n    var els = element.style;\n    var originalVisibility = els.visibility;\n    var originalPosition = els.position;\n    var originalDisplay = els.display;\n    els.visibility = 'hidden';\n    els.position = 'absolute';\n    els.display = 'block';\n    var originalWidth = element.clientWidth;\n    var originalHeight = element.clientHeight;\n    els.display = originalDisplay;\n    els.position = originalPosition;\n    els.visibility = originalVisibility;\n    return {width: originalWidth, height: originalHeight};\n  },\n\n  makePositioned: function(element) {\n    element = $(element);\n    var pos = Element.getStyle(element, 'position');\n    if (pos == 'static' || !pos) {\n      element._madePositioned = true;\n      element.style.position = 'relative';\n      // Opera returns the offset relative to the positioning context, when an\n      // element is position relative but top and left have not been defined\n      if (Prototype.Browser.Opera) {\n        element.style.top = 0;\n        element.style.left = 0;\n      }\n    }\n    return element;\n  },\n\n  undoPositioned: function(element) {\n    element = $(element);\n    if (element._madePositioned) {\n      element._madePositioned = undefined;\n      element.style.position =\n        element.style.top =\n        element.style.left =\n        element.style.bottom =\n        element.style.right = '';\n    }\n    return element;\n  },\n\n  makeClipping: function(element) {\n    element = $(element);\n    if (element._overflow) return element;\n    element._overflow = Element.getStyle(element, 'overflow') || 'auto';\n    if (element._overflow !== 'hidden')\n      element.style.overflow = 'hidden';\n    return element;\n  },\n\n  undoClipping: function(element) {\n    element = $(element);\n    if (!element._overflow) return element;\n    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;\n    element._overflow = null;\n    return element;\n  },\n\n  cumulativeOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      element = element.offsetParent;\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  positionedOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      element = element.offsetParent;\n      if (element) {\n        if (element.tagName.toUpperCase() == 'BODY') break;\n        var p = Element.getStyle(element, 'position');\n        if (p !== 'static') break;\n      }\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  absolutize: function(element) {\n    element = $(element);\n    if (element.getStyle('position') == 'absolute') return element;\n    // Position.prepare(); // To be done manually by Scripty when it needs it.\n\n    var offsets = element.positionedOffset();\n    var top     = offsets[1];\n    var left    = offsets[0];\n    var width   = element.clientWidth;\n    var height  = element.clientHeight;\n\n    element._originalLeft   = left - parseFloat(element.style.left  || 0);\n    element._originalTop    = top  - parseFloat(element.style.top || 0);\n    element._originalWidth  = element.style.width;\n    element._originalHeight = element.style.height;\n\n    element.style.position = 'absolute';\n    element.style.top    = top + 'px';\n    element.style.left   = left + 'px';\n    element.style.width  = width + 'px';\n    element.style.height = height + 'px';\n    return element;\n  },\n\n  relativize: function(element) {\n    element = $(element);\n    if (element.getStyle('position') == 'relative') return element;\n    // Position.prepare(); // To be done manually by Scripty when it needs it.\n\n    element.style.position = 'relative';\n    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);\n    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);\n\n    element.style.top    = top + 'px';\n    element.style.left   = left + 'px';\n    element.style.height = element._originalHeight;\n    element.style.width  = element._originalWidth;\n    return element;\n  },\n\n  cumulativeScrollOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.scrollTop  || 0;\n      valueL += element.scrollLeft || 0;\n      element = element.parentNode;\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  getOffsetParent: function(element) {\n    if (element.offsetParent) return $(element.offsetParent);\n    if (element == document.body) return $(element);\n\n    while ((element = element.parentNode) && element != document.body)\n      if (Element.getStyle(element, 'position') != 'static')\n        return $(element);\n\n    return $(document.body);\n  },\n\n  viewportOffset: function(forElement) {\n    var valueT = 0, valueL = 0;\n\n    var element = forElement;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n\n      // Safari fix\n      if (element.offsetParent == document.body &&\n        Element.getStyle(element, 'position') == 'absolute') break;\n\n    } while (element = element.offsetParent);\n\n    element = forElement;\n    do {\n      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {\n        valueT -= element.scrollTop  || 0;\n        valueL -= element.scrollLeft || 0;\n      }\n    } while (element = element.parentNode);\n\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  clonePosition: function(element, source) {\n    var options = Object.extend({\n      setLeft:    true,\n      setTop:     true,\n      setWidth:   true,\n      setHeight:  true,\n      offsetTop:  0,\n      offsetLeft: 0\n    }, arguments[2] || { });\n\n    // find page position of source\n    source = $(source);\n    var p = source.viewportOffset();\n\n    // find coordinate system to use\n    element = $(element);\n    var delta = [0, 0];\n    var parent = null;\n    // delta [0,0] will do fine with position: fixed elements,\n    // position:absolute needs offsetParent deltas\n    if (Element.getStyle(element, 'position') == 'absolute') {\n      parent = element.getOffsetParent();\n      delta = parent.viewportOffset();\n    }\n\n    // correct by body offsets (fixes Safari)\n    if (parent == document.body) {\n      delta[0] -= document.body.offsetLeft;\n      delta[1] -= document.body.offsetTop;\n    }\n\n    // set position\n    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';\n    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';\n    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';\n    if (options.setHeight) element.style.height = source.offsetHeight + 'px';\n    return element;\n  }\n};\n\nElement.Methods.identify.counter = 1;\n\nObject.extend(Element.Methods, {\n  getElementsBySelector: Element.Methods.select,\n  childElements: Element.Methods.immediateDescendants\n});\n\nElement._attributeTranslations = {\n  write: {\n    names: {\n      className: 'class',\n      htmlFor:   'for'\n    },\n    values: { }\n  }\n};\n\nif (Prototype.Browser.Opera) {\n  Element.Methods.getStyle = Element.Methods.getStyle.wrap(\n    function(proceed, element, style) {\n      switch (style) {\n        case 'left': case 'top': case 'right': case 'bottom':\n          if (proceed(element, 'position') === 'static') return null;\n        case 'height': case 'width':\n          // returns '0px' for hidden elements; we want it to return null\n          if (!Element.visible(element)) return null;\n\n          // returns the border-box dimensions rather than the content-box\n          // dimensions, so we subtract padding and borders from the value\n          var dim = parseInt(proceed(element, style), 10);\n\n          if (dim !== element['offset' + style.capitalize()])\n            return dim + 'px';\n\n          var properties;\n          if (style === 'height') {\n            properties = ['border-top-width', 'padding-top',\n             'padding-bottom', 'border-bottom-width'];\n          }\n          else {\n            properties = ['border-left-width', 'padding-left',\n             'padding-right', 'border-right-width'];\n          }\n          return properties.inject(dim, function(memo, property) {\n            var val = proceed(element, property);\n            return val === null ? memo : memo - parseInt(val, 10);\n          }) + 'px';\n        default: return proceed(element, style);\n      }\n    }\n  );\n\n  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(\n    function(proceed, element, attribute) {\n      if (attribute === 'title') return element.title;\n      return proceed(element, attribute);\n    }\n  );\n}\n\nelse if (Prototype.Browser.IE) {\n  // IE doesn't report offsets correctly for static elements, so we change them\n  // to \"relative\" to get the values, then change them back.\n  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(\n    function(proceed, element) {\n      element = $(element);\n      // IE throws an error if element is not in document\n      try { element.offsetParent }\n      catch(e) { return $(document.body) }\n      var position = element.getStyle('position');\n      if (position !== 'static') return proceed(element);\n      element.setStyle({ position: 'relative' });\n      var value = proceed(element);\n      element.setStyle({ position: position });\n      return value;\n    }\n  );\n\n  $w('positionedOffset viewportOffset').each(function(method) {\n    Element.Methods[method] = Element.Methods[method].wrap(\n      function(proceed, element) {\n        element = $(element);\n        try { element.offsetParent }\n        catch(e) { return Element._returnOffset(0,0) }\n        var position = element.getStyle('position');\n        if (position !== 'static') return proceed(element);\n        // Trigger hasLayout on the offset parent so that IE6 reports\n        // accurate offsetTop and offsetLeft values for position: fixed.\n        var offsetParent = element.getOffsetParent();\n        if (offsetParent && offsetParent.getStyle('position') === 'fixed')\n          offsetParent.setStyle({ zoom: 1 });\n        element.setStyle({ position: 'relative' });\n        var value = proceed(element);\n        element.setStyle({ position: position });\n        return value;\n      }\n    );\n  });\n\n  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(\n    function(proceed, element) {\n      try { element.offsetParent }\n      catch(e) { return Element._returnOffset(0,0) }\n      return proceed(element);\n    }\n  );\n\n  Element.Methods.getStyle = function(element, style) {\n    element = $(element);\n    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();\n    var value = element.style[style];\n    if (!value && element.currentStyle) value = element.currentStyle[style];\n\n    if (style == 'opacity') {\n      if (value = (element.getStyle('filter') || '').match(/alpha\\(opacity=(.*)\\)/))\n        if (value[1]) return parseFloat(value[1]) / 100;\n      return 1.0;\n    }\n\n    if (value == 'auto') {\n      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))\n        return element['offset' + style.capitalize()] + 'px';\n      return null;\n    }\n    return value;\n  };\n\n  Element.Methods.setOpacity = function(element, value) {\n    function stripAlpha(filter){\n      return filter.replace(/alpha\\([^\\)]*\\)/gi,'');\n    }\n    element = $(element);\n    var currentStyle = element.currentStyle;\n    if ((currentStyle && !currentStyle.hasLayout) ||\n      (!currentStyle && element.style.zoom == 'normal'))\n        element.style.zoom = 1;\n\n    var filter = element.getStyle('filter'), style = element.style;\n    if (value == 1 || value === '') {\n      (filter = stripAlpha(filter)) ?\n        style.filter = filter : style.removeAttribute('filter');\n      return element;\n    } else if (value < 0.00001) value = 0;\n    style.filter = stripAlpha(filter) +\n      'alpha(opacity=' + (value * 100) + ')';\n    return element;\n  };\n\n  Element._attributeTranslations = {\n    read: {\n      names: {\n        'class': 'className',\n        'for':   'htmlFor'\n      },\n      values: {\n        _getAttr: function(element, attribute) {\n          return element.getAttribute(attribute, 2);\n        },\n        _getAttrNode: function(element, attribute) {\n          var node = element.getAttributeNode(attribute);\n          return node ? node.value : \"\";\n        },\n        _getEv: function(element, attribute) {\n          attribute = element.getAttribute(attribute);\n          return attribute ? attribute.toString().slice(23, -2) : null;\n        },\n        _flag: function(element, attribute) {\n          return $(element).hasAttribute(attribute) ? attribute : null;\n        },\n        style: function(element) {\n          return element.style.cssText.toLowerCase();\n        },\n        title: function(element) {\n          return element.title;\n        }\n      }\n    }\n  };\n\n  Element._attributeTranslations.write = {\n    names: Object.extend({\n      cellpadding: 'cellPadding',\n      cellspacing: 'cellSpacing'\n    }, Element._attributeTranslations.read.names),\n    values: {\n      checked: function(element, value) {\n        element.checked = !!value;\n      },\n\n      style: function(element, value) {\n        element.style.cssText = value ? value : '';\n      }\n    }\n  };\n\n  Element._attributeTranslations.has = {};\n\n  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +\n      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {\n    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;\n    Element._attributeTranslations.has[attr.toLowerCase()] = attr;\n  });\n\n  (function(v) {\n    Object.extend(v, {\n      href:        v._getAttr,\n      src:         v._getAttr,\n      type:        v._getAttr,\n      action:      v._getAttrNode,\n      disabled:    v._flag,\n      checked:     v._flag,\n      readonly:    v._flag,\n      multiple:    v._flag,\n      onload:      v._getEv,\n      onunload:    v._getEv,\n      onclick:     v._getEv,\n      ondblclick:  v._getEv,\n      onmousedown: v._getEv,\n      onmouseup:   v._getEv,\n      onmouseover: v._getEv,\n      onmousemove: v._getEv,\n      onmouseout:  v._getEv,\n      onfocus:     v._getEv,\n      onblur:      v._getEv,\n      onkeypress:  v._getEv,\n      onkeydown:   v._getEv,\n      onkeyup:     v._getEv,\n      onsubmit:    v._getEv,\n      onreset:     v._getEv,\n      onselect:    v._getEv,\n      onchange:    v._getEv\n    });\n  })(Element._attributeTranslations.read.values);\n}\n\nelse if (Prototype.Browser.Gecko && /rv:1\\.8\\.0/.test(navigator.userAgent)) {\n  Element.Methods.setOpacity = function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1) ? 0.999999 :\n      (value === '') ? '' : (value < 0.00001) ? 0 : value;\n    return element;\n  };\n}\n\nelse if (Prototype.Browser.WebKit) {\n  Element.Methods.setOpacity = function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1 || value === '') ? '' :\n      (value < 0.00001) ? 0 : value;\n\n    if (value == 1)\n      if(element.tagName.toUpperCase() == 'IMG' && element.width) {\n        element.width++; element.width--;\n      } else try {\n        var n = document.createTextNode(' ');\n        element.appendChild(n);\n        element.removeChild(n);\n      } catch (e) { }\n\n    return element;\n  };\n\n  // Safari returns margins on body which is incorrect if the child is absolutely\n  // positioned.  For performance reasons, redefine Element#cumulativeOffset for\n  // KHTML/WebKit only.\n  Element.Methods.cumulativeOffset = function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      if (element.offsetParent == document.body)\n        if (Element.getStyle(element, 'position') == 'absolute') break;\n\n      element = element.offsetParent;\n    } while (element);\n\n    return Element._returnOffset(valueL, valueT);\n  };\n}\n\nif (Prototype.Browser.IE || Prototype.Browser.Opera) {\n  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements\n  Element.Methods.update = function(element, content) {\n    element = $(element);\n\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) return element.update().insert(content);\n\n    content = Object.toHTML(content);\n    var tagName = element.tagName.toUpperCase();\n\n    if (tagName in Element._insertionTranslations.tags) {\n      $A(element.childNodes).each(function(node) { element.removeChild(node) });\n      Element._getContentFromAnonymousElement(tagName, content.stripScripts())\n        .each(function(node) { element.appendChild(node) });\n    }\n    else element.innerHTML = content.stripScripts();\n\n    content.evalScripts.bind(content).defer();\n    return element;\n  };\n}\n\nif ('outerHTML' in document.createElement('div')) {\n  Element.Methods.replace = function(element, content) {\n    element = $(element);\n\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) {\n      element.parentNode.replaceChild(content, element);\n      return element;\n    }\n\n    content = Object.toHTML(content);\n    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();\n\n    if (Element._insertionTranslations.tags[tagName]) {\n      var nextSibling = element.next();\n      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());\n      parent.removeChild(element);\n      if (nextSibling)\n        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });\n      else\n        fragments.each(function(node) { parent.appendChild(node) });\n    }\n    else element.outerHTML = content.stripScripts();\n\n    content.evalScripts.bind(content).defer();\n    return element;\n  };\n}\n\nElement._returnOffset = function(l, t) {\n  var result = [l, t];\n  result.left = l;\n  result.top = t;\n  return result;\n};\n\nElement._getContentFromAnonymousElement = function(tagName, html) {\n  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];\n  if (t) {\n    div.innerHTML = t[0] + html + t[1];\n    t[2].times(function() { div = div.firstChild });\n  } else div.innerHTML = html;\n  return $A(div.childNodes);\n};\n\nElement._insertionTranslations = {\n  before: function(element, node) {\n    element.parentNode.insertBefore(node, element);\n  },\n  top: function(element, node) {\n    element.insertBefore(node, element.firstChild);\n  },\n  bottom: function(element, node) {\n    element.appendChild(node);\n  },\n  after: function(element, node) {\n    element.parentNode.insertBefore(node, element.nextSibling);\n  },\n  tags: {\n    TABLE:  ['<table>',                '</table>',                   1],\n    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],\n    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],\n    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],\n    SELECT: ['<select>',               '</select>',                  1]\n  }\n};\n\n(function() {\n  Object.extend(this.tags, {\n    THEAD: this.tags.TBODY,\n    TFOOT: this.tags.TBODY,\n    TH:    this.tags.TD\n  });\n}).call(Element._insertionTranslations);\n\nElement.Methods.Simulated = {\n  hasAttribute: function(element, attribute) {\n    attribute = Element._attributeTranslations.has[attribute] || attribute;\n    var node = $(element).getAttributeNode(attribute);\n    return !!(node && node.specified);\n  }\n};\n\nElement.Methods.ByTag = { };\n\nObject.extend(Element, Element.Methods);\n\nif (!Prototype.BrowserFeatures.ElementExtensions &&\n    document.createElement('div')['__proto__']) {\n  window.HTMLElement = { };\n  window.HTMLElement.prototype = document.createElement('div')['__proto__'];\n  Prototype.BrowserFeatures.ElementExtensions = true;\n}\n\nElement.extend = (function() {\n  if (Prototype.BrowserFeatures.SpecificElementExtensions)\n    return Prototype.K;\n\n  var Methods = { }, ByTag = Element.Methods.ByTag;\n\n  var extend = Object.extend(function(element) {\n    if (!element || element._extendedByPrototype ||\n        element.nodeType != 1 || element == window) return element;\n\n    var methods = Object.clone(Methods),\n      tagName = element.tagName.toUpperCase(), property, value;\n\n    // extend methods for specific tags\n    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);\n\n    for (property in methods) {\n      value = methods[property];\n      if (Object.isFunction(value) && !(property in element))\n        element[property] = value.methodize();\n    }\n\n    element._extendedByPrototype = Prototype.emptyFunction;\n    return element;\n\n  }, {\n    refresh: function() {\n      // extend methods for all tags (Safari doesn't need this)\n      if (!Prototype.BrowserFeatures.ElementExtensions) {\n        Object.extend(Methods, Element.Methods);\n        Object.extend(Methods, Element.Methods.Simulated);\n      }\n    }\n  });\n\n  extend.refresh();\n  return extend;\n})();\n\nElement.hasAttribute = function(element, attribute) {\n  if (element.hasAttribute) return element.hasAttribute(attribute);\n  return Element.Methods.Simulated.hasAttribute(element, attribute);\n};\n\nElement.addMethods = function(methods) {\n  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;\n\n  if (!methods) {\n    Object.extend(Form, Form.Methods);\n    Object.extend(Form.Element, Form.Element.Methods);\n    Object.extend(Element.Methods.ByTag, {\n      \"FORM\":     Object.clone(Form.Methods),\n      \"INPUT\":    Object.clone(Form.Element.Methods),\n      \"SELECT\":   Object.clone(Form.Element.Methods),\n      \"TEXTAREA\": Object.clone(Form.Element.Methods)\n    });\n  }\n\n  if (arguments.length == 2) {\n    var tagName = methods;\n    methods = arguments[1];\n  }\n\n  if (!tagName) Object.extend(Element.Methods, methods || { });\n  else {\n    if (Object.isArray(tagName)) tagName.each(extend);\n    else extend(tagName);\n  }\n\n  function extend(tagName) {\n    tagName = tagName.toUpperCase();\n    if (!Element.Methods.ByTag[tagName])\n      Element.Methods.ByTag[tagName] = { };\n    Object.extend(Element.Methods.ByTag[tagName], methods);\n  }\n\n  function copy(methods, destination, onlyIfAbsent) {\n    onlyIfAbsent = onlyIfAbsent || false;\n    for (var property in methods) {\n      var value = methods[property];\n      if (!Object.isFunction(value)) continue;\n      if (!onlyIfAbsent || !(property in destination))\n        destination[property] = value.methodize();\n    }\n  }\n\n  function findDOMClass(tagName) {\n    var klass;\n    var trans = {\n      \"OPTGROUP\": \"OptGroup\", \"TEXTAREA\": \"TextArea\", \"P\": \"Paragraph\",\n      \"FIELDSET\": \"FieldSet\", \"UL\": \"UList\", \"OL\": \"OList\", \"DL\": \"DList\",\n      \"DIR\": \"Directory\", \"H1\": \"Heading\", \"H2\": \"Heading\", \"H3\": \"Heading\",\n      \"H4\": \"Heading\", \"H5\": \"Heading\", \"H6\": \"Heading\", \"Q\": \"Quote\",\n      \"INS\": \"Mod\", \"DEL\": \"Mod\", \"A\": \"Anchor\", \"IMG\": \"Image\", \"CAPTION\":\n      \"TableCaption\", \"COL\": \"TableCol\", \"COLGROUP\": \"TableCol\", \"THEAD\":\n      \"TableSection\", \"TFOOT\": \"TableSection\", \"TBODY\": \"TableSection\", \"TR\":\n      \"TableRow\", \"TH\": \"TableCell\", \"TD\": \"TableCell\", \"FRAMESET\":\n      \"FrameSet\", \"IFRAME\": \"IFrame\"\n    };\n    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';\n    if (window[klass]) return window[klass];\n    klass = 'HTML' + tagName + 'Element';\n    if (window[klass]) return window[klass];\n    klass = 'HTML' + tagName.capitalize() + 'Element';\n    if (window[klass]) return window[klass];\n\n    window[klass] = { };\n    window[klass].prototype = document.createElement(tagName)['__proto__'];\n    return window[klass];\n  }\n\n  if (F.ElementExtensions) {\n    copy(Element.Methods, HTMLElement.prototype);\n    copy(Element.Methods.Simulated, HTMLElement.prototype, true);\n  }\n\n  if (F.SpecificElementExtensions) {\n    for (var tag in Element.Methods.ByTag) {\n      var klass = findDOMClass(tag);\n      if (Object.isUndefined(klass)) continue;\n      copy(T[tag], klass.prototype);\n    }\n  }\n\n  Object.extend(Element, Element.Methods);\n  delete Element.ByTag;\n\n  if (Element.extend.refresh) Element.extend.refresh();\n  Element.cache = { };\n};\n\ndocument.viewport = {\n  getDimensions: function() {\n    var dimensions = { }, B = Prototype.Browser;\n    $w('width height').each(function(d) {\n      var D = d.capitalize();\n      if (B.WebKit && !document.evaluate) {\n        // Safari <3.0 needs self.innerWidth/Height\n        dimensions[d] = self['inner' + D];\n      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {\n        // Opera <9.5 needs document.body.clientWidth/Height\n        dimensions[d] = document.body['client' + D]\n      } else {\n        dimensions[d] = document.documentElement['client' + D];\n      }\n    });\n    return dimensions;\n  },\n\n  getWidth: function() {\n    return this.getDimensions().width;\n  },\n\n  getHeight: function() {\n    return this.getDimensions().height;\n  },\n\n  getScrollOffsets: function() {\n    return Element._returnOffset(\n      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,\n      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);\n  }\n};\n/* Portions of the Selector class are derived from Jack Slocum's DomQuery,\n * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style\n * license.  Please see http://www.yui-ext.com/ for more information. */\n\nvar Selector = Class.create({\n  initialize: function(expression) {\n    this.expression = expression.strip();\n\n    if (this.shouldUseSelectorsAPI()) {\n      this.mode = 'selectorsAPI';\n    } else if (this.shouldUseXPath()) {\n      this.mode = 'xpath';\n      this.compileXPathMatcher();\n    } else {\n      this.mode = \"normal\";\n      this.compileMatcher();\n    }\n\n  },\n\n  shouldUseXPath: function() {\n    if (!Prototype.BrowserFeatures.XPath) return false;\n\n    var e = this.expression;\n\n    // Safari 3 chokes on :*-of-type and :empty\n    if (Prototype.Browser.WebKit &&\n     (e.include(\"-of-type\") || e.include(\":empty\")))\n      return false;\n\n    // XPath can't do namespaced attributes, nor can it read\n    // the \"checked\" property from DOM nodes\n    if ((/(\\[[\\w-]*?:|:checked)/).test(e))\n      return false;\n\n    return true;\n  },\n\n  shouldUseSelectorsAPI: function() {\n    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;\n\n    if (!Selector._div) Selector._div = new Element('div');\n\n    // Make sure the browser treats the selector as valid. Test on an\n    // isolated element to minimize cost of this check.\n    try {\n      Selector._div.querySelector(this.expression);\n    } catch(e) {\n      return false;\n    }\n\n    return true;\n  },\n\n  compileMatcher: function() {\n    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,\n        c = Selector.criteria, le, p, m;\n\n    if (Selector._cache[e]) {\n      this.matcher = Selector._cache[e];\n      return;\n    }\n\n    this.matcher = [\"this.matcher = function(root) {\",\n                    \"var r = root, h = Selector.handlers, c = false, n;\"];\n\n    while (e && le != e && (/\\S/).test(e)) {\n      le = e;\n      for (var i in ps) {\n        p = ps[i];\n        if (m = e.match(p)) {\n          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :\n            new Template(c[i]).evaluate(m));\n          e = e.replace(m[0], '');\n          break;\n        }\n      }\n    }\n\n    this.matcher.push(\"return h.unique(n);\\n}\");\n    eval(this.matcher.join('\\n'));\n    Selector._cache[this.expression] = this.matcher;\n  },\n\n  compileXPathMatcher: function() {\n    var e = this.expression, ps = Selector.patterns,\n        x = Selector.xpath, le, m;\n\n    if (Selector._cache[e]) {\n      this.xpath = Selector._cache[e]; return;\n    }\n\n    this.matcher = ['.//*'];\n    while (e && le != e && (/\\S/).test(e)) {\n      le = e;\n      for (var i in ps) {\n        if (m = e.match(ps[i])) {\n          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :\n            new Template(x[i]).evaluate(m));\n          e = e.replace(m[0], '');\n          break;\n        }\n      }\n    }\n\n    this.xpath = this.matcher.join('');\n    Selector._cache[this.expression] = this.xpath;\n  },\n\n  findElements: function(root) {\n    root = root || document;\n    var e = this.expression, results;\n\n    switch (this.mode) {\n      case 'selectorsAPI':\n        // querySelectorAll queries document-wide, then filters to descendants\n        // of the context element. That's not what we want.\n        // Add an explicit context to the selector if necessary.\n        if (root !== document) {\n          var oldId = root.id, id = $(root).identify();\n          e = \"#\" + id + \" \" + e;\n        }\n\n        results = $A(root.querySelectorAll(e)).map(Element.extend);\n        root.id = oldId;\n\n        return results;\n      case 'xpath':\n        return document._getElementsByXPath(this.xpath, root);\n      default:\n       return this.matcher(root);\n    }\n  },\n\n  match: function(element) {\n    this.tokens = [];\n\n    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;\n    var le, p, m;\n\n    while (e && le !== e && (/\\S/).test(e)) {\n      le = e;\n      for (var i in ps) {\n        p = ps[i];\n        if (m = e.match(p)) {\n          // use the Selector.assertions methods unless the selector\n          // is too complex.\n          if (as[i]) {\n            this.tokens.push([i, Object.clone(m)]);\n            e = e.replace(m[0], '');\n          } else {\n            // reluctantly do a document-wide search\n            // and look for a match in the array\n            return this.findElements(document).include(element);\n          }\n        }\n      }\n    }\n\n    var match = true, name, matches;\n    for (var i = 0, token; token = this.tokens[i]; i++) {\n      name = token[0], matches = token[1];\n      if (!Selector.assertions[name](element, matches)) {\n        match = false; break;\n      }\n    }\n\n    return match;\n  },\n\n  toString: function() {\n    return this.expression;\n  },\n\n  inspect: function() {\n    return \"#<Selector:\" + this.expression.inspect() + \">\";\n  }\n});\n\nObject.extend(Selector, {\n  _cache: { },\n\n  xpath: {\n    descendant:   \"//*\",\n    child:        \"/*\",\n    adjacent:     \"/following-sibling::*[1]\",\n    laterSibling: '/following-sibling::*',\n    tagName:      function(m) {\n      if (m[1] == '*') return '';\n      return \"[local-name()='\" + m[1].toLowerCase() +\n             \"' or local-name()='\" + m[1].toUpperCase() + \"']\";\n    },\n    className:    \"[contains(concat(' ', @class, ' '), ' #{1} ')]\",\n    id:           \"[@id='#{1}']\",\n    attrPresence: function(m) {\n      m[1] = m[1].toLowerCase();\n      return new Template(\"[@#{1}]\").evaluate(m);\n    },\n    attr: function(m) {\n      m[1] = m[1].toLowerCase();\n      m[3] = m[5] || m[6];\n      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);\n    },\n    pseudo: function(m) {\n      var h = Selector.xpath.pseudos[m[1]];\n      if (!h) return '';\n      if (Object.isFunction(h)) return h(m);\n      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);\n    },\n    operators: {\n      '=':  \"[@#{1}='#{3}']\",\n      '!=': \"[@#{1}!='#{3}']\",\n      '^=': \"[starts-with(@#{1}, '#{3}')]\",\n      '$=': \"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']\",\n      '*=': \"[contains(@#{1}, '#{3}')]\",\n      '~=': \"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]\",\n      '|=': \"[contains(concat('-', @#{1}, '-'), '-#{3}-')]\"\n    },\n    pseudos: {\n      'first-child': '[not(preceding-sibling::*)]',\n      'last-child':  '[not(following-sibling::*)]',\n      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',\n      'empty':       \"[count(*) = 0 and (count(text()) = 0)]\",\n      'checked':     \"[@checked]\",\n      'disabled':    \"[(@disabled) and (@type!='hidden')]\",\n      'enabled':     \"[not(@disabled) and (@type!='hidden')]\",\n      'not': function(m) {\n        var e = m[6], p = Selector.patterns,\n            x = Selector.xpath, le, v;\n\n        var exclusion = [];\n        while (e && le != e && (/\\S/).test(e)) {\n          le = e;\n          for (var i in p) {\n            if (m = e.match(p[i])) {\n              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);\n              exclusion.push(\"(\" + v.substring(1, v.length - 1) + \")\");\n              e = e.replace(m[0], '');\n              break;\n            }\n          }\n        }\n        return \"[not(\" + exclusion.join(\" and \") + \")]\";\n      },\n      'nth-child':      function(m) {\n        return Selector.xpath.pseudos.nth(\"(count(./preceding-sibling::*) + 1) \", m);\n      },\n      'nth-last-child': function(m) {\n        return Selector.xpath.pseudos.nth(\"(count(./following-sibling::*) + 1) \", m);\n      },\n      'nth-of-type':    function(m) {\n        return Selector.xpath.pseudos.nth(\"position() \", m);\n      },\n      'nth-last-of-type': function(m) {\n        return Selector.xpath.pseudos.nth(\"(last() + 1 - position()) \", m);\n      },\n      'first-of-type':  function(m) {\n        m[6] = \"1\"; return Selector.xpath.pseudos['nth-of-type'](m);\n      },\n      'last-of-type':   function(m) {\n        m[6] = \"1\"; return Selector.xpath.pseudos['nth-last-of-type'](m);\n      },\n      'only-of-type':   function(m) {\n        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);\n      },\n      nth: function(fragment, m) {\n        var mm, formula = m[6], predicate;\n        if (formula == 'even') formula = '2n+0';\n        if (formula == 'odd')  formula = '2n+1';\n        if (mm = formula.match(/^(\\d+)$/)) // digit only\n          return '[' + fragment + \"= \" + mm[1] + ']';\n        if (mm = formula.match(/^(-?\\d*)?n(([+-])(\\d+))?/)) { // an+b\n          if (mm[1] == \"-\") mm[1] = -1;\n          var a = mm[1] ? Number(mm[1]) : 1;\n          var b = mm[2] ? Number(mm[2]) : 0;\n          predicate = \"[((#{fragment} - #{b}) mod #{a} = 0) and \" +\n          \"((#{fragment} - #{b}) div #{a} >= 0)]\";\n          return new Template(predicate).evaluate({\n            fragment: fragment, a: a, b: b });\n        }\n      }\n    }\n  },\n\n  criteria: {\n    tagName:      'n = h.tagName(n, r, \"#{1}\", c);      c = false;',\n    className:    'n = h.className(n, r, \"#{1}\", c);    c = false;',\n    id:           'n = h.id(n, r, \"#{1}\", c);           c = false;',\n    attrPresence: 'n = h.attrPresence(n, r, \"#{1}\", c); c = false;',\n    attr: function(m) {\n      m[3] = (m[5] || m[6]);\n      return new Template('n = h.attr(n, r, \"#{1}\", \"#{3}\", \"#{2}\", c); c = false;').evaluate(m);\n    },\n    pseudo: function(m) {\n      if (m[6]) m[6] = m[6].replace(/\"/g, '\\\\\"');\n      return new Template('n = h.pseudo(n, \"#{1}\", \"#{6}\", r, c); c = false;').evaluate(m);\n    },\n    descendant:   'c = \"descendant\";',\n    child:        'c = \"child\";',\n    adjacent:     'c = \"adjacent\";',\n    laterSibling: 'c = \"laterSibling\";'\n  },\n\n  patterns: {\n    // combinators must be listed first\n    // (and descendant needs to be last combinator)\n    laterSibling: /^\\s*~\\s*/,\n    child:        /^\\s*>\\s*/,\n    adjacent:     /^\\s*\\+\\s*/,\n    descendant:   /^\\s/,\n\n    // selectors follow\n    tagName:      /^\\s*(\\*|[\\w\\-]+)(\\b|$)?/,\n    id:           /^#([\\w\\-\\*]+)(\\b|$)/,\n    className:    /^\\.([\\w\\-\\*]+)(\\b|$)/,\n    pseudo:\n/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\\((.*?)\\))?(\\b|$|(?=\\s|[:+~>]))/,\n    attrPresence: /^\\[((?:[\\w]+:)?[\\w]+)\\]/,\n    attr:         /\\[((?:[\\w-]*:)?[\\w-]+)\\s*(?:([!^$*~|]?=)\\s*((['\"])([^\\4]*?)\\4|([^'\"][^\\]]*?)))?\\]/\n  },\n\n  // for Selector.match and Element#match\n  assertions: {\n    tagName: function(element, matches) {\n      return matches[1].toUpperCase() == element.tagName.toUpperCase();\n    },\n\n    className: function(element, matches) {\n      return Element.hasClassName(element, matches[1]);\n    },\n\n    id: function(element, matches) {\n      return element.id === matches[1];\n    },\n\n    attrPresence: function(element, matches) {\n      return Element.hasAttribute(element, matches[1]);\n    },\n\n    attr: function(element, matches) {\n      var nodeValue = Element.readAttribute(element, matches[1]);\n      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);\n    }\n  },\n\n  handlers: {\n    // UTILITY FUNCTIONS\n    // joins two collections\n    concat: function(a, b) {\n      for (var i = 0, node; node = b[i]; i++)\n        a.push(node);\n      return a;\n    },\n\n    // marks an array of nodes for counting\n    mark: function(nodes) {\n      var _true = Prototype.emptyFunction;\n      for (var i = 0, node; node = nodes[i]; i++)\n        node._countedByPrototype = _true;\n      return nodes;\n    },\n\n    unmark: function(nodes) {\n      for (var i = 0, node; node = nodes[i]; i++)\n        node._countedByPrototype = undefined;\n      return nodes;\n    },\n\n    // mark each child node with its position (for nth calls)\n    // \"ofType\" flag indicates whether we're indexing for nth-of-type\n    // rather than nth-child\n    index: function(parentNode, reverse, ofType) {\n      parentNode._countedByPrototype = Prototype.emptyFunction;\n      if (reverse) {\n        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {\n          var node = nodes[i];\n          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;\n        }\n      } else {\n        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)\n          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;\n      }\n    },\n\n    // filters out duplicates and extends all nodes\n    unique: function(nodes) {\n      if (nodes.length == 0) return nodes;\n      var results = [], n;\n      for (var i = 0, l = nodes.length; i < l; i++)\n        if (!(n = nodes[i])._countedByPrototype) {\n          n._countedByPrototype = Prototype.emptyFunction;\n          results.push(Element.extend(n));\n        }\n      return Selector.handlers.unmark(results);\n    },\n\n    // COMBINATOR FUNCTIONS\n    descendant: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        h.concat(results, node.getElementsByTagName('*'));\n      return results;\n    },\n\n    child: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        for (var j = 0, child; child = node.childNodes[j]; j++)\n          if (child.nodeType == 1 && child.tagName != '!') results.push(child);\n      }\n      return results;\n    },\n\n    adjacent: function(nodes) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        var next = this.nextElementSibling(node);\n        if (next) results.push(next);\n      }\n      return results;\n    },\n\n    laterSibling: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        h.concat(results, Element.nextSiblings(node));\n      return results;\n    },\n\n    nextElementSibling: function(node) {\n      while (node = node.nextSibling)\n        if (node.nodeType == 1) return node;\n      return null;\n    },\n\n    previousElementSibling: function(node) {\n      while (node = node.previousSibling)\n        if (node.nodeType == 1) return node;\n      return null;\n    },\n\n    // TOKEN FUNCTIONS\n    tagName: function(nodes, root, tagName, combinator) {\n      var uTagName = tagName.toUpperCase();\n      var results = [], h = Selector.handlers;\n      if (nodes) {\n        if (combinator) {\n          // fastlane for ordinary descendant combinators\n          if (combinator == \"descendant\") {\n            for (var i = 0, node; node = nodes[i]; i++)\n              h.concat(results, node.getElementsByTagName(tagName));\n            return results;\n          } else nodes = this[combinator](nodes);\n          if (tagName == \"*\") return nodes;\n        }\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.tagName.toUpperCase() === uTagName) results.push(node);\n        return results;\n      } else return root.getElementsByTagName(tagName);\n    },\n\n    id: function(nodes, root, id, combinator) {\n      var targetNode = $(id), h = Selector.handlers;\n      if (!targetNode) return [];\n      if (!nodes && root == document) return [targetNode];\n      if (nodes) {\n        if (combinator) {\n          if (combinator == 'child') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (targetNode.parentNode == node) return [targetNode];\n          } else if (combinator == 'descendant') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (Element.descendantOf(targetNode, node)) return [targetNode];\n          } else if (combinator == 'adjacent') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (Selector.handlers.previousElementSibling(targetNode) == node)\n                return [targetNode];\n          } else nodes = h[combinator](nodes);\n        }\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node == targetNode) return [targetNode];\n        return [];\n      }\n      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];\n    },\n\n    className: function(nodes, root, className, combinator) {\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      return Selector.handlers.byClassName(nodes, root, className);\n    },\n\n    byClassName: function(nodes, root, className) {\n      if (!nodes) nodes = Selector.handlers.descendant([root]);\n      var needle = ' ' + className + ' ';\n      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {\n        nodeClassName = node.className;\n        if (nodeClassName.length == 0) continue;\n        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))\n          results.push(node);\n      }\n      return results;\n    },\n\n    attrPresence: function(nodes, root, attr, combinator) {\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      var results = [];\n      for (var i = 0, node; node = nodes[i]; i++)\n        if (Element.hasAttribute(node, attr)) results.push(node);\n      return results;\n    },\n\n    attr: function(nodes, root, attr, value, operator, combinator) {\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      var handler = Selector.operators[operator], results = [];\n      for (var i = 0, node; node = nodes[i]; i++) {\n        var nodeValue = Element.readAttribute(node, attr);\n        if (nodeValue === null) continue;\n        if (handler(nodeValue, value)) results.push(node);\n      }\n      return results;\n    },\n\n    pseudo: function(nodes, name, value, root, combinator) {\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      return Selector.pseudos[name](nodes, value, root);\n    }\n  },\n\n  pseudos: {\n    'first-child': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (Selector.handlers.previousElementSibling(node)) continue;\n          results.push(node);\n      }\n      return results;\n    },\n    'last-child': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (Selector.handlers.nextElementSibling(node)) continue;\n          results.push(node);\n      }\n      return results;\n    },\n    'only-child': function(nodes, value, root) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))\n          results.push(node);\n      return results;\n    },\n    'nth-child':        function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root);\n    },\n    'nth-last-child':   function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, true);\n    },\n    'nth-of-type':      function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, false, true);\n    },\n    'nth-last-of-type': function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, true, true);\n    },\n    'first-of-type':    function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, \"1\", root, false, true);\n    },\n    'last-of-type':     function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, \"1\", root, true, true);\n    },\n    'only-of-type':     function(nodes, formula, root) {\n      var p = Selector.pseudos;\n      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);\n    },\n\n    // handles the an+b logic\n    getIndices: function(a, b, total) {\n      if (a == 0) return b > 0 ? [b] : [];\n      return $R(1, total).inject([], function(memo, i) {\n        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);\n        return memo;\n      });\n    },\n\n    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type\n    nth: function(nodes, formula, root, reverse, ofType) {\n      if (nodes.length == 0) return [];\n      if (formula == 'even') formula = '2n+0';\n      if (formula == 'odd')  formula = '2n+1';\n      var h = Selector.handlers, results = [], indexed = [], m;\n      h.mark(nodes);\n      for (var i = 0, node; node = nodes[i]; i++) {\n        if (!node.parentNode._countedByPrototype) {\n          h.index(node.parentNode, reverse, ofType);\n          indexed.push(node.parentNode);\n        }\n      }\n      if (formula.match(/^\\d+$/)) { // just a number\n        formula = Number(formula);\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.nodeIndex == formula) results.push(node);\n      } else if (m = formula.match(/^(-?\\d*)?n(([+-])(\\d+))?/)) { // an+b\n        if (m[1] == \"-\") m[1] = -1;\n        var a = m[1] ? Number(m[1]) : 1;\n        var b = m[2] ? Number(m[2]) : 0;\n        var indices = Selector.pseudos.getIndices(a, b, nodes.length);\n        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {\n          for (var j = 0; j < l; j++)\n            if (node.nodeIndex == indices[j]) results.push(node);\n        }\n      }\n      h.unmark(nodes);\n      h.unmark(indexed);\n      return results;\n    },\n\n    'empty': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        // IE treats comments as element nodes\n        if (node.tagName == '!' || node.firstChild) continue;\n        results.push(node);\n      }\n      return results;\n    },\n\n    'not': function(nodes, selector, root) {\n      var h = Selector.handlers, selectorType, m;\n      var exclusions = new Selector(selector).findElements(root);\n      h.mark(exclusions);\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!node._countedByPrototype) results.push(node);\n      h.unmark(exclusions);\n      return results;\n    },\n\n    'enabled': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!node.disabled && (!node.type || node.type !== 'hidden'))\n          results.push(node);\n      return results;\n    },\n\n    'disabled': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (node.disabled) results.push(node);\n      return results;\n    },\n\n    'checked': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (node.checked) results.push(node);\n      return results;\n    }\n  },\n\n  operators: {\n    '=':  function(nv, v) { return nv == v; },\n    '!=': function(nv, v) { return nv != v; },\n    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },\n    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },\n    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },\n    '$=': function(nv, v) { return nv.endsWith(v); },\n    '*=': function(nv, v) { return nv.include(v); },\n    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },\n    '|=': function(nv, v) { return ('-' + (nv || \"\").toUpperCase() +\n     '-').include('-' + (v || \"\").toUpperCase() + '-'); }\n  },\n\n  split: function(expression) {\n    var expressions = [];\n    expression.scan(/(([\\w#:.~>+()\\s-]+|\\*|\\[.*?\\])+)\\s*(,|$)/, function(m) {\n      expressions.push(m[1].strip());\n    });\n    return expressions;\n  },\n\n  matchElements: function(elements, expression) {\n    var matches = $$(expression), h = Selector.handlers;\n    h.mark(matches);\n    for (var i = 0, results = [], element; element = elements[i]; i++)\n      if (element._countedByPrototype) results.push(element);\n    h.unmark(matches);\n    return results;\n  },\n\n  findElement: function(elements, expression, index) {\n    if (Object.isNumber(expression)) {\n      index = expression; expression = false;\n    }\n    return Selector.matchElements(elements, expression || '*')[index || 0];\n  },\n\n  findChildElements: function(element, expressions) {\n    expressions = Selector.split(expressions.join(','));\n    var results = [], h = Selector.handlers;\n    for (var i = 0, l = expressions.length, selector; i < l; i++) {\n      selector = new Selector(expressions[i].strip());\n      h.concat(results, selector.findElements(element));\n    }\n    return (l > 1) ? h.unique(results) : results;\n  }\n});\n\nif (Prototype.Browser.IE) {\n  Object.extend(Selector.handlers, {\n    // IE returns comment nodes on getElementsByTagName(\"*\").\n    // Filter them out.\n    concat: function(a, b) {\n      for (var i = 0, node; node = b[i]; i++)\n        if (node.tagName !== \"!\") a.push(node);\n      return a;\n    },\n\n    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.\n    unmark: function(nodes) {\n      for (var i = 0, node; node = nodes[i]; i++)\n        node.removeAttribute('_countedByPrototype');\n      return nodes;\n    }\n  });\n}\n\nfunction $$() {\n  return Selector.findChildElements(document, $A(arguments));\n}\nvar Form = {\n  reset: function(form) {\n    $(form).reset();\n    return form;\n  },\n\n  serializeElements: function(elements, options) {\n    if (typeof options != 'object') options = { hash: !!options };\n    else if (Object.isUndefined(options.hash)) options.hash = true;\n    var key, value, submitted = false, submit = options.submit;\n\n    var data = elements.inject({ }, function(result, element) {\n      if (!element.disabled && element.name) {\n        key = element.name; value = $(element).getValue();\n        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&\n            submit !== false && (!submit || key == submit) && (submitted = true)))) {\n          if (key in result) {\n            // a key is already present; construct an array of values\n            if (!Object.isArray(result[key])) result[key] = [result[key]];\n            result[key].push(value);\n          }\n          else result[key] = value;\n        }\n      }\n      return result;\n    });\n\n    return options.hash ? data : Object.toQueryString(data);\n  }\n};\n\nForm.Methods = {\n  serialize: function(form, options) {\n    return Form.serializeElements(Form.getElements(form), options);\n  },\n\n  getElements: function(form) {\n    return $A($(form).getElementsByTagName('*')).inject([],\n      function(elements, child) {\n        if (Form.Element.Serializers[child.tagName.toLowerCase()])\n          elements.push(Element.extend(child));\n        return elements;\n      }\n    );\n  },\n\n  getInputs: function(form, typeName, name) {\n    form = $(form);\n    var inputs = form.getElementsByTagName('input');\n\n    if (!typeName && !name) return $A(inputs).map(Element.extend);\n\n    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {\n      var input = inputs[i];\n      if ((typeName && input.type != typeName) || (name && input.name != name))\n        continue;\n      matchingInputs.push(Element.extend(input));\n    }\n\n    return matchingInputs;\n  },\n\n  disable: function(form) {\n    form = $(form);\n    Form.getElements(form).invoke('disable');\n    return form;\n  },\n\n  enable: function(form) {\n    form = $(form);\n    Form.getElements(form).invoke('enable');\n    return form;\n  },\n\n  findFirstElement: function(form) {\n    var elements = $(form).getElements().findAll(function(element) {\n      return 'hidden' != element.type && !element.disabled;\n    });\n    var firstByIndex = elements.findAll(function(element) {\n      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;\n    }).sortBy(function(element) { return element.tabIndex }).first();\n\n    return firstByIndex ? firstByIndex : elements.find(function(element) {\n      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());\n    });\n  },\n\n  focusFirstElement: function(form) {\n    form = $(form);\n    form.findFirstElement().activate();\n    return form;\n  },\n\n  request: function(form, options) {\n    form = $(form), options = Object.clone(options || { });\n\n    var params = options.parameters, action = form.readAttribute('action') || '';\n    if (action.blank()) action = window.location.href;\n    options.parameters = form.serialize(true);\n\n    if (params) {\n      if (Object.isString(params)) params = params.toQueryParams();\n      Object.extend(options.parameters, params);\n    }\n\n    if (form.hasAttribute('method') && !options.method)\n      options.method = form.method;\n\n    return new Ajax.Request(action, options);\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nForm.Element = {\n  focus: function(element) {\n    $(element).focus();\n    return element;\n  },\n\n  select: function(element) {\n    $(element).select();\n    return element;\n  }\n};\n\nForm.Element.Methods = {\n  serialize: function(element) {\n    element = $(element);\n    if (!element.disabled && element.name) {\n      var value = element.getValue();\n      if (value != undefined) {\n        var pair = { };\n        pair[element.name] = value;\n        return Object.toQueryString(pair);\n      }\n    }\n    return '';\n  },\n\n  getValue: function(element) {\n    element = $(element);\n    var method = element.tagName.toLowerCase();\n    return Form.Element.Serializers[method](element);\n  },\n\n  setValue: function(element, value) {\n    element = $(element);\n    var method = element.tagName.toLowerCase();\n    Form.Element.Serializers[method](element, value);\n    return element;\n  },\n\n  clear: function(element) {\n    $(element).value = '';\n    return element;\n  },\n\n  present: function(element) {\n    return $(element).value != '';\n  },\n\n  activate: function(element) {\n    element = $(element);\n    try {\n      element.focus();\n      if (element.select && (element.tagName.toLowerCase() != 'input' ||\n          !['button', 'reset', 'submit'].include(element.type)))\n        element.select();\n    } catch (e) { }\n    return element;\n  },\n\n  disable: function(element) {\n    element = $(element);\n    element.disabled = true;\n    return element;\n  },\n\n  enable: function(element) {\n    element = $(element);\n    element.disabled = false;\n    return element;\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Field = Form.Element;\nvar $F = Form.Element.Methods.getValue;\n\n/*--------------------------------------------------------------------------*/\n\nForm.Element.Serializers = {\n  input: function(element, value) {\n    switch (element.type.toLowerCase()) {\n      case 'checkbox':\n      case 'radio':\n        return Form.Element.Serializers.inputSelector(element, value);\n      default:\n        return Form.Element.Serializers.textarea(element, value);\n    }\n  },\n\n  inputSelector: function(element, value) {\n    if (Object.isUndefined(value)) return element.checked ? element.value : null;\n    else element.checked = !!value;\n  },\n\n  textarea: function(element, value) {\n    if (Object.isUndefined(value)) return element.value;\n    else element.value = value;\n  },\n\n  select: function(element, value) {\n    if (Object.isUndefined(value))\n      return this[element.type == 'select-one' ?\n        'selectOne' : 'selectMany'](element);\n    else {\n      var opt, currentValue, single = !Object.isArray(value);\n      for (var i = 0, length = element.length; i < length; i++) {\n        opt = element.options[i];\n        currentValue = this.optionValue(opt);\n        if (single) {\n          if (currentValue == value) {\n            opt.selected = true;\n            return;\n          }\n        }\n        else opt.selected = value.include(currentValue);\n      }\n    }\n  },\n\n  selectOne: function(element) {\n    var index = element.selectedIndex;\n    return index >= 0 ? this.optionValue(element.options[index]) : null;\n  },\n\n  selectMany: function(element) {\n    var values, length = element.length;\n    if (!length) return null;\n\n    for (var i = 0, values = []; i < length; i++) {\n      var opt = element.options[i];\n      if (opt.selected) values.push(this.optionValue(opt));\n    }\n    return values;\n  },\n\n  optionValue: function(opt) {\n    // extend element because hasAttribute may not be native\n    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nAbstract.TimedObserver = Class.create(PeriodicalExecuter, {\n  initialize: function($super, element, frequency, callback) {\n    $super(callback, frequency);\n    this.element   = $(element);\n    this.lastValue = this.getValue();\n  },\n\n  execute: function() {\n    var value = this.getValue();\n    if (Object.isString(this.lastValue) && Object.isString(value) ?\n        this.lastValue != value : String(this.lastValue) != String(value)) {\n      this.callback(this.element, value);\n      this.lastValue = value;\n    }\n  }\n});\n\nForm.Element.Observer = Class.create(Abstract.TimedObserver, {\n  getValue: function() {\n    return Form.Element.getValue(this.element);\n  }\n});\n\nForm.Observer = Class.create(Abstract.TimedObserver, {\n  getValue: function() {\n    return Form.serialize(this.element);\n  }\n});\n\n/*--------------------------------------------------------------------------*/\n\nAbstract.EventObserver = Class.create({\n  initialize: function(element, callback) {\n    this.element  = $(element);\n    this.callback = callback;\n\n    this.lastValue = this.getValue();\n    if (this.element.tagName.toLowerCase() == 'form')\n      this.registerFormCallbacks();\n    else\n      this.registerCallback(this.element);\n  },\n\n  onElementEvent: function() {\n    var value = this.getValue();\n    if (this.lastValue != value) {\n      this.callback(this.element, value);\n      this.lastValue = value;\n    }\n  },\n\n  registerFormCallbacks: function() {\n    Form.getElements(this.element).each(this.registerCallback, this);\n  },\n\n  registerCallback: function(element) {\n    if (element.type) {\n      switch (element.type.toLowerCase()) {\n        case 'checkbox':\n        case 'radio':\n          Event.observe(element, 'click', this.onElementEvent.bind(this));\n          break;\n        default:\n          Event.observe(element, 'change', this.onElementEvent.bind(this));\n          break;\n      }\n    }\n  }\n});\n\nForm.Element.EventObserver = Class.create(Abstract.EventObserver, {\n  getValue: function() {\n    return Form.Element.getValue(this.element);\n  }\n});\n\nForm.EventObserver = Class.create(Abstract.EventObserver, {\n  getValue: function() {\n    return Form.serialize(this.element);\n  }\n});\nif (!window.Event) var Event = { };\n\nObject.extend(Event, {\n  KEY_BACKSPACE: 8,\n  KEY_TAB:       9,\n  KEY_RETURN:   13,\n  KEY_ESC:      27,\n  KEY_LEFT:     37,\n  KEY_UP:       38,\n  KEY_RIGHT:    39,\n  KEY_DOWN:     40,\n  KEY_DELETE:   46,\n  KEY_HOME:     36,\n  KEY_END:      35,\n  KEY_PAGEUP:   33,\n  KEY_PAGEDOWN: 34,\n  KEY_INSERT:   45,\n\n  cache: { },\n\n  relatedTarget: function(event) {\n    var element;\n    switch(event.type) {\n      case 'mouseover': element = event.fromElement; break;\n      case 'mouseout':  element = event.toElement;   break;\n      default: return null;\n    }\n    return Element.extend(element);\n  }\n});\n\nEvent.Methods = (function() {\n  var isButton;\n\n  if (Prototype.Browser.IE) {\n    var buttonMap = { 0: 1, 1: 4, 2: 2 };\n    isButton = function(event, code) {\n      return event.button == buttonMap[code];\n    };\n\n  } else if (Prototype.Browser.WebKit) {\n    isButton = function(event, code) {\n      switch (code) {\n        case 0: return event.which == 1 && !event.metaKey;\n        case 1: return event.which == 1 && event.metaKey;\n        default: return false;\n      }\n    };\n\n  } else {\n    isButton = function(event, code) {\n      return event.which ? (event.which === code + 1) : (event.button === code);\n    };\n  }\n\n  return {\n    isLeftClick:   function(event) { return isButton(event, 0) },\n    isMiddleClick: function(event) { return isButton(event, 1) },\n    isRightClick:  function(event) { return isButton(event, 2) },\n\n    element: function(event) {\n      event = Event.extend(event);\n\n      var node          = event.target,\n          type          = event.type,\n          currentTarget = event.currentTarget;\n\n      if (currentTarget && currentTarget.tagName) {\n        // Firefox screws up the \"click\" event when moving between radio buttons\n        // via arrow keys. It also screws up the \"load\" and \"error\" events on images,\n        // reporting the document as the target instead of the original image.\n        if (type === 'load' || type === 'error' ||\n          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'\n            && currentTarget.type === 'radio'))\n              node = currentTarget;\n      }\n      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;\n      return Element.extend(node);\n    },\n\n    findElement: function(event, expression) {\n      var element = Event.element(event);\n      if (!expression) return element;\n      var elements = [element].concat(element.ancestors());\n      return Selector.findElement(elements, expression, 0);\n    },\n\n    pointer: function(event) {\n      var docElement = document.documentElement,\n      body = document.body || { scrollLeft: 0, scrollTop: 0 };\n      return {\n        x: event.pageX || (event.clientX +\n          (docElement.scrollLeft || body.scrollLeft) -\n          (docElement.clientLeft || 0)),\n        y: event.pageY || (event.clientY +\n          (docElement.scrollTop || body.scrollTop) -\n          (docElement.clientTop || 0))\n      };\n    },\n\n    pointerX: function(event) { return Event.pointer(event).x },\n    pointerY: function(event) { return Event.pointer(event).y },\n\n    stop: function(event) {\n      Event.extend(event);\n      event.preventDefault();\n      event.stopPropagation();\n      event.stopped = true;\n    }\n  };\n})();\n\nEvent.extend = (function() {\n  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {\n    m[name] = Event.Methods[name].methodize();\n    return m;\n  });\n\n  if (Prototype.Browser.IE) {\n    Object.extend(methods, {\n      stopPropagation: function() { this.cancelBubble = true },\n      preventDefault:  function() { this.returnValue = false },\n      inspect: function() { return \"[object Event]\" }\n    });\n\n    return function(event) {\n      if (!event) return false;\n      if (event._extendedByPrototype) return event;\n\n      event._extendedByPrototype = Prototype.emptyFunction;\n      var pointer = Event.pointer(event);\n      Object.extend(event, {\n        target: event.srcElement,\n        relatedTarget: Event.relatedTarget(event),\n        pageX:  pointer.x,\n        pageY:  pointer.y\n      });\n      return Object.extend(event, methods);\n    };\n\n  } else {\n    Event.prototype = Event.prototype || document.createEvent(\"HTMLEvents\")['__proto__'];\n    Object.extend(Event.prototype, methods);\n    return Prototype.K;\n  }\n})();\n\nObject.extend(Event, (function() {\n  var cache = Event.cache;\n\n  function getEventID(element) {\n    if (element._prototypeEventID) return element._prototypeEventID[0];\n    arguments.callee.id = arguments.callee.id || 1;\n    return element._prototypeEventID = [++arguments.callee.id];\n  }\n\n  function getDOMEventName(eventName) {\n    if (eventName && eventName.include(':')) return \"dataavailable\";\n    return eventName;\n  }\n\n  function getCacheForID(id) {\n    return cache[id] = cache[id] || { };\n  }\n\n  function getWrappersForEventName(id, eventName) {\n    var c = getCacheForID(id);\n    return c[eventName] = c[eventName] || [];\n  }\n\n  function createWrapper(element, eventName, handler) {\n    var id = getEventID(element);\n    var c = getWrappersForEventName(id, eventName);\n    if (c.pluck(\"handler\").include(handler)) return false;\n\n    var wrapper = function(event) {\n      if (!Event || !Event.extend ||\n        (event.eventName && event.eventName != eventName))\n          return false;\n\n      Event.extend(event);\n      handler.call(element, event);\n    };\n\n    wrapper.handler = handler;\n    c.push(wrapper);\n    return wrapper;\n  }\n\n  function findWrapper(id, eventName, handler) {\n    var c = getWrappersForEventName(id, eventName);\n    return c.find(function(wrapper) { return wrapper.handler == handler });\n  }\n\n  function destroyWrapper(id, eventName, handler) {\n    var c = getCacheForID(id);\n    if (!c[eventName]) return false;\n    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));\n  }\n\n  function destroyCache() {\n    for (var id in cache)\n      for (var eventName in cache[id])\n        cache[id][eventName] = null;\n  }\n\n\n  // Internet Explorer needs to remove event handlers on page unload\n  // in order to avoid memory leaks.\n  if (window.attachEvent) {\n    window.attachEvent(\"onunload\", destroyCache);\n  }\n\n  // Safari has a dummy event handler on page unload so that it won't\n  // use its bfcache. Safari <= 3.1 has an issue with restoring the \"document\"\n  // object when page is returned to via the back button using its bfcache.\n  if (Prototype.Browser.WebKit) {\n    window.addEventListener('unload', Prototype.emptyFunction, false);\n  }\n\n  return {\n    observe: function(element, eventName, handler) {\n      element = $(element);\n      var name = getDOMEventName(eventName);\n\n      var wrapper = createWrapper(element, eventName, handler);\n      if (!wrapper) return element;\n\n      if (element.addEventListener) {\n        element.addEventListener(name, wrapper, false);\n      } else {\n        element.attachEvent(\"on\" + name, wrapper);\n      }\n\n      return element;\n    },\n\n    stopObserving: function(element, eventName, handler) {\n      element = $(element);\n      var id = getEventID(element), name = getDOMEventName(eventName);\n\n      if (!handler && eventName) {\n        getWrappersForEventName(id, eventName).each(function(wrapper) {\n          element.stopObserving(eventName, wrapper.handler);\n        });\n        return element;\n\n      } else if (!eventName) {\n        Object.keys(getCacheForID(id)).each(function(eventName) {\n          element.stopObserving(eventName);\n        });\n        return element;\n      }\n\n      var wrapper = findWrapper(id, eventName, handler);\n      if (!wrapper) return element;\n\n      if (element.removeEventListener) {\n        element.removeEventListener(name, wrapper, false);\n      } else {\n        element.detachEvent(\"on\" + name, wrapper);\n      }\n\n      destroyWrapper(id, eventName, handler);\n\n      return element;\n    },\n\n    fire: function(element, eventName, memo) {\n      element = $(element);\n      if (element == document && document.createEvent && !element.dispatchEvent)\n        element = document.documentElement;\n\n      var event;\n      if (document.createEvent) {\n        event = document.createEvent(\"HTMLEvents\");\n        event.initEvent(\"dataavailable\", true, true);\n      } else {\n        event = document.createEventObject();\n        event.eventType = \"ondataavailable\";\n      }\n\n      event.eventName = eventName;\n      event.memo = memo || { };\n\n      if (document.createEvent) {\n        element.dispatchEvent(event);\n      } else {\n        element.fireEvent(event.eventType, event);\n      }\n\n      return Event.extend(event);\n    }\n  };\n})());\n\nObject.extend(Event, Event.Methods);\n\nElement.addMethods({\n  fire:          Event.fire,\n  observe:       Event.observe,\n  stopObserving: Event.stopObserving\n});\n\nObject.extend(document, {\n  fire:          Element.Methods.fire.methodize(),\n  observe:       Element.Methods.observe.methodize(),\n  stopObserving: Element.Methods.stopObserving.methodize(),\n  loaded:        false\n});\n\n(function() {\n  /* Support for the DOMContentLoaded event is based on work by Dan Webb,\n     Matthias Miller, Dean Edwards and John Resig. */\n\n  var timer;\n\n  function fireContentLoadedEvent() {\n    if (document.loaded) return;\n    if (timer) window.clearInterval(timer);\n    document.fire(\"dom:loaded\");\n    document.loaded = true;\n  }\n\n  if (document.addEventListener) {\n    if (Prototype.Browser.WebKit) {\n      timer = window.setInterval(function() {\n        if (/loaded|complete/.test(document.readyState))\n          fireContentLoadedEvent();\n      }, 0);\n\n      Event.observe(window, \"load\", fireContentLoadedEvent);\n\n    } else {\n      document.addEventListener(\"DOMContentLoaded\",\n        fireContentLoadedEvent, false);\n    }\n\n  } else {\n    document.write(\"<script id=__onDOMContentLoaded defer src=//:><\\/script>\");\n    $(\"__onDOMContentLoaded\").onreadystatechange = function() {\n      if (this.readyState == \"complete\") {\n        this.onreadystatechange = null;\n        fireContentLoadedEvent();\n      }\n    };\n  }\n})();\n/*------------------------------- DEPRECATED -------------------------------*/\n\nHash.toQueryString = Object.toQueryString;\n\nvar Toggle = { display: Element.toggle };\n\nElement.Methods.childOf = Element.Methods.descendantOf;\n\nvar Insertion = {\n  Before: function(element, content) {\n    return Element.insert(element, {before:content});\n  },\n\n  Top: function(element, content) {\n    return Element.insert(element, {top:content});\n  },\n\n  Bottom: function(element, content) {\n    return Element.insert(element, {bottom:content});\n  },\n\n  After: function(element, content) {\n    return Element.insert(element, {after:content});\n  }\n};\n\nvar $continue = new Error('\"throw $continue\" is deprecated, use \"return\" instead');\n\n// This should be moved to script.aculo.us; notice the deprecated methods\n// further below, that map to the newer Element methods.\nvar Position = {\n  // set to true if needed, warning: firefox performance problems\n  // NOT neeeded for page scrolling, only if draggable contained in\n  // scrollable elements\n  includeScrollOffsets: false,\n\n  // must be called before calling withinIncludingScrolloffset, every time the\n  // page is scrolled\n  prepare: function() {\n    this.deltaX =  window.pageXOffset\n                || document.documentElement.scrollLeft\n                || document.body.scrollLeft\n                || 0;\n    this.deltaY =  window.pageYOffset\n                || document.documentElement.scrollTop\n                || document.body.scrollTop\n                || 0;\n  },\n\n  // caches x/y coordinate pair to use with overlap\n  within: function(element, x, y) {\n    if (this.includeScrollOffsets)\n      return this.withinIncludingScrolloffsets(element, x, y);\n    this.xcomp = x;\n    this.ycomp = y;\n    this.offset = Element.cumulativeOffset(element);\n\n    return (y >= this.offset[1] &&\n            y <  this.offset[1] + element.offsetHeight &&\n            x >= this.offset[0] &&\n            x <  this.offset[0] + element.offsetWidth);\n  },\n\n  withinIncludingScrolloffsets: function(element, x, y) {\n    var offsetcache = Element.cumulativeScrollOffset(element);\n\n    this.xcomp = x + offsetcache[0] - this.deltaX;\n    this.ycomp = y + offsetcache[1] - this.deltaY;\n    this.offset = Element.cumulativeOffset(element);\n\n    return (this.ycomp >= this.offset[1] &&\n            this.ycomp <  this.offset[1] + element.offsetHeight &&\n            this.xcomp >= this.offset[0] &&\n            this.xcomp <  this.offset[0] + element.offsetWidth);\n  },\n\n  // within must be called directly before\n  overlap: function(mode, element) {\n    if (!mode) return 0;\n    if (mode == 'vertical')\n      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /\n        element.offsetHeight;\n    if (mode == 'horizontal')\n      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /\n        element.offsetWidth;\n  },\n\n  // Deprecation layer -- use newer Element methods now (1.5.2).\n\n  cumulativeOffset: Element.Methods.cumulativeOffset,\n\n  positionedOffset: Element.Methods.positionedOffset,\n\n  absolutize: function(element) {\n    Position.prepare();\n    return Element.absolutize(element);\n  },\n\n  relativize: function(element) {\n    Position.prepare();\n    return Element.relativize(element);\n  },\n\n  realOffset: Element.Methods.cumulativeScrollOffset,\n\n  offsetParent: Element.Methods.getOffsetParent,\n\n  page: Element.Methods.viewportOffset,\n\n  clone: function(source, target, options) {\n    options = options || { };\n    return Element.clonePosition(target, source, options);\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nif (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){\n  function iter(name) {\n    return name.blank() ? null : \"[contains(concat(' ', @class, ' '), ' \" + name + \" ')]\";\n  }\n\n  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?\n  function(element, className) {\n    className = className.toString().strip();\n    var cond = /\\s/.test(className) ? $w(className).map(iter).join('') : iter(className);\n    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];\n  } : function(element, className) {\n    className = className.toString().strip();\n    var elements = [], classNames = (/\\s/.test(className) ? $w(className) : null);\n    if (!classNames && !className) return elements;\n\n    var nodes = $(element).getElementsByTagName('*');\n    className = ' ' + className + ' ';\n\n    for (var i = 0, child, cn; child = nodes[i]; i++) {\n      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||\n          (classNames && classNames.all(function(name) {\n            return !name.toString().blank() && cn.include(' ' + name + ' ');\n          }))))\n        elements.push(Element.extend(child));\n    }\n    return elements;\n  };\n\n  return function(className, parentElement) {\n    return $(parentElement || document.body).getElementsByClassName(className);\n  };\n}(Element.Methods);\n\n/*--------------------------------------------------------------------------*/\n\nElement.ClassNames = Class.create();\nElement.ClassNames.prototype = {\n  initialize: function(element) {\n    this.element = $(element);\n  },\n\n  _each: function(iterator) {\n    this.element.className.split(/\\s+/).select(function(name) {\n      return name.length > 0;\n    })._each(iterator);\n  },\n\n  set: function(className) {\n    this.element.className = className;\n  },\n\n  add: function(classNameToAdd) {\n    if (this.include(classNameToAdd)) return;\n    this.set($A(this).concat(classNameToAdd).join(' '));\n  },\n\n  remove: function(classNameToRemove) {\n    if (!this.include(classNameToRemove)) return;\n    this.set($A(this).without(classNameToRemove).join(' '));\n  },\n\n  toString: function() {\n    return $A(this).join(' ');\n  }\n};\n\nObject.extend(Element.ClassNames.prototype, Enumerable);\n\n/*--------------------------------------------------------------------------*/\n\nElement.addMethods();"
  },
  {
    "path": "example/rails2.3/public/robots.txt",
    "content": "# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-Agent: *\n# Disallow: /\n"
  },
  {
    "path": "example/rails2.3/script/about",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\n$LOAD_PATH.unshift \"#{RAILTIES_PATH}/builtin/rails_info\"\nrequire 'commands/about'\n"
  },
  {
    "path": "example/rails2.3/script/console",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/console'\n"
  },
  {
    "path": "example/rails2.3/script/dbconsole",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/dbconsole'\n"
  },
  {
    "path": "example/rails2.3/script/destroy",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/destroy'\n"
  },
  {
    "path": "example/rails2.3/script/generate",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/generate'\n"
  },
  {
    "path": "example/rails2.3/script/performance/benchmarker",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../../config/boot',  __FILE__)\nrequire 'commands/performance/benchmarker'\n"
  },
  {
    "path": "example/rails2.3/script/performance/profiler",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../../config/boot',  __FILE__)\nrequire 'commands/performance/profiler'\n"
  },
  {
    "path": "example/rails2.3/script/plugin",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/plugin'\n"
  },
  {
    "path": "example/rails2.3/script/runner",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/runner'\n"
  },
  {
    "path": "example/rails2.3/script/server",
    "content": "#!/usr/bin/env ruby\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'commands/server'\n"
  },
  {
    "path": "example/rails2.3/test/performance/browsing_test.rb",
    "content": "require 'test_helper'\nrequire 'performance_test_help'\n\n# Profiling results for each test method are written to tmp/performance.\nclass BrowsingTest < ActionController::PerformanceTest\n  def test_homepage\n    get '/'\n  end\nend\n"
  },
  {
    "path": "example/rails2.3/test/test_helper.rb",
    "content": "ENV[\"RAILS_ENV\"] = \"test\"\nrequire File.expand_path(File.dirname(__FILE__) + \"/../config/environment\")\nrequire 'test_help'\n\nclass ActiveSupport::TestCase\n  # Transactional fixtures accelerate your tests by wrapping each test method\n  # in a transaction that's rolled back on completion.  This ensures that the\n  # test database remains unchanged so your fixtures don't have to be reloaded\n  # between every test method.  Fewer database queries means faster tests.\n  #\n  # Read Mike Clark's excellent walkthrough at\n  #   http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting\n  #\n  # Every Active Record database supports transactions except MyISAM tables\n  # in MySQL.  Turn off transactional fixtures in this case; however, if you\n  # don't care one way or the other, switching from MyISAM to InnoDB tables\n  # is recommended.\n  #\n  # The only drawback to using transactional fixtures is when you actually \n  # need to test transactions.  Since your test is bracketed by a transaction,\n  # any transactions started in your code will be automatically rolled back.\n  self.use_transactional_fixtures = true\n\n  # Instantiated fixtures are slow, but give you @david where otherwise you\n  # would need people(:david).  If you don't want to migrate your existing\n  # test cases which use the @david style and don't mind the speed hit (each\n  # instantiated fixtures translates to a database query per test method),\n  # then set this back to true.\n  self.use_instantiated_fixtures  = false\n\n  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.\n  #\n  # Note: You'll currently still have to declare fixtures explicitly in integration tests\n  # -- they do not yet inherit this setting\n  fixtures :all\n\n  # Add more helper methods to be used by all tests here...\nend\n"
  },
  {
    "path": "example/rails3/Gemfile",
    "content": "source 'http://rubygems.org'\n\ngem 'rails', '~> 3.0.0'\n\n# Bundle edge Rails instead:\n# gem 'rails', :git => 'git://github.com/rails/rails.git'\n\ngem 'sqlite3-ruby', :require => 'sqlite3'\ngem 'analytical', :path => '../..'\n\n# Use unicorn as the web server\n# gem 'unicorn'\n\n# Deploy with Capistrano\n# gem 'capistrano'\n\n# Bundle the extra gems:\n# gem 'bj'\n# gem 'nokogiri', '1.4.1'\n# gem 'sqlite3-ruby', :require => 'sqlite3'\n# gem 'aws-s3', :require => 'aws/s3'\n\n# Bundle gems for certain environments:\n# gem 'rspec', :group => :test\n# group :test do\n#   gem 'webrat'\n# end\n"
  },
  {
    "path": "example/rails3/README",
    "content": "== Welcome to Rails\n\nRails is a web-application framework that includes everything needed to create \ndatabase-backed web applications according to the Model-View-Control pattern. \n\nThis pattern splits the view (also called the presentation) into \"dumb\" templates\nthat are primarily responsible for inserting pre-built data in between HTML tags.\nThe model contains the \"smart\" domain objects (such as Account, Product, Person,\nPost) that holds all the business logic and knows how to persist themselves to\na database. The controller handles the incoming requests (such as Save New Account,\nUpdate Product, Show Post) by manipulating the model and directing data to the view.\n\nIn Rails, the model is handled by what's called an object-relational mapping\nlayer entitled Active Record. This layer allows you to present the data from\ndatabase rows as objects and embellish these data objects with business logic\nmethods. You can read more about Active Record in\nlink:files/vendor/rails/activerecord/README.html.\n\nThe controller and view are handled by the Action Pack, which handles both\nlayers by its two parts: Action View and Action Controller. These two layers\nare bundled in a single package due to their heavy interdependence. This is\nunlike the relationship between the Active Record and Action Pack that is much\nmore separate. Each of these packages can be used independently outside of\nRails.  You can read more about Action Pack in\nlink:files/vendor/rails/actionpack/README.html.\n\n\n== Getting Started\n\n1. At the command prompt, start a new Rails application using the <tt>rails</tt> command\n   and your application name. Ex: rails myapp\n2. Change directory into myapp and start the web server: <tt>rails server</tt> (run with --help for options)\n3. Go to http://localhost:3000/ and get \"Welcome aboard: You're riding the Rails!\"\n4. Follow the guidelines to start developing your application\n\n\n== Web Servers\n\nBy default, Rails will try to use Mongrel if it's installed when started with <tt>rails server</tt>, otherwise\nRails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails\nwith a variety of other web servers.\n\nMongrel is a Ruby-based webserver with a C component (which requires compilation) that is\nsuitable for development and deployment of Rails applications. If you have Ruby Gems installed,\ngetting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.\nMore info at: http://mongrel.rubyforge.org\n\nSay other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or\nLighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use\nFCGI or proxy to a pack of Mongrels/Thin/Ebb servers.\n\n== Apache .htaccess example for FCGI/CGI\n\n# General Apache options\nAddHandler fastcgi-script .fcgi\nAddHandler cgi-script .cgi\nOptions +FollowSymLinks +ExecCGI\n\n# If you don't want Rails to look in certain directories,\n# use the following rewrite rules so that Apache won't rewrite certain requests\n# \n# Example:\n#   RewriteCond %{REQUEST_URI} ^/notrails.*\n#   RewriteRule .* - [L]\n\n# Redirect all requests not available on the filesystem to Rails\n# By default the cgi dispatcher is used which is very slow\n# \n# For better performance replace the dispatcher with the fastcgi one\n#\n# Example:\n#   RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]\nRewriteEngine On\n\n# If your Rails application is accessed via an Alias directive,\n# then you MUST also set the RewriteBase in this htaccess file.\n#\n# Example:\n#   Alias /myrailsapp /path/to/myrailsapp/public\n#   RewriteBase /myrailsapp\n\nRewriteRule ^$ index.html [QSA]\nRewriteRule ^([^.]+)$ $1.html [QSA]\nRewriteCond %{REQUEST_FILENAME} !-f\nRewriteRule ^(.*)$ dispatch.cgi [QSA,L]\n\n# In case Rails experiences terminal errors\n# Instead of displaying this message you can supply a file here which will be rendered instead\n# \n# Example:\n#   ErrorDocument 500 /500.html\n\nErrorDocument 500 \"<h2>Application error</h2>Rails application failed to start properly\"\n\n\n== Debugging Rails\n\nSometimes your application goes wrong.  Fortunately there are a lot of tools that\nwill help you debug it and get it back on the rails.\n\nFirst area to check is the application log files.  Have \"tail -f\" commands running\non the server.log and development.log. Rails will automatically display debugging\nand runtime information to these files. Debugging info will also be shown in the\nbrowser on requests from 127.0.0.1.\n\nYou can also log your own messages directly into the log file from your code using\nthe Ruby logger class from inside your controllers. Example:\n\n  class WeblogController < ActionController::Base\n    def destroy\n      @weblog = Weblog.find(params[:id])\n      @weblog.destroy\n      logger.info(\"#{Time.now} Destroyed Weblog ID ##{@weblog.id}!\")\n    end\n  end\n\nThe result will be a message in your log file along the lines of:\n\n  Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1\n\nMore information on how to use the logger is at http://www.ruby-doc.org/core/\n\nAlso, Ruby documentation can be found at http://www.ruby-lang.org/ including:\n\n* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/\n* Learn to Program: http://pine.fm/LearnToProgram/  (a beginners guide)\n\nThese two online (and free) books will bring you up to speed on the Ruby language\nand also on programming in general.\n\n\n== Debugger\n\nDebugger support is available through the debugger command when you start your Mongrel or\nWebrick server with --debugger. This means that you can break out of execution at any point\nin the code, investigate and change the model, AND then resume execution! \nYou need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'\nExample:\n\n  class WeblogController < ActionController::Base\n    def index\n      @posts = Post.find(:all)\n      debugger\n    end\n  end\n\nSo the controller will accept the action, run the first line, then present you\nwith a IRB prompt in the server window. Here you can do things like:\n\n  >> @posts.inspect\n  => \"[#<Post:0x14a6be8 @attributes={\\\"title\\\"=>nil, \\\"body\\\"=>nil, \\\"id\\\"=>\\\"1\\\"}>,\n       #<Post:0x14a6620 @attributes={\\\"title\\\"=>\\\"Rails you know!\\\", \\\"body\\\"=>\\\"Only ten..\\\", \\\"id\\\"=>\\\"2\\\"}>]\"\n  >> @posts.first.title = \"hello from a debugger\"\n  => \"hello from a debugger\"\n\n...and even better is that you can examine how your runtime objects actually work:\n\n  >> f = @posts.first\n  => #<Post:0x13630c4 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>\n  >> f.\n  Display all 152 possibilities? (y or n)\n\nFinally, when you're ready to resume execution, you enter \"cont\"\n\n\n== Console\n\nYou can interact with the domain model by starting the console through <tt>rails console</tt>.\nHere you'll have all parts of the application configured, just like it is when the\napplication is running. You can inspect domain models, change values, and save to the\ndatabase. Starting the script without arguments will launch it in the development environment.\nPassing an argument will specify a different environment, like <tt>rails console production</tt>.\n\nTo reload your controllers and models after launching the console run <tt>reload!</tt>\n\n== dbconsole\n\nYou can go to the command line of your database directly through <tt>rails dbconsole</tt>.\nYou would be connected to the database with the credentials defined in database.yml.\nStarting the script without arguments will connect you to the development database. Passing an\nargument will connect you to a different database, like <tt>rails dbconsole production</tt>.\nCurrently works for mysql, postgresql and sqlite.\n\n== Description of Contents\n\napp\n  Holds all the code that's specific to this particular application.\n\napp/controllers\n  Holds controllers that should be named like weblogs_controller.rb for\n  automated URL mapping. All controllers should descend from ApplicationController\n  which itself descends from ActionController::Base.\n\napp/models\n  Holds models that should be named like post.rb.\n  Most models will descend from ActiveRecord::Base.\n\napp/views\n  Holds the template files for the view that should be named like\n  weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby\n  syntax.\n\napp/views/layouts\n  Holds the template files for layouts to be used with views. This models the common\n  header/footer method of wrapping views. In your views, define a layout using the\n  <tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,\n  call <% yield %> to render the view using this layout.\n\napp/helpers\n  Holds view helpers that should be named like weblogs_helper.rb. These are generated\n  for you automatically when using rails generate for controllers. Helpers can be used to\n  wrap functionality for your views into methods.\n\nconfig\n  Configuration files for the Rails environment, the routing map, the database, and other dependencies.\n\ndb\n  Contains the database schema in schema.rb.  db/migrate contains all\n  the sequence of Migrations for your schema.\n\ndoc\n  This directory is where your application documentation will be stored when generated\n  using <tt>rake doc:app</tt>\n\nlib\n  Application specific libraries. Basically, any kind of custom code that doesn't\n  belong under controllers, models, or helpers. This directory is in the load path.\n\npublic\n  The directory available for the web server. Contains subdirectories for images, stylesheets,\n  and javascripts. Also contains the dispatchers and the default HTML files. This should be\n  set as the DOCUMENT_ROOT of your web server.\n\nscript\n  Helper scripts for automation and generation.\n\ntest\n  Unit and functional tests along with fixtures. When using the rails generate command, template\n  test files will be generated for you and placed in this directory.\n\nvendor\n  External libraries that the application depends on. Also includes the plugins subdirectory.\n  If the app has frozen rails, those gems also go here, under vendor/rails/.\n  This directory is in the load path.\n"
  },
  {
    "path": "example/rails3/Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire File.expand_path('../config/application', __FILE__)\n\nrequire 'rake'\nrequire 'rake/testtask'\nrequire 'rake/rdoctask'\n\nRails::Application.load_tasks\n"
  },
  {
    "path": "example/rails3/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  protect_from_forgery\n  layout 'application'\n\n  analytical :modules=>[:console, :google, :clicky], :disable_if=>lambda{|controller| false}, :use_session_store=>true\nend\n"
  },
  {
    "path": "example/rails3/app/controllers/page_controller.rb",
    "content": "class PageController < ApplicationController\n  def index\n  end\n\n  def test_a\n  end\n\n  def test_b\n    analytical.clicky.track('track link in A')\n    analytical.track 'track in controller'\n  end\n\n  def test_c\n    analytical.track 'track in controller that redirects'\n    redirect_to root_path\n  end\n\nend\n"
  },
  {
    "path": "example/rails3/app/helpers/application_helper.rb",
    "content": "module ApplicationHelper\nend\n"
  },
  {
    "path": "example/rails3/app/helpers/page_helper.rb",
    "content": "module PageHelper\nend\n"
  },
  {
    "path": "example/rails3/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Example</title>\n  <%= stylesheet_link_tag :all %>\n  <%= javascript_include_tag :defaults %>\n  <%= csrf_meta_tag %>\n\n\t<% analytical.identify '5', :email=>'josh@feefighters.com' %>\n\t<%= raw analytical.head_javascript %>\n</head>\n<body>\n\t<%= raw analytical.body_prepend_javascript %>\n\t<%= yield %>\n\t<%= raw analytical.body_append_javascript %>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3/app/views/page/index.html.erb",
    "content": "<h1>Page#index</h1>\n<p>\n\t<%= link_to 'A', '/page/test_a' %><br />\n\t<%= link_to 'B', '/page/test_b' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n</p>"
  },
  {
    "path": "example/rails3/app/views/page/test_a.html.erb",
    "content": "<% analytical.track 'track in view A' %>\n<% analytical.event 'Something Important', {:some=>'data'} %>\n<h1>Page#test_a</h1>\n<p>\n\t<%= link_to 'B', '/page/test_b' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n\t<%= link_to 'index', '/' %><br />\n\t<a href='#' onclick=\"<%= analytical.now.track('track link in A') %>; return false;\">click me to track all modules</a><br />\n</p>\n\n\n"
  },
  {
    "path": "example/rails3/app/views/page/test_b.html.erb",
    "content": "<h1>Page#test_b</h1>\n<p>\n\t<%= link_to 'A', '/page/test_a' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n\t<%= link_to 'index', '/' %><br />\n\t<a href='#' onclick=\"<%= analytical.console.track('track link in A') %>; return false;\">track console only</a><br />\n</p>\n"
  },
  {
    "path": "example/rails3/config/analytical.yml",
    "content": "google:\n  key: google_12345\nclicky:\n  key: clicky_12345\nkiss_metrics:\n  js_url_key: //doug1izaerwt3.cloudfront.net/kiss_metrics_12345.1.js\nadwords:\n  'Some Event':\n    id: 4444555555\n    language: en\n    format: 2\n    color: ffffff\n    label: xxxxxxxxxxxxxxxx\n    value: 0\n  'Another Event':\n    id: 1111333333\n    language: en\n    format: 2\n    color: ffffff\n    label: yyyyyyyyyyyyyyyy\n    value: 0\nchartbeat:\n  key: chartbeat_12345\n  domain: your.domain.com\ncomscore:\n  key: comscore_12345\noptimizely:\n  key: optimizely_12345\nquantcast:\n  key: p-abc123"
  },
  {
    "path": "example/rails3/config/application.rb",
    "content": "require File.expand_path('../boot', __FILE__)\n\nrequire 'rails/all'\n\n# If you have a Gemfile, require the gems listed there, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(:default, Rails.env) if defined?(Bundler)\n\nmodule Example\n  class Application < Rails::Application\n    # Settings in config/environments/* take precedence over those specified here.\n    # Application configuration should go into files in config/initializers\n    # -- all .rb files in that directory are automatically loaded.\n\n    # Add additional load paths for your own custom dirs\n    # config.load_paths += %W( #{config.root}/extras )\n\n    # Only load the plugins named here, in the order given (default is alphabetical).\n    # :all can be used as a placeholder for all plugins not explicitly named\n    # config.plugins = [ :exception_notification, :ssl_requirement, :all ]\n\n    # Activate observers that should always be running\n    # config.active_record.observers = :cacher, :garbage_collector, :forum_observer\n\n    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.\n    # Run \"rake -D time\" for a list of tasks for finding time zone names. Default is UTC.\n    # config.time_zone = 'Central Time (US & Canada)'\n\n    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.\n    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]\n    # config.i18n.default_locale = :de\n\n    # Configure generators values. Many other options are available, be sure to check the documentation.\n    # config.generators do |g|\n    #   g.orm             :active_record\n    #   g.template_engine :erb\n    #   g.test_framework  :test_unit, :fixture => true\n    # end\n\n    # Configure the default encoding used in templates for Ruby 1.9.\n    config.encoding = \"utf-8\"\n\n    # Configure sensitive parameters which will be filtered from the log file.\n    config.filter_parameters += [:password]\n  end\nend\n"
  },
  {
    "path": "example/rails3/config/boot.rb",
    "content": "require 'rubygems'\n# Set up gems listed in the Gemfile.\nif File.exist?(File.expand_path('../../Gemfile', __FILE__))\n  require 'bundler'\n  Bundler.setup\nend\n"
  },
  {
    "path": "example/rails3/config/database.yml",
    "content": "# SQLite version 3.x\n#   gem install sqlite3-ruby (not necessary on OS X Leopard)\ndevelopment:\n  adapter: sqlite3\n  database: db/development.sqlite3\n  pool: 5\n  timeout: 5000\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  adapter: sqlite3\n  database: db/test.sqlite3\n  pool: 5\n  timeout: 5000\n\nproduction:\n  adapter: sqlite3\n  database: db/production.sqlite3\n  pool: 5\n  timeout: 5000\n"
  },
  {
    "path": "example/rails3/config/environment.rb",
    "content": "# Load the rails application\nrequire File.expand_path('../application', __FILE__)\n\n# Initialize the rails application\nExample::Application.initialize!\n\n# Rails.backtrace_cleaner.remove_silencers!"
  },
  {
    "path": "example/rails3/config/environments/development.rb",
    "content": "Example::Application.configure do\n  # Settings specified here will take precedence over those in config/environment.rb\n\n  # In the development environment your application's code is reloaded on\n  # every request.  This slows down response time but is perfect for development\n  # since you don't have to restart the webserver when you make code changes.\n  config.cache_classes = false\n\n  # Log error messages when you accidentally call methods on nil.\n  config.whiny_nils = true\n\n  # Show full error reports and disable caching\n  config.consider_all_requests_local       = true\n  config.action_view.debug_rjs             = true\n  config.action_controller.perform_caching = false\n\n  # Don't care if the mailer can't send\n  config.action_mailer.raise_delivery_errors = false\n\n  config.active_support.deprecation = :log\nend\n"
  },
  {
    "path": "example/rails3/config/environments/production.rb",
    "content": "Example::Application.configure do\n  # Settings specified here will take precedence over those in config/environment.rb\n\n  # The production environment is meant for finished, \"live\" apps.\n  # Code is not reloaded between requests\n  config.cache_classes = true\n\n  # Full error reports are disabled and caching is turned on\n  config.consider_all_requests_local       = false\n  config.action_controller.perform_caching = true\n\n  # Specifies the header that your server uses for sending files\n  config.action_dispatch.x_sendfile_header = \"X-Sendfile\"\n\n  # For nginx:\n  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'\n\n  # If you have no front-end server that supports something like X-Sendfile,\n  # just comment this out and Rails will serve the files\n\n  # See everything in the log (default is :info)\n  # config.log_level = :debug\n\n  # Use a different logger for distributed setups\n  # config.logger = SyslogLogger.new\n\n  # Use a different cache store in production\n  # config.cache_store = :mem_cache_store\n\n  # Disable Rails's static asset server\n  # In production, Apache or nginx will already do this\n  config.serve_static_assets = false\n\n  # Enable serving of images, stylesheets, and javascripts from an asset server\n  # config.action_controller.asset_host = \"http://assets.example.com\"\n\n  # Disable delivery errors, bad email addresses will be ignored\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable threaded mode\n  # config.threadsafe!\nend\n"
  },
  {
    "path": "example/rails3/config/environments/test.rb",
    "content": "Example::Application.configure do\n  # Settings specified here will take precedence over those in config/environment.rb\n\n  # The test environment is used exclusively to run your application's\n  # test suite.  You never need to work with it otherwise.  Remember that\n  # your test database is \"scratch space\" for the test suite and is wiped\n  # and recreated between test runs.  Don't rely on the data there!\n  config.cache_classes = true\n\n  # Log error messages when you accidentally call methods on nil.\n  config.whiny_nils = true\n\n  # Show full error reports and disable caching\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n\n  # Raise exceptions instead of rendering exception templates\n  config.action_dispatch.show_exceptions = false\n\n  # Disable request forgery protection in test environment\n  config.action_controller.allow_forgery_protection    = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Use SQL instead of Active Record's schema dumper when creating the test database.\n  # This is necessary if your schema can't be completely dumped by the schema dumper,\n  # like if you have constraints or database-specific column types\n  # config.active_record.schema_format = :sql\nend\n"
  },
  {
    "path": "example/rails3/config/initializers/backtrace_silencers.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.\nRails.backtrace_cleaner.remove_silencers!\n"
  },
  {
    "path": "example/rails3/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format \n# (all these examples are active by default):\n# ActiveSupport::Inflector.inflections do |inflect|\n#   inflect.plural /^(ox)$/i, '\\1en'\n#   inflect.singular /^(ox)en/i, '\\1'\n#   inflect.irregular 'person', 'people'\n#   inflect.uncountable %w( fish sheep )\n# end\n"
  },
  {
    "path": "example/rails3/config/initializers/mime_types.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\n# Mime::Type.register_alias \"text/html\", :iphone\n"
  },
  {
    "path": "example/rails3/config/initializers/secret_token.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Your secret key for verifying the integrity of signed cookies.\n# If you change this key, all old signed cookies will become invalid!\n# Make sure the secret is at least 30 characters and all random, \n# no regular words or you'll be exposed to dictionary attacks.\nRails.application.config.secret_token = 'b978bc4b8224be53593088340d0a4bf8f954eec388027567a326ffacec8c6c2e3d00374a5ce9cbdbb5a4c6cd9e3983985b39980ee5c2a35a16aad8a5506d69f3'\n"
  },
  {
    "path": "example/rails3/config/initializers/session_store.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\nRails.application.config.session_store :cookie_store, :key => '_example_session'\n\n# Use the database for sessions instead of the cookie-based default,\n# which shouldn't be used to store highly confidential information\n# (create the session table with \"rake db:sessions:create\")\n# Rails.application.config.session_store :active_record_store\n"
  },
  {
    "path": "example/rails3/config/locales/en.yml",
    "content": "# Sample localization file for English. Add more files in this directory for other locales.\n# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "example/rails3/config/routes.rb",
    "content": "Example::Application.routes.draw do |map|\n  get \"page/index\"\n  get \"page/test_a\"\n  get \"page/test_b\"\n  get \"page/test_c\"\n\n  # You can have the root of your site routed with \"root\"\n  # just remember to delete public/index.html.\n  root :to => \"page#index\"\n\n  # The priority is based upon order of creation:\n  # first created -> highest priority.\n\n  # Sample of regular route:\n  #   match 'products/:id' => 'catalog#view'\n  # Keep in mind you can assign values other than :controller and :action\n\n  # Sample of named route:\n  #   match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase\n  # This route can be invoked with purchase_url(:id => product.id)\n\n  # Sample resource route (maps HTTP verbs to controller actions automatically):\n  #   resources :products\n\n  # Sample resource route with options:\n  #   resources :products do\n  #     member do\n  #       get :short\n  #       post :toggle\n  #     end\n  #\n  #     collection do\n  #       get :sold\n  #     end\n  #   end\n\n  # Sample resource route with sub-resources:\n  #   resources :products do\n  #     resources :comments, :sales\n  #     resource :seller\n  #   end\n\n  # Sample resource route with more complex sub-resources\n  #   resources :products do\n  #     resources :comments\n  #     resources :sales do\n  #       get :recent, :on => :collection\n  #     end\n  #   end\n\n  # Sample resource route within a namespace:\n  #   namespace :admin do\n  #     # Directs /admin/products/* to Admin::ProductsController\n  #     # (app/controllers/admin/products_controller.rb)\n  #     resources :products\n  #   end\n\n  # See how all your routes lay out with \"rake routes\"\n\n  # This is a legacy wild controller route that's not recommended for RESTful applications.\n  # Note: This route will make all actions in every controller accessible via GET requests.\n  # match ':controller(/:action(/:id(.:format)))'\nend\n"
  },
  {
    "path": "example/rails3/config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire ::File.expand_path('../config/environment',  __FILE__)\nrun Example::Application\n"
  },
  {
    "path": "example/rails3/db/development.sqlite3",
    "content": ""
  },
  {
    "path": "example/rails3/db/production.sqlite3",
    "content": ""
  },
  {
    "path": "example/rails3/db/seeds.rb",
    "content": "# This file should contain all the record creation needed to seed the database with its default values.\n# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).\n#\n# Examples:\n#\n#   cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])\n#   Mayor.create(:name => 'Daley', :city => cities.first)\n"
  },
  {
    "path": "example/rails3/doc/README_FOR_APP",
    "content": "Use this README file to introduce your application and point to useful places in the API for learning more.\nRun \"rake doc:app\" to generate API documentation for your models, controllers, helpers, and libraries.\n"
  },
  {
    "path": "example/rails3/lib/tasks/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3/public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <h1>The page you were looking for doesn't exist.</h1>\n    <p>You may have mistyped the address or the page may have moved.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3/public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <h1>The change you wanted was rejected.</h1>\n    <p>Maybe you tried to change something you didn't have access to.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3/public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <h1>We're sorry, but something went wrong.</h1>\n    <p>We've been notified about this issue and we'll take a look at it shortly.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3/public/javascripts/application.js",
    "content": "// Place your application-specific JavaScript functions and classes here\n// This file is automatically included by javascript_include_tag :defaults\n"
  },
  {
    "path": "example/rails3/public/javascripts/controls.js",
    "content": "// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009\n\n// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n//           (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)\n//           (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)\n// Contributors:\n//  Richard Livsey\n//  Rahul Bhargava\n//  Rob Wills\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\n// Autocompleter.Base handles all the autocompletion functionality\n// that's independent of the data source for autocompletion. This\n// includes drawing the autocompletion menu, observing keyboard\n// and mouse events, and similar.\n//\n// Specific autocompleters need to provide, at the very least,\n// a getUpdatedChoices function that will be invoked every time\n// the text inside the monitored textbox changes. This method\n// should get the text for which to provide autocompletion by\n// invoking this.getToken(), NOT by directly accessing\n// this.element.value. This is to allow incremental tokenized\n// autocompletion. Specific auto-completion logic (AJAX, etc)\n// belongs in getUpdatedChoices.\n//\n// Tokenized incremental autocompletion is enabled automatically\n// when an autocompleter is instantiated with the 'tokens' option\n// in the options parameter, e.g.:\n// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });\n// will incrementally autocomplete with a comma as the token.\n// Additionally, ',' in the above example can be replaced with\n// a token array, e.g. { tokens: [',', '\\n'] } which\n// enables autocompletion on multiple tokens. This is most\n// useful when one of the tokens is \\n (a newline), as it\n// allows smart autocompletion after linebreaks.\n\nif(typeof Effect == 'undefined')\n  throw(\"controls.js requires including script.aculo.us' effects.js library\");\n\nvar Autocompleter = { };\nAutocompleter.Base = Class.create({\n  baseInitialize: function(element, update, options) {\n    element          = $(element);\n    this.element     = element;\n    this.update      = $(update);\n    this.hasFocus    = false;\n    this.changed     = false;\n    this.active      = false;\n    this.index       = 0;\n    this.entryCount  = 0;\n    this.oldElementValue = this.element.value;\n\n    if(this.setOptions)\n      this.setOptions(options);\n    else\n      this.options = options || { };\n\n    this.options.paramName    = this.options.paramName || this.element.name;\n    this.options.tokens       = this.options.tokens || [];\n    this.options.frequency    = this.options.frequency || 0.4;\n    this.options.minChars     = this.options.minChars || 1;\n    this.options.onShow       = this.options.onShow ||\n      function(element, update){\n        if(!update.style.position || update.style.position=='absolute') {\n          update.style.position = 'absolute';\n          Position.clone(element, update, {\n            setHeight: false,\n            offsetTop: element.offsetHeight\n          });\n        }\n        Effect.Appear(update,{duration:0.15});\n      };\n    this.options.onHide = this.options.onHide ||\n      function(element, update){ new Effect.Fade(update,{duration:0.15}) };\n\n    if(typeof(this.options.tokens) == 'string')\n      this.options.tokens = new Array(this.options.tokens);\n    // Force carriage returns as token delimiters anyway\n    if (!this.options.tokens.include('\\n'))\n      this.options.tokens.push('\\n');\n\n    this.observer = null;\n\n    this.element.setAttribute('autocomplete','off');\n\n    Element.hide(this.update);\n\n    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));\n    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));\n  },\n\n  show: function() {\n    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);\n    if(!this.iefix &&\n      (Prototype.Browser.IE) &&\n      (Element.getStyle(this.update, 'position')=='absolute')) {\n      new Insertion.After(this.update,\n       '<iframe id=\"' + this.update.id + '_iefix\" '+\n       'style=\"display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);\" ' +\n       'src=\"javascript:false;\" frameborder=\"0\" scrolling=\"no\"></iframe>');\n      this.iefix = $(this.update.id+'_iefix');\n    }\n    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);\n  },\n\n  fixIEOverlapping: function() {\n    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});\n    this.iefix.style.zIndex = 1;\n    this.update.style.zIndex = 2;\n    Element.show(this.iefix);\n  },\n\n  hide: function() {\n    this.stopIndicator();\n    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);\n    if(this.iefix) Element.hide(this.iefix);\n  },\n\n  startIndicator: function() {\n    if(this.options.indicator) Element.show(this.options.indicator);\n  },\n\n  stopIndicator: function() {\n    if(this.options.indicator) Element.hide(this.options.indicator);\n  },\n\n  onKeyPress: function(event) {\n    if(this.active)\n      switch(event.keyCode) {\n       case Event.KEY_TAB:\n       case Event.KEY_RETURN:\n         this.selectEntry();\n         Event.stop(event);\n       case Event.KEY_ESC:\n         this.hide();\n         this.active = false;\n         Event.stop(event);\n         return;\n       case Event.KEY_LEFT:\n       case Event.KEY_RIGHT:\n         return;\n       case Event.KEY_UP:\n         this.markPrevious();\n         this.render();\n         Event.stop(event);\n         return;\n       case Event.KEY_DOWN:\n         this.markNext();\n         this.render();\n         Event.stop(event);\n         return;\n      }\n     else\n       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||\n         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;\n\n    this.changed = true;\n    this.hasFocus = true;\n\n    if(this.observer) clearTimeout(this.observer);\n      this.observer =\n        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);\n  },\n\n  activate: function() {\n    this.changed = false;\n    this.hasFocus = true;\n    this.getUpdatedChoices();\n  },\n\n  onHover: function(event) {\n    var element = Event.findElement(event, 'LI');\n    if(this.index != element.autocompleteIndex)\n    {\n        this.index = element.autocompleteIndex;\n        this.render();\n    }\n    Event.stop(event);\n  },\n\n  onClick: function(event) {\n    var element = Event.findElement(event, 'LI');\n    this.index = element.autocompleteIndex;\n    this.selectEntry();\n    this.hide();\n  },\n\n  onBlur: function(event) {\n    // needed to make click events working\n    setTimeout(this.hide.bind(this), 250);\n    this.hasFocus = false;\n    this.active = false;\n  },\n\n  render: function() {\n    if(this.entryCount > 0) {\n      for (var i = 0; i < this.entryCount; i++)\n        this.index==i ?\n          Element.addClassName(this.getEntry(i),\"selected\") :\n          Element.removeClassName(this.getEntry(i),\"selected\");\n      if(this.hasFocus) {\n        this.show();\n        this.active = true;\n      }\n    } else {\n      this.active = false;\n      this.hide();\n    }\n  },\n\n  markPrevious: function() {\n    if(this.index > 0) this.index--;\n      else this.index = this.entryCount-1;\n    this.getEntry(this.index).scrollIntoView(true);\n  },\n\n  markNext: function() {\n    if(this.index < this.entryCount-1) this.index++;\n      else this.index = 0;\n    this.getEntry(this.index).scrollIntoView(false);\n  },\n\n  getEntry: function(index) {\n    return this.update.firstChild.childNodes[index];\n  },\n\n  getCurrentEntry: function() {\n    return this.getEntry(this.index);\n  },\n\n  selectEntry: function() {\n    this.active = false;\n    this.updateElement(this.getCurrentEntry());\n  },\n\n  updateElement: function(selectedElement) {\n    if (this.options.updateElement) {\n      this.options.updateElement(selectedElement);\n      return;\n    }\n    var value = '';\n    if (this.options.select) {\n      var nodes = $(selectedElement).select('.' + this.options.select) || [];\n      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);\n    } else\n      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');\n\n    var bounds = this.getTokenBounds();\n    if (bounds[0] != -1) {\n      var newValue = this.element.value.substr(0, bounds[0]);\n      var whitespace = this.element.value.substr(bounds[0]).match(/^\\s+/);\n      if (whitespace)\n        newValue += whitespace[0];\n      this.element.value = newValue + value + this.element.value.substr(bounds[1]);\n    } else {\n      this.element.value = value;\n    }\n    this.oldElementValue = this.element.value;\n    this.element.focus();\n\n    if (this.options.afterUpdateElement)\n      this.options.afterUpdateElement(this.element, selectedElement);\n  },\n\n  updateChoices: function(choices) {\n    if(!this.changed && this.hasFocus) {\n      this.update.innerHTML = choices;\n      Element.cleanWhitespace(this.update);\n      Element.cleanWhitespace(this.update.down());\n\n      if(this.update.firstChild && this.update.down().childNodes) {\n        this.entryCount =\n          this.update.down().childNodes.length;\n        for (var i = 0; i < this.entryCount; i++) {\n          var entry = this.getEntry(i);\n          entry.autocompleteIndex = i;\n          this.addObservers(entry);\n        }\n      } else {\n        this.entryCount = 0;\n      }\n\n      this.stopIndicator();\n      this.index = 0;\n\n      if(this.entryCount==1 && this.options.autoSelect) {\n        this.selectEntry();\n        this.hide();\n      } else {\n        this.render();\n      }\n    }\n  },\n\n  addObservers: function(element) {\n    Event.observe(element, \"mouseover\", this.onHover.bindAsEventListener(this));\n    Event.observe(element, \"click\", this.onClick.bindAsEventListener(this));\n  },\n\n  onObserverEvent: function() {\n    this.changed = false;\n    this.tokenBounds = null;\n    if(this.getToken().length>=this.options.minChars) {\n      this.getUpdatedChoices();\n    } else {\n      this.active = false;\n      this.hide();\n    }\n    this.oldElementValue = this.element.value;\n  },\n\n  getToken: function() {\n    var bounds = this.getTokenBounds();\n    return this.element.value.substring(bounds[0], bounds[1]).strip();\n  },\n\n  getTokenBounds: function() {\n    if (null != this.tokenBounds) return this.tokenBounds;\n    var value = this.element.value;\n    if (value.strip().empty()) return [-1, 0];\n    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);\n    var offset = (diff == this.oldElementValue.length ? 1 : 0);\n    var prevTokenPos = -1, nextTokenPos = value.length;\n    var tp;\n    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {\n      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);\n      if (tp > prevTokenPos) prevTokenPos = tp;\n      tp = value.indexOf(this.options.tokens[index], diff + offset);\n      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;\n    }\n    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);\n  }\n});\n\nAutocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {\n  var boundary = Math.min(newS.length, oldS.length);\n  for (var index = 0; index < boundary; ++index)\n    if (newS[index] != oldS[index])\n      return index;\n  return boundary;\n};\n\nAjax.Autocompleter = Class.create(Autocompleter.Base, {\n  initialize: function(element, update, url, options) {\n    this.baseInitialize(element, update, options);\n    this.options.asynchronous  = true;\n    this.options.onComplete    = this.onComplete.bind(this);\n    this.options.defaultParams = this.options.parameters || null;\n    this.url                   = url;\n  },\n\n  getUpdatedChoices: function() {\n    this.startIndicator();\n\n    var entry = encodeURIComponent(this.options.paramName) + '=' +\n      encodeURIComponent(this.getToken());\n\n    this.options.parameters = this.options.callback ?\n      this.options.callback(this.element, entry) : entry;\n\n    if(this.options.defaultParams)\n      this.options.parameters += '&' + this.options.defaultParams;\n\n    new Ajax.Request(this.url, this.options);\n  },\n\n  onComplete: function(request) {\n    this.updateChoices(request.responseText);\n  }\n});\n\n// The local array autocompleter. Used when you'd prefer to\n// inject an array of autocompletion options into the page, rather\n// than sending out Ajax queries, which can be quite slow sometimes.\n//\n// The constructor takes four parameters. The first two are, as usual,\n// the id of the monitored textbox, and id of the autocompletion menu.\n// The third is the array you want to autocomplete from, and the fourth\n// is the options block.\n//\n// Extra local autocompletion options:\n// - choices - How many autocompletion choices to offer\n//\n// - partialSearch - If false, the autocompleter will match entered\n//                    text only at the beginning of strings in the\n//                    autocomplete array. Defaults to true, which will\n//                    match text at the beginning of any *word* in the\n//                    strings in the autocomplete array. If you want to\n//                    search anywhere in the string, additionally set\n//                    the option fullSearch to true (default: off).\n//\n// - fullSsearch - Search anywhere in autocomplete array strings.\n//\n// - partialChars - How many characters to enter before triggering\n//                   a partial match (unlike minChars, which defines\n//                   how many characters are required to do any match\n//                   at all). Defaults to 2.\n//\n// - ignoreCase - Whether to ignore case when autocompleting.\n//                 Defaults to true.\n//\n// It's possible to pass in a custom function as the 'selector'\n// option, if you prefer to write your own autocompletion logic.\n// In that case, the other options above will not apply unless\n// you support them.\n\nAutocompleter.Local = Class.create(Autocompleter.Base, {\n  initialize: function(element, update, array, options) {\n    this.baseInitialize(element, update, options);\n    this.options.array = array;\n  },\n\n  getUpdatedChoices: function() {\n    this.updateChoices(this.options.selector(this));\n  },\n\n  setOptions: function(options) {\n    this.options = Object.extend({\n      choices: 10,\n      partialSearch: true,\n      partialChars: 2,\n      ignoreCase: true,\n      fullSearch: false,\n      selector: function(instance) {\n        var ret       = []; // Beginning matches\n        var partial   = []; // Inside matches\n        var entry     = instance.getToken();\n        var count     = 0;\n\n        for (var i = 0; i < instance.options.array.length &&\n          ret.length < instance.options.choices ; i++) {\n\n          var elem = instance.options.array[i];\n          var foundPos = instance.options.ignoreCase ?\n            elem.toLowerCase().indexOf(entry.toLowerCase()) :\n            elem.indexOf(entry);\n\n          while (foundPos != -1) {\n            if (foundPos == 0 && elem.length != entry.length) {\n              ret.push(\"<li><strong>\" + elem.substr(0, entry.length) + \"</strong>\" +\n                elem.substr(entry.length) + \"</li>\");\n              break;\n            } else if (entry.length >= instance.options.partialChars &&\n              instance.options.partialSearch && foundPos != -1) {\n              if (instance.options.fullSearch || /\\s/.test(elem.substr(foundPos-1,1))) {\n                partial.push(\"<li>\" + elem.substr(0, foundPos) + \"<strong>\" +\n                  elem.substr(foundPos, entry.length) + \"</strong>\" + elem.substr(\n                  foundPos + entry.length) + \"</li>\");\n                break;\n              }\n            }\n\n            foundPos = instance.options.ignoreCase ?\n              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :\n              elem.indexOf(entry, foundPos + 1);\n\n          }\n        }\n        if (partial.length)\n          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));\n        return \"<ul>\" + ret.join('') + \"</ul>\";\n      }\n    }, options || { });\n  }\n});\n\n// AJAX in-place editor and collection editor\n// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).\n\n// Use this if you notice weird scrolling problems on some browsers,\n// the DOM might be a bit confused when this gets called so do this\n// waits 1 ms (with setTimeout) until it does the activation\nField.scrollFreeActivate = function(field) {\n  setTimeout(function() {\n    Field.activate(field);\n  }, 1);\n};\n\nAjax.InPlaceEditor = Class.create({\n  initialize: function(element, url, options) {\n    this.url = url;\n    this.element = element = $(element);\n    this.prepareOptions();\n    this._controls = { };\n    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!\n    Object.extend(this.options, options || { });\n    if (!this.options.formId && this.element.id) {\n      this.options.formId = this.element.id + '-inplaceeditor';\n      if ($(this.options.formId))\n        this.options.formId = '';\n    }\n    if (this.options.externalControl)\n      this.options.externalControl = $(this.options.externalControl);\n    if (!this.options.externalControl)\n      this.options.externalControlOnly = false;\n    this._originalBackground = this.element.getStyle('background-color') || 'transparent';\n    this.element.title = this.options.clickToEditText;\n    this._boundCancelHandler = this.handleFormCancellation.bind(this);\n    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);\n    this._boundFailureHandler = this.handleAJAXFailure.bind(this);\n    this._boundSubmitHandler = this.handleFormSubmission.bind(this);\n    this._boundWrapperHandler = this.wrapUp.bind(this);\n    this.registerListeners();\n  },\n  checkForEscapeOrReturn: function(e) {\n    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;\n    if (Event.KEY_ESC == e.keyCode)\n      this.handleFormCancellation(e);\n    else if (Event.KEY_RETURN == e.keyCode)\n      this.handleFormSubmission(e);\n  },\n  createControl: function(mode, handler, extraClasses) {\n    var control = this.options[mode + 'Control'];\n    var text = this.options[mode + 'Text'];\n    if ('button' == control) {\n      var btn = document.createElement('input');\n      btn.type = 'submit';\n      btn.value = text;\n      btn.className = 'editor_' + mode + '_button';\n      if ('cancel' == mode)\n        btn.onclick = this._boundCancelHandler;\n      this._form.appendChild(btn);\n      this._controls[mode] = btn;\n    } else if ('link' == control) {\n      var link = document.createElement('a');\n      link.href = '#';\n      link.appendChild(document.createTextNode(text));\n      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;\n      link.className = 'editor_' + mode + '_link';\n      if (extraClasses)\n        link.className += ' ' + extraClasses;\n      this._form.appendChild(link);\n      this._controls[mode] = link;\n    }\n  },\n  createEditField: function() {\n    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());\n    var fld;\n    if (1 >= this.options.rows && !/\\r|\\n/.test(this.getText())) {\n      fld = document.createElement('input');\n      fld.type = 'text';\n      var size = this.options.size || this.options.cols || 0;\n      if (0 < size) fld.size = size;\n    } else {\n      fld = document.createElement('textarea');\n      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);\n      fld.cols = this.options.cols || 40;\n    }\n    fld.name = this.options.paramName;\n    fld.value = text; // No HTML breaks conversion anymore\n    fld.className = 'editor_field';\n    if (this.options.submitOnBlur)\n      fld.onblur = this._boundSubmitHandler;\n    this._controls.editor = fld;\n    if (this.options.loadTextURL)\n      this.loadExternalText();\n    this._form.appendChild(this._controls.editor);\n  },\n  createForm: function() {\n    var ipe = this;\n    function addText(mode, condition) {\n      var text = ipe.options['text' + mode + 'Controls'];\n      if (!text || condition === false) return;\n      ipe._form.appendChild(document.createTextNode(text));\n    };\n    this._form = $(document.createElement('form'));\n    this._form.id = this.options.formId;\n    this._form.addClassName(this.options.formClassName);\n    this._form.onsubmit = this._boundSubmitHandler;\n    this.createEditField();\n    if ('textarea' == this._controls.editor.tagName.toLowerCase())\n      this._form.appendChild(document.createElement('br'));\n    if (this.options.onFormCustomization)\n      this.options.onFormCustomization(this, this._form);\n    addText('Before', this.options.okControl || this.options.cancelControl);\n    this.createControl('ok', this._boundSubmitHandler);\n    addText('Between', this.options.okControl && this.options.cancelControl);\n    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');\n    addText('After', this.options.okControl || this.options.cancelControl);\n  },\n  destroy: function() {\n    if (this._oldInnerHTML)\n      this.element.innerHTML = this._oldInnerHTML;\n    this.leaveEditMode();\n    this.unregisterListeners();\n  },\n  enterEditMode: function(e) {\n    if (this._saving || this._editing) return;\n    this._editing = true;\n    this.triggerCallback('onEnterEditMode');\n    if (this.options.externalControl)\n      this.options.externalControl.hide();\n    this.element.hide();\n    this.createForm();\n    this.element.parentNode.insertBefore(this._form, this.element);\n    if (!this.options.loadTextURL)\n      this.postProcessEditField();\n    if (e) Event.stop(e);\n  },\n  enterHover: function(e) {\n    if (this.options.hoverClassName)\n      this.element.addClassName(this.options.hoverClassName);\n    if (this._saving) return;\n    this.triggerCallback('onEnterHover');\n  },\n  getText: function() {\n    return this.element.innerHTML.unescapeHTML();\n  },\n  handleAJAXFailure: function(transport) {\n    this.triggerCallback('onFailure', transport);\n    if (this._oldInnerHTML) {\n      this.element.innerHTML = this._oldInnerHTML;\n      this._oldInnerHTML = null;\n    }\n  },\n  handleFormCancellation: function(e) {\n    this.wrapUp();\n    if (e) Event.stop(e);\n  },\n  handleFormSubmission: function(e) {\n    var form = this._form;\n    var value = $F(this._controls.editor);\n    this.prepareSubmission();\n    var params = this.options.callback(form, value) || '';\n    if (Object.isString(params))\n      params = params.toQueryParams();\n    params.editorId = this.element.id;\n    if (this.options.htmlResponse) {\n      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);\n      Object.extend(options, {\n        parameters: params,\n        onComplete: this._boundWrapperHandler,\n        onFailure: this._boundFailureHandler\n      });\n      new Ajax.Updater({ success: this.element }, this.url, options);\n    } else {\n      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n      Object.extend(options, {\n        parameters: params,\n        onComplete: this._boundWrapperHandler,\n        onFailure: this._boundFailureHandler\n      });\n      new Ajax.Request(this.url, options);\n    }\n    if (e) Event.stop(e);\n  },\n  leaveEditMode: function() {\n    this.element.removeClassName(this.options.savingClassName);\n    this.removeForm();\n    this.leaveHover();\n    this.element.style.backgroundColor = this._originalBackground;\n    this.element.show();\n    if (this.options.externalControl)\n      this.options.externalControl.show();\n    this._saving = false;\n    this._editing = false;\n    this._oldInnerHTML = null;\n    this.triggerCallback('onLeaveEditMode');\n  },\n  leaveHover: function(e) {\n    if (this.options.hoverClassName)\n      this.element.removeClassName(this.options.hoverClassName);\n    if (this._saving) return;\n    this.triggerCallback('onLeaveHover');\n  },\n  loadExternalText: function() {\n    this._form.addClassName(this.options.loadingClassName);\n    this._controls.editor.disabled = true;\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        this._form.removeClassName(this.options.loadingClassName);\n        var text = transport.responseText;\n        if (this.options.stripLoadedTextTags)\n          text = text.stripTags();\n        this._controls.editor.value = text;\n        this._controls.editor.disabled = false;\n        this.postProcessEditField();\n      }.bind(this),\n      onFailure: this._boundFailureHandler\n    });\n    new Ajax.Request(this.options.loadTextURL, options);\n  },\n  postProcessEditField: function() {\n    var fpc = this.options.fieldPostCreation;\n    if (fpc)\n      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();\n  },\n  prepareOptions: function() {\n    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);\n    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);\n    [this._extraDefaultOptions].flatten().compact().each(function(defs) {\n      Object.extend(this.options, defs);\n    }.bind(this));\n  },\n  prepareSubmission: function() {\n    this._saving = true;\n    this.removeForm();\n    this.leaveHover();\n    this.showSaving();\n  },\n  registerListeners: function() {\n    this._listeners = { };\n    var listener;\n    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {\n      listener = this[pair.value].bind(this);\n      this._listeners[pair.key] = listener;\n      if (!this.options.externalControlOnly)\n        this.element.observe(pair.key, listener);\n      if (this.options.externalControl)\n        this.options.externalControl.observe(pair.key, listener);\n    }.bind(this));\n  },\n  removeForm: function() {\n    if (!this._form) return;\n    this._form.remove();\n    this._form = null;\n    this._controls = { };\n  },\n  showSaving: function() {\n    this._oldInnerHTML = this.element.innerHTML;\n    this.element.innerHTML = this.options.savingText;\n    this.element.addClassName(this.options.savingClassName);\n    this.element.style.backgroundColor = this._originalBackground;\n    this.element.show();\n  },\n  triggerCallback: function(cbName, arg) {\n    if ('function' == typeof this.options[cbName]) {\n      this.options[cbName](this, arg);\n    }\n  },\n  unregisterListeners: function() {\n    $H(this._listeners).each(function(pair) {\n      if (!this.options.externalControlOnly)\n        this.element.stopObserving(pair.key, pair.value);\n      if (this.options.externalControl)\n        this.options.externalControl.stopObserving(pair.key, pair.value);\n    }.bind(this));\n  },\n  wrapUp: function(transport) {\n    this.leaveEditMode();\n    // Can't use triggerCallback due to backward compatibility: requires\n    // binding + direct element\n    this._boundComplete(transport, this.element);\n  }\n});\n\nObject.extend(Ajax.InPlaceEditor.prototype, {\n  dispose: Ajax.InPlaceEditor.prototype.destroy\n});\n\nAjax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {\n  initialize: function($super, element, url, options) {\n    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;\n    $super(element, url, options);\n  },\n\n  createEditField: function() {\n    var list = document.createElement('select');\n    list.name = this.options.paramName;\n    list.size = 1;\n    this._controls.editor = list;\n    this._collection = this.options.collection || [];\n    if (this.options.loadCollectionURL)\n      this.loadCollection();\n    else\n      this.checkForExternalText();\n    this._form.appendChild(this._controls.editor);\n  },\n\n  loadCollection: function() {\n    this._form.addClassName(this.options.loadingClassName);\n    this.showLoadingText(this.options.loadingCollectionText);\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        var js = transport.responseText.strip();\n        if (!/^\\[.*\\]$/.test(js)) // TODO: improve sanity check\n          throw('Server returned an invalid collection representation.');\n        this._collection = eval(js);\n        this.checkForExternalText();\n      }.bind(this),\n      onFailure: this.onFailure\n    });\n    new Ajax.Request(this.options.loadCollectionURL, options);\n  },\n\n  showLoadingText: function(text) {\n    this._controls.editor.disabled = true;\n    var tempOption = this._controls.editor.firstChild;\n    if (!tempOption) {\n      tempOption = document.createElement('option');\n      tempOption.value = '';\n      this._controls.editor.appendChild(tempOption);\n      tempOption.selected = true;\n    }\n    tempOption.update((text || '').stripScripts().stripTags());\n  },\n\n  checkForExternalText: function() {\n    this._text = this.getText();\n    if (this.options.loadTextURL)\n      this.loadExternalText();\n    else\n      this.buildOptionList();\n  },\n\n  loadExternalText: function() {\n    this.showLoadingText(this.options.loadingText);\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        this._text = transport.responseText.strip();\n        this.buildOptionList();\n      }.bind(this),\n      onFailure: this.onFailure\n    });\n    new Ajax.Request(this.options.loadTextURL, options);\n  },\n\n  buildOptionList: function() {\n    this._form.removeClassName(this.options.loadingClassName);\n    this._collection = this._collection.map(function(entry) {\n      return 2 === entry.length ? entry : [entry, entry].flatten();\n    });\n    var marker = ('value' in this.options) ? this.options.value : this._text;\n    var textFound = this._collection.any(function(entry) {\n      return entry[0] == marker;\n    }.bind(this));\n    this._controls.editor.update('');\n    var option;\n    this._collection.each(function(entry, index) {\n      option = document.createElement('option');\n      option.value = entry[0];\n      option.selected = textFound ? entry[0] == marker : 0 == index;\n      option.appendChild(document.createTextNode(entry[1]));\n      this._controls.editor.appendChild(option);\n    }.bind(this));\n    this._controls.editor.disabled = false;\n    Field.scrollFreeActivate(this._controls.editor);\n  }\n});\n\n//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****\n//**** This only  exists for a while,  in order to  let ****\n//**** users adapt to  the new API.  Read up on the new ****\n//**** API and convert your code to it ASAP!            ****\n\nAjax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {\n  if (!options) return;\n  function fallback(name, expr) {\n    if (name in options || expr === undefined) return;\n    options[name] = expr;\n  };\n  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :\n    options.cancelLink == options.cancelButton == false ? false : undefined)));\n  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :\n    options.okLink == options.okButton == false ? false : undefined)));\n  fallback('highlightColor', options.highlightcolor);\n  fallback('highlightEndColor', options.highlightendcolor);\n};\n\nObject.extend(Ajax.InPlaceEditor, {\n  DefaultOptions: {\n    ajaxOptions: { },\n    autoRows: 3,                                // Use when multi-line w/ rows == 1\n    cancelControl: 'link',                      // 'link'|'button'|false\n    cancelText: 'cancel',\n    clickToEditText: 'Click to edit',\n    externalControl: null,                      // id|elt\n    externalControlOnly: false,\n    fieldPostCreation: 'activate',              // 'activate'|'focus'|false\n    formClassName: 'inplaceeditor-form',\n    formId: null,                               // id|elt\n    highlightColor: '#ffff99',\n    highlightEndColor: '#ffffff',\n    hoverClassName: '',\n    htmlResponse: true,\n    loadingClassName: 'inplaceeditor-loading',\n    loadingText: 'Loading...',\n    okControl: 'button',                        // 'link'|'button'|false\n    okText: 'ok',\n    paramName: 'value',\n    rows: 1,                                    // If 1 and multi-line, uses autoRows\n    savingClassName: 'inplaceeditor-saving',\n    savingText: 'Saving...',\n    size: 0,\n    stripLoadedTextTags: false,\n    submitOnBlur: false,\n    textAfterControls: '',\n    textBeforeControls: '',\n    textBetweenControls: ''\n  },\n  DefaultCallbacks: {\n    callback: function(form) {\n      return Form.serialize(form);\n    },\n    onComplete: function(transport, element) {\n      // For backward compatibility, this one is bound to the IPE, and passes\n      // the element directly.  It was too often customized, so we don't break it.\n      new Effect.Highlight(element, {\n        startcolor: this.options.highlightColor, keepBackgroundImage: true });\n    },\n    onEnterEditMode: null,\n    onEnterHover: function(ipe) {\n      ipe.element.style.backgroundColor = ipe.options.highlightColor;\n      if (ipe._effect)\n        ipe._effect.cancel();\n    },\n    onFailure: function(transport, ipe) {\n      alert('Error communication with the server: ' + transport.responseText.stripTags());\n    },\n    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.\n    onLeaveEditMode: null,\n    onLeaveHover: function(ipe) {\n      ipe._effect = new Effect.Highlight(ipe.element, {\n        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,\n        restorecolor: ipe._originalBackground, keepBackgroundImage: true\n      });\n    }\n  },\n  Listeners: {\n    click: 'enterEditMode',\n    keydown: 'checkForEscapeOrReturn',\n    mouseover: 'enterHover',\n    mouseout: 'leaveHover'\n  }\n});\n\nAjax.InPlaceCollectionEditor.DefaultOptions = {\n  loadingCollectionText: 'Loading options...'\n};\n\n// Delayed observer, like Form.Element.Observer,\n// but waits for delay after last key input\n// Ideal for live-search fields\n\nForm.Element.DelayedObserver = Class.create({\n  initialize: function(element, delay, callback) {\n    this.delay     = delay || 0.5;\n    this.element   = $(element);\n    this.callback  = callback;\n    this.timer     = null;\n    this.lastValue = $F(this.element);\n    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));\n  },\n  delayedListener: function(event) {\n    if(this.lastValue == $F(this.element)) return;\n    if(this.timer) clearTimeout(this.timer);\n    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);\n    this.lastValue = $F(this.element);\n  },\n  onTimerEvent: function() {\n    this.timer = null;\n    this.callback(this.element, $F(this.element));\n  }\n});"
  },
  {
    "path": "example/rails3/public/javascripts/dragdrop.js",
    "content": "// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009\n\n// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\nif(Object.isUndefined(Effect))\n  throw(\"dragdrop.js requires including script.aculo.us' effects.js library\");\n\nvar Droppables = {\n  drops: [],\n\n  remove: function(element) {\n    this.drops = this.drops.reject(function(d) { return d.element==$(element) });\n  },\n\n  add: function(element) {\n    element = $(element);\n    var options = Object.extend({\n      greedy:     true,\n      hoverclass: null,\n      tree:       false\n    }, arguments[1] || { });\n\n    // cache containers\n    if(options.containment) {\n      options._containers = [];\n      var containment = options.containment;\n      if(Object.isArray(containment)) {\n        containment.each( function(c) { options._containers.push($(c)) });\n      } else {\n        options._containers.push($(containment));\n      }\n    }\n\n    if(options.accept) options.accept = [options.accept].flatten();\n\n    Element.makePositioned(element); // fix IE\n    options.element = element;\n\n    this.drops.push(options);\n  },\n\n  findDeepestChild: function(drops) {\n    deepest = drops[0];\n\n    for (i = 1; i < drops.length; ++i)\n      if (Element.isParent(drops[i].element, deepest.element))\n        deepest = drops[i];\n\n    return deepest;\n  },\n\n  isContained: function(element, drop) {\n    var containmentNode;\n    if(drop.tree) {\n      containmentNode = element.treeNode;\n    } else {\n      containmentNode = element.parentNode;\n    }\n    return drop._containers.detect(function(c) { return containmentNode == c });\n  },\n\n  isAffected: function(point, element, drop) {\n    return (\n      (drop.element!=element) &&\n      ((!drop._containers) ||\n        this.isContained(element, drop)) &&\n      ((!drop.accept) ||\n        (Element.classNames(element).detect(\n          function(v) { return drop.accept.include(v) } ) )) &&\n      Position.within(drop.element, point[0], point[1]) );\n  },\n\n  deactivate: function(drop) {\n    if(drop.hoverclass)\n      Element.removeClassName(drop.element, drop.hoverclass);\n    this.last_active = null;\n  },\n\n  activate: function(drop) {\n    if(drop.hoverclass)\n      Element.addClassName(drop.element, drop.hoverclass);\n    this.last_active = drop;\n  },\n\n  show: function(point, element) {\n    if(!this.drops.length) return;\n    var drop, affected = [];\n\n    this.drops.each( function(drop) {\n      if(Droppables.isAffected(point, element, drop))\n        affected.push(drop);\n    });\n\n    if(affected.length>0)\n      drop = Droppables.findDeepestChild(affected);\n\n    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);\n    if (drop) {\n      Position.within(drop.element, point[0], point[1]);\n      if(drop.onHover)\n        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));\n\n      if (drop != this.last_active) Droppables.activate(drop);\n    }\n  },\n\n  fire: function(event, element) {\n    if(!this.last_active) return;\n    Position.prepare();\n\n    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))\n      if (this.last_active.onDrop) {\n        this.last_active.onDrop(element, this.last_active.element, event);\n        return true;\n      }\n  },\n\n  reset: function() {\n    if(this.last_active)\n      this.deactivate(this.last_active);\n  }\n};\n\nvar Draggables = {\n  drags: [],\n  observers: [],\n\n  register: function(draggable) {\n    if(this.drags.length == 0) {\n      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);\n      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);\n      this.eventKeypress  = this.keyPress.bindAsEventListener(this);\n\n      Event.observe(document, \"mouseup\", this.eventMouseUp);\n      Event.observe(document, \"mousemove\", this.eventMouseMove);\n      Event.observe(document, \"keypress\", this.eventKeypress);\n    }\n    this.drags.push(draggable);\n  },\n\n  unregister: function(draggable) {\n    this.drags = this.drags.reject(function(d) { return d==draggable });\n    if(this.drags.length == 0) {\n      Event.stopObserving(document, \"mouseup\", this.eventMouseUp);\n      Event.stopObserving(document, \"mousemove\", this.eventMouseMove);\n      Event.stopObserving(document, \"keypress\", this.eventKeypress);\n    }\n  },\n\n  activate: function(draggable) {\n    if(draggable.options.delay) {\n      this._timeout = setTimeout(function() {\n        Draggables._timeout = null;\n        window.focus();\n        Draggables.activeDraggable = draggable;\n      }.bind(this), draggable.options.delay);\n    } else {\n      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari\n      this.activeDraggable = draggable;\n    }\n  },\n\n  deactivate: function() {\n    this.activeDraggable = null;\n  },\n\n  updateDrag: function(event) {\n    if(!this.activeDraggable) return;\n    var pointer = [Event.pointerX(event), Event.pointerY(event)];\n    // Mozilla-based browsers fire successive mousemove events with\n    // the same coordinates, prevent needless redrawing (moz bug?)\n    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;\n    this._lastPointer = pointer;\n\n    this.activeDraggable.updateDrag(event, pointer);\n  },\n\n  endDrag: function(event) {\n    if(this._timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    }\n    if(!this.activeDraggable) return;\n    this._lastPointer = null;\n    this.activeDraggable.endDrag(event);\n    this.activeDraggable = null;\n  },\n\n  keyPress: function(event) {\n    if(this.activeDraggable)\n      this.activeDraggable.keyPress(event);\n  },\n\n  addObserver: function(observer) {\n    this.observers.push(observer);\n    this._cacheObserverCallbacks();\n  },\n\n  removeObserver: function(element) {  // element instead of observer fixes mem leaks\n    this.observers = this.observers.reject( function(o) { return o.element==element });\n    this._cacheObserverCallbacks();\n  },\n\n  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'\n    if(this[eventName+'Count'] > 0)\n      this.observers.each( function(o) {\n        if(o[eventName]) o[eventName](eventName, draggable, event);\n      });\n    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);\n  },\n\n  _cacheObserverCallbacks: function() {\n    ['onStart','onEnd','onDrag'].each( function(eventName) {\n      Draggables[eventName+'Count'] = Draggables.observers.select(\n        function(o) { return o[eventName]; }\n      ).length;\n    });\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Draggable = Class.create({\n  initialize: function(element) {\n    var defaults = {\n      handle: false,\n      reverteffect: function(element, top_offset, left_offset) {\n        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;\n        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,\n          queue: {scope:'_draggable', position:'end'}\n        });\n      },\n      endeffect: function(element) {\n        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;\n        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,\n          queue: {scope:'_draggable', position:'end'},\n          afterFinish: function(){\n            Draggable._dragging[element] = false\n          }\n        });\n      },\n      zindex: 1000,\n      revert: false,\n      quiet: false,\n      scroll: false,\n      scrollSensitivity: 20,\n      scrollSpeed: 15,\n      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }\n      delay: 0\n    };\n\n    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))\n      Object.extend(defaults, {\n        starteffect: function(element) {\n          element._opacity = Element.getOpacity(element);\n          Draggable._dragging[element] = true;\n          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});\n        }\n      });\n\n    var options = Object.extend(defaults, arguments[1] || { });\n\n    this.element = $(element);\n\n    if(options.handle && Object.isString(options.handle))\n      this.handle = this.element.down('.'+options.handle, 0);\n\n    if(!this.handle) this.handle = $(options.handle);\n    if(!this.handle) this.handle = this.element;\n\n    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {\n      options.scroll = $(options.scroll);\n      this._isScrollChild = Element.childOf(this.element, options.scroll);\n    }\n\n    Element.makePositioned(this.element); // fix IE\n\n    this.options  = options;\n    this.dragging = false;\n\n    this.eventMouseDown = this.initDrag.bindAsEventListener(this);\n    Event.observe(this.handle, \"mousedown\", this.eventMouseDown);\n\n    Draggables.register(this);\n  },\n\n  destroy: function() {\n    Event.stopObserving(this.handle, \"mousedown\", this.eventMouseDown);\n    Draggables.unregister(this);\n  },\n\n  currentDelta: function() {\n    return([\n      parseInt(Element.getStyle(this.element,'left') || '0'),\n      parseInt(Element.getStyle(this.element,'top') || '0')]);\n  },\n\n  initDrag: function(event) {\n    if(!Object.isUndefined(Draggable._dragging[this.element]) &&\n      Draggable._dragging[this.element]) return;\n    if(Event.isLeftClick(event)) {\n      // abort on form elements, fixes a Firefox issue\n      var src = Event.element(event);\n      if((tag_name = src.tagName.toUpperCase()) && (\n        tag_name=='INPUT' ||\n        tag_name=='SELECT' ||\n        tag_name=='OPTION' ||\n        tag_name=='BUTTON' ||\n        tag_name=='TEXTAREA')) return;\n\n      var pointer = [Event.pointerX(event), Event.pointerY(event)];\n      var pos     = this.element.cumulativeOffset();\n      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });\n\n      Draggables.activate(this);\n      Event.stop(event);\n    }\n  },\n\n  startDrag: function(event) {\n    this.dragging = true;\n    if(!this.delta)\n      this.delta = this.currentDelta();\n\n    if(this.options.zindex) {\n      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);\n      this.element.style.zIndex = this.options.zindex;\n    }\n\n    if(this.options.ghosting) {\n      this._clone = this.element.cloneNode(true);\n      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');\n      if (!this._originallyAbsolute)\n        Position.absolutize(this.element);\n      this.element.parentNode.insertBefore(this._clone, this.element);\n    }\n\n    if(this.options.scroll) {\n      if (this.options.scroll == window) {\n        var where = this._getWindowScroll(this.options.scroll);\n        this.originalScrollLeft = where.left;\n        this.originalScrollTop = where.top;\n      } else {\n        this.originalScrollLeft = this.options.scroll.scrollLeft;\n        this.originalScrollTop = this.options.scroll.scrollTop;\n      }\n    }\n\n    Draggables.notify('onStart', this, event);\n\n    if(this.options.starteffect) this.options.starteffect(this.element);\n  },\n\n  updateDrag: function(event, pointer) {\n    if(!this.dragging) this.startDrag(event);\n\n    if(!this.options.quiet){\n      Position.prepare();\n      Droppables.show(pointer, this.element);\n    }\n\n    Draggables.notify('onDrag', this, event);\n\n    this.draw(pointer);\n    if(this.options.change) this.options.change(this);\n\n    if(this.options.scroll) {\n      this.stopScrolling();\n\n      var p;\n      if (this.options.scroll == window) {\n        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }\n      } else {\n        p = Position.page(this.options.scroll);\n        p[0] += this.options.scroll.scrollLeft + Position.deltaX;\n        p[1] += this.options.scroll.scrollTop + Position.deltaY;\n        p.push(p[0]+this.options.scroll.offsetWidth);\n        p.push(p[1]+this.options.scroll.offsetHeight);\n      }\n      var speed = [0,0];\n      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);\n      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);\n      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);\n      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);\n      this.startScrolling(speed);\n    }\n\n    // fix AppleWebKit rendering\n    if(Prototype.Browser.WebKit) window.scrollBy(0,0);\n\n    Event.stop(event);\n  },\n\n  finishDrag: function(event, success) {\n    this.dragging = false;\n\n    if(this.options.quiet){\n      Position.prepare();\n      var pointer = [Event.pointerX(event), Event.pointerY(event)];\n      Droppables.show(pointer, this.element);\n    }\n\n    if(this.options.ghosting) {\n      if (!this._originallyAbsolute)\n        Position.relativize(this.element);\n      delete this._originallyAbsolute;\n      Element.remove(this._clone);\n      this._clone = null;\n    }\n\n    var dropped = false;\n    if(success) {\n      dropped = Droppables.fire(event, this.element);\n      if (!dropped) dropped = false;\n    }\n    if(dropped && this.options.onDropped) this.options.onDropped(this.element);\n    Draggables.notify('onEnd', this, event);\n\n    var revert = this.options.revert;\n    if(revert && Object.isFunction(revert)) revert = revert(this.element);\n\n    var d = this.currentDelta();\n    if(revert && this.options.reverteffect) {\n      if (dropped == 0 || revert != 'failure')\n        this.options.reverteffect(this.element,\n          d[1]-this.delta[1], d[0]-this.delta[0]);\n    } else {\n      this.delta = d;\n    }\n\n    if(this.options.zindex)\n      this.element.style.zIndex = this.originalZ;\n\n    if(this.options.endeffect)\n      this.options.endeffect(this.element);\n\n    Draggables.deactivate(this);\n    Droppables.reset();\n  },\n\n  keyPress: function(event) {\n    if(event.keyCode!=Event.KEY_ESC) return;\n    this.finishDrag(event, false);\n    Event.stop(event);\n  },\n\n  endDrag: function(event) {\n    if(!this.dragging) return;\n    this.stopScrolling();\n    this.finishDrag(event, true);\n    Event.stop(event);\n  },\n\n  draw: function(point) {\n    var pos = this.element.cumulativeOffset();\n    if(this.options.ghosting) {\n      var r   = Position.realOffset(this.element);\n      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;\n    }\n\n    var d = this.currentDelta();\n    pos[0] -= d[0]; pos[1] -= d[1];\n\n    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {\n      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;\n      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;\n    }\n\n    var p = [0,1].map(function(i){\n      return (point[i]-pos[i]-this.offset[i])\n    }.bind(this));\n\n    if(this.options.snap) {\n      if(Object.isFunction(this.options.snap)) {\n        p = this.options.snap(p[0],p[1],this);\n      } else {\n      if(Object.isArray(this.options.snap)) {\n        p = p.map( function(v, i) {\n          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));\n      } else {\n        p = p.map( function(v) {\n          return (v/this.options.snap).round()*this.options.snap }.bind(this));\n      }\n    }}\n\n    var style = this.element.style;\n    if((!this.options.constraint) || (this.options.constraint=='horizontal'))\n      style.left = p[0] + \"px\";\n    if((!this.options.constraint) || (this.options.constraint=='vertical'))\n      style.top  = p[1] + \"px\";\n\n    if(style.visibility==\"hidden\") style.visibility = \"\"; // fix gecko rendering\n  },\n\n  stopScrolling: function() {\n    if(this.scrollInterval) {\n      clearInterval(this.scrollInterval);\n      this.scrollInterval = null;\n      Draggables._lastScrollPointer = null;\n    }\n  },\n\n  startScrolling: function(speed) {\n    if(!(speed[0] || speed[1])) return;\n    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];\n    this.lastScrolled = new Date();\n    this.scrollInterval = setInterval(this.scroll.bind(this), 10);\n  },\n\n  scroll: function() {\n    var current = new Date();\n    var delta = current - this.lastScrolled;\n    this.lastScrolled = current;\n    if(this.options.scroll == window) {\n      with (this._getWindowScroll(this.options.scroll)) {\n        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {\n          var d = delta / 1000;\n          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );\n        }\n      }\n    } else {\n      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;\n      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;\n    }\n\n    Position.prepare();\n    Droppables.show(Draggables._lastPointer, this.element);\n    Draggables.notify('onDrag', this);\n    if (this._isScrollChild) {\n      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);\n      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;\n      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;\n      if (Draggables._lastScrollPointer[0] < 0)\n        Draggables._lastScrollPointer[0] = 0;\n      if (Draggables._lastScrollPointer[1] < 0)\n        Draggables._lastScrollPointer[1] = 0;\n      this.draw(Draggables._lastScrollPointer);\n    }\n\n    if(this.options.change) this.options.change(this);\n  },\n\n  _getWindowScroll: function(w) {\n    var T, L, W, H;\n    with (w.document) {\n      if (w.document.documentElement && documentElement.scrollTop) {\n        T = documentElement.scrollTop;\n        L = documentElement.scrollLeft;\n      } else if (w.document.body) {\n        T = body.scrollTop;\n        L = body.scrollLeft;\n      }\n      if (w.innerWidth) {\n        W = w.innerWidth;\n        H = w.innerHeight;\n      } else if (w.document.documentElement && documentElement.clientWidth) {\n        W = documentElement.clientWidth;\n        H = documentElement.clientHeight;\n      } else {\n        W = body.offsetWidth;\n        H = body.offsetHeight;\n      }\n    }\n    return { top: T, left: L, width: W, height: H };\n  }\n});\n\nDraggable._dragging = { };\n\n/*--------------------------------------------------------------------------*/\n\nvar SortableObserver = Class.create({\n  initialize: function(element, observer) {\n    this.element   = $(element);\n    this.observer  = observer;\n    this.lastValue = Sortable.serialize(this.element);\n  },\n\n  onStart: function() {\n    this.lastValue = Sortable.serialize(this.element);\n  },\n\n  onEnd: function() {\n    Sortable.unmark();\n    if(this.lastValue != Sortable.serialize(this.element))\n      this.observer(this.element)\n  }\n});\n\nvar Sortable = {\n  SERIALIZE_RULE: /^[^_\\-](?:[A-Za-z0-9\\-\\_]*)[_](.*)$/,\n\n  sortables: { },\n\n  _findRootElement: function(element) {\n    while (element.tagName.toUpperCase() != \"BODY\") {\n      if(element.id && Sortable.sortables[element.id]) return element;\n      element = element.parentNode;\n    }\n  },\n\n  options: function(element) {\n    element = Sortable._findRootElement($(element));\n    if(!element) return;\n    return Sortable.sortables[element.id];\n  },\n\n  destroy: function(element){\n    element = $(element);\n    var s = Sortable.sortables[element.id];\n\n    if(s) {\n      Draggables.removeObserver(s.element);\n      s.droppables.each(function(d){ Droppables.remove(d) });\n      s.draggables.invoke('destroy');\n\n      delete Sortable.sortables[s.element.id];\n    }\n  },\n\n  create: function(element) {\n    element = $(element);\n    var options = Object.extend({\n      element:     element,\n      tag:         'li',       // assumes li children, override with tag: 'tagname'\n      dropOnEmpty: false,\n      tree:        false,\n      treeTag:     'ul',\n      overlap:     'vertical', // one of 'vertical', 'horizontal'\n      constraint:  'vertical', // one of 'vertical', 'horizontal', false\n      containment: element,    // also takes array of elements (or id's); or false\n      handle:      false,      // or a CSS class\n      only:        false,\n      delay:       0,\n      hoverclass:  null,\n      ghosting:    false,\n      quiet:       false,\n      scroll:      false,\n      scrollSensitivity: 20,\n      scrollSpeed: 15,\n      format:      this.SERIALIZE_RULE,\n\n      // these take arrays of elements or ids and can be\n      // used for better initialization performance\n      elements:    false,\n      handles:     false,\n\n      onChange:    Prototype.emptyFunction,\n      onUpdate:    Prototype.emptyFunction\n    }, arguments[1] || { });\n\n    // clear any old sortable with same element\n    this.destroy(element);\n\n    // build options for the draggables\n    var options_for_draggable = {\n      revert:      true,\n      quiet:       options.quiet,\n      scroll:      options.scroll,\n      scrollSpeed: options.scrollSpeed,\n      scrollSensitivity: options.scrollSensitivity,\n      delay:       options.delay,\n      ghosting:    options.ghosting,\n      constraint:  options.constraint,\n      handle:      options.handle };\n\n    if(options.starteffect)\n      options_for_draggable.starteffect = options.starteffect;\n\n    if(options.reverteffect)\n      options_for_draggable.reverteffect = options.reverteffect;\n    else\n      if(options.ghosting) options_for_draggable.reverteffect = function(element) {\n        element.style.top  = 0;\n        element.style.left = 0;\n      };\n\n    if(options.endeffect)\n      options_for_draggable.endeffect = options.endeffect;\n\n    if(options.zindex)\n      options_for_draggable.zindex = options.zindex;\n\n    // build options for the droppables\n    var options_for_droppable = {\n      overlap:     options.overlap,\n      containment: options.containment,\n      tree:        options.tree,\n      hoverclass:  options.hoverclass,\n      onHover:     Sortable.onHover\n    };\n\n    var options_for_tree = {\n      onHover:      Sortable.onEmptyHover,\n      overlap:      options.overlap,\n      containment:  options.containment,\n      hoverclass:   options.hoverclass\n    };\n\n    // fix for gecko engine\n    Element.cleanWhitespace(element);\n\n    options.draggables = [];\n    options.droppables = [];\n\n    // drop on empty handling\n    if(options.dropOnEmpty || options.tree) {\n      Droppables.add(element, options_for_tree);\n      options.droppables.push(element);\n    }\n\n    (options.elements || this.findElements(element, options) || []).each( function(e,i) {\n      var handle = options.handles ? $(options.handles[i]) :\n        (options.handle ? $(e).select('.' + options.handle)[0] : e);\n      options.draggables.push(\n        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));\n      Droppables.add(e, options_for_droppable);\n      if(options.tree) e.treeNode = element;\n      options.droppables.push(e);\n    });\n\n    if(options.tree) {\n      (Sortable.findTreeElements(element, options) || []).each( function(e) {\n        Droppables.add(e, options_for_tree);\n        e.treeNode = element;\n        options.droppables.push(e);\n      });\n    }\n\n    // keep reference\n    this.sortables[element.identify()] = options;\n\n    // for onupdate\n    Draggables.addObserver(new SortableObserver(element, options.onUpdate));\n\n  },\n\n  // return all suitable-for-sortable elements in a guaranteed order\n  findElements: function(element, options) {\n    return Element.findChildren(\n      element, options.only, options.tree ? true : false, options.tag);\n  },\n\n  findTreeElements: function(element, options) {\n    return Element.findChildren(\n      element, options.only, options.tree ? true : false, options.treeTag);\n  },\n\n  onHover: function(element, dropon, overlap) {\n    if(Element.isParent(dropon, element)) return;\n\n    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {\n      return;\n    } else if(overlap>0.5) {\n      Sortable.mark(dropon, 'before');\n      if(dropon.previousSibling != element) {\n        var oldParentNode = element.parentNode;\n        element.style.visibility = \"hidden\"; // fix gecko rendering\n        dropon.parentNode.insertBefore(element, dropon);\n        if(dropon.parentNode!=oldParentNode)\n          Sortable.options(oldParentNode).onChange(element);\n        Sortable.options(dropon.parentNode).onChange(element);\n      }\n    } else {\n      Sortable.mark(dropon, 'after');\n      var nextElement = dropon.nextSibling || null;\n      if(nextElement != element) {\n        var oldParentNode = element.parentNode;\n        element.style.visibility = \"hidden\"; // fix gecko rendering\n        dropon.parentNode.insertBefore(element, nextElement);\n        if(dropon.parentNode!=oldParentNode)\n          Sortable.options(oldParentNode).onChange(element);\n        Sortable.options(dropon.parentNode).onChange(element);\n      }\n    }\n  },\n\n  onEmptyHover: function(element, dropon, overlap) {\n    var oldParentNode = element.parentNode;\n    var droponOptions = Sortable.options(dropon);\n\n    if(!Element.isParent(dropon, element)) {\n      var index;\n\n      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});\n      var child = null;\n\n      if(children) {\n        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);\n\n        for (index = 0; index < children.length; index += 1) {\n          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {\n            offset -= Element.offsetSize (children[index], droponOptions.overlap);\n          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {\n            child = index + 1 < children.length ? children[index + 1] : null;\n            break;\n          } else {\n            child = children[index];\n            break;\n          }\n        }\n      }\n\n      dropon.insertBefore(element, child);\n\n      Sortable.options(oldParentNode).onChange(element);\n      droponOptions.onChange(element);\n    }\n  },\n\n  unmark: function() {\n    if(Sortable._marker) Sortable._marker.hide();\n  },\n\n  mark: function(dropon, position) {\n    // mark on ghosting only\n    var sortable = Sortable.options(dropon.parentNode);\n    if(sortable && !sortable.ghosting) return;\n\n    if(!Sortable._marker) {\n      Sortable._marker =\n        ($('dropmarker') || Element.extend(document.createElement('DIV'))).\n          hide().addClassName('dropmarker').setStyle({position:'absolute'});\n      document.getElementsByTagName(\"body\").item(0).appendChild(Sortable._marker);\n    }\n    var offsets = dropon.cumulativeOffset();\n    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});\n\n    if(position=='after')\n      if(sortable.overlap == 'horizontal')\n        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});\n      else\n        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});\n\n    Sortable._marker.show();\n  },\n\n  _tree: function(element, options, parent) {\n    var children = Sortable.findElements(element, options) || [];\n\n    for (var i = 0; i < children.length; ++i) {\n      var match = children[i].id.match(options.format);\n\n      if (!match) continue;\n\n      var child = {\n        id: encodeURIComponent(match ? match[1] : null),\n        element: element,\n        parent: parent,\n        children: [],\n        position: parent.children.length,\n        container: $(children[i]).down(options.treeTag)\n      };\n\n      /* Get the element containing the children and recurse over it */\n      if (child.container)\n        this._tree(child.container, options, child);\n\n      parent.children.push (child);\n    }\n\n    return parent;\n  },\n\n  tree: function(element) {\n    element = $(element);\n    var sortableOptions = this.options(element);\n    var options = Object.extend({\n      tag: sortableOptions.tag,\n      treeTag: sortableOptions.treeTag,\n      only: sortableOptions.only,\n      name: element.id,\n      format: sortableOptions.format\n    }, arguments[1] || { });\n\n    var root = {\n      id: null,\n      parent: null,\n      children: [],\n      container: element,\n      position: 0\n    };\n\n    return Sortable._tree(element, options, root);\n  },\n\n  /* Construct a [i] index for a particular node */\n  _constructIndex: function(node) {\n    var index = '';\n    do {\n      if (node.id) index = '[' + node.position + ']' + index;\n    } while ((node = node.parent) != null);\n    return index;\n  },\n\n  sequence: function(element) {\n    element = $(element);\n    var options = Object.extend(this.options(element), arguments[1] || { });\n\n    return $(this.findElements(element, options) || []).map( function(item) {\n      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';\n    });\n  },\n\n  setSequence: function(element, new_sequence) {\n    element = $(element);\n    var options = Object.extend(this.options(element), arguments[2] || { });\n\n    var nodeMap = { };\n    this.findElements(element, options).each( function(n) {\n        if (n.id.match(options.format))\n            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];\n        n.parentNode.removeChild(n);\n    });\n\n    new_sequence.each(function(ident) {\n      var n = nodeMap[ident];\n      if (n) {\n        n[1].appendChild(n[0]);\n        delete nodeMap[ident];\n      }\n    });\n  },\n\n  serialize: function(element) {\n    element = $(element);\n    var options = Object.extend(Sortable.options(element), arguments[1] || { });\n    var name = encodeURIComponent(\n      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);\n\n    if (options.tree) {\n      return Sortable.tree(element, arguments[1]).children.map( function (item) {\n        return [name + Sortable._constructIndex(item) + \"[id]=\" +\n                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));\n      }).flatten().join('&');\n    } else {\n      return Sortable.sequence(element, arguments[1]).map( function(item) {\n        return name + \"[]=\" + encodeURIComponent(item);\n      }).join('&');\n    }\n  }\n};\n\n// Returns true if child is contained within element\nElement.isParent = function(child, element) {\n  if (!child.parentNode || child == element) return false;\n  if (child.parentNode == element) return true;\n  return Element.isParent(child.parentNode, element);\n};\n\nElement.findChildren = function(element, only, recursive, tagName) {\n  if(!element.hasChildNodes()) return null;\n  tagName = tagName.toUpperCase();\n  if(only) only = [only].flatten();\n  var elements = [];\n  $A(element.childNodes).each( function(e) {\n    if(e.tagName && e.tagName.toUpperCase()==tagName &&\n      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))\n        elements.push(e);\n    if(recursive) {\n      var grandchildren = Element.findChildren(e, only, recursive, tagName);\n      if(grandchildren) elements.push(grandchildren);\n    }\n  });\n\n  return (elements.length>0 ? elements.flatten() : []);\n};\n\nElement.offsetSize = function (element, type) {\n  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];\n};"
  },
  {
    "path": "example/rails3/public/javascripts/effects.js",
    "content": "// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009\n\n// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n// Contributors:\n//  Justin Palmer (http://encytemedia.com/)\n//  Mark Pilgrim (http://diveintomark.org/)\n//  Martin Bialasinki\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\n// converts rgb() and #xxx to #xxxxxx format,\n// returns self (or first argument) if not convertable\nString.prototype.parseColor = function() {\n  var color = '#';\n  if (this.slice(0,4) == 'rgb(') {\n    var cols = this.slice(4,this.length-1).split(',');\n    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);\n  } else {\n    if (this.slice(0,1) == '#') {\n      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();\n      if (this.length==7) color = this.toLowerCase();\n    }\n  }\n  return (color.length==7 ? color : (arguments[0] || this));\n};\n\n/*--------------------------------------------------------------------------*/\n\nElement.collectTextNodes = function(element) {\n  return $A($(element).childNodes).collect( function(node) {\n    return (node.nodeType==3 ? node.nodeValue :\n      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));\n  }).flatten().join('');\n};\n\nElement.collectTextNodesIgnoreClass = function(element, className) {\n  return $A($(element).childNodes).collect( function(node) {\n    return (node.nodeType==3 ? node.nodeValue :\n      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?\n        Element.collectTextNodesIgnoreClass(node, className) : ''));\n  }).flatten().join('');\n};\n\nElement.setContentZoom = function(element, percent) {\n  element = $(element);\n  element.setStyle({fontSize: (percent/100) + 'em'});\n  if (Prototype.Browser.WebKit) window.scrollBy(0,0);\n  return element;\n};\n\nElement.getInlineOpacity = function(element){\n  return $(element).style.opacity || '';\n};\n\nElement.forceRerendering = function(element) {\n  try {\n    element = $(element);\n    var n = document.createTextNode(' ');\n    element.appendChild(n);\n    element.removeChild(n);\n  } catch(e) { }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Effect = {\n  _elementDoesNotExistError: {\n    name: 'ElementDoesNotExistError',\n    message: 'The specified DOM element does not exist, but is required for this effect to operate'\n  },\n  Transitions: {\n    linear: Prototype.K,\n    sinoidal: function(pos) {\n      return (-Math.cos(pos*Math.PI)/2) + .5;\n    },\n    reverse: function(pos) {\n      return 1-pos;\n    },\n    flicker: function(pos) {\n      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;\n      return pos > 1 ? 1 : pos;\n    },\n    wobble: function(pos) {\n      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;\n    },\n    pulse: function(pos, pulses) {\n      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;\n    },\n    spring: function(pos) {\n      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));\n    },\n    none: function(pos) {\n      return 0;\n    },\n    full: function(pos) {\n      return 1;\n    }\n  },\n  DefaultOptions: {\n    duration:   1.0,   // seconds\n    fps:        100,   // 100= assume 66fps max.\n    sync:       false, // true for combining\n    from:       0.0,\n    to:         1.0,\n    delay:      0.0,\n    queue:      'parallel'\n  },\n  tagifyText: function(element) {\n    var tagifyStyle = 'position:relative';\n    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';\n\n    element = $(element);\n    $A(element.childNodes).each( function(child) {\n      if (child.nodeType==3) {\n        child.nodeValue.toArray().each( function(character) {\n          element.insertBefore(\n            new Element('span', {style: tagifyStyle}).update(\n              character == ' ' ? String.fromCharCode(160) : character),\n              child);\n        });\n        Element.remove(child);\n      }\n    });\n  },\n  multiple: function(element, effect) {\n    var elements;\n    if (((typeof element == 'object') ||\n        Object.isFunction(element)) &&\n       (element.length))\n      elements = element;\n    else\n      elements = $(element).childNodes;\n\n    var options = Object.extend({\n      speed: 0.1,\n      delay: 0.0\n    }, arguments[2] || { });\n    var masterDelay = options.delay;\n\n    $A(elements).each( function(element, index) {\n      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));\n    });\n  },\n  PAIRS: {\n    'slide':  ['SlideDown','SlideUp'],\n    'blind':  ['BlindDown','BlindUp'],\n    'appear': ['Appear','Fade']\n  },\n  toggle: function(element, effect, options) {\n    element = $(element);\n    effect  = (effect || 'appear').toLowerCase();\n    \n    return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({\n      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }\n    }, options || {}));\n  }\n};\n\nEffect.DefaultOptions.transition = Effect.Transitions.sinoidal;\n\n/* ------------- core effects ------------- */\n\nEffect.ScopedQueue = Class.create(Enumerable, {\n  initialize: function() {\n    this.effects  = [];\n    this.interval = null;\n  },\n  _each: function(iterator) {\n    this.effects._each(iterator);\n  },\n  add: function(effect) {\n    var timestamp = new Date().getTime();\n\n    var position = Object.isString(effect.options.queue) ?\n      effect.options.queue : effect.options.queue.position;\n\n    switch(position) {\n      case 'front':\n        // move unstarted effects after this effect\n        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {\n            e.startOn  += effect.finishOn;\n            e.finishOn += effect.finishOn;\n          });\n        break;\n      case 'with-last':\n        timestamp = this.effects.pluck('startOn').max() || timestamp;\n        break;\n      case 'end':\n        // start effect after last queued effect has finished\n        timestamp = this.effects.pluck('finishOn').max() || timestamp;\n        break;\n    }\n\n    effect.startOn  += timestamp;\n    effect.finishOn += timestamp;\n\n    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))\n      this.effects.push(effect);\n\n    if (!this.interval)\n      this.interval = setInterval(this.loop.bind(this), 15);\n  },\n  remove: function(effect) {\n    this.effects = this.effects.reject(function(e) { return e==effect });\n    if (this.effects.length == 0) {\n      clearInterval(this.interval);\n      this.interval = null;\n    }\n  },\n  loop: function() {\n    var timePos = new Date().getTime();\n    for(var i=0, len=this.effects.length;i<len;i++)\n      this.effects[i] && this.effects[i].loop(timePos);\n  }\n});\n\nEffect.Queues = {\n  instances: $H(),\n  get: function(queueName) {\n    if (!Object.isString(queueName)) return queueName;\n\n    return this.instances.get(queueName) ||\n      this.instances.set(queueName, new Effect.ScopedQueue());\n  }\n};\nEffect.Queue = Effect.Queues.get('global');\n\nEffect.Base = Class.create({\n  position: null,\n  start: function(options) {\n    if (options && options.transition === false) options.transition = Effect.Transitions.linear;\n    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });\n    this.currentFrame = 0;\n    this.state        = 'idle';\n    this.startOn      = this.options.delay*1000;\n    this.finishOn     = this.startOn+(this.options.duration*1000);\n    this.fromToDelta  = this.options.to-this.options.from;\n    this.totalTime    = this.finishOn-this.startOn;\n    this.totalFrames  = this.options.fps*this.options.duration;\n\n    this.render = (function() {\n      function dispatch(effect, eventName) {\n        if (effect.options[eventName + 'Internal'])\n          effect.options[eventName + 'Internal'](effect);\n        if (effect.options[eventName])\n          effect.options[eventName](effect);\n      }\n\n      return function(pos) {\n        if (this.state === \"idle\") {\n          this.state = \"running\";\n          dispatch(this, 'beforeSetup');\n          if (this.setup) this.setup();\n          dispatch(this, 'afterSetup');\n        }\n        if (this.state === \"running\") {\n          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;\n          this.position = pos;\n          dispatch(this, 'beforeUpdate');\n          if (this.update) this.update(pos);\n          dispatch(this, 'afterUpdate');\n        }\n      };\n    })();\n\n    this.event('beforeStart');\n    if (!this.options.sync)\n      Effect.Queues.get(Object.isString(this.options.queue) ?\n        'global' : this.options.queue.scope).add(this);\n  },\n  loop: function(timePos) {\n    if (timePos >= this.startOn) {\n      if (timePos >= this.finishOn) {\n        this.render(1.0);\n        this.cancel();\n        this.event('beforeFinish');\n        if (this.finish) this.finish();\n        this.event('afterFinish');\n        return;\n      }\n      var pos   = (timePos - this.startOn) / this.totalTime,\n          frame = (pos * this.totalFrames).round();\n      if (frame > this.currentFrame) {\n        this.render(pos);\n        this.currentFrame = frame;\n      }\n    }\n  },\n  cancel: function() {\n    if (!this.options.sync)\n      Effect.Queues.get(Object.isString(this.options.queue) ?\n        'global' : this.options.queue.scope).remove(this);\n    this.state = 'finished';\n  },\n  event: function(eventName) {\n    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);\n    if (this.options[eventName]) this.options[eventName](this);\n  },\n  inspect: function() {\n    var data = $H();\n    for(property in this)\n      if (!Object.isFunction(this[property])) data.set(property, this[property]);\n    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';\n  }\n});\n\nEffect.Parallel = Class.create(Effect.Base, {\n  initialize: function(effects) {\n    this.effects = effects || [];\n    this.start(arguments[1]);\n  },\n  update: function(position) {\n    this.effects.invoke('render', position);\n  },\n  finish: function(position) {\n    this.effects.each( function(effect) {\n      effect.render(1.0);\n      effect.cancel();\n      effect.event('beforeFinish');\n      if (effect.finish) effect.finish(position);\n      effect.event('afterFinish');\n    });\n  }\n});\n\nEffect.Tween = Class.create(Effect.Base, {\n  initialize: function(object, from, to) {\n    object = Object.isString(object) ? $(object) : object;\n    var args = $A(arguments), method = args.last(),\n      options = args.length == 5 ? args[3] : null;\n    this.method = Object.isFunction(method) ? method.bind(object) :\n      Object.isFunction(object[method]) ? object[method].bind(object) :\n      function(value) { object[method] = value };\n    this.start(Object.extend({ from: from, to: to }, options || { }));\n  },\n  update: function(position) {\n    this.method(position);\n  }\n});\n\nEffect.Event = Class.create(Effect.Base, {\n  initialize: function() {\n    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));\n  },\n  update: Prototype.emptyFunction\n});\n\nEffect.Opacity = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    // make this work on IE on elements without 'layout'\n    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))\n      this.element.setStyle({zoom: 1});\n    var options = Object.extend({\n      from: this.element.getOpacity() || 0.0,\n      to:   1.0\n    }, arguments[1] || { });\n    this.start(options);\n  },\n  update: function(position) {\n    this.element.setOpacity(position);\n  }\n});\n\nEffect.Move = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      x:    0,\n      y:    0,\n      mode: 'relative'\n    }, arguments[1] || { });\n    this.start(options);\n  },\n  setup: function() {\n    this.element.makePositioned();\n    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');\n    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');\n    if (this.options.mode == 'absolute') {\n      this.options.x = this.options.x - this.originalLeft;\n      this.options.y = this.options.y - this.originalTop;\n    }\n  },\n  update: function(position) {\n    this.element.setStyle({\n      left: (this.options.x  * position + this.originalLeft).round() + 'px',\n      top:  (this.options.y  * position + this.originalTop).round()  + 'px'\n    });\n  }\n});\n\n// for backwards compatibility\nEffect.MoveBy = function(element, toTop, toLeft) {\n  return new Effect.Move(element,\n    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));\n};\n\nEffect.Scale = Class.create(Effect.Base, {\n  initialize: function(element, percent) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      scaleX: true,\n      scaleY: true,\n      scaleContent: true,\n      scaleFromCenter: false,\n      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values\n      scaleFrom: 100.0,\n      scaleTo:   percent\n    }, arguments[2] || { });\n    this.start(options);\n  },\n  setup: function() {\n    this.restoreAfterFinish = this.options.restoreAfterFinish || false;\n    this.elementPositioning = this.element.getStyle('position');\n\n    this.originalStyle = { };\n    ['top','left','width','height','fontSize'].each( function(k) {\n      this.originalStyle[k] = this.element.style[k];\n    }.bind(this));\n\n    this.originalTop  = this.element.offsetTop;\n    this.originalLeft = this.element.offsetLeft;\n\n    var fontSize = this.element.getStyle('font-size') || '100%';\n    ['em','px','%','pt'].each( function(fontSizeType) {\n      if (fontSize.indexOf(fontSizeType)>0) {\n        this.fontSize     = parseFloat(fontSize);\n        this.fontSizeType = fontSizeType;\n      }\n    }.bind(this));\n\n    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;\n\n    this.dims = null;\n    if (this.options.scaleMode=='box')\n      this.dims = [this.element.offsetHeight, this.element.offsetWidth];\n    if (/^content/.test(this.options.scaleMode))\n      this.dims = [this.element.scrollHeight, this.element.scrollWidth];\n    if (!this.dims)\n      this.dims = [this.options.scaleMode.originalHeight,\n                   this.options.scaleMode.originalWidth];\n  },\n  update: function(position) {\n    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);\n    if (this.options.scaleContent && this.fontSize)\n      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });\n    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);\n  },\n  finish: function(position) {\n    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);\n  },\n  setDimensions: function(height, width) {\n    var d = { };\n    if (this.options.scaleX) d.width = width.round() + 'px';\n    if (this.options.scaleY) d.height = height.round() + 'px';\n    if (this.options.scaleFromCenter) {\n      var topd  = (height - this.dims[0])/2;\n      var leftd = (width  - this.dims[1])/2;\n      if (this.elementPositioning == 'absolute') {\n        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';\n        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';\n      } else {\n        if (this.options.scaleY) d.top = -topd + 'px';\n        if (this.options.scaleX) d.left = -leftd + 'px';\n      }\n    }\n    this.element.setStyle(d);\n  }\n});\n\nEffect.Highlight = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });\n    this.start(options);\n  },\n  setup: function() {\n    // Prevent executing on elements not in the layout flow\n    if (this.element.getStyle('display')=='none') { this.cancel(); return; }\n    // Disable background image during the effect\n    this.oldStyle = { };\n    if (!this.options.keepBackgroundImage) {\n      this.oldStyle.backgroundImage = this.element.getStyle('background-image');\n      this.element.setStyle({backgroundImage: 'none'});\n    }\n    if (!this.options.endcolor)\n      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');\n    if (!this.options.restorecolor)\n      this.options.restorecolor = this.element.getStyle('background-color');\n    // init color calculations\n    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));\n    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));\n  },\n  update: function(position) {\n    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){\n      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });\n  },\n  finish: function() {\n    this.element.setStyle(Object.extend(this.oldStyle, {\n      backgroundColor: this.options.restorecolor\n    }));\n  }\n});\n\nEffect.ScrollTo = function(element) {\n  var options = arguments[1] || { },\n  scrollOffsets = document.viewport.getScrollOffsets(),\n  elementOffsets = $(element).cumulativeOffset();\n\n  if (options.offset) elementOffsets[1] += options.offset;\n\n  return new Effect.Tween(null,\n    scrollOffsets.top,\n    elementOffsets[1],\n    options,\n    function(p){ scrollTo(scrollOffsets.left, p.round()); }\n  );\n};\n\n/* ------------- combination effects ------------- */\n\nEffect.Fade = function(element) {\n  element = $(element);\n  var oldOpacity = element.getInlineOpacity();\n  var options = Object.extend({\n    from: element.getOpacity() || 1.0,\n    to:   0.0,\n    afterFinishInternal: function(effect) {\n      if (effect.options.to!=0) return;\n      effect.element.hide().setStyle({opacity: oldOpacity});\n    }\n  }, arguments[1] || { });\n  return new Effect.Opacity(element,options);\n};\n\nEffect.Appear = function(element) {\n  element = $(element);\n  var options = Object.extend({\n  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),\n  to:   1.0,\n  // force Safari to render floated elements properly\n  afterFinishInternal: function(effect) {\n    effect.element.forceRerendering();\n  },\n  beforeSetup: function(effect) {\n    effect.element.setOpacity(effect.options.from).show();\n  }}, arguments[1] || { });\n  return new Effect.Opacity(element,options);\n};\n\nEffect.Puff = function(element) {\n  element = $(element);\n  var oldStyle = {\n    opacity: element.getInlineOpacity(),\n    position: element.getStyle('position'),\n    top:  element.style.top,\n    left: element.style.left,\n    width: element.style.width,\n    height: element.style.height\n  };\n  return new Effect.Parallel(\n   [ new Effect.Scale(element, 200,\n      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),\n     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],\n     Object.extend({ duration: 1.0,\n      beforeSetupInternal: function(effect) {\n        Position.absolutize(effect.effects[0].element);\n      },\n      afterFinishInternal: function(effect) {\n         effect.effects[0].element.hide().setStyle(oldStyle); }\n     }, arguments[1] || { })\n   );\n};\n\nEffect.BlindUp = function(element) {\n  element = $(element);\n  element.makeClipping();\n  return new Effect.Scale(element, 0,\n    Object.extend({ scaleContent: false,\n      scaleX: false,\n      restoreAfterFinish: true,\n      afterFinishInternal: function(effect) {\n        effect.element.hide().undoClipping();\n      }\n    }, arguments[1] || { })\n  );\n};\n\nEffect.BlindDown = function(element) {\n  element = $(element);\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, 100, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    scaleFrom: 0,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makeClipping().setStyle({height: '0px'}).show();\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.undoClipping();\n    }\n  }, arguments[1] || { }));\n};\n\nEffect.SwitchOff = function(element) {\n  element = $(element);\n  var oldOpacity = element.getInlineOpacity();\n  return new Effect.Appear(element, Object.extend({\n    duration: 0.4,\n    from: 0,\n    transition: Effect.Transitions.flicker,\n    afterFinishInternal: function(effect) {\n      new Effect.Scale(effect.element, 1, {\n        duration: 0.3, scaleFromCenter: true,\n        scaleX: false, scaleContent: false, restoreAfterFinish: true,\n        beforeSetup: function(effect) {\n          effect.element.makePositioned().makeClipping();\n        },\n        afterFinishInternal: function(effect) {\n          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});\n        }\n      });\n    }\n  }, arguments[1] || { }));\n};\n\nEffect.DropOut = function(element) {\n  element = $(element);\n  var oldStyle = {\n    top: element.getStyle('top'),\n    left: element.getStyle('left'),\n    opacity: element.getInlineOpacity() };\n  return new Effect.Parallel(\n    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),\n      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],\n    Object.extend(\n      { duration: 0.5,\n        beforeSetup: function(effect) {\n          effect.effects[0].element.makePositioned();\n        },\n        afterFinishInternal: function(effect) {\n          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);\n        }\n      }, arguments[1] || { }));\n};\n\nEffect.Shake = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    distance: 20,\n    duration: 0.5\n  }, arguments[1] || {});\n  var distance = parseFloat(options.distance);\n  var split = parseFloat(options.duration) / 10.0;\n  var oldStyle = {\n    top: element.getStyle('top'),\n    left: element.getStyle('left') };\n    return new Effect.Move(element,\n      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {\n        effect.element.undoPositioned().setStyle(oldStyle);\n  }}); }}); }}); }}); }}); }});\n};\n\nEffect.SlideDown = function(element) {\n  element = $(element).cleanWhitespace();\n  // SlideDown need to have the content of the element wrapped in a container element with fixed height!\n  var oldInnerBottom = element.down().getStyle('bottom');\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, 100, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    scaleFrom: window.opera ? 0 : 1,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makePositioned();\n      effect.element.down().makePositioned();\n      if (window.opera) effect.element.setStyle({top: ''});\n      effect.element.makeClipping().setStyle({height: '0px'}).show();\n    },\n    afterUpdateInternal: function(effect) {\n      effect.element.down().setStyle({bottom:\n        (effect.dims[0] - effect.element.clientHeight) + 'px' });\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.undoClipping().undoPositioned();\n      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }\n    }, arguments[1] || { })\n  );\n};\n\nEffect.SlideUp = function(element) {\n  element = $(element).cleanWhitespace();\n  var oldInnerBottom = element.down().getStyle('bottom');\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, window.opera ? 0 : 1,\n   Object.extend({ scaleContent: false,\n    scaleX: false,\n    scaleMode: 'box',\n    scaleFrom: 100,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makePositioned();\n      effect.element.down().makePositioned();\n      if (window.opera) effect.element.setStyle({top: ''});\n      effect.element.makeClipping().show();\n    },\n    afterUpdateInternal: function(effect) {\n      effect.element.down().setStyle({bottom:\n        (effect.dims[0] - effect.element.clientHeight) + 'px' });\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.hide().undoClipping().undoPositioned();\n      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});\n    }\n   }, arguments[1] || { })\n  );\n};\n\n// Bug in opera makes the TD containing this element expand for a instance after finish\nEffect.Squish = function(element) {\n  return new Effect.Scale(element, window.opera ? 1 : 0, {\n    restoreAfterFinish: true,\n    beforeSetup: function(effect) {\n      effect.element.makeClipping();\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.hide().undoClipping();\n    }\n  });\n};\n\nEffect.Grow = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    direction: 'center',\n    moveTransition: Effect.Transitions.sinoidal,\n    scaleTransition: Effect.Transitions.sinoidal,\n    opacityTransition: Effect.Transitions.full\n  }, arguments[1] || { });\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    height: element.style.height,\n    width: element.style.width,\n    opacity: element.getInlineOpacity() };\n\n  var dims = element.getDimensions();\n  var initialMoveX, initialMoveY;\n  var moveX, moveY;\n\n  switch (options.direction) {\n    case 'top-left':\n      initialMoveX = initialMoveY = moveX = moveY = 0;\n      break;\n    case 'top-right':\n      initialMoveX = dims.width;\n      initialMoveY = moveY = 0;\n      moveX = -dims.width;\n      break;\n    case 'bottom-left':\n      initialMoveX = moveX = 0;\n      initialMoveY = dims.height;\n      moveY = -dims.height;\n      break;\n    case 'bottom-right':\n      initialMoveX = dims.width;\n      initialMoveY = dims.height;\n      moveX = -dims.width;\n      moveY = -dims.height;\n      break;\n    case 'center':\n      initialMoveX = dims.width / 2;\n      initialMoveY = dims.height / 2;\n      moveX = -dims.width / 2;\n      moveY = -dims.height / 2;\n      break;\n  }\n\n  return new Effect.Move(element, {\n    x: initialMoveX,\n    y: initialMoveY,\n    duration: 0.01,\n    beforeSetup: function(effect) {\n      effect.element.hide().makeClipping().makePositioned();\n    },\n    afterFinishInternal: function(effect) {\n      new Effect.Parallel(\n        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),\n          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),\n          new Effect.Scale(effect.element, 100, {\n            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },\n            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})\n        ], Object.extend({\n             beforeSetup: function(effect) {\n               effect.effects[0].element.setStyle({height: '0px'}).show();\n             },\n             afterFinishInternal: function(effect) {\n               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);\n             }\n           }, options)\n      );\n    }\n  });\n};\n\nEffect.Shrink = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    direction: 'center',\n    moveTransition: Effect.Transitions.sinoidal,\n    scaleTransition: Effect.Transitions.sinoidal,\n    opacityTransition: Effect.Transitions.none\n  }, arguments[1] || { });\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    height: element.style.height,\n    width: element.style.width,\n    opacity: element.getInlineOpacity() };\n\n  var dims = element.getDimensions();\n  var moveX, moveY;\n\n  switch (options.direction) {\n    case 'top-left':\n      moveX = moveY = 0;\n      break;\n    case 'top-right':\n      moveX = dims.width;\n      moveY = 0;\n      break;\n    case 'bottom-left':\n      moveX = 0;\n      moveY = dims.height;\n      break;\n    case 'bottom-right':\n      moveX = dims.width;\n      moveY = dims.height;\n      break;\n    case 'center':\n      moveX = dims.width / 2;\n      moveY = dims.height / 2;\n      break;\n  }\n\n  return new Effect.Parallel(\n    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),\n      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),\n      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })\n    ], Object.extend({\n         beforeStartInternal: function(effect) {\n           effect.effects[0].element.makePositioned().makeClipping();\n         },\n         afterFinishInternal: function(effect) {\n           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }\n       }, options)\n  );\n};\n\nEffect.Pulsate = function(element) {\n  element = $(element);\n  var options    = arguments[1] || { },\n    oldOpacity = element.getInlineOpacity(),\n    transition = options.transition || Effect.Transitions.linear,\n    reverser   = function(pos){\n      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);\n    };\n\n  return new Effect.Opacity(element,\n    Object.extend(Object.extend({  duration: 2.0, from: 0,\n      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }\n    }, options), {transition: reverser}));\n};\n\nEffect.Fold = function(element) {\n  element = $(element);\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    width: element.style.width,\n    height: element.style.height };\n  element.makeClipping();\n  return new Effect.Scale(element, 5, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    afterFinishInternal: function(effect) {\n    new Effect.Scale(element, 1, {\n      scaleContent: false,\n      scaleY: false,\n      afterFinishInternal: function(effect) {\n        effect.element.hide().undoClipping().setStyle(oldStyle);\n      } });\n  }}, arguments[1] || { }));\n};\n\nEffect.Morph = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      style: { }\n    }, arguments[1] || { });\n\n    if (!Object.isString(options.style)) this.style = $H(options.style);\n    else {\n      if (options.style.include(':'))\n        this.style = options.style.parseStyle();\n      else {\n        this.element.addClassName(options.style);\n        this.style = $H(this.element.getStyles());\n        this.element.removeClassName(options.style);\n        var css = this.element.getStyles();\n        this.style = this.style.reject(function(style) {\n          return style.value == css[style.key];\n        });\n        options.afterFinishInternal = function(effect) {\n          effect.element.addClassName(effect.options.style);\n          effect.transforms.each(function(transform) {\n            effect.element.style[transform.style] = '';\n          });\n        };\n      }\n    }\n    this.start(options);\n  },\n\n  setup: function(){\n    function parseColor(color){\n      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';\n      color = color.parseColor();\n      return $R(0,2).map(function(i){\n        return parseInt( color.slice(i*2+1,i*2+3), 16 );\n      });\n    }\n    this.transforms = this.style.map(function(pair){\n      var property = pair[0], value = pair[1], unit = null;\n\n      if (value.parseColor('#zzzzzz') != '#zzzzzz') {\n        value = value.parseColor();\n        unit  = 'color';\n      } else if (property == 'opacity') {\n        value = parseFloat(value);\n        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))\n          this.element.setStyle({zoom: 1});\n      } else if (Element.CSS_LENGTH.test(value)) {\n          var components = value.match(/^([\\+\\-]?[0-9\\.]+)(.*)$/);\n          value = parseFloat(components[1]);\n          unit = (components.length == 3) ? components[2] : null;\n      }\n\n      var originalValue = this.element.getStyle(property);\n      return {\n        style: property.camelize(),\n        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),\n        targetValue: unit=='color' ? parseColor(value) : value,\n        unit: unit\n      };\n    }.bind(this)).reject(function(transform){\n      return (\n        (transform.originalValue == transform.targetValue) ||\n        (\n          transform.unit != 'color' &&\n          (isNaN(transform.originalValue) || isNaN(transform.targetValue))\n        )\n      );\n    });\n  },\n  update: function(position) {\n    var style = { }, transform, i = this.transforms.length;\n    while(i--)\n      style[(transform = this.transforms[i]).style] =\n        transform.unit=='color' ? '#'+\n          (Math.round(transform.originalValue[0]+\n            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +\n          (Math.round(transform.originalValue[1]+\n            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +\n          (Math.round(transform.originalValue[2]+\n            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :\n        (transform.originalValue +\n          (transform.targetValue - transform.originalValue) * position).toFixed(3) +\n            (transform.unit === null ? '' : transform.unit);\n    this.element.setStyle(style, true);\n  }\n});\n\nEffect.Transform = Class.create({\n  initialize: function(tracks){\n    this.tracks  = [];\n    this.options = arguments[1] || { };\n    this.addTracks(tracks);\n  },\n  addTracks: function(tracks){\n    tracks.each(function(track){\n      track = $H(track);\n      var data = track.values().first();\n      this.tracks.push($H({\n        ids:     track.keys().first(),\n        effect:  Effect.Morph,\n        options: { style: data }\n      }));\n    }.bind(this));\n    return this;\n  },\n  play: function(){\n    return new Effect.Parallel(\n      this.tracks.map(function(track){\n        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');\n        var elements = [$(ids) || $$(ids)].flatten();\n        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });\n      }).flatten(),\n      this.options\n    );\n  }\n});\n\nElement.CSS_PROPERTIES = $w(\n  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +\n  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +\n  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +\n  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +\n  'fontSize fontWeight height left letterSpacing lineHeight ' +\n  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+\n  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +\n  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +\n  'right textIndent top width wordSpacing zIndex');\n\nElement.CSS_LENGTH = /^(([\\+\\-]?[0-9\\.]+)(em|ex|px|in|cm|mm|pt|pc|\\%))|0$/;\n\nString.__parseStyleElement = document.createElement('div');\nString.prototype.parseStyle = function(){\n  var style, styleRules = $H();\n  if (Prototype.Browser.WebKit)\n    style = new Element('div',{style:this}).style;\n  else {\n    String.__parseStyleElement.innerHTML = '<div style=\"' + this + '\"></div>';\n    style = String.__parseStyleElement.childNodes[0].style;\n  }\n\n  Element.CSS_PROPERTIES.each(function(property){\n    if (style[property]) styleRules.set(property, style[property]);\n  });\n\n  if (Prototype.Browser.IE && this.include('opacity'))\n    styleRules.set('opacity', this.match(/opacity:\\s*((?:0|1)?(?:\\.\\d*)?)/)[1]);\n\n  return styleRules;\n};\n\nif (document.defaultView && document.defaultView.getComputedStyle) {\n  Element.getStyles = function(element) {\n    var css = document.defaultView.getComputedStyle($(element), null);\n    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {\n      styles[property] = css[property];\n      return styles;\n    });\n  };\n} else {\n  Element.getStyles = function(element) {\n    element = $(element);\n    var css = element.currentStyle, styles;\n    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {\n      results[property] = css[property];\n      return results;\n    });\n    if (!styles.opacity) styles.opacity = element.getOpacity();\n    return styles;\n  };\n}\n\nEffect.Methods = {\n  morph: function(element, style) {\n    element = $(element);\n    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));\n    return element;\n  },\n  visualEffect: function(element, effect, options) {\n    element = $(element);\n    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);\n    new Effect[klass](element, options);\n    return element;\n  },\n  highlight: function(element, options) {\n    element = $(element);\n    new Effect.Highlight(element, options);\n    return element;\n  }\n};\n\n$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+\n  'pulsate shake puff squish switchOff dropOut').each(\n  function(effect) {\n    Effect.Methods[effect] = function(element, options){\n      element = $(element);\n      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);\n      return element;\n    };\n  }\n);\n\n$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(\n  function(f) { Effect.Methods[f] = Element[f]; }\n);\n\nElement.addMethods(Effect.Methods);"
  },
  {
    "path": "example/rails3/public/javascripts/prototype.js",
    "content": "/*  Prototype JavaScript framework, version 1.6.1\n *  (c) 2005-2009 Sam Stephenson\n *\n *  Prototype is freely distributable under the terms of an MIT-style license.\n *  For details, see the Prototype web site: http://www.prototypejs.org/\n *\n *--------------------------------------------------------------------------*/\n\nvar Prototype = {\n  Version: '1.6.1',\n\n  Browser: (function(){\n    var ua = navigator.userAgent;\n    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';\n    return {\n      IE:             !!window.attachEvent && !isOpera,\n      Opera:          isOpera,\n      WebKit:         ua.indexOf('AppleWebKit/') > -1,\n      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,\n      MobileSafari:   /Apple.*Mobile.*Safari/.test(ua)\n    }\n  })(),\n\n  BrowserFeatures: {\n    XPath: !!document.evaluate,\n    SelectorsAPI: !!document.querySelector,\n    ElementExtensions: (function() {\n      var constructor = window.Element || window.HTMLElement;\n      return !!(constructor && constructor.prototype);\n    })(),\n    SpecificElementExtensions: (function() {\n      if (typeof window.HTMLDivElement !== 'undefined')\n        return true;\n\n      var div = document.createElement('div');\n      var form = document.createElement('form');\n      var isSupported = false;\n\n      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {\n        isSupported = true;\n      }\n\n      div = form = null;\n\n      return isSupported;\n    })()\n  },\n\n  ScriptFragment: '<script[^>]*>([\\\\S\\\\s]*?)<\\/script>',\n  JSONFilter: /^\\/\\*-secure-([\\s\\S]*)\\*\\/\\s*$/,\n\n  emptyFunction: function() { },\n  K: function(x) { return x }\n};\n\nif (Prototype.Browser.MobileSafari)\n  Prototype.BrowserFeatures.SpecificElementExtensions = false;\n\n\nvar Abstract = { };\n\n\nvar Try = {\n  these: function() {\n    var returnValue;\n\n    for (var i = 0, length = arguments.length; i < length; i++) {\n      var lambda = arguments[i];\n      try {\n        returnValue = lambda();\n        break;\n      } catch (e) { }\n    }\n\n    return returnValue;\n  }\n};\n\n/* Based on Alex Arnell's inheritance implementation. */\n\nvar Class = (function() {\n  function subclass() {};\n  function create() {\n    var parent = null, properties = $A(arguments);\n    if (Object.isFunction(properties[0]))\n      parent = properties.shift();\n\n    function klass() {\n      this.initialize.apply(this, arguments);\n    }\n\n    Object.extend(klass, Class.Methods);\n    klass.superclass = parent;\n    klass.subclasses = [];\n\n    if (parent) {\n      subclass.prototype = parent.prototype;\n      klass.prototype = new subclass;\n      parent.subclasses.push(klass);\n    }\n\n    for (var i = 0; i < properties.length; i++)\n      klass.addMethods(properties[i]);\n\n    if (!klass.prototype.initialize)\n      klass.prototype.initialize = Prototype.emptyFunction;\n\n    klass.prototype.constructor = klass;\n    return klass;\n  }\n\n  function addMethods(source) {\n    var ancestor   = this.superclass && this.superclass.prototype;\n    var properties = Object.keys(source);\n\n    if (!Object.keys({ toString: true }).length) {\n      if (source.toString != Object.prototype.toString)\n        properties.push(\"toString\");\n      if (source.valueOf != Object.prototype.valueOf)\n        properties.push(\"valueOf\");\n    }\n\n    for (var i = 0, length = properties.length; i < length; i++) {\n      var property = properties[i], value = source[property];\n      if (ancestor && Object.isFunction(value) &&\n          value.argumentNames().first() == \"$super\") {\n        var method = value;\n        value = (function(m) {\n          return function() { return ancestor[m].apply(this, arguments); };\n        })(property).wrap(method);\n\n        value.valueOf = method.valueOf.bind(method);\n        value.toString = method.toString.bind(method);\n      }\n      this.prototype[property] = value;\n    }\n\n    return this;\n  }\n\n  return {\n    create: create,\n    Methods: {\n      addMethods: addMethods\n    }\n  };\n})();\n(function() {\n\n  var _toString = Object.prototype.toString;\n\n  function extend(destination, source) {\n    for (var property in source)\n      destination[property] = source[property];\n    return destination;\n  }\n\n  function inspect(object) {\n    try {\n      if (isUndefined(object)) return 'undefined';\n      if (object === null) return 'null';\n      return object.inspect ? object.inspect() : String(object);\n    } catch (e) {\n      if (e instanceof RangeError) return '...';\n      throw e;\n    }\n  }\n\n  function toJSON(object) {\n    var type = typeof object;\n    switch (type) {\n      case 'undefined':\n      case 'function':\n      case 'unknown': return;\n      case 'boolean': return object.toString();\n    }\n\n    if (object === null) return 'null';\n    if (object.toJSON) return object.toJSON();\n    if (isElement(object)) return;\n\n    var results = [];\n    for (var property in object) {\n      var value = toJSON(object[property]);\n      if (!isUndefined(value))\n        results.push(property.toJSON() + ': ' + value);\n    }\n\n    return '{' + results.join(', ') + '}';\n  }\n\n  function toQueryString(object) {\n    return $H(object).toQueryString();\n  }\n\n  function toHTML(object) {\n    return object && object.toHTML ? object.toHTML() : String.interpret(object);\n  }\n\n  function keys(object) {\n    var results = [];\n    for (var property in object)\n      results.push(property);\n    return results;\n  }\n\n  function values(object) {\n    var results = [];\n    for (var property in object)\n      results.push(object[property]);\n    return results;\n  }\n\n  function clone(object) {\n    return extend({ }, object);\n  }\n\n  function isElement(object) {\n    return !!(object && object.nodeType == 1);\n  }\n\n  function isArray(object) {\n    return _toString.call(object) == \"[object Array]\";\n  }\n\n\n  function isHash(object) {\n    return object instanceof Hash;\n  }\n\n  function isFunction(object) {\n    return typeof object === \"function\";\n  }\n\n  function isString(object) {\n    return _toString.call(object) == \"[object String]\";\n  }\n\n  function isNumber(object) {\n    return _toString.call(object) == \"[object Number]\";\n  }\n\n  function isUndefined(object) {\n    return typeof object === \"undefined\";\n  }\n\n  extend(Object, {\n    extend:        extend,\n    inspect:       inspect,\n    toJSON:        toJSON,\n    toQueryString: toQueryString,\n    toHTML:        toHTML,\n    keys:          keys,\n    values:        values,\n    clone:         clone,\n    isElement:     isElement,\n    isArray:       isArray,\n    isHash:        isHash,\n    isFunction:    isFunction,\n    isString:      isString,\n    isNumber:      isNumber,\n    isUndefined:   isUndefined\n  });\n})();\nObject.extend(Function.prototype, (function() {\n  var slice = Array.prototype.slice;\n\n  function update(array, args) {\n    var arrayLength = array.length, length = args.length;\n    while (length--) array[arrayLength + length] = args[length];\n    return array;\n  }\n\n  function merge(array, args) {\n    array = slice.call(array, 0);\n    return update(array, args);\n  }\n\n  function argumentNames() {\n    var names = this.toString().match(/^[\\s\\(]*function[^(]*\\(([^)]*)\\)/)[1]\n      .replace(/\\/\\/.*?[\\r\\n]|\\/\\*(?:.|[\\r\\n])*?\\*\\//g, '')\n      .replace(/\\s+/g, '').split(',');\n    return names.length == 1 && !names[0] ? [] : names;\n  }\n\n  function bind(context) {\n    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;\n    var __method = this, args = slice.call(arguments, 1);\n    return function() {\n      var a = merge(args, arguments);\n      return __method.apply(context, a);\n    }\n  }\n\n  function bindAsEventListener(context) {\n    var __method = this, args = slice.call(arguments, 1);\n    return function(event) {\n      var a = update([event || window.event], args);\n      return __method.apply(context, a);\n    }\n  }\n\n  function curry() {\n    if (!arguments.length) return this;\n    var __method = this, args = slice.call(arguments, 0);\n    return function() {\n      var a = merge(args, arguments);\n      return __method.apply(this, a);\n    }\n  }\n\n  function delay(timeout) {\n    var __method = this, args = slice.call(arguments, 1);\n    timeout = timeout * 1000\n    return window.setTimeout(function() {\n      return __method.apply(__method, args);\n    }, timeout);\n  }\n\n  function defer() {\n    var args = update([0.01], arguments);\n    return this.delay.apply(this, args);\n  }\n\n  function wrap(wrapper) {\n    var __method = this;\n    return function() {\n      var a = update([__method.bind(this)], arguments);\n      return wrapper.apply(this, a);\n    }\n  }\n\n  function methodize() {\n    if (this._methodized) return this._methodized;\n    var __method = this;\n    return this._methodized = function() {\n      var a = update([this], arguments);\n      return __method.apply(null, a);\n    };\n  }\n\n  return {\n    argumentNames:       argumentNames,\n    bind:                bind,\n    bindAsEventListener: bindAsEventListener,\n    curry:               curry,\n    delay:               delay,\n    defer:               defer,\n    wrap:                wrap,\n    methodize:           methodize\n  }\n})());\n\n\nDate.prototype.toJSON = function() {\n  return '\"' + this.getUTCFullYear() + '-' +\n    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +\n    this.getUTCDate().toPaddedString(2) + 'T' +\n    this.getUTCHours().toPaddedString(2) + ':' +\n    this.getUTCMinutes().toPaddedString(2) + ':' +\n    this.getUTCSeconds().toPaddedString(2) + 'Z\"';\n};\n\n\nRegExp.prototype.match = RegExp.prototype.test;\n\nRegExp.escape = function(str) {\n  return String(str).replace(/([.*+?^=!:${}()|[\\]\\/\\\\])/g, '\\\\$1');\n};\nvar PeriodicalExecuter = Class.create({\n  initialize: function(callback, frequency) {\n    this.callback = callback;\n    this.frequency = frequency;\n    this.currentlyExecuting = false;\n\n    this.registerCallback();\n  },\n\n  registerCallback: function() {\n    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);\n  },\n\n  execute: function() {\n    this.callback(this);\n  },\n\n  stop: function() {\n    if (!this.timer) return;\n    clearInterval(this.timer);\n    this.timer = null;\n  },\n\n  onTimerEvent: function() {\n    if (!this.currentlyExecuting) {\n      try {\n        this.currentlyExecuting = true;\n        this.execute();\n        this.currentlyExecuting = false;\n      } catch(e) {\n        this.currentlyExecuting = false;\n        throw e;\n      }\n    }\n  }\n});\nObject.extend(String, {\n  interpret: function(value) {\n    return value == null ? '' : String(value);\n  },\n  specialChar: {\n    '\\b': '\\\\b',\n    '\\t': '\\\\t',\n    '\\n': '\\\\n',\n    '\\f': '\\\\f',\n    '\\r': '\\\\r',\n    '\\\\': '\\\\\\\\'\n  }\n});\n\nObject.extend(String.prototype, (function() {\n\n  function prepareReplacement(replacement) {\n    if (Object.isFunction(replacement)) return replacement;\n    var template = new Template(replacement);\n    return function(match) { return template.evaluate(match) };\n  }\n\n  function gsub(pattern, replacement) {\n    var result = '', source = this, match;\n    replacement = prepareReplacement(replacement);\n\n    if (Object.isString(pattern))\n      pattern = RegExp.escape(pattern);\n\n    if (!(pattern.length || pattern.source)) {\n      replacement = replacement('');\n      return replacement + source.split('').join(replacement) + replacement;\n    }\n\n    while (source.length > 0) {\n      if (match = source.match(pattern)) {\n        result += source.slice(0, match.index);\n        result += String.interpret(replacement(match));\n        source  = source.slice(match.index + match[0].length);\n      } else {\n        result += source, source = '';\n      }\n    }\n    return result;\n  }\n\n  function sub(pattern, replacement, count) {\n    replacement = prepareReplacement(replacement);\n    count = Object.isUndefined(count) ? 1 : count;\n\n    return this.gsub(pattern, function(match) {\n      if (--count < 0) return match[0];\n      return replacement(match);\n    });\n  }\n\n  function scan(pattern, iterator) {\n    this.gsub(pattern, iterator);\n    return String(this);\n  }\n\n  function truncate(length, truncation) {\n    length = length || 30;\n    truncation = Object.isUndefined(truncation) ? '...' : truncation;\n    return this.length > length ?\n      this.slice(0, length - truncation.length) + truncation : String(this);\n  }\n\n  function strip() {\n    return this.replace(/^\\s+/, '').replace(/\\s+$/, '');\n  }\n\n  function stripTags() {\n    return this.replace(/<\\w+(\\s+(\"[^\"]*\"|'[^']*'|[^>])+)?>|<\\/\\w+>/gi, '');\n  }\n\n  function stripScripts() {\n    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');\n  }\n\n  function extractScripts() {\n    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');\n    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');\n    return (this.match(matchAll) || []).map(function(scriptTag) {\n      return (scriptTag.match(matchOne) || ['', ''])[1];\n    });\n  }\n\n  function evalScripts() {\n    return this.extractScripts().map(function(script) { return eval(script) });\n  }\n\n  function escapeHTML() {\n    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n  }\n\n  function unescapeHTML() {\n    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');\n  }\n\n\n  function toQueryParams(separator) {\n    var match = this.strip().match(/([^?#]*)(#.*)?$/);\n    if (!match) return { };\n\n    return match[1].split(separator || '&').inject({ }, function(hash, pair) {\n      if ((pair = pair.split('='))[0]) {\n        var key = decodeURIComponent(pair.shift());\n        var value = pair.length > 1 ? pair.join('=') : pair[0];\n        if (value != undefined) value = decodeURIComponent(value);\n\n        if (key in hash) {\n          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];\n          hash[key].push(value);\n        }\n        else hash[key] = value;\n      }\n      return hash;\n    });\n  }\n\n  function toArray() {\n    return this.split('');\n  }\n\n  function succ() {\n    return this.slice(0, this.length - 1) +\n      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);\n  }\n\n  function times(count) {\n    return count < 1 ? '' : new Array(count + 1).join(this);\n  }\n\n  function camelize() {\n    var parts = this.split('-'), len = parts.length;\n    if (len == 1) return parts[0];\n\n    var camelized = this.charAt(0) == '-'\n      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)\n      : parts[0];\n\n    for (var i = 1; i < len; i++)\n      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);\n\n    return camelized;\n  }\n\n  function capitalize() {\n    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();\n  }\n\n  function underscore() {\n    return this.replace(/::/g, '/')\n               .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')\n               .replace(/([a-z\\d])([A-Z])/g, '$1_$2')\n               .replace(/-/g, '_')\n               .toLowerCase();\n  }\n\n  function dasherize() {\n    return this.replace(/_/g, '-');\n  }\n\n  function inspect(useDoubleQuotes) {\n    var escapedString = this.replace(/[\\x00-\\x1f\\\\]/g, function(character) {\n      if (character in String.specialChar) {\n        return String.specialChar[character];\n      }\n      return '\\\\u00' + character.charCodeAt().toPaddedString(2, 16);\n    });\n    if (useDoubleQuotes) return '\"' + escapedString.replace(/\"/g, '\\\\\"') + '\"';\n    return \"'\" + escapedString.replace(/'/g, '\\\\\\'') + \"'\";\n  }\n\n  function toJSON() {\n    return this.inspect(true);\n  }\n\n  function unfilterJSON(filter) {\n    return this.replace(filter || Prototype.JSONFilter, '$1');\n  }\n\n  function isJSON() {\n    var str = this;\n    if (str.blank()) return false;\n    str = this.replace(/\\\\./g, '@').replace(/\"[^\"\\\\\\n\\r]*\"/g, '');\n    return (/^[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]*$/).test(str);\n  }\n\n  function evalJSON(sanitize) {\n    var json = this.unfilterJSON();\n    try {\n      if (!sanitize || json.isJSON()) return eval('(' + json + ')');\n    } catch (e) { }\n    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());\n  }\n\n  function include(pattern) {\n    return this.indexOf(pattern) > -1;\n  }\n\n  function startsWith(pattern) {\n    return this.indexOf(pattern) === 0;\n  }\n\n  function endsWith(pattern) {\n    var d = this.length - pattern.length;\n    return d >= 0 && this.lastIndexOf(pattern) === d;\n  }\n\n  function empty() {\n    return this == '';\n  }\n\n  function blank() {\n    return /^\\s*$/.test(this);\n  }\n\n  function interpolate(object, pattern) {\n    return new Template(this, pattern).evaluate(object);\n  }\n\n  return {\n    gsub:           gsub,\n    sub:            sub,\n    scan:           scan,\n    truncate:       truncate,\n    strip:          String.prototype.trim ? String.prototype.trim : strip,\n    stripTags:      stripTags,\n    stripScripts:   stripScripts,\n    extractScripts: extractScripts,\n    evalScripts:    evalScripts,\n    escapeHTML:     escapeHTML,\n    unescapeHTML:   unescapeHTML,\n    toQueryParams:  toQueryParams,\n    parseQuery:     toQueryParams,\n    toArray:        toArray,\n    succ:           succ,\n    times:          times,\n    camelize:       camelize,\n    capitalize:     capitalize,\n    underscore:     underscore,\n    dasherize:      dasherize,\n    inspect:        inspect,\n    toJSON:         toJSON,\n    unfilterJSON:   unfilterJSON,\n    isJSON:         isJSON,\n    evalJSON:       evalJSON,\n    include:        include,\n    startsWith:     startsWith,\n    endsWith:       endsWith,\n    empty:          empty,\n    blank:          blank,\n    interpolate:    interpolate\n  };\n})());\n\nvar Template = Class.create({\n  initialize: function(template, pattern) {\n    this.template = template.toString();\n    this.pattern = pattern || Template.Pattern;\n  },\n\n  evaluate: function(object) {\n    if (object && Object.isFunction(object.toTemplateReplacements))\n      object = object.toTemplateReplacements();\n\n    return this.template.gsub(this.pattern, function(match) {\n      if (object == null) return (match[1] + '');\n\n      var before = match[1] || '';\n      if (before == '\\\\') return match[2];\n\n      var ctx = object, expr = match[3];\n      var pattern = /^([^.[]+|\\[((?:.*?[^\\\\])?)\\])(\\.|\\[|$)/;\n      match = pattern.exec(expr);\n      if (match == null) return before;\n\n      while (match != null) {\n        var comp = match[1].startsWith('[') ? match[2].replace(/\\\\\\\\]/g, ']') : match[1];\n        ctx = ctx[comp];\n        if (null == ctx || '' == match[3]) break;\n        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);\n        match = pattern.exec(expr);\n      }\n\n      return before + String.interpret(ctx);\n    });\n  }\n});\nTemplate.Pattern = /(^|.|\\r|\\n)(#\\{(.*?)\\})/;\n\nvar $break = { };\n\nvar Enumerable = (function() {\n  function each(iterator, context) {\n    var index = 0;\n    try {\n      this._each(function(value) {\n        iterator.call(context, value, index++);\n      });\n    } catch (e) {\n      if (e != $break) throw e;\n    }\n    return this;\n  }\n\n  function eachSlice(number, iterator, context) {\n    var index = -number, slices = [], array = this.toArray();\n    if (number < 1) return array;\n    while ((index += number) < array.length)\n      slices.push(array.slice(index, index+number));\n    return slices.collect(iterator, context);\n  }\n\n  function all(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result = true;\n    this.each(function(value, index) {\n      result = result && !!iterator.call(context, value, index);\n      if (!result) throw $break;\n    });\n    return result;\n  }\n\n  function any(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result = false;\n    this.each(function(value, index) {\n      if (result = !!iterator.call(context, value, index))\n        throw $break;\n    });\n    return result;\n  }\n\n  function collect(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var results = [];\n    this.each(function(value, index) {\n      results.push(iterator.call(context, value, index));\n    });\n    return results;\n  }\n\n  function detect(iterator, context) {\n    var result;\n    this.each(function(value, index) {\n      if (iterator.call(context, value, index)) {\n        result = value;\n        throw $break;\n      }\n    });\n    return result;\n  }\n\n  function findAll(iterator, context) {\n    var results = [];\n    this.each(function(value, index) {\n      if (iterator.call(context, value, index))\n        results.push(value);\n    });\n    return results;\n  }\n\n  function grep(filter, iterator, context) {\n    iterator = iterator || Prototype.K;\n    var results = [];\n\n    if (Object.isString(filter))\n      filter = new RegExp(RegExp.escape(filter));\n\n    this.each(function(value, index) {\n      if (filter.match(value))\n        results.push(iterator.call(context, value, index));\n    });\n    return results;\n  }\n\n  function include(object) {\n    if (Object.isFunction(this.indexOf))\n      if (this.indexOf(object) != -1) return true;\n\n    var found = false;\n    this.each(function(value) {\n      if (value == object) {\n        found = true;\n        throw $break;\n      }\n    });\n    return found;\n  }\n\n  function inGroupsOf(number, fillWith) {\n    fillWith = Object.isUndefined(fillWith) ? null : fillWith;\n    return this.eachSlice(number, function(slice) {\n      while(slice.length < number) slice.push(fillWith);\n      return slice;\n    });\n  }\n\n  function inject(memo, iterator, context) {\n    this.each(function(value, index) {\n      memo = iterator.call(context, memo, value, index);\n    });\n    return memo;\n  }\n\n  function invoke(method) {\n    var args = $A(arguments).slice(1);\n    return this.map(function(value) {\n      return value[method].apply(value, args);\n    });\n  }\n\n  function max(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result;\n    this.each(function(value, index) {\n      value = iterator.call(context, value, index);\n      if (result == null || value >= result)\n        result = value;\n    });\n    return result;\n  }\n\n  function min(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result;\n    this.each(function(value, index) {\n      value = iterator.call(context, value, index);\n      if (result == null || value < result)\n        result = value;\n    });\n    return result;\n  }\n\n  function partition(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var trues = [], falses = [];\n    this.each(function(value, index) {\n      (iterator.call(context, value, index) ?\n        trues : falses).push(value);\n    });\n    return [trues, falses];\n  }\n\n  function pluck(property) {\n    var results = [];\n    this.each(function(value) {\n      results.push(value[property]);\n    });\n    return results;\n  }\n\n  function reject(iterator, context) {\n    var results = [];\n    this.each(function(value, index) {\n      if (!iterator.call(context, value, index))\n        results.push(value);\n    });\n    return results;\n  }\n\n  function sortBy(iterator, context) {\n    return this.map(function(value, index) {\n      return {\n        value: value,\n        criteria: iterator.call(context, value, index)\n      };\n    }).sort(function(left, right) {\n      var a = left.criteria, b = right.criteria;\n      return a < b ? -1 : a > b ? 1 : 0;\n    }).pluck('value');\n  }\n\n  function toArray() {\n    return this.map();\n  }\n\n  function zip() {\n    var iterator = Prototype.K, args = $A(arguments);\n    if (Object.isFunction(args.last()))\n      iterator = args.pop();\n\n    var collections = [this].concat(args).map($A);\n    return this.map(function(value, index) {\n      return iterator(collections.pluck(index));\n    });\n  }\n\n  function size() {\n    return this.toArray().length;\n  }\n\n  function inspect() {\n    return '#<Enumerable:' + this.toArray().inspect() + '>';\n  }\n\n\n\n\n\n\n\n\n\n  return {\n    each:       each,\n    eachSlice:  eachSlice,\n    all:        all,\n    every:      all,\n    any:        any,\n    some:       any,\n    collect:    collect,\n    map:        collect,\n    detect:     detect,\n    findAll:    findAll,\n    select:     findAll,\n    filter:     findAll,\n    grep:       grep,\n    include:    include,\n    member:     include,\n    inGroupsOf: inGroupsOf,\n    inject:     inject,\n    invoke:     invoke,\n    max:        max,\n    min:        min,\n    partition:  partition,\n    pluck:      pluck,\n    reject:     reject,\n    sortBy:     sortBy,\n    toArray:    toArray,\n    entries:    toArray,\n    zip:        zip,\n    size:       size,\n    inspect:    inspect,\n    find:       detect\n  };\n})();\nfunction $A(iterable) {\n  if (!iterable) return [];\n  if ('toArray' in Object(iterable)) return iterable.toArray();\n  var length = iterable.length || 0, results = new Array(length);\n  while (length--) results[length] = iterable[length];\n  return results;\n}\n\nfunction $w(string) {\n  if (!Object.isString(string)) return [];\n  string = string.strip();\n  return string ? string.split(/\\s+/) : [];\n}\n\nArray.from = $A;\n\n\n(function() {\n  var arrayProto = Array.prototype,\n      slice = arrayProto.slice,\n      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available\n\n  function each(iterator) {\n    for (var i = 0, length = this.length; i < length; i++)\n      iterator(this[i]);\n  }\n  if (!_each) _each = each;\n\n  function clear() {\n    this.length = 0;\n    return this;\n  }\n\n  function first() {\n    return this[0];\n  }\n\n  function last() {\n    return this[this.length - 1];\n  }\n\n  function compact() {\n    return this.select(function(value) {\n      return value != null;\n    });\n  }\n\n  function flatten() {\n    return this.inject([], function(array, value) {\n      if (Object.isArray(value))\n        return array.concat(value.flatten());\n      array.push(value);\n      return array;\n    });\n  }\n\n  function without() {\n    var values = slice.call(arguments, 0);\n    return this.select(function(value) {\n      return !values.include(value);\n    });\n  }\n\n  function reverse(inline) {\n    return (inline !== false ? this : this.toArray())._reverse();\n  }\n\n  function uniq(sorted) {\n    return this.inject([], function(array, value, index) {\n      if (0 == index || (sorted ? array.last() != value : !array.include(value)))\n        array.push(value);\n      return array;\n    });\n  }\n\n  function intersect(array) {\n    return this.uniq().findAll(function(item) {\n      return array.detect(function(value) { return item === value });\n    });\n  }\n\n\n  function clone() {\n    return slice.call(this, 0);\n  }\n\n  function size() {\n    return this.length;\n  }\n\n  function inspect() {\n    return '[' + this.map(Object.inspect).join(', ') + ']';\n  }\n\n  function toJSON() {\n    var results = [];\n    this.each(function(object) {\n      var value = Object.toJSON(object);\n      if (!Object.isUndefined(value)) results.push(value);\n    });\n    return '[' + results.join(', ') + ']';\n  }\n\n  function indexOf(item, i) {\n    i || (i = 0);\n    var length = this.length;\n    if (i < 0) i = length + i;\n    for (; i < length; i++)\n      if (this[i] === item) return i;\n    return -1;\n  }\n\n  function lastIndexOf(item, i) {\n    i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;\n    var n = this.slice(0, i).reverse().indexOf(item);\n    return (n < 0) ? n : i - n - 1;\n  }\n\n  function concat() {\n    var array = slice.call(this, 0), item;\n    for (var i = 0, length = arguments.length; i < length; i++) {\n      item = arguments[i];\n      if (Object.isArray(item) && !('callee' in item)) {\n        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)\n          array.push(item[j]);\n      } else {\n        array.push(item);\n      }\n    }\n    return array;\n  }\n\n  Object.extend(arrayProto, Enumerable);\n\n  if (!arrayProto._reverse)\n    arrayProto._reverse = arrayProto.reverse;\n\n  Object.extend(arrayProto, {\n    _each:     _each,\n    clear:     clear,\n    first:     first,\n    last:      last,\n    compact:   compact,\n    flatten:   flatten,\n    without:   without,\n    reverse:   reverse,\n    uniq:      uniq,\n    intersect: intersect,\n    clone:     clone,\n    toArray:   clone,\n    size:      size,\n    inspect:   inspect,\n    toJSON:    toJSON\n  });\n\n  var CONCAT_ARGUMENTS_BUGGY = (function() {\n    return [].concat(arguments)[0][0] !== 1;\n  })(1,2)\n\n  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;\n\n  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;\n  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;\n})();\nfunction $H(object) {\n  return new Hash(object);\n};\n\nvar Hash = Class.create(Enumerable, (function() {\n  function initialize(object) {\n    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);\n  }\n\n  function _each(iterator) {\n    for (var key in this._object) {\n      var value = this._object[key], pair = [key, value];\n      pair.key = key;\n      pair.value = value;\n      iterator(pair);\n    }\n  }\n\n  function set(key, value) {\n    return this._object[key] = value;\n  }\n\n  function get(key) {\n    if (this._object[key] !== Object.prototype[key])\n      return this._object[key];\n  }\n\n  function unset(key) {\n    var value = this._object[key];\n    delete this._object[key];\n    return value;\n  }\n\n  function toObject() {\n    return Object.clone(this._object);\n  }\n\n  function keys() {\n    return this.pluck('key');\n  }\n\n  function values() {\n    return this.pluck('value');\n  }\n\n  function index(value) {\n    var match = this.detect(function(pair) {\n      return pair.value === value;\n    });\n    return match && match.key;\n  }\n\n  function merge(object) {\n    return this.clone().update(object);\n  }\n\n  function update(object) {\n    return new Hash(object).inject(this, function(result, pair) {\n      result.set(pair.key, pair.value);\n      return result;\n    });\n  }\n\n  function toQueryPair(key, value) {\n    if (Object.isUndefined(value)) return key;\n    return key + '=' + encodeURIComponent(String.interpret(value));\n  }\n\n  function toQueryString() {\n    return this.inject([], function(results, pair) {\n      var key = encodeURIComponent(pair.key), values = pair.value;\n\n      if (values && typeof values == 'object') {\n        if (Object.isArray(values))\n          return results.concat(values.map(toQueryPair.curry(key)));\n      } else results.push(toQueryPair(key, values));\n      return results;\n    }).join('&');\n  }\n\n  function inspect() {\n    return '#<Hash:{' + this.map(function(pair) {\n      return pair.map(Object.inspect).join(': ');\n    }).join(', ') + '}>';\n  }\n\n  function toJSON() {\n    return Object.toJSON(this.toObject());\n  }\n\n  function clone() {\n    return new Hash(this);\n  }\n\n  return {\n    initialize:             initialize,\n    _each:                  _each,\n    set:                    set,\n    get:                    get,\n    unset:                  unset,\n    toObject:               toObject,\n    toTemplateReplacements: toObject,\n    keys:                   keys,\n    values:                 values,\n    index:                  index,\n    merge:                  merge,\n    update:                 update,\n    toQueryString:          toQueryString,\n    inspect:                inspect,\n    toJSON:                 toJSON,\n    clone:                  clone\n  };\n})());\n\nHash.from = $H;\nObject.extend(Number.prototype, (function() {\n  function toColorPart() {\n    return this.toPaddedString(2, 16);\n  }\n\n  function succ() {\n    return this + 1;\n  }\n\n  function times(iterator, context) {\n    $R(0, this, true).each(iterator, context);\n    return this;\n  }\n\n  function toPaddedString(length, radix) {\n    var string = this.toString(radix || 10);\n    return '0'.times(length - string.length) + string;\n  }\n\n  function toJSON() {\n    return isFinite(this) ? this.toString() : 'null';\n  }\n\n  function abs() {\n    return Math.abs(this);\n  }\n\n  function round() {\n    return Math.round(this);\n  }\n\n  function ceil() {\n    return Math.ceil(this);\n  }\n\n  function floor() {\n    return Math.floor(this);\n  }\n\n  return {\n    toColorPart:    toColorPart,\n    succ:           succ,\n    times:          times,\n    toPaddedString: toPaddedString,\n    toJSON:         toJSON,\n    abs:            abs,\n    round:          round,\n    ceil:           ceil,\n    floor:          floor\n  };\n})());\n\nfunction $R(start, end, exclusive) {\n  return new ObjectRange(start, end, exclusive);\n}\n\nvar ObjectRange = Class.create(Enumerable, (function() {\n  function initialize(start, end, exclusive) {\n    this.start = start;\n    this.end = end;\n    this.exclusive = exclusive;\n  }\n\n  function _each(iterator) {\n    var value = this.start;\n    while (this.include(value)) {\n      iterator(value);\n      value = value.succ();\n    }\n  }\n\n  function include(value) {\n    if (value < this.start)\n      return false;\n    if (this.exclusive)\n      return value < this.end;\n    return value <= this.end;\n  }\n\n  return {\n    initialize: initialize,\n    _each:      _each,\n    include:    include\n  };\n})());\n\n\n\nvar Ajax = {\n  getTransport: function() {\n    return Try.these(\n      function() {return new XMLHttpRequest()},\n      function() {return new ActiveXObject('Msxml2.XMLHTTP')},\n      function() {return new ActiveXObject('Microsoft.XMLHTTP')}\n    ) || false;\n  },\n\n  activeRequestCount: 0\n};\n\nAjax.Responders = {\n  responders: [],\n\n  _each: function(iterator) {\n    this.responders._each(iterator);\n  },\n\n  register: function(responder) {\n    if (!this.include(responder))\n      this.responders.push(responder);\n  },\n\n  unregister: function(responder) {\n    this.responders = this.responders.without(responder);\n  },\n\n  dispatch: function(callback, request, transport, json) {\n    this.each(function(responder) {\n      if (Object.isFunction(responder[callback])) {\n        try {\n          responder[callback].apply(responder, [request, transport, json]);\n        } catch (e) { }\n      }\n    });\n  }\n};\n\nObject.extend(Ajax.Responders, Enumerable);\n\nAjax.Responders.register({\n  onCreate:   function() { Ajax.activeRequestCount++ },\n  onComplete: function() { Ajax.activeRequestCount-- }\n});\nAjax.Base = Class.create({\n  initialize: function(options) {\n    this.options = {\n      method:       'post',\n      asynchronous: true,\n      contentType:  'application/x-www-form-urlencoded',\n      encoding:     'UTF-8',\n      parameters:   '',\n      evalJSON:     true,\n      evalJS:       true\n    };\n    Object.extend(this.options, options || { });\n\n    this.options.method = this.options.method.toLowerCase();\n\n    if (Object.isString(this.options.parameters))\n      this.options.parameters = this.options.parameters.toQueryParams();\n    else if (Object.isHash(this.options.parameters))\n      this.options.parameters = this.options.parameters.toObject();\n  }\n});\nAjax.Request = Class.create(Ajax.Base, {\n  _complete: false,\n\n  initialize: function($super, url, options) {\n    $super(options);\n    this.transport = Ajax.getTransport();\n    this.request(url);\n  },\n\n  request: function(url) {\n    this.url = url;\n    this.method = this.options.method;\n    var params = Object.clone(this.options.parameters);\n\n    if (!['get', 'post'].include(this.method)) {\n      params['_method'] = this.method;\n      this.method = 'post';\n    }\n\n    this.parameters = params;\n\n    if (params = Object.toQueryString(params)) {\n      if (this.method == 'get')\n        this.url += (this.url.include('?') ? '&' : '?') + params;\n      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))\n        params += '&_=';\n    }\n\n    try {\n      var response = new Ajax.Response(this);\n      if (this.options.onCreate) this.options.onCreate(response);\n      Ajax.Responders.dispatch('onCreate', this, response);\n\n      this.transport.open(this.method.toUpperCase(), this.url,\n        this.options.asynchronous);\n\n      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);\n\n      this.transport.onreadystatechange = this.onStateChange.bind(this);\n      this.setRequestHeaders();\n\n      this.body = this.method == 'post' ? (this.options.postBody || params) : null;\n      this.transport.send(this.body);\n\n      /* Force Firefox to handle ready state 4 for synchronous requests */\n      if (!this.options.asynchronous && this.transport.overrideMimeType)\n        this.onStateChange();\n\n    }\n    catch (e) {\n      this.dispatchException(e);\n    }\n  },\n\n  onStateChange: function() {\n    var readyState = this.transport.readyState;\n    if (readyState > 1 && !((readyState == 4) && this._complete))\n      this.respondToReadyState(this.transport.readyState);\n  },\n\n  setRequestHeaders: function() {\n    var headers = {\n      'X-Requested-With': 'XMLHttpRequest',\n      'X-Prototype-Version': Prototype.Version,\n      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'\n    };\n\n    if (this.method == 'post') {\n      headers['Content-type'] = this.options.contentType +\n        (this.options.encoding ? '; charset=' + this.options.encoding : '');\n\n      /* Force \"Connection: close\" for older Mozilla browsers to work\n       * around a bug where XMLHttpRequest sends an incorrect\n       * Content-length header. See Mozilla Bugzilla #246651.\n       */\n      if (this.transport.overrideMimeType &&\n          (navigator.userAgent.match(/Gecko\\/(\\d{4})/) || [0,2005])[1] < 2005)\n            headers['Connection'] = 'close';\n    }\n\n    if (typeof this.options.requestHeaders == 'object') {\n      var extras = this.options.requestHeaders;\n\n      if (Object.isFunction(extras.push))\n        for (var i = 0, length = extras.length; i < length; i += 2)\n          headers[extras[i]] = extras[i+1];\n      else\n        $H(extras).each(function(pair) { headers[pair.key] = pair.value });\n    }\n\n    for (var name in headers)\n      this.transport.setRequestHeader(name, headers[name]);\n  },\n\n  success: function() {\n    var status = this.getStatus();\n    return !status || (status >= 200 && status < 300);\n  },\n\n  getStatus: function() {\n    try {\n      return this.transport.status || 0;\n    } catch (e) { return 0 }\n  },\n\n  respondToReadyState: function(readyState) {\n    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);\n\n    if (state == 'Complete') {\n      try {\n        this._complete = true;\n        (this.options['on' + response.status]\n         || this.options['on' + (this.success() ? 'Success' : 'Failure')]\n         || Prototype.emptyFunction)(response, response.headerJSON);\n      } catch (e) {\n        this.dispatchException(e);\n      }\n\n      var contentType = response.getHeader('Content-type');\n      if (this.options.evalJS == 'force'\n          || (this.options.evalJS && this.isSameOrigin() && contentType\n          && contentType.match(/^\\s*(text|application)\\/(x-)?(java|ecma)script(;.*)?\\s*$/i)))\n        this.evalResponse();\n    }\n\n    try {\n      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);\n      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);\n    } catch (e) {\n      this.dispatchException(e);\n    }\n\n    if (state == 'Complete') {\n      this.transport.onreadystatechange = Prototype.emptyFunction;\n    }\n  },\n\n  isSameOrigin: function() {\n    var m = this.url.match(/^\\s*https?:\\/\\/[^\\/]*/);\n    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({\n      protocol: location.protocol,\n      domain: document.domain,\n      port: location.port ? ':' + location.port : ''\n    }));\n  },\n\n  getHeader: function(name) {\n    try {\n      return this.transport.getResponseHeader(name) || null;\n    } catch (e) { return null; }\n  },\n\n  evalResponse: function() {\n    try {\n      return eval((this.transport.responseText || '').unfilterJSON());\n    } catch (e) {\n      this.dispatchException(e);\n    }\n  },\n\n  dispatchException: function(exception) {\n    (this.options.onException || Prototype.emptyFunction)(this, exception);\n    Ajax.Responders.dispatch('onException', this, exception);\n  }\n});\n\nAjax.Request.Events =\n  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];\n\n\n\n\n\n\n\n\nAjax.Response = Class.create({\n  initialize: function(request){\n    this.request = request;\n    var transport  = this.transport  = request.transport,\n        readyState = this.readyState = transport.readyState;\n\n    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {\n      this.status       = this.getStatus();\n      this.statusText   = this.getStatusText();\n      this.responseText = String.interpret(transport.responseText);\n      this.headerJSON   = this._getHeaderJSON();\n    }\n\n    if(readyState == 4) {\n      var xml = transport.responseXML;\n      this.responseXML  = Object.isUndefined(xml) ? null : xml;\n      this.responseJSON = this._getResponseJSON();\n    }\n  },\n\n  status:      0,\n\n  statusText: '',\n\n  getStatus: Ajax.Request.prototype.getStatus,\n\n  getStatusText: function() {\n    try {\n      return this.transport.statusText || '';\n    } catch (e) { return '' }\n  },\n\n  getHeader: Ajax.Request.prototype.getHeader,\n\n  getAllHeaders: function() {\n    try {\n      return this.getAllResponseHeaders();\n    } catch (e) { return null }\n  },\n\n  getResponseHeader: function(name) {\n    return this.transport.getResponseHeader(name);\n  },\n\n  getAllResponseHeaders: function() {\n    return this.transport.getAllResponseHeaders();\n  },\n\n  _getHeaderJSON: function() {\n    var json = this.getHeader('X-JSON');\n    if (!json) return null;\n    json = decodeURIComponent(escape(json));\n    try {\n      return json.evalJSON(this.request.options.sanitizeJSON ||\n        !this.request.isSameOrigin());\n    } catch (e) {\n      this.request.dispatchException(e);\n    }\n  },\n\n  _getResponseJSON: function() {\n    var options = this.request.options;\n    if (!options.evalJSON || (options.evalJSON != 'force' &&\n      !(this.getHeader('Content-type') || '').include('application/json')) ||\n        this.responseText.blank())\n          return null;\n    try {\n      return this.responseText.evalJSON(options.sanitizeJSON ||\n        !this.request.isSameOrigin());\n    } catch (e) {\n      this.request.dispatchException(e);\n    }\n  }\n});\n\nAjax.Updater = Class.create(Ajax.Request, {\n  initialize: function($super, container, url, options) {\n    this.container = {\n      success: (container.success || container),\n      failure: (container.failure || (container.success ? null : container))\n    };\n\n    options = Object.clone(options);\n    var onComplete = options.onComplete;\n    options.onComplete = (function(response, json) {\n      this.updateContent(response.responseText);\n      if (Object.isFunction(onComplete)) onComplete(response, json);\n    }).bind(this);\n\n    $super(url, options);\n  },\n\n  updateContent: function(responseText) {\n    var receiver = this.container[this.success() ? 'success' : 'failure'],\n        options = this.options;\n\n    if (!options.evalScripts) responseText = responseText.stripScripts();\n\n    if (receiver = $(receiver)) {\n      if (options.insertion) {\n        if (Object.isString(options.insertion)) {\n          var insertion = { }; insertion[options.insertion] = responseText;\n          receiver.insert(insertion);\n        }\n        else options.insertion(receiver, responseText);\n      }\n      else receiver.update(responseText);\n    }\n  }\n});\n\nAjax.PeriodicalUpdater = Class.create(Ajax.Base, {\n  initialize: function($super, container, url, options) {\n    $super(options);\n    this.onComplete = this.options.onComplete;\n\n    this.frequency = (this.options.frequency || 2);\n    this.decay = (this.options.decay || 1);\n\n    this.updater = { };\n    this.container = container;\n    this.url = url;\n\n    this.start();\n  },\n\n  start: function() {\n    this.options.onComplete = this.updateComplete.bind(this);\n    this.onTimerEvent();\n  },\n\n  stop: function() {\n    this.updater.options.onComplete = undefined;\n    clearTimeout(this.timer);\n    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);\n  },\n\n  updateComplete: function(response) {\n    if (this.options.decay) {\n      this.decay = (response.responseText == this.lastText ?\n        this.decay * this.options.decay : 1);\n\n      this.lastText = response.responseText;\n    }\n    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);\n  },\n\n  onTimerEvent: function() {\n    this.updater = new Ajax.Updater(this.container, this.url, this.options);\n  }\n});\n\n\n\nfunction $(element) {\n  if (arguments.length > 1) {\n    for (var i = 0, elements = [], length = arguments.length; i < length; i++)\n      elements.push($(arguments[i]));\n    return elements;\n  }\n  if (Object.isString(element))\n    element = document.getElementById(element);\n  return Element.extend(element);\n}\n\nif (Prototype.BrowserFeatures.XPath) {\n  document._getElementsByXPath = function(expression, parentElement) {\n    var results = [];\n    var query = document.evaluate(expression, $(parentElement) || document,\n      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n    for (var i = 0, length = query.snapshotLength; i < length; i++)\n      results.push(Element.extend(query.snapshotItem(i)));\n    return results;\n  };\n}\n\n/*--------------------------------------------------------------------------*/\n\nif (!window.Node) var Node = { };\n\nif (!Node.ELEMENT_NODE) {\n  Object.extend(Node, {\n    ELEMENT_NODE: 1,\n    ATTRIBUTE_NODE: 2,\n    TEXT_NODE: 3,\n    CDATA_SECTION_NODE: 4,\n    ENTITY_REFERENCE_NODE: 5,\n    ENTITY_NODE: 6,\n    PROCESSING_INSTRUCTION_NODE: 7,\n    COMMENT_NODE: 8,\n    DOCUMENT_NODE: 9,\n    DOCUMENT_TYPE_NODE: 10,\n    DOCUMENT_FRAGMENT_NODE: 11,\n    NOTATION_NODE: 12\n  });\n}\n\n\n(function(global) {\n\n  var SETATTRIBUTE_IGNORES_NAME = (function(){\n    var elForm = document.createElement(\"form\");\n    var elInput = document.createElement(\"input\");\n    var root = document.documentElement;\n    elInput.setAttribute(\"name\", \"test\");\n    elForm.appendChild(elInput);\n    root.appendChild(elForm);\n    var isBuggy = elForm.elements\n      ? (typeof elForm.elements.test == \"undefined\")\n      : null;\n    root.removeChild(elForm);\n    elForm = elInput = null;\n    return isBuggy;\n  })();\n\n  var element = global.Element;\n  global.Element = function(tagName, attributes) {\n    attributes = attributes || { };\n    tagName = tagName.toLowerCase();\n    var cache = Element.cache;\n    if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {\n      tagName = '<' + tagName + ' name=\"' + attributes.name + '\">';\n      delete attributes.name;\n      return Element.writeAttribute(document.createElement(tagName), attributes);\n    }\n    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));\n    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);\n  };\n  Object.extend(global.Element, element || { });\n  if (element) global.Element.prototype = element.prototype;\n})(this);\n\nElement.cache = { };\nElement.idCounter = 1;\n\nElement.Methods = {\n  visible: function(element) {\n    return $(element).style.display != 'none';\n  },\n\n  toggle: function(element) {\n    element = $(element);\n    Element[Element.visible(element) ? 'hide' : 'show'](element);\n    return element;\n  },\n\n\n  hide: function(element) {\n    element = $(element);\n    element.style.display = 'none';\n    return element;\n  },\n\n  show: function(element) {\n    element = $(element);\n    element.style.display = '';\n    return element;\n  },\n\n  remove: function(element) {\n    element = $(element);\n    element.parentNode.removeChild(element);\n    return element;\n  },\n\n  update: (function(){\n\n    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){\n      var el = document.createElement(\"select\"),\n          isBuggy = true;\n      el.innerHTML = \"<option value=\\\"test\\\">test</option>\";\n      if (el.options && el.options[0]) {\n        isBuggy = el.options[0].nodeName.toUpperCase() !== \"OPTION\";\n      }\n      el = null;\n      return isBuggy;\n    })();\n\n    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){\n      try {\n        var el = document.createElement(\"table\");\n        if (el && el.tBodies) {\n          el.innerHTML = \"<tbody><tr><td>test</td></tr></tbody>\";\n          var isBuggy = typeof el.tBodies[0] == \"undefined\";\n          el = null;\n          return isBuggy;\n        }\n      } catch (e) {\n        return true;\n      }\n    })();\n\n    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {\n      var s = document.createElement(\"script\"),\n          isBuggy = false;\n      try {\n        s.appendChild(document.createTextNode(\"\"));\n        isBuggy = !s.firstChild ||\n          s.firstChild && s.firstChild.nodeType !== 3;\n      } catch (e) {\n        isBuggy = true;\n      }\n      s = null;\n      return isBuggy;\n    })();\n\n    function update(element, content) {\n      element = $(element);\n\n      if (content && content.toElement)\n        content = content.toElement();\n\n      if (Object.isElement(content))\n        return element.update().insert(content);\n\n      content = Object.toHTML(content);\n\n      var tagName = element.tagName.toUpperCase();\n\n      if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {\n        element.text = content;\n        return element;\n      }\n\n      if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {\n        if (tagName in Element._insertionTranslations.tags) {\n          while (element.firstChild) {\n            element.removeChild(element.firstChild);\n          }\n          Element._getContentFromAnonymousElement(tagName, content.stripScripts())\n            .each(function(node) {\n              element.appendChild(node)\n            });\n        }\n        else {\n          element.innerHTML = content.stripScripts();\n        }\n      }\n      else {\n        element.innerHTML = content.stripScripts();\n      }\n\n      content.evalScripts.bind(content).defer();\n      return element;\n    }\n\n    return update;\n  })(),\n\n  replace: function(element, content) {\n    element = $(element);\n    if (content && content.toElement) content = content.toElement();\n    else if (!Object.isElement(content)) {\n      content = Object.toHTML(content);\n      var range = element.ownerDocument.createRange();\n      range.selectNode(element);\n      content.evalScripts.bind(content).defer();\n      content = range.createContextualFragment(content.stripScripts());\n    }\n    element.parentNode.replaceChild(content, element);\n    return element;\n  },\n\n  insert: function(element, insertions) {\n    element = $(element);\n\n    if (Object.isString(insertions) || Object.isNumber(insertions) ||\n        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))\n          insertions = {bottom:insertions};\n\n    var content, insert, tagName, childNodes;\n\n    for (var position in insertions) {\n      content  = insertions[position];\n      position = position.toLowerCase();\n      insert = Element._insertionTranslations[position];\n\n      if (content && content.toElement) content = content.toElement();\n      if (Object.isElement(content)) {\n        insert(element, content);\n        continue;\n      }\n\n      content = Object.toHTML(content);\n\n      tagName = ((position == 'before' || position == 'after')\n        ? element.parentNode : element).tagName.toUpperCase();\n\n      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());\n\n      if (position == 'top' || position == 'after') childNodes.reverse();\n      childNodes.each(insert.curry(element));\n\n      content.evalScripts.bind(content).defer();\n    }\n\n    return element;\n  },\n\n  wrap: function(element, wrapper, attributes) {\n    element = $(element);\n    if (Object.isElement(wrapper))\n      $(wrapper).writeAttribute(attributes || { });\n    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);\n    else wrapper = new Element('div', wrapper);\n    if (element.parentNode)\n      element.parentNode.replaceChild(wrapper, element);\n    wrapper.appendChild(element);\n    return wrapper;\n  },\n\n  inspect: function(element) {\n    element = $(element);\n    var result = '<' + element.tagName.toLowerCase();\n    $H({'id': 'id', 'className': 'class'}).each(function(pair) {\n      var property = pair.first(), attribute = pair.last();\n      var value = (element[property] || '').toString();\n      if (value) result += ' ' + attribute + '=' + value.inspect(true);\n    });\n    return result + '>';\n  },\n\n  recursivelyCollect: function(element, property) {\n    element = $(element);\n    var elements = [];\n    while (element = element[property])\n      if (element.nodeType == 1)\n        elements.push(Element.extend(element));\n    return elements;\n  },\n\n  ancestors: function(element) {\n    return Element.recursivelyCollect(element, 'parentNode');\n  },\n\n  descendants: function(element) {\n    return Element.select(element, \"*\");\n  },\n\n  firstDescendant: function(element) {\n    element = $(element).firstChild;\n    while (element && element.nodeType != 1) element = element.nextSibling;\n    return $(element);\n  },\n\n  immediateDescendants: function(element) {\n    if (!(element = $(element).firstChild)) return [];\n    while (element && element.nodeType != 1) element = element.nextSibling;\n    if (element) return [element].concat($(element).nextSiblings());\n    return [];\n  },\n\n  previousSiblings: function(element) {\n    return Element.recursivelyCollect(element, 'previousSibling');\n  },\n\n  nextSiblings: function(element) {\n    return Element.recursivelyCollect(element, 'nextSibling');\n  },\n\n  siblings: function(element) {\n    element = $(element);\n    return Element.previousSiblings(element).reverse()\n      .concat(Element.nextSiblings(element));\n  },\n\n  match: function(element, selector) {\n    if (Object.isString(selector))\n      selector = new Selector(selector);\n    return selector.match($(element));\n  },\n\n  up: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(element.parentNode);\n    var ancestors = Element.ancestors(element);\n    return Object.isNumber(expression) ? ancestors[expression] :\n      Selector.findElement(ancestors, expression, index);\n  },\n\n  down: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return Element.firstDescendant(element);\n    return Object.isNumber(expression) ? Element.descendants(element)[expression] :\n      Element.select(element, expression)[index || 0];\n  },\n\n  previous: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));\n    var previousSiblings = Element.previousSiblings(element);\n    return Object.isNumber(expression) ? previousSiblings[expression] :\n      Selector.findElement(previousSiblings, expression, index);\n  },\n\n  next: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));\n    var nextSiblings = Element.nextSiblings(element);\n    return Object.isNumber(expression) ? nextSiblings[expression] :\n      Selector.findElement(nextSiblings, expression, index);\n  },\n\n\n  select: function(element) {\n    var args = Array.prototype.slice.call(arguments, 1);\n    return Selector.findChildElements(element, args);\n  },\n\n  adjacent: function(element) {\n    var args = Array.prototype.slice.call(arguments, 1);\n    return Selector.findChildElements(element.parentNode, args).without(element);\n  },\n\n  identify: function(element) {\n    element = $(element);\n    var id = Element.readAttribute(element, 'id');\n    if (id) return id;\n    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));\n    Element.writeAttribute(element, 'id', id);\n    return id;\n  },\n\n  readAttribute: function(element, name) {\n    element = $(element);\n    if (Prototype.Browser.IE) {\n      var t = Element._attributeTranslations.read;\n      if (t.values[name]) return t.values[name](element, name);\n      if (t.names[name]) name = t.names[name];\n      if (name.include(':')) {\n        return (!element.attributes || !element.attributes[name]) ? null :\n         element.attributes[name].value;\n      }\n    }\n    return element.getAttribute(name);\n  },\n\n  writeAttribute: function(element, name, value) {\n    element = $(element);\n    var attributes = { }, t = Element._attributeTranslations.write;\n\n    if (typeof name == 'object') attributes = name;\n    else attributes[name] = Object.isUndefined(value) ? true : value;\n\n    for (var attr in attributes) {\n      name = t.names[attr] || attr;\n      value = attributes[attr];\n      if (t.values[attr]) name = t.values[attr](element, value);\n      if (value === false || value === null)\n        element.removeAttribute(name);\n      else if (value === true)\n        element.setAttribute(name, name);\n      else element.setAttribute(name, value);\n    }\n    return element;\n  },\n\n  getHeight: function(element) {\n    return Element.getDimensions(element).height;\n  },\n\n  getWidth: function(element) {\n    return Element.getDimensions(element).width;\n  },\n\n  classNames: function(element) {\n    return new Element.ClassNames(element);\n  },\n\n  hasClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    var elementClassName = element.className;\n    return (elementClassName.length > 0 && (elementClassName == className ||\n      new RegExp(\"(^|\\\\s)\" + className + \"(\\\\s|$)\").test(elementClassName)));\n  },\n\n  addClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    if (!Element.hasClassName(element, className))\n      element.className += (element.className ? ' ' : '') + className;\n    return element;\n  },\n\n  removeClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    element.className = element.className.replace(\n      new RegExp(\"(^|\\\\s+)\" + className + \"(\\\\s+|$)\"), ' ').strip();\n    return element;\n  },\n\n  toggleClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    return Element[Element.hasClassName(element, className) ?\n      'removeClassName' : 'addClassName'](element, className);\n  },\n\n  cleanWhitespace: function(element) {\n    element = $(element);\n    var node = element.firstChild;\n    while (node) {\n      var nextNode = node.nextSibling;\n      if (node.nodeType == 3 && !/\\S/.test(node.nodeValue))\n        element.removeChild(node);\n      node = nextNode;\n    }\n    return element;\n  },\n\n  empty: function(element) {\n    return $(element).innerHTML.blank();\n  },\n\n  descendantOf: function(element, ancestor) {\n    element = $(element), ancestor = $(ancestor);\n\n    if (element.compareDocumentPosition)\n      return (element.compareDocumentPosition(ancestor) & 8) === 8;\n\n    if (ancestor.contains)\n      return ancestor.contains(element) && ancestor !== element;\n\n    while (element = element.parentNode)\n      if (element == ancestor) return true;\n\n    return false;\n  },\n\n  scrollTo: function(element) {\n    element = $(element);\n    var pos = Element.cumulativeOffset(element);\n    window.scrollTo(pos[0], pos[1]);\n    return element;\n  },\n\n  getStyle: function(element, style) {\n    element = $(element);\n    style = style == 'float' ? 'cssFloat' : style.camelize();\n    var value = element.style[style];\n    if (!value || value == 'auto') {\n      var css = document.defaultView.getComputedStyle(element, null);\n      value = css ? css[style] : null;\n    }\n    if (style == 'opacity') return value ? parseFloat(value) : 1.0;\n    return value == 'auto' ? null : value;\n  },\n\n  getOpacity: function(element) {\n    return $(element).getStyle('opacity');\n  },\n\n  setStyle: function(element, styles) {\n    element = $(element);\n    var elementStyle = element.style, match;\n    if (Object.isString(styles)) {\n      element.style.cssText += ';' + styles;\n      return styles.include('opacity') ?\n        element.setOpacity(styles.match(/opacity:\\s*(\\d?\\.?\\d*)/)[1]) : element;\n    }\n    for (var property in styles)\n      if (property == 'opacity') element.setOpacity(styles[property]);\n      else\n        elementStyle[(property == 'float' || property == 'cssFloat') ?\n          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :\n            property] = styles[property];\n\n    return element;\n  },\n\n  setOpacity: function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1 || value === '') ? '' :\n      (value < 0.00001) ? 0 : value;\n    return element;\n  },\n\n  getDimensions: function(element) {\n    element = $(element);\n    var display = Element.getStyle(element, 'display');\n    if (display != 'none' && display != null) // Safari bug\n      return {width: element.offsetWidth, height: element.offsetHeight};\n\n    var els = element.style;\n    var originalVisibility = els.visibility;\n    var originalPosition = els.position;\n    var originalDisplay = els.display;\n    els.visibility = 'hidden';\n    if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari\n      els.position = 'absolute';\n    els.display = 'block';\n    var originalWidth = element.clientWidth;\n    var originalHeight = element.clientHeight;\n    els.display = originalDisplay;\n    els.position = originalPosition;\n    els.visibility = originalVisibility;\n    return {width: originalWidth, height: originalHeight};\n  },\n\n  makePositioned: function(element) {\n    element = $(element);\n    var pos = Element.getStyle(element, 'position');\n    if (pos == 'static' || !pos) {\n      element._madePositioned = true;\n      element.style.position = 'relative';\n      if (Prototype.Browser.Opera) {\n        element.style.top = 0;\n        element.style.left = 0;\n      }\n    }\n    return element;\n  },\n\n  undoPositioned: function(element) {\n    element = $(element);\n    if (element._madePositioned) {\n      element._madePositioned = undefined;\n      element.style.position =\n        element.style.top =\n        element.style.left =\n        element.style.bottom =\n        element.style.right = '';\n    }\n    return element;\n  },\n\n  makeClipping: function(element) {\n    element = $(element);\n    if (element._overflow) return element;\n    element._overflow = Element.getStyle(element, 'overflow') || 'auto';\n    if (element._overflow !== 'hidden')\n      element.style.overflow = 'hidden';\n    return element;\n  },\n\n  undoClipping: function(element) {\n    element = $(element);\n    if (!element._overflow) return element;\n    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;\n    element._overflow = null;\n    return element;\n  },\n\n  cumulativeOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      element = element.offsetParent;\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  positionedOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      element = element.offsetParent;\n      if (element) {\n        if (element.tagName.toUpperCase() == 'BODY') break;\n        var p = Element.getStyle(element, 'position');\n        if (p !== 'static') break;\n      }\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  absolutize: function(element) {\n    element = $(element);\n    if (Element.getStyle(element, 'position') == 'absolute') return element;\n\n    var offsets = Element.positionedOffset(element);\n    var top     = offsets[1];\n    var left    = offsets[0];\n    var width   = element.clientWidth;\n    var height  = element.clientHeight;\n\n    element._originalLeft   = left - parseFloat(element.style.left  || 0);\n    element._originalTop    = top  - parseFloat(element.style.top || 0);\n    element._originalWidth  = element.style.width;\n    element._originalHeight = element.style.height;\n\n    element.style.position = 'absolute';\n    element.style.top    = top + 'px';\n    element.style.left   = left + 'px';\n    element.style.width  = width + 'px';\n    element.style.height = height + 'px';\n    return element;\n  },\n\n  relativize: function(element) {\n    element = $(element);\n    if (Element.getStyle(element, 'position') == 'relative') return element;\n\n    element.style.position = 'relative';\n    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);\n    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);\n\n    element.style.top    = top + 'px';\n    element.style.left   = left + 'px';\n    element.style.height = element._originalHeight;\n    element.style.width  = element._originalWidth;\n    return element;\n  },\n\n  cumulativeScrollOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.scrollTop  || 0;\n      valueL += element.scrollLeft || 0;\n      element = element.parentNode;\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  getOffsetParent: function(element) {\n    if (element.offsetParent) return $(element.offsetParent);\n    if (element == document.body) return $(element);\n\n    while ((element = element.parentNode) && element != document.body)\n      if (Element.getStyle(element, 'position') != 'static')\n        return $(element);\n\n    return $(document.body);\n  },\n\n  viewportOffset: function(forElement) {\n    var valueT = 0, valueL = 0;\n\n    var element = forElement;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n\n      if (element.offsetParent == document.body &&\n        Element.getStyle(element, 'position') == 'absolute') break;\n\n    } while (element = element.offsetParent);\n\n    element = forElement;\n    do {\n      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {\n        valueT -= element.scrollTop  || 0;\n        valueL -= element.scrollLeft || 0;\n      }\n    } while (element = element.parentNode);\n\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  clonePosition: function(element, source) {\n    var options = Object.extend({\n      setLeft:    true,\n      setTop:     true,\n      setWidth:   true,\n      setHeight:  true,\n      offsetTop:  0,\n      offsetLeft: 0\n    }, arguments[2] || { });\n\n    source = $(source);\n    var p = Element.viewportOffset(source);\n\n    element = $(element);\n    var delta = [0, 0];\n    var parent = null;\n    if (Element.getStyle(element, 'position') == 'absolute') {\n      parent = Element.getOffsetParent(element);\n      delta = Element.viewportOffset(parent);\n    }\n\n    if (parent == document.body) {\n      delta[0] -= document.body.offsetLeft;\n      delta[1] -= document.body.offsetTop;\n    }\n\n    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';\n    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';\n    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';\n    if (options.setHeight) element.style.height = source.offsetHeight + 'px';\n    return element;\n  }\n};\n\nObject.extend(Element.Methods, {\n  getElementsBySelector: Element.Methods.select,\n\n  childElements: Element.Methods.immediateDescendants\n});\n\nElement._attributeTranslations = {\n  write: {\n    names: {\n      className: 'class',\n      htmlFor:   'for'\n    },\n    values: { }\n  }\n};\n\nif (Prototype.Browser.Opera) {\n  Element.Methods.getStyle = Element.Methods.getStyle.wrap(\n    function(proceed, element, style) {\n      switch (style) {\n        case 'left': case 'top': case 'right': case 'bottom':\n          if (proceed(element, 'position') === 'static') return null;\n        case 'height': case 'width':\n          if (!Element.visible(element)) return null;\n\n          var dim = parseInt(proceed(element, style), 10);\n\n          if (dim !== element['offset' + style.capitalize()])\n            return dim + 'px';\n\n          var properties;\n          if (style === 'height') {\n            properties = ['border-top-width', 'padding-top',\n             'padding-bottom', 'border-bottom-width'];\n          }\n          else {\n            properties = ['border-left-width', 'padding-left',\n             'padding-right', 'border-right-width'];\n          }\n          return properties.inject(dim, function(memo, property) {\n            var val = proceed(element, property);\n            return val === null ? memo : memo - parseInt(val, 10);\n          }) + 'px';\n        default: return proceed(element, style);\n      }\n    }\n  );\n\n  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(\n    function(proceed, element, attribute) {\n      if (attribute === 'title') return element.title;\n      return proceed(element, attribute);\n    }\n  );\n}\n\nelse if (Prototype.Browser.IE) {\n  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(\n    function(proceed, element) {\n      element = $(element);\n      try { element.offsetParent }\n      catch(e) { return $(document.body) }\n      var position = element.getStyle('position');\n      if (position !== 'static') return proceed(element);\n      element.setStyle({ position: 'relative' });\n      var value = proceed(element);\n      element.setStyle({ position: position });\n      return value;\n    }\n  );\n\n  $w('positionedOffset viewportOffset').each(function(method) {\n    Element.Methods[method] = Element.Methods[method].wrap(\n      function(proceed, element) {\n        element = $(element);\n        try { element.offsetParent }\n        catch(e) { return Element._returnOffset(0,0) }\n        var position = element.getStyle('position');\n        if (position !== 'static') return proceed(element);\n        var offsetParent = element.getOffsetParent();\n        if (offsetParent && offsetParent.getStyle('position') === 'fixed')\n          offsetParent.setStyle({ zoom: 1 });\n        element.setStyle({ position: 'relative' });\n        var value = proceed(element);\n        element.setStyle({ position: position });\n        return value;\n      }\n    );\n  });\n\n  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(\n    function(proceed, element) {\n      try { element.offsetParent }\n      catch(e) { return Element._returnOffset(0,0) }\n      return proceed(element);\n    }\n  );\n\n  Element.Methods.getStyle = function(element, style) {\n    element = $(element);\n    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();\n    var value = element.style[style];\n    if (!value && element.currentStyle) value = element.currentStyle[style];\n\n    if (style == 'opacity') {\n      if (value = (element.getStyle('filter') || '').match(/alpha\\(opacity=(.*)\\)/))\n        if (value[1]) return parseFloat(value[1]) / 100;\n      return 1.0;\n    }\n\n    if (value == 'auto') {\n      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))\n        return element['offset' + style.capitalize()] + 'px';\n      return null;\n    }\n    return value;\n  };\n\n  Element.Methods.setOpacity = function(element, value) {\n    function stripAlpha(filter){\n      return filter.replace(/alpha\\([^\\)]*\\)/gi,'');\n    }\n    element = $(element);\n    var currentStyle = element.currentStyle;\n    if ((currentStyle && !currentStyle.hasLayout) ||\n      (!currentStyle && element.style.zoom == 'normal'))\n        element.style.zoom = 1;\n\n    var filter = element.getStyle('filter'), style = element.style;\n    if (value == 1 || value === '') {\n      (filter = stripAlpha(filter)) ?\n        style.filter = filter : style.removeAttribute('filter');\n      return element;\n    } else if (value < 0.00001) value = 0;\n    style.filter = stripAlpha(filter) +\n      'alpha(opacity=' + (value * 100) + ')';\n    return element;\n  };\n\n  Element._attributeTranslations = (function(){\n\n    var classProp = 'className';\n    var forProp = 'for';\n\n    var el = document.createElement('div');\n\n    el.setAttribute(classProp, 'x');\n\n    if (el.className !== 'x') {\n      el.setAttribute('class', 'x');\n      if (el.className === 'x') {\n        classProp = 'class';\n      }\n    }\n    el = null;\n\n    el = document.createElement('label');\n    el.setAttribute(forProp, 'x');\n    if (el.htmlFor !== 'x') {\n      el.setAttribute('htmlFor', 'x');\n      if (el.htmlFor === 'x') {\n        forProp = 'htmlFor';\n      }\n    }\n    el = null;\n\n    return {\n      read: {\n        names: {\n          'class':      classProp,\n          'className':  classProp,\n          'for':        forProp,\n          'htmlFor':    forProp\n        },\n        values: {\n          _getAttr: function(element, attribute) {\n            return element.getAttribute(attribute);\n          },\n          _getAttr2: function(element, attribute) {\n            return element.getAttribute(attribute, 2);\n          },\n          _getAttrNode: function(element, attribute) {\n            var node = element.getAttributeNode(attribute);\n            return node ? node.value : \"\";\n          },\n          _getEv: (function(){\n\n            var el = document.createElement('div');\n            el.onclick = Prototype.emptyFunction;\n            var value = el.getAttribute('onclick');\n            var f;\n\n            if (String(value).indexOf('{') > -1) {\n              f = function(element, attribute) {\n                attribute = element.getAttribute(attribute);\n                if (!attribute) return null;\n                attribute = attribute.toString();\n                attribute = attribute.split('{')[1];\n                attribute = attribute.split('}')[0];\n                return attribute.strip();\n              };\n            }\n            else if (value === '') {\n              f = function(element, attribute) {\n                attribute = element.getAttribute(attribute);\n                if (!attribute) return null;\n                return attribute.strip();\n              };\n            }\n            el = null;\n            return f;\n          })(),\n          _flag: function(element, attribute) {\n            return $(element).hasAttribute(attribute) ? attribute : null;\n          },\n          style: function(element) {\n            return element.style.cssText.toLowerCase();\n          },\n          title: function(element) {\n            return element.title;\n          }\n        }\n      }\n    }\n  })();\n\n  Element._attributeTranslations.write = {\n    names: Object.extend({\n      cellpadding: 'cellPadding',\n      cellspacing: 'cellSpacing'\n    }, Element._attributeTranslations.read.names),\n    values: {\n      checked: function(element, value) {\n        element.checked = !!value;\n      },\n\n      style: function(element, value) {\n        element.style.cssText = value ? value : '';\n      }\n    }\n  };\n\n  Element._attributeTranslations.has = {};\n\n  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +\n      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {\n    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;\n    Element._attributeTranslations.has[attr.toLowerCase()] = attr;\n  });\n\n  (function(v) {\n    Object.extend(v, {\n      href:        v._getAttr2,\n      src:         v._getAttr2,\n      type:        v._getAttr,\n      action:      v._getAttrNode,\n      disabled:    v._flag,\n      checked:     v._flag,\n      readonly:    v._flag,\n      multiple:    v._flag,\n      onload:      v._getEv,\n      onunload:    v._getEv,\n      onclick:     v._getEv,\n      ondblclick:  v._getEv,\n      onmousedown: v._getEv,\n      onmouseup:   v._getEv,\n      onmouseover: v._getEv,\n      onmousemove: v._getEv,\n      onmouseout:  v._getEv,\n      onfocus:     v._getEv,\n      onblur:      v._getEv,\n      onkeypress:  v._getEv,\n      onkeydown:   v._getEv,\n      onkeyup:     v._getEv,\n      onsubmit:    v._getEv,\n      onreset:     v._getEv,\n      onselect:    v._getEv,\n      onchange:    v._getEv\n    });\n  })(Element._attributeTranslations.read.values);\n\n  if (Prototype.BrowserFeatures.ElementExtensions) {\n    (function() {\n      function _descendants(element) {\n        var nodes = element.getElementsByTagName('*'), results = [];\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.tagName !== \"!\") // Filter out comment nodes.\n            results.push(node);\n        return results;\n      }\n\n      Element.Methods.down = function(element, expression, index) {\n        element = $(element);\n        if (arguments.length == 1) return element.firstDescendant();\n        return Object.isNumber(expression) ? _descendants(element)[expression] :\n          Element.select(element, expression)[index || 0];\n      }\n    })();\n  }\n\n}\n\nelse if (Prototype.Browser.Gecko && /rv:1\\.8\\.0/.test(navigator.userAgent)) {\n  Element.Methods.setOpacity = function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1) ? 0.999999 :\n      (value === '') ? '' : (value < 0.00001) ? 0 : value;\n    return element;\n  };\n}\n\nelse if (Prototype.Browser.WebKit) {\n  Element.Methods.setOpacity = function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1 || value === '') ? '' :\n      (value < 0.00001) ? 0 : value;\n\n    if (value == 1)\n      if(element.tagName.toUpperCase() == 'IMG' && element.width) {\n        element.width++; element.width--;\n      } else try {\n        var n = document.createTextNode(' ');\n        element.appendChild(n);\n        element.removeChild(n);\n      } catch (e) { }\n\n    return element;\n  };\n\n  Element.Methods.cumulativeOffset = function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      if (element.offsetParent == document.body)\n        if (Element.getStyle(element, 'position') == 'absolute') break;\n\n      element = element.offsetParent;\n    } while (element);\n\n    return Element._returnOffset(valueL, valueT);\n  };\n}\n\nif ('outerHTML' in document.documentElement) {\n  Element.Methods.replace = function(element, content) {\n    element = $(element);\n\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) {\n      element.parentNode.replaceChild(content, element);\n      return element;\n    }\n\n    content = Object.toHTML(content);\n    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();\n\n    if (Element._insertionTranslations.tags[tagName]) {\n      var nextSibling = element.next();\n      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());\n      parent.removeChild(element);\n      if (nextSibling)\n        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });\n      else\n        fragments.each(function(node) { parent.appendChild(node) });\n    }\n    else element.outerHTML = content.stripScripts();\n\n    content.evalScripts.bind(content).defer();\n    return element;\n  };\n}\n\nElement._returnOffset = function(l, t) {\n  var result = [l, t];\n  result.left = l;\n  result.top = t;\n  return result;\n};\n\nElement._getContentFromAnonymousElement = function(tagName, html) {\n  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];\n  if (t) {\n    div.innerHTML = t[0] + html + t[1];\n    t[2].times(function() { div = div.firstChild });\n  } else div.innerHTML = html;\n  return $A(div.childNodes);\n};\n\nElement._insertionTranslations = {\n  before: function(element, node) {\n    element.parentNode.insertBefore(node, element);\n  },\n  top: function(element, node) {\n    element.insertBefore(node, element.firstChild);\n  },\n  bottom: function(element, node) {\n    element.appendChild(node);\n  },\n  after: function(element, node) {\n    element.parentNode.insertBefore(node, element.nextSibling);\n  },\n  tags: {\n    TABLE:  ['<table>',                '</table>',                   1],\n    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],\n    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],\n    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],\n    SELECT: ['<select>',               '</select>',                  1]\n  }\n};\n\n(function() {\n  var tags = Element._insertionTranslations.tags;\n  Object.extend(tags, {\n    THEAD: tags.TBODY,\n    TFOOT: tags.TBODY,\n    TH:    tags.TD\n  });\n})();\n\nElement.Methods.Simulated = {\n  hasAttribute: function(element, attribute) {\n    attribute = Element._attributeTranslations.has[attribute] || attribute;\n    var node = $(element).getAttributeNode(attribute);\n    return !!(node && node.specified);\n  }\n};\n\nElement.Methods.ByTag = { };\n\nObject.extend(Element, Element.Methods);\n\n(function(div) {\n\n  if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {\n    window.HTMLElement = { };\n    window.HTMLElement.prototype = div['__proto__'];\n    Prototype.BrowserFeatures.ElementExtensions = true;\n  }\n\n  div = null;\n\n})(document.createElement('div'))\n\nElement.extend = (function() {\n\n  function checkDeficiency(tagName) {\n    if (typeof window.Element != 'undefined') {\n      var proto = window.Element.prototype;\n      if (proto) {\n        var id = '_' + (Math.random()+'').slice(2);\n        var el = document.createElement(tagName);\n        proto[id] = 'x';\n        var isBuggy = (el[id] !== 'x');\n        delete proto[id];\n        el = null;\n        return isBuggy;\n      }\n    }\n    return false;\n  }\n\n  function extendElementWith(element, methods) {\n    for (var property in methods) {\n      var value = methods[property];\n      if (Object.isFunction(value) && !(property in element))\n        element[property] = value.methodize();\n    }\n  }\n\n  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');\n\n  if (Prototype.BrowserFeatures.SpecificElementExtensions) {\n    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {\n      return function(element) {\n        if (element && typeof element._extendedByPrototype == 'undefined') {\n          var t = element.tagName;\n          if (t && (/^(?:object|applet|embed)$/i.test(t))) {\n            extendElementWith(element, Element.Methods);\n            extendElementWith(element, Element.Methods.Simulated);\n            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);\n          }\n        }\n        return element;\n      }\n    }\n    return Prototype.K;\n  }\n\n  var Methods = { }, ByTag = Element.Methods.ByTag;\n\n  var extend = Object.extend(function(element) {\n    if (!element || typeof element._extendedByPrototype != 'undefined' ||\n        element.nodeType != 1 || element == window) return element;\n\n    var methods = Object.clone(Methods),\n        tagName = element.tagName.toUpperCase();\n\n    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);\n\n    extendElementWith(element, methods);\n\n    element._extendedByPrototype = Prototype.emptyFunction;\n    return element;\n\n  }, {\n    refresh: function() {\n      if (!Prototype.BrowserFeatures.ElementExtensions) {\n        Object.extend(Methods, Element.Methods);\n        Object.extend(Methods, Element.Methods.Simulated);\n      }\n    }\n  });\n\n  extend.refresh();\n  return extend;\n})();\n\nElement.hasAttribute = function(element, attribute) {\n  if (element.hasAttribute) return element.hasAttribute(attribute);\n  return Element.Methods.Simulated.hasAttribute(element, attribute);\n};\n\nElement.addMethods = function(methods) {\n  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;\n\n  if (!methods) {\n    Object.extend(Form, Form.Methods);\n    Object.extend(Form.Element, Form.Element.Methods);\n    Object.extend(Element.Methods.ByTag, {\n      \"FORM\":     Object.clone(Form.Methods),\n      \"INPUT\":    Object.clone(Form.Element.Methods),\n      \"SELECT\":   Object.clone(Form.Element.Methods),\n      \"TEXTAREA\": Object.clone(Form.Element.Methods)\n    });\n  }\n\n  if (arguments.length == 2) {\n    var tagName = methods;\n    methods = arguments[1];\n  }\n\n  if (!tagName) Object.extend(Element.Methods, methods || { });\n  else {\n    if (Object.isArray(tagName)) tagName.each(extend);\n    else extend(tagName);\n  }\n\n  function extend(tagName) {\n    tagName = tagName.toUpperCase();\n    if (!Element.Methods.ByTag[tagName])\n      Element.Methods.ByTag[tagName] = { };\n    Object.extend(Element.Methods.ByTag[tagName], methods);\n  }\n\n  function copy(methods, destination, onlyIfAbsent) {\n    onlyIfAbsent = onlyIfAbsent || false;\n    for (var property in methods) {\n      var value = methods[property];\n      if (!Object.isFunction(value)) continue;\n      if (!onlyIfAbsent || !(property in destination))\n        destination[property] = value.methodize();\n    }\n  }\n\n  function findDOMClass(tagName) {\n    var klass;\n    var trans = {\n      \"OPTGROUP\": \"OptGroup\", \"TEXTAREA\": \"TextArea\", \"P\": \"Paragraph\",\n      \"FIELDSET\": \"FieldSet\", \"UL\": \"UList\", \"OL\": \"OList\", \"DL\": \"DList\",\n      \"DIR\": \"Directory\", \"H1\": \"Heading\", \"H2\": \"Heading\", \"H3\": \"Heading\",\n      \"H4\": \"Heading\", \"H5\": \"Heading\", \"H6\": \"Heading\", \"Q\": \"Quote\",\n      \"INS\": \"Mod\", \"DEL\": \"Mod\", \"A\": \"Anchor\", \"IMG\": \"Image\", \"CAPTION\":\n      \"TableCaption\", \"COL\": \"TableCol\", \"COLGROUP\": \"TableCol\", \"THEAD\":\n      \"TableSection\", \"TFOOT\": \"TableSection\", \"TBODY\": \"TableSection\", \"TR\":\n      \"TableRow\", \"TH\": \"TableCell\", \"TD\": \"TableCell\", \"FRAMESET\":\n      \"FrameSet\", \"IFRAME\": \"IFrame\"\n    };\n    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';\n    if (window[klass]) return window[klass];\n    klass = 'HTML' + tagName + 'Element';\n    if (window[klass]) return window[klass];\n    klass = 'HTML' + tagName.capitalize() + 'Element';\n    if (window[klass]) return window[klass];\n\n    var element = document.createElement(tagName);\n    var proto = element['__proto__'] || element.constructor.prototype;\n    element = null;\n    return proto;\n  }\n\n  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :\n   Element.prototype;\n\n  if (F.ElementExtensions) {\n    copy(Element.Methods, elementPrototype);\n    copy(Element.Methods.Simulated, elementPrototype, true);\n  }\n\n  if (F.SpecificElementExtensions) {\n    for (var tag in Element.Methods.ByTag) {\n      var klass = findDOMClass(tag);\n      if (Object.isUndefined(klass)) continue;\n      copy(T[tag], klass.prototype);\n    }\n  }\n\n  Object.extend(Element, Element.Methods);\n  delete Element.ByTag;\n\n  if (Element.extend.refresh) Element.extend.refresh();\n  Element.cache = { };\n};\n\n\ndocument.viewport = {\n\n  getDimensions: function() {\n    return { width: this.getWidth(), height: this.getHeight() };\n  },\n\n  getScrollOffsets: function() {\n    return Element._returnOffset(\n      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,\n      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);\n  }\n};\n\n(function(viewport) {\n  var B = Prototype.Browser, doc = document, element, property = {};\n\n  function getRootElement() {\n    if (B.WebKit && !doc.evaluate)\n      return document;\n\n    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)\n      return document.body;\n\n    return document.documentElement;\n  }\n\n  function define(D) {\n    if (!element) element = getRootElement();\n\n    property[D] = 'client' + D;\n\n    viewport['get' + D] = function() { return element[property[D]] };\n    return viewport['get' + D]();\n  }\n\n  viewport.getWidth  = define.curry('Width');\n\n  viewport.getHeight = define.curry('Height');\n})(document.viewport);\n\n\nElement.Storage = {\n  UID: 1\n};\n\nElement.addMethods({\n  getStorage: function(element) {\n    if (!(element = $(element))) return;\n\n    var uid;\n    if (element === window) {\n      uid = 0;\n    } else {\n      if (typeof element._prototypeUID === \"undefined\")\n        element._prototypeUID = [Element.Storage.UID++];\n      uid = element._prototypeUID[0];\n    }\n\n    if (!Element.Storage[uid])\n      Element.Storage[uid] = $H();\n\n    return Element.Storage[uid];\n  },\n\n  store: function(element, key, value) {\n    if (!(element = $(element))) return;\n\n    if (arguments.length === 2) {\n      Element.getStorage(element).update(key);\n    } else {\n      Element.getStorage(element).set(key, value);\n    }\n\n    return element;\n  },\n\n  retrieve: function(element, key, defaultValue) {\n    if (!(element = $(element))) return;\n    var hash = Element.getStorage(element), value = hash.get(key);\n\n    if (Object.isUndefined(value)) {\n      hash.set(key, defaultValue);\n      value = defaultValue;\n    }\n\n    return value;\n  },\n\n  clone: function(element, deep) {\n    if (!(element = $(element))) return;\n    var clone = element.cloneNode(deep);\n    clone._prototypeUID = void 0;\n    if (deep) {\n      var descendants = Element.select(clone, '*'),\n          i = descendants.length;\n      while (i--) {\n        descendants[i]._prototypeUID = void 0;\n      }\n    }\n    return Element.extend(clone);\n  }\n});\n/* Portions of the Selector class are derived from Jack Slocum's DomQuery,\n * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style\n * license.  Please see http://www.yui-ext.com/ for more information. */\n\nvar Selector = Class.create({\n  initialize: function(expression) {\n    this.expression = expression.strip();\n\n    if (this.shouldUseSelectorsAPI()) {\n      this.mode = 'selectorsAPI';\n    } else if (this.shouldUseXPath()) {\n      this.mode = 'xpath';\n      this.compileXPathMatcher();\n    } else {\n      this.mode = \"normal\";\n      this.compileMatcher();\n    }\n\n  },\n\n  shouldUseXPath: (function() {\n\n    var IS_DESCENDANT_SELECTOR_BUGGY = (function(){\n      var isBuggy = false;\n      if (document.evaluate && window.XPathResult) {\n        var el = document.createElement('div');\n        el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';\n\n        var xpath = \".//*[local-name()='ul' or local-name()='UL']\" +\n          \"//*[local-name()='li' or local-name()='LI']\";\n\n        var result = document.evaluate(xpath, el, null,\n          XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n\n        isBuggy = (result.snapshotLength !== 2);\n        el = null;\n      }\n      return isBuggy;\n    })();\n\n    return function() {\n      if (!Prototype.BrowserFeatures.XPath) return false;\n\n      var e = this.expression;\n\n      if (Prototype.Browser.WebKit &&\n       (e.include(\"-of-type\") || e.include(\":empty\")))\n        return false;\n\n      if ((/(\\[[\\w-]*?:|:checked)/).test(e))\n        return false;\n\n      if (IS_DESCENDANT_SELECTOR_BUGGY) return false;\n\n      return true;\n    }\n\n  })(),\n\n  shouldUseSelectorsAPI: function() {\n    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;\n\n    if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;\n\n    if (!Selector._div) Selector._div = new Element('div');\n\n    try {\n      Selector._div.querySelector(this.expression);\n    } catch(e) {\n      return false;\n    }\n\n    return true;\n  },\n\n  compileMatcher: function() {\n    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,\n        c = Selector.criteria, le, p, m, len = ps.length, name;\n\n    if (Selector._cache[e]) {\n      this.matcher = Selector._cache[e];\n      return;\n    }\n\n    this.matcher = [\"this.matcher = function(root) {\",\n                    \"var r = root, h = Selector.handlers, c = false, n;\"];\n\n    while (e && le != e && (/\\S/).test(e)) {\n      le = e;\n      for (var i = 0; i<len; i++) {\n        p = ps[i].re;\n        name = ps[i].name;\n        if (m = e.match(p)) {\n          this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :\n            new Template(c[name]).evaluate(m));\n          e = e.replace(m[0], '');\n          break;\n        }\n      }\n    }\n\n    this.matcher.push(\"return h.unique(n);\\n}\");\n    eval(this.matcher.join('\\n'));\n    Selector._cache[this.expression] = this.matcher;\n  },\n\n  compileXPathMatcher: function() {\n    var e = this.expression, ps = Selector.patterns,\n        x = Selector.xpath, le, m, len = ps.length, name;\n\n    if (Selector._cache[e]) {\n      this.xpath = Selector._cache[e]; return;\n    }\n\n    this.matcher = ['.//*'];\n    while (e && le != e && (/\\S/).test(e)) {\n      le = e;\n      for (var i = 0; i<len; i++) {\n        name = ps[i].name;\n        if (m = e.match(ps[i].re)) {\n          this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :\n            new Template(x[name]).evaluate(m));\n          e = e.replace(m[0], '');\n          break;\n        }\n      }\n    }\n\n    this.xpath = this.matcher.join('');\n    Selector._cache[this.expression] = this.xpath;\n  },\n\n  findElements: function(root) {\n    root = root || document;\n    var e = this.expression, results;\n\n    switch (this.mode) {\n      case 'selectorsAPI':\n        if (root !== document) {\n          var oldId = root.id, id = $(root).identify();\n          id = id.replace(/([\\.:])/g, \"\\\\$1\");\n          e = \"#\" + id + \" \" + e;\n        }\n\n        results = $A(root.querySelectorAll(e)).map(Element.extend);\n        root.id = oldId;\n\n        return results;\n      case 'xpath':\n        return document._getElementsByXPath(this.xpath, root);\n      default:\n       return this.matcher(root);\n    }\n  },\n\n  match: function(element) {\n    this.tokens = [];\n\n    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;\n    var le, p, m, len = ps.length, name;\n\n    while (e && le !== e && (/\\S/).test(e)) {\n      le = e;\n      for (var i = 0; i<len; i++) {\n        p = ps[i].re;\n        name = ps[i].name;\n        if (m = e.match(p)) {\n          if (as[name]) {\n            this.tokens.push([name, Object.clone(m)]);\n            e = e.replace(m[0], '');\n          } else {\n            return this.findElements(document).include(element);\n          }\n        }\n      }\n    }\n\n    var match = true, name, matches;\n    for (var i = 0, token; token = this.tokens[i]; i++) {\n      name = token[0], matches = token[1];\n      if (!Selector.assertions[name](element, matches)) {\n        match = false; break;\n      }\n    }\n\n    return match;\n  },\n\n  toString: function() {\n    return this.expression;\n  },\n\n  inspect: function() {\n    return \"#<Selector:\" + this.expression.inspect() + \">\";\n  }\n});\n\nif (Prototype.BrowserFeatures.SelectorsAPI &&\n document.compatMode === 'BackCompat') {\n  Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){\n    var div = document.createElement('div'),\n     span = document.createElement('span');\n\n    div.id = \"prototype_test_id\";\n    span.className = 'Test';\n    div.appendChild(span);\n    var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);\n    div = span = null;\n    return isIgnored;\n  })();\n}\n\nObject.extend(Selector, {\n  _cache: { },\n\n  xpath: {\n    descendant:   \"//*\",\n    child:        \"/*\",\n    adjacent:     \"/following-sibling::*[1]\",\n    laterSibling: '/following-sibling::*',\n    tagName:      function(m) {\n      if (m[1] == '*') return '';\n      return \"[local-name()='\" + m[1].toLowerCase() +\n             \"' or local-name()='\" + m[1].toUpperCase() + \"']\";\n    },\n    className:    \"[contains(concat(' ', @class, ' '), ' #{1} ')]\",\n    id:           \"[@id='#{1}']\",\n    attrPresence: function(m) {\n      m[1] = m[1].toLowerCase();\n      return new Template(\"[@#{1}]\").evaluate(m);\n    },\n    attr: function(m) {\n      m[1] = m[1].toLowerCase();\n      m[3] = m[5] || m[6];\n      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);\n    },\n    pseudo: function(m) {\n      var h = Selector.xpath.pseudos[m[1]];\n      if (!h) return '';\n      if (Object.isFunction(h)) return h(m);\n      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);\n    },\n    operators: {\n      '=':  \"[@#{1}='#{3}']\",\n      '!=': \"[@#{1}!='#{3}']\",\n      '^=': \"[starts-with(@#{1}, '#{3}')]\",\n      '$=': \"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']\",\n      '*=': \"[contains(@#{1}, '#{3}')]\",\n      '~=': \"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]\",\n      '|=': \"[contains(concat('-', @#{1}, '-'), '-#{3}-')]\"\n    },\n    pseudos: {\n      'first-child': '[not(preceding-sibling::*)]',\n      'last-child':  '[not(following-sibling::*)]',\n      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',\n      'empty':       \"[count(*) = 0 and (count(text()) = 0)]\",\n      'checked':     \"[@checked]\",\n      'disabled':    \"[(@disabled) and (@type!='hidden')]\",\n      'enabled':     \"[not(@disabled) and (@type!='hidden')]\",\n      'not': function(m) {\n        var e = m[6], p = Selector.patterns,\n            x = Selector.xpath, le, v, len = p.length, name;\n\n        var exclusion = [];\n        while (e && le != e && (/\\S/).test(e)) {\n          le = e;\n          for (var i = 0; i<len; i++) {\n            name = p[i].name\n            if (m = e.match(p[i].re)) {\n              v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);\n              exclusion.push(\"(\" + v.substring(1, v.length - 1) + \")\");\n              e = e.replace(m[0], '');\n              break;\n            }\n          }\n        }\n        return \"[not(\" + exclusion.join(\" and \") + \")]\";\n      },\n      'nth-child':      function(m) {\n        return Selector.xpath.pseudos.nth(\"(count(./preceding-sibling::*) + 1) \", m);\n      },\n      'nth-last-child': function(m) {\n        return Selector.xpath.pseudos.nth(\"(count(./following-sibling::*) + 1) \", m);\n      },\n      'nth-of-type':    function(m) {\n        return Selector.xpath.pseudos.nth(\"position() \", m);\n      },\n      'nth-last-of-type': function(m) {\n        return Selector.xpath.pseudos.nth(\"(last() + 1 - position()) \", m);\n      },\n      'first-of-type':  function(m) {\n        m[6] = \"1\"; return Selector.xpath.pseudos['nth-of-type'](m);\n      },\n      'last-of-type':   function(m) {\n        m[6] = \"1\"; return Selector.xpath.pseudos['nth-last-of-type'](m);\n      },\n      'only-of-type':   function(m) {\n        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);\n      },\n      nth: function(fragment, m) {\n        var mm, formula = m[6], predicate;\n        if (formula == 'even') formula = '2n+0';\n        if (formula == 'odd')  formula = '2n+1';\n        if (mm = formula.match(/^(\\d+)$/)) // digit only\n          return '[' + fragment + \"= \" + mm[1] + ']';\n        if (mm = formula.match(/^(-?\\d*)?n(([+-])(\\d+))?/)) { // an+b\n          if (mm[1] == \"-\") mm[1] = -1;\n          var a = mm[1] ? Number(mm[1]) : 1;\n          var b = mm[2] ? Number(mm[2]) : 0;\n          predicate = \"[((#{fragment} - #{b}) mod #{a} = 0) and \" +\n          \"((#{fragment} - #{b}) div #{a} >= 0)]\";\n          return new Template(predicate).evaluate({\n            fragment: fragment, a: a, b: b });\n        }\n      }\n    }\n  },\n\n  criteria: {\n    tagName:      'n = h.tagName(n, r, \"#{1}\", c);      c = false;',\n    className:    'n = h.className(n, r, \"#{1}\", c);    c = false;',\n    id:           'n = h.id(n, r, \"#{1}\", c);           c = false;',\n    attrPresence: 'n = h.attrPresence(n, r, \"#{1}\", c); c = false;',\n    attr: function(m) {\n      m[3] = (m[5] || m[6]);\n      return new Template('n = h.attr(n, r, \"#{1}\", \"#{3}\", \"#{2}\", c); c = false;').evaluate(m);\n    },\n    pseudo: function(m) {\n      if (m[6]) m[6] = m[6].replace(/\"/g, '\\\\\"');\n      return new Template('n = h.pseudo(n, \"#{1}\", \"#{6}\", r, c); c = false;').evaluate(m);\n    },\n    descendant:   'c = \"descendant\";',\n    child:        'c = \"child\";',\n    adjacent:     'c = \"adjacent\";',\n    laterSibling: 'c = \"laterSibling\";'\n  },\n\n  patterns: [\n    { name: 'laterSibling', re: /^\\s*~\\s*/ },\n    { name: 'child',        re: /^\\s*>\\s*/ },\n    { name: 'adjacent',     re: /^\\s*\\+\\s*/ },\n    { name: 'descendant',   re: /^\\s/ },\n\n    { name: 'tagName',      re: /^\\s*(\\*|[\\w\\-]+)(\\b|$)?/ },\n    { name: 'id',           re: /^#([\\w\\-\\*]+)(\\b|$)/ },\n    { name: 'className',    re: /^\\.([\\w\\-\\*]+)(\\b|$)/ },\n    { name: 'pseudo',       re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\\((.*?)\\))?(\\b|$|(?=\\s|[:+~>]))/ },\n    { name: 'attrPresence', re: /^\\[((?:[\\w-]+:)?[\\w-]+)\\]/ },\n    { name: 'attr',         re: /\\[((?:[\\w-]*:)?[\\w-]+)\\s*(?:([!^$*~|]?=)\\s*((['\"])([^\\4]*?)\\4|([^'\"][^\\]]*?)))?\\]/ }\n  ],\n\n  assertions: {\n    tagName: function(element, matches) {\n      return matches[1].toUpperCase() == element.tagName.toUpperCase();\n    },\n\n    className: function(element, matches) {\n      return Element.hasClassName(element, matches[1]);\n    },\n\n    id: function(element, matches) {\n      return element.id === matches[1];\n    },\n\n    attrPresence: function(element, matches) {\n      return Element.hasAttribute(element, matches[1]);\n    },\n\n    attr: function(element, matches) {\n      var nodeValue = Element.readAttribute(element, matches[1]);\n      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);\n    }\n  },\n\n  handlers: {\n    concat: function(a, b) {\n      for (var i = 0, node; node = b[i]; i++)\n        a.push(node);\n      return a;\n    },\n\n    mark: function(nodes) {\n      var _true = Prototype.emptyFunction;\n      for (var i = 0, node; node = nodes[i]; i++)\n        node._countedByPrototype = _true;\n      return nodes;\n    },\n\n    unmark: (function(){\n\n      var PROPERTIES_ATTRIBUTES_MAP = (function(){\n        var el = document.createElement('div'),\n            isBuggy = false,\n            propName = '_countedByPrototype',\n            value = 'x'\n        el[propName] = value;\n        isBuggy = (el.getAttribute(propName) === value);\n        el = null;\n        return isBuggy;\n      })();\n\n      return PROPERTIES_ATTRIBUTES_MAP ?\n        function(nodes) {\n          for (var i = 0, node; node = nodes[i]; i++)\n            node.removeAttribute('_countedByPrototype');\n          return nodes;\n        } :\n        function(nodes) {\n          for (var i = 0, node; node = nodes[i]; i++)\n            node._countedByPrototype = void 0;\n          return nodes;\n        }\n    })(),\n\n    index: function(parentNode, reverse, ofType) {\n      parentNode._countedByPrototype = Prototype.emptyFunction;\n      if (reverse) {\n        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {\n          var node = nodes[i];\n          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;\n        }\n      } else {\n        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)\n          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;\n      }\n    },\n\n    unique: function(nodes) {\n      if (nodes.length == 0) return nodes;\n      var results = [], n;\n      for (var i = 0, l = nodes.length; i < l; i++)\n        if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {\n          n._countedByPrototype = Prototype.emptyFunction;\n          results.push(Element.extend(n));\n        }\n      return Selector.handlers.unmark(results);\n    },\n\n    descendant: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        h.concat(results, node.getElementsByTagName('*'));\n      return results;\n    },\n\n    child: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        for (var j = 0, child; child = node.childNodes[j]; j++)\n          if (child.nodeType == 1 && child.tagName != '!') results.push(child);\n      }\n      return results;\n    },\n\n    adjacent: function(nodes) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        var next = this.nextElementSibling(node);\n        if (next) results.push(next);\n      }\n      return results;\n    },\n\n    laterSibling: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        h.concat(results, Element.nextSiblings(node));\n      return results;\n    },\n\n    nextElementSibling: function(node) {\n      while (node = node.nextSibling)\n        if (node.nodeType == 1) return node;\n      return null;\n    },\n\n    previousElementSibling: function(node) {\n      while (node = node.previousSibling)\n        if (node.nodeType == 1) return node;\n      return null;\n    },\n\n    tagName: function(nodes, root, tagName, combinator) {\n      var uTagName = tagName.toUpperCase();\n      var results = [], h = Selector.handlers;\n      if (nodes) {\n        if (combinator) {\n          if (combinator == \"descendant\") {\n            for (var i = 0, node; node = nodes[i]; i++)\n              h.concat(results, node.getElementsByTagName(tagName));\n            return results;\n          } else nodes = this[combinator](nodes);\n          if (tagName == \"*\") return nodes;\n        }\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.tagName.toUpperCase() === uTagName) results.push(node);\n        return results;\n      } else return root.getElementsByTagName(tagName);\n    },\n\n    id: function(nodes, root, id, combinator) {\n      var targetNode = $(id), h = Selector.handlers;\n\n      if (root == document) {\n        if (!targetNode) return [];\n        if (!nodes) return [targetNode];\n      } else {\n        if (!root.sourceIndex || root.sourceIndex < 1) {\n          var nodes = root.getElementsByTagName('*');\n          for (var j = 0, node; node = nodes[j]; j++) {\n            if (node.id === id) return [node];\n          }\n        }\n      }\n\n      if (nodes) {\n        if (combinator) {\n          if (combinator == 'child') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (targetNode.parentNode == node) return [targetNode];\n          } else if (combinator == 'descendant') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (Element.descendantOf(targetNode, node)) return [targetNode];\n          } else if (combinator == 'adjacent') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (Selector.handlers.previousElementSibling(targetNode) == node)\n                return [targetNode];\n          } else nodes = h[combinator](nodes);\n        }\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node == targetNode) return [targetNode];\n        return [];\n      }\n      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];\n    },\n\n    className: function(nodes, root, className, combinator) {\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      return Selector.handlers.byClassName(nodes, root, className);\n    },\n\n    byClassName: function(nodes, root, className) {\n      if (!nodes) nodes = Selector.handlers.descendant([root]);\n      var needle = ' ' + className + ' ';\n      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {\n        nodeClassName = node.className;\n        if (nodeClassName.length == 0) continue;\n        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))\n          results.push(node);\n      }\n      return results;\n    },\n\n    attrPresence: function(nodes, root, attr, combinator) {\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      var results = [];\n      for (var i = 0, node; node = nodes[i]; i++)\n        if (Element.hasAttribute(node, attr)) results.push(node);\n      return results;\n    },\n\n    attr: function(nodes, root, attr, value, operator, combinator) {\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      var handler = Selector.operators[operator], results = [];\n      for (var i = 0, node; node = nodes[i]; i++) {\n        var nodeValue = Element.readAttribute(node, attr);\n        if (nodeValue === null) continue;\n        if (handler(nodeValue, value)) results.push(node);\n      }\n      return results;\n    },\n\n    pseudo: function(nodes, name, value, root, combinator) {\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      return Selector.pseudos[name](nodes, value, root);\n    }\n  },\n\n  pseudos: {\n    'first-child': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (Selector.handlers.previousElementSibling(node)) continue;\n          results.push(node);\n      }\n      return results;\n    },\n    'last-child': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (Selector.handlers.nextElementSibling(node)) continue;\n          results.push(node);\n      }\n      return results;\n    },\n    'only-child': function(nodes, value, root) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))\n          results.push(node);\n      return results;\n    },\n    'nth-child':        function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root);\n    },\n    'nth-last-child':   function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, true);\n    },\n    'nth-of-type':      function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, false, true);\n    },\n    'nth-last-of-type': function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, true, true);\n    },\n    'first-of-type':    function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, \"1\", root, false, true);\n    },\n    'last-of-type':     function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, \"1\", root, true, true);\n    },\n    'only-of-type':     function(nodes, formula, root) {\n      var p = Selector.pseudos;\n      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);\n    },\n\n    getIndices: function(a, b, total) {\n      if (a == 0) return b > 0 ? [b] : [];\n      return $R(1, total).inject([], function(memo, i) {\n        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);\n        return memo;\n      });\n    },\n\n    nth: function(nodes, formula, root, reverse, ofType) {\n      if (nodes.length == 0) return [];\n      if (formula == 'even') formula = '2n+0';\n      if (formula == 'odd')  formula = '2n+1';\n      var h = Selector.handlers, results = [], indexed = [], m;\n      h.mark(nodes);\n      for (var i = 0, node; node = nodes[i]; i++) {\n        if (!node.parentNode._countedByPrototype) {\n          h.index(node.parentNode, reverse, ofType);\n          indexed.push(node.parentNode);\n        }\n      }\n      if (formula.match(/^\\d+$/)) { // just a number\n        formula = Number(formula);\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.nodeIndex == formula) results.push(node);\n      } else if (m = formula.match(/^(-?\\d*)?n(([+-])(\\d+))?/)) { // an+b\n        if (m[1] == \"-\") m[1] = -1;\n        var a = m[1] ? Number(m[1]) : 1;\n        var b = m[2] ? Number(m[2]) : 0;\n        var indices = Selector.pseudos.getIndices(a, b, nodes.length);\n        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {\n          for (var j = 0; j < l; j++)\n            if (node.nodeIndex == indices[j]) results.push(node);\n        }\n      }\n      h.unmark(nodes);\n      h.unmark(indexed);\n      return results;\n    },\n\n    'empty': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (node.tagName == '!' || node.firstChild) continue;\n        results.push(node);\n      }\n      return results;\n    },\n\n    'not': function(nodes, selector, root) {\n      var h = Selector.handlers, selectorType, m;\n      var exclusions = new Selector(selector).findElements(root);\n      h.mark(exclusions);\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!node._countedByPrototype) results.push(node);\n      h.unmark(exclusions);\n      return results;\n    },\n\n    'enabled': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!node.disabled && (!node.type || node.type !== 'hidden'))\n          results.push(node);\n      return results;\n    },\n\n    'disabled': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (node.disabled) results.push(node);\n      return results;\n    },\n\n    'checked': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (node.checked) results.push(node);\n      return results;\n    }\n  },\n\n  operators: {\n    '=':  function(nv, v) { return nv == v; },\n    '!=': function(nv, v) { return nv != v; },\n    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },\n    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },\n    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },\n    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },\n    '|=': function(nv, v) { return ('-' + (nv || \"\").toUpperCase() +\n     '-').include('-' + (v || \"\").toUpperCase() + '-'); }\n  },\n\n  split: function(expression) {\n    var expressions = [];\n    expression.scan(/(([\\w#:.~>+()\\s-]+|\\*|\\[.*?\\])+)\\s*(,|$)/, function(m) {\n      expressions.push(m[1].strip());\n    });\n    return expressions;\n  },\n\n  matchElements: function(elements, expression) {\n    var matches = $$(expression), h = Selector.handlers;\n    h.mark(matches);\n    for (var i = 0, results = [], element; element = elements[i]; i++)\n      if (element._countedByPrototype) results.push(element);\n    h.unmark(matches);\n    return results;\n  },\n\n  findElement: function(elements, expression, index) {\n    if (Object.isNumber(expression)) {\n      index = expression; expression = false;\n    }\n    return Selector.matchElements(elements, expression || '*')[index || 0];\n  },\n\n  findChildElements: function(element, expressions) {\n    expressions = Selector.split(expressions.join(','));\n    var results = [], h = Selector.handlers;\n    for (var i = 0, l = expressions.length, selector; i < l; i++) {\n      selector = new Selector(expressions[i].strip());\n      h.concat(results, selector.findElements(element));\n    }\n    return (l > 1) ? h.unique(results) : results;\n  }\n});\n\nif (Prototype.Browser.IE) {\n  Object.extend(Selector.handlers, {\n    concat: function(a, b) {\n      for (var i = 0, node; node = b[i]; i++)\n        if (node.tagName !== \"!\") a.push(node);\n      return a;\n    }\n  });\n}\n\nfunction $$() {\n  return Selector.findChildElements(document, $A(arguments));\n}\n\nvar Form = {\n  reset: function(form) {\n    form = $(form);\n    form.reset();\n    return form;\n  },\n\n  serializeElements: function(elements, options) {\n    if (typeof options != 'object') options = { hash: !!options };\n    else if (Object.isUndefined(options.hash)) options.hash = true;\n    var key, value, submitted = false, submit = options.submit;\n\n    var data = elements.inject({ }, function(result, element) {\n      if (!element.disabled && element.name) {\n        key = element.name; value = $(element).getValue();\n        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&\n            submit !== false && (!submit || key == submit) && (submitted = true)))) {\n          if (key in result) {\n            if (!Object.isArray(result[key])) result[key] = [result[key]];\n            result[key].push(value);\n          }\n          else result[key] = value;\n        }\n      }\n      return result;\n    });\n\n    return options.hash ? data : Object.toQueryString(data);\n  }\n};\n\nForm.Methods = {\n  serialize: function(form, options) {\n    return Form.serializeElements(Form.getElements(form), options);\n  },\n\n  getElements: function(form) {\n    var elements = $(form).getElementsByTagName('*'),\n        element,\n        arr = [ ],\n        serializers = Form.Element.Serializers;\n    for (var i = 0; element = elements[i]; i++) {\n      arr.push(element);\n    }\n    return arr.inject([], function(elements, child) {\n      if (serializers[child.tagName.toLowerCase()])\n        elements.push(Element.extend(child));\n      return elements;\n    })\n  },\n\n  getInputs: function(form, typeName, name) {\n    form = $(form);\n    var inputs = form.getElementsByTagName('input');\n\n    if (!typeName && !name) return $A(inputs).map(Element.extend);\n\n    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {\n      var input = inputs[i];\n      if ((typeName && input.type != typeName) || (name && input.name != name))\n        continue;\n      matchingInputs.push(Element.extend(input));\n    }\n\n    return matchingInputs;\n  },\n\n  disable: function(form) {\n    form = $(form);\n    Form.getElements(form).invoke('disable');\n    return form;\n  },\n\n  enable: function(form) {\n    form = $(form);\n    Form.getElements(form).invoke('enable');\n    return form;\n  },\n\n  findFirstElement: function(form) {\n    var elements = $(form).getElements().findAll(function(element) {\n      return 'hidden' != element.type && !element.disabled;\n    });\n    var firstByIndex = elements.findAll(function(element) {\n      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;\n    }).sortBy(function(element) { return element.tabIndex }).first();\n\n    return firstByIndex ? firstByIndex : elements.find(function(element) {\n      return /^(?:input|select|textarea)$/i.test(element.tagName);\n    });\n  },\n\n  focusFirstElement: function(form) {\n    form = $(form);\n    form.findFirstElement().activate();\n    return form;\n  },\n\n  request: function(form, options) {\n    form = $(form), options = Object.clone(options || { });\n\n    var params = options.parameters, action = form.readAttribute('action') || '';\n    if (action.blank()) action = window.location.href;\n    options.parameters = form.serialize(true);\n\n    if (params) {\n      if (Object.isString(params)) params = params.toQueryParams();\n      Object.extend(options.parameters, params);\n    }\n\n    if (form.hasAttribute('method') && !options.method)\n      options.method = form.method;\n\n    return new Ajax.Request(action, options);\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\n\nForm.Element = {\n  focus: function(element) {\n    $(element).focus();\n    return element;\n  },\n\n  select: function(element) {\n    $(element).select();\n    return element;\n  }\n};\n\nForm.Element.Methods = {\n\n  serialize: function(element) {\n    element = $(element);\n    if (!element.disabled && element.name) {\n      var value = element.getValue();\n      if (value != undefined) {\n        var pair = { };\n        pair[element.name] = value;\n        return Object.toQueryString(pair);\n      }\n    }\n    return '';\n  },\n\n  getValue: function(element) {\n    element = $(element);\n    var method = element.tagName.toLowerCase();\n    return Form.Element.Serializers[method](element);\n  },\n\n  setValue: function(element, value) {\n    element = $(element);\n    var method = element.tagName.toLowerCase();\n    Form.Element.Serializers[method](element, value);\n    return element;\n  },\n\n  clear: function(element) {\n    $(element).value = '';\n    return element;\n  },\n\n  present: function(element) {\n    return $(element).value != '';\n  },\n\n  activate: function(element) {\n    element = $(element);\n    try {\n      element.focus();\n      if (element.select && (element.tagName.toLowerCase() != 'input' ||\n          !(/^(?:button|reset|submit)$/i.test(element.type))))\n        element.select();\n    } catch (e) { }\n    return element;\n  },\n\n  disable: function(element) {\n    element = $(element);\n    element.disabled = true;\n    return element;\n  },\n\n  enable: function(element) {\n    element = $(element);\n    element.disabled = false;\n    return element;\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Field = Form.Element;\n\nvar $F = Form.Element.Methods.getValue;\n\n/*--------------------------------------------------------------------------*/\n\nForm.Element.Serializers = {\n  input: function(element, value) {\n    switch (element.type.toLowerCase()) {\n      case 'checkbox':\n      case 'radio':\n        return Form.Element.Serializers.inputSelector(element, value);\n      default:\n        return Form.Element.Serializers.textarea(element, value);\n    }\n  },\n\n  inputSelector: function(element, value) {\n    if (Object.isUndefined(value)) return element.checked ? element.value : null;\n    else element.checked = !!value;\n  },\n\n  textarea: function(element, value) {\n    if (Object.isUndefined(value)) return element.value;\n    else element.value = value;\n  },\n\n  select: function(element, value) {\n    if (Object.isUndefined(value))\n      return this[element.type == 'select-one' ?\n        'selectOne' : 'selectMany'](element);\n    else {\n      var opt, currentValue, single = !Object.isArray(value);\n      for (var i = 0, length = element.length; i < length; i++) {\n        opt = element.options[i];\n        currentValue = this.optionValue(opt);\n        if (single) {\n          if (currentValue == value) {\n            opt.selected = true;\n            return;\n          }\n        }\n        else opt.selected = value.include(currentValue);\n      }\n    }\n  },\n\n  selectOne: function(element) {\n    var index = element.selectedIndex;\n    return index >= 0 ? this.optionValue(element.options[index]) : null;\n  },\n\n  selectMany: function(element) {\n    var values, length = element.length;\n    if (!length) return null;\n\n    for (var i = 0, values = []; i < length; i++) {\n      var opt = element.options[i];\n      if (opt.selected) values.push(this.optionValue(opt));\n    }\n    return values;\n  },\n\n  optionValue: function(opt) {\n    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\n\nAbstract.TimedObserver = Class.create(PeriodicalExecuter, {\n  initialize: function($super, element, frequency, callback) {\n    $super(callback, frequency);\n    this.element   = $(element);\n    this.lastValue = this.getValue();\n  },\n\n  execute: function() {\n    var value = this.getValue();\n    if (Object.isString(this.lastValue) && Object.isString(value) ?\n        this.lastValue != value : String(this.lastValue) != String(value)) {\n      this.callback(this.element, value);\n      this.lastValue = value;\n    }\n  }\n});\n\nForm.Element.Observer = Class.create(Abstract.TimedObserver, {\n  getValue: function() {\n    return Form.Element.getValue(this.element);\n  }\n});\n\nForm.Observer = Class.create(Abstract.TimedObserver, {\n  getValue: function() {\n    return Form.serialize(this.element);\n  }\n});\n\n/*--------------------------------------------------------------------------*/\n\nAbstract.EventObserver = Class.create({\n  initialize: function(element, callback) {\n    this.element  = $(element);\n    this.callback = callback;\n\n    this.lastValue = this.getValue();\n    if (this.element.tagName.toLowerCase() == 'form')\n      this.registerFormCallbacks();\n    else\n      this.registerCallback(this.element);\n  },\n\n  onElementEvent: function() {\n    var value = this.getValue();\n    if (this.lastValue != value) {\n      this.callback(this.element, value);\n      this.lastValue = value;\n    }\n  },\n\n  registerFormCallbacks: function() {\n    Form.getElements(this.element).each(this.registerCallback, this);\n  },\n\n  registerCallback: function(element) {\n    if (element.type) {\n      switch (element.type.toLowerCase()) {\n        case 'checkbox':\n        case 'radio':\n          Event.observe(element, 'click', this.onElementEvent.bind(this));\n          break;\n        default:\n          Event.observe(element, 'change', this.onElementEvent.bind(this));\n          break;\n      }\n    }\n  }\n});\n\nForm.Element.EventObserver = Class.create(Abstract.EventObserver, {\n  getValue: function() {\n    return Form.Element.getValue(this.element);\n  }\n});\n\nForm.EventObserver = Class.create(Abstract.EventObserver, {\n  getValue: function() {\n    return Form.serialize(this.element);\n  }\n});\n(function() {\n\n  var Event = {\n    KEY_BACKSPACE: 8,\n    KEY_TAB:       9,\n    KEY_RETURN:   13,\n    KEY_ESC:      27,\n    KEY_LEFT:     37,\n    KEY_UP:       38,\n    KEY_RIGHT:    39,\n    KEY_DOWN:     40,\n    KEY_DELETE:   46,\n    KEY_HOME:     36,\n    KEY_END:      35,\n    KEY_PAGEUP:   33,\n    KEY_PAGEDOWN: 34,\n    KEY_INSERT:   45,\n\n    cache: {}\n  };\n\n  var docEl = document.documentElement;\n  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl\n    && 'onmouseleave' in docEl;\n\n  var _isButton;\n  if (Prototype.Browser.IE) {\n    var buttonMap = { 0: 1, 1: 4, 2: 2 };\n    _isButton = function(event, code) {\n      return event.button === buttonMap[code];\n    };\n  } else if (Prototype.Browser.WebKit) {\n    _isButton = function(event, code) {\n      switch (code) {\n        case 0: return event.which == 1 && !event.metaKey;\n        case 1: return event.which == 1 && event.metaKey;\n        default: return false;\n      }\n    };\n  } else {\n    _isButton = function(event, code) {\n      return event.which ? (event.which === code + 1) : (event.button === code);\n    };\n  }\n\n  function isLeftClick(event)   { return _isButton(event, 0) }\n\n  function isMiddleClick(event) { return _isButton(event, 1) }\n\n  function isRightClick(event)  { return _isButton(event, 2) }\n\n  function element(event) {\n    event = Event.extend(event);\n\n    var node = event.target, type = event.type,\n     currentTarget = event.currentTarget;\n\n    if (currentTarget && currentTarget.tagName) {\n      if (type === 'load' || type === 'error' ||\n        (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'\n          && currentTarget.type === 'radio'))\n            node = currentTarget;\n    }\n\n    if (node.nodeType == Node.TEXT_NODE)\n      node = node.parentNode;\n\n    return Element.extend(node);\n  }\n\n  function findElement(event, expression) {\n    var element = Event.element(event);\n    if (!expression) return element;\n    var elements = [element].concat(element.ancestors());\n    return Selector.findElement(elements, expression, 0);\n  }\n\n  function pointer(event) {\n    return { x: pointerX(event), y: pointerY(event) };\n  }\n\n  function pointerX(event) {\n    var docElement = document.documentElement,\n     body = document.body || { scrollLeft: 0 };\n\n    return event.pageX || (event.clientX +\n      (docElement.scrollLeft || body.scrollLeft) -\n      (docElement.clientLeft || 0));\n  }\n\n  function pointerY(event) {\n    var docElement = document.documentElement,\n     body = document.body || { scrollTop: 0 };\n\n    return  event.pageY || (event.clientY +\n       (docElement.scrollTop || body.scrollTop) -\n       (docElement.clientTop || 0));\n  }\n\n\n  function stop(event) {\n    Event.extend(event);\n    event.preventDefault();\n    event.stopPropagation();\n\n    event.stopped = true;\n  }\n\n  Event.Methods = {\n    isLeftClick: isLeftClick,\n    isMiddleClick: isMiddleClick,\n    isRightClick: isRightClick,\n\n    element: element,\n    findElement: findElement,\n\n    pointer: pointer,\n    pointerX: pointerX,\n    pointerY: pointerY,\n\n    stop: stop\n  };\n\n\n  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {\n    m[name] = Event.Methods[name].methodize();\n    return m;\n  });\n\n  if (Prototype.Browser.IE) {\n    function _relatedTarget(event) {\n      var element;\n      switch (event.type) {\n        case 'mouseover': element = event.fromElement; break;\n        case 'mouseout':  element = event.toElement;   break;\n        default: return null;\n      }\n      return Element.extend(element);\n    }\n\n    Object.extend(methods, {\n      stopPropagation: function() { this.cancelBubble = true },\n      preventDefault:  function() { this.returnValue = false },\n      inspect: function() { return '[object Event]' }\n    });\n\n    Event.extend = function(event, element) {\n      if (!event) return false;\n      if (event._extendedByPrototype) return event;\n\n      event._extendedByPrototype = Prototype.emptyFunction;\n      var pointer = Event.pointer(event);\n\n      Object.extend(event, {\n        target: event.srcElement || element,\n        relatedTarget: _relatedTarget(event),\n        pageX:  pointer.x,\n        pageY:  pointer.y\n      });\n\n      return Object.extend(event, methods);\n    };\n  } else {\n    Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;\n    Object.extend(Event.prototype, methods);\n    Event.extend = Prototype.K;\n  }\n\n  function _createResponder(element, eventName, handler) {\n    var registry = Element.retrieve(element, 'prototype_event_registry');\n\n    if (Object.isUndefined(registry)) {\n      CACHE.push(element);\n      registry = Element.retrieve(element, 'prototype_event_registry', $H());\n    }\n\n    var respondersForEvent = registry.get(eventName);\n    if (Object.isUndefined(respondersForEvent)) {\n      respondersForEvent = [];\n      registry.set(eventName, respondersForEvent);\n    }\n\n    if (respondersForEvent.pluck('handler').include(handler)) return false;\n\n    var responder;\n    if (eventName.include(\":\")) {\n      responder = function(event) {\n        if (Object.isUndefined(event.eventName))\n          return false;\n\n        if (event.eventName !== eventName)\n          return false;\n\n        Event.extend(event, element);\n        handler.call(element, event);\n      };\n    } else {\n      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&\n       (eventName === \"mouseenter\" || eventName === \"mouseleave\")) {\n        if (eventName === \"mouseenter\" || eventName === \"mouseleave\") {\n          responder = function(event) {\n            Event.extend(event, element);\n\n            var parent = event.relatedTarget;\n            while (parent && parent !== element) {\n              try { parent = parent.parentNode; }\n              catch(e) { parent = element; }\n            }\n\n            if (parent === element) return;\n\n            handler.call(element, event);\n          };\n        }\n      } else {\n        responder = function(event) {\n          Event.extend(event, element);\n          handler.call(element, event);\n        };\n      }\n    }\n\n    responder.handler = handler;\n    respondersForEvent.push(responder);\n    return responder;\n  }\n\n  function _destroyCache() {\n    for (var i = 0, length = CACHE.length; i < length; i++) {\n      Event.stopObserving(CACHE[i]);\n      CACHE[i] = null;\n    }\n  }\n\n  var CACHE = [];\n\n  if (Prototype.Browser.IE)\n    window.attachEvent('onunload', _destroyCache);\n\n  if (Prototype.Browser.WebKit)\n    window.addEventListener('unload', Prototype.emptyFunction, false);\n\n\n  var _getDOMEventName = Prototype.K;\n\n  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {\n    _getDOMEventName = function(eventName) {\n      var translations = { mouseenter: \"mouseover\", mouseleave: \"mouseout\" };\n      return eventName in translations ? translations[eventName] : eventName;\n    };\n  }\n\n  function observe(element, eventName, handler) {\n    element = $(element);\n\n    var responder = _createResponder(element, eventName, handler);\n\n    if (!responder) return element;\n\n    if (eventName.include(':')) {\n      if (element.addEventListener)\n        element.addEventListener(\"dataavailable\", responder, false);\n      else {\n        element.attachEvent(\"ondataavailable\", responder);\n        element.attachEvent(\"onfilterchange\", responder);\n      }\n    } else {\n      var actualEventName = _getDOMEventName(eventName);\n\n      if (element.addEventListener)\n        element.addEventListener(actualEventName, responder, false);\n      else\n        element.attachEvent(\"on\" + actualEventName, responder);\n    }\n\n    return element;\n  }\n\n  function stopObserving(element, eventName, handler) {\n    element = $(element);\n\n    var registry = Element.retrieve(element, 'prototype_event_registry');\n\n    if (Object.isUndefined(registry)) return element;\n\n    if (eventName && !handler) {\n      var responders = registry.get(eventName);\n\n      if (Object.isUndefined(responders)) return element;\n\n      responders.each( function(r) {\n        Element.stopObserving(element, eventName, r.handler);\n      });\n      return element;\n    } else if (!eventName) {\n      registry.each( function(pair) {\n        var eventName = pair.key, responders = pair.value;\n\n        responders.each( function(r) {\n          Element.stopObserving(element, eventName, r.handler);\n        });\n      });\n      return element;\n    }\n\n    var responders = registry.get(eventName);\n\n    if (!responders) return;\n\n    var responder = responders.find( function(r) { return r.handler === handler; });\n    if (!responder) return element;\n\n    var actualEventName = _getDOMEventName(eventName);\n\n    if (eventName.include(':')) {\n      if (element.removeEventListener)\n        element.removeEventListener(\"dataavailable\", responder, false);\n      else {\n        element.detachEvent(\"ondataavailable\", responder);\n        element.detachEvent(\"onfilterchange\",  responder);\n      }\n    } else {\n      if (element.removeEventListener)\n        element.removeEventListener(actualEventName, responder, false);\n      else\n        element.detachEvent('on' + actualEventName, responder);\n    }\n\n    registry.set(eventName, responders.without(responder));\n\n    return element;\n  }\n\n  function fire(element, eventName, memo, bubble) {\n    element = $(element);\n\n    if (Object.isUndefined(bubble))\n      bubble = true;\n\n    if (element == document && document.createEvent && !element.dispatchEvent)\n      element = document.documentElement;\n\n    var event;\n    if (document.createEvent) {\n      event = document.createEvent('HTMLEvents');\n      event.initEvent('dataavailable', true, true);\n    } else {\n      event = document.createEventObject();\n      event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';\n    }\n\n    event.eventName = eventName;\n    event.memo = memo || { };\n\n    if (document.createEvent)\n      element.dispatchEvent(event);\n    else\n      element.fireEvent(event.eventType, event);\n\n    return Event.extend(event);\n  }\n\n\n  Object.extend(Event, Event.Methods);\n\n  Object.extend(Event, {\n    fire:          fire,\n    observe:       observe,\n    stopObserving: stopObserving\n  });\n\n  Element.addMethods({\n    fire:          fire,\n\n    observe:       observe,\n\n    stopObserving: stopObserving\n  });\n\n  Object.extend(document, {\n    fire:          fire.methodize(),\n\n    observe:       observe.methodize(),\n\n    stopObserving: stopObserving.methodize(),\n\n    loaded:        false\n  });\n\n  if (window.Event) Object.extend(window.Event, Event);\n  else window.Event = Event;\n})();\n\n(function() {\n  /* Support for the DOMContentLoaded event is based on work by Dan Webb,\n     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */\n\n  var timer;\n\n  function fireContentLoadedEvent() {\n    if (document.loaded) return;\n    if (timer) window.clearTimeout(timer);\n    document.loaded = true;\n    document.fire('dom:loaded');\n  }\n\n  function checkReadyState() {\n    if (document.readyState === 'complete') {\n      document.stopObserving('readystatechange', checkReadyState);\n      fireContentLoadedEvent();\n    }\n  }\n\n  function pollDoScroll() {\n    try { document.documentElement.doScroll('left'); }\n    catch(e) {\n      timer = pollDoScroll.defer();\n      return;\n    }\n    fireContentLoadedEvent();\n  }\n\n  if (document.addEventListener) {\n    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);\n  } else {\n    document.observe('readystatechange', checkReadyState);\n    if (window == top)\n      timer = pollDoScroll.defer();\n  }\n\n  Event.observe(window, 'load', fireContentLoadedEvent);\n})();\n\nElement.addMethods();\n\n/*------------------------------- DEPRECATED -------------------------------*/\n\nHash.toQueryString = Object.toQueryString;\n\nvar Toggle = { display: Element.toggle };\n\nElement.Methods.childOf = Element.Methods.descendantOf;\n\nvar Insertion = {\n  Before: function(element, content) {\n    return Element.insert(element, {before:content});\n  },\n\n  Top: function(element, content) {\n    return Element.insert(element, {top:content});\n  },\n\n  Bottom: function(element, content) {\n    return Element.insert(element, {bottom:content});\n  },\n\n  After: function(element, content) {\n    return Element.insert(element, {after:content});\n  }\n};\n\nvar $continue = new Error('\"throw $continue\" is deprecated, use \"return\" instead');\n\nvar Position = {\n  includeScrollOffsets: false,\n\n  prepare: function() {\n    this.deltaX =  window.pageXOffset\n                || document.documentElement.scrollLeft\n                || document.body.scrollLeft\n                || 0;\n    this.deltaY =  window.pageYOffset\n                || document.documentElement.scrollTop\n                || document.body.scrollTop\n                || 0;\n  },\n\n  within: function(element, x, y) {\n    if (this.includeScrollOffsets)\n      return this.withinIncludingScrolloffsets(element, x, y);\n    this.xcomp = x;\n    this.ycomp = y;\n    this.offset = Element.cumulativeOffset(element);\n\n    return (y >= this.offset[1] &&\n            y <  this.offset[1] + element.offsetHeight &&\n            x >= this.offset[0] &&\n            x <  this.offset[0] + element.offsetWidth);\n  },\n\n  withinIncludingScrolloffsets: function(element, x, y) {\n    var offsetcache = Element.cumulativeScrollOffset(element);\n\n    this.xcomp = x + offsetcache[0] - this.deltaX;\n    this.ycomp = y + offsetcache[1] - this.deltaY;\n    this.offset = Element.cumulativeOffset(element);\n\n    return (this.ycomp >= this.offset[1] &&\n            this.ycomp <  this.offset[1] + element.offsetHeight &&\n            this.xcomp >= this.offset[0] &&\n            this.xcomp <  this.offset[0] + element.offsetWidth);\n  },\n\n  overlap: function(mode, element) {\n    if (!mode) return 0;\n    if (mode == 'vertical')\n      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /\n        element.offsetHeight;\n    if (mode == 'horizontal')\n      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /\n        element.offsetWidth;\n  },\n\n\n  cumulativeOffset: Element.Methods.cumulativeOffset,\n\n  positionedOffset: Element.Methods.positionedOffset,\n\n  absolutize: function(element) {\n    Position.prepare();\n    return Element.absolutize(element);\n  },\n\n  relativize: function(element) {\n    Position.prepare();\n    return Element.relativize(element);\n  },\n\n  realOffset: Element.Methods.cumulativeScrollOffset,\n\n  offsetParent: Element.Methods.getOffsetParent,\n\n  page: Element.Methods.viewportOffset,\n\n  clone: function(source, target, options) {\n    options = options || { };\n    return Element.clonePosition(target, source, options);\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nif (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){\n  function iter(name) {\n    return name.blank() ? null : \"[contains(concat(' ', @class, ' '), ' \" + name + \" ')]\";\n  }\n\n  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?\n  function(element, className) {\n    className = className.toString().strip();\n    var cond = /\\s/.test(className) ? $w(className).map(iter).join('') : iter(className);\n    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];\n  } : function(element, className) {\n    className = className.toString().strip();\n    var elements = [], classNames = (/\\s/.test(className) ? $w(className) : null);\n    if (!classNames && !className) return elements;\n\n    var nodes = $(element).getElementsByTagName('*');\n    className = ' ' + className + ' ';\n\n    for (var i = 0, child, cn; child = nodes[i]; i++) {\n      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||\n          (classNames && classNames.all(function(name) {\n            return !name.toString().blank() && cn.include(' ' + name + ' ');\n          }))))\n        elements.push(Element.extend(child));\n    }\n    return elements;\n  };\n\n  return function(className, parentElement) {\n    return $(parentElement || document.body).getElementsByClassName(className);\n  };\n}(Element.Methods);\n\n/*--------------------------------------------------------------------------*/\n\nElement.ClassNames = Class.create();\nElement.ClassNames.prototype = {\n  initialize: function(element) {\n    this.element = $(element);\n  },\n\n  _each: function(iterator) {\n    this.element.className.split(/\\s+/).select(function(name) {\n      return name.length > 0;\n    })._each(iterator);\n  },\n\n  set: function(className) {\n    this.element.className = className;\n  },\n\n  add: function(classNameToAdd) {\n    if (this.include(classNameToAdd)) return;\n    this.set($A(this).concat(classNameToAdd).join(' '));\n  },\n\n  remove: function(classNameToRemove) {\n    if (!this.include(classNameToRemove)) return;\n    this.set($A(this).without(classNameToRemove).join(' '));\n  },\n\n  toString: function() {\n    return $A(this).join(' ');\n  }\n};\n\nObject.extend(Element.ClassNames.prototype, Enumerable);\n\n/*--------------------------------------------------------------------------*/\n"
  },
  {
    "path": "example/rails3/public/javascripts/rails.js",
    "content": "document.observe(\"dom:loaded\", function() {\n  function handleRemote(element) {\n    var method, url, params;\n\n    if (element.tagName.toLowerCase() === 'form') {\n      method = element.readAttribute('method') || 'post';\n      url    = element.readAttribute('action');\n      params = element.serialize(true);\n    } else {\n      method = element.readAttribute('data-method') || 'get';\n      url    = element.readAttribute('href');\n      params = {};\n    }\n\n    var event = element.fire(\"ajax:before\");\n    if (event.stopped) return false;\n\n    new Ajax.Request(url, {\n      method: method,\n      parameters: params,\n      asynchronous: true,\n      evalScripts: true,\n\n      onLoading:     function(request) { element.fire(\"ajax:loading\", {request: request}); },\n      onLoaded:      function(request) { element.fire(\"ajax:loaded\", {request: request}); },\n      onInteractive: function(request) { element.fire(\"ajax:interactive\", {request: request}); },\n      onComplete:    function(request) { element.fire(\"ajax:complete\", {request: request}); },\n      onSuccess:     function(request) { element.fire(\"ajax:success\", {request: request}); },\n      onFailure:     function(request) { element.fire(\"ajax:failure\", {request: request}); }\n    });\n\n    element.fire(\"ajax:after\");\n  }\n\n  function handleMethod(element) {\n    var method, url, token_name, token;\n\n    method     = element.readAttribute('data-method');\n    url        = element.readAttribute('href');\n    csrf_param = $$('meta[name=csrf-param]').first();\n    csrf_token = $$('meta[name=csrf-token]').first();\n\n    var form = new Element('form', { method: \"POST\", action: url, style: \"display: none;\" });\n    element.parentNode.appendChild(form);\n\n    if (method != 'post') {\n      var field = new Element('input', { type: 'hidden', name: '_method', value: method });\n      form.appendChild(field);\n    }\n\n    if (csrf_param) {\n      var param = csrf_param.readAttribute('content');\n      var token = csrf_token.readAttribute('content');\n      var field = new Element('input', { type: 'hidden', name: param, value: token });\n      form.appendChild(field);\n    }\n\n    form.submit();\n  }\n\n  $(document.body).observe(\"click\", function(event) {\n    var message = event.findElement().readAttribute('data-confirm');\n    if (message && !confirm(message)) {\n      event.stop();\n      return false;\n    }\n\n    var element = event.findElement(\"a[data-remote]\");\n    if (element) {\n      handleRemote(element);\n      event.stop();\n      return true;\n    }\n\n    var element = event.findElement(\"a[data-method]\");\n    if (element) {\n      handleMethod(element);\n      event.stop();\n      return true;\n    }\n  });\n\n  // TODO: I don't think submit bubbles in IE\n  $(document.body).observe(\"submit\", function(event) {\n    var element = event.findElement(),\n        message = element.readAttribute('data-confirm');\n    if (message && !confirm(message)) {\n      event.stop();\n      return false;\n    }\n\n    var inputs = element.select(\"input[type=submit][data-disable-with]\");\n    inputs.each(function(input) {\n      input.disabled = true;\n      input.writeAttribute('data-original-value', input.value);\n      input.value = input.readAttribute('data-disable-with');\n    });\n\n    var element = event.findElement(\"form[data-remote]\");\n    if (element) {\n      handleRemote(element);\n      event.stop();\n    }\n  });\n\n  $(document.body).observe(\"ajax:after\", function(event) {\n    var element = event.findElement();\n\n    if (element.tagName.toLowerCase() === 'form') {\n      var inputs = element.select(\"input[type=submit][disabled=true][data-disable-with]\");\n      inputs.each(function(input) {\n        input.value = input.readAttribute('data-original-value');\n        input.writeAttribute('data-original-value', null);\n        input.disabled = false;\n      });\n    }\n  });\n});"
  },
  {
    "path": "example/rails3/public/robots.txt",
    "content": "# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-Agent: *\n# Disallow: /\n"
  },
  {
    "path": "example/rails3/public/stylesheets/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3/script/rails",
    "content": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails 3 gems installed from the root of your application.\n\nENV_PATH  = File.expand_path('../../config/environment',  __FILE__)\nBOOT_PATH = File.expand_path('../../config/boot',  __FILE__)\nAPP_PATH  = File.expand_path('../../config/application',  __FILE__)\n\nrequire BOOT_PATH\nrequire 'rails/commands'\n"
  },
  {
    "path": "example/rails3/test/functional/page_controller_test.rb",
    "content": "require 'test_helper'\n\nclass PageControllerTest < ActionController::TestCase\n  test \"should get index\" do\n    get :index\n    assert_response :success\n  end\n\n  test \"should get test_a\" do\n    get :test_a\n    assert_response :success\n  end\n\n  test \"should get test_b\" do\n    get :test_b\n    assert_response :success\n  end\n\nend\n"
  },
  {
    "path": "example/rails3/test/performance/browsing_test.rb",
    "content": "require 'test_helper'\nrequire 'rails/performance_test_help'\n\n# Profiling results for each test method are written to tmp/performance.\nclass BrowsingTest < ActionDispatch::PerformanceTest\n  def test_homepage\n    get '/'\n  end\nend\n"
  },
  {
    "path": "example/rails3/test/test_helper.rb",
    "content": "ENV[\"RAILS_ENV\"] = \"test\"\nrequire File.expand_path('../../config/environment', __FILE__)\nrequire 'rails/test_help'\n\nclass ActiveSupport::TestCase\n  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.\n  #\n  # Note: You'll currently still have to declare fixtures explicitly in integration tests\n  # -- they do not yet inherit this setting\n  fixtures :all\n\n  # Add more helper methods to be used by all tests here...\nend\n"
  },
  {
    "path": "example/rails3/test/unit/helpers/page_helper_test.rb",
    "content": "require 'test_helper'\n\nclass PageHelperTest < ActionView::TestCase\nend\n"
  },
  {
    "path": "example/rails3/vendor/plugins/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/Gemfile",
    "content": "source 'http://rubygems.org'\n\ngem 'rails', '3.1.0'\n\n# Bundle edge Rails instead:\n# gem 'rails',     :git => 'git://github.com/rails/rails.git'\n\ngem 'sqlite3'\n\ngem 'json'\n\n# Gems used only for assets and not required\n# in production environments by default.\ngroup :assets do\n  gem 'sass-rails', \"  ~> 3.1.0\"\n  gem 'coffee-rails', \"~> 3.1.0\"\n  gem 'uglifier'\nend\n\ngem 'jquery-rails'\n\ngem 'analytical', :path => '../..'\n\n\n# Use unicorn as the web server\n# gem 'unicorn'\n\n# Deploy with Capistrano\n# gem 'capistrano'\n\n# To use debugger\n# gem 'ruby-debug'\n\n"
  },
  {
    "path": "example/rails3.1/README",
    "content": "== Welcome to Rails\n\nRails is a web-application framework that includes everything needed to create\ndatabase-backed web applications according to the Model-View-Control pattern.\n\nThis pattern splits the view (also called the presentation) into \"dumb\"\ntemplates that are primarily responsible for inserting pre-built data in between\nHTML tags. The model contains the \"smart\" domain objects (such as Account,\nProduct, Person, Post) that holds all the business logic and knows how to\npersist themselves to a database. The controller handles the incoming requests\n(such as Save New Account, Update Product, Show Post) by manipulating the model\nand directing data to the view.\n\nIn Rails, the model is handled by what's called an object-relational mapping\nlayer entitled Active Record. This layer allows you to present the data from\ndatabase rows as objects and embellish these data objects with business logic\nmethods. You can read more about Active Record in\nlink:files/vendor/rails/activerecord/README.html.\n\nThe controller and view are handled by the Action Pack, which handles both\nlayers by its two parts: Action View and Action Controller. These two layers\nare bundled in a single package due to their heavy interdependence. This is\nunlike the relationship between the Active Record and Action Pack that is much\nmore separate. Each of these packages can be used independently outside of\nRails. You can read more about Action Pack in\nlink:files/vendor/rails/actionpack/README.html.\n\n\n== Getting Started\n\n1. At the command prompt, create a new Rails application:\n       <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)\n\n2. Change directory to <tt>myapp</tt> and start the web server:\n       <tt>cd myapp; rails server</tt> (run with --help for options)\n\n3. Go to http://localhost:3000/ and you'll see:\n       \"Welcome aboard: You're riding Ruby on Rails!\"\n\n4. Follow the guidelines to start developing your application. You can find\nthe following resources handy:\n\n* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html\n* Ruby on Rails Tutorial Book: http://www.railstutorial.org/\n\n\n== Debugging Rails\n\nSometimes your application goes wrong. Fortunately there are a lot of tools that\nwill help you debug it and get it back on the rails.\n\nFirst area to check is the application log files. Have \"tail -f\" commands\nrunning on the server.log and development.log. Rails will automatically display\ndebugging and runtime information to these files. Debugging info will also be\nshown in the browser on requests from 127.0.0.1.\n\nYou can also log your own messages directly into the log file from your code\nusing the Ruby logger class from inside your controllers. Example:\n\n  class WeblogController < ActionController::Base\n    def destroy\n      @weblog = Weblog.find(params[:id])\n      @weblog.destroy\n      logger.info(\"#{Time.now} Destroyed Weblog ID ##{@weblog.id}!\")\n    end\n  end\n\nThe result will be a message in your log file along the lines of:\n\n  Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!\n\nMore information on how to use the logger is at http://www.ruby-doc.org/core/\n\nAlso, Ruby documentation can be found at http://www.ruby-lang.org/. There are\nseveral books available online as well:\n\n* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)\n* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)\n\nThese two books will bring you up to speed on the Ruby language and also on\nprogramming in general.\n\n\n== Debugger\n\nDebugger support is available through the debugger command when you start your\nMongrel or WEBrick server with --debugger. This means that you can break out of\nexecution at any point in the code, investigate and change the model, and then,\nresume execution! You need to install ruby-debug to run the server in debugging\nmode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:\n\n  class WeblogController < ActionController::Base\n    def index\n      @posts = Post.all\n      debugger\n    end\n  end\n\nSo the controller will accept the action, run the first line, then present you\nwith a IRB prompt in the server window. Here you can do things like:\n\n  >> @posts.inspect\n  => \"[#<Post:0x14a6be8\n          @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,\n       #<Post:0x14a6620\n          @attributes={\"title\"=>\"Rails\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]\"\n  >> @posts.first.title = \"hello from a debugger\"\n  => \"hello from a debugger\"\n\n...and even better, you can examine how your runtime objects actually work:\n\n  >> f = @posts.first\n  => #<Post:0x13630c4 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>\n  >> f.\n  Display all 152 possibilities? (y or n)\n\nFinally, when you're ready to resume execution, you can enter \"cont\".\n\n\n== Console\n\nThe console is a Ruby shell, which allows you to interact with your\napplication's domain model. Here you'll have all parts of the application\nconfigured, just like it is when the application is running. You can inspect\ndomain models, change values, and save to the database. Starting the script\nwithout arguments will launch it in the development environment.\n\nTo start the console, run <tt>rails console</tt> from the application\ndirectory.\n\nOptions:\n\n* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications\n  made to the database.\n* Passing an environment name as an argument will load the corresponding\n  environment. Example: <tt>rails console production</tt>.\n\nTo reload your controllers and models after launching the console run\n<tt>reload!</tt>\n\nMore information about irb can be found at:\nlink:http://www.rubycentral.org/pickaxe/irb.html\n\n\n== dbconsole\n\nYou can go to the command line of your database directly through <tt>rails\ndbconsole</tt>. You would be connected to the database with the credentials\ndefined in database.yml. Starting the script without arguments will connect you\nto the development database. Passing an argument will connect you to a different\ndatabase, like <tt>rails dbconsole production</tt>. Currently works for MySQL,\nPostgreSQL and SQLite 3.\n\n== Description of Contents\n\nThe default directory structure of a generated Ruby on Rails application:\n\n  |-- app\n  |   |-- assets\n  |       |-- images\n  |       |-- javascripts\n  |       `-- stylesheets\n  |   |-- controllers\n  |   |-- helpers\n  |   |-- mailers\n  |   |-- models\n  |   `-- views\n  |       `-- layouts\n  |-- config\n  |   |-- environments\n  |   |-- initializers\n  |   `-- locales\n  |-- db\n  |-- doc\n  |-- lib\n  |   `-- tasks\n  |-- log\n  |-- public\n  |-- script\n  |-- test\n  |   |-- fixtures\n  |   |-- functional\n  |   |-- integration\n  |   |-- performance\n  |   `-- unit\n  |-- tmp\n  |   |-- cache\n  |   |-- pids\n  |   |-- sessions\n  |   `-- sockets\n  `-- vendor\n      |-- assets\n          `-- stylesheets\n      `-- plugins\n\napp\n  Holds all the code that's specific to this particular application.\n\napp/assets\n  Contains subdirectories for images, stylesheets, and JavaScript files.\n\napp/controllers\n  Holds controllers that should be named like weblogs_controller.rb for\n  automated URL mapping. All controllers should descend from\n  ApplicationController which itself descends from ActionController::Base.\n\napp/models\n  Holds models that should be named like post.rb. Models descend from\n  ActiveRecord::Base by default.\n\napp/views\n  Holds the template files for the view that should be named like\n  weblogs/index.html.erb for the WeblogsController#index action. All views use\n  eRuby syntax by default.\n\napp/views/layouts\n  Holds the template files for layouts to be used with views. This models the\n  common header/footer method of wrapping views. In your views, define a layout\n  using the <tt>layout :default</tt> and create a file named default.html.erb.\n  Inside default.html.erb, call <% yield %> to render the view using this\n  layout.\n\napp/helpers\n  Holds view helpers that should be named like weblogs_helper.rb. These are\n  generated for you automatically when using generators for controllers.\n  Helpers can be used to wrap functionality for your views into methods.\n\nconfig\n  Configuration files for the Rails environment, the routing map, the database,\n  and other dependencies.\n\ndb\n  Contains the database schema in schema.rb. db/migrate contains all the\n  sequence of Migrations for your schema.\n\ndoc\n  This directory is where your application documentation will be stored when\n  generated using <tt>rake doc:app</tt>\n\nlib\n  Application specific libraries. Basically, any kind of custom code that\n  doesn't belong under controllers, models, or helpers. This directory is in\n  the load path.\n\npublic\n  The directory available for the web server. Also contains the dispatchers and the\n  default HTML files. This should be set as the DOCUMENT_ROOT of your web\n  server.\n\nscript\n  Helper scripts for automation and generation.\n\ntest\n  Unit and functional tests along with fixtures. When using the rails generate\n  command, template test files will be generated for you and placed in this\n  directory.\n\nvendor\n  External libraries that the application depends on. Also includes the plugins\n  subdirectory. If the app has frozen rails, those gems also go here, under\n  vendor/rails/. This directory is in the load path.\n"
  },
  {
    "path": "example/rails3.1/Rakefile",
    "content": "#!/usr/bin/env rake\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire File.expand_path('../config/application', __FILE__)\n\nExample::Application.load_tasks\n"
  },
  {
    "path": "example/rails3.1/app/assets/javascripts/application.js",
    "content": "// This is a manifest file that'll be compiled into including all the files listed below.\n// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically\n// be included in the compiled file accessible from http://example.com/assets/application.js\n// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the\n// the compiled file.\n//\n//= require jquery\n//= require jquery_ujs\n//= require_tree .\n"
  },
  {
    "path": "example/rails3.1/app/assets/stylesheets/application.css",
    "content": "/*\n * This is a manifest file that'll automatically include all the stylesheets available in this directory\n * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at\n * the top of the compiled file, but it's generally better to create a new file per style scope.\n *= require_self\n *= require_tree . \n*/"
  },
  {
    "path": "example/rails3.1/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  protect_from_forgery\n\n  analytical :modules=>[:console, :google, :clicky], :disable_if=>lambda{|controller| false}, :use_session_store=>true\nend\n"
  },
  {
    "path": "example/rails3.1/app/controllers/page_controller.rb",
    "content": "class PageController < ApplicationController\n  def index\n  end\n\n  def test_a\n  end\n\n  def test_b\n    analytical.clicky.track('track link in A')\n    analytical.track 'track in controller'\n  end\n\n  def test_c\n    analytical.track 'track in controller that redirects'\n    redirect_to root_path\n  end\n\nend\n"
  },
  {
    "path": "example/rails3.1/app/helpers/application_helper.rb",
    "content": "module ApplicationHelper\nend\n"
  },
  {
    "path": "example/rails3.1/app/mailers/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/app/models/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Example</title>\n  <%= stylesheet_link_tag :all %>\n  <%= javascript_include_tag :defaults %>\n  <%= csrf_meta_tag %>\n\n\t<% analytical.identify '5', :email=>'josh@feefighters.com' %>\n\t<%= raw analytical.head_javascript %>\n</head>\n<body>\n\t<%= raw analytical.body_prepend_javascript %>\n\t<%= yield %>\n\t<%= raw analytical.body_append_javascript %>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3.1/app/views/page/index.html.erb",
    "content": "<h1>Page#index</h1>\n<p>\n\t<%= link_to 'A', '/page/test_a' %><br />\n\t<%= link_to 'B', '/page/test_b' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n</p>"
  },
  {
    "path": "example/rails3.1/app/views/page/test_a.html.erb",
    "content": "<% analytical.track 'track in view A' %>\n<% analytical.event 'Something Important', {:some=>'data'} %>\n<h1>Page#test_a</h1>\n<p>\n\t<%= link_to 'B', '/page/test_b' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n\t<%= link_to 'index', '/' %><br />\n\t<a href='#' onclick=\"<%= analytical.now.track('track link in A') %>; return false;\">click me to track all modules</a><br />\n</p>\n\n\n"
  },
  {
    "path": "example/rails3.1/app/views/page/test_b.html.erb",
    "content": "<h1>Page#test_b</h1>\n<p>\n\t<%= link_to 'A', '/page/test_a' %><br />\n\t<%= link_to 'C', '/page/test_c' %><br />\n\t<%= link_to 'index', '/' %><br />\n\t<a href='#' onclick=\"<%= analytical.console.track('track link in A') %>; return false;\">track console only</a><br />\n</p>\n"
  },
  {
    "path": "example/rails3.1/config/application.rb",
    "content": "require File.expand_path('../boot', __FILE__)\n\nrequire 'rails/all'\n\nif defined?(Bundler)\n  # If you precompile assets before deploying to production, use this line\n  Bundler.require *Rails.groups(:assets => %w(development test))\n  # If you want your assets lazily compiled in production, use this line\n  # Bundler.require(:default, :assets, Rails.env)\nend\n\nmodule Example\n  class Application < Rails::Application\n    # Settings in config/environments/* take precedence over those specified here.\n    # Application configuration should go into files in config/initializers\n    # -- all .rb files in that directory are automatically loaded.\n\n    # Custom directories with classes and modules you want to be autoloadable.\n    # config.autoload_paths += %W(#{config.root}/extras)\n\n    # Only load the plugins named here, in the order given (default is alphabetical).\n    # :all can be used as a placeholder for all plugins not explicitly named.\n    # config.plugins = [ :exception_notification, :ssl_requirement, :all ]\n\n    # Activate observers that should always be running.\n    # config.active_record.observers = :cacher, :garbage_collector, :forum_observer\n\n    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.\n    # Run \"rake -D time\" for a list of tasks for finding time zone names. Default is UTC.\n    # config.time_zone = 'Central Time (US & Canada)'\n\n    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.\n    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]\n    # config.i18n.default_locale = :de\n\n    # Configure the default encoding used in templates for Ruby 1.9.\n    config.encoding = \"utf-8\"\n\n    # Configure sensitive parameters which will be filtered from the log file.\n    config.filter_parameters += [:password]\n\n    # Enable the asset pipeline\n    config.assets.enabled = true\n\n    # Version of your assets, change this if you want to expire all your assets\n    config.assets.version = '1.0'\n  end\nend\n"
  },
  {
    "path": "example/rails3.1/config/boot.rb",
    "content": "require 'rubygems'\n\n# Set up gems listed in the Gemfile.\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)\n\nrequire 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])\n"
  },
  {
    "path": "example/rails3.1/config/database.yml",
    "content": "# SQLite version 3.x\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem 'sqlite3'\ndevelopment:\n  adapter: sqlite3\n  database: db/development.sqlite3\n  pool: 5\n  timeout: 5000\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  adapter: sqlite3\n  database: db/test.sqlite3\n  pool: 5\n  timeout: 5000\n\nproduction:\n  adapter: sqlite3\n  database: db/production.sqlite3\n  pool: 5\n  timeout: 5000\n"
  },
  {
    "path": "example/rails3.1/config/environment.rb",
    "content": "# Load the rails application\nrequire File.expand_path('../application', __FILE__)\n\n# Initialize the rails application\nExample::Application.initialize!\n"
  },
  {
    "path": "example/rails3.1/config/environments/development.rb",
    "content": "Example::Application.configure do\n  # Settings specified here will take precedence over those in config/application.rb\n\n  # In the development environment your application's code is reloaded on\n  # every request.  This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.cache_classes = false\n\n  # Log error messages when you accidentally call methods on nil.\n  config.whiny_nils = true\n\n  # Show full error reports and disable caching\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n\n  # Don't care if the mailer can't send\n  config.action_mailer.raise_delivery_errors = false\n\n  # Print deprecation notices to the Rails logger\n  config.active_support.deprecation = :log\n\n  # Only use best-standards-support built into browsers\n  config.action_dispatch.best_standards_support = :builtin\n\n  # Do not compress assets\n  config.assets.compress = false\n\n  # Expands the lines which load the assets\n  config.assets.debug = true\nend\n"
  },
  {
    "path": "example/rails3.1/config/environments/production.rb",
    "content": "Example::Application.configure do\n  # Settings specified here will take precedence over those in config/application.rb\n\n  # Code is not reloaded between requests\n  config.cache_classes = true\n\n  # Full error reports are disabled and caching is turned on\n  config.consider_all_requests_local       = false\n  config.action_controller.perform_caching = true\n\n  # Disable Rails's static asset server (Apache or nginx will already do this)\n  config.serve_static_assets = false\n\n  # Compress JavaScripts and CSS\n  config.assets.compress = true\n\n  # Don't fallback to assets pipeline if a precompiled asset is missed\n  config.assets.compile = false\n\n  # Generate digests for assets URLs\n  config.assets.digest = true\n\n  # Defaults to Rails.root.join(\"public/assets\")\n  # config.assets.manifest = YOUR_PATH\n\n  # Specifies the header that your server uses for sending files\n  # config.action_dispatch.x_sendfile_header = \"X-Sendfile\" # for apache\n  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  # config.force_ssl = true\n\n  # See everything in the log (default is :info)\n  # config.log_level = :debug\n\n  # Use a different logger for distributed setups\n  # config.logger = SyslogLogger.new\n\n  # Use a different cache store in production\n  # config.cache_store = :mem_cache_store\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server\n  # config.action_controller.asset_host = \"http://assets.example.com\"\n\n  # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)\n  # config.assets.precompile += %w( search.js )\n\n  # Disable delivery errors, bad email addresses will be ignored\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable threaded mode\n  # config.threadsafe!\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation can not be found)\n  config.i18n.fallbacks = true\n\n  # Send deprecation notices to registered listeners\n  config.active_support.deprecation = :notify\nend\n"
  },
  {
    "path": "example/rails3.1/config/environments/test.rb",
    "content": "Example::Application.configure do\n  # Settings specified here will take precedence over those in config/application.rb\n\n  # The test environment is used exclusively to run your application's\n  # test suite.  You never need to work with it otherwise.  Remember that\n  # your test database is \"scratch space\" for the test suite and is wiped\n  # and recreated between test runs.  Don't rely on the data there!\n  config.cache_classes = true\n\n  # Configure static asset server for tests with Cache-Control for performance\n  config.serve_static_assets = true\n  config.static_cache_control = \"public, max-age=3600\"\n\n  # Log error messages when you accidentally call methods on nil\n  config.whiny_nils = true\n\n  # Show full error reports and disable caching\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n\n  # Raise exceptions instead of rendering exception templates\n  config.action_dispatch.show_exceptions = false\n\n  # Disable request forgery protection in test environment\n  config.action_controller.allow_forgery_protection    = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Use SQL instead of Active Record's schema dumper when creating the test database.\n  # This is necessary if your schema can't be completely dumped by the schema dumper,\n  # like if you have constraints or database-specific column types\n  # config.active_record.schema_format = :sql\n\n  # Print deprecation notices to the stderr\n  config.active_support.deprecation = :stderr\n\n  # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets\n  config.assets.allow_debugging = true\nend\n"
  },
  {
    "path": "example/rails3.1/config/initializers/backtrace_silencers.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.\n# Rails.backtrace_cleaner.remove_silencers!\n"
  },
  {
    "path": "example/rails3.1/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format\n# (all these examples are active by default):\n# ActiveSupport::Inflector.inflections do |inflect|\n#   inflect.plural /^(ox)$/i, '\\1en'\n#   inflect.singular /^(ox)en/i, '\\1'\n#   inflect.irregular 'person', 'people'\n#   inflect.uncountable %w( fish sheep )\n# end\n"
  },
  {
    "path": "example/rails3.1/config/initializers/mime_types.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\n# Mime::Type.register_alias \"text/html\", :iphone\n"
  },
  {
    "path": "example/rails3.1/config/initializers/secret_token.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Your secret key for verifying the integrity of signed cookies.\n# If you change this key, all old signed cookies will become invalid!\n# Make sure the secret is at least 30 characters and all random,\n# no regular words or you'll be exposed to dictionary attacks.\nExample::Application.config.secret_token = '233c9a6a0af358dcad8ed91c695b7a68000809f77cc32cf18b822d21242cf1ee820f62c69f8f6820c834c77d2b3f073860da40bbf510f5ca036257e2fb84b041'\n"
  },
  {
    "path": "example/rails3.1/config/initializers/session_store.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\nExample::Application.config.session_store :cookie_store, :key => '_example_session'\n\n# Use the database for sessions instead of the cookie-based default,\n# which shouldn't be used to store highly confidential information\n# (create the session table with \"rails generate session_migration\")\n# Example::Application.config.session_store :active_record_store\n"
  },
  {
    "path": "example/rails3.1/config/initializers/wrap_parameters.rb",
    "content": "# Be sure to restart your server when you modify this file.\n#\n# This file contains settings for ActionController::ParamsWrapper which\n# is enabled by default.\n\n# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.\nActiveSupport.on_load(:action_controller) do\n  wrap_parameters :format => [:json]\nend\n\n# Disable root element in JSON by default.\nActiveSupport.on_load(:active_record) do\n  self.include_root_in_json = false\nend\n"
  },
  {
    "path": "example/rails3.1/config/locales/en.yml",
    "content": "# Sample localization file for English. Add more files in this directory for other locales.\n# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "example/rails3.1/config/routes.rb",
    "content": "Example::Application.routes.draw do\n  get \"page/index\"\n  get \"page/test_a\"\n  get \"page/test_b\"\n  get \"page/test_c\"\n\n  # You can have the root of your site routed with \"root\"\n  # just remember to delete public/index.html.\n  root :to => \"page#index\"\n\n  # The priority is based upon order of creation:\n  # first created -> highest priority.\n\n  # Sample of regular route:\n  #   match 'products/:id' => 'catalog#view'\n  # Keep in mind you can assign values other than :controller and :action\n\n  # Sample of named route:\n  #   match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase\n  # This route can be invoked with purchase_url(:id => product.id)\n\n  # Sample resource route (maps HTTP verbs to controller actions automatically):\n  #   resources :products\n\n  # Sample resource route with options:\n  #   resources :products do\n  #     member do\n  #       get 'short'\n  #       post 'toggle'\n  #     end\n  #\n  #     collection do\n  #       get 'sold'\n  #     end\n  #   end\n\n  # Sample resource route with sub-resources:\n  #   resources :products do\n  #     resources :comments, :sales\n  #     resource :seller\n  #   end\n\n  # Sample resource route with more complex sub-resources\n  #   resources :products do\n  #     resources :comments\n  #     resources :sales do\n  #       get 'recent', :on => :collection\n  #     end\n  #   end\n\n  # Sample resource route within a namespace:\n  #   namespace :admin do\n  #     # Directs /admin/products/* to Admin::ProductsController\n  #     # (app/controllers/admin/products_controller.rb)\n  #     resources :products\n  #   end\n\n  # You can have the root of your site routed with \"root\"\n  # just remember to delete public/index.html.\n  # root :to => 'welcome#index'\n\n  # See how all your routes lay out with \"rake routes\"\n\n  # This is a legacy wild controller route that's not recommended for RESTful applications.\n  # Note: This route will make all actions in every controller accessible via GET requests.\n  # match ':controller(/:action(/:id(.:format)))'\nend\n"
  },
  {
    "path": "example/rails3.1/config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire ::File.expand_path('../config/environment',  __FILE__)\nrun Example::Application\n"
  },
  {
    "path": "example/rails3.1/db/development.sqlite3",
    "content": ""
  },
  {
    "path": "example/rails3.1/db/seeds.rb",
    "content": "# This file should contain all the record creation needed to seed the database with its default values.\n# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).\n#\n# Examples:\n#\n#   cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])\n#   Mayor.create(:name => 'Emanuel', :city => cities.first)\n"
  },
  {
    "path": "example/rails3.1/doc/README_FOR_APP",
    "content": "Use this README file to introduce your application and point to useful places in the API for learning more.\nRun \"rake doc:app\" to generate API documentation for your models, controllers, helpers, and libraries.\n"
  },
  {
    "path": "example/rails3.1/lib/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/lib/tasks/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <h1>The page you were looking for doesn't exist.</h1>\n    <p>You may have mistyped the address or the page may have moved.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3.1/public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <h1>The change you wanted was rejected.</h1>\n    <p>Maybe you tried to change something you didn't have access to.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3.1/public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <h1>We're sorry, but something went wrong.</h1>\n    <p>We've been notified about this issue and we'll take a look at it shortly.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "example/rails3.1/public/robots.txt",
    "content": "# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-Agent: *\n# Disallow: /\n"
  },
  {
    "path": "example/rails3.1/script/rails",
    "content": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails 3 gems installed from the root of your application.\n\nAPP_PATH = File.expand_path('../../config/application',  __FILE__)\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'rails/commands'\n"
  },
  {
    "path": "example/rails3.1/test/fixtures/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/test/functional/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/test/integration/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/test/performance/browsing_test.rb",
    "content": "require 'test_helper'\nrequire 'rails/performance_test_help'\n\nclass BrowsingTest < ActionDispatch::PerformanceTest\n  # Refer to the documentation for all available options\n  # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]\n  #                          :output => 'tmp/performance', :formats => [:flat] }\n\n  def test_homepage\n    get '/'\n  end\nend\n"
  },
  {
    "path": "example/rails3.1/test/test_helper.rb",
    "content": "ENV[\"RAILS_ENV\"] = \"test\"\nrequire File.expand_path('../../config/environment', __FILE__)\nrequire 'rails/test_help'\n\nclass ActiveSupport::TestCase\n  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.\n  #\n  # Note: You'll currently still have to declare fixtures explicitly in integration tests\n  # -- they do not yet inherit this setting\n  fixtures :all\n\n  # Add more helper methods to be used by all tests here...\nend\n"
  },
  {
    "path": "example/rails3.1/test/unit/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/vendor/assets/stylesheets/.gitkeep",
    "content": ""
  },
  {
    "path": "example/rails3.1/vendor/plugins/.gitkeep",
    "content": ""
  },
  {
    "path": "lib/analytical/api.rb",
    "content": "module Analytical\n  module Modules\n    class DummyModule\n      include Analytical::Modules::Base\n      def method_missing(method, *args, &block); nil; end\n    end\n  end\n\n  class Api\n    attr_accessor :options, :modules\n\n    def initialize(options={})\n      @options = options\n      @modules = ActiveSupport::OrderedHash.new\n      @options[:modules].each do |m|\n        module_options = @options.merge(@options[m] || {})\n        module_options.delete(:modules)\n        module_options[:session_store] = Analytical::SessionCommandStore.new(@options[:session], m) if @options[:session]\n        @modules[m] = get_mod(m).new(module_options)\n      end\n      @dummy_module = Analytical::Modules::DummyModule.new\n    end\n\n    def get_mod(name)\n      name = name.to_s.camelize\n      \"Analytical::Modules::#{name}\".constantize\n    rescue NameError\n      raise \"You're trying to configure a module named '#{name}', but \" +\n        \"Analytical doesn't have one. Check your analytical.yml file for typos.\"\n    end\n\n    #\n    # Catch commands such as :track, :identify and send them on to all of the modules.\n    # Or... if a module name is passed, return that module so it can be used directly, ie:\n    # analytical.console.go 'make', :some=>:cookies\n    #\n    def method_missing(method, *args, &block)\n      method = method.to_sym\n      if @modules.keys.include?(method)\n        @modules[method]\n      elsif available_modules.include?(method)\n        @dummy_module\n      else\n        process_command method, *args\n      end\n    end\n\n\n    #\n    # Delegation class that passes methods to\n    #\n    class ImmediateDelegateHelper\n      def initialize(_parent)\n        @parent = _parent\n      end\n      def method_missing(method, *args, &block)\n        @parent.modules.values.collect do |m|\n          m.send(method, *args) if m.respond_to?(method)\n        end.delete_if{|c| c.blank?}.join(\"\\n\")\n      end\n    end\n\n    #\n    # Returns a new delegation object for immediate processing of a command\n    #\n    def now\n      ImmediateDelegateHelper.new(self)\n    end\n\n    #\n    # These methods return the javascript that should be inserted into each section of your layout\n    #\n    def head_prepend_javascript\n      [init_javascript(:head_prepend), tracking_javascript(:head_prepend)].delete_if{|s| s.blank?}.join(\"\\n\")\n    end\n\n    def head_append_javascript\n      js = [\n        init_javascript(:head_append),\n        tracking_javascript(:head_append),\n      ]\n\n      if options[:javascript_helpers]\n        if Gem::Version.new(::Rails::VERSION::STRING) >= Gem::Version.new('3.1.0')  # Rails 3.1 lets us override views in engines\n          js << options[:controller].send(:render_to_string, :partial=>'analytical_javascript', :formats => [:html], :handlers => [:erb]) if options[:controller]\n        else # All other rails\n          _partial_path = Pathname.new(__FILE__).dirname.join('..', '..', 'app/views/application', '_analytical_javascript.html.erb').to_s\n          js << options[:controller].send(:render_to_string, :file=>_partial_path, :layout=>false) if options[:controller]\n        end\n      end\n\n      js.delete_if{|s| s.blank?}.join(\"\\n\")\n    end\n\n    alias_method :head_javascript, :head_append_javascript\n\n    def body_prepend_javascript\n      [init_javascript(:body_prepend), tracking_javascript(:body_prepend)].delete_if{|s| s.blank?}.join(\"\\n\")\n    end\n    def body_append_javascript\n      [init_javascript(:body_append), tracking_javascript(:body_append)].delete_if{|s| s.blank?}.join(\"\\n\")\n    end\n\n    private\n\n    def process_command(command, *args)\n      @modules.values.each do |m|\n        m.queue command, *args\n      end\n    end\n\n    def tracking_javascript(location)\n      commands = []\n      @modules.each do |name, m|\n        commands += m.process_queued_commands if m.init_location?(location) || m.initialized\n      end\n      commands = commands.delete_if{|c| c.blank? || c.empty?}\n      unless commands.empty?\n        commands.unshift \"<script type='text/javascript'>\"\n        commands << \"</script>\"\n      end\n      commands.join(\"\\n\")\n    end\n\n    def init_javascript(location)\n      @modules.values.collect do |m|\n        m.init_javascript(location) if m.respond_to?(:init_javascript)\n      end.delete_if{|c| c.blank?}.join\n    end\n\n    def available_modules\n      Dir.glob(File.dirname(__FILE__)+'/modules/*.rb').collect do |f|\n        File.basename(f).sub(/.rb/,'').to_sym\n      end - [:base]\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/analytical/bot_detector.rb",
    "content": "module Analytical\n  module BotDetector\n\n    def analytical_is_robot?(user_agent, whitelist = [])\n      whitelist = Array(whitelist)\n\n      unless user_agent.blank?\n        user_agent = user_agent.to_s.downcase\n\n        # We mark something as a bot if it contains any of the $bot_indicators\n        # or if it does not contain one of the $browser_indicators. In addition,\n        # if the user-agent string contains \"mozilla\" we make sure it has version\n        # information. Finally anything that starts with a word in the $whitelist\n        # is never considered a bot.\n\n        whitelist.concat %w(w3m dillo links elinks lynx)\n        whitelist.each do |word|\n          return false if user_agent.index(word) == 0\n        end\n\n        bot_indicators = %w(bot spider search jeeves crawl seek heritrix slurp thumbnails capture ferret webinator scan retriever accelerator upload digg extractor grub scrub)\n        bot_indicators.each do |word|\n          return true if user_agent.index word\n        end\n\n        browser_indicators = %w(mozilla browser iphone lynx mobile opera icab)\n        has_browser_indicator = false\n\n        browser_indicators.each do |word|\n          if user_agent.index word\n            has_browser_indicator = true\n            break\n          end\n        end\n\n        return true if not has_browser_indicator\n\n        # Check for mozilla version information\n        if user_agent.include? \"mozilla\"\n          return true if not user_agent.include? \"(\"\n          return true if user_agent !~ /mozilla\\/\\d+/i\n        end\n      end\n      return false\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/analytical/command_store.rb",
    "content": "module Analytical\n  class CommandStore\n    attr_accessor :commands\n\n    def initialize(initial_list=nil)\n      @commands = initial_list || []\n    end\n\n    def flush\n      @commands = []\n    end\n\n    # Pass any array methods on to the internal array\n    def method_missing(method, *args, &block)\n      @commands.send(method, *args, &block)\n    end\n\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/adroll.rb",
    "content": "module Analytical\n  module Modules\n    class Adroll\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Adroll -->\n          <script type=\"text/javascript\">\n          adroll_adv_id = \"#{options[:adv_id]}\";\n          adroll_pix_id = \"#{options[:pix_id]}\";\n          (function () {\n          var oldonload = window.onload;\n          window.onload = function(){\n             __adroll_loaded=true;\n             var scr = document.createElement(\"script\");\n             var host = ((\"https:\" == document.location.protocol) ? \"https://s.adroll.com\" : \"http://a.adroll.com\");\n             scr.setAttribute('async', 'true');\n             scr.type = \"text/javascript\";\n             scr.src = host + \"/j/roundtrip.js\";\n             ((document.getElementsByTagName('head') || [null])[0] || document.getElementsByTagName('script')[0].parentNode).appendChild(scr);\n             if(oldonload){oldonload()}};\n          }());\n          </script>\n          HTML\n          js\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/adwords.rb",
    "content": "module Analytical\n  module Modules\n    class Adwords\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          @initializing = true\n          html = \"<!-- Analytical Init: Google Adwords -->\\n\"\n          event_commands = []\n          @command_store.commands.each do |c|\n            if c[0] == :event\n              event_commands << event(*c[1..-1])\n            end\n          end\n          html += event_commands.join(\"\\n\")\n          @command_store.commands = @command_store.commands.delete_if {|c| c[0] == :event }\n          @initializing = false\n\n          html\n        end\n      end\n\n      #\n      # Define conversion events in analytical.yml like:\n      #\n      # adwords:\n      #   'Some Event':\n      #     id: 4444555555\n      #     language: en\n      #     format: 2\n      #     color: ffffff\n      #     label: xxxxxxxxxxxxxxxx\n      #     value: 0\n      #   'Another Event':\n      #     id: 1111333333\n      #     language: en\n      #     format: 2\n      #     color: ffffff\n      #     label: yyyyyyyyyyyyyyyy\n      #     value: 0\n      #\n      def event(name, *args)\n        return '' unless @initializing\n\n        data = args.first || {}\n        if conversion = options[name.to_sym]\n          conversion.symbolize_keys!\n          js = <<-HTML\n          <script type=\"text/javascript\">\n            /* <![CDATA[ */\n            var google_conversion_id = #{conversion[:id]};\n            var google_conversion_language = \"#{conversion[:language]}\";\n            var google_conversion_format = \"#{conversion[:format]}\";\n            var google_conversion_color = \"#{conversion[:color]}\";\n            var google_conversion_label = \"#{conversion[:label]}\";\n            var google_conversion_value = #{data[:value] || conversion[:value]};\n            /* ]]> */\n          </script>\n          <script type=\"text/javascript\" src=\"#{protocol}://www.googleadservices.com/pagead/conversion.js\"></script>\n          <noscript>\n            <div style=\"display:inline;\">\n            <img height=\"1\" width=\"1\" style=\"border-style:none;\" alt=\"\" src=\"#{protocol}://www.googleadservices.com/pagead/conversion/#{conversion[:id]}/?label=#{conversion[:label]}&amp;guid=ON&amp;script=0\"/>\n            </div>\n          </noscript>\n          HTML\n          js\n        else\n          \"<!-- No Adwords Conversion for: #{name} -->\"\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/base.rb",
    "content": "module Analytical\n  module Modules\n    module Base\n      attr_reader :tracking_command_location, :options, :initialized\n      attr_reader :command_store\n\n      def initialize(_options={})\n        @options = _options\n        @tracking_command_location = :body_prepend\n        @initialized = false\n        @command_store = @options[:session_store] || Analytical::CommandStore.new\n      end\n\n      def protocol\n        @options[:ssl] ? 'https' : 'http'\n      end\n\n      #\n      # The core methods that most analytics services implement are listed below.\n      # Modules will ignore any calls that they don't respond to, allowing them to\n      # only partially implement this basic template (or implement their own arbitrary custom methods)\n      #\n\n      # This is used to record page-view events, where you want to pass a URL to an analytics service\n      # def track(*args)\n\n      # Identify provides a unique identifier to an analytics service to keep track of the current user\n      # id should be a unique string (depending on which service you use), and some services also\n      # make use of a data hash as a second parameters, containing :email=>'test@test.com', for instance\n      # def identify(id, *args)\n\n      # Event is meant to track important funnel conversions in your app, or other important events\n      # that you want to inform a funnel analytics service about.  You can pass optional data to this method as well.\n      # def event(name, *args)\n\n      # Set passes some data to the analytics service that should be attached to the current user identity\n      # It can be used to set AB-testing choices and other unique data, so that split testing results can be\n      # reported by an analytics service\n      # def set(data)\n\n      # This method generates the initialization javascript that an analytics service uses to track your site\n      # def init_javascript(location)\n\n      def queue(*args)\n        return if @options[:ignore_duplicates] && @command_store.include?(args)\n        if args.first==:identify\n          @command_store.unshift args\n        else\n          @command_store << args\n        end\n      end\n      def process_queued_commands\n        command_strings = @command_store.collect do |c|\n          send(*c) if respond_to?(c.first)\n        end.compact\n        @command_store.flush\n        command_strings\n      end\n\n      def init_location?(location)\n        if @tracking_command_location.is_a?(Array)\n          @tracking_command_location.map { |item|item.to_sym }.include?(location.to_sym)\n        else\n          @tracking_command_location.to_sym == location.to_sym\n        end\n      end\n\n      def init_location(location, &block)\n        if init_location?(location)\n          @initialized = true\n          if block_given?\n            yield\n          else\n            ''\n          end\n        else\n          ''\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/chartbeat.rb",
    "content": "module Analytical\n  module Modules\n    class Chartbeat\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = [:head_prepend, :body_append]\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          case location.to_sym\n            when :head_prepend\n            js = <<-HTML\n            <!-- Analytical Head Init: Chartbeat -->\n            <script type=\"text/javascript\">var _sf_startpt=(new Date()).getTime();</script>\n            HTML\n            js\n            when :body_append\n            js = <<-HTML\n            <!-- Analytical Body Init: Chartbeat -->\n            <script type=\"text/javascript\">\n              var _sf_async_config={};\n              /** CONFIGURATION START **/\n              _sf_async_config.uid = #{options[:key]};\n              _sf_async_config.domain = '#{options[:domain]}';\n              /** CONFIGURATION END **/\n              (function(){\n                function loadChartbeat() {\n                  window._sf_endpt=(new Date()).getTime();\n                  var e = document.createElement('script');\n                  e.setAttribute('language', 'javascript');\n                  e.setAttribute('type', 'text/javascript');\n                  e.setAttribute('src', '//static.chartbeat.com/js/chartbeat.js');\n                  document.body.appendChild(e);\n                }\n                var oldonload = window.onload;\n                window.onload = (typeof window.onload != 'function') ?\n                   loadChartbeat : function() { oldonload(); loadChartbeat(); };\n              })();\n              </script>\n            HTML\n            js\n            end\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/click_tale.rb",
    "content": "module Analytical\n  module Modules\n    class ClickTale\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = [:body_prepend, :body_append]\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          case location.to_sym\n            when :body_prepend\n              js = <<-HTML\n              <!-- Analytical Init: ClickTale Top part -->\n              <script type=\"text/javascript\">\n              var WRInitTime=(new Date()).getTime();\n              </script>\n              <!-- ClickTale end of Top part -->\n              HTML\n              js\n            when :body_append\n              js = <<-HTML\n              <!-- Analytical Init: ClickTale Bottom part -->\n          \t\t<div id=\"ClickTaleDiv\" style=\"display: none;\"></div>\n          \t\t<script type='text/javascript'>\n          \t\tdocument.write(unescape(\"%3Cscript%20src='\"+\n          \t\t (document.location.protocol=='https:'?\n          \t\t  'https://clicktale.pantherssl.com/':\n          \t\t  'http://s.clicktale.net/')+\n          \t\t \"#{@options[:script_name] || \"WRb6\"}.js'%20type='text/javascript'%3E%3C/script%3E\"));\n          \t\t</script>\n          \t\t<script type=\"text/javascript\">\n          \t\tvar ClickTaleSSL=1;\n          \t\tif(typeof ClickTale=='function') ClickTale(#{@options[:project_id]},#{@options[:site_traffic]},\\\"#{@options[:www_param] || 'www'}\\\");\n          \t\t</script>\n          \t\t<!-- ClickTale end of Bottom part -->\n              HTML\n              js\n          end\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/clicky.rb",
    "content": "module Analytical\n  module Modules\n    class Clicky\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Clicky -->\n          <script type=\"text/javascript\">\n            var clicky_site_ids = clicky_site_ids || [];\n            clicky_site_ids.push(#{@options[:key]});\n            (function() {\n              var s = document.createElement('script');\n              s.type = 'text/javascript';\n              s.async = true;\n              s.src = '//static.getclicky.com/js';\n              ( document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] ).appendChild( s );\n            })();\n          </script>\n          <noscript><p><img alt=\"Clicky\" width=\"1\" height=\"1\" src=\"//in.getclicky.com/#{@options[:key]}ns.gif\" /></p></noscript>\n          HTML\n\n          identify_commands = []\n          @command_store.commands.each do |c|\n            if c[0] == :identify\n              identify_commands << identify(*c[1..-1])\n            end\n          end\n          js = identify_commands.join(\"\\n\") + \"\\n\" + js\n          @command_store.commands = @command_store.commands.delete_if {|c| c[0] == :identify }\n\n          js\n        end\n      end\n\n      def track(*args)\n        \"clicky.log(\\\"#{args.first}\\\");\"\n      end\n\n      def identify(id, *args)\n        data = { :id=>id }.merge(args.first || {})\n        code = <<-HTML\n        <script type='text/javascript'>\n          var clicky_custom_session = #{data.to_json};\n        </script>\n        HTML\n        code\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/comscore.rb",
    "content": "module Analytical\n  module Modules\n    class Comscore\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :head_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: comScore -->\n          <script>document.write(unescape(\"%3Cscript src='\" + (document.location.protocol == \"https:\" ? \"https://sb\" : \"http://b\") + \".scorecardresearch.com/beacon.js' %3E%3C/script%3E\"));</script>\n          <script>COMSCORE.beacon({c1:2, c2:#{options[:key]}, c3:\"\", c4:\"\", c5:\"\", c6:\"\", c15:\"\"});</script>\n          <noscript><img src=\"http://b.scorecardresearch.com/p?c1=2&c2=#{options[:key]}&c3=&c4=&c5=&c6=&c15=&cj=1\" /></noscript>\n          HTML\n          js\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/console.rb",
    "content": "module Analytical\n  module Modules\n    class Console\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Console -->\n          <script type=\"text/javascript\">\n            if(typeof(console) !== 'undefined' && console != null) {\n              console.log('Analytical Init: Console');\n            }\n          </script>\n          HTML\n          js\n        end\n      end\n\n      def track(*args)\n        check_for_console <<-HERE\n        console.log(\"Analytical Track: \"+\"#{escape args.first}\");\n        HERE\n      end\n\n      def identify(id, *args)\n        data = args.first || {}\n        check_for_console <<-HERE\n        console.log(\"Analytical Identify: \"+\"#{escape id}\");\n        console.log(#{data.to_json});\n        HERE\n      end\n\n      def event(name, *args)\n        data = args.first || {}\n        check_for_console <<-HERE\n        console.log(\"Analytical Event: \"+\"#{escape name}\");\n        console.log(#{data.to_json});\n        HERE\n      end\n\n      def set(data)\n        check_for_console <<-HERE\n        console.log(\"Analytical Set: \");\n        console.log(#{data.to_json});\n        HERE\n      end\n\n      def alias_identity(old_identity,new_identity)\n        check_for_console <<-HERE\n        console.log(\"Analytical Alias: #{old_identity} => #{new_identity}\");\n        HERE\n      end\n\n      private\n\n      CONSOLE_JS_ESCAPE_MAP = {\n        '\\\\' => '\\\\\\\\',\n        '</' => '<\\/',\n        \"\\r\\n\" => '\\n',\n        \"\\n\" => '\\n',\n        \"\\r\" => '\\n',\n        '\"' => '\\\\\"',\n        \"'\" => \"\\\\'\"\n      } unless defined?(CONSOLE_JS_ESCAPE_MAP)\n\n      def escape(js)\n        js.to_s.gsub(/(\\\\|<\\/|\\r\\n|[\\n\\r\"'])/) { CONSOLE_JS_ESCAPE_MAP[$1] }\n      end\n\n      def check_for_console(data)\n        \"if(typeof(console) !== 'undefined' && console != null) { \\n#{data} }\"\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/crazy_egg.rb",
    "content": "module Analytical\n  module Modules\n    class CrazyEgg\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          code_url = \"#{options[:key].to_s[0,4]}/#{options[:key].to_s[4,4]}\"\n          js = <<-HTML\n          <!-- Analytical Init: CrazyEgg -->\n          <script type=\"text/javascript\" src=\"#{protocol}://s3.amazonaws.com/new.cetrk.com/pages/scripts/#{code_url}.js\"> </script>\n          HTML\n          js\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/gauges.rb",
    "content": "module Analytical\n  module Modules\n    class Gauges\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def track(*args)\n        \"_gauges.push(['track']);\"\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Gauges -->\n          <script type=\"text/javascript\">\n            var _gauges = _gauges || [];\n            (function() {\n              var t   = document.createElement('script');\n              t.type  = 'text/javascript';\n              t.async = true;\n              t.id    = 'gauges-tracker';\n              t.setAttribute('data-site-id', '#{options[:site_id]}');\n              t.src = '//secure.gaug.es/track.js';\n              var s = document.getElementsByTagName('script')[0];\n              s.parentNode.insertBefore(t, s);\n            })();\n          </script>\n          HTML\n          js\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/google.rb",
    "content": "module Analytical\n  module Modules\n    class Google\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :head_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Google -->\n          <script type=\"text/javascript\">\n            var _gaq = _gaq || [];\n            _gaq.push(['_setAccount', '#{options[:key]}']);\n            #{\"_gaq.push(['_setDomainName', '#{options[:domain]}']);\" if options[:domain]}\n            #{\"_gaq.push(['_setAllowLinker', true]);\" if options[:allow_linker]}\n            #{\"_gaq.push(['_setSiteSpeedSampleRate', #{options[:sample_rate]}]);\" if options[:sample_rate]}\n            #{\"_gaq.push(['_require', 'inpage_linkid', '//www.google-analytics.com/plugins/ga/inpage_linkid.js']);\" if options[:enhanced_link_attribution]}\n            _gaq.push(['_trackPageview']);\n            (function() {\n              var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;\n              ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';\n              var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);\n            })();\n          </script>\n          HTML\n          js\n        end\n      end\n\n      def track(*args)\n        \"_gaq.push(['_trackPageview'#{args.empty? ? ']' : ', \"' + args.first + '\"]'});\"\n      end\n      \n      def event(name, *args)\n        data = args.first || {}\n        data = data[:value] if data.is_a?(Hash)\n        data_string = !data.nil? ? \", #{data}\" : \"\"\n        \"_gaq.push(['_trackEvent', \\\"Event\\\", \\\"#{name}\\\"\" + data_string + \"]);\"\n      end\n      \n      def custom_event(category, action, opt_label=nil, opt_value=nil)\n        args = [category, action, opt_label, opt_value].compact\n        \"_gaq.push(\" + [ \"_trackEvent\", *args].to_json + \");\"\n      end\n\n\n      # http://code.google.com/apis/analytics/docs/tracking/gaTrackingCustomVariables.html\n      #\n      #_setCustomVar(index, name, value, opt_scope)\n      #\n      # index — The slot for the custom variable. Required. This is a number whose value can range from 1 - 5, inclusive.\n      #\n      # name —  The name for the custom variable. Required. This is a string that identifies the custom variable and appears in the top-level Custom Variables report of the Analytics reports.\n      #\n      # value — The value for the custom variable. Required. This is a string that is paired with a name.\n      #\n      # opt_scope — The scope for the custom variable. Optional. As described above, the scope defines the level of user engagement with your site.\n      # It is a number whose possible values are 1 (visitor-level), 2 (session-level), or 3 (page-level).\n      # When left undefined, the custom variable scope defaults to page-level interaction.\n      def set(data)\n        if data.is_a?(Hash) && data.keys.any?\n          index = data[:index].to_i\n          name  = data[:name ]\n          value = data[:value]\n          scope = data[:scope]\n          if (1..5).to_a.include?(index) && !name.nil? && !value.nil?\n            data = \"#{index}, '#{name}', '#{value}'\"\n            data += (1..3).to_a.include?(scope) ? \", #{scope}\" : \"\"\n            return \"_gaq.push(['_setCustomVar', #{ data }]);\"\n          end\n        end\n      end\n\n      # http://code.google.com/apis/analytics/docs/gaJS/gaJSApiEcommerce.html#_gat.GA_Tracker_._addTrans\n      # String orderId      Required. Internal unique order id number for this transaction.\n      # String affiliation  Optional. Partner or store affiliation (undefined if absent).\n      # String total        Required. Total dollar amount of the transaction.\n      # String tax          Optional. Tax amount of the transaction.\n      # String shipping     Optional. Shipping charge for the transaction.\n      # String city         Optional. City to associate with transaction.\n      # String state        Optional. State to associate with transaction.\n      # String country      Optional. Country to associate with transaction.\n      def add_trans(order_id, affiliation=nil, total=nil, tax=nil, shipping=nil, city=nil, state=nil, country=nil)\n        data = []\n        data << \"'#{order_id}'\"\n        data << \"'#{affiliation}'\"\n        data << \"'#{total}'\"\n        data << \"'#{tax}'\"\n        data << \"'#{shipping}'\"\n        data << \"'#{city}'\"\n        data << \"'#{state}'\"\n        data << \"'#{country}'\"\n\n        \"_gaq.push(['_addTrans', #{data.join(', ')}]);\"\n      end\n\n      # http://code.google.com/apis/analytics/docs/gaJS/gaJSApiEcommerce.html#_gat.GA_Tracker_._addItem\n      # String orderId  Optional Order ID of the transaction to associate with item.\n      # String sku      Required. Item's SKU code.\n      # String name     Required. Product name. Required to see data in the product detail report.\n      # String category Optional. Product category.\n      # String price    Required. Product price.\n      # String quantity Required. Purchase quantity.\n      def add_item(order_id, sku, name, category, price, quantity)\n        data  = \"'#{order_id}', '#{sku}', '#{name}', '#{category}', '#{price}', '#{quantity}'\"\n        \"_gaq.push(['_addItem', #{data}]);\"\n      end\n\n      # http://code.google.com/apis/analytics/docs/gaJS/gaJSApiEcommerce.html#_gat.GA_Tracker_._trackTrans\n      # Sends both the transaction and item data to the Google Analytics server.\n      # This method should be used in conjunction with the add_item and add_trans methods.\n      def track_trans\n        \"_gaq.push(['_trackTrans']);\"\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/google_optimizer.rb",
    "content": "module Analytical\n  module Modules\n    class GoogleOptimizer\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :head_prepend\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init:  Google Website Optimizer Control Script -->\n      \t\t<script>\n      \t\tfunction utmx_section(){}function utmx(){}\n      \t\t(function(){var k='#{options[:key]}',d=document,l=d.location,c=d.cookie;function f(n){\n      \t\tif(c){var i=c.indexOf(n+'=');if(i>-1){var j=c.indexOf(';',i);return c.substring(i+n.\n      \t\tlength+1,j<0?c.length:j)}}}var x=f('__utmx'),xx=f('__utmxx'),h=l.hash;\n      \t\td.write('<sc'+'ript src=\"'+\n      \t\t'http'+(l.protocol=='https:'?'s://ssl':'://www')+'.google-analytics.com'\n      \t\t+'/siteopt.js?v=1&utmxkey='+k+'&utmx='+(x?x:'')+'&utmxx='+(xx?xx:'')+'&utmxtime='\n      \t\t+new Date().valueOf()+(h?'&utmxhash='+escape(h.substr(1)):'')+\n      \t\t'\" type=\"text/javascript\" charset=\"utf-8\"></sc'+'ript>')})();\n      \t\t</script>\n      \t\t<!-- End of Google Website Optimizer Control Script -->\n      \t\t<!-- Analytical Init: Google Website Optimizer Tracking Script -->\n          <script type=\"text/javascript\">\n          if(typeof(_gat)!='object')document.write('<sc'+'ript src=\"http'+\n          (document.location.protocol=='https:'?'s://ssl':'://www')+\n          '.google-analytics.com/ga.js\"></sc'+'ript>')</script>\n          <script type=\"text/javascript\">\n          try {\n            var gwoTracker=_gat._getTracker(\"#{options[:account]}\");\n            gwoTracker._trackPageview(\"/#{options[:key]}/test\");\n            }catch(err){}</script>\n          <!-- End of Google Website Optimizer Tracking Script -->\n          HTML\n          js\n        end\n      end\n\n      def conversion(*args)\n        js = <<-HTML\n        <!-- Analytical Init:  Google Website Optimizer Conversion Script -->\n        <script type=\"text/javascript\">\n        if(typeof(_gat)!='object')document.write('<sc'+'ript src=\"http'+\n        (document.location.protocol=='https:'?'s://ssl':'://www')+\n        '.google-analytics.com/ga.js\"></sc'+'ript>')</script>\n        <script type=\"text/javascript\">\n        try {\n          var gwoTracker=_gat._getTracker(\"#{options[:account]}\");\n          gwoTracker._trackPageview(\"/#{options[:key]}/goal\");\n          }catch(err){}</script>\n        <!-- End of Google Website Optimizer Conversion Script -->\n        HTML\n        js\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/google_universal.rb",
    "content": "module Analytical\n  module Modules\n    class GoogleUniversal\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :head_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Google Universal -->\n          <script>\n            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n            ga('create', '#{options[:key]}', '#{options[:domain]}');\n            ga('send', 'pageview');\n\n          </script>          \n          HTML\n          js\n        end\n      end\n\n      def event(name, *args)\n        data = args.first || {}\n        data = data[:value] if data.is_a?(Hash)\n        data_string = !data.nil? ? \", #{data}\" : \"\"\n        \"ga('send', 'event', \\\"Event\\\", \\\"#{name}\\\"\" + data_string + \");\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/hubspot.rb",
    "content": "module Analytical\n  module Modules\n    class Hubspot\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        check_hub_id\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Hubspot -->\n          <script type=\"text/javascript\">\n            (function(d,s,i,r) {\n              if (d.getElementById(i)){return;}\n              var n=d.createElement(s),e=d.getElementsByTagName(s)[0];\n              n.id=i;n.src='//js.hubspot.com/analytics/'+(Math.ceil(new Date()/r)*r)+'/#{options[:hub_id]}.js';\n              e.parentNode.insertBefore(n, e);\n            })(document,\"script\",\"hs-analytics\",300000);\n          </script>\n          HTML\n          js\n        end\n      end\n\n      private\n\n      def check_hub_id\n        if options[:hub_id].nil?\n          raise ArgumentError, \"Hubspot tracking requires 'hub_id' to be set in the 'analytical.yml' config file; \" +\n            \"'portal_id' and 'domain' are no longer used.\"\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/kiss_metrics.rb",
    "content": "module Analytical\n  module Modules\n    class KissMetrics\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        check_js_url_key\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: KissMetrics -->\n          <script type=\"text/javascript\">\n            var _kmq = _kmq || [];\n            function _kms(u){\n              setTimeout(function(){\n                var s = document.createElement('script'); var f = document.getElementsByTagName('script')[0]; s.type = 'text/javascript'; s.async = true;\n                s.src = u; f.parentNode.insertBefore(s, f);\n              }, 1);\n            }\n            _kms('//i.kissmetrics.com/i.js');_kms('#{options[:js_url_key]}');\n          </script>\n          HTML\n          js\n        end\n      end\n\n      def identify(id, *args)\n        data = args.first || {}\n        km_id = data[:email] || id\n        \"_kmq.push([\\\"identify\\\", \\\"#{ km_id }\\\"]);\"\n      end\n\n      def event(name, *args)\n        data = args.first || {}\n        \"_kmq.push([\\\"record\\\", \\\"#{name}\\\", #{data.to_json}]);\"\n      end\n\n      def set(data)\n        return '' if data.blank?\n        \"_kmq.push([\\\"set\\\", #{data.to_json}]);\"\n      end\n\n      def alias_identity(old_identity, new_identity)\n        \"_kmq.push([\\\"alias\\\", \\\"#{old_identity}\\\", \\\"#{new_identity}\\\"]);\"\n      end\n\n    private\n\n      def check_js_url_key\n        if options[:js_url_key].nil?\n          raise \"You didn't provide a js_url_key for kiss_metrics. \" +\n            \"Add one to your analytical.yml file so KissMetrics will work.\"\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/loopfuse.rb",
    "content": "module Analytical\n  module Modules\n    class Loopfuse\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: LOOPFUSE TRACKING -->\n          <script type=\"text/javascript\">\n          var LFHost = ((\"https:\" == document.location.protocol) ? \"https://\" : \"http://\");\n          document.write(unescape(\"%3Cscript src='\" + LFHost + \"lfov.net/webrecorder/js/listen.js' type='text/javascript'%3E%3C/script%3E\"));\n          </script>\n          <script type=\"text/javascript\">\n          _lf_cid = \"#{options[:cid]}\";\n          _lf_remora();\n          </script>\n      \t\t<!-- END: LOOPFUSE TRACKING -->\n          HTML\n          js\n        end\n      end\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/microsoft_ads.rb",
    "content": "module Analytical\n  module Modules\n    class MicrosoftAds\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          @initializing = true\n          html = \"<!-- Analytical Init: Microsoft Ads -->\\n\"\n          event_commands = []\n          @command_store.commands.each do |c|\n            if c[0] == :event\n              event_commands << event(*c[1..-1])\n            end\n          end\n          html += event_commands.join(\"\\n\")\n          @command_store.commands = @command_store.commands.delete_if {|c| c[0] == :event }\n          @initializing = false\n\n          html\n        end\n      end\n\n      #\n      # Define conversion events in analytical.yml like:\n      #\n      # microsoft_ads:\n      #   'Some Event':\n      #     id: 55555555-6666-7777-8888-111111111111\n      def event(name, *args)\n        return '' unless @initializing\n\n        data = args.first || {}\n        if conversion = options[name.to_sym]\n          conversion.symbolize_keys!\n          js = <<-HTML\n          <script type=\"text/javascript\">if (!window.mstag) mstag = {loadTag : function(){},time : (new Date()).getTime()};</script>\n          <script id=\"mstag_tops\" type=\"text/javascript\" src=\"//flex.atdmt.com/mstag/site/#{conversion[:id]}/mstag.js\"></script>\n          <script type=\"text/javascript\"> mstag.loadTag(\"conversion\", {cp:\"5050\",dedup:\"1\"})</script>\n          <noscript>\n            <iframe src=\"//flex.atdmt.com/mstag/tag/#{conversion[:id]}/conversion.html?cp=5050&dedup=1\" frameborder=\"0\" scrolling=\"no\" width=\"1\" height=\"1\" style=\"visibility:hidden; display:none\"></iframe>\n          </noscript>\n          HTML\n          js\n        else\n          \"<!-- No Microsoft Ads Conversion for: #{name} -->\"\n        end\n      end\n\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/analytical/modules/mixpanel.rb",
    "content": "module Analytical\n  module Modules\n    class Mixpanel\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      # Mixpanel-specific queueing behavior, overrides Base#queue\n      def queue(*args)\n        return if @options[:ignore_duplicates] && @command_store.include?(args)\n        if args.first==:alias_identity\n          @command_store.unshift args\n        elsif args.first==:identify\n          if @command_store.empty?\n            @command_store.unshift args\n          else\n            first_command = @command_store.first\n            first_command = first_command.first if first_command.respond_to?(:first)\n            @command_store.unshift args unless first_command == :alias_identity\n          end\n        else\n          @command_store << args\n        end\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Mixpanel -->\n          <script type=\"text/javascript\">\n            (function(c,a){window.mixpanel=a;var b,d,h,e;b=c.createElement(\"script\");\n            b.type=\"text/javascript\";b.async=!0;b.src=(\"https:\"===c.location.protocol?\"https:\":\"http:\")+\n            '//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';d=c.getElementsByTagName(\"script\")[0];\n            d.parentNode.insertBefore(b,d);a._i=[];a.init=function(b,c,f){function d(a,b){\n            var c=b.split(\".\");2==c.length&&(a=a[c[0]],b=c[1]);a[b]=function(){a.push([b].concat(\n            Array.prototype.slice.call(arguments,0)))}}var g=a;\"undefined\"!==typeof f?g=a[f]=[]:\n            f=\"mixpanel\";g.people=g.people||[];h=['disable','track','track_pageview','track_links',\n            'track_forms','register','register_once','unregister','identify','alias','name_tag',\n            'set_config','people.set','people.increment','people.track_charge','people.append'];\n            for(e=0;e<h.length;e++)d(g,h[e]);a._i.push([b,c,f])};a.__SV=1.2;})(document,window.mixpanel||[]);\n            var config = { track_pageview: #{options.fetch(:track_pageview, true)} };\n            mixpanel.init(\"#{options[:key]}\", config);\n          </script>\n          HTML\n          js\n        end\n      end\n\n      \n      # Examples:\n      #     analytical.track(url_viewed)\n      #     analytical.track(url_viewed, 'page name' => @page_title)\n      #     analytical.track(url_viewed, :event => 'pageview')\n      #\n      # By default, this module tracks all pageviews under a single Mixpanel event\n      # named 'page viewed'. This follows a recommendation in the Mixpanel docs for\n      # minimizing the number of distinct events you log, thus keeping your event data uncluttered.\n      #\n      # The url is followed by a Hash parameter that contains any other custom properties \n      # you want logged along with the pageview event. The following Hash keys get special treatment:\n      # * :callback => String representing javascript function to callback\n      # * :event => overrides the default event name for pageviews\n      # * :url => gets assigned the url you pass in\n      #\n      # Mixpanel docs also recommend specifying a 'page name' property when tracking pageviews.\n      #\n      # To turn off pageview tracking for Mixpanel entirely, initialize analytical as follows: \n      #        analytical( ... mixpanel: { key: ENV['MIXPANEL_KEY'], track_pageview: false } ... )\n      def track(*args)\n        return if args.empty?\n        url = args.first\n        properties = args.extract_options!\n        callback = properties.delete(:callback) || \"function(){}\"\n        event = properties.delete(:event) || 'page viewed'\n        if options[:track_pageview] != false\n          properties[:url] = url\n          # Docs recommend: mixpanel.track('page viewed', {'page name' : document.title, 'url' : window.location.pathname});\n          %(mixpanel.track(\"#{event}\", #{properties.to_json}, #{callback});)\n        end          \n      end\n\n      # Used to set \"Super Properties\" - http://mixpanel.com/api/docs/guides/super-properties\n      def set(properties)\n        \"mixpanel.register(#{properties.to_json});\"\n      end\n\n      def identify(id, *args)\n        opts = args.first || {}\n        name = opts.is_a?(Hash) ? opts[:name] : \"\"\n        name_str = name.blank? ? \"\" : \" mixpanel.name_tag('#{name}');\"\n        %(mixpanel.identify('#{id}');#{name_str})\n      end\n\n      # See https://mixpanel.com/docs/integration-libraries/using-mixpanel-alias\n      # For consistency with KissMetrics this method accepts two parameters.\n      # However, the first parameter is ignored because Mixpanel doesn't need it;\n      # pass any value for the first parameter, e.g. nil.\n      def alias_identity(_, new_identity)\n        %(mixpanel.alias(\"#{new_identity}\");)\n      end\n\n      def event(name, attributes = {})\n        %(mixpanel.track(\"#{name}\", #{attributes.to_json});)\n      end\n      \n      def person(attributes = {})\n        %(mixpanel.people.set(#{attributes.to_json});)\n      end\n      \n      def revenue(charge, attributes = {})\n        %(mixpanel.people.track_charge(#{charge}, #{attributes.to_json});)\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/modules/mouseflow.rb",
    "content": "module Analytical\n  module Modules\n    class Mouseflow\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        check_js_url\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Mouseflow -->\n          <script type=\"text/javascript\">\n             var _mfq = _mfq || [];\n             (function() {\n                 var mf = document.createElement(\"script\"); mf.type = \"text/javascript\"; mf.async = true;\n                 mf.src = \"#{options[:js_url]}\";\n                 document.getElementsByTagName(\"head\")[0].appendChild(mf);\n             })();\n          </script>\n          HTML\n          js\n        end\n      end\n\n    private\n\n      def check_js_url\n        if options[:js_url].nil?\n          raise \"You didn't provide a js_url for mouseflow. \" +\n            \"Add one to your analytical.yml file so Mouseflow will work.\"\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/optimizely.rb",
    "content": "module Analytical\n  module Modules\n    class Optimizely\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :head_prepend\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Optimizely -->\n          <script type=\"text/javascript\">document.write(unescape(\"%3Cscript src='\"+document.location.protocol+\"//cdn.optimizely.com/js/#{options[:key]}.js' type='text/javascript'%3E%3C/script%3E\"));</script>\n          HTML\n          js\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/performancing.rb",
    "content": "module Analytical\n  module Modules\n    class Performancing\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Performancing Metrics -->\n      \t\t<script type=\"text/javascript\">\n      \t\tvar clicky = { log: function(){ return; }, goal: function(){ return; }};\n      \t\tvar clicky_site_id = #{options[:site_id]};\n      \t\t(function() {\n      \t\t  var s = document.createElement('script');\n      \t\t  s.type = 'text/javascript';\n      \t\t  s.async = true;\n      \t\t  s.src = ( document.location.protocol == 'https:' ? 'https://pmetrics.performancing.com' : 'http://pmetrics.performancing.com' ) + '/js';\n      \t\t  ( document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] ).appendChild( s );\n      \t\t})();\n      \t\t</script>\n      \t  <!-- End Performancing Metrics -->\n          HTML\n          js\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/quantcast.rb",
    "content": "module Analytical\n  module Modules\n    class Quantcast\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = [:head_append, :body_append]\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          case location.to_sym\n            when :head_append\n            js = <<-HTML\n            <!-- Analytical Head Init: Quantcast -->\n            <script type=\"text/javascript\">\n              var _qevents = _qevents || [];\n\n              (function() {\n               var elem = document.createElement('script');\n\n               elem.src = (document.location.protocol == \"https:\" ? \"https://secure\" : \"http://edge\") + \".quantserve.com/quant.js\";\n               elem.async = true;\n               elem.type = \"text/javascript\";\n               var scpt = document.getElementsByTagName('script')[0];\n               scpt.parentNode.insertBefore(elem, scpt);\n              })();\n            </script>\n            HTML\n            js\n            when :body_append\n            js = <<-HTML\n            <!-- Analytical Body Init: Quantcast -->\n            <script type=\"text/javascript\">\n              _qevents.push( { qacct:\"#{options[:key]}\"} );\n              </script>\n              <noscript>\n              <div style=\"display: none;\"><img src=\"//pixel.quantserve.com/pixel/#{options[:key]}.gif\" height=\"1\" width=\"1\" alt=\"Quantcast\"/></div>\n              </noscript>\n            HTML\n            js\n            end\n        end\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/reinvigorate.rb",
    "content": "module Analytical\n  module Modules\n    class Reinvigorate\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :body_append\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Reinvigorate -->\n          <script type=\"text/javascript\">\n            document.write(unescape(\"%3Cscript src='\" + ((\"https:\" == document.location.protocol) ? \"https://ssl-\" : \"http://\")\n            + \"include.reinvigorate.net/re_.js' type='text/javascript'%3E%3C/script%3E\"));\n          </script>\n          HTML\n          js\n        end\n      end\n\n      def identify(id, *args)\n        data = args.first || {}\n        \"var re_name_tag = \\\"#{id}\\\";\"\n      end\n\n      def context(data)\n        return '' if data.blank?\n        if data[:email]\n          \"var re_context_tag = \\\"mailto:#{data[:email]}\\\";\"\n        elsif data[:url]\n          \"var re_context_tag = \\\"http://#{data[:url]}\\\";\"\n        else\n          \"var re_context_tag = \\\"#{data.first.last}\\\";\"\n        end\n      end\n\n      def track(data, *args)\n        \"try {\n          reinvigorate.track(\\\"#{options[:key]}\\\");\n        } catch(err) {}\"\n      end\n\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/modules/segment_io.rb",
    "content": "module Analytical\n  module Modules\n    class SegmentIo\n      include Analytical::Modules::Base\n\n      def initialize(options={})\n        super\n        @tracking_command_location = :head_prepend\n      end\n\n      def init_javascript(location)\n        init_location(location) do\n          js = <<-HTML\n          <!-- Analytical Init: Segment.io -->\n          <script type=\"text/javascript\">\n            window.analytics=window.analytics||[];window.analytics.load=function(apiKey){var script=document.createElement('script');script.type='text/javascript';script.async=true;script.src=('https:'===document.location.protocol?'https://':'http://')+'d2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/'+apiKey+'/analytics.min.js';var firstScript=document.getElementsByTagName('script')[0];firstScript.parentNode.insertBefore(script,firstScript);var methodFactory=function(type){return function(){window.analytics.push([type].concat(Array.prototype.slice.call(arguments,0)))}};var methods=['identify','track','trackLink','trackForm','trackClick','trackSubmit','pageview','ab','alias','ready'];for(var i=0;i<methods.length;i++){window.analytics[methods[i]]=methodFactory(methods[i])}};window.analytics.load('#{options[:key]}');\n          </script>\n          HTML\n          js\n        end\n      end\n\n      def track(*args)\n        if args.any?\n          %(window.analytics.pageview(\"#{args.first}\");)\n        else\n          %(window.analytics.pageview();)\n        end\n      end\n\n      def identify(id, attributes = {})\n        %(window.analytics.identify(\"#{id}\", #{attributes.to_json});)\n      end\n\n      def event(name, attributes = {})\n        %(window.analytics.track(\"#{name}\", #{attributes.to_json});)\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/analytical/rails/engine.rb",
    "content": "if defined?(::Rails::Engine)\n  module Analytical\n    module Rails\n      class Engine < ::Rails::Engine\n        # auto wire\n      end\n    end\n  end\nend"
  },
  {
    "path": "lib/analytical/session_command_store.rb",
    "content": "module Analytical\n  class SessionCommandStore\n    attr_reader :session, :module_key\n\n    def initialize(session, module_key, initial_list=nil)\n      @session = session\n      @module_key = module_key\n      @session_key = ('analytical_'+module_key.to_s).to_sym\n      ensure_session_setup!(initial_list)\n    end\n\n    def assign(v)\n      self.commands = v\n    end\n\n    def commands\n      @session[@session_key]\n    end\n    def commands=(v)\n      @session[@session_key] = v\n    end\n\n    def flush\n      self.commands = []\n    end\n\n    # Pass any array methods on to the internal array\n    def method_missing(method, *args, &block)\n      commands.send(method, *args, &block)\n    end\n\n    private\n\n    def ensure_session_setup!(initial_list=nil)\n      self.commands ||= (initial_list || [])\n    end\n\n  end\nend"
  },
  {
    "path": "lib/analytical.rb",
    "content": "require File.dirname(__FILE__)+'/analytical/rails/engine'\nrequire File.dirname(__FILE__)+'/analytical/modules/base'\nDir.glob(File.dirname(__FILE__)+'/analytical/**/*.rb').each do |f|\n  require f\nend\n\nmodule Analytical\n\n  def analytical(options={})\n    self.analytical_options = options\n\n    config_options = { :modules => [] }\n    File.open(\"#{::Rails.root}/config/analytical.yml\") do |f|\n      file_options = YAML::load(ERB.new(f.read).result).symbolize_keys\n      env = (::Rails.env || :production).to_sym\n      file_options = file_options[env] if file_options.has_key?(env)\n      file_options.each do |k, v|\n        if v.respond_to?(:symbolize_keys)\n          # module configuration\n          config_options[k.to_sym] = v.symbolize_keys\n          config_options[:modules] << k.to_sym unless options && options[:modules]\n        else\n          # regular option\n          config_options[k.to_sym] = v\n        end\n      end if file_options\n    end if File.exists?(\"#{::Rails.root}/config/analytical.yml\")\n\n    self.analytical_options = self.analytical_options.reverse_merge config_options\n  end\n\n  module InstanceMethods\n    def analytical\n      @analytical ||= begin\n        options = self.class.analytical_options.merge({\n          :ssl => request.ssl?,\n          :controller => self,\n        })\n        if options[:modules] && (options[:modules].is_a?(Hash) || options[:modules].is_a?(Proc))\n          items = options[:modules].is_a?(Hash) ? options[:modules] : options[:modules].call(self)\n          modules = []\n          items.each do |k,v|\n            options[k.to_sym] = v.symbolize_keys\n            modules << k.to_sym\n          end\n          options[:modules] = modules\n        end\n        if options[:disable_if] && options[:disable_if].call(self)\n          options[:modules] = []\n        end\n        options[:session] = session if options[:use_session_store]\n        if analytical_is_robot?(request.user_agent, options[:user_agent_whitelist] || [])\n          options[:modules] = []\n        end\n        options[:modules] = options[:filter_modules].call(self, options[:modules]) if options[:filter_modules]\n        options[:javascript_helpers] ||= true if options[:javascript_helpers].nil?\n        Analytical::Api.new options\n      end\n    end\n  end\n\n  module HelperMethods\n    def analytical\n      controller.analytical\n    end\n  end\n\nend\n\nif defined?(ActionController::Base)\n  ActionController::Base.class_eval do\n    extend Analytical\n    include Analytical::InstanceMethods\n    include Analytical::BotDetector\n    helper Analytical::HelperMethods\n\n    if ::Rails::VERSION::MAJOR < 3\n      class_inheritable_accessor :analytical_options\n    else\n      class_attribute :analytical_options\n    end\n  end\nend\n"
  },
  {
    "path": "rails/init.rb",
    "content": "require 'analytical'\n"
  },
  {
    "path": "spec/analytical/api_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')\n\ndescribe \"Analytical::Api\" do\n\n  describe 'on initialization' do\n    it 'should construct an api class for each module' do\n      Analytical::Modules::Console.should_receive(:new).and_return(@console = mock('console'))\n      Analytical::Modules::Google.should_receive(:new).and_return(@google = mock('google'))\n      a = Analytical::Api.new :modules=>[:console, :google]\n      a.modules.should == {\n        :console=>@console,\n        :google=>@google,\n      }\n    end\n    it 'should pass the ssl option on to the module constructor' do\n      Analytical::Modules::Console.should_receive(:new).with(hash_including(:ssl=>true)).and_return(@console = mock('console'))\n      Analytical::Api.new :modules=>[:console], :ssl=>true\n    end\n    describe 'with a session option' do\n      before(:each) do\n        @session = {}\n      end\n      it 'should create a new SessionCommandStore for each module' do\n        Analytical::SessionCommandStore.should_receive(:new).with(@session, :console).and_return(@console_store = mock('console_store'))\n        Analytical::SessionCommandStore.should_receive(:new).with(@session, :google).and_return(@google_store = mock('google_store'))        \n        Analytical::Modules::Console.should_receive(:new).with(:session_store=>@console_store, :session=>@session).and_return(mock('console'))\n        Analytical::Modules::Google.should_receive(:new).with(:session_store=>@google_store, :session=>@session).and_return(mock('google'))\n        Analytical::Api.new :modules=>[:console, :google], :session=>@session\n      end\n    end\n  end\n\n  describe 'with modules' do\n    before(:each) do\n      Analytical::Modules::Console.stub!(:new).and_return(@console = mock('console'))\n      Analytical::Modules::Google.stub!(:new).and_return(@google = mock('google'))\n      Analytical::Modules::Clicky.stub!(:new).and_return(@clicky = mock('clicky'))\n      Analytical::Modules::Chartbeat.stub!(:new).and_return(@chartbeat = mock('chartbeat'))\n\n      @api = Analytical::Api.new :modules=>[:console, :google]\n    end\n\n    describe '#track' do\n      it 'should store the #track command for each module api class' do\n        @api = Analytical::Api.new :modules=>[:console, :google, :clicky]\n\n        @console.should_receive(:queue).with(:track, 'something', {:a=>1, :b=>2})\n        @clicky.should_receive(:queue).with(:track, 'something', {:a=>1, :b=>2})\n        @google.should_receive(:queue).with(:track, 'something', {:a=>1, :b=>2})\n\n        @api.track('something', {:a=>1, :b=>2})\n      end\n    end\n\n    describe '#identify' do\n      it 'should store the #track command for each module api class' do\n        @api = Analytical::Api.new :modules=>[:console, :google, :clicky]\n\n        @console.should_receive(:queue).with(:identify, 'something', {:a=>1, :b=>2})\n        @clicky.should_receive(:queue).with(:identify, 'something', {:a=>1, :b=>2})\n        @google.should_receive(:queue).with(:identify, 'something', {:a=>1, :b=>2})\n\n        @api.identify('something', {:a=>1, :b=>2})\n      end\n    end\n\n    describe '#now' do\n      it 'should call a command on each module and collect the results' do\n        @api = Analytical::Api.new :modules=>[:console, :google, :clicky]\n\n        @console.should_receive(:track).with('something', {:a=>1, :b=>2}).and_return('console track')\n        @clicky.should_receive(:track).with('something', {:a=>1, :b=>2}).and_return('clicky track')\n        @google.should_receive(:respond_to?).with(:track).and_return(false)\n        @google.should_not_receive(:track)\n\n        @api.now.track('something', {:a=>1, :b=>2}).should == \"console track\\nclicky track\"\n      end\n    end\n\n    describe 'when accessing a module by name' do\n      it 'should return the module api object' do\n        @api = Analytical::Api.new :modules=>[:console, :google, :clicky, :chartbeat]\n        @api.console.should == @console\n        @api.clicky.should == @clicky\n        @api.google.should == @google\n        @api.chartbeat.should == @chartbeat\n      end\n    end\n\n    describe 'gathering javascript' do\n      before(:each) do\n        @console.stub!(:init_location?).and_return(false)\n        @console.stub!(:initialized).and_return(false)\n        @console.stub!(:process_queued_commands).and_return([])\n        @google.stub!(:init_location?).and_return(false)\n        @google.stub!(:initialized).and_return(false)\n        @google.stub!(:process_queued_commands).and_return([])\n      end\n\n      describe '#head_prepend_javascript' do\n        it 'should return the javascript' do\n          @console.should_receive(:init_javascript).with(:head_prepend).and_return('console_a')\n          @google.should_receive(:init_javascript).with(:head_prepend).and_return('google_a')\n          @api.head_prepend_javascript.should == \"console_agoogle_a\"\n        end\n      end\n\n      describe '#head_append_javascript' do\n        it 'should return the javascript' do\n          @console.should_receive(:init_javascript).with(:head_append).and_return('console_a')\n          @google.should_receive(:init_javascript).with(:head_append).and_return('google_a')\n          @api.head_append_javascript.should == \"console_agoogle_a\"\n        end\n        it 'should render an existing template for Rails 3.0' do\n          @api.options[:javascript_helpers] = true\n          (@api.options[:controller] ||= Object.new).stub!(:render_to_string) { |param| param[:file] }\n          File.exist?(@api.head_append_javascript).should be_true\n        end\n        it 'should not render an existing template if javascript_helpers is false' do\n          @api.options[:javascript_helpers] = false\n          (@api.options[:controller] ||= Object.new).should_not_receive(:render_to_string)\n          @api.head_append_javascript.should be_blank\n        end\n      end\n      \n      describe '#body_prepend_javascript' do\n        it 'should return the javascript' do\n          @console.should_receive(:init_javascript).with(:body_prepend).and_return('console_b')\n          @google.should_receive(:init_javascript).with(:body_prepend).and_return('google_b')\n          @api.body_prepend_javascript.should == \"console_bgoogle_b\"\n        end\n      end\n      describe '#body_append_javascript' do\n        it 'should return the javascript' do\n          @console.should_receive(:init_javascript).with(:body_append).and_return('console_c')\n          @google.should_receive(:init_javascript).with(:body_append).and_return('google_c')\n          @api.body_append_javascript.should == \"console_cgoogle_c\"\n        end\n      end\n      describe 'with stored commands' do\n        before(:each) do\n          @console.stub!(:init_location?).and_return(true)\n          @console.stub!(:initialized).and_return(false)\n          @console.stub!(:track).and_return('console track called')\n          @console.stub!(:queue)\n          @console.stub!(:process_queued_commands).and_return(['console track called'])\n          @google.stub!(:init_location?).and_return(false)\n          @google.stub!(:initialized).and_return(true)\n          @google.stub!(:track).and_return('google track called')\n          @google.stub!(:queue)\n          @google.stub!(:process_queued_commands).and_return(['google track called'])\n          @api.track('something', {:a=>1, :b=>2})\n        end\n        describe '#body_prepend_javascript' do\n          it 'should return the javascript' do\n            @console.should_receive(:init_javascript).with(:body_prepend).and_return('console_b')\n            @google.should_receive(:init_javascript).with(:body_prepend).and_return('google_b')\n            @api.body_prepend_javascript.should == \"console_bgoogle_b\\n<script type='text/javascript'>\\nconsole track called\\ngoogle track called\\n</script>\"\n          end\n        end\n      end\n    end\n  end\n\n\n\nend\n"
  },
  {
    "path": "spec/analytical/bot_detector_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')\n\ndescribe \"Analytical::BotDetector\" do\n  class DummyForBotDetector\n    include Analytical::BotDetector\n  end\n  before(:each) do\n    @d = DummyForBotDetector.new\n  end\n  \n  describe 'with nil user_agent' do\n    it 'should return false' do\n      @d.analytical_is_robot?(nil).should be_false\n    end\n  end\n\n  describe 'with empty user_agent' do\n    it 'should return false' do\n      @d.analytical_is_robot?('').should be_false\n    end\n  end\n  \n  describe 'with whitelist user_agent' do\n    it 'should return false' do\n      @d.analytical_is_robot?('whitelist', ['whitelist']).should be_false\n    end\n  end\n\n  describe 'with whitelist user_agent' do\n    it 'should return true' do\n      @d.analytical_is_robot?('whitelist').should be_true\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/command_store_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Analytical::CommandStore do\n  \n  describe 'when behaving exactly like an array' do\n    it 'should add elements' do\n      @store = Analytical::CommandStore.new ['a']\n      @store << 'b'\n      @store.commands.should == ['a', 'b']\n    end\n    it 'should unshift elements' do\n      @store = Analytical::CommandStore.new ['b']\n      @store.unshift 'a'\n      @store.commands.should == ['a', 'b']\n    end\n    it 'should iterate over elements' do\n      @store = Analytical::CommandStore.new ['a', 'b']\n      @store.each do |elem|\n        ['a', 'b'].include?(elem).should be_true\n      end\n    end\n    it 'should have size' do\n      @store = Analytical::CommandStore.new ['a', 'b']\n      @store.size.should == 2\n    end\n  end\n  \n  describe 'when flushing' do\n    it 'should empty the list' do\n      @store = Analytical::CommandStore.new ['a', 'b']\n      @store.flush\n      @store.size.should == 0\n    end\n  end\n  \nend\n"
  },
  {
    "path": "spec/analytical/modules/adroll_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Adroll\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:Adroll=>{:key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Adroll.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :body_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Adroll.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Adroll.new :parent=>@parent, :adv_id=>'abcdef', :pix_id=>'123456'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_append).should =~ /abcdef/\n      @api.init_javascript(:body_append).should =~ /123456/\n      @api.init_javascript(:body_append).should =~ /adroll\\.com/\n    end\n  end\nend"
  },
  {
    "path": "spec/analytical/modules/adwords_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Adwords\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:google=>{:key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Adwords.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :body_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Adwords.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#event' do\n    it 'should return an empty string' do\n      @api = Analytical::Modules::Adwords.new :parent=>@parent, :key=>'abcdef'\n      @api.event('Nothing', {:some=>'data'}).should == ''\n    end\n  end\n  describe '#init_javascript' do\n    describe 'when a matching conversion event is not found' do\n      it 'should return the event js' do\n        @api = Analytical::Modules::Adwords.new :parent=>@parent, :key=>'abcdef'\n        @api.queue :event, 'Nothing', {:some=>'data'}\n        @api.init_javascript(:body_append).should.should =~ /<!-- No Adwords Conversion for: Nothing -->/\n      end\n    end\n    describe 'for a given conversion event' do\n      before(:each) do\n        @api = Analytical::Modules::Adwords.new :parent=>@parent, :key=>'abcdef', :'TheBigEvent'=>{\n          :id=>'55555',\n          :language=>'en',\n          :format=>'2',\n          :color=>'ffffff',\n          :label=>'abcdef',\n          :value=>'0',\n        }\n      end\n      it 'should return the event js' do\n        @api.queue :event, 'TheBigEvent'\n        body_append = @api.init_javascript(:body_append).should\n        body_append.should =~ /google_conversion_id = 55555/m\n        body_append.should =~ /google_conversion_language = \"en\";/m\n        body_append.should =~ /google_conversion_format = \"2\";/m\n        body_append.should =~ /google_conversion_color = \"ffffff\";/m\n        body_append.should =~ /google_conversion_label = \"abcdef\";/m\n        body_append.should =~ /google_conversion_value = 0;/m\n        body_append.should =~ /http:\\/\\/www.googleadservices.com\\/pagead\\/conversion.js/m\n      end\n      describe 'with a custom value' do\n        it 'should return the event js' do\n          @api.queue :event, 'TheBigEvent', :value=>666.66\n          body_append = @api.init_javascript(:body_append).should\n          body_append.should =~ /google_conversion_value = 666.66;/m\n        end\n      end\n      describe 'for https' do\n        it 'should return the event js' do\n          @api = Analytical::Modules::Adwords.new :parent=>@parent, :key=>'abcdef', :ssl=>true, :'TheBigEvent'=>{\n            :id=>'55555',\n            :language=>'en',\n            :format=>'2',\n            :color=>'ffffff',\n            :label=>'abcdef',\n            :value=>'0',\n          }\n          @api.queue :event, 'TheBigEvent'\n          @api.init_javascript(:body_append).should =~ /https:\\/\\/www.googleadservices.com\\/pagead\\/conversion.js/\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/base_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe Analytical::Modules::Base do\n\n  class BaseApiDummy\n    include Analytical::Modules::Base\n  end\n\n  describe '#protocol' do\n    before(:each) do\n      @api = BaseApiDummy.new(:parent=>mock('parent'))\n    end\n    describe 'with :ssl=>true option' do\n      it 'should return https' do\n        @api = BaseApiDummy.new(:parent=>mock('parent'), :ssl=>true)\n        @api.protocol.should == 'https'\n      end\n    end\n    it 'should return http' do\n      @api.protocol.should == 'http'\n    end\n  end\n\n  describe '#queue' do\n    before(:each) do\n      @api = BaseApiDummy.new(:parent=>mock('parent'))\n      @api.command_store.commands = [:a, :b, :c]\n    end\n    describe 'with an identify command' do\n      it 'should store it at the head of the command list' do\n        @api.queue :identify, 'someone', {:some=>:args}\n        @api.command_store.commands.should == [[:identify, 'someone', {:some=>:args}], :a, :b, :c]\n      end\n    end\n    describe 'with any other command' do\n      it 'should store it at the end of the command list' do\n        @api.queue :other, {:some=>:args}\n        @api.command_store.commands.should == [:a, :b, :c, [:other, {:some=>:args}]]\n      end\n    end\n    describe 'ignoring duplicates' do\n      before(:each) do\n        @api = BaseApiDummy.new(:parent=>mock('parent'), :ignore_duplicates=>true)\n        @api.command_store.commands = [[:a]]\n      end\n      it 'should store only unique commands' do\n        @api.queue :other, {:some=>:args}\n        @api.queue :a\n        @api.queue :other, {:some=>:args}\n        @api.queue :b\n        @api.command_store.commands.should == [[:a], [:other, {:some=>:args}], [:b]]\n      end\n    end\n  end\n\n  describe '#process_queued_commands' do\n    before(:each) do\n      @api = BaseApiDummy.new(:parent=>mock('parent'))\n      @api.command_store.commands = [[:a, 1, 2, 3], [:b, {:some=>:args}]]\n      @api.stub!(:a).and_return('a')\n      @api.stub!(:b).and_return('b')\n    end\n    it 'should send each of the args arrays in the command list' do\n      @api.should_receive(:a).with(1, 2, 3).and_return('a')\n      @api.should_receive(:b).with({:some=>:args}).and_return('b')\n      @api.process_queued_commands\n    end\n    it 'should return the results as an array' do\n      @api.process_queued_commands.should == ['a', 'b']\n    end\n    it 'should clear the commands list' do\n      @api.process_queued_commands\n      @api.command_store.commands == []\n    end\n    it \"should not store an unrecognized command\" do\n      @api.command_store.commands << [:c, 1]\n      @api.process_queued_commands.should == ['a','b']\n    end\n  end\n\n  describe '#init_location?' do\n    before(:each) do\n      @api = BaseApiDummy.new(:parent=>mock('parent'))\n      @api.instance_variable_set '@tracking_command_location', :my_location\n    end\n    describe 'when the command location matches the init location' do\n      it 'should return true' do\n        @api.init_location?(:my_location).should be_true\n      end\n    end\n    describe 'when the command location does not match the init location' do\n      it 'should return false' do\n        @api.init_location?(:not_my_location).should be_false\n      end\n    end\n  end\n\n  describe '#init_location' do\n    before(:each) do\n      @api = BaseApiDummy.new\n    end\n    it 'should check for the init_location' do\n      @api.should_receive(:init_location?).with(:some_location).and_return(false)\n      @api.init_location(:some_location)\n    end\n    describe 'for a valid init location' do\n      before(:each) { @api.stub!(:init_location?).and_return(true) }\n      it 'should set initialized to true' do\n        @api.init_location(:some_location)\n        @api.initialized.should be_true\n      end\n      it 'should yield to the block and return its result' do\n        @api.init_location(:some_location) do\n          'result from the block'\n        end.should == 'result from the block'\n      end\n    end\n    describe 'not for an init location' do\n      before(:each) do\n        @api.stub!(:init_location?).and_return(false)\n      end\n      it 'should return an empty string' do\n        @api.init_location(:not_my_init_location).should == ''\n      end\n    end\n  end\n\n  describe 'with a custom session_store' do\n    before(:each) do\n      @session = {}\n      @store = Analytical::SessionCommandStore.new @session, :some_module\n    end\n    it 'should use the session_store' do\n      @api = BaseApiDummy.new :session_store=>@store\n      @api.command_store.should == @store\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/chartbeat_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Chartbeat\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:chartbeat=>{:key=>1234, :domain =>'test.com'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Chartbeat.new :parent=>@parent, :key=>1234\n      a.tracking_command_location.should == [:head_prepend, :body_append]\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Chartbeat.new :parent=>@parent, :key=>12345, :domain =>'abcdef.com'\n      a.options.should == {:key=>12345, :domain => 'abcdef.com', :parent=>@parent}\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Chartbeat.new :parent=>@parent, :key=>12345, :domain =>'abcdef.com'\n      @api.init_javascript(:head_prepend).should =~ /_sf_startpt=\\(new.Date\\(\\)\\)\\.getTime\\(\\);/\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should =~ /_sf_async_config.uid = 12345;/\n      @api.init_javascript(:body_append).should =~ /_sf_async_config.domain = 'abcdef\\.com';/\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/clicky_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Clicky\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:google=>{:key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Clicky.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :body_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Clicky.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#track' do\n    it 'should return the tracking javascript' do\n      @api = Analytical::Modules::Clicky.new :parent=>@parent, :key=>'abcdef'\n      @api.track('pagename', {:some=>'data'}).should == \"clicky.log(\\\"pagename\\\");\"\n    end\n  end\n  describe '#identify' do\n    it 'should return the init code string' do\n      @api = Analytical::Modules::Clicky.new :parent=>@parent, :key=>'abcdef'\n      @api.identify('user id', {:a=>'b'}).should =~ /#{Regexp.escape({:id=>'user id', :a=>'b'}.to_json)}/\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Clicky.new :parent=>@parent, :key=>'abcdef'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should == ''            \n      @api.init_javascript(:body_append).should =~ /static.getclicky.com\\/js/\n      @api.init_javascript(:body_append).should =~ /abcdef/      \n    end\n    describe 'with an identify command queued' do\n      @api = Analytical::Modules::Clicky.new :parent=>@parent, :key=>'abcdef'\n      @api.queue :identify, 'user id', {:email=>'someone@test.com'}\n      @api.init_javascript(:body_append).should =~ /\"email\":\\w*\"someone@test\\.com\"/\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/comscore_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Comscore\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:comscore=>{:key=>123}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Comscore.new :parent=>@parent, :key=>123\n      a.tracking_command_location.should == :head_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Comscore.new :parent=>@parent, :key=>1234\n      a.options.should == {:key=>1234, :parent=>@parent}\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Comscore.new :parent=>@parent, :key=>1234\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should =~ /scorecardresearch.com\\/beacon.js/\n      @api.init_javascript(:head_append).should =~ /1234/\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should == ''\n    end\n  end\nend"
  },
  {
    "path": "spec/analytical/modules/gauges_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Gauges\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:gauges=>{:site_id=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Gauges.new :parent=>@parent, :site_id=>'abc'\n      a.tracking_command_location.should == :body_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Gauges.new :parent=>@parent, :site_id=>'abc'\n      a.options.should == {:site_id=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#track' do\n    it 'should return the tracking javascript' do\n      @api = Analytical::Modules::Gauges.new :parent=>@parent, :site_id=>'abcdef'\n      @api.track.should == \"_gauges.push(['track']);\"\n      @api.track('pagename', {:some=>'data'}).should ==  \"_gauges.push(['track']);\"\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Gauges.new :parent=>@parent, :site_id=>'abcdef'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should =~ /secure\\.gaug\\.es/\n      @api.init_javascript(:body_append).should =~ /abcdef/\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/google_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Google\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:google=>{:key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Google.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :head_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Google.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#track' do\n    it 'should return the tracking javascript' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.track.should == \"_gaq.push(['_trackPageview']);\"\n      @api.track('pagename', {:some=>'data'}).should ==  \"_gaq.push(['_trackPageview', \\\"pagename\\\"]);\"\n    end\n  end\n  describe '#event' do\n    it 'should return the event javascript' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename').should ==  \"_gaq.push(['_trackEvent', \\\"Event\\\", \\\"pagename\\\"]);\"\n    end\n    \n    it 'should include data value' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', {:value=>555, :more=>'info'}).should ==  \"_gaq.push(['_trackEvent', \\\"Event\\\", \\\"pagename\\\", 555]);\"\n    end\n    it 'should not include data if there is no value' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', {:more=>'info'}).should ==  \"_gaq.push(['_trackEvent', \\\"Event\\\", \\\"pagename\\\"]);\"\n    end\n    it 'should not include data if it is not a hash' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', 555).should ==  \"_gaq.push(['_trackEvent', \\\"Event\\\", \\\"pagename\\\", 555]);\"\n    end\n\n  end\n  describe '#custom_event' do\n    it 'should return the event javascript' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.custom_event('Tag', 'view').should ==  '_gaq.push([\"_trackEvent\",\"Tag\",\"view\"]);'\n    end\n\n    it 'should set the optional label and event value' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.custom_event('Tag', 'view', 'rails', 27).should ==  '_gaq.push([\"_trackEvent\",\"Tag\",\"view\",\"rails\",27]);'\n    end\n  end\n  \n  describe '#set' do\n    it 'should return the set javascript' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.set(:index => 1, :name => 'gender', :value => 'male').should ==  \"_gaq.push(['_setCustomVar', 1, 'gender', 'male']);\"\n    end\n\n    it 'should handle an optional scope' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.set(:index => 1, :name => 'gender', :value => 'male', :scope => 3).should ==  \"_gaq.push(['_setCustomVar', 1, 'gender', 'male', 3]);\"\n    end\n  end\n\n  describe \"google ecommerce\" do\n    before do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n    end\n\n    describe \"#add_item\" do\n      it \"adds an item\" do\n        @api.add_item(123, 'foo', 'bar', 'baz', 10.24, 42).should == \"_gaq.push(['_addItem', '123', 'foo', 'bar', 'baz', '10.24', '42']);\"\n      end\n\n      it \"adds an item with a nil category\" do\n        @api.add_item(123, 'foo', 'bar', nil, 10.24, 42).should == \"_gaq.push(['_addItem', '123', 'foo', 'bar', '', '10.24', '42']);\"\n      end\n    end\n\n    describe \"#add_trans\" do\n      it \"sets up a transaction\" do\n        @api.add_trans(123, 'foo', 100.0, 5.12, 10.24, 'NYC', 'NY', 'USA').should == \"_gaq.push(['_addTrans', '123', 'foo', '100.0', '5.12', '10.24', 'NYC', 'NY', 'USA']);\"\n      end\n\n      it \"sets up a transaction without optional params\" do\n         @api.add_trans(123, nil, 100.0).should == \"_gaq.push(['_addTrans', '123', '', '100.0', '', '', '', '', '']);\"\n       end\n\n    end\n\n    describe \"#track_trans\" do\n      it \"pushes the transaction data to google\" do\n        @api.track_trans.should == \"_gaq.push(['_trackTrans']);\"\n      end\n    end\n\n  end\n\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should =~ /abcdef/\n      @api.init_javascript(:head_append).should =~ /google-analytics.com\\/ga.js/\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should == ''\n    end\n\n    it 'should include enhanced link attribution code if required' do\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef', :enhanced_link_attribution => true\n      @api.init_javascript(:head_append).should =~ /plugins\\/ga\\/inpage_linkid\\.js/\n\n      @api = Analytical::Modules::Google.new :parent=>@parent, :key=>'abcdef', :enhanced_link_attribution => false\n      @api.init_javascript(:head_append).should_not =~ /plugins\\/ga\\/inpage_linkid\\.js/\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/google_universal_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::GoogleUniversal\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:google=>{:key=>'abc'}})\n  end\n  describe \"on initialize\" do\n    it \"should set the command_location\" do\n      a = Analytical::Modules::GoogleUniversal.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :head_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::GoogleUniversal.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#event' do\n    it 'should return the event javascript' do\n      @api = Analytical::Modules::GoogleUniversal.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename').should ==  \"ga('send', 'event', \\\"Event\\\", \\\"pagename\\\");\"\n    end\n    it 'should include data value' do\n      @api = Analytical::Modules::GoogleUniversal.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', {:value=>555, :more=>'info'}).should ==  \"ga('send', 'event', \\\"Event\\\", \\\"pagename\\\", 555);\"\n    end\n    it 'should not include data if there is no value' do\n      @api = Analytical::Modules::GoogleUniversal.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', {:more=>'info'}).should ==  \"ga('send', 'event', \\\"Event\\\", \\\"pagename\\\");\"\n    end\n    it 'should not include data if it is not a hash' do\n      @api = Analytical::Modules::GoogleUniversal.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', 555).should ==  \"ga('send', 'event', \\\"Event\\\", \\\"pagename\\\", 555);\"\n    end\n  end\nend"
  },
  {
    "path": "spec/analytical/modules/kiss_metrics_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::KissMetrics\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:google=>{:js_url_key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abc'\n      a.tracking_command_location.should == :body_prepend\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abc'\n      a.options.should == {:js_url_key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#identify' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.identify('id', {:email=>'test@test.com'}).should == \"_kmq.push([\\\"identify\\\", \\\"test@test.com\\\"]);\"\n    end\n    it 'uses the id parameter when the email parameter is not present' do\n      @api = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.identify('id').should == \"_kmq.push([\\\"identify\\\", \\\"id\\\"]);\"\n    end\n  end\n  describe '#event' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.event('Big Deal', {:something=>'good'}).should == \"_kmq.push([\\\"record\\\", \\\"Big Deal\\\", #{{:something=>'good'}.to_json}]);\"\n    end\n  end\n  describe '#set' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.set({:something=>'good', :b=>2}).should == \"_kmq.push([\\\"set\\\", #{{:something=>'good', :b=>2}.to_json}]);\"\n    end\n  end\n  describe '#alias_identity' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.alias_identity('foo', 'bar').should == \"_kmq.push([\\\"alias\\\", \\\"foo\\\", \\\"bar\\\"]);\"\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::KissMetrics.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should =~ /i\\.kissmetrics\\.com/\n      @api.init_javascript(:body_prepend).should =~ /abcdef/\n      @api.init_javascript(:body_append).should == ''\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/mixpanel_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\nrequire 'json'\n\ndescribe \"Analytical::Modules::Mixpanel\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:google=>{:js_url_key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abc'\n      a.tracking_command_location.should == :body_append\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abc'\n      a.options.should == {:js_url_key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#identify' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.identify('id', {:email=>'test@test.com'}).should == \"mixpanel.identify('id');\"\n    end\n    it 'should return a js string with name if included' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.identify('id', {:email=>'test@test.com', :name => \"user_name\"}).should == \"mixpanel.identify('id'); mixpanel.name_tag('user_name');\"\n    end\n  end\n  describe '#track' do\n    it 'should return the tracking javascript' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :key=>'abcdef'\n      result = @api.track('pagename', {'page title'=>'lovely day'})\n      match = result.match /mixpanel.track\\(\"page viewed\", \\{(.+)\\}, function\\(\\)\\{\\}/ \n      match.should_not be_nil\n      # parse the JSON to work around varying order of key/value pairs in analytical's result string\n      JSON.parse(\"{#{ match[1] }}\").should == { 'url' => 'pagename', 'page title' => 'lovely day'}\n    end\n    it 'should return the tracking javascript with a callback' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :key=>'abcdef'\n      result = @api.track('pagename', {'page title'=>'lovely day', :callback=>'fubar'})\n      match = result.match /mixpanel.track\\(\"page viewed\", \\{(.+)\\}, fubar\\);/ \n      match.should_not be_nil\n      # parse the JSON to work around varying order of key/value pairs in analytical's result string\n      JSON.parse(\"{#{ match[1] }}\").should == { 'url' => 'pagename', 'page title' => 'lovely day'}\n    end\n    it 'should return the tracking javascript with a custom event name' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :key=>'abcdef'\n      @api.track('pagename', {:event=>'virtual pageview'}).should == \"mixpanel.track(\\\"virtual pageview\\\", {\\\"url\\\":\\\"pagename\\\"}, function(){});\"\n    end\n    it 'should return nil when Mixpanel pageview tracking is disabled' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :key=>'abcdef', :track_pageview => false\n      @api.track('pagename', {'page title'=>'lovely day', :callback=>'fubar'}).should be_nil\n    end\n\n  end\n  describe '#event' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.event('An event happened', { :item => 43 }).should == \"mixpanel.track(\\\"An event happened\\\", {\\\"item\\\":43});\"\n    end\n  end\n  describe '#alias_identity' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.alias_identity(nil, 'new identity').should == \"mixpanel.alias(\\\"new identity\\\");\"\n    end\n    it 'ignores the first parameter' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.alias_identity('foo', 'new identity').should == \"mixpanel.alias(\\\"new identity\\\");\"\n    end\n  end\n  \n  describe '#person' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::Mixpanel.new :parent => @parent, :js_url_key => 'abcdef'\n      @api.person({ :name => 'P. Dummy', :email => 'p.dummy@ask.com' }).should == \"mixpanel.people.set({\\\"name\\\":\\\"P. Dummy\\\",\\\"email\\\":\\\"p.dummy@ask.com\\\"});\"\n    end\n  end\n  \n  describe '#revenue' do\n    it 'should return a js string' do\n      @api = Analytical::Modules::Mixpanel.new :parent => @parent, :js_url_key => 'abcdef'\n      @api.revenue(100, { :product => 'Book' }).should == \"mixpanel.people.track_charge(100, {\\\"product\\\":\\\"Book\\\"});\"\n    end\n  end\n\n  describe '#queue' do\n    before(:each) do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :js_url_key=>'abcdef'\n      @api.command_store.commands = [:a, :b, :c]\n    end\n    describe 'with an alias_identity command' do\n      it 'should store it at the head of the command list' do\n        @api.queue :identify, 'someone', {:some=>:args}\n        @api.queue :alias_identity, 'someone new'\n        @api.command_store.commands.should == [[:alias_identity, 'someone new'], [:identify, 'someone', {:some=>:args}], :a, :b, :c]\n      end\n    end\n    describe 'with an identify command' do\n      it 'should store it at the head of the command list' do\n        @api.queue :identify, 'someone', {:some=>:args}\n        @api.command_store.commands.should == [[:identify, 'someone', {:some=>:args}], :a, :b, :c]\n      end\n      it 'should ignore it if head of queue is :alias_identity' do\n        @api.queue :alias_identity, 'someone new'\n        @api.queue :identify, 'someone', {:some=>:args}\n        @api.command_store.commands.should == [[:alias_identity, 'someone new'], :a, :b, :c]\n      end\n    end\n    describe 'with any other command' do\n      it 'should store it at the end of the command list' do\n        @api.queue :other, {:some=>:args}\n        @api.command_store.commands.should == [:a, :b, :c, [:other, {:some=>:args}]]\n      end\n    end\n  end\n\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :key=>'abcdef'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should =~ %r(cdn\\.mxpnl\\.com\\/libs/mixpanel-2\\.2\\.min\\.js)\n      @api.init_javascript(:body_append).should =~ %r(var config = { track_pageview: true };)\n      @api.init_javascript(:body_append).should =~ %r(mixpanel\\.init\\(\"abcdef\"\\, config)\n      @api.init_javascript(:body_append).should =~ %r(<script type=\"text\\/javascript\">)\n    end\n    \n    context 'with track set to false' do\n      @api = Analytical::Modules::Mixpanel.new :parent=>@parent, :key=>'abcdef', :track_pageview => false\n      @api.init_javascript(:body_append).should =~ %r(var config = { track_pageview: false };)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/modules/optimizely_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Optimizely\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:optimizely=>{:key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Optimizely.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :head_prepend\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Optimizely.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Optimizely.new :parent=>@parent, :key=>'abcdef'\n      @api.init_javascript(:head_prepend).should =~ /cdn\\.optimizely.com\\/js\\/abcdef\\.js/\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should == ''\n    end\n  end\nend"
  },
  {
    "path": "spec/analytical/modules/quantcast.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Quantcast\" do\n  before(:each) do\n    @parent = mock('api', :options=>{:quantcast=>{:key=>1234}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = Analytical::Modules::Quantcast.new :parent=>@parent, :key=>1234\n      a.tracking_command_location.should == [:head_append, :body_append]\n    end\n    it 'should set the options' do\n      a = Analytical::Modules::Quantcast.new :parent=>@parent, :key=>12345\n      a.options.should == {:key=>12345, :parent=>@parent}\n    end\n  end\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Quantcast.new :parent=>@parent, :key=>12345\n      @api.init_javascript(:head_prepend).should =~ ''\n      @api.init_javascript(:head_append).should == /var._qevents.=._qevents.\\|\\|.\\[\\];/\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should =~ /_qevents.push\\(.\\{.qacct:\"12345\"\\} \\);/\n    end\n  end\nend"
  },
  {
    "path": "spec/analytical/modules/reinvigorate_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe \"Analytical::Modules::Reinvigorate\" do\n\n  before(:each) do\n    @parent = mock('api', :options=>{:reinvigorate=>{:key=>'abc'}})\n  end\n\n  describe 'on initialize' do\n\n    it 'should set the command_location' do\n      a = Analytical::Modules::Reinvigorate.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should == :body_append\n    end\n\n    it 'should set the options' do\n      a = Analytical::Modules::Reinvigorate.new :parent=>@parent, :key=>'abc'\n      a.options.should == {:key=>'abc', :parent=>@parent}\n    end\n\n  end\n\n  describe '#init_javascript' do\n\n    it 'should return the init javascript' do\n      @api = Analytical::Modules::Reinvigorate.new :parent=>@parent, :key=>'abcdef'\n      @api.init_javascript(:head_prepend).should == ''\n      @api.init_javascript(:head_append).should == ''\n      @api.init_javascript(:body_prepend).should == ''\n      @api.init_javascript(:body_append).should =~ /reinvigorate/\n    end\n\n  end\n\nend"
  },
  {
    "path": "spec/analytical/modules/segment_io_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')\n\ndescribe Analytical::Modules::SegmentIo do\n  before(:each) do\n    @parent = mock('api', :options=>{:segment_io=>{:key=>'abc'}})\n  end\n  describe 'on initialize' do\n    it 'should set the command_location' do\n      a = described_class.new :parent=>@parent, :key=>'abc'\n      a.tracking_command_location.should eq :head_prepend\n    end\n    it 'should set the options' do\n      a = described_class.new :parent=>@parent, :key=>'abc'\n      a.options.should eq({:key=>'abc', :parent=>@parent})\n    end\n  end\n  describe '#track' do\n    it 'should return the pageview javascript' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.track.should eq \"window.analytics.pageview();\"\n      @api.track('pagename', {:some=>'data'}).should eq  \"window.analytics.pageview(\\\"pagename\\\");\"\n    end\n  end\n  describe '#event' do\n    it 'should return the track javascript' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename').should eq \"window.analytics.track(\\\"pagename\\\", {});\"\n    end\n\n    it 'should include data value' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', {:value=>555, :more=>'info'}).should eq \"window.analytics.track(\\\"pagename\\\", {\\\"value\\\":555,\\\"more\\\":\\\"info\\\"});\"\n    end\n    it 'should not include data if there is no value' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', {:more=>'info'}).should eq  \"window.analytics.track(\\\"pagename\\\", {\\\"more\\\":\\\"info\\\"});\"\n    end\n    it 'should not include data if it is not a hash' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.event('pagename', 555).should eq  \"window.analytics.track(\\\"pagename\\\", 555);\"\n    end\n  end\n  describe '#identify' do\n    it 'should return the identify javascript' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.identify('id', {:email=>'test@test.com'}).should == \"window.analytics.identify(\\\"id\\\", {\\\"email\\\":\\\"test@test.com\\\"});\"\n    end\n  end\n\n  describe '#init_javascript' do\n    it 'should return the init javascript' do\n      @api = described_class.new :parent=>@parent, :key=>'abcdef'\n      @api.init_javascript(:head_append).should eq ''\n      @api.init_javascript(:head_prepend).should =~ /window.analytics=/\n      @api.init_javascript(:head_prepend).should =~ /abcdef/\n      @api.init_javascript(:head_prepend).should =~ /analytics.js\\/v1/\n      @api.init_javascript(:body_prepend).should eq ''\n      @api.init_javascript(:body_append).should eq ''\n    end\n  end\nend\n"
  },
  {
    "path": "spec/analytical/session_command_store_spec.rb",
    "content": "require 'spec_helper'\n\ndescribe Analytical::SessionCommandStore do\n  \n  describe 'with a session hash' do\n    before(:each) do\n      @session = {}\n    end\n    \n    it 'should add elements' do\n      @store = Analytical::SessionCommandStore.new @session, :some_module, ['a']\n      @store << 'b'\n      @session[:analytical_some_module].should == ['a', 'b']\n    end\n    it 'should unshift elements' do\n      @store = Analytical::SessionCommandStore.new @session, :some_module, ['b']\n      @store.unshift 'a'\n      @session[:analytical_some_module].should == ['a', 'b']\n    end\n    it 'should iterate over elements' do\n      @store = Analytical::SessionCommandStore.new @session, :some_module, ['a', 'b']\n      @store.each do |elem|\n        ['a', 'b'].include?(elem).should be_true\n      end\n    end\n    it 'should have size' do\n      @store = Analytical::SessionCommandStore.new @session, :some_module, ['a', 'b']\n      @store.size.should == 2\n    end\n\n    it 'should set up the :analytical session hash' do\n      @store = Analytical::SessionCommandStore.new @session, :some_module, ['a', 'b']\n      @session[:analytical_some_module].should_not be_nil   \n    end\n\n    describe 'when flushing' do\n      it 'should empty the list' do\n        @store = Analytical::SessionCommandStore.new @session, :some_module, ['a', 'b']\n        @store.flush\n        @store.size.should == 0\n      end\n      it 'should empty the session key' do\n        @store = Analytical::SessionCommandStore.new @session, :some_module, ['a', 'b']\n        @store.flush\n        @session[:analytical_some_module].should == []\n      end\n    end\n  end\n  \nend\n"
  },
  {
    "path": "spec/analytical_spec.rb",
    "content": "require File.expand_path(File.dirname(__FILE__) + '/spec_helper')\nrequire 'ostruct'\n\ndescribe \"Analytical\" do\n  describe 'on initialization' do\n    class DummyForInit\n      extend Analytical\n      include Analytical::InstanceMethods\n      include Analytical::BotDetector\n      if ::Rails::VERSION::MAJOR < 3\n        class_inheritable_accessor :analytical_options\n      else\n        class_attribute :analytical_options\n      end\n\n      def self.helper_method(*a); end\n      def request\n        RSpec::Mocks::Mock.new 'request',\n          :'ssl?'=>true,\n          :user_agent=>'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0'\n      end\n    end\n\n    it 'should have the options from analytical.yml' do\n      DummyForInit.analytical\n      d = DummyForInit.new.analytical\n      d.options[:modules].sort_by { |m| m.to_s }.should == [:chartbeat, :clicky, :google, :kiss_metrics]\n      d.options[:true_option].should be_true\n      d.options[:false_option].should be_false\n      d.options[:string_option].should == \"string\"\n    end\n\n    it 'should load module configurations from hash' do\n      options = { :modules => { :console => { :some_key => 'rubberduck' } }}\n      DummyForInit.analytical options\n      a = DummyForInit.new.analytical\n      a.options[:modules].should include(:console)\n      a.options[:console].should eq({ :some_key => 'rubberduck' })\n    end\n\n    it 'should load module configurations from a lambda' do\n      class DummyForInit\n        def analytical_modules\n          { :console => { :some_key => 'rubberduck' } }\n        end\n      end\n      options = { :modules => lambda { |controller| controller.analytical_modules }}\n      DummyForInit.analytical options\n      a = DummyForInit.new.analytical\n      a.options[:modules].should include(:console)\n      a.options[:console].should eq({ :some_key => 'rubberduck' })\n    end\n\n    it 'should preserve :javascript_helpers option' do\n      options = { :javascript_helpers => false, :modules => [] }\n      DummyForInit.analytical options\n      a = DummyForInit.new.analytical\n      a.options[:javascript_helpers].should be_false\n    end\n\n    it 'should use the supplied options' do\n      DummyForInit.analytical :modules=>[:google]\n      d = DummyForInit.new.analytical\n      d.options[:modules].should == [:google]\n    end\n\n    describe 'conditionally disabled' do\n      it 'should set the modules to []' do\n        DummyForInit.analytical :disable_if => lambda { |x| true }\n        d = DummyForInit.new\n        d.analytical.options[:modules].should == []\n      end\n    end\n\n    describe 'with filtered modules' do\n      it 'should set the modules to []' do\n        DummyForInit.analytical :filter_modules => lambda { |x, modules| modules - [:clicky] }\n        d = DummyForInit.new\n        d.analytical.options[:modules].include?(:clicky).should be_false\n      end\n    end\n\n    describe 'with a robot request' do\n      it 'should set the modules to []' do\n        DummyForInit.analytical\n        d = DummyForInit.new\n        d.stub!(:'analytical_is_robot?').and_return(true)\n        d.analytical.options[:modules].should == []\n      end\n    end\n\n    it 'should open the initialization file' do\n      File.should_receive(:'exists?').with(\"#{Rails.root}/config/analytical.yml\").and_return(true)\n      DummyForInit.analytical\n      DummyForInit.analytical_options[:google][:key].should == 'google_12345'\n      DummyForInit.analytical_options[:kiss_metrics][:key].should == 'kiss_metrics_12345'\n      DummyForInit.analytical_options[:clicky][:key].should == 'clicky_12345'\n      DummyForInit.analytical_options[:chartbeat][:key].should == 'chartbeat_12345'\n      DummyForInit.analytical_options[:chartbeat][:domain].should == 'your.domain.com'\n    end\n\n    it 'should allow for module-specific controller overrides' do\n      DummyForInit.analytical :google=>{:key=>'override_google_key'}\n      DummyForInit.analytical_options[:google][:key].should == 'override_google_key'\n    end\n\n    describe 'in production mode' do\n      before(:each) do\n        Rails.env.stub!(:production?).and_return(true)\n      end\n      it 'should start with no modules' do\n        Analytical::Api.should_not_receive(:include)\n        DummyForInit.analytical\n        DummyForInit.new.analytical.options[:modules] = []\n      end\n    end\n\n    describe 'in non-production mode' do\n      before(:each) do\n        Rails.env.stub!(:production?).and_return(false)\n      end\n      it 'should start with no modules' do\n        DummyForInit.analytical\n        DummyForInit.new.analytical.options[:modules] = [:console]\n      end\n    end\n\n    describe 'in development mode' do\n      before(:each) do\n        Rails.stub!(:env).and_return(:development)\n      end\n      it 'should start with no modules' do\n        DummyForInit.analytical\n        DummyForInit.new.analytical.options[:modules] = []\n      end\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "spec/config/analytical.yml",
    "content": "test:\n  javascript_helpers: true\n  false_option: false\n  true_option: true\n  string_option: \"string\"\n  google:\n    key: google_12345\n  clicky:\n    key: clicky_12345\n  kiss_metrics:\n    key: kiss_metrics_12345\n    js_url_key: kiss_metrics_js_url_12345\n  chartbeat:\n    key: chartbeat_12345\n    domain: your.domain.com\n\nproduction:\n\ndevelopment:\n\n"
  },
  {
    "path": "spec/spec.opts",
    "content": "--color\n--loadby mtime\n--reverse\n--diff unified\n--backtrace\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "require 'rubygems'\n\nENV[\"RAILS_ENV\"] ||= 'test'\n\nrequire 'active_support'\nrequire 'active_support/core_ext'\nrequire 'active_support/json'\nrequire 'action_view'\nrequire 'active_model'\nrequire 'action_controller'\nrequire 'rails'\nrequire 'rspec/rails'\n\nRSpec.configure do |config|\n  config.mock_with :rspec\n  config.expect_with :rspec\n  config.before do\n    Rails.stub(:root).and_return(Pathname.new(__FILE__).dirname)\n  end\nend\n\nrequire 'analytical'\n"
  }
]