Commit e395d6a7799ea634fac7b388495b91dbdf627bc3

  • avatar
  • Ardenta <support @ard…ta.com>
  • Tue Jun 23 22:22:04 CEST 2009
Initial hobo install 0.8.7
.gitignore
(7 / 0)
  
1log/*.log
2tmp/*
3db/*.sqlite3
4app/views/taglibs/auto
5db/data.yml
6patches-*/*
7.stgit-edit.txt
README
(243 / 0)
  
1== Welcome to Rails
2
3Rails is a web-application framework that includes everything needed to create
4database-backed web applications according to the Model-View-Control pattern.
5
6This pattern splits the view (also called the presentation) into "dumb" templates
7that are primarily responsible for inserting pre-built data in between HTML tags.
8The model contains the "smart" domain objects (such as Account, Product, Person,
9Post) that holds all the business logic and knows how to persist themselves to
10a database. The controller handles the incoming requests (such as Save New Account,
11Update Product, Show Post) by manipulating the model and directing data to the view.
12
13In Rails, the model is handled by what's called an object-relational mapping
14layer entitled Active Record. This layer allows you to present the data from
15database rows as objects and embellish these data objects with business logic
16methods. You can read more about Active Record in
17link:files/vendor/rails/activerecord/README.html.
18
19The controller and view are handled by the Action Pack, which handles both
20layers by its two parts: Action View and Action Controller. These two layers
21are bundled in a single package due to their heavy interdependence. This is
22unlike the relationship between the Active Record and Action Pack that is much
23more separate. Each of these packages can be used independently outside of
24Rails. You can read more about Action Pack in
25link:files/vendor/rails/actionpack/README.html.
26
27
28== Getting Started
29
301. At the command prompt, start a new Rails application using the <tt>rails</tt> command
31 and your application name. Ex: rails myapp
322. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
333. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!"
344. Follow the guidelines to start developing your application
35
36
37== Web Servers
38
39By 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
40with a variety of other web servers.
41
42Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is
43suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
44getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
45More info at: http://mongrel.rubyforge.org
46
47Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or
48Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use
49FCGI or proxy to a pack of Mongrels/Thin/Ebb servers.
50
51== Apache .htaccess example for FCGI/CGI
52
53# General Apache options
54AddHandler fastcgi-script .fcgi
55AddHandler cgi-script .cgi
56Options +FollowSymLinks +ExecCGI
57
58# If you don't want Rails to look in certain directories,
59# use the following rewrite rules so that Apache won't rewrite certain requests
60#
61# Example:
62# RewriteCond %{REQUEST_URI} ^/notrails.*
63# RewriteRule .* - [L]
64
65# Redirect all requests not available on the filesystem to Rails
66# By default the cgi dispatcher is used which is very slow
67#
68# For better performance replace the dispatcher with the fastcgi one
69#
70# Example:
71# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
72RewriteEngine On
73
74# If your Rails application is accessed via an Alias directive,
75# then you MUST also set the RewriteBase in this htaccess file.
76#
77# Example:
78# Alias /myrailsapp /path/to/myrailsapp/public
79# RewriteBase /myrailsapp
80
81RewriteRule ^$ index.html [QSA]
82RewriteRule ^([^.]+)$ $1.html [QSA]
83RewriteCond %{REQUEST_FILENAME} !-f
84RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
85
86# In case Rails experiences terminal errors
87# Instead of displaying this message you can supply a file here which will be rendered instead
88#
89# Example:
90# ErrorDocument 500 /500.html
91
92ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
93
94
95== Debugging Rails
96
97Sometimes your application goes wrong. Fortunately there are a lot of tools that
98will help you debug it and get it back on the rails.
99
100First area to check is the application log files. Have "tail -f" commands running
101on the server.log and development.log. Rails will automatically display debugging
102and runtime information to these files. Debugging info will also be shown in the
103browser on requests from 127.0.0.1.
104
105You can also log your own messages directly into the log file from your code using
106the Ruby logger class from inside your controllers. Example:
107
108 class WeblogController < ActionController::Base
109 def destroy
110 @weblog = Weblog.find(params[:id])
111 @weblog.destroy
112 logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
113 end
114 end
115
116The result will be a message in your log file along the lines of:
117
118 Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1
119
120More information on how to use the logger is at http://www.ruby-doc.org/core/
121
122Also, Ruby documentation can be found at http://www.ruby-lang.org/ including:
123
124* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/
125* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
126
127These two online (and free) books will bring you up to speed on the Ruby language
128and also on programming in general.
129
130
131== Debugger
132
133Debugger support is available through the debugger command when you start your Mongrel or
134Webrick server with --debugger. This means that you can break out of execution at any point
135in the code, investigate and change the model, AND then resume execution!
136You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'
137Example:
138
139 class WeblogController < ActionController::Base
140 def index
141 @posts = Post.find(:all)
142 debugger
143 end
144 end
145
146So the controller will accept the action, run the first line, then present you
147with a IRB prompt in the server window. Here you can do things like:
148
149 >> @posts.inspect
150 => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
151 #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
152 >> @posts.first.title = "hello from a debugger"
153 => "hello from a debugger"
154
155...and even better is that you can examine how your runtime objects actually work:
156
157 >> f = @posts.first
158 => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
159 >> f.
160 Display all 152 possibilities? (y or n)
161
162Finally, when you're ready to resume execution, you enter "cont"
163
164
165== Console
166
167You can interact with the domain model by starting the console through <tt>script/console</tt>.
168Here you'll have all parts of the application configured, just like it is when the
169application is running. You can inspect domain models, change values, and save to the
170database. Starting the script without arguments will launch it in the development environment.
171Passing an argument will specify a different environment, like <tt>script/console production</tt>.
172
173To reload your controllers and models after launching the console run <tt>reload!</tt>
174
175== dbconsole
176
177You can go to the command line of your database directly through <tt>script/dbconsole</tt>.
178You would be connected to the database with the credentials defined in database.yml.
179Starting the script without arguments will connect you to the development database. Passing an
180argument will connect you to a different database, like <tt>script/dbconsole production</tt>.
181Currently works for mysql, postgresql and sqlite.
182
183== Description of Contents
184
185app
186 Holds all the code that's specific to this particular application.
187
188app/controllers
189 Holds controllers that should be named like weblogs_controller.rb for
190 automated URL mapping. All controllers should descend from ApplicationController
191 which itself descends from ActionController::Base.
192
193app/models
194 Holds models that should be named like post.rb.
195 Most models will descend from ActiveRecord::Base.
196
197app/views
198 Holds the template files for the view that should be named like
199 weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby
200 syntax.
201
202app/views/layouts
203 Holds the template files for layouts to be used with views. This models the common
204 header/footer method of wrapping views. In your views, define a layout using the
205 <tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,
206 call <% yield %> to render the view using this layout.
207
208app/helpers
209 Holds view helpers that should be named like weblogs_helper.rb. These are generated
210 for you automatically when using script/generate for controllers. Helpers can be used to
211 wrap functionality for your views into methods.
212
213config
214 Configuration files for the Rails environment, the routing map, the database, and other dependencies.
215
216db
217 Contains the database schema in schema.rb. db/migrate contains all
218 the sequence of Migrations for your schema.
219
220doc
221 This directory is where your application documentation will be stored when generated
222 using <tt>rake doc:app</tt>
223
224lib
225 Application specific libraries. Basically, any kind of custom code that doesn't
226 belong under controllers, models, or helpers. This directory is in the load path.
227
228public
229 The directory available for the web server. Contains subdirectories for images, stylesheets,
230 and javascripts. Also contains the dispatchers and the default HTML files. This should be
231 set as the DOCUMENT_ROOT of your web server.
232
233script
234 Helper scripts for automation and generation.
235
236test
237 Unit and functional tests along with fixtures. When using the script/generate scripts, template
238 test files will be generated for you and placed in this directory.
239
240vendor
241 External libraries that the application depends on. Also includes the plugins subdirectory.
242 If the app has frozen rails, those gems also go here, under vendor/rails/.
243 This directory is in the load path.
Rakefile
(12 / 0)
  
1# Add your own tasks in files placed in lib/tasks ending in .rake,
2# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
4require(File.join(File.dirname(__FILE__), 'config', 'boot'))
5
6require 'rake'
7require 'rake/testtask'
8require 'rake/rdoctask'
9
10require 'tasks/rails'
11
12require 'hobo/tasks/rails'
  
1# Filters added to this controller apply to all controllers in the application.
2# Likewise, all the methods added will be available for all controllers.
3
4class ApplicationController < ActionController::Base
5 helper :all # include all helpers, all the time
6 protect_from_forgery # See ActionController::RequestForgeryProtection for details
7
8 # Scrub sensitive parameters from your log
9 # filter_parameter_logging :password
10end
  
1class FrontController < ApplicationController
2
3 hobo_controller
4
5 def index; end
6
7 def search
8 if params[:query]
9 site_search(params[:query])
10 end
11 end
12
13end
  
1class UsersController < ApplicationController
2
3 hobo_user_controller
4
5 auto_actions :all, :except => [ :index, :new, :create ]
6
7end
  
1# Methods added to this helper will be available to all templates in the application.
2module ApplicationHelper
3end
  
1module FrontHelper
2end
  
1module UsersHelper
2end
  
1class Guest < Hobo::Guest
2
3 def administrator?
4 false
5 end
6
7end
  
1class User < ActiveRecord::Base
2
3 hobo_user_model # Don't put anything above this
4
5 fields do
6 name :string, :unique
7 email_address :email_address, :unique, :login => true
8 administrator :boolean, :default => false
9 timestamps
10 end
11
12 # This gives admin rights to the first sign-up.
13 # Just remove it if you don't want that
14 before_create { |user| user.administrator = true if RAILS_ENV != "test" && count == 0 }
15
16
17 # --- Signup lifecycle --- #
18
19 lifecycle do
20
21 state :active, :default => true
22
23 create :signup, :available_to => "Guest",
24 :params => [:name, :email_address, :password, :password_confirmation],
25 :become => :active
26
27 transition :request_password_reset, { :active => :active }, :new_key => true do
28 UserMailer.deliver_forgot_password(self, lifecycle.key)
29 end
30
31 transition :reset_password, { :active => :active }, :available_to => :key_holder,
32 :params => [ :password, :password_confirmation ]
33
34 end
35
36
37 # --- Permissions --- #
38
39 def create_permitted?
40 false
41 end
42
43 def update_permitted?
44 acting_user.administrator? || (acting_user == self && only_changed?(:crypted_password, :email_address))
45 # Note: crypted_password has attr_protected so although it is permitted to change, it cannot be changed
46 # directly from a form submission.
47 end
48
49 def destroy_permitted?
50 acting_user.administrator?
51 end
52
53 def view_permitted?(field)
54 true
55 end
56
57end
  
1class UserMailer < ActionMailer::Base
2
3 def forgot_password(user, key)
4 host = Hobo::Controller.request_host
5 app_name = Hobo::Controller.app_name || host
6 @subject = "#{app_name} -- forgotten password"
7 @body = { :user => user, :key => key, :host => host, :app_name => app_name }
8 @recipients = user.email_address
9 @from = "no-reply@#{host}"
10 @sent_on = Time.now
11 @headers = {}
12 end
13
14end
  
1<page title="Home">
2
3 <body: class="front-page"/>
4
5 <content:>
6 <header class="content-header">
7 <h1>Welcome to <app-name/></h1>
8 <section class="welcome-message">
9 <h3>Congratulations! Your Hobo Rails App is up and running</h3>
10 <ul>
11 <li>To customise this page: edit app/views/front/index.dryml</li>
12 </ul>
13 </section>
14 </header>
15
16 <section class="content-body">
17 </section>
18 </content:>
19
20</page>
  
1<include src="rapid" plugin="hobo"/>
2
3<include src="taglibs/auto/rapid/cards"/>
4<include src="taglibs/auto/rapid/pages"/>
5<include src="taglibs/auto/rapid/forms"/>
6
7<set-theme name="clean"/>
8
9<def tag="app-name">Hobo</def>
  
1<extend tag="page">
2 <old-page merge>
3 <stylesheets: param>
4 <stylesheet name="reset"/>
5 <theme-stylesheet/>
6 <theme-stylesheet name="rapid-ui"/>
7 <stylesheet name="application" param="app-stylesheet"/>
8 </stylesheets:>
9 </old-page>
10</extend>
  
1<%= @user %>,
2
3If you have forgotten your password for <%= @app_name %>, you can choose
4a new one by clicking on this link:
5
6 <%= user_reset_password_url :host => @host, :id => @user, :key => @key %>
7
8Thank you,
9
10The <%= @app_name %> team.
  
1# Don't change this file!
2# Configure your app in config/environment.rb and config/environments/*.rb
3
4RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
5
6module Rails
7 class << self
8 def boot!
9 unless booted?
10 preinitialize
11 pick_boot.run
12 end
13 end
14
15 def booted?
16 defined? Rails::Initializer
17 end
18
19 def pick_boot
20 (vendor_rails? ? VendorBoot : GemBoot).new
21 end
22
23 def vendor_rails?
24 File.exist?("#{RAILS_ROOT}/vendor/rails")
25 end
26
27 def preinitialize
28 load(preinitializer_path) if File.exist?(preinitializer_path)
29 end
30
31 def preinitializer_path
32 "#{RAILS_ROOT}/config/preinitializer.rb"
33 end
34 end
35
36 class Boot
37 def run
38 load_initializer
39 Rails::Initializer.run(:set_load_path)
40 end
41 end
42
43 class VendorBoot < Boot
44 def load_initializer
45 require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46 Rails::Initializer.run(:install_gem_spec_stubs)
47 Rails::GemDependency.add_frozen_gem_path
48 end
49 end
50
51 class GemBoot < Boot
52 def load_initializer
53 self.class.load_rubygems
54 load_rails_gem
55 require 'initializer'
56 end
57
58 def load_rails_gem
59 if version = self.class.gem_version
60 gem 'rails', version
61 else
62 gem 'rails'
63 end
64 rescue Gem::LoadError => load_error
65 $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.)
66 exit 1
67 end
68
69 class << self
70 def rubygems_version
71 Gem::RubyGemsVersion rescue nil
72 end
73
74 def gem_version
75 if defined? RAILS_GEM_VERSION
76 RAILS_GEM_VERSION
77 elsif ENV.include?('RAILS_GEM_VERSION')
78 ENV['RAILS_GEM_VERSION']
79 else
80 parse_gem_version(read_environment_rb)
81 end
82 end
83
84 def load_rubygems
85 require 'rubygems'
86 min_version = '1.3.1'
87 unless rubygems_version >= min_version
88 $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
89 exit 1
90 end
91
92 rescue LoadError
93 $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
94 exit 1
95 end
96
97 def parse_gem_version(text)
98 $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
99 end
100
101 private
102 def read_environment_rb
103 File.read("#{RAILS_ROOT}/config/environment.rb")
104 end
105 end
106 end
107end
108
109# All that for this:
110Rails.boot!
  
1# SQLite version 3.x
2# gem install sqlite3-ruby (not necessary on OS X Leopard)
3development:
4 adapter: sqlite3
5 database: db/development.sqlite3
6 pool: 5
7 timeout: 5000
8
9# Warning: The database defined as "test" will be erased and
10# re-generated from your development database when you run "rake".
11# Do not set this db to the same as development or production.
12test:
13 adapter: sqlite3
14 database: db/test.sqlite3
15 pool: 5
16 timeout: 5000
17
18production:
19 adapter: sqlite3
20 database: db/production.sqlite3
21 pool: 5
22 timeout: 5000
  
1# Be sure to restart your server when you modify this file
2
3# Specifies gem version of Rails to use when vendor/rails is not present
4RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION
5
6# Bootstrap the Rails environment, frameworks, and default configuration
7require File.join(File.dirname(__FILE__), 'boot')
8
9Rails::Initializer.run do |config|
10 config.gem 'hobo'
11
12 # Settings in config/environments/* take precedence over those specified here.
13 # Application configuration should go into files in config/initializers
14 # -- all .rb files in that directory are automatically loaded.
15
16 # Add additional load paths for your own custom dirs
17 # config.load_paths += %W( #{RAILS_ROOT}/extras )
18
19 # Specify gems that this application depends on and have them installed with rake gems:install
20 # config.gem "bj"
21 # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
22 # config.gem "sqlite3-ruby", :lib => "sqlite3"
23 # config.gem "aws-s3", :lib => "aws/s3"
24
25 # Only load the plugins named here, in the order given (default is alphabetical).
26 # :all can be used as a placeholder for all plugins not explicitly named
27 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
28
29 # Skip frameworks you're not going to use. To use Rails without a database,
30 # you must remove the Active Record framework.
31 # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
32
33 # Activate observers that should always be running
34 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
35
36 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
37 # Run "rake -D time" for a list of tasks for finding time zone names.
38 config.time_zone = 'UTC'
39
40 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
41 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
42 # config.i18n.default_locale = :de
43end
  
1# Settings specified here will take precedence over those in config/environment.rb
2
3# In the development environment your application's code is reloaded on
4# every request. This slows down response time but is perfect for development
5# since you don't have to restart the webserver when you make code changes.
6config.cache_classes = false
7
8# Log error messages when you accidentally call methods on nil.
9config.whiny_nils = true
10
11# Show full error reports and disable caching
12config.action_controller.consider_all_requests_local = true
13config.action_view.debug_rjs = true
14config.action_controller.perform_caching = false
15
16# Don't care if the mailer can't send
17config.action_mailer.raise_delivery_errors = false
  
1# Settings specified here will take precedence over those in config/environment.rb
2
3# The production environment is meant for finished, "live" apps.
4# Code is not reloaded between requests
5config.cache_classes = true
6
7# Full error reports are disabled and caching is turned on
8config.action_controller.consider_all_requests_local = false
9config.action_controller.perform_caching = true
10config.action_view.cache_template_loading = true
11
12# See everything in the log (default is :info)
13# config.log_level = :debug
14
15# Use a different logger for distributed setups
16# config.logger = SyslogLogger.new
17
18# Use a different cache store in production
19# config.cache_store = :mem_cache_store
20
21# Enable serving of images, stylesheets, and javascripts from an asset server
22# config.action_controller.asset_host = "http://assets.example.com"
23
24# Disable delivery errors, bad email addresses will be ignored
25# config.action_mailer.raise_delivery_errors = false
26
27# Enable threaded mode
28# config.threadsafe!
  
1# Settings specified here will take precedence over those in config/environment.rb
2
3# The test environment is used exclusively to run your application's
4# test suite. You never need to work with it otherwise. Remember that
5# your test database is "scratch space" for the test suite and is wiped
6# and recreated between test runs. Don't rely on the data there!
7config.cache_classes = true
8
9# Log error messages when you accidentally call methods on nil.
10config.whiny_nils = true
11
12# Show full error reports and disable caching
13config.action_controller.consider_all_requests_local = true
14config.action_controller.perform_caching = false
15config.action_view.cache_template_loading = true
16
17# Disable request forgery protection in test environment
18config.action_controller.allow_forgery_protection = false
19
20# Tell Action Mailer not to deliver emails to the real world.
21# The :test delivery method accumulates sent emails in the
22# ActionMailer::Base.deliveries array.
23config.action_mailer.delivery_method = :test
24
25# Use SQL instead of Active Record's schema dumper when creating the test database.
26# This is necessary if your schema can't be completely dumped by the schema dumper,
27# like if you have constraints or database-specific column types
28# config.active_record.schema_format = :sql
  
1# Be sure to restart your server when you modify this file.
2
3# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
6# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
7# Rails.backtrace_cleaner.remove_silencers!
  
1Hobo::ModelRouter.reload_routes_on_every_request = true
  
1# Be sure to restart your server when you modify this file.
2
3# Add new inflection rules using the following format
4# (all these examples are active by default):
5# ActiveSupport::Inflector.inflections do |inflect|
6# inflect.plural /^(ox)$/i, '\1en'
7# inflect.singular /^(ox)en/i, '\1'
8# inflect.irregular 'person', 'people'
9# inflect.uncountable %w( fish sheep )
10# end
  
1# Be sure to restart your server when you modify this file.
2
3# Add new mime types for use in respond_to blocks:
4# Mime::Type.register "text/richtext", :rtf
5# Mime::Type.register_alias "text/html", :iphone
  
1# Be sure to restart your server when you modify this file.
2
3# These settings change the behavior of Rails 2 apps and will be defaults
4# for Rails 3. You can remove this initializer when Rails 3 is released.
5
6if defined?(ActiveRecord)
7 # Include Active Record class name as root for JSON serialized output.
8 ActiveRecord::Base.include_root_in_json = true
9
10 # Store the full class name (including module namespace) in STI type column.
11 ActiveRecord::Base.store_full_sti_class = true
12end
13
14# Use ISO 8601 format for JSON serialized times and dates.
15ActiveSupport.use_standard_json_time_format = true
16
17# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
18# if you're including raw json in an HTML page.
19ActiveSupport.escape_html_entities_in_json = false
  
1# Be sure to restart your server when you modify this file.
2
3# Your secret key for verifying cookie session data integrity.
4# If you change this key, all old sessions will become invalid!
5# Make sure the secret is at least 30 characters and all random,
6# no regular words or you'll be exposed to dictionary attacks.
7ActionController::Base.session = {
8 :key => '_hobo_session',
9 :secret => 'f588622a250f1a889f290d9f11d8694901c0f31997bf61959a908a3ea96e86c2ce900d48f68c1af3301baa396702c20c5181ec226ae9fe568faca11d70ab4574'
10}
11
12# Use the database for sessions instead of the cookie-based default,
13# which shouldn't be used to store highly confidential information
14# (create the session table with "rake db:sessions:create")
15# ActionController::Base.session_store = :active_record_store
  
1# Sample localization file for English. Add more files in this directory for other locales.
2# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
4en:
5 hello: "Hello world"
  
1ActionController::Routing::Routes.draw do |map|
2
3 map.site_search 'search', :controller => 'front', :action => 'search'
4 map.root :controller => 'front', :action => 'index'
5
6 Hobo.add_routes(map)
7
8 # The priority is based upon order of creation: first created -> highest priority.
9
10 # Sample of regular route:
11 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
12 # Keep in mind you can assign values other than :controller and :action
13
14 # Sample of named route:
15 # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
16 # This route can be invoked with purchase_url(:id => product.id)
17
18 # Sample resource route (maps HTTP verbs to controller actions automatically):
19 # map.resources :products
20
21 # Sample resource route with options:
22 # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
23
24 # Sample resource route with sub-resources:
25 # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
26
27 # Sample resource route with more complex sub-resources
28 # map.resources :products do |products|
29 # products.resources :comments
30 # products.resources :sales, :collection => { :recent => :get }
31 # end
32
33 # Sample resource route within a namespace:
34 # map.namespace :admin do |admin|
35 # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
36 # admin.resources :products
37 # end
38
39 # You can have the root of your site routed with map.root -- just remember to delete public/index.html.
40 # map.root :controller => "welcome"
41
42 # See how all your routes lay out with "rake routes"
43
44 # Install the default routes as the lowest priority.
45 # Note: These default routes make all actions in every controller accessible via GET requests. You should
46 # consider removing the them or commenting them out if you're using named routes and resources.
47 map.connect ':controller/:action/:id'
48 map.connect ':controller/:action/:id.:format'
49end
  
1Use this README file to introduce your application and point to useful places in the API for learning more.
2Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
  
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
6<head>
7 <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
8 <title>The page you were looking for doesn't exist (404)</title>
9 <style type="text/css">
10 body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
11 div.dialog {
12 width: 25em;
13 padding: 0 4em;
14 margin: 4em auto 0 auto;
15 border: 1px solid #ccc;
16 border-right-color: #999;
17 border-bottom-color: #999;
18 }
19 h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
20 </style>
21</head>
22
23<body>
24 <!-- This file lives in public/404.html -->
25 <div class="dialog">
26 <h1>The page you were looking for doesn't exist.</h1>
27 <p>You may have mistyped the address or the page may have moved.</p>
28 </div>
29</body>
30</html>
  
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
6<head>
7 <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
8 <title>The change you wanted was rejected (422)</title>
9 <style type="text/css">
10 body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
11 div.dialog {
12 width: 25em;
13 padding: 0 4em;
14 margin: 4em auto 0 auto;
15 border: 1px solid #ccc;
16 border-right-color: #999;
17 border-bottom-color: #999;
18 }
19 h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
20 </style>
21</head>
22
23<body>
24 <!-- This file lives in public/422.html -->
25 <div class="dialog">
26 <h1>The change you wanted was rejected.</h1>
27 <p>Maybe you tried to change something you didn't have access to.</p>
28 </div>
29</body>
30</html>
  
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
6<head>
7 <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
8 <title>We're sorry, but something went wrong (500)</title>
9 <style type="text/css">
10 body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
11 div.dialog {
12 width: 25em;
13 padding: 0 4em;
14 margin: 4em auto 0 auto;
15 border: 1px solid #ccc;
16 border-right-color: #999;
17 border-bottom-color: #999;
18 }
19 h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
20 </style>
21</head>
22
23<body>
24 <!-- This file lives in public/500.html -->
25 <div class="dialog">
26 <h1>We're sorry, but something went wrong.</h1>
27 <p>We've been notified about this issue and we'll take a look at it shortly.</p>
28 </div>
29</body>
30</html>
  
1/* Graphite */
2/*html, body {color: #222; background: #222;}
3.page-header {color: white; background: #444;}
4.page-header .main-nav a {background: #666;}
5.page-header .main-nav a:hover {background: #222;}
6.content {background: white;}
7.button {color: white; background: #666;}
8.button:hover {background-color: #222;}
9.add-to-collection {background: #f5f5f5;}
10*/
11/* Cubicle Blue */
12html, body {color: #193440; background: #193440;}
13.page-header {color: white; background: #3F606E;}
14.page-header .navigation.main-nav a {background: #5B8BA0;}
15.page-header .navigation.main-nav li.current a {background: #FCFFF4; color: #222;}
16.page-header .navigation.main-nav a:hover {background: #193440;}
17.section.content {background: #FCFFF5;}
18.button {color: white; background: #5B8BA0;}
19.button:hover {background-color: #193440;}
20.add-to-collection {background: #E6E7DE;}
21.aside { background: #E5E5E5;}
22
23/* Column Layout */
24
25.section-group {display: table; width: 100%;}
26.section-group-inner {display: table-row;}
27.section-group-inner > * {display: table-cell; vertical-align: top;}
28.aside {width: 210px;} /* works with % too */
29.aside { padding: 20px;}
30/* ------ */
31
32
33body {
34 width: 960px;
35 margin: 0 auto 20px;
36 font: 12px "Lucida Grande", "Trebuchet MS", Arial, sans-serif; line-height: 18px;
37}
38h1, h2, h3 {font-weight: normal;}
39h1 {margin: 20px 0 10px; font-size: 22px; line-height: 22px;}
40h2 {margin: 15px 0 10px; font-size: 18px; line-height: 18px;}
41h3 {margin: 10px 0 5px; font-size: 16px; line-height: 16px;}
42h4 {margin: 10px 0 5px; font-size: 14px; line-height: 14px;}
43h5 {margin: 10px 0 5px; font-size: 12px; line-height: 12px;}
44h6 {margin: 10px 0 5px; font-size: 10px; line-height: 10px;}
45
46li {margin-left: 20px;}
47
48a {
49 border-bottom: 1px dotted #ccc;
50 color: #222; background: #fafafa;
51 text-decoration: none;
52}
53a:hover {
54 border-bottom: 1px dotted #aaa;
55 color: black; background: #f2f2f2;
56}
57h1 a, h2 a, h3 a {border: none; background: none;}
58
59pre, code {
60 font-family: "Courier New", Courier, monospace;
61}
62
63input.text, input.string, input.email-address, input.password, input.search, input.integer, input.float, textarea {
64 border-top:1px solid #7c7c7c;
65 border-left:1px solid #c3c3c3;
66 border-right:1px solid #c3c3c3;
67 border-bottom:1px solid #ddd;
68 background: #fff url(../images/fieldbg.gif) repeat-x top;
69 font-size: 1.1em; line-height: 1.3em;
70}
71
72input.file_upload {
73 border: 1px dotted #666;
74}
75
76.button {
77 padding: 6px 10px;
78 border: none;
79 width: auto;
80 font-size: 11px; font-weight: bold;
81 margin-top: 10px;
82}
83.button:hover {cursor: pointer;}
84form .actions {_zoom: 1; overflow: hidden; font-size: 11px;}
85form .actions input { margin: 0; }
86
87.flash {
88 margin: 0 40px 10px; padding: 10px 30px; border-width: 2px 0;
89 color: white;
90}
91.flash.notice {background: #92ab6e;}
92.flash.error {background: #BC1C3D;}
93.section.with-flash { padding-top: 20px; }
94
95/* rails error message */
96.error-messages {
97 background: #BC1C3D;
98 border: 1px solid #900024;
99 padding: 15px 30px;
100 color: white;
101 margin-bottom: 20px;
102}
103.error-messages h2 {
104 color: white; margin-top: 0; padding-bottom: 0; font-size: 16px;
105}
106.error-messages li {margin-left: 20px; list-style: square;}
107
108.field-with-errors input, .field-with-errors textarea, .field-with-errors select {border: 2px solid #BC1C3D;}
109
110#ajax-progress {
111 padding: 8px 20px 8px 40px;
112 border: 1px solid #444;
113 background: black url(../images/spinner.gif) no-repeat 10px 8px;
114 color: white;
115}
116
117.article {margin: 20px 0; border-top: 1px dotted #ccc;}
118
119.field-list th {width: 120px; white-space: nowrap;}
120.field-list td {width: auto;}
121.field-list .input-help { color: #888;}
122
123.content-header, .content-body, .content-footer {margin: 0 45px 15px; padding: 0;}
124.content-header {padding: 5px 0;}
125.content-body {padding: 15px 0;}
126.content-footer {padding-bottom: 20px;}
127
128.page-header {margin-top: 25px; padding: 0 0 0;}
129.page-header h1 {
130 margin: 0; padding: 20px 30px 30px;
131 font-family: "Arial Black", Tahoma, Arial, sans-serif; font-size: 36px; letter-spacing: -1.5pt;
132}
133.page-header ul {zoom: 1; overflow: hidden;}
134.page-header li {float: left; margin-left: 0; list-style: none;}
135
136.page-header .navigation a,
137.page-header .navigation a:hover,
138.page-header h1 a,
139.page-header h1 a:hover
140 {border: none; color: white; background: none;}
141
142.page-header div.search {
143 float: right;
144 padding: 0 30px 8px 15px;
145 }
146.page-header div.search label {
147 padding-right: 10px;
148 font: bold 9px Arial, sans-serif; text-transform: uppercase; letter-spacing: 1.0pt;
149}
150.page-header div.search input {
151 font-size: 10px;
152}
153#search-results-panel {
154 position: absolute; top: 35px; right: 25px; z-index: 50;
155 width: 350px; height: 500px; overflow: auto;
156 padding: 0 20px 20px; border: 1px solid #ddd;
157 color: black; background: #f2f2f2;
158}
159#search-results-panel .card.linkable a {color: black;}
160#search-spinner {background:black;border:1px solid #666666;opacity:0.6;padding:2px;position:absolute;right:4px;top:6px;}
161
162.main-nav {padding: 0 30px;}
163.main-nav li {margin-right: 10px;}
164.page-header .main-nav a {
165 display: block;
166 padding: 5px 15px 7px;
167 font-size: 13px; font-weight: bold;
168}
169
170.account-nav {
171 float:right;
172 margin: -22px 5px 0 0;
173 font-size: 11px;
174}
175.account-nav li {
176 float: left;
177 margin-left: 0; padding-left: 20px;
178 color: #ddd;
179 list-style: none;
180}
181.account-nav a {font-weight: bold;}
182.account-nav a:hover {border-bottom: 1px dotted #ddd;}
183
184.user-account-page .change-password {width: 350px;}
185.user-account-page .change-password th {width: 150px;}
186
187.page-footer {text-align: right; color: #ccc; padding: 5px; font-size: 11px;}
188
189/* pagination nav, needs a more specific class on the wrapper */
190.content-body .nav {margin-bottom: 10px; font-size: 11px;}
191.content-body .nav a {margin-right: 5px;}
192
193.login-page, .forgot-password-page {width: 470px; margin-top: 40px;}
194.login-page .content-header {padding-bottom: 0;}
195.login-page .field-list {width: 370px;}
196.login-page form .actions {text-align: left; margin: 0; padding: 10px 0 10px 160px;}
197.signup-page .field-list {width: 370px;}
198.signup-page .content-body, .signup-page .content-header { margin-left: 120px; margin-right: 120px;}
199.login-page .field-list td, .signup-page .field-list td {width: auto;}
200.login-page .field-list th, .signup-page .field-list th {width: 150px !important; width: 150px;}
201.login-page .content-header { position: relative; }
202.login-page .forgot-password {font-size: 11px; margin-left: 160px;}
203
204.edit-page .content-header {overflow: hidden; _zoom: 1;}
205.edit-page .content-header h1 {float: left;}
206.edit-page .content-header .delete-button {float: right; margin-top: 25px;}
207form .actions {margin: 30px 0; width: 100%; text-align: center;}
208
209.show-page .content-header {position: relative; border-bottom: 1px dotted #888;}
210.show-page .content-header h1, .new-in-collection-page .content-header h1 {margin-bottom: 2px; margin-right: 70px;}
211.show-page .content-header a.edit-link {position: absolute; top: 30px; right: 0;}
212.aside-content .collection {padding-bottom: 10px;}
213
214.content-header .creation-details {font-size: 11px; line-height: 11px;}
215.content-header .creator {margin-right: 15px;}
216.content-header .created-at {color: #444;}
217
218.new-in-collection-page .content-header h2 {margin-top: 6px; font-size: 14px;}
219.new-in-collection-page .content-header h2, .new-in-collection-page .content-header h2 a {color: #222;}
220
221.add-to-collection {padding: 20px 30px; margin-top: 20px;}
222.collection-section .add-to-collection h3 {margin: 0 0 20px; padding: 0; border-bottom: none;}
223.add-to-collection form .submit-button {margin: 20px 130px;}
224.add-to-collection .actions {text-align: left; margin-top: 0; margin-bottom: 0;}
225
226/* styling of generic elements */
227.creator {font-weight: bold;}
228.card {
229 overflow: hidden; _zoom: 1;
230 margin: 10px 0; padding: 12px; border: 1px solid #e8e8e8;
231 background: #f5f5f5;
232 position: relative;
233}
234.card h4 {margin-top: 0;}
235.card a {background: #f5f5f5;}
236.card .creation-details {
237 display: block; color: #333; font-size: 11px;
238}
239.card .datetime {color: #666;}
240.card .actions { position:absolute; right: 10px; top: 10px; }
241div.ordering-handle { float: left; background: #ccc; color: white; margin-right: 5px; cursor: move; padding: 0 2px;}
242
243.card.content.with-owner {
244 padding: 0; margin: 10px 0 30px; border: none;
245 background: none;
246 font-size: 11px;
247}
248.card.content .creation-details {
249 float: left; width: 28%;
250 line-height: 14px;
251}
252.card.content .creation-details .created-at {display: block;}
253.card.content.with-owner .content {
254 float: right; width: 72%;
255}
256ul.collection > li { margin-left: 0; list-style: none;}
257.empty-collection-message {margin-top: 20px;}
258
259.new-link {margin-top: 20px;}
260
261.collection-section h3 {
262 padding: 15px 0; margin-bottom: 20px; border-bottom: 1px dotted #888;
263}
264
265.table-plus .header {_zoom: 1; overflow: hidden;}
266.table-plus .header a {float: left; margin-right: 20px;}
267.table-plus div.search {float: right;}
268.table-plus div.search span {color: #444;}
269.table-plus div.search input.search {margin: 0 5px;}
270.table-plus div.search .button {padding: 2px 4px;}
271
272.table-plus table {width: 100%; margin: 20px 0;}
273.table-plus table th {
274 padding: 2px 10px;
275 color: white; background: #444;
276 font-weight: bold; font-size: 10px;
277}
278.table-plus table th a {color: white;}
279.table-plus table td {
280 padding: 6px 10px; border-bottom: 1px solid #e2e2e2; color: #666;
281}
282.table-plus table td input.button, .collection .button {padding: 1px 3px; margin-left: 10px; margin-top: 0;}
283.table-plus table td.controls {width: 100px;}
284.table-plus .even {background: #f8f8f8;}
285.table-plus a {background: none;}
286
287/* <select-many> */
288
289div.select-many {border-top: 1px dotted #999;}
290div.select-many .items {margin-bottom: 10px;}
291div.select-many .item {
292 overflow:hidden; _zoom: 1; font-weight: bold;
293 border-bottom: 1px dotted #999; padding: 5px 10px; /*margin: 5px 25px 5px 0;*/
294}
295div.select-many .item span { float: left; }
296div.select-many .item .remove-item { float: right; }
297
298/* <live-search> */
299
300#search-results-panel { postition: relative; }
301#search-results-panel .close-button { cursor: pointer; text-decoration: underline; position: absolute; top: 3px; right: 6px;}
302
303/*******************************************************/
304/* these styles are for the generated front index page */
305/* you can delete them if you over-ride it */
306
307.front-page .welcome-message {
308 padding: 10px 20px 20px; border: 1px solid #e8e8e8;
309 color: #222; background: #eee;
310}
311.front-page .welcome-message h2 {
312 font-size: 18px; line-height: 27px;
313}
314.front-page ul.models li {margin-left: 0; list-style: none;}
315
316ul.input-many {list-style-type: none;}
317ul.input-all {list-style-type: none;}
318
319ul.input-many > li { overflow:hidden; zoom:1;}
320ul.input-many .input-many-item {float:left;}
321ul.input-many div.buttons {float:left; margin-left:10px;}
322
323ul.check-many { list-style-type: none; margin-left: 0px;}
324ul.check-many li input { vertical-align: -20%;}
  
1.hidden {display: none;}
2
3.in-place-textfield-bhv, .in-place-textarea-bhv, .in-place-html-textarea-bhv {
4 border: 1px dotted #666;
5 padding: 0 3px; padding-right: 20px;
6 background-image: url(../images/pencil.png);
7 background-position: top right;
8 background-repeat: no-repeat;
9}
10
11.inplaceeditor-form input, .inplaceeditor-form textarea,
12table.new-record textarea, table.new-record input {
13 border: 1px dotted #666;
14 padding: 3px; width: 100%;
15}
16.inplaceeditor-form, .inplaceeditor-form input {
17 display: inline;
18}
19
20/**** Admin ****/
21
22.admin-banner {
23 background: #9d0018; border-top: 2px solid #7a0013; border-bottom: 2px solid #7a0013;
24 padding: 2px 0;
25 margin: 10px 0;
26}
27.admin-banner p, .admin-banner span {
28 font: 12px "Lucida Grande", Arial, sans-serif;
29}
30.admin-banner p, .admin-banner div {margin-bottom: 0;}
31.admin-banner a {color: white; text-decoration: none; padding: 1px 5px; font-weight: bold;}
32.admin-banner a:hover {color: #9d0018; background: white;}
33.admin-banner .logged-in {
34 float: right;
35}
36
37/********* everything below here came from hobo_rapid.css, needs looking at ********/
38
39/**** Default styling for Rapid ***/
40
41#ajax-progress {
42 float: right; margin: 20px;
43 position: fixed; display: none; z-index: 10;
44}
45
46/* Scriptaculous Autocompleter ---*/
47
48div.completions-popup {
49 position:absolute;
50 width:250px;
51 background-color:white;
52 border:1px solid #888;
53 margin:0px;
54 padding:0px;
55}
56div.completions-popup ul {
57 list-style-type:none;
58 margin:0px;
59 padding:0px;
60}
61div.completions-popup ul li.selected { background-color: #ffb;}
62div.completions-popup ul li {
63 list-style-type:none;
64 display:block;
65 margin:0;
66 padding:2px;
67 cursor:pointer;
68}
69
70
71.field-list {width:100%;}
72.field-list td {vertical-align: middle;}
73.field-list th {font-weight: bold;}
74.field-list th, .field-list td {padding: 5px 0;}
75.field-list th {padding-right: 10px;}
76
77.field-list td.field-label {
78 text-align: left; width: 1px; white-space: nowrap; vertical-align: top;
79 padding-top: 10px; padding-bottom: 10px;
80}
81.field-list textarea, .field-list input[type=text], .field-list input[type=password] { width: 99%; margin: 0; }
82
83/*input[type=text].wide { width: 100%; }*/
84textarea { height: 170px; }
85textarea.wide { width: 100%; }
86textarea.tall { height: 350px; }
87
88.field-list input.percentage {width: 25px; display: inline; margin-right: 5px; padding: 1px 3px;}
89
90select.dev-user-changer { opacity: 0.3; }
91select.dev-user-changer:hover { opacity: 1; }
92
93.part-wrapper {
94 display: inline; /* don't mess up layout when wrapping something */
95}
Binary files differ
  
1/* IE7/IE8.js - copyright 2004-2008, Dean Edwards */
2(function(){IE7={toString:function(){return"IE7 version 2.0 (beta4)"}};var k=IE7.appVersion=navigator.appVersion.match(/MSIE (\d\.\d)/)[1];if(/ie7_off/.test(top.location.search)||k<5)return;var Q=bG();var C=document.compatMode!="CSS1Compat";var bm=document.documentElement,v,s;var bA="!";var G=":link{ie7-link:link}:visited{ie7-link:visited}";var cj=/^[\w\.]+[^:]*$/;function W(a,b){if(cj.test(a))a=(b||"")+a;return a};function bn(a,b){a=W(a,b);return a.slice(0,a.lastIndexOf("/")+1)};var bB=document.scripts[document.scripts.length-1];var ck=bn(bB.src);try{var H=new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}var X={};function cl(a,b){try{a=W(a,b);if(!X[a]){H.open("GET",a,false);H.send();if(H.status==0||H.status==200){X[a]=H.responseText}}}catch(e){}finally{return X[a]||""}};if(k<5.5){undefined=Q();bA="HTML:!";var cm=/(g|gi)$/;var cn=String.prototype.replace;String.prototype.replace=function(a,b){if(typeof b=="function"){if(a&&a.constructor==RegExp){var c=a;var d=c.global;if(d==null)d=cm.test(c);if(d)c=new RegExp(c.source)}else{c=new RegExp(bb(a))}var f,g=this,h="";while(g&&(f=c.exec(g))){h+=g.slice(0,f.index)+b.apply(this,f);g=g.slice(f.index+f[0].length);if(!d)break}return h+g}return cn.apply(this,arguments)};Array.prototype.pop=function(){if(this.length){var a=this[this.length-1];this.length--;return a}return undefined};Array.prototype.push=function(){for(var a=0;a<arguments.length;a++){this[this.length]=arguments[a]}return this.length};var co=this;Function.prototype.apply=function(a,b){if(a===undefined)a=co;else if(a==null)a=window;else if(typeof a=="string")a=new String(a);else if(typeof a=="number")a=new Number(a);else if(typeof a=="boolean")a=new Boolean(a);if(arguments.length==1)b=[];else if(b[0]&&b[0].writeln)b[0]=b[0].documentElement.document||b[0];var c="#ie7_apply",d;a[c]=this;switch(b.length){case 0:d=a[c]();break;case 1:d=a[c](b[0]);break;case 2:d=a[c](b[0],b[1]);break;case 3:d=a[c](b[0],b[1],b[2]);break;case 4:d=a[c](b[0],b[1],b[2],b[3]);break;case 5:d=a[c](b[0],b[1],b[2],b[3],b[4]);break;default:var f=[],g=b.length-1;do f[g]="a["+g+"]";while(g--);eval("r=o[$]("+f+")")}if(typeof a.valueOf=="function"){delete a[c]}else{a[c]=undefined;if(d&&d.writeln)d=d.documentElement.document||d}return d};Function.prototype.call=function(a){return this.apply(a,bC.apply(arguments,[1]))};G+="address,blockquote,body,dd,div,dt,fieldset,form,"+"frame,frameset,h1,h2,h3,h4,h5,h6,iframe,noframes,object,p,"+"hr,applet,center,dir,menu,pre,dl,li,ol,ul{display:block}"}var bC=Array.prototype.slice;var cJ=/%([1-9])/g;var cp=/^\s\s*/;var cq=/\s\s*$/;var cr=/([\/()[\]{}|*+-.,^$?\\])/g;var bD=/\bbase\b/;var bE=["constructor","toString"];var Y;function z(){};z.extend=function(a,b){Y=true;var c=new this;ba(c,a);Y=false;var d=c.constructor;function f(){if(!Y)d.apply(this,arguments)};c.constructor=f;f.extend=arguments.callee;ba(f,b);f.prototype=c;return f};z.prototype.extend=function(a){return ba(this,a)};var bo="#";var Z="~";var cs=/\\./g;var ct=/\(\?[:=!]|\[[^\]]+\]/g;var cu=/\(/g;var D=z.extend({constructor:function(a){this[Z]=[];this.merge(a)},exec:function(g){var h=this,p=this[Z];return String(g).replace(new RegExp(this,this.ignoreCase?"gi":"g"),function(){var a,b=1,c=0;while((a=h[bo+p[c++]])){var d=b+a.length+1;if(arguments[b]){var f=a.replacement;switch(typeof f){case"function":return f.apply(h,bC.call(arguments,b,d));case"number":return arguments[b+f];default:return f}}b=d}})},add:function(a,b){if(a instanceof RegExp){a=a.source}if(!this[bo+a])this[Z].push(String(a));this[bo+a]=new D.Item(a,b)},merge:function(a){for(var b in a)this.add(b,a[b])},toString:function(){return"("+this[Z].join(")|(")+")"}},{IGNORE:"$0",Item:z.extend({constructor:function(a,b){a=a instanceof RegExp?a.source:String(a);if(typeof b=="number")b=String(b);else if(b==null)b="";if(typeof b=="string"&&/\$(\d+)/.test(b)){if(/^\$\d+$/.test(b)){b=parseInt(b.slice(1))}else{var c=/'/.test(b.replace(/\\./g,""))?'"':"'";b=b.replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\$(\d+)/g,c+"+(arguments[$1]||"+c+c+")+"+c);b=new Function("return "+c+b.replace(/(['"])\1\+(.*)\+\1\1$/,"$1")+c)}}this.length=D.count(a);this.replacement=b;this.toString=bG(a)}}),count:function(a){a=String(a).replace(cs,"").replace(ct,"");return I(a,cu).length}});function ba(a,b){if(a&&b){var c=(typeof b=="function"?Function:Object).prototype;var d=bE.length,f;if(Y)while(f=bE[--d]){var g=b[f];if(g!=c[f]){if(bD.test(g)){bF(a,f,g)}else{a[f]=g}}}for(f in b)if(c[f]===undefined){var g=b[f];if(a[f]&&typeof g=="function"&&bD.test(g)){bF(a,f,g)}else{a[f]=g}}}return a};function bF(c,d,f){var g=c[d];c[d]=function(){var a=this.base;this.base=g;var b=f.apply(this,arguments);this.base=a;return b}};function cv(a,b){if(!b)b=a;var c={};for(var d in a)c[d]=b[d];return c};function i(c){var d=arguments;var f=new RegExp("%([1-"+arguments.length+"])","g");return String(c).replace(f,function(a,b){return b<d.length?d[b]:a})};function I(a,b){return String(a).match(b)||[]};function bb(a){return String(a).replace(cr,"\\$1")};function cK(a){return String(a).replace(cp,"").replace(cq,"")};function bG(a){return function(){return a}};var bH=D.extend({ignoreCase:true});var cw=/\x01(\d+)/g,cx=/'/g,cy=/^\x01/,cz=/\\([\da-fA-F]{1,4})/g;var bp=[];var cA=new bH({"<!\\-\\-|\\-\\->":"","\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/":"","@(namespace|import)[^;\\n]+[;\\n]":"","'(\\\\.|[^'\\\\])*'":bJ,'"(\\\\.|[^"\\\\])*"':bJ,"\\s+":" "});function cB(a){return cA.exec(a)};function bI(c){return c.replace(cw,function(a,b){return bp[b-1]})};function bJ(c){return"\x01"+bp.push(c.replace(cz,function(a,b){return eval("'\\u"+"0000".slice(b.length)+b+"'")}).slice(1,-1).replace(cx,"\\'"))};function cC(a){return cy.test(a)?bp[a.slice(1)-1]:a};var cD=new D({Width:"Height",width:"height",Left:"Top",left:"top",Right:"Bottom",right:"bottom",onX:"onY"});function A(a){return cD.exec(a)};var bK=[];function bq(a){cF(a);w(window,"onresize",a)};function w(a,b,c){a.attachEvent(b,c);bK.push(arguments)};function cE(a,b,c){try{a.detachEvent(b,c)}catch(ignore){}};w(window,"onunload",function(){var a;while(a=bK.pop()){cE(a[0],a[1],a[2])}});function R(a,b,c){if(!a.elements)a.elements={};if(c)a.elements[b.uniqueID]=b;else delete a.elements[b.uniqueID];return c};w(window,"onbeforeprint",function(){if(!IE7.CSS.print)new bw("print");IE7.CSS.print.recalc()});var bL=/^\d+(px)?$/i;var J=/^\d+%$/;var E=function(a,b){if(bL.test(b))return parseInt(b);var c=a.style.left;var d=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;a.style.left=b||0;b=a.style.pixelLeft;a.style.left=c;a.runtimeStyle.left=d;return b};var br="ie7-";var bM=z.extend({constructor:function(){this.fixes=[];this.recalcs=[]},init:Q});var bs=[];function cF(a){bs.push(a)};IE7.recalc=function(){IE7.HTML.recalc();IE7.CSS.recalc();for(var a=0;a<bs.length;a++)bs[a]()};function bc(a){return a.currentStyle["ie7-position"]=="fixed"};function bt(a,b){return a.currentStyle[br+b]||a.currentStyle[b]};function K(a,b,c){if(a.currentStyle[br+b]==null){a.runtimeStyle[br+b]=a.currentStyle[b]}a.runtimeStyle[b]=c};function bN(a){var b=document.createElement(a||"object");b.style.cssText="position:absolute;padding:0;display:block;border:none;clip:rect(0 0 0 0);left:-9999";b.ie7_anon=true;return b};function B(a,b,c){if(!be[a]){F=[];var d="";var f=T.escape(a).split(",");for(var g=0;g<f.length;g++){o=m=x=0;S=f.length>1?2:0;var h=T.exec(f[g])||"if(0){";if(o){h+=i("if(e%1.nodeName!='!'){",m)}var p=S>1?bV:"";h+=i(p+bW,m);h+=Array(I(h,/\{/g).length+1).join("}");d+=h}eval(i(bX,F)+T.unescape(d)+"return s?null:r}");be[a]=_h}return be[a](b||document,c)};var bd=k<6;var bO=/^(href|src)$/;var bu={"class":"className","for":"htmlFor"};IE7._5=1;IE7._e=function(a,b){var c=a.all[b]||null;if(!c||c.id==b)return c;for(var d=0;d<c.length;d++){if(c[d].id==b)return c[d]}return null};IE7._f=function(a,b){if(b=="src"&&a.pngSrc)return a.pngSrc;var c=bd?(a.attributes[b]||a.attributes[bu[b.toLowerCase()]]):a.getAttributeNode(b);if(c&&(c.specified||b=="value")){if(bO.test(b)){return a.getAttribute(b,2)}else if(b=="class"){return a.className.replace(/\sie7_\w+/g,"")}else if(b=="style"){return a.style.cssText}else{return c.nodeValue}}return null};var bP="colSpan,rowSpan,vAlign,dateTime,accessKey,tabIndex,encType,maxLength,readOnly,longDesc";ba(bu,cv(bP.toLowerCase().split(","),bP.split(",")));IE7._a=function(a){while(a&&(a=a.nextSibling)&&(a.nodeType!=1||a.nodeName=="!"))continue;return a};IE7._b=function(a){while(a&&(a=a.previousSibling)&&(a.nodeType!=1||a.nodeName=="!"))continue;return a};var cG=/([\s>+~,]|[^(]\+|^)([#.:\[])/g,cH=/(^|,)([^\s>+~])/g,cI=/\s*([\s>+~(),]|^|$)\s*/g,bQ=/\s\*\s/g;var bR=D.extend({constructor:function(a){this.base(a);this.sorter=new D;this.sorter.add(/:not\([^)]*\)/,D.IGNORE);this.sorter.add(/([ >](\*|[\w-]+))([^: >+~]*)(:\w+-child(\([^)]+\))?)([^: >+~]*)/,"$1$3$6$4")},ignoreCase:true,escape:function(a){return this.optimise(this.format(a))},format:function(a){return a.replace(cI,"$1").replace(cH,"$1 $2").replace(cG,"$1*$2")},optimise:function(a){return this.sorter.exec(a.replace(bQ,">* "))},unescape:function(a){return bI(a)}});var bS={"":"%1!=null","=":"%1=='%2'","~=":/(^| )%1( |$)/,"|=":/^%1(-|$)/,"^=":/^%1/,"$=":/%1$/,"*=":/%1/};var bT={"first-child":"!IE7._b(e%1)","link":"e%1.currentStyle['ie7-link']=='link'","visited":"e%1.currentStyle['ie7-link']=='visited'"};var bv="var p%2=0,i%2,e%2,n%2=e%1.";var bU="e%1.sourceIndex";var bV="var g="+bU+";if(!p[g]){p[g]=1;";var bW="r[r.length]=e%1;if(s)return e%1;";var bX="var _h=function(e0,s){IE7._5++;var r=[],p={},reg=[%1],d=document;";var F;var m;var o;var x;var S;var be={};var T=new bR({" (\\*|[\\w-]+)#([\\w-]+)":function(a,b,c){o=false;var d="var e%2=IE7._e(d,'%4');if(e%2&&";if(b!="*")d+="e%2.nodeName=='%3'&&";d+="(e%1==d||e%1.contains(e%2))){";if(x)d+=i("i%1=n%1.length;",x);return i(d,m++,m,b.toUpperCase(),c)}," (\\*|[\\w-]+)":function(a,b){S++;o=b=="*";var c=bv;c+=(o&&bd)?"all":"getElementsByTagName('%3')";c+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";return i(c,m++,x=m,b.toUpperCase())},">(\\*|[\\w-]+)":function(a,b){var c=x;o=b=="*";var d=bv;d+=c?"children":"childNodes";if(!o&&c)d+=".tags('%3')";d+=";for(i%2=0;(e%2=n%2[i%2]);i%2++){";if(o){d+="if(e%2.nodeType==1){";o=bd}else{if(!c)d+="if(e%2.nodeName=='%3'){"}return i(d,m++,x=m,b.toUpperCase())},"\\+(\\*|[\\w-]+)":function(a,b){var c="";if(o)c+="if(e%1.nodeName!='!'){";o=false;c+="e%1=IE7._a(e%1);if(e%1";if(b!="*")c+="&&e%1.nodeName=='%2'";c+="){";return i(c,m,b.toUpperCase())},"~(\\*|[\\w-]+)":function(a,b){var c="";if(o)c+="if(e%1.nodeName!='!'){";o=false;S=2;c+="while(e%1=e%1.nextSibling){if(e%1.ie7_adjacent==IE7._5)break;if(";if(b=="*"){c+="e%1.nodeType==1";if(bd)c+="&&e%1.nodeName!='!'"}else c+="e%1.nodeName=='%2'";c+="){e%1.ie7_adjacent=IE7._5;";return i(c,m,b.toUpperCase())},"#([\\w-]+)":function(a,b){o=false;var c="if(e%1.id=='%2'){";if(x)c+=i("i%1=n%1.length;",x);return i(c,m,b)},"\\.([\\w-]+)":function(a,b){o=false;F.push(new RegExp("(^|\\s)"+bb(b)+"(\\s|$)"));return i("if(e%1.className&&reg[%2].test(e%1.className)){",m,F.length-1)},"\\[([\\w-]+)\\s*([^=]?=)?\\s*([^\\]]*)\\]":function(a,b,c,d){var f=bu[b]||b;if(c){var g="e%1.getAttribute('%2',2)";if(!bO.test(b)){g="e%1.%3||"+g}b=i("("+g+")",m,b,f)}else{b=i("IE7._f(e%1,'%2')",m,b)}var h=bS[c||""]||"0";if(h&&h.source){F.push(new RegExp(i(h.source,bb(T.unescape(d)))));h="reg[%2].test(%1)";d=F.length-1}return"if("+i(h,b,d)+"){"},":+([\\w-]+)(\\(([^)]+)\\))?":function(a,b,c,d){b=bT[b];return"if("+(b?i(b,m,d||""):"0")+"){"}});var bY=/a(#[\w-]+)?(\.[\w-]+)?:(hover|active)/i;var bZ=/\s*\{\s*/,ca=/\s*\}\s*/,cb=/\s*\,\s*/;var cc=/(.*)(:first-(line|letter))/;var y=document.styleSheets;IE7.CSS=new(bM.extend({parser:new bH,screen:"",print:"",styles:[],rules:[],pseudoClasses:k<7?"first\\-child":"",dynamicPseudoClasses:{toString:function(){var a=[];for(var b in this)a.push(b);return a.join("|")}},init:function(){var a="^\x01$";var b="\\[class=?[^\\]]*\\]";var c=[];if(this.pseudoClasses)c.push(this.pseudoClasses);var d=this.dynamicPseudoClasses.toString();if(d)c.push(d);c=c.join("|");var f=k<7?["[>+~[(]|([:.])\\w+\\1"]:[b];if(c)f.push(":("+c+")");this.UNKNOWN=new RegExp(f.join("|")||a,"i");var g=k<7?["\\[[^\\]]+\\]|[^\\s(\\[]+\\s*[+~]"]:[b];var h=g.concat();if(c)h.push(":("+c+")");n.COMPLEX=new RegExp(h.join("|")||a,"ig");if(this.pseudoClasses)g.push(":("+this.pseudoClasses+")");L.COMPLEX=new RegExp(g.join("|")||a,"i");L.MATCH=new RegExp(d?"(.*):("+d+")(.*)":a,"i");this.createStyleSheet();this.refresh()},addEventHandler:function(){w.apply(null,arguments)},addFix:function(a,b){this.parser.add(a,b)},addRecalc:function(c,d,f,g){d=new RegExp("([{;\\s])"+c+"\\s*:\\s*"+d+"[^;}]*");var h=this.recalcs.length;if(g)g=c+":"+g;this.addFix(d,function(a,b){return(g?b+g:a)+";ie7-"+a.slice(1)+";ie7_recalc"+h+":1"});this.recalcs.push(arguments);return h},apply:function(){this.getInlineStyles();new bw("screen");this.trash()},createStyleSheet:function(){this.styleSheet=document.createStyleSheet();this.styleSheet.ie7=true;this.styleSheet.owningElement.ie7=true;this.styleSheet.cssText=G},getInlineStyles:function(){var a=document.getElementsByTagName("style"),b;for(var c=a.length-1;(b=a[c]);c--){if(!b.disabled&&!b.ie7){this.styles.push(b.innerHTML)}}},getText:function(a,b){try{var c=a.cssText}catch(e){c=""}if(H)c=cl(a.href,b)||c;return c},recalc:function(){this.screen.recalc();var a=/ie7_recalc\d+/g;var b=G.match(/[{,]/g).length;var c=b+(this.screen.cssText.match(/\{/g)||"").length;var d=this.styleSheet.rules,f;var g,h,p,t,q,j,u,l;for(q=b;q<c;q++){f=d[q];var r=f.style.cssText;if(f&&(g=r.match(a))){p=B(f.selectorText);if(p.length)for(j=0;j<g.length;j++){l=g[j];h=IE7.CSS.recalcs[l.slice(10)][2];for(u=0;(t=p[u]);u++){if(t.currentStyle[l])h(t,r)}}}}},refresh:function(){this.styleSheet.cssText=G+this.screen+this.print},trash:function(){for(var a=0;a<y.length;a++){if(!y[a].ie7){try{var b=y[a].cssText}catch(e){b=""}if(b)y[a].cssText=""}}}}));var bw=z.extend({constructor:function(a){this.media=a;this.load();IE7.CSS[a]=this;IE7.CSS.refresh()},createRule:function(a,b){if(IE7.CSS.UNKNOWN.test(a)){var c;if(bf&&(c=a.match(bf.MATCH))){return new bf(c[1],c[2],b)}else if(c=a.match(L.MATCH)){if(!bY.test(c[0])||L.COMPLEX.test(c[0])){return new L(a,c[1],c[2],c[3],b)}}else return new n(a,b)}return a+" {"+b+"}"},getText:function(){var h=[].concat(IE7.CSS.styles);var p=/@media\s+([^{]*)\{([^@]+\})\s*\}/gi;var t=/\ball\b|^$/i,q=/\bscreen\b/i,j=/\bprint\b/i;function u(a,b){l.value=b;return a.replace(p,l)};function l(a,b,c){b=r(b);switch(b){case"screen":case"print":if(b!=l.value)return"";case"all":return c}return""};function r(a){if(t.test(a))return"all";else if(q.test(a))return(j.test(a))?"all":"screen";else if(j.test(a))return"print"};var N=this;function O(a,b,c,d){var f="";if(!d){c=r(a.media);d=0}if(c=="all"||c==N.media){if(d<3){for(var g=0;g<a.imports.length;g++){f+=O(a.imports[g],bn(a.href,b),c,d+1)}}f+=cB(a.href?cg(a,b):h.pop()||"");f=u(f,N.media)}return f};var bl={};function cg(a,b){var c=W(a.href,b);if(bl[c])return"";bl[c]=(a.disabled)?"":ci(IE7.CSS.getText(a,b),bn(a.href,b));return bl[c]};var ch=/(url\s*\(\s*['"]?)([\w\.]+[^:\)]*['"]?\))/gi;function ci(a,b){return a.replace(ch,"$1"+b.slice(0,b.lastIndexOf("/")+1)+"$2")};for(var P=0;P<y.length;P++){if(!y[P].disabled&&!y[P].ie7){this.cssText+=O(y[P])}}},load:function(){this.cssText="";this.getText();this.parse();this.cssText=bI(this.cssText);X={}},parse:function(){this.cssText=IE7.CSS.parser.exec(this.cssText);var a=IE7.CSS.rules.length;var b=this.cssText.split(ca),c;var d,f,g,h;for(g=0;g<b.length;g++){c=b[g].split(bZ);d=c[0].split(cb);f=c[1];for(h=0;h<d.length;h++){d[h]=f?this.createRule(d[h],f):""}b[g]=d.join("\n")}this.cssText=b.join("\n");this.rules=IE7.CSS.rules.slice(a)},recalc:function(){var a,b;for(b=0;(a=this.rules[b]);b++)a.recalc()},toString:function(){return"@media "+this.media+"{"+this.cssText+"}"}});var bf;var n=IE7.Rule=z.extend({constructor:function(a,b){this.id=IE7.CSS.rules.length;this.className=n.PREFIX+this.id;a=a.match(cc)||a||"*";this.selector=a[1]||a;this.selectorText=this.parse(this.selector)+(a[2]||"");this.cssText=b;this.MATCH=new RegExp("\\s"+this.className+"(\\s|$)","g");IE7.CSS.rules.push(this);this.init()},init:Q,add:function(a){a.className+=" "+this.className},recalc:function(){var a=B(this.selector);for(var b=0;b<a.length;b++)this.add(a[b])},parse:function(a){var b=a.replace(n.CHILD," ").replace(n.COMPLEX,"");if(k<7)b=b.replace(n.MULTI,"");var c=I(b,n.TAGS).length-I(a,n.TAGS).length;var d=I(b,n.CLASSES).length-I(a,n.CLASSES).length+1;while(d>0&&n.CLASS.test(b)){b=b.replace(n.CLASS,"");d--}while(c>0&&n.TAG.test(b)){b=b.replace(n.TAG,"$1*");c--}b+="."+this.className;d=Math.min(d,2);c=Math.min(c,2);var f=-10*d-c;if(f>0){b=b+","+n.MAP[f]+" "+b}return b},remove:function(a){a.className=a.className.replace(this.MATCH,"$1")},toString:function(){return i("%1 {%2}",this.selectorText,this.cssText)}},{CHILD:/>/g,CLASS:/\.[\w-]+/,CLASSES:/[.:\[]/g,MULTI:/(\.[\w-]+)+/g,PREFIX:"ie7_class",TAG:/^\w+|([\s>+~])\w+/,TAGS:/^\w|[\s>+~]\w/g,MAP:{1:"html",2:"html body",10:".ie7_html",11:"html.ie7_html",12:"html.ie7_html body",20:".ie7_html .ie7_body",21:"html.ie7_html .ie7_body",22:"html.ie7_html body.ie7_body"}});var L=n.extend({constructor:function(a,b,c,d,f){this.attach=b||"*";this.dynamicPseudoClass=IE7.CSS.dynamicPseudoClasses[c];this.target=d;this.base(a,f)},recalc:function(){var a=B(this.attach),b;for(var c=0;b=a[c];c++){var d=this.target?B(this.target,b):[b];if(d.length)this.dynamicPseudoClass.apply(b,d,this)}}});var cd=z.extend({constructor:function(a,b){this.name=a;this.apply=b;this.instances={};IE7.CSS.dynamicPseudoClasses[a]=this},register:function(a){var b=a[2];a.id=b.id+a[0].uniqueID;if(!this.instances[a.id]){var c=a[1],d;for(d=0;d<c.length;d++)b.add(c[d]);this.instances[a.id]=a}},unregister:function(a){if(this.instances[a.id]){var b=a[2];var c=a[1],d;for(d=0;d<c.length;d++)b.remove(c[d]);delete this.instances[a.id]}}});if(k<7){var U=new cd("hover",function(a){var b=arguments;IE7.CSS.addEventHandler(a,k<5.5?"onmouseover":"onmouseenter",function(){U.register(b)});IE7.CSS.addEventHandler(a,k<5.5?"onmouseout":"onmouseleave",function(){U.unregister(b)})});w(document,"onmouseup",function(){var a=U.instances;for(var b in a)if(!a[b][0].contains(event.srcElement))U.unregister(a[b])})}IE7.HTML=new(bM.extend({fixed:{},init:Q,addFix:function(){this.fixes.push(arguments)},apply:function(){for(var a=0;a<this.fixes.length;a++){var b=B(this.fixes[a][0]);var c=this.fixes[a][1];for(var d=0;d<b.length;d++)c(b[d])}},addRecalc:function(){this.recalcs.push(arguments)},recalc:function(){for(var a=0;a<this.recalcs.length;a++){var b=B(this.recalcs[a][0]);var c=this.recalcs[a][1],d;var f=Math.pow(2,a);for(var g=0;(d=b[g]);g++){var h=d.uniqueID;if((this.fixed[h]&f)==0){d=c(d)||d;this.fixed[h]|=f}}}}}));if(k<7){document.createElement("abbr");IE7.HTML.addRecalc("label",function(a){if(!a.htmlFor){var b=B("input,textarea",a,true);if(b){w(a,"onclick",function(){b.click()})}}})}var V="[.\\d]";new function(_){var layout=IE7.Layout=this;G+="*{boxSizing:content-box}";IE7.hasLayout=k<5.5?function(a){return a.clientWidth}:function(a){return a.currentStyle.hasLayout};layout.boxSizing=function(a){if(!IE7.hasLayout(a)){a.style.height="0cm";if(a.currentStyle.verticalAlign=="auto")a.runtimeStyle.verticalAlign="top";collapseMargins(a)}};function collapseMargins(a){if(a!=s&&a.currentStyle.position!="absolute"){collapseMargin(a,"marginTop");collapseMargin(a,"marginBottom")}};function collapseMargin(a,b){if(!a.runtimeStyle[b]){var c=a.parentElement;if(c&&IE7.hasLayout(c)&&!IE7[b=="marginTop"?"_b":"_a"](a))return;var d=B(">*:"+(b=="marginTop"?"first":"last")+"-child",a,true);if(d&&d.currentStyle.styleFloat=="none"&&IE7.hasLayout(d)){collapseMargin(d,b);margin=_9(a,a.currentStyle[b]);childMargin=_9(d,d.currentStyle[b]);if(margin<0||childMargin<0){a.runtimeStyle[b]=margin+childMargin}else{a.runtimeStyle[b]=Math.max(childMargin,margin)}d.runtimeStyle[b]="0px"}}};function _9(a,b){return b=="auto"?0:E(a,b)};var UNIT=/^[.\d][\w%]*$/,AUTO=/^(auto|0cm)$/;var applyWidth,applyHeight;IE7.Layout.borderBox=function(a){applyWidth(a);applyHeight(a)};var fixWidth=function(g){applyWidth=function(a){if(!J.test(a.currentStyle.width))h(a);collapseMargins(a)};function h(a,b){if(!a.runtimeStyle.fixedWidth){if(!b)b=a.currentStyle.width;a.runtimeStyle.fixedWidth=(UNIT.test(b))?Math.max(0,q(a,b)):b;K(a,"width",a.runtimeStyle.fixedWidth)}};function p(a){if(!bc(a)){var b=a.offsetParent;while(b&&!IE7.hasLayout(b))b=b.offsetParent}return(b||s).clientWidth};function t(a,b){if(J.test(b))return parseInt(parseFloat(b)/100*p(a));return E(a,b)};var q=function(a,b){var c=a.currentStyle["box-sizing"]=="border-box";var d=0;if(C&&!c)d+=j(a)+u(a,"padding");else if(!C&&c)d-=j(a)+u(a,"padding");return t(a,b)+d};function j(a){return a.offsetWidth-a.clientWidth};function u(a,b){return t(a,a.currentStyle[b+"Left"])+t(a,a.currentStyle[b+"Right"])};G+="*{minWidth:none;maxWidth:none;min-width:none;max-width:none}";layout.minWidth=function(a){if(a.currentStyle["min-width"]!=null){a.style.minWidth=a.currentStyle["min-width"]}if(R(arguments.callee,a,a.currentStyle.minWidth!="none")){layout.boxSizing(a);h(a);l(a)}};eval("IE7.Layout.maxWidth="+String(layout.minWidth).replace(/min/g,"max"));function l(a){var b=a.getBoundingClientRect();var c=b.right-b.left;if(a.currentStyle.minWidth!="none"&&c<=q(a,a.currentStyle.minWidth)){a.runtimeStyle.width=a.currentStyle.minWidth}else if(a.currentStyle.maxWidth!="none"&&c>=q(a,a.currentStyle.maxWidth)){a.runtimeStyle.width=a.currentStyle.maxWidth}else{a.runtimeStyle.width=a.runtimeStyle.fixedWidth}};function r(a){if(R(r,a,/^(fixed|absolute)$/.test(a.currentStyle.position)&&bt(a,"left")!="auto"&&bt(a,"right")!="auto"&&AUTO.test(bt(a,"width")))){N(a);IE7.Layout.boxSizing(a)}};IE7.Layout.fixRight=r;function N(a){var b=t(a,a.runtimeStyle._c||a.currentStyle.left);var c=p(a)-t(a,a.currentStyle.right)-b-u(a,"margin");if(parseInt(a.runtimeStyle.width)==c)return;a.runtimeStyle.width="";if(bc(a)||g||a.offsetWidth<c){if(!C)c-=j(a)+u(a,"padding");if(c<0)c=0;a.runtimeStyle.fixedWidth=c;K(a,"width",c)}};var O=0;bq(function(){if(!s)return;var a,b=(O<s.clientWidth);O=s.clientWidth;var c=layout.minWidth.elements;for(a in c){var d=c[a];var f=(parseInt(d.runtimeStyle.width)==q(d,d.currentStyle.minWidth));if(b&&f)d.runtimeStyle.width="";if(b==f)l(d)}var c=layout.maxWidth.elements;for(a in c){var d=c[a];var f=(parseInt(d.runtimeStyle.width)==q(d,d.currentStyle.maxWidth));if(!b&&f)d.runtimeStyle.width="";if(b!=f)l(d)}for(a in r.elements)N(r.elements[a])});if(C){IE7.CSS.addRecalc("width",V,applyWidth)}if(k<7){IE7.CSS.addRecalc("min-width",V,layout.minWidth);IE7.CSS.addRecalc("max-width",V,layout.maxWidth);IE7.CSS.addRecalc("right",V,r)}};eval("var fixHeight="+A(fixWidth));fixWidth();fixHeight(true)};var bg=W("blank.gif",ck);var bh="DXImageTransform.Microsoft.AlphaImageLoader";var bx="progid:"+bh+"(src='%1',sizingMethod='%2')";var bi;var M=[];function by(a){if(bi.test(a.src)){var b=new Image(a.width,a.height);b.onload=function(){a.width=b.width;a.height=b.height;b=null};b.src=a.src;a.pngSrc=a.src;bz(a)}};if(k>=5.5&&k<7){IE7.CSS.addFix(/background(-image)?\s*:\s*([^};]*)?url\(([^\)]+)\)([^;}]*)?/,function(a,b,c,d,f){d=cC(d);return bi.test(d)?"filter:"+i(bx,d,"crop")+";zoom:1;background"+(b||"")+":"+(c||"")+"none"+(f||""):a});IE7.HTML.addRecalc("img,input",function(a){if(a.tagName=="INPUT"&&a.type!="image")return;by(a);w(a,"onpropertychange",function(){if(!bj&&event.propertyName=="src"&&a.src.indexOf(bg)==-1)by(a)})});var bj=false;w(window,"onbeforeprint",function(){bj=true;for(var a=0;a<M.length;a++)ce(M[a])});w(window,"onafterprint",function(){for(var a=0;a<M.length;a++)bz(M[a]);bj=false})}function bz(a,b){var c=a.filters[bh];if(c){c.src=a.src;c.enabled=true}else{a.runtimeStyle.filter=i(bx,a.src,b||"scale");M.push(a)}a.src=bg};function ce(a){a.src=a.pngSrc;a.filters[bh].enabled=false};new function(_){if(k>=7)return;IE7.CSS.addRecalc("position","fixed",_6,"absolute");IE7.CSS.addRecalc("background(-attachment)?","[^};]*fixed",_2);var $viewport=C?"body":"documentElement";function _3(){if(v.currentStyle.backgroundAttachment!="fixed"){if(v.currentStyle.backgroundImage=="none"){v.runtimeStyle.backgroundRepeat="no-repeat";v.runtimeStyle.backgroundImage="url("+bg+")"}v.runtimeStyle.backgroundAttachment="fixed"}_3=Q};var _0=bN("img");function _1(a){return a?bc(a)||_1(a.parentElement):false};function _d(a,b,c){setTimeout("document.all."+a.uniqueID+".runtimeStyle.setExpression('"+b+"','"+c+"')",0)};function _2(a){if(R(_2,a,a.currentStyle.backgroundAttachment=="fixed"&&!a.contains(v))){_3();bgLeft(a);bgTop(a);_8(a)}};function _8(a){_0.src=a.currentStyle.backgroundImage.slice(5,-2);var b=a.canHaveChildren?a:a.parentElement;b.appendChild(_0);setOffsetLeft(a);setOffsetTop(a);b.removeChild(_0)};function bgLeft(a){a.style.backgroundPositionX=a.currentStyle.backgroundPositionX;if(!_1(a)){_d(a,"backgroundPositionX","(parseInt(runtimeStyle.offsetLeft)+document."+$viewport+".scrollLeft)||0")}};eval(A(bgLeft));function setOffsetLeft(a){var b=_1(a)?"backgroundPositionX":"offsetLeft";a.runtimeStyle[b]=getOffsetLeft(a,a.style.backgroundPositionX)-a.getBoundingClientRect().left-a.clientLeft+2};eval(A(setOffsetLeft));function getOffsetLeft(a,b){switch(b){case"left":case"top":return 0;case"right":case"bottom":return s.clientWidth-_0.offsetWidth;case"center":return(s.clientWidth-_0.offsetWidth)/2;default:if(J.test(b)){return parseInt((s.clientWidth-_0.offsetWidth)*parseFloat(b)/100)}_0.style.left=b;return _0.offsetLeft}};eval(A(getOffsetLeft));function _6(a){if(R(_6,a,bc(a))){K(a,"position","absolute");K(a,"left",a.currentStyle.left);K(a,"top",a.currentStyle.top);_3();IE7.Layout.fixRight(a);_4(a)}};function _4(a,b){positionTop(a,b);positionLeft(a,b,true);if(!a.runtimeStyle.autoLeft&&a.currentStyle.marginLeft=="auto"&&a.currentStyle.right!="auto"){var c=s.clientWidth-getPixelWidth(a,a.currentStyle.right)-getPixelWidth(a,a.runtimeStyle._c)-a.clientWidth;if(a.currentStyle.marginRight=="auto")c=parseInt(c/2);if(_1(a.offsetParent))a.runtimeStyle.pixelLeft+=c;else a.runtimeStyle.shiftLeft=c}clipWidth(a);clipHeight(a)};function clipWidth(a){var b=a.runtimeStyle.fixWidth;a.runtimeStyle.borderRightWidth="";a.runtimeStyle.width=b?getPixelWidth(a,b):"";if(a.currentStyle.width!="auto"){var c=a.getBoundingClientRect();var d=a.offsetWidth-s.clientWidth+c.left-2;if(d>=0){a.runtimeStyle.borderRightWidth="0px";d=Math.max(E(a,a.currentStyle.width)-d,0);K(a,"width",d);return d}}};eval(A(clipWidth));function positionLeft(a,b){if(!b&&J.test(a.currentStyle.width)){a.runtimeStyle.fixWidth=a.currentStyle.width}if(a.runtimeStyle.fixWidth){a.runtimeStyle.width=getPixelWidth(a,a.runtimeStyle.fixWidth)}a.runtimeStyle.shiftLeft=0;a.runtimeStyle._c=a.currentStyle.left;a.runtimeStyle.autoLeft=a.currentStyle.right!="auto"&&a.currentStyle.left=="auto";a.runtimeStyle.left="";a.runtimeStyle.screenLeft=getScreenLeft(a);a.runtimeStyle.pixelLeft=a.runtimeStyle.screenLeft;if(!b&&!_1(a.offsetParent)){_d(a,"pixelLeft","runtimeStyle.screenLeft+runtimeStyle.shiftLeft+document."+$viewport+".scrollLeft")}};eval(A(positionLeft));function getScreenLeft(a){var b=a.offsetLeft,c=1;if(a.runtimeStyle.autoLeft){b=s.clientWidth-a.offsetWidth-getPixelWidth(a,a.currentStyle.right)}if(a.currentStyle.marginLeft!="auto"){b-=getPixelWidth(a,a.currentStyle.marginLeft)}while(a=a.offsetParent){if(a.currentStyle.position!="static")c=-1;b+=a.offsetLeft*c}return b};eval(A(getScreenLeft));function getPixelWidth(a,b){return J.test(b)?parseInt(parseFloat(b)/100*s.clientWidth):E(a,b)};eval(A(getPixelWidth));function _g(){var a=_2.elements;for(var b in a)_8(a[b]);a=_6.elements;for(b in a){_4(a[b],true);_4(a[b],true)}_7=0};var _7;bq(function(){if(!_7)_7=setTimeout(_g,0)})};var bk={backgroundColor:"transparent",backgroundImage:"none",backgroundPositionX:null,backgroundPositionY:null,backgroundRepeat:null,borderTopWidth:0,borderRightWidth:0,borderBottomWidth:0,borderLeftStyle:"none",borderTopStyle:"none",borderRightStyle:"none",borderBottomStyle:"none",borderLeftWidth:0,height:null,marginTop:0,marginBottom:0,marginRight:0,marginLeft:0,width:"100%"};IE7.CSS.addRecalc("overflow","visible",function(a){if(a.parentNode.ie7_wrapped)return;if(IE7.Layout&&a.currentStyle["max-height"]!="auto"){IE7.Layout.maxHeight(a)}if(a.currentStyle.marginLeft=="auto")a.style.marginLeft=0;if(a.currentStyle.marginRight=="auto")a.style.marginRight=0;var b=document.createElement(bA);b.ie7_wrapped=a;for(var c in bk){b.style[c]=a.currentStyle[c];if(bk[c]!=null){a.runtimeStyle[c]=bk[c]}}b.style.display="block";b.style.position="relative";a.runtimeStyle.position="absolute";a.parentNode.insertBefore(b,a);b.appendChild(a)});function cf(){var f="xx-small,x-small,small,medium,large,x-large,xx-large".split(",");for(var g=0;g<f.length;g++){f[f[g]]=f[g-1]||"0.67em"}IE7.CSS.addFix(/(font(-size)?\s*:\s*)([\w.-]+)/,function(a,b,c,d){return b+(f[d]||d)});if(k<6){var h=/^\-/,p=/(em|ex)$/i;var t=/em$/i,q=/ex$/i;E=function(a,b){if(bL.test(b))return parseInt(b)||0;var c=h.test(b)?-1:1;if(p.test(b))c*=u(a);j.style.width=(c<0)?b.slice(1):b;v.appendChild(j);b=c*j.offsetWidth;j.removeNode();return parseInt(b)};var j=bN();function u(a){var b=1;j.style.fontFamily=a.currentStyle.fontFamily;j.style.lineHeight=a.currentStyle.lineHeight;while(a!=v){var c=a.currentStyle["ie7-font-size"];if(c){if(t.test(c))b*=parseFloat(c);else if(J.test(c))b*=(parseFloat(c)/100);else if(q.test(c))b*=(parseFloat(c)/2);else{j.style.fontSize=c;return 1}}a=a.parentElement}return b};IE7.CSS.addFix(/cursor\s*:\s*pointer/,"cursor:hand");IE7.CSS.addFix(/display\s*:\s*list-item/,"display:block")}function l(a){if(k<5.5)IE7.Layout.boxSizing(a.parentElement);var b=a.parentElement;var c=b.offsetWidth-a.offsetWidth-r(b);var d=(a.currentStyle["ie7-margin"]&&a.currentStyle.marginRight=="auto")||a.currentStyle["ie7-margin-right"]=="auto";switch(b.currentStyle.textAlign){case"right":c=d?parseInt(c/2):0;a.runtimeStyle.marginRight=c+"px";break;case"center":if(d)c=0;default:if(d)c/=2;a.runtimeStyle.marginLeft=parseInt(c)+"px"}};function r(a){return E(a,a.currentStyle.paddingLeft)+E(a,a.currentStyle.paddingRight)};IE7.CSS.addRecalc("margin(-left|-right)?","[^};]*auto",function(a){if(R(l,a,a.parentElement&&a.currentStyle.display=="block"&&a.currentStyle.marginLeft=="auto"&&a.currentStyle.position!="absolute")){l(a)}});bq(function(){for(var a in l.elements){var b=l.elements[a];b.runtimeStyle.marginLeft=b.runtimeStyle.marginRight="";l(b)}})};IE7.loaded=true;(function(){try{bm.doScroll("left")}catch(e){setTimeout(arguments.callee,1);return}try{eval(bB.innerHTML)}catch(e){}bi=new RegExp(bb(typeof IE7_PNG_SUFFIX=="string"?IE7_PNG_SUFFIX:"-trans.png")+"$","i");v=document.body;s=C?v:bm;v.className+=" ie7_body";bm.className+=" ie7_html";if(C)cf();IE7.CSS.init();IE7.HTML.init();IE7.HTML.apply();IE7.CSS.apply();IE7.recalc()})()})();
  
1// Place your application-specific JavaScript functions and classes here
2// This file is automatically included by javascript_include_tag :defaults
Binary files differ
  
1// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
4// Contributors:
5// Richard Livsey
6// Rahul Bhargava
7// Rob Wills
8//
9// script.aculo.us is freely distributable under the terms of an MIT-style license.
10// For details, see the script.aculo.us web site: http://script.aculo.us/
11
12// Autocompleter.Base handles all the autocompletion functionality
13// that's independent of the data source for autocompletion. This
14// includes drawing the autocompletion menu, observing keyboard
15// and mouse events, and similar.
16//
17// Specific autocompleters need to provide, at the very least,
18// a getUpdatedChoices function that will be invoked every time
19// the text inside the monitored textbox changes. This method
20// should get the text for which to provide autocompletion by
21// invoking this.getToken(), NOT by directly accessing
22// this.element.value. This is to allow incremental tokenized
23// autocompletion. Specific auto-completion logic (AJAX, etc)
24// belongs in getUpdatedChoices.
25//
26// Tokenized incremental autocompletion is enabled automatically
27// when an autocompleter is instantiated with the 'tokens' option
28// in the options parameter, e.g.:
29// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
30// will incrementally autocomplete with a comma as the token.
31// Additionally, ',' in the above example can be replaced with
32// a token array, e.g. { tokens: [',', '\n'] } which
33// enables autocompletion on multiple tokens. This is most
34// useful when one of the tokens is \n (a newline), as it
35// allows smart autocompletion after linebreaks.
36
37if(typeof Effect == 'undefined')
38 throw("controls.js requires including script.aculo.us' effects.js library");
39
40var Autocompleter = { };
41Autocompleter.Base = Class.create({
42 baseInitialize: function(element, update, options) {
43 element = $(element);
44 this.element = element;
45 this.update = $(update);
46 this.hasFocus = false;
47 this.changed = false;
48 this.active = false;
49 this.index = 0;
50 this.entryCount = 0;
51 this.oldElementValue = this.element.value;
52
53 if(this.setOptions)
54 this.setOptions(options);
55 else
56 this.options = options || { };
57
58 this.options.paramName = this.options.paramName || this.element.name;
59 this.options.tokens = this.options.tokens || [];
60 this.options.frequency = this.options.frequency || 0.4;
61 this.options.minChars = this.options.minChars || 1;
62 this.options.onShow = this.options.onShow ||
63 function(element, update){
64 if(!update.style.position || update.style.position=='absolute') {
65 update.style.position = 'absolute';
66 Position.clone(element, update, {
67 setHeight: false,
68 offsetTop: element.offsetHeight
69 });
70 }
71 Effect.Appear(update,{duration:0.15});
72 };
73 this.options.onHide = this.options.onHide ||
74 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
75
76 if(typeof(this.options.tokens) == 'string')
77 this.options.tokens = new Array(this.options.tokens);
78 // Force carriage returns as token delimiters anyway
79 if (!this.options.tokens.include('\n'))
80 this.options.tokens.push('\n');
81
82 this.observer = null;
83
84 this.element.setAttribute('autocomplete','off');
85
86 Element.hide(this.update);
87
88 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
89 Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
90 },
91
92 show: function() {
93 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
94 if(!this.iefix &&
95 (Prototype.Browser.IE) &&
96 (Element.getStyle(this.update, 'position')=='absolute')) {
97 new Insertion.After(this.update,
98 '<iframe id="' + this.update.id + '_iefix" '+
99 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
100 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
101 this.iefix = $(this.update.id+'_iefix');
102 }
103 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
104 },
105
106 fixIEOverlapping: function() {
107 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
108 this.iefix.style.zIndex = 1;
109 this.update.style.zIndex = 2;
110 Element.show(this.iefix);
111 },
112
113 hide: function() {
114 this.stopIndicator();
115 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
116 if(this.iefix) Element.hide(this.iefix);
117 },
118
119 startIndicator: function() {
120 if(this.options.indicator) Element.show(this.options.indicator);
121 },
122
123 stopIndicator: function() {
124 if(this.options.indicator) Element.hide(this.options.indicator);
125 },
126
127 onKeyPress: function(event) {
128 if(this.active)
129 switch(event.keyCode) {
130 case Event.KEY_TAB:
131 case Event.KEY_RETURN:
132 this.selectEntry();
133 Event.stop(event);
134 case Event.KEY_ESC:
135 this.hide();
136 this.active = false;
137 Event.stop(event);
138 return;
139 case Event.KEY_LEFT:
140 case Event.KEY_RIGHT:
141 return;
142 case Event.KEY_UP:
143 this.markPrevious();
144 this.render();
145 Event.stop(event);
146 return;
147 case Event.KEY_DOWN:
148 this.markNext();
149 this.render();
150 Event.stop(event);
151 return;
152 }
153 else
154 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
155 (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
156
157 this.changed = true;
158 this.hasFocus = true;
159
160 if(this.observer) clearTimeout(this.observer);
161 this.observer =
162 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
163 },
164
165 activate: function() {
166 this.changed = false;
167 this.hasFocus = true;
168 this.getUpdatedChoices();
169 },
170
171 onHover: function(event) {
172 var element = Event.findElement(event, 'LI');
173 if(this.index != element.autocompleteIndex)
174 {
175 this.index = element.autocompleteIndex;
176 this.render();
177 }
178 Event.stop(event);
179 },
180
181 onClick: function(event) {
182 var element = Event.findElement(event, 'LI');
183 this.index = element.autocompleteIndex;
184 this.selectEntry();
185 this.hide();
186 },
187
188 onBlur: function(event) {
189 // needed to make click events working
190 setTimeout(this.hide.bind(this), 250);
191 this.hasFocus = false;
192 this.active = false;
193 },
194
195 render: function() {
196 if(this.entryCount > 0) {
197 for (var i = 0; i < this.entryCount; i++)
198 this.index==i ?
199 Element.addClassName(this.getEntry(i),"selected") :
200 Element.removeClassName(this.getEntry(i),"selected");
201 if(this.hasFocus) {
202 this.show();
203 this.active = true;
204 }
205 } else {
206 this.active = false;
207 this.hide();
208 }
209 },
210
211 markPrevious: function() {
212 if(this.index > 0) this.index--;
213 else this.index = this.entryCount-1;
214 this.getEntry(this.index).scrollIntoView(true);
215 },
216
217 markNext: function() {
218 if(this.index < this.entryCount-1) this.index++;
219 else this.index = 0;
220 this.getEntry(this.index).scrollIntoView(false);
221 },
222
223 getEntry: function(index) {
224 return this.update.firstChild.childNodes[index];
225 },
226
227 getCurrentEntry: function() {
228 return this.getEntry(this.index);
229 },
230
231 selectEntry: function() {
232 this.active = false;
233 this.updateElement(this.getCurrentEntry());
234 },
235
236 updateElement: function(selectedElement) {
237 if (this.options.updateElement) {
238 this.options.updateElement(selectedElement);
239 return;
240 }
241 var value = '';
242 if (this.options.select) {
243 var nodes = $(selectedElement).select('.' + this.options.select) || [];
244 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
245 } else
246 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
247
248 var bounds = this.getTokenBounds();
249 if (bounds[0] != -1) {
250 var newValue = this.element.value.substr(0, bounds[0]);
251 var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
252 if (whitespace)
253 newValue += whitespace[0];
254 this.element.value = newValue + value + this.element.value.substr(bounds[1]);
255 } else {
256 this.element.value = value;
257 }
258 this.oldElementValue = this.element.value;
259 this.element.focus();
260
261 if (this.options.afterUpdateElement)
262 this.options.afterUpdateElement(this.element, selectedElement);
263 },
264
265 updateChoices: function(choices) {
266 if(!this.changed && this.hasFocus) {
267 this.update.innerHTML = choices;
268 Element.cleanWhitespace(this.update);
269 Element.cleanWhitespace(this.update.down());
270
271 if(this.update.firstChild && this.update.down().childNodes) {
272 this.entryCount =
273 this.update.down().childNodes.length;
274 for (var i = 0; i < this.entryCount; i++) {
275 var entry = this.getEntry(i);
276 entry.autocompleteIndex = i;
277 this.addObservers(entry);
278 }
279 } else {
280 this.entryCount = 0;
281 }
282
283 this.stopIndicator();
284 this.index = 0;
285
286 if(this.entryCount==1 && this.options.autoSelect) {
287 this.selectEntry();
288 this.hide();
289 } else {
290 this.render();
291 }
292 }
293 },
294
295 addObservers: function(element) {
296 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
297 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
298 },
299
300 onObserverEvent: function() {
301 this.changed = false;
302 this.tokenBounds = null;
303 if(this.getToken().length>=this.options.minChars) {
304 this.getUpdatedChoices();
305 } else {
306 this.active = false;
307 this.hide();
308 }
309 this.oldElementValue = this.element.value;
310 },
311
312 getToken: function() {
313 var bounds = this.getTokenBounds();
314 return this.element.value.substring(bounds[0], bounds[1]).strip();
315 },
316
317 getTokenBounds: function() {
318 if (null != this.tokenBounds) return this.tokenBounds;
319 var value = this.element.value;
320 if (value.strip().empty()) return [-1, 0];
321 var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
322 var offset = (diff == this.oldElementValue.length ? 1 : 0);
323 var prevTokenPos = -1, nextTokenPos = value.length;
324 var tp;
325 for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
326 tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
327 if (tp > prevTokenPos) prevTokenPos = tp;
328 tp = value.indexOf(this.options.tokens[index], diff + offset);
329 if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
330 }
331 return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
332 }
333});
334
335Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
336 var boundary = Math.min(newS.length, oldS.length);
337 for (var index = 0; index < boundary; ++index)
338 if (newS[index] != oldS[index])
339 return index;
340 return boundary;
341};
342
343Ajax.Autocompleter = Class.create(Autocompleter.Base, {
344 initialize: function(element, update, url, options) {
345 this.baseInitialize(element, update, options);
346 this.options.asynchronous = true;
347 this.options.onComplete = this.onComplete.bind(this);
348 this.options.defaultParams = this.options.parameters || null;
349 this.url = url;
350 },
351
352 getUpdatedChoices: function() {
353 this.startIndicator();
354
355 var entry = encodeURIComponent(this.options.paramName) + '=' +
356 encodeURIComponent(this.getToken());
357
358 this.options.parameters = this.options.callback ?
359 this.options.callback(this.element, entry) : entry;
360
361 if(this.options.defaultParams)
362 this.options.parameters += '&' + this.options.defaultParams;
363
364 new Ajax.Request(this.url, this.options);
365 },
366
367 onComplete: function(request) {
368 this.updateChoices(request.responseText);
369 }
370});
371
372// The local array autocompleter. Used when you'd prefer to
373// inject an array of autocompletion options into the page, rather
374// than sending out Ajax queries, which can be quite slow sometimes.
375//
376// The constructor takes four parameters. The first two are, as usual,
377// the id of the monitored textbox, and id of the autocompletion menu.
378// The third is the array you want to autocomplete from, and the fourth
379// is the options block.
380//
381// Extra local autocompletion options:
382// - choices - How many autocompletion choices to offer
383//
384// - partialSearch - If false, the autocompleter will match entered
385// text only at the beginning of strings in the
386// autocomplete array. Defaults to true, which will
387// match text at the beginning of any *word* in the
388// strings in the autocomplete array. If you want to
389// search anywhere in the string, additionally set
390// the option fullSearch to true (default: off).
391//
392// - fullSsearch - Search anywhere in autocomplete array strings.
393//
394// - partialChars - How many characters to enter before triggering
395// a partial match (unlike minChars, which defines
396// how many characters are required to do any match
397// at all). Defaults to 2.
398//
399// - ignoreCase - Whether to ignore case when autocompleting.
400// Defaults to true.
401//
402// It's possible to pass in a custom function as the 'selector'
403// option, if you prefer to write your own autocompletion logic.
404// In that case, the other options above will not apply unless
405// you support them.
406
407Autocompleter.Local = Class.create(Autocompleter.Base, {
408 initialize: function(element, update, array, options) {
409 this.baseInitialize(element, update, options);
410 this.options.array = array;
411 },
412
413 getUpdatedChoices: function() {
414 this.updateChoices(this.options.selector(this));
415 },
416
417 setOptions: function(options) {
418 this.options = Object.extend({
419 choices: 10,
420 partialSearch: true,
421 partialChars: 2,
422 ignoreCase: true,
423 fullSearch: false,
424 selector: function(instance) {
425 var ret = []; // Beginning matches
426 var partial = []; // Inside matches
427 var entry = instance.getToken();
428 var count = 0;
429
430 for (var i = 0; i < instance.options.array.length &&
431 ret.length < instance.options.choices ; i++) {
432
433 var elem = instance.options.array[i];
434 var foundPos = instance.options.ignoreCase ?
435 elem.toLowerCase().indexOf(entry.toLowerCase()) :
436 elem.indexOf(entry);
437
438 while (foundPos != -1) {
439 if (foundPos == 0 && elem.length != entry.length) {
440 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
441 elem.substr(entry.length) + "</li>");
442 break;
443 } else if (entry.length >= instance.options.partialChars &&
444 instance.options.partialSearch && foundPos != -1) {
445 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
446 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
447 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
448 foundPos + entry.length) + "</li>");
449 break;
450 }
451 }
452
453 foundPos = instance.options.ignoreCase ?
454 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
455 elem.indexOf(entry, foundPos + 1);
456
457 }
458 }
459 if (partial.length)
460 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
461 return "<ul>" + ret.join('') + "</ul>";
462 }
463 }, options || { });
464 }
465});
466
467// AJAX in-place editor and collection editor
468// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
469
470// Use this if you notice weird scrolling problems on some browsers,
471// the DOM might be a bit confused when this gets called so do this
472// waits 1 ms (with setTimeout) until it does the activation
473Field.scrollFreeActivate = function(field) {
474 setTimeout(function() {
475 Field.activate(field);
476 }, 1);
477};
478
479Ajax.InPlaceEditor = Class.create({
480 initialize: function(element, url, options) {
481 this.url = url;
482 this.element = element = $(element);
483 this.prepareOptions();
484 this._controls = { };
485 arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
486 Object.extend(this.options, options || { });
487 if (!this.options.formId && this.element.id) {
488 this.options.formId = this.element.id + '-inplaceeditor';
489 if ($(this.options.formId))
490 this.options.formId = '';
491 }
492 if (this.options.externalControl)
493 this.options.externalControl = $(this.options.externalControl);
494 if (!this.options.externalControl)
495 this.options.externalControlOnly = false;
496 this._originalBackground = this.element.getStyle('background-color') || 'transparent';
497 this.element.title = this.options.clickToEditText;
498 this._boundCancelHandler = this.handleFormCancellation.bind(this);
499 this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
500 this._boundFailureHandler = this.handleAJAXFailure.bind(this);
501 this._boundSubmitHandler = this.handleFormSubmission.bind(this);
502 this._boundWrapperHandler = this.wrapUp.bind(this);
503 this.registerListeners();
504 },
505 checkForEscapeOrReturn: function(e) {
506 if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
507 if (Event.KEY_ESC == e.keyCode)
508 this.handleFormCancellation(e);
509 else if (Event.KEY_RETURN == e.keyCode)
510 this.handleFormSubmission(e);
511 },
512 createControl: function(mode, handler, extraClasses) {
513 var control = this.options[mode + 'Control'];
514 var text = this.options[mode + 'Text'];
515 if ('button' == control) {
516 var btn = document.createElement('input');
517 btn.type = 'submit';
518 btn.value = text;
519 btn.className = 'editor_' + mode + '_button';
520 if ('cancel' == mode)
521 btn.onclick = this._boundCancelHandler;
522 this._form.appendChild(btn);
523 this._controls[mode] = btn;
524 } else if ('link' == control) {
525 var link = document.createElement('a');
526 link.href = '#';
527 link.appendChild(document.createTextNode(text));
528 link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
529 link.className = 'editor_' + mode + '_link';
530 if (extraClasses)
531 link.className += ' ' + extraClasses;
532 this._form.appendChild(link);
533 this._controls[mode] = link;
534 }
535 },
536 createEditField: function() {
537 var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
538 var fld;
539 if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
540 fld = document.createElement('input');
541 fld.type = 'text';
542 var size = this.options.size || this.options.cols || 0;
543 if (0 < size) fld.size = size;
544 } else {
545 fld = document.createElement('textarea');
546 fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
547 fld.cols = this.options.cols || 40;
548 }
549 fld.name = this.options.paramName;
550 fld.value = text; // No HTML breaks conversion anymore
551 fld.className = 'editor_field';
552 if (this.options.submitOnBlur)
553 fld.onblur = this._boundSubmitHandler;
554 this._controls.editor = fld;
555 if (this.options.loadTextURL)
556 this.loadExternalText();
557 this._form.appendChild(this._controls.editor);
558 },
559 createForm: function() {
560 var ipe = this;
561 function addText(mode, condition) {
562 var text = ipe.options['text' + mode + 'Controls'];
563 if (!text || condition === false) return;
564 ipe._form.appendChild(document.createTextNode(text));
565 };
566 this._form = $(document.createElement('form'));
567 this._form.id = this.options.formId;
568 this._form.addClassName(this.options.formClassName);
569 this._form.onsubmit = this._boundSubmitHandler;
570 this.createEditField();
571 if ('textarea' == this._controls.editor.tagName.toLowerCase())
572 this._form.appendChild(document.createElement('br'));
573 if (this.options.onFormCustomization)
574 this.options.onFormCustomization(this, this._form);
575 addText('Before', this.options.okControl || this.options.cancelControl);
576 this.createControl('ok', this._boundSubmitHandler);
577 addText('Between', this.options.okControl && this.options.cancelControl);
578 this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
579 addText('After', this.options.okControl || this.options.cancelControl);
580 },
581 destroy: function() {
582 if (this._oldInnerHTML)
583 this.element.innerHTML = this._oldInnerHTML;
584 this.leaveEditMode();
585 this.unregisterListeners();
586 },
587 enterEditMode: function(e) {
588 if (this._saving || this._editing) return;
589 this._editing = true;
590 this.triggerCallback('onEnterEditMode');
591 if (this.options.externalControl)
592 this.options.externalControl.hide();
593 this.element.hide();
594 this.createForm();
595 this.element.parentNode.insertBefore(this._form, this.element);
596 if (!this.options.loadTextURL)
597 this.postProcessEditField();
598 if (e) Event.stop(e);
599 },
600 enterHover: function(e) {
601 if (this.options.hoverClassName)
602 this.element.addClassName(this.options.hoverClassName);
603 if (this._saving) return;
604 this.triggerCallback('onEnterHover');
605 },
606 getText: function() {
607 return this.element.innerHTML.unescapeHTML();
608 },
609 handleAJAXFailure: function(transport) {
610 this.triggerCallback('onFailure', transport);
611 if (this._oldInnerHTML) {
612 this.element.innerHTML = this._oldInnerHTML;
613 this._oldInnerHTML = null;
614 }
615 },
616 handleFormCancellation: function(e) {
617 this.wrapUp();
618 if (e) Event.stop(e);
619 },
620 handleFormSubmission: function(e) {
621 var form = this._form;
622 var value = $F(this._controls.editor);
623 this.prepareSubmission();
624 var params = this.options.callback(form, value) || '';
625 if (Object.isString(params))
626 params = params.toQueryParams();
627 params.editorId = this.element.id;
628 if (this.options.htmlResponse) {
629 var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
630 Object.extend(options, {
631 parameters: params,
632 onComplete: this._boundWrapperHandler,
633 onFailure: this._boundFailureHandler
634 });
635 new Ajax.Updater({ success: this.element }, this.url, options);
636 } else {
637 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
638 Object.extend(options, {
639 parameters: params,
640 onComplete: this._boundWrapperHandler,
641 onFailure: this._boundFailureHandler
642 });
643 new Ajax.Request(this.url, options);
644 }
645 if (e) Event.stop(e);
646 },
647 leaveEditMode: function() {
648 this.element.removeClassName(this.options.savingClassName);
649 this.removeForm();
650 this.leaveHover();
651 this.element.style.backgroundColor = this._originalBackground;
652 this.element.show();
653 if (this.options.externalControl)
654 this.options.externalControl.show();
655 this._saving = false;
656 this._editing = false;
657 this._oldInnerHTML = null;
658 this.triggerCallback('onLeaveEditMode');
659 },
660 leaveHover: function(e) {
661 if (this.options.hoverClassName)
662 this.element.removeClassName(this.options.hoverClassName);
663 if (this._saving) return;
664 this.triggerCallback('onLeaveHover');
665 },
666 loadExternalText: function() {
667 this._form.addClassName(this.options.loadingClassName);
668 this._controls.editor.disabled = true;
669 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
670 Object.extend(options, {
671 parameters: 'editorId=' + encodeURIComponent(this.element.id),
672 onComplete: Prototype.emptyFunction,
673 onSuccess: function(transport) {
674 this._form.removeClassName(this.options.loadingClassName);
675 var text = transport.responseText;
676 if (this.options.stripLoadedTextTags)
677 text = text.stripTags();
678 this._controls.editor.value = text;
679 this._controls.editor.disabled = false;
680 this.postProcessEditField();
681 }.bind(this),
682 onFailure: this._boundFailureHandler
683 });
684 new Ajax.Request(this.options.loadTextURL, options);
685 },
686 postProcessEditField: function() {
687 var fpc = this.options.fieldPostCreation;
688 if (fpc)
689 $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
690 },
691 prepareOptions: function() {
692 this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
693 Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
694 [this._extraDefaultOptions].flatten().compact().each(function(defs) {
695 Object.extend(this.options, defs);
696 }.bind(this));
697 },
698 prepareSubmission: function() {
699 this._saving = true;
700 this.removeForm();
701 this.leaveHover();
702 this.showSaving();
703 },
704 registerListeners: function() {
705 this._listeners = { };
706 var listener;
707 $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
708 listener = this[pair.value].bind(this);
709 this._listeners[pair.key] = listener;
710 if (!this.options.externalControlOnly)
711 this.element.observe(pair.key, listener);
712 if (this.options.externalControl)
713 this.options.externalControl.observe(pair.key, listener);
714 }.bind(this));
715 },
716 removeForm: function() {
717 if (!this._form) return;
718 this._form.remove();
719 this._form = null;
720 this._controls = { };
721 },
722 showSaving: function() {
723 this._oldInnerHTML = this.element.innerHTML;
724 this.element.innerHTML = this.options.savingText;
725 this.element.addClassName(this.options.savingClassName);
726 this.element.style.backgroundColor = this._originalBackground;
727 this.element.show();
728 },
729 triggerCallback: function(cbName, arg) {
730 if ('function' == typeof this.options[cbName]) {
731 this.options[cbName](this, arg);
732 }
733 },
734 unregisterListeners: function() {
735 $H(this._listeners).each(function(pair) {
736 if (!this.options.externalControlOnly)
737 this.element.stopObserving(pair.key, pair.value);
738 if (this.options.externalControl)
739 this.options.externalControl.stopObserving(pair.key, pair.value);
740 }.bind(this));
741 },
742 wrapUp: function(transport) {
743 this.leaveEditMode();
744 // Can't use triggerCallback due to backward compatibility: requires
745 // binding + direct element
746 this._boundComplete(transport, this.element);
747 }
748});
749
750Object.extend(Ajax.InPlaceEditor.prototype, {
751 dispose: Ajax.InPlaceEditor.prototype.destroy
752});
753
754Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
755 initialize: function($super, element, url, options) {
756 this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
757 $super(element, url, options);
758 },
759
760 createEditField: function() {
761 var list = document.createElement('select');
762 list.name = this.options.paramName;
763 list.size = 1;
764 this._controls.editor = list;
765 this._collection = this.options.collection || [];
766 if (this.options.loadCollectionURL)
767 this.loadCollection();
768 else
769 this.checkForExternalText();
770 this._form.appendChild(this._controls.editor);
771 },
772
773 loadCollection: function() {
774 this._form.addClassName(this.options.loadingClassName);
775 this.showLoadingText(this.options.loadingCollectionText);
776 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
777 Object.extend(options, {
778 parameters: 'editorId=' + encodeURIComponent(this.element.id),
779 onComplete: Prototype.emptyFunction,
780 onSuccess: function(transport) {
781 var js = transport.responseText.strip();
782 if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
783 throw('Server returned an invalid collection representation.');
784 this._collection = eval(js);
785 this.checkForExternalText();
786 }.bind(this),
787 onFailure: this.onFailure
788 });
789 new Ajax.Request(this.options.loadCollectionURL, options);
790 },
791
792 showLoadingText: function(text) {
793 this._controls.editor.disabled = true;
794 var tempOption = this._controls.editor.firstChild;
795 if (!tempOption) {
796 tempOption = document.createElement('option');
797 tempOption.value = '';
798 this._controls.editor.appendChild(tempOption);
799 tempOption.selected = true;
800 }
801 tempOption.update((text || '').stripScripts().stripTags());
802 },
803
804 checkForExternalText: function() {
805 this._text = this.getText();
806 if (this.options.loadTextURL)
807 this.loadExternalText();
808 else
809 this.buildOptionList();
810 },
811
812 loadExternalText: function() {
813 this.showLoadingText(this.options.loadingText);
814 var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
815 Object.extend(options, {
816 parameters: 'editorId=' + encodeURIComponent(this.element.id),
817 onComplete: Prototype.emptyFunction,
818 onSuccess: function(transport) {
819 this._text = transport.responseText.strip();
820 this.buildOptionList();
821 }.bind(this),
822 onFailure: this.onFailure
823 });
824 new Ajax.Request(this.options.loadTextURL, options);
825 },
826
827 buildOptionList: function() {
828 this._form.removeClassName(this.options.loadingClassName);
829 this._collection = this._collection.map(function(entry) {
830 return 2 === entry.length ? entry : [entry, entry].flatten();
831 });
832 var marker = ('value' in this.options) ? this.options.value : this._text;
833 var textFound = this._collection.any(function(entry) {
834 return entry[0] == marker;
835 }.bind(this));
836 this._controls.editor.update('');
837 var option;
838 this._collection.each(function(entry, index) {
839 option = document.createElement('option');
840 option.value = entry[0];
841 option.selected = textFound ? entry[0] == marker : 0 == index;
842 option.appendChild(document.createTextNode(entry[1]));
843 this._controls.editor.appendChild(option);
844 }.bind(this));
845 this._controls.editor.disabled = false;
846 Field.scrollFreeActivate(this._controls.editor);
847 }
848});
849
850//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
851//**** This only exists for a while, in order to let ****
852//**** users adapt to the new API. Read up on the new ****
853//**** API and convert your code to it ASAP! ****
854
855Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
856 if (!options) return;
857 function fallback(name, expr) {
858 if (name in options || expr === undefined) return;
859 options[name] = expr;
860 };
861 fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
862 options.cancelLink == options.cancelButton == false ? false : undefined)));
863 fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
864 options.okLink == options.okButton == false ? false : undefined)));
865 fallback('highlightColor', options.highlightcolor);
866 fallback('highlightEndColor', options.highlightendcolor);
867};
868
869Object.extend(Ajax.InPlaceEditor, {
870 DefaultOptions: {
871 ajaxOptions: { },
872 autoRows: 3, // Use when multi-line w/ rows == 1
873 cancelControl: 'link', // 'link'|'button'|false
874 cancelText: 'cancel',
875 clickToEditText: 'Click to edit',
876 externalControl: null, // id|elt
877 externalControlOnly: false,
878 fieldPostCreation: 'activate', // 'activate'|'focus'|false
879 formClassName: 'inplaceeditor-form',
880 formId: null, // id|elt
881 highlightColor: '#ffff99',
882 highlightEndColor: '#ffffff',
883 hoverClassName: '',
884 htmlResponse: true,
885 loadingClassName: 'inplaceeditor-loading',
886 loadingText: 'Loading...',
887 okControl: 'button', // 'link'|'button'|false
888 okText: 'ok',
889 paramName: 'value',
890 rows: 1, // If 1 and multi-line, uses autoRows
891 savingClassName: 'inplaceeditor-saving',
892 savingText: 'Saving...',
893 size: 0,
894 stripLoadedTextTags: false,
895 submitOnBlur: false,
896 textAfterControls: '',
897 textBeforeControls: '',
898 textBetweenControls: ''
899 },
900 DefaultCallbacks: {
901 callback: function(form) {
902 return Form.serialize(form);
903 },
904 onComplete: function(transport, element) {
905 // For backward compatibility, this one is bound to the IPE, and passes
906 // the element directly. It was too often customized, so we don't break it.
907 new Effect.Highlight(element, {
908 startcolor: this.options.highlightColor, keepBackgroundImage: true });
909 },
910 onEnterEditMode: null,
911 onEnterHover: function(ipe) {
912 ipe.element.style.backgroundColor = ipe.options.highlightColor;
913 if (ipe._effect)
914 ipe._effect.cancel();
915 },
916 onFailure: function(transport, ipe) {
917 alert('Error communication with the server: ' + transport.responseText.stripTags());
918 },
919 onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
920 onLeaveEditMode: null,
921 onLeaveHover: function(ipe) {
922 ipe._effect = new Effect.Highlight(ipe.element, {
923 startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
924 restorecolor: ipe._originalBackground, keepBackgroundImage: true
925 });
926 }
927 },
928 Listeners: {
929 click: 'enterEditMode',
930 keydown: 'checkForEscapeOrReturn',
931 mouseover: 'enterHover',
932 mouseout: 'leaveHover'
933 }
934});
935
936Ajax.InPlaceCollectionEditor.DefaultOptions = {
937 loadingCollectionText: 'Loading options...'
938};
939
940// Delayed observer, like Form.Element.Observer,
941// but waits for delay after last key input
942// Ideal for live-search fields
943
944Form.Element.DelayedObserver = Class.create({
945 initialize: function(element, delay, callback) {
946 this.delay = delay || 0.5;
947 this.element = $(element);
948 this.callback = callback;
949 this.timer = null;
950 this.lastValue = $F(this.element);
951 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
952 },
953 delayedListener: function(event) {
954 if(this.lastValue == $F(this.element)) return;
955 if(this.timer) clearTimeout(this.timer);
956 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
957 this.lastValue = $F(this.element);
958 },
959 onTimerEvent: function() {
960 this.timer = null;
961 this.callback(this.element, $F(this.element));
962 }
963});
  
1// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
3//
4// script.aculo.us is freely distributable under the terms of an MIT-style license.
5// For details, see the script.aculo.us web site: http://script.aculo.us/
6
7if(Object.isUndefined(Effect))
8 throw("dragdrop.js requires including script.aculo.us' effects.js library");
9
10var Droppables = {
11 drops: [],
12
13 remove: function(element) {
14 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
15 },
16
17 add: function(element) {
18 element = $(element);
19 var options = Object.extend({
20 greedy: true,
21 hoverclass: null,
22 tree: false
23 }, arguments[1] || { });
24
25 // cache containers
26 if(options.containment) {
27 options._containers = [];
28 var containment = options.containment;
29 if(Object.isArray(containment)) {
30 containment.each( function(c) { options._containers.push($(c)) });
31 } else {
32 options._containers.push($(containment));
33 }
34 }
35
36 if(options.accept) options.accept = [options.accept].flatten();
37
38 Element.makePositioned(element); // fix IE
39 options.element = element;
40
41 this.drops.push(options);
42 },
43
44 findDeepestChild: function(drops) {
45 deepest = drops[0];
46
47 for (i = 1; i < drops.length; ++i)
48 if (Element.isParent(drops[i].element, deepest.element))
49 deepest = drops[i];
50
51 return deepest;
52 },
53
54 isContained: function(element, drop) {
55 var containmentNode;
56 if(drop.tree) {
57 containmentNode = element.treeNode;
58 } else {
59 containmentNode = element.parentNode;
60 }
61 return drop._containers.detect(function(c) { return containmentNode == c });
62 },
63
64 isAffected: function(point, element, drop) {
65 return (
66 (drop.element!=element) &&
67 ((!drop._containers) ||
68 this.isContained(element, drop)) &&
69 ((!drop.accept) ||
70 (Element.classNames(element).detect(
71 function(v) { return drop.accept.include(v) } ) )) &&
72 Position.within(drop.element, point[0], point[1]) );
73 },
74
75 deactivate: function(drop) {
76 if(drop.hoverclass)
77 Element.removeClassName(drop.element, drop.hoverclass);
78 this.last_active = null;
79 },
80
81 activate: function(drop) {
82 if(drop.hoverclass)
83 Element.addClassName(drop.element, drop.hoverclass);
84 this.last_active = drop;
85 },
86
87 show: function(point, element) {
88 if(!this.drops.length) return;
89 var drop, affected = [];
90
91 this.drops.each( function(drop) {
92 if(Droppables.isAffected(point, element, drop))
93 affected.push(drop);
94 });
95
96 if(affected.length>0)
97 drop = Droppables.findDeepestChild(affected);
98
99 if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
100 if (drop) {
101 Position.within(drop.element, point[0], point[1]);
102 if(drop.onHover)
103 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
104
105 if (drop != this.last_active) Droppables.activate(drop);
106 }
107 },
108
109 fire: function(event, element) {
110 if(!this.last_active) return;
111 Position.prepare();
112
113 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
114 if (this.last_active.onDrop) {
115 this.last_active.onDrop(element, this.last_active.element, event);
116 return true;
117 }
118 },
119
120 reset: function() {
121 if(this.last_active)
122 this.deactivate(this.last_active);
123 }
124};
125
126var Draggables = {
127 drags: [],
128 observers: [],
129
130 register: function(draggable) {
131 if(this.drags.length == 0) {
132 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
133 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
134 this.eventKeypress = this.keyPress.bindAsEventListener(this);
135
136 Event.observe(document, "mouseup", this.eventMouseUp);
137 Event.observe(document, "mousemove", this.eventMouseMove);
138 Event.observe(document, "keypress", this.eventKeypress);
139 }
140 this.drags.push(draggable);
141 },
142
143 unregister: function(draggable) {
144 this.drags = this.drags.reject(function(d) { return d==draggable });
145 if(this.drags.length == 0) {
146 Event.stopObserving(document, "mouseup", this.eventMouseUp);
147 Event.stopObserving(document, "mousemove", this.eventMouseMove);
148 Event.stopObserving(document, "keypress", this.eventKeypress);
149 }
150 },
151
152 activate: function(draggable) {
153 if(draggable.options.delay) {
154 this._timeout = setTimeout(function() {
155 Draggables._timeout = null;
156 window.focus();
157 Draggables.activeDraggable = draggable;
158 }.bind(this), draggable.options.delay);
159 } else {
160 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
161 this.activeDraggable = draggable;
162 }
163 },
164
165 deactivate: function() {
166 this.activeDraggable = null;
167 },
168
169 updateDrag: function(event) {
170 if(!this.activeDraggable) return;
171 var pointer = [Event.pointerX(event), Event.pointerY(event)];
172 // Mozilla-based browsers fire successive mousemove events with
173 // the same coordinates, prevent needless redrawing (moz bug?)
174 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
175 this._lastPointer = pointer;
176
177 this.activeDraggable.updateDrag(event, pointer);
178 },
179
180 endDrag: function(event) {
181 if(this._timeout) {
182 clearTimeout(this._timeout);
183 this._timeout = null;
184 }
185 if(!this.activeDraggable) return;
186 this._lastPointer = null;
187 this.activeDraggable.endDrag(event);
188 this.activeDraggable = null;
189 },
190
191 keyPress: function(event) {
192 if(this.activeDraggable)
193 this.activeDraggable.keyPress(event);
194 },
195
196 addObserver: function(observer) {
197 this.observers.push(observer);
198 this._cacheObserverCallbacks();
199 },
200
201 removeObserver: function(element) { // element instead of observer fixes mem leaks
202 this.observers = this.observers.reject( function(o) { return o.element==element });
203 this._cacheObserverCallbacks();
204 },
205
206 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
207 if(this[eventName+'Count'] > 0)
208 this.observers.each( function(o) {
209 if(o[eventName]) o[eventName](eventName, draggable, event);
210 });
211 if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
212 },
213
214 _cacheObserverCallbacks: function() {
215 ['onStart','onEnd','onDrag'].each( function(eventName) {
216 Draggables[eventName+'Count'] = Draggables.observers.select(
217 function(o) { return o[eventName]; }
218 ).length;
219 });
220 }
221};
222
223/*--------------------------------------------------------------------------*/
224
225var Draggable = Class.create({
226 initialize: function(element) {
227 var defaults = {
228 handle: false,
229 reverteffect: function(element, top_offset, left_offset) {
230 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
231 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
232 queue: {scope:'_draggable', position:'end'}
233 });
234 },
235 endeffect: function(element) {
236 var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
237 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
238 queue: {scope:'_draggable', position:'end'},
239 afterFinish: function(){
240 Draggable._dragging[element] = false
241 }
242 });
243 },
244 zindex: 1000,
245 revert: false,
246 quiet: false,
247 scroll: false,
248 scrollSensitivity: 20,
249 scrollSpeed: 15,
250 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
251 delay: 0
252 };
253
254 if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
255 Object.extend(defaults, {
256 starteffect: function(element) {
257 element._opacity = Element.getOpacity(element);
258 Draggable._dragging[element] = true;
259 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
260 }
261 });
262
263 var options = Object.extend(defaults, arguments[1] || { });
264
265 this.element = $(element);
266
267 if(options.handle && Object.isString(options.handle))
268 this.handle = this.element.down('.'+options.handle, 0);
269
270 if(!this.handle) this.handle = $(options.handle);
271 if(!this.handle) this.handle = this.element;
272
273 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
274 options.scroll = $(options.scroll);
275 this._isScrollChild = Element.childOf(this.element, options.scroll);
276 }
277
278 Element.makePositioned(this.element); // fix IE
279
280 this.options = options;
281 this.dragging = false;
282
283 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
284 Event.observe(this.handle, "mousedown", this.eventMouseDown);
285
286 Draggables.register(this);
287 },
288
289 destroy: function() {
290 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
291 Draggables.unregister(this);
292 },
293
294 currentDelta: function() {
295 return([
296 parseInt(Element.getStyle(this.element,'left') || '0'),
297 parseInt(Element.getStyle(this.element,'top') || '0')]);
298 },
299
300 initDrag: function(event) {
301 if(!Object.isUndefined(Draggable._dragging[this.element]) &&
302 Draggable._dragging[this.element]) return;
303 if(Event.isLeftClick(event)) {
304 // abort on form elements, fixes a Firefox issue
305 var src = Event.element(event);
306 if((tag_name = src.tagName.toUpperCase()) && (
307 tag_name=='INPUT' ||
308 tag_name=='SELECT' ||
309 tag_name=='OPTION' ||
310 tag_name=='BUTTON' ||
311 tag_name=='TEXTAREA')) return;
312
313 var pointer = [Event.pointerX(event), Event.pointerY(event)];
314 var pos = Position.cumulativeOffset(this.element);
315 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
316
317 Draggables.activate(this);
318 Event.stop(event);
319 }
320 },
321
322 startDrag: function(event) {
323 this.dragging = true;
324 if(!this.delta)
325 this.delta = this.currentDelta();
326
327 if(this.options.zindex) {
328 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
329 this.element.style.zIndex = this.options.zindex;
330 }
331
332 if(this.options.ghosting) {
333 this._clone = this.element.cloneNode(true);
334 this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
335 if (!this._originallyAbsolute)
336 Position.absolutize(this.element);
337 this.element.parentNode.insertBefore(this._clone, this.element);
338 }
339
340 if(this.options.scroll) {
341 if (this.options.scroll == window) {
342 var where = this._getWindowScroll(this.options.scroll);
343 this.originalScrollLeft = where.left;
344 this.originalScrollTop = where.top;
345 } else {
346 this.originalScrollLeft = this.options.scroll.scrollLeft;
347 this.originalScrollTop = this.options.scroll.scrollTop;
348 }
349 }
350
351 Draggables.notify('onStart', this, event);
352
353 if(this.options.starteffect) this.options.starteffect(this.element);
354 },
355
356 updateDrag: function(event, pointer) {
357 if(!this.dragging) this.startDrag(event);
358
359 if(!this.options.quiet){
360 Position.prepare();
361 Droppables.show(pointer, this.element);
362 }
363
364 Draggables.notify('onDrag', this, event);
365
366 this.draw(pointer);
367 if(this.options.change) this.options.change(this);
368
369 if(this.options.scroll) {
370 this.stopScrolling();
371
372 var p;
373 if (this.options.scroll == window) {
374 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
375 } else {
376 p = Position.page(this.options.scroll);
377 p[0] += this.options.scroll.scrollLeft + Position.deltaX;
378 p[1] += this.options.scroll.scrollTop + Position.deltaY;
379 p.push(p[0]+this.options.scroll.offsetWidth);
380 p.push(p[1]+this.options.scroll.offsetHeight);
381 }
382 var speed = [0,0];
383 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
384 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
385 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
386 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
387 this.startScrolling(speed);
388 }
389
390 // fix AppleWebKit rendering
391 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
392
393 Event.stop(event);
394 },
395
396 finishDrag: function(event, success) {
397 this.dragging = false;
398
399 if(this.options.quiet){
400 Position.prepare();
401 var pointer = [Event.pointerX(event), Event.pointerY(event)];
402 Droppables.show(pointer, this.element);
403 }
404
405 if(this.options.ghosting) {
406 if (!this._originallyAbsolute)
407 Position.relativize(this.element);
408 delete this._originallyAbsolute;
409 Element.remove(this._clone);
410 this._clone = null;
411 }
412
413 var dropped = false;
414 if(success) {
415 dropped = Droppables.fire(event, this.element);
416 if (!dropped) dropped = false;
417 }
418 if(dropped && this.options.onDropped) this.options.onDropped(this.element);
419 Draggables.notify('onEnd', this, event);
420
421 var revert = this.options.revert;
422 if(revert && Object.isFunction(revert)) revert = revert(this.element);
423
424 var d = this.currentDelta();
425 if(revert && this.options.reverteffect) {
426 if (dropped == 0 || revert != 'failure')
427 this.options.reverteffect(this.element,
428 d[1]-this.delta[1], d[0]-this.delta[0]);
429 } else {
430 this.delta = d;
431 }
432
433 if(this.options.zindex)
434 this.element.style.zIndex = this.originalZ;
435
436 if(this.options.endeffect)
437 this.options.endeffect(this.element);
438
439 Draggables.deactivate(this);
440 Droppables.reset();
441 },
442
443 keyPress: function(event) {
444 if(event.keyCode!=Event.KEY_ESC) return;
445 this.finishDrag(event, false);
446 Event.stop(event);
447 },
448
449 endDrag: function(event) {
450 if(!this.dragging) return;
451 this.stopScrolling();
452 this.finishDrag(event, true);
453 Event.stop(event);
454 },
455
456 draw: function(point) {
457 var pos = Position.cumulativeOffset(this.element);
458 if(this.options.ghosting) {
459 var r = Position.realOffset(this.element);
460 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
461 }
462
463 var d = this.currentDelta();
464 pos[0] -= d[0]; pos[1] -= d[1];
465
466 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
467 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
468 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
469 }
470
471 var p = [0,1].map(function(i){
472 return (point[i]-pos[i]-this.offset[i])
473 }.bind(this));
474
475 if(this.options.snap) {
476 if(Object.isFunction(this.options.snap)) {
477 p = this.options.snap(p[0],p[1],this);
478 } else {
479 if(Object.isArray(this.options.snap)) {
480 p = p.map( function(v, i) {
481 return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
482 } else {
483 p = p.map( function(v) {
484 return (v/this.options.snap).round()*this.options.snap }.bind(this));
485 }
486 }}
487
488 var style = this.element.style;
489 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
490 style.left = p[0] + "px";
491 if((!this.options.constraint) || (this.options.constraint=='vertical'))
492 style.top = p[1] + "px";
493
494 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
495 },
496
497 stopScrolling: function() {
498 if(this.scrollInterval) {
499 clearInterval(this.scrollInterval);
500 this.scrollInterval = null;
501 Draggables._lastScrollPointer = null;
502 }
503 },
504
505 startScrolling: function(speed) {
506 if(!(speed[0] || speed[1])) return;
507 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
508 this.lastScrolled = new Date();
509 this.scrollInterval = setInterval(this.scroll.bind(this), 10);
510 },
511
512 scroll: function() {
513 var current = new Date();
514 var delta = current - this.lastScrolled;
515 this.lastScrolled = current;
516 if(this.options.scroll == window) {
517 with (this._getWindowScroll(this.options.scroll)) {
518 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
519 var d = delta / 1000;
520 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
521 }
522 }
523 } else {
524 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
525 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
526 }
527
528 Position.prepare();
529 Droppables.show(Draggables._lastPointer, this.element);
530 Draggables.notify('onDrag', this);
531 if (this._isScrollChild) {
532 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
533 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
534 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
535 if (Draggables._lastScrollPointer[0] < 0)
536 Draggables._lastScrollPointer[0] = 0;
537 if (Draggables._lastScrollPointer[1] < 0)
538 Draggables._lastScrollPointer[1] = 0;
539 this.draw(Draggables._lastScrollPointer);
540 }
541
542 if(this.options.change) this.options.change(this);
543 },
544
545 _getWindowScroll: function(w) {
546 var T, L, W, H;
547 with (w.document) {
548 if (w.document.documentElement && documentElement.scrollTop) {
549 T = documentElement.scrollTop;
550 L = documentElement.scrollLeft;
551 } else if (w.document.body) {
552 T = body.scrollTop;
553 L = body.scrollLeft;
554 }
555 if (w.innerWidth) {
556 W = w.innerWidth;
557 H = w.innerHeight;
558 } else if (w.document.documentElement && documentElement.clientWidth) {
559 W = documentElement.clientWidth;
560 H = documentElement.clientHeight;
561 } else {
562 W = body.offsetWidth;
563 H = body.offsetHeight;
564 }
565 }
566 return { top: T, left: L, width: W, height: H };
567 }
568});
569
570Draggable._dragging = { };
571
572/*--------------------------------------------------------------------------*/
573
574var SortableObserver = Class.create({
575 initialize: function(element, observer) {
576 this.element = $(element);
577 this.observer = observer;
578 this.lastValue = Sortable.serialize(this.element);
579 },
580
581 onStart: function() {
582 this.lastValue = Sortable.serialize(this.element);
583 },
584
585 onEnd: function() {
586 Sortable.unmark();
587 if(this.lastValue != Sortable.serialize(this.element))
588 this.observer(this.element)
589 }
590});
591
592var Sortable = {
593 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
594
595 sortables: { },
596
597 _findRootElement: function(element) {
598 while (element.tagName.toUpperCase() != "BODY") {
599 if(element.id && Sortable.sortables[element.id]) return element;
600 element = element.parentNode;
601 }
602 },
603
604 options: function(element) {
605 element = Sortable._findRootElement($(element));
606 if(!element) return;
607 return Sortable.sortables[element.id];
608 },
609
610 destroy: function(element){
611 element = $(element);
612 var s = Sortable.sortables[element.id];
613
614 if(s) {
615 Draggables.removeObserver(s.element);
616 s.droppables.each(function(d){ Droppables.remove(d) });
617 s.draggables.invoke('destroy');
618
619 delete Sortable.sortables[s.element.id];
620 }
621 },
622
623 create: function(element) {
624 element = $(element);
625 var options = Object.extend({
626 element: element,
627 tag: 'li', // assumes li children, override with tag: 'tagname'
628 dropOnEmpty: false,
629 tree: false,
630 treeTag: 'ul',
631 overlap: 'vertical', // one of 'vertical', 'horizontal'
632 constraint: 'vertical', // one of 'vertical', 'horizontal', false
633 containment: element, // also takes array of elements (or id's); or false
634 handle: false, // or a CSS class
635 only: false,
636 delay: 0,
637 hoverclass: null,
638 ghosting: false,
639 quiet: false,
640 scroll: false,
641 scrollSensitivity: 20,
642 scrollSpeed: 15,
643 format: this.SERIALIZE_RULE,
644
645 // these take arrays of elements or ids and can be
646 // used for better initialization performance
647 elements: false,
648 handles: false,
649
650 onChange: Prototype.emptyFunction,
651 onUpdate: Prototype.emptyFunction
652 }, arguments[1] || { });
653
654 // clear any old sortable with same element
655 this.destroy(element);
656
657 // build options for the draggables
658 var options_for_draggable = {
659 revert: true,
660 quiet: options.quiet,
661 scroll: options.scroll,
662 scrollSpeed: options.scrollSpeed,
663 scrollSensitivity: options.scrollSensitivity,
664 delay: options.delay,
665 ghosting: options.ghosting,
666 constraint: options.constraint,
667 handle: options.handle };
668
669 if(options.starteffect)
670 options_for_draggable.starteffect = options.starteffect;
671
672 if(options.reverteffect)
673 options_for_draggable.reverteffect = options.reverteffect;
674 else
675 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
676 element.style.top = 0;
677 element.style.left = 0;
678 };
679
680 if(options.endeffect)
681 options_for_draggable.endeffect = options.endeffect;
682
683 if(options.zindex)
684 options_for_draggable.zindex = options.zindex;
685
686 // build options for the droppables
687 var options_for_droppable = {
688 overlap: options.overlap,
689 containment: options.containment,
690 tree: options.tree,
691 hoverclass: options.hoverclass,
692 onHover: Sortable.onHover
693 };
694
695 var options_for_tree = {
696 onHover: Sortable.onEmptyHover,
697 overlap: options.overlap,
698 containment: options.containment,
699 hoverclass: options.hoverclass
700 };
701
702 // fix for gecko engine
703 Element.cleanWhitespace(element);
704
705 options.draggables = [];
706 options.droppables = [];
707
708 // drop on empty handling
709 if(options.dropOnEmpty || options.tree) {
710 Droppables.add(element, options_for_tree);
711 options.droppables.push(element);
712 }
713
714 (options.elements || this.findElements(element, options) || []).each( function(e,i) {
715 var handle = options.handles ? $(options.handles[i]) :
716 (options.handle ? $(e).select('.' + options.handle)[0] : e);
717 options.draggables.push(
718 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
719 Droppables.add(e, options_for_droppable);
720 if(options.tree) e.treeNode = element;
721 options.droppables.push(e);
722 });
723
724 if(options.tree) {
725 (Sortable.findTreeElements(element, options) || []).each( function(e) {
726 Droppables.add(e, options_for_tree);
727 e.treeNode = element;
728 options.droppables.push(e);
729 });
730 }
731
732 // keep reference
733 this.sortables[element.id] = options;
734
735 // for onupdate
736 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
737
738 },
739
740 // return all suitable-for-sortable elements in a guaranteed order
741 findElements: function(element, options) {
742 return Element.findChildren(
743 element, options.only, options.tree ? true : false, options.tag);
744 },
745
746 findTreeElements: function(element, options) {
747 return Element.findChildren(
748 element, options.only, options.tree ? true : false, options.treeTag);
749 },
750
751 onHover: function(element, dropon, overlap) {
752 if(Element.isParent(dropon, element)) return;
753
754 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
755 return;
756 } else if(overlap>0.5) {
757 Sortable.mark(dropon, 'before');
758 if(dropon.previousSibling != element) {
759 var oldParentNode = element.parentNode;
760 element.style.visibility = "hidden"; // fix gecko rendering
761 dropon.parentNode.insertBefore(element, dropon);
762 if(dropon.parentNode!=oldParentNode)
763 Sortable.options(oldParentNode).onChange(element);
764 Sortable.options(dropon.parentNode).onChange(element);
765 }
766 } else {
767 Sortable.mark(dropon, 'after');
768 var nextElement = dropon.nextSibling || null;
769 if(nextElement != element) {
770 var oldParentNode = element.parentNode;
771 element.style.visibility = "hidden"; // fix gecko rendering
772 dropon.parentNode.insertBefore(element, nextElement);
773 if(dropon.parentNode!=oldParentNode)
774 Sortable.options(oldParentNode).onChange(element);
775 Sortable.options(dropon.parentNode).onChange(element);
776 }
777 }
778 },
779
780 onEmptyHover: function(element, dropon, overlap) {
781 var oldParentNode = element.parentNode;
782 var droponOptions = Sortable.options(dropon);
783
784 if(!Element.isParent(dropon, element)) {
785 var index;
786
787 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
788 var child = null;
789
790 if(children) {
791 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
792
793 for (index = 0; index < children.length; index += 1) {
794 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
795 offset -= Element.offsetSize (children[index], droponOptions.overlap);
796 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
797 child = index + 1 < children.length ? children[index + 1] : null;
798 break;
799 } else {
800 child = children[index];
801 break;
802 }
803 }
804 }
805
806 dropon.insertBefore(element, child);
807
808 Sortable.options(oldParentNode).onChange(element);
809 droponOptions.onChange(element);
810 }
811 },
812
813 unmark: function() {
814 if(Sortable._marker) Sortable._marker.hide();
815 },
816
817 mark: function(dropon, position) {
818 // mark on ghosting only
819 var sortable = Sortable.options(dropon.parentNode);
820 if(sortable && !sortable.ghosting) return;
821
822 if(!Sortable._marker) {
823 Sortable._marker =
824 ($('dropmarker') || Element.extend(document.createElement('DIV'))).
825 hide().addClassName('dropmarker').setStyle({position:'absolute'});
826 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
827 }
828 var offsets = Position.cumulativeOffset(dropon);
829 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
830
831 if(position=='after')
832 if(sortable.overlap == 'horizontal')
833 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
834 else
835 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
836
837 Sortable._marker.show();
838 },
839
840 _tree: function(element, options, parent) {
841 var children = Sortable.findElements(element, options) || [];
842
843 for (var i = 0; i < children.length; ++i) {
844 var match = children[i].id.match(options.format);
845
846 if (!match) continue;
847
848 var child = {
849 id: encodeURIComponent(match ? match[1] : null),
850 element: element,
851 parent: parent,
852 children: [],
853 position: parent.children.length,
854 container: $(children[i]).down(options.treeTag)
855 };
856
857 /* Get the element containing the children and recurse over it */
858 if (child.container)
859 this._tree(child.container, options, child);
860
861 parent.children.push (child);
862 }
863
864 return parent;
865 },
866
867 tree: function(element) {
868 element = $(element);
869 var sortableOptions = this.options(element);
870 var options = Object.extend({
871 tag: sortableOptions.tag,
872 treeTag: sortableOptions.treeTag,
873 only: sortableOptions.only,
874 name: element.id,
875 format: sortableOptions.format
876 }, arguments[1] || { });
877
878 var root = {
879 id: null,
880 parent: null,
881 children: [],
882 container: element,
883 position: 0
884 };
885
886 return Sortable._tree(element, options, root);
887 },
888
889 /* Construct a [i] index for a particular node */
890 _constructIndex: function(node) {
891 var index = '';
892 do {
893 if (node.id) index = '[' + node.position + ']' + index;
894 } while ((node = node.parent) != null);
895 return index;
896 },
897
898 sequence: function(element) {
899 element = $(element);
900 var options = Object.extend(this.options(element), arguments[1] || { });
901
902 return $(this.findElements(element, options) || []).map( function(item) {
903 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
904 });
905 },
906
907 setSequence: function(element, new_sequence) {
908 element = $(element);
909 var options = Object.extend(this.options(element), arguments[2] || { });
910
911 var nodeMap = { };
912 this.findElements(element, options).each( function(n) {
913 if (n.id.match(options.format))
914 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
915 n.parentNode.removeChild(n);
916 });
917
918 new_sequence.each(function(ident) {
919 var n = nodeMap[ident];
920 if (n) {
921 n[1].appendChild(n[0]);
922 delete nodeMap[ident];
923 }
924 });
925 },
926
927 serialize: function(element) {
928 element = $(element);
929 var options = Object.extend(Sortable.options(element), arguments[1] || { });
930 var name = encodeURIComponent(
931 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
932
933 if (options.tree) {
934 return Sortable.tree(element, arguments[1]).children.map( function (item) {
935 return [name + Sortable._constructIndex(item) + "[id]=" +
936 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
937 }).flatten().join('&');
938 } else {
939 return Sortable.sequence(element, arguments[1]).map( function(item) {
940 return name + "[]=" + encodeURIComponent(item);
941 }).join('&');
942 }
943 }
944};
945
946// Returns true if child is contained within element
947Element.isParent = function(child, element) {
948 if (!child.parentNode || child == element) return false;
949 if (child.parentNode == element) return true;
950 return Element.isParent(child.parentNode, element);
951};
952
953Element.findChildren = function(element, only, recursive, tagName) {
954 if(!element.hasChildNodes()) return null;
955 tagName = tagName.toUpperCase();
956 if(only) only = [only].flatten();
957 var elements = [];
958 $A(element.childNodes).each( function(e) {
959 if(e.tagName && e.tagName.toUpperCase()==tagName &&
960 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
961 elements.push(e);
962 if(recursive) {
963 var grandchildren = Element.findChildren(e, only, recursive, tagName);
964 if(grandchildren) elements.push(grandchildren);
965 }
966 });
967
968 return (elements.length>0 ? elements.flatten() : []);
969};
970
971Element.offsetSize = function (element, type) {
972 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
973};
  
1Event.addBehavior({
2 'body:click' : function(event) {
3 if (event.shiftKey && event.altKey) {
4 Dryml.click(event)
5 Event.stop(event)
6 }
7 }
8})
9
10
11var Dryml = {
12
13 menu: null,
14 event: null,
15
16 click: function(event) {
17 Dryml.event = event
18 Dryml.showSourceMenu(event.target)
19 },
20
21 showSourceMenu: function(element) {
22 var stack = Dryml.getSrcInfoStack(element)
23 Dryml.showMenu(stack)
24 },
25
26 getSrcInfoStack: function(element) {
27 var stack = $A()
28 while(element != document.documentElement) {
29 var el = Dryml.findPrecedingDrymlInfo(element)
30 if (el == null) {
31 element = element.parentNode
32 } else {
33 element = el
34 var info = Dryml.getDrymlInfo(element)
35 stack.push(info)
36 }
37 }
38 return stack
39 },
40
41 findPrecedingDrymlInfo: function(element) {
42 var ignoreCount = 0
43 var el = element
44 while (el = el.previousSibling) {
45 if (Dryml.isDrymlInfo(el)) {
46 if (ignoreCount > 0)
47 ignoreCount -= 1;
48 else
49 return el
50 } else if (Dryml.isDrymlInfoClose(el)) {
51 ignoreCount += 1
52 }
53 }
54 return null
55 },
56
57 getDrymlInfo: function(el) {
58 var parts = el.nodeValue.sub(/^\[DRYML\|/, "").sub(/\[$/, "").split("|")
59 return { kind: parts[0], tag: parts[1], line: parts[2], file: parts[3] }
60 },
61
62 isDrymlInfo: function(el) {
63 return el.nodeType == Node.COMMENT_NODE && el.nodeValue.match(/^\[DRYML/)
64 },
65
66 isDrymlInfoClose: function(el) {
67 return el.nodeType == Node.COMMENT_NODE && el.nodeValue == "]DRYML]"
68 },
69
70 showMenu: function(stack) {
71 Dryml.removeMenu()
72
73 var style = $style({id: "dryml-menu-style"},
74 "#dryml-src-menu { position: fixed; margin: 10px; padding: 10px; background: black; color: white; border: 1px solid white; }\n",
75 "#dryml-src-menu a { color: white; text-decoration: none; border: none; }\n",
76 "#dryml-src-menu td { padding: 2px 7px; }\n",
77 "#dryml-src-menu a:hover { background: black; color: white; text-decoration: none; border: none; }\n")
78 $$("head")[0].appendChild(style)
79
80 var items = stack.map(Dryml.makeMenuItem)
81
82 var closer = $a({href:"#"}, "[close]")
83 closer.onclick = Dryml.removeMenu
84 Dryml.menu = $div({id: "dryml-src-menu",
85 style: "position: fixed; margin: 10px; padding: 10px; background: black; color: #cfc; border: 1px solid white;"
86 },
87 closer,
88 $table(items))
89
90 document.body.appendChild(Dryml.menu)
91 Dryml.menu.style.top = "20px"//Dryml.event.clientY + "px"
92 Dryml.menu.style.left = "20px"//Dryml.event.clientX + "px"
93 },
94
95 editSourceFile: function(path, line) {
96 new Ajax.Request("/dryml/edit_source?file=" + path + "&line=" + line)
97 },
98
99
100 makeMenuItem: function(item) {
101 var text
102 switch (item.kind) {
103 case "call":
104 text = "<" + item.tag + ">"
105 break
106 case "param":
107 text = "<" + item.tag + ":>"
108 break
109 case "replace":
110 text = "<" + item.tag + ": replace>"
111 break
112 case "def":
113 text = "<def " + item.tag + ">"
114 break
115 }
116 var a = $a({href:"#"}, text)
117 a.onclick = function() { Dryml.editSourceFile(item.file, item.line); return false }
118
119 var filename = item.file.sub("vendor/plugins", "").sub("app/views", "").sub(/^\/+/, "").sub(".dryml", "")
120
121 return $tr($td({"class": "file"}, filename), $td(a))
122 },
123
124 removeMenu: function() {
125 if (Dryml.menu) {
126 $("dryml-menu-style").remove()
127 Dryml.menu.remove()
128 Dryml.menu = null
129 }
130 }
131
132}
  
1// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2// Contributors:
3// Justin Palmer (http://encytemedia.com/)
4// Mark Pilgrim (http://diveintomark.org/)
5// Martin Bialasinki
6//
7// script.aculo.us is freely distributable under the terms of an MIT-style license.
8// For details, see the script.aculo.us web site: http://script.aculo.us/
9
10// converts rgb() and #xxx to #xxxxxx format,
11// returns self (or first argument) if not convertable
12String.prototype.parseColor = function() {
13 var color = '#';
14 if (this.slice(0,4) == 'rgb(') {
15 var cols = this.slice(4,this.length-1).split(',');
16 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
17 } else {
18 if (this.slice(0,1) == '#') {
19 if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
20 if (this.length==7) color = this.toLowerCase();
21 }
22 }
23 return (color.length==7 ? color : (arguments[0] || this));
24};
25
26/*--------------------------------------------------------------------------*/
27
28Element.collectTextNodes = function(element) {
29 return $A($(element).childNodes).collect( function(node) {
30 return (node.nodeType==3 ? node.nodeValue :
31 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
32 }).flatten().join('');
33};
34
35Element.collectTextNodesIgnoreClass = function(element, className) {
36 return $A($(element).childNodes).collect( function(node) {
37 return (node.nodeType==3 ? node.nodeValue :
38 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
39 Element.collectTextNodesIgnoreClass(node, className) : ''));
40 }).flatten().join('');
41};
42
43Element.setContentZoom = function(element, percent) {
44 element = $(element);
45 element.setStyle({fontSize: (percent/100) + 'em'});
46 if (Prototype.Browser.WebKit) window.scrollBy(0,0);
47 return element;
48};
49
50Element.getInlineOpacity = function(element){
51 return $(element).style.opacity || '';
52};
53
54Element.forceRerendering = function(element) {
55 try {
56 element = $(element);
57 var n = document.createTextNode(' ');
58 element.appendChild(n);
59 element.removeChild(n);
60 } catch(e) { }
61};
62
63/*--------------------------------------------------------------------------*/
64
65var Effect = {
66 _elementDoesNotExistError: {
67 name: 'ElementDoesNotExistError',
68 message: 'The specified DOM element does not exist, but is required for this effect to operate'
69 },
70 Transitions: {
71 linear: Prototype.K,
72 sinoidal: function(pos) {
73 return (-Math.cos(pos*Math.PI)/2) + .5;
74 },
75 reverse: function(pos) {
76 return 1-pos;
77 },
78 flicker: function(pos) {
79 var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
80 return pos > 1 ? 1 : pos;
81 },
82 wobble: function(pos) {
83 return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
84 },
85 pulse: function(pos, pulses) {
86 return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
87 },
88 spring: function(pos) {
89 return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
90 },
91 none: function(pos) {
92 return 0;
93 },
94 full: function(pos) {
95 return 1;
96 }
97 },
98 DefaultOptions: {
99 duration: 1.0, // seconds
100 fps: 100, // 100= assume 66fps max.
101 sync: false, // true for combining
102 from: 0.0,
103 to: 1.0,
104 delay: 0.0,
105 queue: 'parallel'
106 },
107 tagifyText: function(element) {
108 var tagifyStyle = 'position:relative';
109 if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
110
111 element = $(element);
112 $A(element.childNodes).each( function(child) {
113 if (child.nodeType==3) {
114 child.nodeValue.toArray().each( function(character) {
115 element.insertBefore(
116 new Element('span', {style: tagifyStyle}).update(
117 character == ' ' ? String.fromCharCode(160) : character),
118 child);
119 });
120 Element.remove(child);
121 }
122 });
123 },
124 multiple: function(element, effect) {
125 var elements;
126 if (((typeof element == 'object') ||
127 Object.isFunction(element)) &&
128 (element.length))
129 elements = element;
130 else
131 elements = $(element).childNodes;
132
133 var options = Object.extend({
134 speed: 0.1,
135 delay: 0.0
136 }, arguments[2] || { });
137 var masterDelay = options.delay;
138
139 $A(elements).each( function(element, index) {
140 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
141 });
142 },
143 PAIRS: {
144 'slide': ['SlideDown','SlideUp'],
145 'blind': ['BlindDown','BlindUp'],
146 'appear': ['Appear','Fade']
147 },
148 toggle: function(element, effect) {
149 element = $(element);
150 effect = (effect || 'appear').toLowerCase();
151 var options = Object.extend({
152 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
153 }, arguments[2] || { });
154 Effect[element.visible() ?
155 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
156 }
157};
158
159Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
160
161/* ------------- core effects ------------- */
162
163Effect.ScopedQueue = Class.create(Enumerable, {
164 initialize: function() {
165 this.effects = [];
166 this.interval = null;
167 },
168 _each: function(iterator) {
169 this.effects._each(iterator);
170 },
171 add: function(effect) {
172 var timestamp = new Date().getTime();
173
174 var position = Object.isString(effect.options.queue) ?
175 effect.options.queue : effect.options.queue.position;
176
177 switch(position) {
178 case 'front':
179 // move unstarted effects after this effect
180 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
181 e.startOn += effect.finishOn;
182 e.finishOn += effect.finishOn;
183 });
184 break;
185 case 'with-last':
186 timestamp = this.effects.pluck('startOn').max() || timestamp;
187 break;
188 case 'end':
189 // start effect after last queued effect has finished
190 timestamp = this.effects.pluck('finishOn').max() || timestamp;
191 break;
192 }
193
194 effect.startOn += timestamp;
195 effect.finishOn += timestamp;
196
197 if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
198 this.effects.push(effect);
199
200 if (!this.interval)
201 this.interval = setInterval(this.loop.bind(this), 15);
202 },
203 remove: function(effect) {
204 this.effects = this.effects.reject(function(e) { return e==effect });
205 if (this.effects.length == 0) {
206 clearInterval(this.interval);
207 this.interval = null;
208 }
209 },
210 loop: function() {
211 var timePos = new Date().getTime();
212 for(var i=0, len=this.effects.length;i<len;i++)
213 this.effects[i] && this.effects[i].loop(timePos);
214 }
215});
216
217Effect.Queues = {
218 instances: $H(),
219 get: function(queueName) {
220 if (!Object.isString(queueName)) return queueName;
221
222 return this.instances.get(queueName) ||
223 this.instances.set(queueName, new Effect.ScopedQueue());
224 }
225};
226Effect.Queue = Effect.Queues.get('global');
227
228Effect.Base = Class.create({
229 position: null,
230 start: function(options) {
231 function codeForEvent(options,eventName){
232 return (
233 (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
234 (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
235 );
236 }
237 if (options && options.transition === false) options.transition = Effect.Transitions.linear;
238 this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
239 this.currentFrame = 0;
240 this.state = 'idle';
241 this.startOn = this.options.delay*1000;
242 this.finishOn = this.startOn+(this.options.duration*1000);
243 this.fromToDelta = this.options.to-this.options.from;
244 this.totalTime = this.finishOn-this.startOn;
245 this.totalFrames = this.options.fps*this.options.duration;
246
247 this.render = (function() {
248 function dispatch(effect, eventName) {
249 if (effect.options[eventName + 'Internal'])
250 effect.options[eventName + 'Internal'](effect);
251 if (effect.options[eventName])
252 effect.options[eventName](effect);
253 }
254
255 return function(pos) {
256 if (this.state === "idle") {
257 this.state = "running";
258 dispatch(this, 'beforeSetup');
259 if (this.setup) this.setup();
260 dispatch(this, 'afterSetup');
261 }
262 if (this.state === "running") {
263 pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
264 this.position = pos;
265 dispatch(this, 'beforeUpdate');
266 if (this.update) this.update(pos);
267 dispatch(this, 'afterUpdate');
268 }
269 };
270 })();
271
272 this.event('beforeStart');
273 if (!this.options.sync)
274 Effect.Queues.get(Object.isString(this.options.queue) ?
275 'global' : this.options.queue.scope).add(this);
276 },
277 loop: function(timePos) {
278 if (timePos >= this.startOn) {
279 if (timePos >= this.finishOn) {
280 this.render(1.0);
281 this.cancel();
282 this.event('beforeFinish');
283 if (this.finish) this.finish();
284 this.event('afterFinish');
285 return;
286 }
287 var pos = (timePos - this.startOn) / this.totalTime,
288 frame = (pos * this.totalFrames).round();
289 if (frame > this.currentFrame) {
290 this.render(pos);
291 this.currentFrame = frame;
292 }
293 }
294 },
295 cancel: function() {
296 if (!this.options.sync)
297 Effect.Queues.get(Object.isString(this.options.queue) ?
298 'global' : this.options.queue.scope).remove(this);
299 this.state = 'finished';
300 },
301 event: function(eventName) {
302 if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
303 if (this.options[eventName]) this.options[eventName](this);
304 },
305 inspect: function() {
306 var data = $H();
307 for(property in this)
308 if (!Object.isFunction(this[property])) data.set(property, this[property]);
309 return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
310 }
311});
312
313Effect.Parallel = Class.create(Effect.Base, {
314 initialize: function(effects) {
315 this.effects = effects || [];
316 this.start(arguments[1]);
317 },
318 update: function(position) {
319 this.effects.invoke('render', position);
320 },
321 finish: function(position) {
322 this.effects.each( function(effect) {
323 effect.render(1.0);
324 effect.cancel();
325 effect.event('beforeFinish');
326 if (effect.finish) effect.finish(position);
327 effect.event('afterFinish');
328 });
329 }
330});
331
332Effect.Tween = Class.create(Effect.Base, {
333 initialize: function(object, from, to) {
334 object = Object.isString(object) ? $(object) : object;
335 var args = $A(arguments), method = args.last(),
336 options = args.length == 5 ? args[3] : null;
337 this.method = Object.isFunction(method) ? method.bind(object) :
338 Object.isFunction(object[method]) ? object[method].bind(object) :
339 function(value) { object[method] = value };
340 this.start(Object.extend({ from: from, to: to }, options || { }));
341 },
342 update: function(position) {
343 this.method(position);
344 }
345});
346
347Effect.Event = Class.create(Effect.Base, {
348 initialize: function() {
349 this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
350 },
351 update: Prototype.emptyFunction
352});
353
354Effect.Opacity = Class.create(Effect.Base, {
355 initialize: function(element) {
356 this.element = $(element);
357 if (!this.element) throw(Effect._elementDoesNotExistError);
358 // make this work on IE on elements without 'layout'
359 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
360 this.element.setStyle({zoom: 1});
361 var options = Object.extend({
362 from: this.element.getOpacity() || 0.0,
363 to: 1.0
364 }, arguments[1] || { });
365 this.start(options);
366 },
367 update: function(position) {
368 this.element.setOpacity(position);
369 }
370});
371
372Effect.Move = Class.create(Effect.Base, {
373 initialize: function(element) {
374 this.element = $(element);
375 if (!this.element) throw(Effect._elementDoesNotExistError);
376 var options = Object.extend({
377 x: 0,
378 y: 0,
379 mode: 'relative'
380 }, arguments[1] || { });
381 this.start(options);
382 },
383 setup: function() {
384 this.element.makePositioned();
385 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
386 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
387 if (this.options.mode == 'absolute') {
388 this.options.x = this.options.x - this.originalLeft;
389 this.options.y = this.options.y - this.originalTop;
390 }
391 },
392 update: function(position) {
393 this.element.setStyle({
394 left: (this.options.x * position + this.originalLeft).round() + 'px',
395 top: (this.options.y * position + this.originalTop).round() + 'px'
396 });
397 }
398});
399
400// for backwards compatibility
401Effect.MoveBy = function(element, toTop, toLeft) {
402 return new Effect.Move(element,
403 Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
404};
405
406Effect.Scale = Class.create(Effect.Base, {
407 initialize: function(element, percent) {
408 this.element = $(element);
409 if (!this.element) throw(Effect._elementDoesNotExistError);
410 var options = Object.extend({
411 scaleX: true,
412 scaleY: true,
413 scaleContent: true,
414 scaleFromCenter: false,
415 scaleMode: 'box', // 'box' or 'contents' or { } with provided values
416 scaleFrom: 100.0,
417 scaleTo: percent
418 }, arguments[2] || { });
419 this.start(options);
420 },
421 setup: function() {
422 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
423 this.elementPositioning = this.element.getStyle('position');
424
425 this.originalStyle = { };
426 ['top','left','width','height','fontSize'].each( function(k) {
427 this.originalStyle[k] = this.element.style[k];
428 }.bind(this));
429
430 this.originalTop = this.element.offsetTop;
431 this.originalLeft = this.element.offsetLeft;
432
433 var fontSize = this.element.getStyle('font-size') || '100%';
434 ['em','px','%','pt'].each( function(fontSizeType) {
435 if (fontSize.indexOf(fontSizeType)>0) {
436 this.fontSize = parseFloat(fontSize);
437 this.fontSizeType = fontSizeType;
438 }
439 }.bind(this));
440
441 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
442
443 this.dims = null;
444 if (this.options.scaleMode=='box')
445 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
446 if (/^content/.test(this.options.scaleMode))
447 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
448 if (!this.dims)
449 this.dims = [this.options.scaleMode.originalHeight,
450 this.options.scaleMode.originalWidth];
451 },
452 update: function(position) {
453 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
454 if (this.options.scaleContent && this.fontSize)
455 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
456 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
457 },
458 finish: function(position) {
459 if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
460 },
461 setDimensions: function(height, width) {
462 var d = { };
463 if (this.options.scaleX) d.width = width.round() + 'px';
464 if (this.options.scaleY) d.height = height.round() + 'px';
465 if (this.options.scaleFromCenter) {
466 var topd = (height - this.dims[0])/2;
467 var leftd = (width - this.dims[1])/2;
468 if (this.elementPositioning == 'absolute') {
469 if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
470 if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
471 } else {
472 if (this.options.scaleY) d.top = -topd + 'px';
473 if (this.options.scaleX) d.left = -leftd + 'px';
474 }
475 }
476 this.element.setStyle(d);
477 }
478});
479
480Effect.Highlight = Class.create(Effect.Base, {
481 initialize: function(element) {
482 this.element = $(element);
483 if (!this.element) throw(Effect._elementDoesNotExistError);
484 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
485 this.start(options);
486 },
487 setup: function() {
488 // Prevent executing on elements not in the layout flow
489 if (this.element.getStyle('display')=='none') { this.cancel(); return; }
490 // Disable background image during the effect
491 this.oldStyle = { };
492 if (!this.options.keepBackgroundImage) {
493 this.oldStyle.backgroundImage = this.element.getStyle('background-image');
494 this.element.setStyle({backgroundImage: 'none'});
495 }
496 if (!this.options.endcolor)
497 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
498 if (!this.options.restorecolor)
499 this.options.restorecolor = this.element.getStyle('background-color');
500 // init color calculations
501 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
502 this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
503 },
504 update: function(position) {
505 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
506 return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
507 },
508 finish: function() {
509 this.element.setStyle(Object.extend(this.oldStyle, {
510 backgroundColor: this.options.restorecolor
511 }));
512 }
513});
514
515Effect.ScrollTo = function(element) {
516 var options = arguments[1] || { },
517 scrollOffsets = document.viewport.getScrollOffsets(),
518 elementOffsets = $(element).cumulativeOffset();
519
520 if (options.offset) elementOffsets[1] += options.offset;
521
522 return new Effect.Tween(null,
523 scrollOffsets.top,
524 elementOffsets[1],
525 options,
526 function(p){ scrollTo(scrollOffsets.left, p.round()); }
527 );
528};
529
530/* ------------- combination effects ------------- */
531
532Effect.Fade = function(element) {
533 element = $(element);
534 var oldOpacity = element.getInlineOpacity();
535 var options = Object.extend({
536 from: element.getOpacity() || 1.0,
537 to: 0.0,
538 afterFinishInternal: function(effect) {
539 if (effect.options.to!=0) return;
540 effect.element.hide().setStyle({opacity: oldOpacity});
541 }
542 }, arguments[1] || { });
543 return new Effect.Opacity(element,options);
544};
545
546Effect.Appear = function(element) {
547 element = $(element);
548 var options = Object.extend({
549 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
550 to: 1.0,
551 // force Safari to render floated elements properly
552 afterFinishInternal: function(effect) {
553 effect.element.forceRerendering();
554 },
555 beforeSetup: function(effect) {
556 effect.element.setOpacity(effect.options.from).show();
557 }}, arguments[1] || { });
558 return new Effect.Opacity(element,options);
559};
560
561Effect.Puff = function(element) {
562 element = $(element);
563 var oldStyle = {
564 opacity: element.getInlineOpacity(),
565 position: element.getStyle('position'),
566 top: element.style.top,
567 left: element.style.left,
568 width: element.style.width,
569 height: element.style.height
570 };
571 return new Effect.Parallel(
572 [ new Effect.Scale(element, 200,
573 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
574 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
575 Object.extend({ duration: 1.0,
576 beforeSetupInternal: function(effect) {
577 Position.absolutize(effect.effects[0].element);
578 },
579 afterFinishInternal: function(effect) {
580 effect.effects[0].element.hide().setStyle(oldStyle); }
581 }, arguments[1] || { })
582 );
583};
584
585Effect.BlindUp = function(element) {
586 element = $(element);
587 element.makeClipping();
588 return new Effect.Scale(element, 0,
589 Object.extend({ scaleContent: false,
590 scaleX: false,
591 restoreAfterFinish: true,
592 afterFinishInternal: function(effect) {
593 effect.element.hide().undoClipping();
594 }
595 }, arguments[1] || { })
596 );
597};
598
599Effect.BlindDown = function(element) {
600 element = $(element);
601 var elementDimensions = element.getDimensions();
602 return new Effect.Scale(element, 100, Object.extend({
603 scaleContent: false,
604 scaleX: false,
605 scaleFrom: 0,
606 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
607 restoreAfterFinish: true,
608 afterSetup: function(effect) {
609 effect.element.makeClipping().setStyle({height: '0px'}).show();
610 },
611 afterFinishInternal: function(effect) {
612 effect.element.undoClipping();
613 }
614 }, arguments[1] || { }));
615};
616
617Effect.SwitchOff = function(element) {
618 element = $(element);
619 var oldOpacity = element.getInlineOpacity();
620 return new Effect.Appear(element, Object.extend({
621 duration: 0.4,
622 from: 0,
623 transition: Effect.Transitions.flicker,
624 afterFinishInternal: function(effect) {
625 new Effect.Scale(effect.element, 1, {
626 duration: 0.3, scaleFromCenter: true,
627 scaleX: false, scaleContent: false, restoreAfterFinish: true,
628 beforeSetup: function(effect) {
629 effect.element.makePositioned().makeClipping();
630 },
631 afterFinishInternal: function(effect) {
632 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
633 }
634 });
635 }
636 }, arguments[1] || { }));
637};
638
639Effect.DropOut = function(element) {
640 element = $(element);
641 var oldStyle = {
642 top: element.getStyle('top'),
643 left: element.getStyle('left'),
644 opacity: element.getInlineOpacity() };
645 return new Effect.Parallel(
646 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
647 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
648 Object.extend(
649 { duration: 0.5,
650 beforeSetup: function(effect) {
651 effect.effects[0].element.makePositioned();
652 },
653 afterFinishInternal: function(effect) {
654 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
655 }
656 }, arguments[1] || { }));
657};
658
659Effect.Shake = function(element) {
660 element = $(element);
661 var options = Object.extend({
662 distance: 20,
663 duration: 0.5
664 }, arguments[1] || {});
665 var distance = parseFloat(options.distance);
666 var split = parseFloat(options.duration) / 10.0;
667 var oldStyle = {
668 top: element.getStyle('top'),
669 left: element.getStyle('left') };
670 return new Effect.Move(element,
671 { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
672 new Effect.Move(effect.element,
673 { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
674 new Effect.Move(effect.element,
675 { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
676 new Effect.Move(effect.element,
677 { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
678 new Effect.Move(effect.element,
679 { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
680 new Effect.Move(effect.element,
681 { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
682 effect.element.undoPositioned().setStyle(oldStyle);
683 }}); }}); }}); }}); }}); }});
684};
685
686Effect.SlideDown = function(element) {
687 element = $(element).cleanWhitespace();
688 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
689 var oldInnerBottom = element.down().getStyle('bottom');
690 var elementDimensions = element.getDimensions();
691 return new Effect.Scale(element, 100, Object.extend({
692 scaleContent: false,
693 scaleX: false,
694 scaleFrom: window.opera ? 0 : 1,
695 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
696 restoreAfterFinish: true,
697 afterSetup: function(effect) {
698 effect.element.makePositioned();
699 effect.element.down().makePositioned();
700 if (window.opera) effect.element.setStyle({top: ''});
701 effect.element.makeClipping().setStyle({height: '0px'}).show();
702 },
703 afterUpdateInternal: function(effect) {
704 effect.element.down().setStyle({bottom:
705 (effect.dims[0] - effect.element.clientHeight) + 'px' });
706 },
707 afterFinishInternal: function(effect) {
708 effect.element.undoClipping().undoPositioned();
709 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
710 }, arguments[1] || { })
711 );
712};
713
714Effect.SlideUp = function(element) {
715 element = $(element).cleanWhitespace();
716 var oldInnerBottom = element.down().getStyle('bottom');
717 var elementDimensions = element.getDimensions();
718 return new Effect.Scale(element, window.opera ? 0 : 1,
719 Object.extend({ scaleContent: false,
720 scaleX: false,
721 scaleMode: 'box',
722 scaleFrom: 100,
723 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
724 restoreAfterFinish: true,
725 afterSetup: function(effect) {
726 effect.element.makePositioned();
727 effect.element.down().makePositioned();
728 if (window.opera) effect.element.setStyle({top: ''});
729 effect.element.makeClipping().show();
730 },
731 afterUpdateInternal: function(effect) {
732 effect.element.down().setStyle({bottom:
733 (effect.dims[0] - effect.element.clientHeight) + 'px' });
734 },
735 afterFinishInternal: function(effect) {
736 effect.element.hide().undoClipping().undoPositioned();
737 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
738 }
739 }, arguments[1] || { })
740 );
741};
742
743// Bug in opera makes the TD containing this element expand for a instance after finish
744Effect.Squish = function(element) {
745 return new Effect.Scale(element, window.opera ? 1 : 0, {
746 restoreAfterFinish: true,
747 beforeSetup: function(effect) {
748 effect.element.makeClipping();
749 },
750 afterFinishInternal: function(effect) {
751 effect.element.hide().undoClipping();
752 }
753 });
754};
755
756Effect.Grow = function(element) {
757 element = $(element);
758 var options = Object.extend({
759 direction: 'center',
760 moveTransition: Effect.Transitions.sinoidal,
761 scaleTransition: Effect.Transitions.sinoidal,
762 opacityTransition: Effect.Transitions.full
763 }, arguments[1] || { });
764 var oldStyle = {
765 top: element.style.top,
766 left: element.style.left,
767 height: element.style.height,
768 width: element.style.width,
769 opacity: element.getInlineOpacity() };
770
771 var dims = element.getDimensions();
772 var initialMoveX, initialMoveY;
773 var moveX, moveY;
774
775 switch (options.direction) {
776 case 'top-left':
777 initialMoveX = initialMoveY = moveX = moveY = 0;
778 break;
779 case 'top-right':
780 initialMoveX = dims.width;
781 initialMoveY = moveY = 0;
782 moveX = -dims.width;
783 break;
784 case 'bottom-left':
785 initialMoveX = moveX = 0;
786 initialMoveY = dims.height;
787 moveY = -dims.height;
788 break;
789 case 'bottom-right':
790 initialMoveX = dims.width;
791 initialMoveY = dims.height;
792 moveX = -dims.width;
793 moveY = -dims.height;
794 break;
795 case 'center':
796 initialMoveX = dims.width / 2;
797 initialMoveY = dims.height / 2;
798 moveX = -dims.width / 2;
799 moveY = -dims.height / 2;
800 break;
801 }
802
803 return new Effect.Move(element, {
804 x: initialMoveX,
805 y: initialMoveY,
806 duration: 0.01,
807 beforeSetup: function(effect) {
808 effect.element.hide().makeClipping().makePositioned();
809 },
810 afterFinishInternal: function(effect) {
811 new Effect.Parallel(
812 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
813 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
814 new Effect.Scale(effect.element, 100, {
815 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
816 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
817 ], Object.extend({
818 beforeSetup: function(effect) {
819 effect.effects[0].element.setStyle({height: '0px'}).show();
820 },
821 afterFinishInternal: function(effect) {
822 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
823 }
824 }, options)
825 );
826 }
827 });
828};
829
830Effect.Shrink = function(element) {
831 element = $(element);
832 var options = Object.extend({
833 direction: 'center',
834 moveTransition: Effect.Transitions.sinoidal,
835 scaleTransition: Effect.Transitions.sinoidal,
836 opacityTransition: Effect.Transitions.none
837 }, arguments[1] || { });
838 var oldStyle = {
839 top: element.style.top,
840 left: element.style.left,
841 height: element.style.height,
842 width: element.style.width,
843 opacity: element.getInlineOpacity() };
844
845 var dims = element.getDimensions();
846 var moveX, moveY;
847
848 switch (options.direction) {
849 case 'top-left':
850 moveX = moveY = 0;
851 break;
852 case 'top-right':
853 moveX = dims.width;
854 moveY = 0;
855 break;
856 case 'bottom-left':
857 moveX = 0;
858 moveY = dims.height;
859 break;
860 case 'bottom-right':
861 moveX = dims.width;
862 moveY = dims.height;
863 break;
864 case 'center':
865 moveX = dims.width / 2;
866 moveY = dims.height / 2;
867 break;
868 }
869
870 return new Effect.Parallel(
871 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
872 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
873 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
874 ], Object.extend({
875 beforeStartInternal: function(effect) {
876 effect.effects[0].element.makePositioned().makeClipping();
877 },
878 afterFinishInternal: function(effect) {
879 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
880 }, options)
881 );
882};
883
884Effect.Pulsate = function(element) {
885 element = $(element);
886 var options = arguments[1] || { },
887 oldOpacity = element.getInlineOpacity(),
888 transition = options.transition || Effect.Transitions.linear,
889 reverser = function(pos){
890 return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
891 };
892
893 return new Effect.Opacity(element,
894 Object.extend(Object.extend({ duration: 2.0, from: 0,
895 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
896 }, options), {transition: reverser}));
897};
898
899Effect.Fold = function(element) {
900 element = $(element);
901 var oldStyle = {
902 top: element.style.top,
903 left: element.style.left,
904 width: element.style.width,
905 height: element.style.height };
906 element.makeClipping();
907 return new Effect.Scale(element, 5, Object.extend({
908 scaleContent: false,
909 scaleX: false,
910 afterFinishInternal: function(effect) {
911 new Effect.Scale(element, 1, {
912 scaleContent: false,
913 scaleY: false,
914 afterFinishInternal: function(effect) {
915 effect.element.hide().undoClipping().setStyle(oldStyle);
916 } });
917 }}, arguments[1] || { }));
918};
919
920Effect.Morph = Class.create(Effect.Base, {
921 initialize: function(element) {
922 this.element = $(element);
923 if (!this.element) throw(Effect._elementDoesNotExistError);
924 var options = Object.extend({
925 style: { }
926 }, arguments[1] || { });
927
928 if (!Object.isString(options.style)) this.style = $H(options.style);
929 else {
930 if (options.style.include(':'))
931 this.style = options.style.parseStyle();
932 else {
933 this.element.addClassName(options.style);
934 this.style = $H(this.element.getStyles());
935 this.element.removeClassName(options.style);
936 var css = this.element.getStyles();
937 this.style = this.style.reject(function(style) {
938 return style.value == css[style.key];
939 });
940 options.afterFinishInternal = function(effect) {
941 effect.element.addClassName(effect.options.style);
942 effect.transforms.each(function(transform) {
943 effect.element.style[transform.style] = '';
944 });
945 };
946 }
947 }
948 this.start(options);
949 },
950
951 setup: function(){
952 function parseColor(color){
953 if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
954 color = color.parseColor();
955 return $R(0,2).map(function(i){
956 return parseInt( color.slice(i*2+1,i*2+3), 16 );
957 });
958 }
959 this.transforms = this.style.map(function(pair){
960 var property = pair[0], value = pair[1], unit = null;
961
962 if (value.parseColor('#zzzzzz') != '#zzzzzz') {
963 value = value.parseColor();
964 unit = 'color';
965 } else if (property == 'opacity') {
966 value = parseFloat(value);
967 if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
968 this.element.setStyle({zoom: 1});
969 } else if (Element.CSS_LENGTH.test(value)) {
970 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
971 value = parseFloat(components[1]);
972 unit = (components.length == 3) ? components[2] : null;
973 }
974
975 var originalValue = this.element.getStyle(property);
976 return {
977 style: property.camelize(),
978 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
979 targetValue: unit=='color' ? parseColor(value) : value,
980 unit: unit
981 };
982 }.bind(this)).reject(function(transform){
983 return (
984 (transform.originalValue == transform.targetValue) ||
985 (
986 transform.unit != 'color' &&
987 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
988 )
989 );
990 });
991 },
992 update: function(position) {
993 var style = { }, transform, i = this.transforms.length;
994 while(i--)
995 style[(transform = this.transforms[i]).style] =
996 transform.unit=='color' ? '#'+
997 (Math.round(transform.originalValue[0]+
998 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
999 (Math.round(transform.originalValue[1]+
1000 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
1001 (Math.round(transform.originalValue[2]+
1002 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
1003 (transform.originalValue +
1004 (transform.targetValue - transform.originalValue) * position).toFixed(3) +
1005 (transform.unit === null ? '' : transform.unit);
1006 this.element.setStyle(style, true);
1007 }
1008});
1009
1010Effect.Transform = Class.create({
1011 initialize: function(tracks){
1012 this.tracks = [];
1013 this.options = arguments[1] || { };
1014 this.addTracks(tracks);
1015 },
1016 addTracks: function(tracks){
1017 tracks.each(function(track){
1018 track = $H(track);
1019 var data = track.values().first();
1020 this.tracks.push($H({
1021 ids: track.keys().first(),
1022 effect: Effect.Morph,
1023 options: { style: data }
1024 }));
1025 }.bind(this));
1026 return this;
1027 },
1028 play: function(){
1029 return new Effect.Parallel(
1030 this.tracks.map(function(track){
1031 var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
1032 var elements = [$(ids) || $$(ids)].flatten();
1033 return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
1034 }).flatten(),
1035 this.options
1036 );
1037 }
1038});
1039
1040Element.CSS_PROPERTIES = $w(
1041 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1042 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1043 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1044 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1045 'fontSize fontWeight height left letterSpacing lineHeight ' +
1046 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1047 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1048 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1049 'right textIndent top width wordSpacing zIndex');
1050
1051Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1052
1053String.__parseStyleElement = document.createElement('div');
1054String.prototype.parseStyle = function(){
1055 var style, styleRules = $H();
1056 if (Prototype.Browser.WebKit)
1057 style = new Element('div',{style:this}).style;
1058 else {
1059 String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
1060 style = String.__parseStyleElement.childNodes[0].style;
1061 }
1062
1063 Element.CSS_PROPERTIES.each(function(property){
1064 if (style[property]) styleRules.set(property, style[property]);
1065 });
1066
1067 if (Prototype.Browser.IE && this.include('opacity'))
1068 styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1069
1070 return styleRules;
1071};
1072
1073if (document.defaultView && document.defaultView.getComputedStyle) {
1074 Element.getStyles = function(element) {
1075 var css = document.defaultView.getComputedStyle($(element), null);
1076 return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
1077 styles[property] = css[property];
1078 return styles;
1079 });
1080 };
1081} else {
1082 Element.getStyles = function(element) {
1083 element = $(element);
1084 var css = element.currentStyle, styles;
1085 styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
1086 results[property] = css[property];
1087 return results;
1088 });
1089 if (!styles.opacity) styles.opacity = element.getOpacity();
1090 return styles;
1091 };
1092}
1093
1094Effect.Methods = {
1095 morph: function(element, style) {
1096 element = $(element);
1097 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
1098 return element;
1099 },
1100 visualEffect: function(element, effect, options) {
1101 element = $(element);
1102 var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
1103 new Effect[klass](element, options);
1104 return element;
1105 },
1106 highlight: function(element, options) {
1107 element = $(element);
1108 new Effect.Highlight(element, options);
1109 return element;
1110 }
1111};
1112
1113$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1114 'pulsate shake puff squish switchOff dropOut').each(
1115 function(effect) {
1116 Effect.Methods[effect] = function(element, options){
1117 element = $(element);
1118 Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
1119 return element;
1120 };
1121 }
1122);
1123
1124$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1125 function(f) { Effect.Methods[f] = Element[f]; }
1126);
1127
1128Element.addMethods(Effect.Methods);
  
1Object.extend = function(destination) {
2 $A(arguments).slice(1).each(function (src) {
3 for (var property in src) {
4 destination[property] = src[property];
5 }
6 })
7 return destination
8}
9
10Object.merge = function() {
11 return Object.extend.apply(this, [{}].concat($A(arguments)))
12}
13
14var Hobo = {
15
16 searchRequest: null,
17 uidCounter: 0,
18 ipeOldValues: {},
19 spinnerMinTime: 500, // milliseconds
20
21 uid: function() {
22 Hobo.uidCounter += 1
23 return "uid" + Hobo.uidCounter
24 },
25
26 updatesForElement: function(el) {
27 el = $(el)
28 var updates = Hobo.getClassData(el, 'update')
29 return updates ? updates.split(':') : []
30 },
31
32 ajaxSetFieldForElement: function(el, val, options) {
33 var updates = Hobo.updatesForElement(el)
34 var params = Hobo.fieldSetParam(el, val)
35 var p = el.getAttribute("hobo-ajax-params")
36 if (p) params = params + "&" + p
37
38 var opts = Object.merge(options || {}, { params: params, message: el.getAttribute("hobo-ajax-message")})
39 Hobo.ajaxRequest(Hobo.putUrl(el), updates, opts)
40 },
41
42 ajaxUpdateParams: function(updates, resultUpdates) {
43 var params = []
44 var i = 0
45 if (updates.length > 0) {
46 updates.each(function(id_or_el) {
47 var el = $(id_or_el)
48 if (el) { // ignore update of parts that do not exist
49 var partDomId = el.id
50 if (!hoboParts[partDomId]) { throw "Update of dom-id that is not a part: " + partDomId }
51 params.push("render["+i+"][part_context]=" + encodeURIComponent(hoboParts[partDomId]))
52 params.push("render["+i+"][id]=" + partDomId)
53 i += 1
54 }
55 })
56 params.push("page_path=" + hoboPagePath)
57 }
58
59 if (resultUpdates) {
60 resultUpdates.each(function (resultUpdate) {
61 params.push("render["+i+"][id]=" + resultUpdate.id)
62 params.push("render["+i+"][result]=" + resultUpdate.result)
63 if (resultUpdate.func) {
64 params.push("render["+i+"][function]=" + resultUpdate.func)
65 }
66 i += 1
67 })
68 }
69 return params.join('&')
70 },
71
72 ajaxRequest: function(url_or_form, updates, options) {
73 options = Object.merge({ asynchronous:true,
74 evalScripts:true,
75 resetForm: false,
76 refocusForm: false,
77 message: "Saving..."
78 }, options)
79 if (typeof url_or_form == "string") {
80 var url = url_or_form
81 var form = false
82 } else {
83 var form = url_or_form
84 var url = form.action
85 }
86 var params = []
87
88 if (typeof(formAuthToken) != "undefined") {
89 params.push(formAuthToken.name + "=" + formAuthToken.value)
90 }
91
92 updateParams = Hobo.ajaxUpdateParams(updates, options.resultUpdate)
93 if (updateParams != "") { params.push(updateParams) }
94
95 if (options.params) {
96 params.push(options.params)
97 delete options.params
98 }
99
100 if (form) {
101 params.push(Form.serialize(form))
102 }
103
104 if (options.message != false) Hobo.showSpinner(options.message, options.spinnerNextTo)
105
106 var complete = function() {
107 if (options.message != false) Hobo.hideSpinner();
108 if (options.onComplete) options.onComplete.apply(this, arguments)
109 if (form && options.refocusForm) Form.focusFirstElement(form)
110 Event.addBehavior.reload()
111 }
112 var success = function() {
113 if (options.onSuccess) options.onSuccess.apply(this, arguments)
114 if (form && options.resetForm) form.reset();
115 }
116 if (options.method && options.method.toLowerCase() == "put") {
117 delete options.method
118 params.push("_method=PUT")
119 }
120
121 if (!options.onFailure) {
122 options.onFailure = function(response) {
123 alert(response.responseText)
124 }
125 }
126
127 new Ajax.Request(url, Object.merge(options, { parameters: params.join("&"), onComplete: complete, onSuccess: success }))
128 },
129
130 hide: function() {
131 for (i = 0; i < arguments.length; i++) {
132 if ($(arguments[i])) {
133 Element.addClassName(arguments[i], 'hidden')
134 }
135 }
136 },
137
138 show: function() {
139 for (i = 0; i < arguments.length; i++) {
140 if ($(arguments[i])) {
141 Element.removeClassName(arguments[i], 'hidden')
142 }
143 }
144 },
145
146 toggle: function() {
147 for (i = 0; i < arguments.length; i++) {
148 if ($(arguments[i])) {
149 if(Element.hasClassName(arguments[i], 'hidden')) {
150 Element.removeClassName(arguments[i], 'hidden')
151 } else {
152 Element.addClassName(arguments[i], 'hidden')
153 }
154 }
155 }
156 },
157
158 onFieldEditComplete: function(el, newValue) {
159 el = $(el)
160 var oldValue = Hobo.ipeOldValues[el.id]
161 delete Hobo.ipeOldValues[el.id]
162
163 var blank = el.getAttribute("hobo-blank-message")
164 if (blank && newValue.strip().length == 0) {
165 el.update(blank)
166 } else {
167 el.update(newValue)
168 }
169
170 var modelId = Hobo.getModelId(el)
171 if (oldValue) {
172 $$(".model:" + modelId).each(function(e) {
173 if (e != el && e.innerHTML == oldValue) e.update(newValue)
174 })
175 }
176 },
177
178 _makeInPlaceEditor: function(el, options) {
179 var old
180 var updates = Hobo.updatesForElement(el)
181 var id = el.id
182 if (!id) { id = el.id = Hobo.uid() }
183 var updateParams = Hobo.ajaxUpdateParams(updates, [{id: id,
184 result: 'new_field_value',
185 func: "Hobo.onFieldEditComplete"}])
186 var opts = {okButton: false,
187 cancelLink: false,
188 submitOnBlur: true,
189 evalScripts: true,
190 htmlResponse: false,
191 ajaxOptions: { method: "put" },
192 onEnterHover: null,
193 onLeaveHover: null,
194 callback: function(form, val) {
195 old = val
196 return (Hobo.fieldSetParam(el, val) + "&" + updateParams)
197 },
198 onFailure: function(_, resp) {
199 alert(resp.responseText); el.innerHTML = old
200 },
201 onEnterEditMode: function() {
202 var blank_message = el.getAttribute("hobo-blank-message")
203 if (el.innerHTML.gsub("&nbsp;", " ") == blank_message) {
204 el.innerHTML = ""
205 } else {
206 Hobo.ipeOldValues[el.id] = el.innerHTML
207 }
208 }
209 }
210 Object.extend(opts, options)
211 return new Ajax.InPlaceEditor(el, Hobo.putUrl(el), opts)
212 },
213
214
215 doSearch: function(el) {
216 el = $(el)
217 var spinner = $(el.getAttribute("search-spinner") || "search-spinner")
218 var search_results = $(el.getAttribute("search-results") || "search-results")
219 var search_results_panel = $(el.getAttribute("search-results-panel") || "search-results-panel")
220 var url = el.getAttribute("search-url") || (urlBase + "/search")
221
222 var clear = function() { Hobo.hide(search_results_panel); el.clear() }
223
224 // Close window on [Escape]
225 Event.observe(el, 'keypress', function(ev) {
226 if (ev.keyCode == 27) clear()
227 });
228
229 Event.observe(search_results_panel.down('.close-button'), 'click', clear)
230
231 var value = $F(el)
232 if (Hobo.searchRequest) { Hobo.searchRequest.transport.abort() }
233 if (value.length >= 3) {
234 if (spinner) Hobo.show(spinner);
235 Hobo.searchRequest = new Ajax.Updater(search_results,
236 url,
237 { asynchronous:true,
238 evalScripts:true,
239 onSuccess:function(request) {
240 if (spinner) Hobo.hide(spinner)
241 if (search_results_panel) {
242 Hobo.show(search_results_panel)
243 }
244 },
245 method: "get",
246 parameters:"query=" + value });
247 } else {
248 Hobo.updateElement(search_results, '')
249 Hobo.hide(search_results_panel)
250 }
251 },
252
253
254 putUrl: function(el) {
255 var spec = Hobo.modelSpecForElement(el)
256 return urlBase + "/" + Hobo.pluralise(spec.name) + "/" + spec.id + "?_method=PUT"
257 },
258
259
260 urlForId: function(id) {
261 var spec = Hobo.parseModelSpec(id)
262 var url = urlBase + "/" + Hobo.pluralise(spec.name)
263 if (spec.id) { url += "/" + spec.id }
264 return url
265 },
266
267
268 fieldSetParam: function(el, val) {
269 var spec = Hobo.modelSpecForElement(el)
270 var res = spec.name + '[' + spec.field + ']=' + encodeURIComponent(val)
271 if (typeof(formAuthToken) != "undefined") {
272 res = res + "&" + formAuthToken.name + "=" + formAuthToken.value
273 }
274 return res
275 },
276
277
278 fadeObjectElement: function(el) {
279 var fadeEl = Hobo.objectElementFor(el)
280 new Effect.Fade(fadeEl, { duration: 0.5, afterFinish: function (ef) {
281 ef.element.remove()
282 } });
283 Hobo.showEmptyMessageAfterLastRemove(fadeEl)
284 },
285
286
287 removeButton: function(el, url, updates, options) {
288 if (options.fade == null) { options.fade = true; }
289 if (options.confirm == null) { options.fade = "Are you sure?"; }
290
291 if (options.confirm == false || confirm(options.confirm)) {
292 var objEl = Hobo.objectElementFor(el)
293 Hobo.showSpinner('Removing');
294 function complete() {
295 if (options.fade) { Hobo.fadeObjectElement(objEl) }
296 Hobo.hideSpinner()
297 }
298 if (updates && updates.length > 0) {
299 new Hobo.ajaxRequest(url, updates, { method:'delete', message: "Removing...", onComplete: complete});
300 } else {
301 var ajaxOptions = {asynchronous:true, evalScripts:true, method:'delete', onComplete: complete}
302 if (typeof(formAuthToken) != "undefined") {
303 ajaxOptions.parameters = formAuthToken.name + "=" + formAuthToken.value
304 }
305 new Ajax.Request(url, ajaxOptions);
306 }
307 }
308 },
309
310
311 ajaxUpdateField: function(element, field, value, updates) {
312 var objectElement = Hobo.objectElementFor(element)
313 var url = Hobo.putUrl(objectElement)
314 var spec = Hobo.modelSpecForElement(objectElement)
315 var params = spec.name + '[' + field + ']=' + encodeURIComponent(value)
316 new Hobo.ajaxRequest(url, updates, { method:'put', message: "Saving...", params: params });
317 },
318
319
320 showEmptyMessageAfterLastRemove: function(el) {
321 var empty
322 var container = $(el.parentNode)
323 if (container.getElementsByTagName(el.nodeName).length == 1 &&
324 (empty = container.next('.empty-collection-message'))) {
325 new Effect.Appear(empty, {delay:0.3})
326 }
327 },
328
329
330 getClassData: function(el, name) {
331 var match = el.className.match(new RegExp("(^| )" + name + "::(\\S+)($| )"))
332 return match && match[2]
333 },
334
335
336 getModelId: function(el) {
337 return Hobo.getClassData(el, 'model')
338 },
339
340
341 modelSpecForElement: function(el) {
342 var id = Hobo.getModelId(el)
343 return id && Hobo.parseModelSpec(id)
344 },
345
346
347 parseModelSpec: function(id) {
348 m = id.gsub('-', '_').match(/^([^:]+)(?::([^:]+)(?::([^:]+))?)?$/)
349 if (m) return { name: m[1], id: m[2], field: m[3] }
350 },
351
352
353 objectElementFor: function(el) {
354 var m
355 while(el.getAttribute) {
356 id = Hobo.getModelId(el)
357 if (id) m = id.match(/^[^:]+:[^:]+$/);
358 if (m) break;
359 el = el.parentNode;
360 }
361 if (m) return el;
362 },
363
364 modelIdFor: function(el) {
365 var e = Hobo.objectElementFor(el)
366 return e && Hobo.getModelId(e)
367 },
368
369
370 showSpinner: function(message, nextTo) {
371 clearTimeout(Hobo.spinnerTimer)
372 Hobo.spinnerHideAt = new Date().getTime() + Hobo.spinnerMinTime;
373 if (t = $('ajax-progress-text')) {
374 if (!message || message.length == 0) {
375 t.hide()
376 } else {
377 Element.update(t, message);
378 t.show()
379 }
380 }
381 if (e = $('ajax-progress')) {
382 if (nextTo) {
383 var e_nextTo = $(nextTo);
384 var pos = e_nextTo.cumulativeOffset()
385 e.style.top = pos.top - e_nextTo.offsetHeight + "px"
386 e.style.left = (pos.left + e_nextTo.offsetWidth + 5) + "px"
387 }
388 e.style.display = "block";
389 }
390 },
391
392
393 hideSpinner: function() {
394 if (e = $('ajax-progress')) {
395 var remainingTime = Hobo.spinnerHideAt - new Date().getTime()
396 if (remainingTime <= 0) {
397 e.visualEffect('Fade')
398 } else {
399 Hobo.spinnerTimer = setTimeout(function () { e.visualEffect('Fade') }, remainingTime)
400 }
401 }
402 },
403
404
405 updateElement: function(id, content) {
406 // TODO: Do we need this method?
407 Element.update(id, content)
408 },
409
410 getStyle: function(el, styleProp) {
411 if (el.currentStyle)
412 var y = el.currentStyle[styleProp];
413 else if (window.getComputedStyle)
414 var y = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
415 return y;
416 },
417
418 partFor: function(el) {
419 while (el) {
420 if (el.id && hoboParts[el.id]) { return el }
421 el = el.parentNode
422 }
423 return null
424 },
425
426 pluralise: function(s) {
427 return pluralisations[s] || s + "s"
428 },
429
430 addUrlParams: function(params, options) {
431 params = $H(window.location.search.toQueryParams()).merge(params)
432
433 if (options.remove) {
434 var remove = (options.remove instanceof Array) ? options.remove : [options.remove]
435 remove.each(function(k) { params.unset(k) })
436 }
437
438 return window.location.href.sub(/(\?.*|$)/, "?" + params.toQueryString())
439 },
440
441
442 fixSectionGroup: function(e) {
443 rows = e.childElements().map(function(e, i) {
444 cells = e.childElements().map(function(e, i) {
445 return e.outerHTML.sub("<DIV", "<td valign='top'").sub(/<\/DIV>$/i, "</td>")
446 }).join('')
447
448 var attrs = e.outerHTML.match(/<DIV([^>]+)/)[1]
449 return "<tr" + attrs + ">" + cells + "</tr>"
450 }).join("\n")
451
452 var attrs = e.outerHTML.match(/<DIV([^>]+)/)[1]
453
454 var table= "<table cellpadding='0' cellspacing='0' border='0' style='border-collapse: collapse; border-spacing: 0'" + attrs + ">" +
455 rows + "</table>"
456 e.outerHTML = table
457 },
458
459 makeHtmlEditor: function(textarea) {
460 // do nothing - plugins can overwrite this method
461 }
462
463
464}
465
466Element.findContaining = function(el, tag) {
467 el = $(el)
468 tag = tag.toLowerCase()
469 e = el.parentNode
470 while (el) {
471 if (el.nodeName.toLowerCase() == tag) {
472 return el;
473 }
474 e = el.parentNode
475 }
476 return null;
477}
478
479// Add an afterEnterEditMode hook to in-place-editor
480origEnterEditMode = Ajax.InPlaceEditor.prototype.enterEditMode
481Ajax.InPlaceEditor.prototype.enterEditMode = function(evt) {
482 origEnterEditMode.bind(this)(evt)
483 if (this.afterEnterEditMode) this.afterEnterEditMode()
484 return false
485}
486
487// Fix Safari in-place-editor bug
488Ajax.InPlaceEditor.prototype.removeForm = function() {
489 if (!this._form) return;
490
491 if (this._form.parentNode) { try { Element.remove(this._form); } catch (e) {}}
492 this._form = null;
493 this._controls = { };
494}
495
496// Silence errors from IE :-(
497Field.scrollFreeActivate = function(field) {
498 setTimeout(function() {
499 try {
500 Field.activate(field);
501 } catch(e) {}
502 }, 1);
503}
504
505
506Element.Methods.$$ = function(e, css) {
507 return new Selector(css).findElements(e)
508}
509
510
511HoboBehavior = Class.create({
512
513 initialize: function(mainSelector, features) {
514 this.mainSelector = mainSelector
515 this.features = features
516 this.addEvents(mainSelector, features.events)
517 },
518
519 addEvents: function(parentSelector, events) {
520 var self = this
521
522 for (selector in events) {
523 fullSelector = parentSelector + ' ' + selector
524 var rhs = events[selector]
525 if (Object.isString(rhs)) {
526 this.addBehavior(fullSelector, this.features[rhs])
527 } else {
528 this.addEvents(fullSelector, rhs)
529 }
530 }
531
532 },
533
534 addBehavior: function(selector, handler) {
535 var self = this
536 behavior = {}
537 behavior[selector] = function(ev) {
538 self.features.element = this.up(self.mainSelector)
539 handler.call(self.features, ev, this)
540 }
541 Event.addBehavior(behavior)
542 }
543
544})
545
546
547new HoboBehavior("ul.input-many", {
548
549 events: {
550 "> li > div.buttons": {
551 ".add-item:click": 'addOne',
552 ".remove-item:click": 'removeOne'
553 }
554 },
555
556 addOne: function(ev, el) {
557 Event.stop(ev)
558 var ul = el.up('ul'), li = el.up('li')
559
560 var thisItem = li.down('div.input-many-item')
561 var newItem = "<li style='display:none'><div class='input-many-item'>" +
562 thisItem.innerHTML +
563 "</div>" +
564 "<div class='buttons' />" +
565 "</div></li>"
566 var newItem = DOM.Builder.fromHTML(newItem)
567 ul.appendChild(newItem);
568 this.clearInputs(newItem);
569
570 this.updateButtons()
571 this.updateInputNames()
572
573 ul.fire("rapid:add", { element: newItem })
574 ul.fire("rapid:change", { element: newItem })
575
576 new Effect.BlindDown(newItem, {duration: 0.3})
577 },
578
579 removeOne: function(ev, el) {
580 Event.stop(ev)
581 var self = this;
582 var ul = el.up('ul'), li = el.up('li')
583 if (li.parentNode.childElements().length == 1) {
584 // It's the last one - don't remove it, just clear it
585 this.clearInputs(li)
586 } else {
587 new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) {
588 li.remove()
589 self.updateButtons()
590 self.updateInputNames()
591 } });
592 }
593 ul.fire("rapid:remove")
594 ul.fire("rapid:change")
595 },
596
597
598 clearInputs: function(item) {
599 $(item).select('input,select,textarea').each(function(input){
600 t = input.getAttribute('type')
601 if (t && t.match(/hidden/i)) {
602 input.remove()
603 } else {
604 input.value = ""
605 }
606 })
607 },
608
609 updateButtons: function() {
610 var removeButton = "<button class='remove-item'>-</button>"
611 var addButton = "<button class='add-item'>+</button>"
612
613 var ul = this.element
614 var children = ul.childElements();
615 // assumption: only get here after add or remove, so only second last button needs the "+" removed
616 if(children.length > 1) {
617 // cannot use .down() because that's a depth-first search. Did I mention that I hate Prototype?
618 children[children.length-2].childElements().last().innerHTML = removeButton;
619 }
620 if(children.length > 0) {
621 children[children.length-1].childElements().last().innerHTML = removeButton + ' ' + addButton;
622 }
623 Event.addBehavior.reload()
624 },
625
626 updateInputNames: function() {
627 var prefix = Hobo.getClassData(this.element, 'input-many-prefix')
628
629 this.element.selectChildren('li').each(function(li, index) {
630 li.select('*[name]').each(function(control) {
631 var changeId = control.id == control.name
632 control.name = control.name.sub(new RegExp("^" + RegExp.escape(prefix) + "\[[0-9]+\]"), prefix + '[' + index +']')
633 if (changeId) control.id = control.name
634 })
635 })
636 }
637
638})
639
640
641SelectManyInput = Behavior.create({
642
643 initialize : function() {
644 // onchange doesn't bubble in IE6 so...
645 Event.observe(this.element.down('select'), 'change', this.addOne.bind(this))
646 },
647
648 addOne : function() {
649 var select = this.element.down('select')
650 var selected = select.options[select.selectedIndex]
651 if ($F(select) != "") {
652 var newItem = $(DOM.Builder.fromHTML(this.element.down('.item-proto').innerHTML.strip()))
653 this.element.down('.items').appendChild(newItem);
654 newItem.down('span').innerHTML = selected.innerHTML
655 this.itemAdded(newItem, selected)
656 selected.disabled = true
657 select.value = ""
658 Event.addBehavior.reload()
659 this.element.fire("rapid:add", { element: newItem })
660 this.element.fire("rapid:change", { element: newItem })
661 }
662 },
663
664 onclick : function(ev) {
665 var el = Event.element(ev);
666 if (el.match(".remove-item")) { this.removeOne(el.parentNode) }
667 },
668
669 removeOne : function(el) {
670 var element = this.element
671 new Effect.BlindUp(el,
672 { duration: 0.3,
673 afterFinish: function (ef) {
674 ef.element.remove()
675 element.fire("rapid:remove", { element: el })
676 element.fire("rapid:change", { element: el })
677 } } )
678 var label = el.down('span').innerHTML
679 var option = $A(element.getElementsByTagName('option')).find(function(o) { return o.innerHTML == label })
680 option.disabled = false
681 },
682
683 itemAdded: function(item, option) {
684 this.hiddenField(item).value = option.value
685 },
686
687 hiddenField: function(item) {
688 return item.down('input[type=hidden]')
689 //return item.getElementsByClassName("hidden-field")[0]
690 }
691
692
693})
694
695NameManyInput = Object.extend(SelectManyInput, {
696 addOne : function() {
697 var select = this.element.down('select')
698 var selected = select.options[select.selectedIndex]
699 if (selected.value != "") {
700 var newItem = $(DOM.Builder.fromHTML(this.element.down('.item-proto').innerHTML.strip()))
701 this.element.down('.items').appendChild(newItem);
702 newItem.down('span').innerHTML = selected.innerHTML
703 this.itemAdded(newItem, selected)
704 selected.disabled = true
705 select.value = ""
706 Event.addBehavior.reload()
707 }
708 }
709})
710
711
712AutocompleteBehavior = Behavior.create({
713 initialize : function() {
714 var match = this.element.className.match(/complete-on::([\S]+)/)
715 var target = match[1].split('::')
716 var typedId = target[0]
717 var completer = target[1]
718
719 var spec = Hobo.parseModelSpec(typedId)
720 var url = urlBase + "/" + Hobo.pluralise(spec.name) + "/complete_" + completer
721 var parameters = spec.id ? "id=" + spec.id : ""
722 new Ajax.Autocompleter(this.element,
723 this.element.next('.completions-popup'),
724 url,
725 {paramName:'query', method:'get', parameters: parameters});
726 }
727})
728
729
730
731Event.addBehavior.reassignAfterAjax = true;
732Event.addBehavior({
733
734 'div.section-group' : function() {
735 if (Prototype.Browser.IE) Hobo.fixSectionGroup(this);
736 },
737
738 'div.select-many.input' : SelectManyInput(),
739
740 'textarea.html' : function() {
741 Hobo.makeHtmlEditor(this)
742 },
743
744 'form.filter-menu select:change': function(event) {
745 var paramName = this.getAttribute('name')
746 var params = {}
747 var remove = [ 'page' ]
748 if ($F(this) == '') {
749 remove.push(paramName)
750 } else {
751 params[paramName] = $F(this)
752 }
753 location.href = Hobo.addUrlParams(params, {remove: remove})
754 },
755
756 '.autocompleter' : AutocompleteBehavior(),
757
758 '.string.in-place-edit, .datetime.in-place-edit, .date.in-place-edit, .integer.in-place-edit, .float.in-place-edit, big-integer.in-place-edit' :
759 function (ev) {
760
761 var ipe = Hobo._makeInPlaceEditor(this)
762 ipe.getText = function() {
763 return this.element.innerHTML.gsub(/<br\s*\/?>/, "\n").unescapeHTML()
764 }
765 },
766
767 '.text.in-place-edit, .markdown.in-place-edit, .textile.in-place-edit' : function (ev) {
768 var ipe = Hobo._makeInPlaceEditor(this, {rows: 2})
769 ipe.getText = function() {
770 return this.element.innerHTML.gsub(/<br\s*\/?>/, "\n").unescapeHTML()
771 }
772 },
773
774 ".html.in-place-edit" : function (ev) {
775 if (Hobo.makeInPlaceHtmlEditor) {
776 Hobo.makeInPlaceHtmlEditor(this)
777 } else {
778 var options = {
779 rows: 2, handleLineBreaks: false, okButton: true, cancelLink: true, okText: "Save", submitOnBlur: false
780 }
781 var ipe = Hobo._makeInPlaceEditor(this, options)
782 }
783 },
784
785 "select.integer.editor" : function(e) {
786 var el = this
787 el.onchange = function() {
788 Hobo.ajaxSetFieldForElement(el, $F(el))
789 }
790 },
791
792 "input.live-search[type=search]" : function(e) {
793 var element = this
794 new Form.Element.Observer(element, 1.0, function() { Hobo.doSearch(element) })
795 }
796
797
798});
799
800ElementSet = Class.create(Enumerable, {
801
802 initialize: function(array) {
803 this.items = array
804 },
805
806 _each: function(fn) {
807 return this.items.each(fn)
808 },
809
810 selectChildren: function(selector) {
811 return new ElementSet(this.items.invoke('selectChildren', selector).pluck('items').flatten())
812 },
813
814 child: function(selector) {
815 return this.selectChildren(selector).first()
816 },
817
818 select: function(selector) {
819 return new ElementSet(this.items.invoke('select', selector).flatten())
820 },
821
822 down: function(selector) {
823 for (var i = 0; i < this.items.length; i++) {
824 var match = this.items[i].down(selector)
825 if (match) return match
826 }
827 return null
828 },
829
830 size: function() {
831 return this.items.length
832 },
833
834 first: function() {
835 return this.items.first()
836 },
837
838 last: function() {
839 return this.items.last()
840 }
841
842})
843
844Element.addMethods({
845 selectChildren: function(element, selector) {
846 return new ElementSet(Selector.matchElements(element.childElements(), selector))
847 }
848})
  
1/* IE7/IE8.js - copyright 2004-2008, Dean Edwards */
2(function(){if(!IE7.loaded)return;CLASSES=/\sie7_class\d+/g;IE7.CSS.extend({elements:{},handlers:[],reset:function(){this.removeEventHandlers();var a=this.elements;for(var b in a)a[b].runtimeStyle.cssText="";this.elements={};var a=IE7.Rule.elements;for(var b in a){with(a[b])className=className.replace(CLASSES,"")}IE7.Rule.elements={}},reload:function(){this.rules=[];this.getInlineStyles();this.screen.load();if(this.print)this.print.load();this.refresh();this.trash()},addRecalc:function(b,c,d,e){this.base(b,c,function(a){d(a);IE7.CSS.elements[a.uniqueID]=a},e)},recalc:function(){this.reset();this.base()},addEventHandler:function(a,b,c){a.attachEvent(b,c);this.handlers.push(arguments)},removeEventHandlers:function(){var a;while(a=this.handlers.pop()){a[0].detachEvent(a[1],a[2])}},getInlineStyles:function(){var a=document.getElementsByTagName("style"),b;for(var c=a.length-1;(b=a[c]);c--){if(!b.disabled&&!b.ie7){var d=b.cssText||b.innerHTML;this.styles.push(d);b.cssText=d}}},trash:function(){var a=document.styleSheets,b,c;for(c=0;c<a.length;c++){b=a[c];if(!b.ie7&&!b.cssText){b.cssText=b.cssText}}this.base()},getText:function(a){return a.cssText||this.base(a)}});IE7.CSS.addEventHandler(window,"onunload",function(){IE7.CSS.removeEventHandlers()});IE7.Rule.elements={};IE7.Rule.prototype.extend({add:function(a){this.base(a);IE7.Rule.elements[a.uniqueID]=a}});if(IE7.PseudoElement){IE7.PseudoElement.hash={};IE7.PseudoElement.prototype.extend({create:function(a){var b=this.selector+":"+a.uniqueID;if(!IE7.PseudoElement.hash[b]){IE7.PseudoElement.hash[b]=true;this.base(a)}}})}IE7.HTML.extend({elements:{},addRecalc:function(b,c){this.base(b,function(a){if(!this.elements[a.uniqueID]){c(a);this.elements[a.uniqueID]=a}})}});document.recalc=function(a){if(IE7.CSS.screen){if(a)IE7.CSS.reload();IE7.recalc()}}})();
  
1LowPro = {};
2LowPro.Version = '0.5';
3LowPro.CompatibleWithPrototype = '1.6';
4
5if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && console && console.warn)
6 console.warn("This version of Low Pro is tested with Prototype " + LowPro.CompatibleWithPrototype +
7 " it may not work as expected with this version (" + Prototype.Version + ")");
8
9if (!Element.addMethods)
10 Element.addMethods = function(o) { Object.extend(Element.Methods, o) };
11
12// Simple utility methods for working with the DOM
13DOM = {};
14
15// DOMBuilder for prototype
16DOM.Builder = {
17 tagFunc : function(tag) {
18 return function() {
19 var attrs, children;
20 if (arguments.length>0) {
21 if (arguments[0].nodeName ||
22 typeof arguments[0] == "string")
23 children = arguments;
24 else {
25 attrs = arguments[0];
26 children = Array.prototype.slice.call(arguments, 1);
27 };
28 }
29 return DOM.Builder.create(tag, attrs, children);
30 };
31 },
32 create : function(tag, attrs, children) {
33 attrs = attrs || {}; children = children || []; tag = tag.toLowerCase();
34 var el = new Element(tag, attrs);
35
36 for (var i=0; i<children.length; i++) {
37 if (typeof children[i] == 'string')
38 children[i] = document.createTextNode(children[i]);
39 el.appendChild(children[i]);
40 }
41 return $(el);
42 }
43};
44
45// Automatically create node builders as $tagName.
46(function() {
47 var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" +
48 "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" +
49 "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
50 "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" +
51 "label|dfn|kbd|samp|var").split("|");
52 var el, i=0;
53 while (el = els[i++])
54 window['$' + el] = DOM.Builder.tagFunc(el);
55})();
56
57DOM.Builder.fromHTML = function(html) {
58 var root;
59 if (!(root = arguments.callee._root))
60 root = arguments.callee._root = document.createElement('div');
61 root.innerHTML = html;
62 return root.childNodes[0];
63};
64
65
66
67// Wraps the 1.6 contentloaded event for backwards compatibility
68//
69// Usage:
70//
71// Event.onReady(callbackFunction);
72Object.extend(Event, {
73 onReady : function(f) {
74 if (document.body) f();
75 else document.observe('dom:loaded', f);
76 }
77});
78
79// Based on event:Selectors by Justin Palmer
80// http://encytemedia.com/event-selectors/
81//
82// Usage:
83//
84// Event.addBehavior({
85// "selector:event" : function(event) { /* event handler. this refers to the element. */ },
86// "selector" : function() { /* runs function on dom ready. this refers to the element. */ }
87// ...
88// });
89//
90// Multiple calls will add to exisiting rules. Event.addBehavior.reassignAfterAjax and
91// Event.addBehavior.autoTrigger can be adjusted to needs.
92Event.addBehavior = function(rules) {
93 var ab = this.addBehavior;
94 Object.extend(ab.rules, rules);
95
96 if (!ab.responderApplied) {
97 Ajax.Responders.register({
98 onComplete : function() {
99 if (Event.addBehavior.reassignAfterAjax)
100 setTimeout(function() { ab.reload() }, 10);
101 }
102 });
103 ab.responderApplied = true;
104 }
105
106 if (ab.autoTrigger) {
107 this.onReady(ab.load.bind(ab, rules));
108 }
109
110};
111
112Object.extend(Event.addBehavior, {
113 rules : {}, cache : [],
114 reassignAfterAjax : false,
115 autoTrigger : true,
116
117 load : function(rules) {
118 for (var selector in rules) {
119 var observer = rules[selector];
120 var sels = selector.split(',');
121 sels.each(function(sel) {
122 var match = sel.match(/^([^:]*)(?::(.*)$)?/), css = match[1], event = match[2];
123 $$(css).each(function(element) {
124 if (event) {
125 observer = Event.addBehavior._wrapObserver(observer);
126 $(element).observe(event, observer);
127 Event.addBehavior.cache.push([element, event, observer]);
128 } else {
129 if (!element.$$assigned || !element.$$assigned.include(observer)) {
130 if (observer.attach) observer.attach(element);
131
132 else observer.call($(element));
133 element.$$assigned = element.$$assigned || [];
134 element.$$assigned.push(observer);
135 }
136 }
137 });
138 });
139 }
140 },
141
142 unload : function() {
143 this.cache.each(function(c) {
144 Event.stopObserving.apply(Event, c);
145 });
146 this.cache = [];
147 },
148
149 reload: function() {
150 var ab = Event.addBehavior;
151 ab.unload();
152 ab.load(ab.rules);
153 },
154
155 _wrapObserver: function(observer) {
156 return function(event) {
157 if (observer.call(this, event) === false) event.stop();
158 }
159 }
160
161});
162
163Event.observe(window, 'unload', Event.addBehavior.unload.bind(Event.addBehavior));
164
165// A silly Prototype style shortcut for the reckless
166$$$ = Event.addBehavior.bind(Event);
167
168// Behaviors can be bound to elements to provide an object orientated way of controlling elements
169// and their behavior. Use Behavior.create() to make a new behavior class then use attach() to
170// glue it to an element. Each element then gets it's own instance of the behavior and any
171// methods called onxxx are bound to the relevent event.
172//
173// Usage:
174//
175// var MyBehavior = Behavior.create({
176// onmouseover : function() { this.element.addClassName('bong') }
177// });
178//
179// Event.addBehavior({ 'a.rollover' : MyBehavior });
180//
181// If you need to pass additional values to initialize use:
182//
183// Event.addBehavior({ 'a.rollover' : MyBehavior(10, { thing : 15 }) })
184//
185// You can also use the attach() method. If you specify extra arguments to attach they get passed to initialize.
186//
187// MyBehavior.attach(el, values, to, init);
188//
189// Finally, the rawest method is using the new constructor normally:
190// var draggable = new Draggable(element, init, vals);
191//
192// Each behaviour has a collection of all its instances in Behavior.instances
193//
194var Behavior = {
195 create: function() {
196 var parent = null, properties = $A(arguments);
197 if (Object.isFunction(properties[0]))
198 parent = properties.shift();
199
200 var behavior = function() {
201 var behavior = arguments.callee;
202 if (!this.initialize) {
203 var args = $A(arguments);
204
205 return function() {
206 var initArgs = [this].concat(args);
207 behavior.attach.apply(behavior, initArgs);
208 };
209 } else {
210 var args = (arguments.length == 2 && arguments[1] instanceof Array) ?
211 arguments[1] : Array.prototype.slice.call(arguments, 1);
212
213 this.element = $(arguments[0]);
214 this.initialize.apply(this, args);
215 behavior._bindEvents(this);
216 behavior.instances.push(this);
217 }
218 };
219
220 Object.extend(behavior, Class.Methods);
221 Object.extend(behavior, Behavior.Methods);
222 behavior.superclass = parent;
223 behavior.subclasses = [];
224 behavior.instances = [];
225
226 if (parent) {
227 var subclass = function() { };
228 subclass.prototype = parent.prototype;
229 behavior.prototype = new subclass;
230 parent.subclasses.push(behavior);
231 }
232
233 for (var i = 0; i < properties.length; i++)
234 behavior.addMethods(properties[i]);
235
236 if (!behavior.prototype.initialize)
237 behavior.prototype.initialize = Prototype.emptyFunction;
238
239 behavior.prototype.constructor = behavior;
240
241 return behavior;
242 },
243 Methods : {
244 attach : function(element) {
245 return new this(element, Array.prototype.slice.call(arguments, 1));
246 },
247 _bindEvents : function(bound) {
248 for (var member in bound)
249 if (member.match(/^on(.+)/) && typeof bound[member] == 'function')
250 bound.element.observe(RegExp.$1, Event.addBehavior._wrapObserver(bound[member].bindAsEventListener(bound)));
251 }
252 }
253};
254
255Remote = Behavior.create({
256 initialize: function(options) {
257 if (this.element.nodeName == 'FORM') new Remote.Form(this.element, options);
258 else new Remote.Link(this.element, options);
259 }
260});
261
262Remote.Base = {
263 initialize : function(options) {
264 this.options = Object.extend({
265 evaluateScripts : true
266 }, options || {});
267 },
268 _makeRequest : function(options) {
269 if (options.update) new Ajax.Updater(options.update, options.url, options);
270 else new Ajax.Request(options.url, options);
271 return false;
272 }
273}
274
275Remote.Link = Behavior.create(Remote.Base, {
276 onclick : function() {
277 var options = Object.extend({ url : this.element.href, method : 'get' }, this.options);
278 return this._makeRequest(options);
279 }
280});
281
282
283Remote.Form = Behavior.create(Remote.Base, {
284 onclick : function(e) {
285 var sourceElement = e.element();
286
287 if (['input', 'button'].include(sourceElement.nodeName.toLowerCase()) &&
288 sourceElement.type == 'submit')
289 this._submitButton = sourceElement;
290 },
291 onsubmit : function() {
292 var options = Object.extend({
293 url : this.element.action,
294 method : this.element.method || 'get',
295 parameters : this.element.serialize({ submit: this._submitButton.name })
296 }, this.options);
297 this._submitButton = null;
298 return this._makeRequest(options);
299 }
300});
301
302Observed = Behavior.create({
303 initialize : function(callback, options) {
304 this.callback = callback.bind(this);
305 this.options = options || {};
306 this.observer = (this.element.nodeName == 'FORM') ? this._observeForm() : this._observeField();
307 },
308 stop: function() {
309 this.observer.stop();
310 },
311 _observeForm: function() {
312 return (this.options.frequency) ? new Form.Observer(this.element, this.options.frequency, this.callback) :
313 new Form.EventObserver(this.element, this.callback);
314 },
315 _observeField: function() {
316 return (this.options.frequency) ? new Form.Element.Observer(this.element, this.options.frequency, this.callback) :
317 new Form.Element.EventObserver(this.element, this.callback);
318 }
319});
  
1/* Prototype JavaScript framework, version 1.6.0.3
2 * (c) 2005-2008 Sam Stephenson
3 *
4 * Prototype is freely distributable under the terms of an MIT-style license.
5 * For details, see the Prototype web site: http://www.prototypejs.org/
6 *
7 *--------------------------------------------------------------------------*/
8
9var Prototype = {
10 Version: '1.6.0.3',
11
12 Browser: {
13 IE: !!(window.attachEvent &&
14 navigator.userAgent.indexOf('Opera') === -1),
15 Opera: navigator.userAgent.indexOf('Opera') > -1,
16 WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
17 Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
18 navigator.userAgent.indexOf('KHTML') === -1,
19 MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
20 },
21
22 BrowserFeatures: {
23 XPath: !!document.evaluate,
24 SelectorsAPI: !!document.querySelector,
25 ElementExtensions: !!window.HTMLElement,
26 SpecificElementExtensions:
27 document.createElement('div')['__proto__'] &&
28 document.createElement('div')['__proto__'] !==
29 document.createElement('form')['__proto__']
30 },
31
32 ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
33 JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
34
35 emptyFunction: function() { },
36 K: function(x) { return x }
37};
38
39if (Prototype.Browser.MobileSafari)
40 Prototype.BrowserFeatures.SpecificElementExtensions = false;
41
42
43/* Based on Alex Arnell's inheritance implementation. */
44var Class = {
45 create: function() {
46 var parent = null, properties = $A(arguments);
47 if (Object.isFunction(properties[0]))
48 parent = properties.shift();
49
50 function klass() {
51 this.initialize.apply(this, arguments);
52 }
53
54 Object.extend(klass, Class.Methods);
55 klass.superclass = parent;
56 klass.subclasses = [];
57
58 if (parent) {
59 var subclass = function() { };
60 subclass.prototype = parent.prototype;
61 klass.prototype = new subclass;
62 parent.subclasses.push(klass);
63 }
64
65 for (var i = 0; i < properties.length; i++)
66 klass.addMethods(properties[i]);
67
68 if (!klass.prototype.initialize)
69 klass.prototype.initialize = Prototype.emptyFunction;
70
71 klass.prototype.constructor = klass;
72
73 return klass;
74 }
75};
76
77Class.Methods = {
78 addMethods: function(source) {
79 var ancestor = this.superclass && this.superclass.prototype;
80 var properties = Object.keys(source);
81
82 if (!Object.keys({ toString: true }).length)
83 properties.push("toString", "valueOf");
84
85 for (var i = 0, length = properties.length; i < length; i++) {
86 var property = properties[i], value = source[property];
87 if (ancestor && Object.isFunction(value) &&
88 value.argumentNames().first() == "$super") {
89 var method = value;
90 value = (function(m) {
91 return function() { return ancestor[m].apply(this, arguments) };
92 })(property).wrap(method);
93
94 value.valueOf = method.valueOf.bind(method);
95 value.toString = method.toString.bind(method);
96 }
97 this.prototype[property] = value;
98 }
99
100 return this;
101 }
102};
103
104var Abstract = { };
105
106Object.extend = function(destination, source) {
107 for (var property in source)
108 destination[property] = source[property];
109 return destination;
110};
111
112Object.extend(Object, {
113 inspect: function(object) {
114 try {
115 if (Object.isUndefined(object)) return 'undefined';
116 if (object === null) return 'null';
117 return object.inspect ? object.inspect() : String(object);
118 } catch (e) {
119 if (e instanceof RangeError) return '...';
120 throw e;
121 }
122 },
123
124 toJSON: function(object) {
125 var type = typeof object;
126 switch (type) {
127 case 'undefined':
128 case 'function':
129 case 'unknown': return;
130 case 'boolean': return object.toString();
131 }
132
133 if (object === null) return 'null';
134 if (object.toJSON) return object.toJSON();
135 if (Object.isElement(object)) return;
136
137 var results = [];
138 for (var property in object) {
139 var value = Object.toJSON(object[property]);
140 if (!Object.isUndefined(value))
141 results.push(property.toJSON() + ': ' + value);
142 }
143
144 return '{' + results.join(', ') + '}';
145 },
146
147 toQueryString: function(object) {
148 return $H(object).toQueryString();
149 },
150
151 toHTML: function(object) {
152 return object && object.toHTML ? object.toHTML() : String.interpret(object);
153 },
154
155 keys: function(object) {
156 var keys = [];
157 for (var property in object)
158 keys.push(property);
159 return keys;
160 },
161
162 values: function(object) {
163 var values = [];
164 for (var property in object)
165 values.push(object[property]);
166 return values;
167 },
168
169 clone: function(object) {
170 return Object.extend({ }, object);
171 },
172
173 isElement: function(object) {
174 return !!(object && object.nodeType == 1);
175 },
176
177 isArray: function(object) {
178 return object != null && typeof object == "object" &&
179 'splice' in object && 'join' in object;
180 },
181
182 isHash: function(object) {
183 return object instanceof Hash;
184 },
185
186 isFunction: function(object) {
187 return typeof object == "function";
188 },
189
190 isString: function(object) {
191 return typeof object == "string";
192 },
193
194 isNumber: function(object) {
195 return typeof object == "number";
196 },
197
198 isUndefined: function(object) {
199 return typeof object == "undefined";
200 }
201});
202
203Object.extend(Function.prototype, {
204 argumentNames: function() {
205 var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
206 .replace(/\s+/g, '').split(',');
207 return names.length == 1 && !names[0] ? [] : names;
208 },
209
210 bind: function() {
211 if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
212 var __method = this, args = $A(arguments), object = args.shift();
213 return function() {
214 return __method.apply(object, args.concat($A(arguments)));
215 }
216 },
217
218 bindAsEventListener: function() {
219 var __method = this, args = $A(arguments), object = args.shift();
220 return function(event) {
221 return __method.apply(object, [event || window.event].concat(args));
222 }
223 },
224
225 curry: function() {
226 if (!arguments.length) return this;
227 var __method = this, args = $A(arguments);
228 return function() {
229 return __method.apply(this, args.concat($A(arguments)));
230 }
231 },
232
233 delay: function() {
234 var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
235 return window.setTimeout(function() {
236 return __method.apply(__method, args);
237 }, timeout);
238 },
239
240 defer: function() {
241 var args = [0.01].concat($A(arguments));
242 return this.delay.apply(this, args);
243 },
244
245 wrap: function(wrapper) {
246 var __method = this;
247 return function() {
248 return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
249 }
250 },
251
252 methodize: function() {
253 if (this._methodized) return this._methodized;
254 var __method = this;
255 return this._methodized = function() {
256 return __method.apply(null, [this].concat($A(arguments)));
257 };
258 }
259});
260
261Date.prototype.toJSON = function() {
262 return '"' + this.getUTCFullYear() + '-' +
263 (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
264 this.getUTCDate().toPaddedString(2) + 'T' +
265 this.getUTCHours().toPaddedString(2) + ':' +
266 this.getUTCMinutes().toPaddedString(2) + ':' +
267 this.getUTCSeconds().toPaddedString(2) + 'Z"';
268};
269
270var Try = {
271 these: function() {
272 var returnValue;
273
274 for (var i = 0, length = arguments.length; i < length; i++) {
275 var lambda = arguments[i];
276 try {
277 returnValue = lambda();
278 break;
279 } catch (e) { }
280 }
281
282 return returnValue;
283 }
284};
285
286RegExp.prototype.match = RegExp.prototype.test;
287
288RegExp.escape = function(str) {
289 return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
290};
291
292/*--------------------------------------------------------------------------*/
293
294var PeriodicalExecuter = Class.create({
295 initialize: function(callback, frequency) {
296 this.callback = callback;
297 this.frequency = frequency;
298 this.currentlyExecuting = false;
299
300 this.registerCallback();
301 },
302
303 registerCallback: function() {
304 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
305 },
306
307 execute: function() {
308 this.callback(this);
309 },
310
311 stop: function() {
312 if (!this.timer) return;
313 clearInterval(this.timer);
314 this.timer = null;
315 },
316
317 onTimerEvent: function() {
318 if (!this.currentlyExecuting) {
319 try {
320 this.currentlyExecuting = true;
321 this.execute();
322 } finally {
323 this.currentlyExecuting = false;
324 }
325 }
326 }
327});
328Object.extend(String, {
329 interpret: function(value) {
330 return value == null ? '' : String(value);
331 },
332 specialChar: {
333 '\b': '\\b',
334 '\t': '\\t',
335 '\n': '\\n',
336 '\f': '\\f',
337 '\r': '\\r',
338 '\\': '\\\\'
339 }
340});
341
342Object.extend(String.prototype, {
343 gsub: function(pattern, replacement) {
344 var result = '', source = this, match;
345 replacement = arguments.callee.prepareReplacement(replacement);
346
347 while (source.length > 0) {
348 if (match = source.match(pattern)) {
349 result += source.slice(0, match.index);
350 result += String.interpret(replacement(match));
351 source = source.slice(match.index + match[0].length);
352 } else {
353 result += source, source = '';
354 }
355 }
356 return result;
357 },
358
359 sub: function(pattern, replacement, count) {
360 replacement = this.gsub.prepareReplacement(replacement);
361 count = Object.isUndefined(count) ? 1 : count;
362
363 return this.gsub(pattern, function(match) {
364 if (--count < 0) return match[0];
365 return replacement(match);
366 });
367 },
368
369 scan: function(pattern, iterator) {
370 this.gsub(pattern, iterator);
371 return String(this);
372 },
373
374 truncate: function(length, truncation) {
375 length = length || 30;
376 truncation = Object.isUndefined(truncation) ? '...' : truncation;
377 return this.length > length ?
378 this.slice(0, length - truncation.length) + truncation : String(this);
379 },
380
381 strip: function() {
382 return this.replace(/^\s+/, '').replace(/\s+$/, '');
383 },
384
385 stripTags: function() {
386 return this.replace(/<\/?[^>]+>/gi, '');
387 },
388
389 stripScripts: function() {
390 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
391 },
392
393 extractScripts: function() {
394 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
395 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
396 return (this.match(matchAll) || []).map(function(scriptTag) {
397 return (scriptTag.match(matchOne) || ['', ''])[1];
398 });
399 },
400
401 evalScripts: function() {
402 return this.extractScripts().map(function(script) { return eval(script) });
403 },
404
405 escapeHTML: function() {
406 var self = arguments.callee;
407 self.text.data = this;
408 return self.div.innerHTML;
409 },
410
411 unescapeHTML: function() {
412 var div = new Element('div');
413 div.innerHTML = this.stripTags();
414 return div.childNodes[0] ? (div.childNodes.length > 1 ?
415 $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
416 div.childNodes[0].nodeValue) : '';
417 },
418
419 toQueryParams: function(separator) {
420 var match = this.strip().match(/([^?#]*)(#.*)?$/);
421 if (!match) return { };
422
423 return match[1].split(separator || '&').inject({ }, function(hash, pair) {
424 if ((pair = pair.split('='))[0]) {
425 var key = decodeURIComponent(pair.shift());
426 var value = pair.length > 1 ? pair.join('=') : pair[0];
427 if (value != undefined) value = decodeURIComponent(value);
428
429 if (key in hash) {
430 if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
431 hash[key].push(value);
432 }
433 else hash[key] = value;
434 }
435 return hash;
436 });
437 },
438
439 toArray: function() {
440 return this.split('');
441 },
442
443 succ: function() {
444 return this.slice(0, this.length - 1) +
445 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
446 },
447
448 times: function(count) {
449 return count < 1 ? '' : new Array(count + 1).join(this);
450 },
451
452 camelize: function() {
453 var parts = this.split('-'), len = parts.length;
454 if (len == 1) return parts[0];
455
456 var camelized = this.charAt(0) == '-'
457 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
458 : parts[0];
459
460 for (var i = 1; i < len; i++)
461 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
462
463 return camelized;
464 },
465
466 capitalize: function() {
467 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
468 },
469
470 underscore: function() {
471 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
472 },
473
474 dasherize: function() {
475 return this.gsub(/_/,'-');
476 },
477
478 inspect: function(useDoubleQuotes) {
479 var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
480 var character = String.specialChar[match[0]];
481 return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
482 });
483 if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
484 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
485 },
486
487 toJSON: function() {
488 return this.inspect(true);
489 },
490
491 unfilterJSON: function(filter) {
492 return this.sub(filter || Prototype.JSONFilter, '#{1}');
493 },
494
495 isJSON: function() {
496 var str = this;
497 if (str.blank()) return false;
498 str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
499 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
500 },
501
502 evalJSON: function(sanitize) {
503 var json = this.unfilterJSON();
504 try {
505 if (!sanitize || json.isJSON()) return eval('(' + json + ')');
506 } catch (e) { }
507 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
508 },
509
510 include: function(pattern) {
511 return this.indexOf(pattern) > -1;
512 },
513
514 startsWith: function(pattern) {
515 return this.indexOf(pattern) === 0;
516 },
517
518 endsWith: function(pattern) {
519 var d = this.length - pattern.length;
520 return d >= 0 && this.lastIndexOf(pattern) === d;
521 },
522
523 empty: function() {
524 return this == '';
525 },
526
527 blank: function() {
528 return /^\s*$/.test(this);
529 },
530
531 interpolate: function(object, pattern) {
532 return new Template(this, pattern).evaluate(object);
533 }
534});
535
536if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
537 escapeHTML: function() {
538 return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
539 },
540 unescapeHTML: function() {
541 return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
542 }
543});
544
545String.prototype.gsub.prepareReplacement = function(replacement) {
546 if (Object.isFunction(replacement)) return replacement;
547 var template = new Template(replacement);
548 return function(match) { return template.evaluate(match) };
549};
550
551String.prototype.parseQuery = String.prototype.toQueryParams;
552
553Object.extend(String.prototype.escapeHTML, {
554 div: document.createElement('div'),
555 text: document.createTextNode('')
556});
557
558String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
559
560var Template = Class.create({
561 initialize: function(template, pattern) {
562 this.template = template.toString();
563 this.pattern = pattern || Template.Pattern;
564 },
565
566 evaluate: function(object) {
567 if (Object.isFunction(object.toTemplateReplacements))
568 object = object.toTemplateReplacements();
569
570 return this.template.gsub(this.pattern, function(match) {
571 if (object == null) return '';
572
573 var before = match[1] || '';
574 if (before == '\\') return match[2];
575
576 var ctx = object, expr = match[3];
577 var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
578 match = pattern.exec(expr);
579 if (match == null) return before;
580
581 while (match != null) {
582 var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
583 ctx = ctx[comp];
584 if (null == ctx || '' == match[3]) break;
585 expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
586 match = pattern.exec(expr);
587 }
588
589 return before + String.interpret(ctx);
590 });
591 }
592});
593Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
594
595var $break = { };
596
597var Enumerable = {
598 each: function(iterator, context) {
599 var index = 0;
600 try {
601 this._each(function(value) {
602 iterator.call(context, value, index++);
603 });
604 } catch (e) {
605 if (e != $break) throw e;
606 }
607 return this;
608 },
609
610 eachSlice: function(number, iterator, context) {
611 var index = -number, slices = [], array = this.toArray();
612 if (number < 1) return array;
613 while ((index += number) < array.length)
614 slices.push(array.slice(index, index+number));
615 return slices.collect(iterator, context);
616 },
617
618 all: function(iterator, context) {
619 iterator = iterator || Prototype.K;
620 var result = true;
621 this.each(function(value, index) {
622 result = result && !!iterator.call(context, value, index);
623 if (!result) throw $break;
624 });
625 return result;
626 },
627
628 any: function(iterator, context) {
629 iterator = iterator || Prototype.K;
630 var result = false;
631 this.each(function(value, index) {
632 if (result = !!iterator.call(context, value, index))
633 throw $break;
634 });
635 return result;
636 },
637
638 collect: function(iterator, context) {
639 iterator = iterator || Prototype.K;
640 var results = [];
641 this.each(function(value, index) {
642 results.push(iterator.call(context, value, index));
643 });
644 return results;
645 },
646
647 detect: function(iterator, context) {
648 var result;
649 this.each(function(value, index) {
650 if (iterator.call(context, value, index)) {
651 result = value;
652 throw $break;
653 }
654 });
655 return result;
656 },
657
658 findAll: function(iterator, context) {
659 var results = [];
660 this.each(function(value, index) {
661 if (iterator.call(context, value, index))
662 results.push(value);
663 });
664 return results;
665 },
666
667 grep: function(filter, iterator, context) {
668 iterator = iterator || Prototype.K;
669 var results = [];
670
671 if (Object.isString(filter))
672 filter = new RegExp(filter);
673
674 this.each(function(value, index) {
675 if (filter.match(value))
676 results.push(iterator.call(context, value, index));
677 });
678 return results;
679 },
680
681 include: function(object) {
682 if (Object.isFunction(this.indexOf))
683 if (this.indexOf(object) != -1) return true;
684
685 var found = false;
686 this.each(function(value) {
687 if (value == object) {
688 found = true;
689 throw $break;
690 }
691 });
692 return found;
693 },
694
695 inGroupsOf: function(number, fillWith) {
696 fillWith = Object.isUndefined(fillWith) ? null : fillWith;
697 return this.eachSlice(number, function(slice) {
698 while(slice.length < number) slice.push(fillWith);
699 return slice;
700 });
701 },
702
703 inject: function(memo, iterator, context) {
704 this.each(function(value, index) {
705 memo = iterator.call(context, memo, value, index);
706 });
707 return memo;
708 },
709
710 invoke: function(method) {
711 var args = $A(arguments).slice(1);
712 return this.map(function(value) {
713 return value[method].apply(value, args);
714 });
715 },
716
717 max: function(iterator, context) {
718 iterator = iterator || Prototype.K;
719 var result;
720 this.each(function(value, index) {
721 value = iterator.call(context, value, index);
722 if (result == null || value >= result)
723 result = value;
724 });
725 return result;
726 },
727
728 min: function(iterator, context) {
729 iterator = iterator || Prototype.K;
730 var result;
731 this.each(function(value, index) {
732 value = iterator.call(context, value, index);
733 if (result == null || value < result)
734 result = value;
735 });
736 return result;
737 },
738
739 partition: function(iterator, context) {
740 iterator = iterator || Prototype.K;
741 var trues = [], falses = [];
742 this.each(function(value, index) {
743 (iterator.call(context, value, index) ?
744 trues : falses).push(value);
745 });
746 return [trues, falses];
747 },
748
749 pluck: function(property) {
750 var results = [];
751 this.each(function(value) {
752 results.push(value[property]);
753 });
754 return results;
755 },
756
757 reject: function(iterator, context) {
758 var results = [];
759 this.each(function(value, index) {
760 if (!iterator.call(context, value, index))
761 results.push(value);
762 });
763 return results;
764 },
765
766 sortBy: function(iterator, context) {
767 return this.map(function(value, index) {
768 return {
769 value: value,
770 criteria: iterator.call(context, value, index)
771 };
772 }).sort(function(left, right) {
773 var a = left.criteria, b = right.criteria;
774 return a < b ? -1 : a > b ? 1 : 0;
775 }).pluck('value');
776 },
777
778 toArray: function() {
779 return this.map();
780 },
781
782 zip: function() {
783 var iterator = Prototype.K, args = $A(arguments);
784 if (Object.isFunction(args.last()))
785 iterator = args.pop();
786
787 var collections = [this].concat(args).map($A);
788 return this.map(function(value, index) {
789 return iterator(collections.pluck(index));
790 });
791 },
792
793 size: function() {
794 return this.toArray().length;
795 },
796
797 inspect: function() {
798 return '#<Enumerable:' + this.toArray().inspect() + '>';
799 }
800};
801
802Object.extend(Enumerable, {
803 map: Enumerable.collect,
804 find: Enumerable.detect,
805 select: Enumerable.findAll,
806 filter: Enumerable.findAll,
807 member: Enumerable.include,
808 entries: Enumerable.toArray,
809 every: Enumerable.all,
810 some: Enumerable.any
811});
812function $A(iterable) {
813 if (!iterable) return [];
814 if (iterable.toArray) return iterable.toArray();
815 var length = iterable.length || 0, results = new Array(length);
816 while (length--) results[length] = iterable[length];
817 return results;
818}
819
820if (Prototype.Browser.WebKit) {
821 $A = function(iterable) {
822 if (!iterable) return [];
823 // In Safari, only use the `toArray` method if it's not a NodeList.
824 // A NodeList is a function, has an function `item` property, and a numeric
825 // `length` property. Adapted from Google Doctype.
826 if (!(typeof iterable === 'function' && typeof iterable.length ===
827 'number' && typeof iterable.item === 'function') && iterable.toArray)
828 return iterable.toArray();
829 var length = iterable.length || 0, results = new Array(length);
830 while (length--) results[length] = iterable[length];
831 return results;
832 };
833}
834
835Array.from = $A;
836
837Object.extend(Array.prototype, Enumerable);
838
839if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
840
841Object.extend(Array.prototype, {
842 _each: function(iterator) {
843 for (var i = 0, length = this.length; i < length; i++)
844 iterator(this[i]);
845 },
846
847 clear: function() {
848 this.length = 0;
849 return this;
850 },
851
852 first: function() {
853 return this[0];
854 },
855
856 last: function() {
857 return this[this.length - 1];
858 },
859
860 compact: function() {
861 return this.select(function(value) {
862 return value != null;
863 });
864 },
865
866 flatten: function() {
867 return this.inject([], function(array, value) {
868 return array.concat(Object.isArray(value) ?
869 value.flatten() : [value]);
870 });
871 },
872
873 without: function() {
874 var values = $A(arguments);
875 return this.select(function(value) {
876 return !values.include(value);
877 });
878 },
879
880 reverse: function(inline) {
881 return (inline !== false ? this : this.toArray())._reverse();
882 },
883
884 reduce: function() {
885 return this.length > 1 ? this : this[0];
886 },
887
888 uniq: function(sorted) {
889 return this.inject([], function(array, value, index) {
890 if (0 == index || (sorted ? array.last() != value : !array.include(value)))
891 array.push(value);
892 return array;
893 });
894 },
895
896 intersect: function(array) {
897 return this.uniq().findAll(function(item) {
898 return array.detect(function(value) { return item === value });
899 });
900 },
901
902 clone: function() {
903 return [].concat(this);
904 },
905
906 size: function() {
907 return this.length;
908 },
909
910 inspect: function() {
911 return '[' + this.map(Object.inspect).join(', ') + ']';
912 },
913
914 toJSON: function() {
915 var results = [];
916 this.each(function(object) {
917 var value = Object.toJSON(object);
918 if (!Object.isUndefined(value)) results.push(value);
919 });
920 return '[' + results.join(', ') + ']';
921 }
922});
923
924// use native browser JS 1.6 implementation if available
925if (Object.isFunction(Array.prototype.forEach))
926 Array.prototype._each = Array.prototype.forEach;
927
928if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
929 i || (i = 0);
930 var length = this.length;
931 if (i < 0) i = length + i;
932 for (; i < length; i++)
933 if (this[i] === item) return i;
934 return -1;
935};
936
937if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
938 i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
939 var n = this.slice(0, i).reverse().indexOf(item);
940 return (n < 0) ? n : i - n - 1;
941};
942
943Array.prototype.toArray = Array.prototype.clone;
944
945function $w(string) {
946 if (!Object.isString(string)) return [];
947 string = string.strip();
948 return string ? string.split(/\s+/) : [];
949}
950
951if (Prototype.Browser.Opera){
952 Array.prototype.concat = function() {
953 var array = [];
954 for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
955 for (var i = 0, length = arguments.length; i < length; i++) {
956 if (Object.isArray(arguments[i])) {
957 for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
958 array.push(arguments[i][j]);
959 } else {
960 array.push(arguments[i]);
961 }
962 }
963 return array;
964 };
965}
966Object.extend(Number.prototype, {
967 toColorPart: function() {
968 return this.toPaddedString(2, 16);
969 },
970
971 succ: function() {
972 return this + 1;
973 },
974
975 times: function(iterator, context) {
976 $R(0, this, true).each(iterator, context);
977 return this;
978 },
979
980 toPaddedString: function(length, radix) {
981 var string = this.toString(radix || 10);
982 return '0'.times(length - string.length) + string;
983 },
984
985 toJSON: function() {
986 return isFinite(this) ? this.toString() : 'null';
987 }
988});
989
990$w('abs round ceil floor').each(function(method){
991 Number.prototype[method] = Math[method].methodize();
992});
993function $H(object) {
994 return new Hash(object);
995};
996
997var Hash = Class.create(Enumerable, (function() {
998
999 function toQueryPair(key, value) {
1000 if (Object.isUndefined(value)) return key;
1001 return key + '=' + encodeURIComponent(String.interpret(value));
1002 }
1003
1004 return {
1005 initialize: function(object) {
1006 this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
1007 },
1008
1009 _each: function(iterator) {
1010 for (var key in this._object) {
1011 var value = this._object[key], pair = [key, value];
1012 pair.key = key;
1013 pair.value = value;
1014 iterator(pair);
1015 }
1016 },
1017
1018 set: function(key, value) {
1019 return this._object[key] = value;
1020 },
1021
1022 get: function(key) {
1023 // simulating poorly supported hasOwnProperty
1024 if (this._object[key] !== Object.prototype[key])
1025 return this._object[key];
1026 },
1027
1028 unset: function(key) {
1029 var value = this._object[key];
1030 delete this._object[key];
1031 return value;
1032 },
1033
1034 toObject: function() {
1035 return Object.clone(this._object);
1036 },
1037
1038 keys: function() {
1039 return this.pluck('key');
1040 },
1041
1042 values: function() {
1043 return this.pluck('value');
1044 },
1045
1046 index: function(value) {
1047 var match = this.detect(function(pair) {
1048 return pair.value === value;
1049 });
1050 return match && match.key;
1051 },
1052
1053 merge: function(object) {
1054 return this.clone().update(object);
1055 },
1056
1057 update: function(object) {
1058 return new Hash(object).inject(this, function(result, pair) {
1059 result.set(pair.key, pair.value);
1060 return result;
1061 });
1062 },
1063
1064 toQueryString: function() {
1065 return this.inject([], function(results, pair) {
1066 var key = encodeURIComponent(pair.key), values = pair.value;
1067
1068 if (values && typeof values == 'object') {
1069 if (Object.isArray(values))
1070 return results.concat(values.map(toQueryPair.curry(key)));
1071 } else results.push(toQueryPair(key, values));
1072 return results;
1073 }).join('&');
1074 },
1075
1076 inspect: function() {
1077 return '#<Hash:{' + this.map(function(pair) {
1078 return pair.map(Object.inspect).join(': ');
1079 }).join(', ') + '}>';
1080 },
1081
1082 toJSON: function() {
1083 return Object.toJSON(this.toObject());
1084 },
1085
1086 clone: function() {
1087 return new Hash(this);
1088 }
1089 }
1090})());
1091
1092Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
1093Hash.from = $H;
1094var ObjectRange = Class.create(Enumerable, {
1095 initialize: function(start, end, exclusive) {
1096 this.start = start;
1097 this.end = end;
1098 this.exclusive = exclusive;
1099 },
1100
1101 _each: function(iterator) {
1102 var value = this.start;
1103 while (this.include(value)) {
1104 iterator(value);
1105 value = value.succ();
1106 }
1107 },
1108
1109 include: function(value) {
1110 if (value < this.start)
1111 return false;
1112 if (this.exclusive)
1113 return value < this.end;
1114 return value <= this.end;
1115 }
1116});
1117
1118var $R = function(start, end, exclusive) {
1119 return new ObjectRange(start, end, exclusive);
1120};
1121
1122var Ajax = {
1123 getTransport: function() {
1124 return Try.these(
1125 function() {return new XMLHttpRequest()},
1126 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
1127 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
1128 ) || false;
1129 },
1130
1131 activeRequestCount: 0
1132};
1133
1134Ajax.Responders = {
1135 responders: [],
1136
1137 _each: function(iterator) {
1138 this.responders._each(iterator);
1139 },
1140
1141 register: function(responder) {
1142 if (!this.include(responder))
1143 this.responders.push(responder);
1144 },
1145
1146 unregister: function(responder) {
1147 this.responders = this.responders.without(responder);
1148 },
1149
1150 dispatch: function(callback, request, transport, json) {
1151 this.each(function(responder) {
1152 if (Object.isFunction(responder[callback])) {
1153 try {
1154 responder[callback].apply(responder, [request, transport, json]);
1155 } catch (e) { }
1156 }
1157 });
1158 }
1159};
1160
1161Object.extend(Ajax.Responders, Enumerable);
1162
1163Ajax.Responders.register({
1164 onCreate: function() { Ajax.activeRequestCount++ },
1165 onComplete: function() { Ajax.activeRequestCount-- }
1166});
1167
1168Ajax.Base = Class.create({
1169 initialize: function(options) {
1170 this.options = {
1171 method: 'post',
1172 asynchronous: true,
1173 contentType: 'application/x-www-form-urlencoded',
1174 encoding: 'UTF-8',
1175 parameters: '',
1176 evalJSON: true,
1177 evalJS: true
1178 };
1179 Object.extend(this.options, options || { });
1180
1181 this.options.method = this.options.method.toLowerCase();
1182
1183 if (Object.isString(this.options.parameters))
1184 this.options.parameters = this.options.parameters.toQueryParams();
1185 else if (Object.isHash(this.options.parameters))
1186 this.options.parameters = this.options.parameters.toObject();
1187 }
1188});
1189
1190Ajax.Request = Class.create(Ajax.Base, {
1191 _complete: false,
1192
1193 initialize: function($super, url, options) {
1194 $super(options);
1195 this.transport = Ajax.getTransport();
1196 this.request(url);
1197 },
1198
1199 request: function(url) {
1200 this.url = url;
1201 this.method = this.options.method;
1202 var params = Object.clone(this.options.parameters);
1203
1204 if (!['get', 'post'].include(this.method)) {
1205 // simulate other verbs over post
1206 params['_method'] = this.method;
1207 this.method = 'post';
1208 }
1209
1210 this.parameters = params;
1211
1212 if (params = Object.toQueryString(params)) {
1213 // when GET, append parameters to URL
1214 if (this.method == 'get')
1215 this.url += (this.url.include('?') ? '&' : '?') + params;
1216 else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1217 params += '&_=';
1218 }
1219
1220 try {
1221 var response = new Ajax.Response(this);
1222 if (this.options.onCreate) this.options.onCreate(response);
1223 Ajax.Responders.dispatch('onCreate', this, response);
1224
1225 this.transport.open(this.method.toUpperCase(), this.url,
1226 this.options.asynchronous);
1227
1228 if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
1229
1230 this.transport.onreadystatechange = this.onStateChange.bind(this);
1231 this.setRequestHeaders();
1232
1233 this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1234 this.transport.send(this.body);
1235
1236 /* Force Firefox to handle ready state 4 for synchronous requests */
1237 if (!this.options.asynchronous && this.transport.overrideMimeType)
1238 this.onStateChange();
1239
1240 }
1241 catch (e) {
1242 this.dispatchException(e);
1243 }
1244 },
1245
1246 onStateChange: function() {
1247 var readyState = this.transport.readyState;
1248 if (readyState > 1 && !((readyState == 4) && this._complete))
1249 this.respondToReadyState(this.transport.readyState);
1250 },
1251
1252 setRequestHeaders: function() {
1253 var headers = {
1254 'X-Requested-With': 'XMLHttpRequest',
1255 'X-Prototype-Version': Prototype.Version,
1256 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1257 };
1258
1259 if (this.method == 'post') {
1260 headers['Content-type'] = this.options.contentType +
1261 (this.options.encoding ? '; charset=' + this.options.encoding : '');
1262
1263 /* Force "Connection: close" for older Mozilla browsers to work
1264 * around a bug where XMLHttpRequest sends an incorrect
1265 * Content-length header. See Mozilla Bugzilla #246651.
1266 */
1267 if (this.transport.overrideMimeType &&
1268 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1269 headers['Connection'] = 'close';
1270 }
1271
1272 // user-defined headers
1273 if (typeof this.options.requestHeaders == 'object') {
1274 var extras = this.options.requestHeaders;
1275
1276 if (Object.isFunction(extras.push))
1277 for (var i = 0, length = extras.length; i < length; i += 2)
1278 headers[extras[i]] = extras[i+1];
1279 else
1280 $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1281 }
1282
1283 for (var name in headers)
1284 this.transport.setRequestHeader(name, headers[name]);
1285 },
1286
1287 success: function() {
1288 var status = this.getStatus();
1289 return !status || (status >= 200 && status < 300);
1290 },
1291
1292 getStatus: function() {
1293 try {
1294 return this.transport.status || 0;
1295 } catch (e) { return 0 }
1296 },
1297
1298 respondToReadyState: function(readyState) {
1299 var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
1300
1301 if (state == 'Complete') {
1302 try {
1303 this._complete = true;
1304 (this.options['on' + response.status]
1305 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1306 || Prototype.emptyFunction)(response, response.headerJSON);
1307 } catch (e) {
1308 this.dispatchException(e);
1309 }
1310
1311 var contentType = response.getHeader('Content-type');
1312 if (this.options.evalJS == 'force'
1313 || (this.options.evalJS && this.isSameOrigin() && contentType
1314 && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
1315 this.evalResponse();
1316 }
1317
1318 try {
1319 (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
1320 Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
1321 } catch (e) {
1322 this.dispatchException(e);
1323 }
1324
1325 if (state == 'Complete') {
1326 // avoid memory leak in MSIE: clean up
1327 this.transport.onreadystatechange = Prototype.emptyFunction;
1328 }
1329 },
1330
1331 isSameOrigin: function() {
1332 var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
1333 return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
1334 protocol: location.protocol,
1335 domain: document.domain,
1336 port: location.port ? ':' + location.port : ''
1337 }));
1338 },
1339
1340 getHeader: function(name) {
1341 try {
1342 return this.transport.getResponseHeader(name) || null;
1343 } catch (e) { return null }
1344 },
1345
1346 evalResponse: function() {
1347 try {
1348 return eval((this.transport.responseText || '').unfilterJSON());
1349 } catch (e) {
1350 this.dispatchException(e);
1351 }
1352 },
1353
1354 dispatchException: function(exception) {
1355 (this.options.onException || Prototype.emptyFunction)(this, exception);
1356 Ajax.Responders.dispatch('onException', this, exception);
1357 }
1358});
1359
1360Ajax.Request.Events =
1361 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1362
1363Ajax.Response = Class.create({
1364 initialize: function(request){
1365 this.request = request;
1366 var transport = this.transport = request.transport,
1367 readyState = this.readyState = transport.readyState;
1368
1369 if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
1370 this.status = this.getStatus();
1371 this.statusText = this.getStatusText();
1372 this.responseText = String.interpret(transport.responseText);
1373 this.headerJSON = this._getHeaderJSON();
1374 }
1375
1376 if(readyState == 4) {
1377 var xml = transport.responseXML;
1378 this.responseXML = Object.isUndefined(xml) ? null : xml;
1379 this.responseJSON = this._getResponseJSON();
1380 }
1381 },
1382
1383 status: 0,
1384 statusText: '',
1385
1386 getStatus: Ajax.Request.prototype.getStatus,
1387
1388 getStatusText: function() {
1389 try {
1390 return this.transport.statusText || '';
1391 } catch (e) { return '' }
1392 },
1393
1394 getHeader: Ajax.Request.prototype.getHeader,
1395
1396 getAllHeaders: function() {
1397 try {
1398 return this.getAllResponseHeaders();
1399 } catch (e) { return null }
1400 },
1401
1402 getResponseHeader: function(name) {
1403 return this.transport.getResponseHeader(name);
1404 },
1405
1406 getAllResponseHeaders: function() {
1407 return this.transport.getAllResponseHeaders();
1408 },
1409
1410 _getHeaderJSON: function() {
1411 var json = this.getHeader('X-JSON');
1412 if (!json) return null;
1413 json = decodeURIComponent(escape(json));
1414 try {
1415 return json.evalJSON(this.request.options.sanitizeJSON ||
1416 !this.request.isSameOrigin());
1417 } catch (e) {
1418 this.request.dispatchException(e);
1419 }
1420 },
1421
1422 _getResponseJSON: function() {
1423 var options = this.request.options;
1424 if (!options.evalJSON || (options.evalJSON != 'force' &&
1425 !(this.getHeader('Content-type') || '').include('application/json')) ||
1426 this.responseText.blank())
1427 return null;
1428 try {
1429 return this.responseText.evalJSON(options.sanitizeJSON ||
1430 !this.request.isSameOrigin());
1431 } catch (e) {
1432 this.request.dispatchException(e);
1433 }
1434 }
1435});
1436
1437Ajax.Updater = Class.create(Ajax.Request, {
1438 initialize: function($super, container, url, options) {
1439 this.container = {
1440 success: (container.success || container),
1441 failure: (container.failure || (container.success ? null : container))
1442 };
1443
1444 options = Object.clone(options);
1445 var onComplete = options.onComplete;
1446 options.onComplete = (function(response, json) {
1447 this.updateContent(response.responseText);
1448 if (Object.isFunction(onComplete)) onComplete(response, json);
1449 }).bind(this);
1450
1451 $super(url, options);
1452 },
1453
1454 updateContent: function(responseText) {
1455 var receiver = this.container[this.success() ? 'success' : 'failure'],
1456 options = this.options;
1457
1458 if (!options.evalScripts) responseText = responseText.stripScripts();
1459
1460 if (receiver = $(receiver)) {
1461 if (options.insertion) {
1462 if (Object.isString(options.insertion)) {
1463 var insertion = { }; insertion[options.insertion] = responseText;
1464 receiver.insert(insertion);
1465 }
1466 else options.insertion(receiver, responseText);
1467 }
1468 else receiver.update(responseText);
1469 }
1470 }
1471});
1472
1473Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
1474 initialize: function($super, container, url, options) {
1475 $super(options);
1476 this.onComplete = this.options.onComplete;
1477
1478 this.frequency = (this.options.frequency || 2);
1479 this.decay = (this.options.decay || 1);
1480
1481 this.updater = { };
1482 this.container = container;
1483 this.url = url;
1484
1485 this.start();
1486 },
1487
1488 start: function() {
1489 this.options.onComplete = this.updateComplete.bind(this);
1490 this.onTimerEvent();
1491 },
1492
1493 stop: function() {
1494 this.updater.options.onComplete = undefined;
1495 clearTimeout(this.timer);
1496 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1497 },
1498
1499 updateComplete: function(response) {
1500 if (this.options.decay) {
1501 this.decay = (response.responseText == this.lastText ?
1502 this.decay * this.options.decay : 1);
1503
1504 this.lastText = response.responseText;
1505 }
1506 this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
1507 },
1508
1509 onTimerEvent: function() {
1510 this.updater = new Ajax.Updater(this.container, this.url, this.options);
1511 }
1512});
1513function $(element) {
1514 if (arguments.length > 1) {
1515 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1516 elements.push($(arguments[i]));
1517 return elements;
1518 }
1519 if (Object.isString(element))
1520 element = document.getElementById(element);
1521 return Element.extend(element);
1522}
1523
1524if (Prototype.BrowserFeatures.XPath) {
1525 document._getElementsByXPath = function(expression, parentElement) {
1526 var results = [];
1527 var query = document.evaluate(expression, $(parentElement) || document,
1528 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1529 for (var i = 0, length = query.snapshotLength; i < length; i++)
1530 results.push(Element.extend(query.snapshotItem(i)));
1531 return results;
1532 };
1533}
1534
1535/*--------------------------------------------------------------------------*/
1536
1537if (!window.Node) var Node = { };
1538
1539if (!Node.ELEMENT_NODE) {
1540 // DOM level 2 ECMAScript Language Binding
1541 Object.extend(Node, {
1542 ELEMENT_NODE: 1,
1543 ATTRIBUTE_NODE: 2,
1544 TEXT_NODE: 3,
1545 CDATA_SECTION_NODE: 4,
1546 ENTITY_REFERENCE_NODE: 5,
1547 ENTITY_NODE: 6,
1548 PROCESSING_INSTRUCTION_NODE: 7,
1549 COMMENT_NODE: 8,
1550 DOCUMENT_NODE: 9,
1551 DOCUMENT_TYPE_NODE: 10,
1552 DOCUMENT_FRAGMENT_NODE: 11,
1553 NOTATION_NODE: 12
1554 });
1555}
1556
1557(function() {
1558 var element = this.Element;
1559 this.Element = function(tagName, attributes) {
1560 attributes = attributes || { };
1561 tagName = tagName.toLowerCase();
1562 var cache = Element.cache;
1563 if (Prototype.Browser.IE && attributes.name) {
1564 tagName = '<' + tagName + ' name="' + attributes.name + '">';
1565 delete attributes.name;
1566 return Element.writeAttribute(document.createElement(tagName), attributes);
1567 }
1568 if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
1569 return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
1570 };
1571 Object.extend(this.Element, element || { });
1572 if (element) this.Element.prototype = element.prototype;
1573}).call(window);
1574
1575Element.cache = { };
1576
1577Element.Methods = {
1578 visible: function(element) {
1579 return $(element).style.display != 'none';
1580 },
1581
1582 toggle: function(element) {
1583 element = $(element);
1584 Element[Element.visible(element) ? 'hide' : 'show'](element);
1585 return element;
1586 },
1587
1588 hide: function(element) {
1589 element = $(element);
1590 element.style.display = 'none';
1591 return element;
1592 },
1593
1594 show: function(element) {
1595 element = $(element);
1596 element.style.display = '';
1597 return element;
1598 },
1599
1600 remove: function(element) {
1601 element = $(element);
1602 element.parentNode.removeChild(element);
1603 return element;
1604 },
1605
1606 update: function(element, content) {
1607 element = $(element);
1608 if (content && content.toElement) content = content.toElement();
1609 if (Object.isElement(content)) return element.update().insert(content);
1610 content = Object.toHTML(content);
1611 element.innerHTML = content.stripScripts();
1612 content.evalScripts.bind(content).defer();
1613 return element;
1614 },
1615
1616 replace: function(element, content) {
1617 element = $(element);
1618 if (content && content.toElement) content = content.toElement();
1619 else if (!Object.isElement(content)) {
1620 content = Object.toHTML(content);
1621 var range = element.ownerDocument.createRange();
1622 range.selectNode(element);
1623 content.evalScripts.bind(content).defer();
1624 content = range.createContextualFragment(content.stripScripts());
1625 }
1626 element.parentNode.replaceChild(content, element);
1627 return element;
1628 },
1629
1630 insert: function(element, insertions) {
1631 element = $(element);
1632
1633 if (Object.isString(insertions) || Object.isNumber(insertions) ||
1634 Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
1635 insertions = {bottom:insertions};
1636
1637 var content, insert, tagName, childNodes;
1638
1639 for (var position in insertions) {
1640 content = insertions[position];
1641 position = position.toLowerCase();
1642 insert = Element._insertionTranslations[position];
1643
1644 if (content && content.toElement) content = content.toElement();
1645 if (Object.isElement(content)) {
1646 insert(element, content);
1647 continue;
1648 }
1649
1650 content = Object.toHTML(content);
1651
1652 tagName = ((position == 'before' || position == 'after')
1653 ? element.parentNode : element).tagName.toUpperCase();
1654
1655 childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
1656
1657 if (position == 'top' || position == 'after') childNodes.reverse();
1658 childNodes.each(insert.curry(element));
1659
1660 content.evalScripts.bind(content).defer();
1661 }
1662
1663 return element;
1664 },
1665
1666 wrap: function(element, wrapper, attributes) {
1667 element = $(element);
1668 if (Object.isElement(wrapper))
1669 $(wrapper).writeAttribute(attributes || { });
1670 else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
1671 else wrapper = new Element('div', wrapper);
1672 if (element.parentNode)
1673 element.parentNode.replaceChild(wrapper, element);
1674 wrapper.appendChild(element);
1675 return wrapper;
1676 },
1677
1678 inspect: function(element) {
1679 element = $(element);
1680 var result = '<' + element.tagName.toLowerCase();
1681 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1682 var property = pair.first(), attribute = pair.last();
1683 var value = (element[property] || '').toString();
1684 if (value) result += ' ' + attribute + '=' + value.inspect(true);
1685 });
1686 return result + '>';
1687 },
1688
1689 recursivelyCollect: function(element, property) {
1690 element = $(element);
1691 var elements = [];
1692 while (element = element[property])
1693 if (element.nodeType == 1)
1694 elements.push(Element.extend(element));
1695 return elements;
1696 },
1697
1698 ancestors: function(element) {
1699 return $(element).recursivelyCollect('parentNode');
1700 },
1701
1702 descendants: function(element) {
1703 return $(element).select("*");
1704 },
1705
1706 firstDescendant: function(element) {
1707 element = $(element).firstChild;
1708 while (element && element.nodeType != 1) element = element.nextSibling;
1709 return $(element);
1710 },
1711
1712 immediateDescendants: function(element) {
1713 if (!(element = $(element).firstChild)) return [];
1714 while (element && element.nodeType != 1) element = element.nextSibling;
1715 if (element) return [element].concat($(element).nextSiblings());
1716 return [];
1717 },
1718
1719 previousSiblings: function(element) {
1720 return $(element).recursivelyCollect('previousSibling');
1721 },
1722
1723 nextSiblings: function(element) {
1724 return $(element).recursivelyCollect('nextSibling');
1725 },
1726
1727 siblings: function(element) {
1728 element = $(element);
1729 return element.previousSiblings().reverse().concat(element.nextSiblings());
1730 },
1731
1732 match: function(element, selector) {
1733 if (Object.isString(selector))
1734 selector = new Selector(selector);
1735 return selector.match($(element));
1736 },
1737
1738 up: function(element, expression, index) {
1739 element = $(element);
1740 if (arguments.length == 1) return $(element.parentNode);
1741 var ancestors = element.ancestors();
1742 return Object.isNumber(expression) ? ancestors[expression] :
1743 Selector.findElement(ancestors, expression, index);
1744 },
1745
1746 down: function(element, expression, index) {
1747 element = $(element);
1748 if (arguments.length == 1) return element.firstDescendant();
1749 return Object.isNumber(expression) ? element.descendants()[expression] :
1750 Element.select(element, expression)[index || 0];
1751 },
1752
1753 previous: function(element, expression, index) {
1754 element = $(element);
1755 if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1756 var previousSiblings = element.previousSiblings();
1757 return Object.isNumber(expression) ? previousSiblings[expression] :
1758 Selector.findElement(previousSiblings, expression, index);
1759 },
1760
1761 next: function(element, expression, index) {
1762 element = $(element);
1763 if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1764 var nextSiblings = element.nextSiblings();
1765 return Object.isNumber(expression) ? nextSiblings[expression] :
1766 Selector.findElement(nextSiblings, expression, index);
1767 },
1768
1769 select: function() {
1770 var args = $A(arguments), element = $(args.shift());
1771 return Selector.findChildElements(element, args);
1772 },
1773
1774 adjacent: function() {
1775 var args = $A(arguments), element = $(args.shift());
1776 return Selector.findChildElements(element.parentNode, args).without(element);
1777 },
1778
1779 identify: function(element) {
1780 element = $(element);
1781 var id = element.readAttribute('id'), self = arguments.callee;
1782 if (id) return id;
1783 do { id = 'anonymous_element_' + self.counter++ } while ($(id));
1784 element.writeAttribute('id', id);
1785 return id;
1786 },
1787
1788 readAttribute: function(element, name) {
1789 element = $(element);
1790 if (Prototype.Browser.IE) {
1791 var t = Element._attributeTranslations.read;
1792 if (t.values[name]) return t.values[name](element, name);
1793 if (t.names[name]) name = t.names[name];
1794 if (name.include(':')) {
1795 return (!element.attributes || !element.attributes[name]) ? null :
1796 element.attributes[name].value;
1797 }
1798 }
1799 return element.getAttribute(name);
1800 },
1801
1802 writeAttribute: function(element, name, value) {
1803 element = $(element);
1804 var attributes = { }, t = Element._attributeTranslations.write;
1805
1806 if (typeof name == 'object') attributes = name;
1807 else attributes[name] = Object.isUndefined(value) ? true : value;
1808
1809 for (var attr in attributes) {
1810 name = t.names[attr] || attr;
1811 value = attributes[attr];
1812 if (t.values[attr]) name = t.values[attr](element, value);
1813 if (value === false || value === null)
1814 element.removeAttribute(name);
1815 else if (value === true)
1816 element.setAttribute(name, name);
1817 else element.setAttribute(name, value);
1818 }
1819 return element;
1820 },
1821
1822 getHeight: function(element) {
1823 return $(element).getDimensions().height;
1824 },
1825
1826 getWidth: function(element) {
1827 return $(element).getDimensions().width;
1828 },
1829
1830 classNames: function(element) {
1831 return new Element.ClassNames(element);
1832 },
1833
1834 hasClassName: function(element, className) {
1835 if (!(element = $(element))) return;
1836 var elementClassName = element.className;
1837 return (elementClassName.length > 0 && (elementClassName == className ||
1838 new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
1839 },
1840
1841 addClassName: function(element, className) {
1842 if (!(element = $(element))) return;
1843 if (!element.hasClassName(className))
1844 element.className += (element.className ? ' ' : '') + className;
1845 return element;
1846 },
1847
1848 removeClassName: function(element, className) {
1849 if (!(element = $(element))) return;
1850 element.className = element.className.replace(
1851 new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
1852 return element;
1853 },
1854
1855 toggleClassName: function(element, className) {
1856 if (!(element = $(element))) return;
1857 return element[element.hasClassName(className) ?
1858 'removeClassName' : 'addClassName'](className);
1859 },
1860
1861 // removes whitespace-only text node children
1862 cleanWhitespace: function(element) {
1863 element = $(element);
1864 var node = element.firstChild;
1865 while (node) {
1866 var nextNode = node.nextSibling;
1867 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1868 element.removeChild(node);
1869 node = nextNode;
1870 }
1871 return element;
1872 },
1873
1874 empty: function(element) {
1875 return $(element).innerHTML.blank();
1876 },
1877
1878 descendantOf: function(element, ancestor) {
1879 element = $(element), ancestor = $(ancestor);
1880
1881 if (element.compareDocumentPosition)
1882 return (element.compareDocumentPosition(ancestor) & 8) === 8;
1883
1884 if (ancestor.contains)
1885 return ancestor.contains(element) && ancestor !== element;
1886
1887 while (element = element.parentNode)
1888 if (element == ancestor) return true;
1889
1890 return false;
1891 },
1892
1893 scrollTo: function(element) {
1894 element = $(element);
1895 var pos = element.cumulativeOffset();
1896 window.scrollTo(pos[0], pos[1]);
1897 return element;
1898 },
1899
1900 getStyle: function(element, style) {
1901 element = $(element);
1902 style = style == 'float' ? 'cssFloat' : style.camelize();
1903 var value = element.style[style];
1904 if (!value || value == 'auto') {
1905 var css = document.defaultView.getComputedStyle(element, null);
1906 value = css ? css[style] : null;
1907 }
1908 if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1909 return value == 'auto' ? null : value;
1910 },
1911
1912 getOpacity: function(element) {
1913 return $(element).getStyle('opacity');
1914 },
1915
1916 setStyle: function(element, styles) {
1917 element = $(element);
1918 var elementStyle = element.style, match;
1919 if (Object.isString(styles)) {
1920 element.style.cssText += ';' + styles;
1921 return styles.include('opacity') ?
1922 element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
1923 }
1924 for (var property in styles)
1925 if (property == 'opacity') element.setOpacity(styles[property]);
1926 else
1927 elementStyle[(property == 'float' || property == 'cssFloat') ?
1928 (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
1929 property] = styles[property];
1930
1931 return element;
1932 },
1933
1934 setOpacity: function(element, value) {
1935 element = $(element);
1936 element.style.opacity = (value == 1 || value === '') ? '' :
1937 (value < 0.00001) ? 0 : value;
1938 return element;
1939 },
1940
1941 getDimensions: function(element) {
1942 element = $(element);
1943 var display = element.getStyle('display');
1944 if (display != 'none' && display != null) // Safari bug
1945 return {width: element.offsetWidth, height: element.offsetHeight};
1946
1947 // All *Width and *Height properties give 0 on elements with display none,
1948 // so enable the element temporarily
1949 var els = element.style;
1950 var originalVisibility = els.visibility;
1951 var originalPosition = els.position;
1952 var originalDisplay = els.display;
1953 els.visibility = 'hidden';
1954 els.position = 'absolute';
1955 els.display = 'block';
1956 var originalWidth = element.clientWidth;
1957 var originalHeight = element.clientHeight;
1958 els.display = originalDisplay;
1959 els.position = originalPosition;
1960 els.visibility = originalVisibility;
1961 return {width: originalWidth, height: originalHeight};
1962 },
1963
1964 makePositioned: function(element) {
1965 element = $(element);
1966 var pos = Element.getStyle(element, 'position');
1967 if (pos == 'static' || !pos) {
1968 element._madePositioned = true;
1969 element.style.position = 'relative';
1970 // Opera returns the offset relative to the positioning context, when an
1971 // element is position relative but top and left have not been defined
1972 if (Prototype.Browser.Opera) {
1973 element.style.top = 0;
1974 element.style.left = 0;
1975 }
1976 }
1977 return element;
1978 },
1979
1980 undoPositioned: function(element) {
1981 element = $(element);
1982 if (element._madePositioned) {
1983 element._madePositioned = undefined;
1984 element.style.position =
1985 element.style.top =
1986 element.style.left =
1987 element.style.bottom =
1988 element.style.right = '';
1989 }
1990 return element;
1991 },
1992
1993 makeClipping: function(element) {
1994 element = $(element);
1995 if (element._overflow) return element;
1996 element._overflow = Element.getStyle(element, 'overflow') || 'auto';
1997 if (element._overflow !== 'hidden')
1998 element.style.overflow = 'hidden';
1999 return element;
2000 },
2001
2002 undoClipping: function(element) {
2003 element = $(element);
2004 if (!element._overflow) return element;
2005 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
2006 element._overflow = null;
2007 return element;
2008 },
2009
2010 cumulativeOffset: function(element) {
2011 var valueT = 0, valueL = 0;
2012 do {
2013 valueT += element.offsetTop || 0;
2014 valueL += element.offsetLeft || 0;
2015 element = element.offsetParent;
2016 } while (element);
2017 return Element._returnOffset(valueL, valueT);
2018 },
2019
2020 positionedOffset: function(element) {
2021 var valueT = 0, valueL = 0;
2022 do {
2023 valueT += element.offsetTop || 0;
2024 valueL += element.offsetLeft || 0;
2025 element = element.offsetParent;
2026 if (element) {
2027 if (element.tagName.toUpperCase() == 'BODY') break;
2028 var p = Element.getStyle(element, 'position');
2029 if (p !== 'static') break;
2030 }
2031 } while (element);
2032 return Element._returnOffset(valueL, valueT);
2033 },
2034
2035 absolutize: function(element) {
2036 element = $(element);
2037 if (element.getStyle('position') == 'absolute') return element;
2038 // Position.prepare(); // To be done manually by Scripty when it needs it.
2039
2040 var offsets = element.positionedOffset();
2041 var top = offsets[1];
2042 var left = offsets[0];
2043 var width = element.clientWidth;
2044 var height = element.clientHeight;
2045
2046 element._originalLeft = left - parseFloat(element.style.left || 0);
2047 element._originalTop = top - parseFloat(element.style.top || 0);
2048 element._originalWidth = element.style.width;
2049 element._originalHeight = element.style.height;
2050
2051 element.style.position = 'absolute';
2052 element.style.top = top + 'px';
2053 element.style.left = left + 'px';
2054 element.style.width = width + 'px';
2055 element.style.height = height + 'px';
2056 return element;
2057 },
2058
2059 relativize: function(element) {
2060 element = $(element);
2061 if (element.getStyle('position') == 'relative') return element;
2062 // Position.prepare(); // To be done manually by Scripty when it needs it.
2063
2064 element.style.position = 'relative';
2065 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
2066 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
2067
2068 element.style.top = top + 'px';
2069 element.style.left = left + 'px';
2070 element.style.height = element._originalHeight;
2071 element.style.width = element._originalWidth;
2072 return element;
2073 },
2074
2075 cumulativeScrollOffset: function(element) {
2076 var valueT = 0, valueL = 0;
2077 do {
2078 valueT += element.scrollTop || 0;
2079 valueL += element.scrollLeft || 0;
2080 element = element.parentNode;
2081 } while (element);
2082 return Element._returnOffset(valueL, valueT);
2083 },
2084
2085 getOffsetParent: function(element) {
2086 if (element.offsetParent) return $(element.offsetParent);
2087 if (element == document.body) return $(element);
2088
2089 while ((element = element.parentNode) && element != document.body)
2090 if (Element.getStyle(element, 'position') != 'static')
2091 return $(element);
2092
2093 return $(document.body);
2094 },
2095
2096 viewportOffset: function(forElement) {
2097 var valueT = 0, valueL = 0;
2098
2099 var element = forElement;
2100 do {
2101 valueT += element.offsetTop || 0;
2102 valueL += element.offsetLeft || 0;
2103
2104 // Safari fix
2105 if (element.offsetParent == document.body &&
2106 Element.getStyle(element, 'position') == 'absolute') break;
2107
2108 } while (element = element.offsetParent);
2109
2110 element = forElement;
2111 do {
2112 if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
2113 valueT -= element.scrollTop || 0;
2114 valueL -= element.scrollLeft || 0;
2115 }
2116 } while (element = element.parentNode);
2117
2118 return Element._returnOffset(valueL, valueT);
2119 },
2120
2121 clonePosition: function(element, source) {
2122 var options = Object.extend({
2123 setLeft: true,
2124 setTop: true,
2125 setWidth: true,
2126 setHeight: true,
2127 offsetTop: 0,
2128 offsetLeft: 0
2129 }, arguments[2] || { });
2130
2131 // find page position of source
2132 source = $(source);
2133 var p = source.viewportOffset();
2134
2135 // find coordinate system to use
2136 element = $(element);
2137 var delta = [0, 0];
2138 var parent = null;
2139 // delta [0,0] will do fine with position: fixed elements,
2140 // position:absolute needs offsetParent deltas
2141 if (Element.getStyle(element, 'position') == 'absolute') {
2142 parent = element.getOffsetParent();
2143 delta = parent.viewportOffset();
2144 }
2145
2146 // correct by body offsets (fixes Safari)
2147 if (parent == document.body) {
2148 delta[0] -= document.body.offsetLeft;
2149 delta[1] -= document.body.offsetTop;
2150 }
2151
2152 // set position
2153 if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
2154 if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
2155 if (options.setWidth) element.style.width = source.offsetWidth + 'px';
2156 if (options.setHeight) element.style.height = source.offsetHeight + 'px';
2157 return element;
2158 }
2159};
2160
2161Element.Methods.identify.counter = 1;
2162
2163Object.extend(Element.Methods, {
2164 getElementsBySelector: Element.Methods.select,
2165 childElements: Element.Methods.immediateDescendants
2166});
2167
2168Element._attributeTranslations = {
2169 write: {
2170 names: {
2171 className: 'class',
2172 htmlFor: 'for'
2173 },
2174 values: { }
2175 }
2176};
2177
2178if (Prototype.Browser.Opera) {
2179 Element.Methods.getStyle = Element.Methods.getStyle.wrap(
2180 function(proceed, element, style) {
2181 switch (style) {
2182 case 'left': case 'top': case 'right': case 'bottom':
2183 if (proceed(element, 'position') === 'static') return null;
2184 case 'height': case 'width':
2185 // returns '0px' for hidden elements; we want it to return null
2186 if (!Element.visible(element)) return null;
2187
2188 // returns the border-box dimensions rather than the content-box
2189 // dimensions, so we subtract padding and borders from the value
2190 var dim = parseInt(proceed(element, style), 10);
2191
2192 if (dim !== element['offset' + style.capitalize()])
2193 return dim + 'px';
2194
2195 var properties;
2196 if (style === 'height') {
2197 properties = ['border-top-width', 'padding-top',
2198 'padding-bottom', 'border-bottom-width'];
2199 }
2200 else {
2201 properties = ['border-left-width', 'padding-left',
2202 'padding-right', 'border-right-width'];
2203 }
2204 return properties.inject(dim, function(memo, property) {
2205 var val = proceed(element, property);
2206 return val === null ? memo : memo - parseInt(val, 10);
2207 }) + 'px';
2208 default: return proceed(element, style);
2209 }
2210 }
2211 );
2212
2213 Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
2214 function(proceed, element, attribute) {
2215 if (attribute === 'title') return element.title;
2216 return proceed(element, attribute);
2217 }
2218 );
2219}
2220
2221else if (Prototype.Browser.IE) {
2222 // IE doesn't report offsets correctly for static elements, so we change them
2223 // to "relative" to get the values, then change them back.
2224 Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
2225 function(proceed, element) {
2226 element = $(element);
2227 // IE throws an error if element is not in document
2228 try { element.offsetParent }
2229 catch(e) { return $(document.body) }
2230 var position = element.getStyle('position');
2231 if (position !== 'static') return proceed(element);
2232 element.setStyle({ position: 'relative' });
2233 var value = proceed(element);
2234 element.setStyle({ position: position });
2235 return value;
2236 }
2237 );
2238
2239 $w('positionedOffset viewportOffset').each(function(method) {
2240 Element.Methods[method] = Element.Methods[method].wrap(
2241 function(proceed, element) {
2242 element = $(element);
2243 try { element.offsetParent }
2244 catch(e) { return Element._returnOffset(0,0) }
2245 var position = element.getStyle('position');
2246 if (position !== 'static') return proceed(element);
2247 // Trigger hasLayout on the offset parent so that IE6 reports
2248 // accurate offsetTop and offsetLeft values for position: fixed.
2249 var offsetParent = element.getOffsetParent();
2250 if (offsetParent && offsetParent.getStyle('position') === 'fixed')
2251 offsetParent.setStyle({ zoom: 1 });
2252 element.setStyle({ position: 'relative' });
2253 var value = proceed(element);
2254 element.setStyle({ position: position });
2255 return value;
2256 }
2257 );
2258 });
2259
2260 Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
2261 function(proceed, element) {
2262 try { element.offsetParent }
2263 catch(e) { return Element._returnOffset(0,0) }
2264 return proceed(element);
2265 }
2266 );
2267
2268 Element.Methods.getStyle = function(element, style) {
2269 element = $(element);
2270 style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
2271 var value = element.style[style];
2272 if (!value && element.currentStyle) value = element.currentStyle[style];
2273
2274 if (style == 'opacity') {
2275 if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
2276 if (value[1]) return parseFloat(value[1]) / 100;
2277 return 1.0;
2278 }
2279
2280 if (value == 'auto') {
2281 if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
2282 return element['offset' + style.capitalize()] + 'px';
2283 return null;
2284 }
2285 return value;
2286 };
2287
2288 Element.Methods.setOpacity = function(element, value) {
2289 function stripAlpha(filter){
2290 return filter.replace(/alpha\([^\)]*\)/gi,'');
2291 }
2292 element = $(element);
2293 var currentStyle = element.currentStyle;
2294 if ((currentStyle && !currentStyle.hasLayout) ||
2295 (!currentStyle && element.style.zoom == 'normal'))
2296 element.style.zoom = 1;
2297
2298 var filter = element.getStyle('filter'), style = element.style;
2299 if (value == 1 || value === '') {
2300 (filter = stripAlpha(filter)) ?
2301 style.filter = filter : style.removeAttribute('filter');
2302 return element;
2303 } else if (value < 0.00001) value = 0;
2304 style.filter = stripAlpha(filter) +
2305 'alpha(opacity=' + (value * 100) + ')';
2306 return element;
2307 };
2308
2309 Element._attributeTranslations = {
2310 read: {
2311 names: {
2312 'class': 'className',
2313 'for': 'htmlFor'
2314 },
2315 values: {
2316 _getAttr: function(element, attribute) {
2317 return element.getAttribute(attribute, 2);
2318 },
2319 _getAttrNode: function(element, attribute) {
2320 var node = element.getAttributeNode(attribute);
2321 return node ? node.value : "";
2322 },
2323 _getEv: function(element, attribute) {
2324 attribute = element.getAttribute(attribute);
2325 return attribute ? attribute.toString().slice(23, -2) : null;
2326 },
2327 _flag: function(element, attribute) {
2328 return $(element).hasAttribute(attribute) ? attribute : null;
2329 },
2330 style: function(element) {
2331 return element.style.cssText.toLowerCase();
2332 },
2333 title: function(element) {
2334 return element.title;
2335 }
2336 }
2337 }
2338 };
2339
2340 Element._attributeTranslations.write = {
2341 names: Object.extend({
2342 cellpadding: 'cellPadding',
2343 cellspacing: 'cellSpacing'
2344 }, Element._attributeTranslations.read.names),
2345 values: {
2346 checked: function(element, value) {
2347 element.checked = !!value;
2348 },
2349
2350 style: function(element, value) {
2351 element.style.cssText = value ? value : '';
2352 }
2353 }
2354 };
2355
2356 Element._attributeTranslations.has = {};
2357
2358 $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
2359 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
2360 Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
2361 Element._attributeTranslations.has[attr.toLowerCase()] = attr;
2362 });
2363
2364 (function(v) {
2365 Object.extend(v, {
2366 href: v._getAttr,
2367 src: v._getAttr,
2368 type: v._getAttr,
2369 action: v._getAttrNode,
2370 disabled: v._flag,
2371 checked: v._flag,
2372 readonly: v._flag,
2373 multiple: v._flag,
2374 onload: v._getEv,
2375 onunload: v._getEv,
2376 onclick: v._getEv,
2377 ondblclick: v._getEv,
2378 onmousedown: v._getEv,
2379 onmouseup: v._getEv,
2380 onmouseover: v._getEv,
2381 onmousemove: v._getEv,
2382 onmouseout: v._getEv,
2383 onfocus: v._getEv,
2384 onblur: v._getEv,
2385 onkeypress: v._getEv,
2386 onkeydown: v._getEv,
2387 onkeyup: v._getEv,
2388 onsubmit: v._getEv,
2389 onreset: v._getEv,
2390 onselect: v._getEv,
2391 onchange: v._getEv
2392 });
2393 })(Element._attributeTranslations.read.values);
2394}
2395
2396else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
2397 Element.Methods.setOpacity = function(element, value) {
2398 element = $(element);
2399 element.style.opacity = (value == 1) ? 0.999999 :
2400 (value === '') ? '' : (value < 0.00001) ? 0 : value;
2401 return element;
2402 };
2403}
2404
2405else if (Prototype.Browser.WebKit) {
2406 Element.Methods.setOpacity = function(element, value) {
2407 element = $(element);
2408 element.style.opacity = (value == 1 || value === '') ? '' :
2409 (value < 0.00001) ? 0 : value;
2410
2411 if (value == 1)
2412 if(element.tagName.toUpperCase() == 'IMG' && element.width) {
2413 element.width++; element.width--;
2414 } else try {
2415 var n = document.createTextNode(' ');
2416 element.appendChild(n);
2417 element.removeChild(n);
2418 } catch (e) { }
2419
2420 return element;
2421 };
2422
2423 // Safari returns margins on body which is incorrect if the child is absolutely
2424 // positioned. For performance reasons, redefine Element#cumulativeOffset for
2425 // KHTML/WebKit only.
2426 Element.Methods.cumulativeOffset = function(element) {
2427 var valueT = 0, valueL = 0;
2428 do {
2429 valueT += element.offsetTop || 0;
2430 valueL += element.offsetLeft || 0;
2431 if (element.offsetParent == document.body)
2432 if (Element.getStyle(element, 'position') == 'absolute') break;
2433
2434 element = element.offsetParent;
2435 } while (element);
2436
2437 return Element._returnOffset(valueL, valueT);
2438 };
2439}
2440
2441if (Prototype.Browser.IE || Prototype.Browser.Opera) {
2442 // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
2443 Element.Methods.update = function(element, content) {
2444 element = $(element);
2445
2446 if (content && content.toElement) content = content.toElement();
2447 if (Object.isElement(content)) return element.update().insert(content);
2448
2449 content = Object.toHTML(content);
2450 var tagName = element.tagName.toUpperCase();
2451
2452 if (tagName in Element._insertionTranslations.tags) {
2453 $A(element.childNodes).each(function(node) { element.removeChild(node) });
2454 Element._getContentFromAnonymousElement(tagName, content.stripScripts())
2455 .each(function(node) { element.appendChild(node) });
2456 }
2457 else element.innerHTML = content.stripScripts();
2458
2459 content.evalScripts.bind(content).defer();
2460 return element;
2461 };
2462}
2463
2464if ('outerHTML' in document.createElement('div')) {
2465 Element.Methods.replace = function(element, content) {
2466 element = $(element);
2467
2468 if (content && content.toElement) content = content.toElement();
2469 if (Object.isElement(content)) {
2470 element.parentNode.replaceChild(content, element);
2471 return element;
2472 }
2473
2474 content = Object.toHTML(content);
2475 var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
2476
2477 if (Element._insertionTranslations.tags[tagName]) {
2478 var nextSibling = element.next();
2479 var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
2480 parent.removeChild(element);
2481 if (nextSibling)
2482 fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
2483 else
2484 fragments.each(function(node) { parent.appendChild(node) });
2485 }
2486 else element.outerHTML = content.stripScripts();
2487
2488 content.evalScripts.bind(content).defer();
2489 return element;
2490 };
2491}
2492
2493Element._returnOffset = function(l, t) {
2494 var result = [l, t];
2495 result.left = l;
2496 result.top = t;
2497 return result;
2498};
2499
2500Element._getContentFromAnonymousElement = function(tagName, html) {
2501 var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
2502 if (t) {
2503 div.innerHTML = t[0] + html + t[1];
2504 t[2].times(function() { div = div.firstChild });
2505 } else div.innerHTML = html;
2506 return $A(div.childNodes);
2507};
2508
2509Element._insertionTranslations = {
2510 before: function(element, node) {
2511 element.parentNode.insertBefore(node, element);
2512 },
2513 top: function(element, node) {
2514 element.insertBefore(node, element.firstChild);
2515 },
2516 bottom: function(element, node) {
2517 element.appendChild(node);
2518 },
2519 after: function(element, node) {
2520 element.parentNode.insertBefore(node, element.nextSibling);
2521 },
2522 tags: {
2523 TABLE: ['<table>', '</table>', 1],
2524 TBODY: ['<table><tbody>', '</tbody></table>', 2],
2525 TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2526 TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2527 SELECT: ['<select>', '</select>', 1]
2528 }
2529};
2530
2531(function() {
2532 Object.extend(this.tags, {
2533 THEAD: this.tags.TBODY,
2534 TFOOT: this.tags.TBODY,
2535 TH: this.tags.TD
2536 });
2537}).call(Element._insertionTranslations);
2538
2539Element.Methods.Simulated = {
2540 hasAttribute: function(element, attribute) {
2541 attribute = Element._attributeTranslations.has[attribute] || attribute;
2542 var node = $(element).getAttributeNode(attribute);
2543 return !!(node && node.specified);
2544 }
2545};
2546
2547Element.Methods.ByTag = { };
2548
2549Object.extend(Element, Element.Methods);
2550
2551if (!Prototype.BrowserFeatures.ElementExtensions &&
2552 document.createElement('div')['__proto__']) {
2553 window.HTMLElement = { };
2554 window.HTMLElement.prototype = document.createElement('div')['__proto__'];
2555 Prototype.BrowserFeatures.ElementExtensions = true;
2556}
2557
2558Element.extend = (function() {
2559 if (Prototype.BrowserFeatures.SpecificElementExtensions)
2560 return Prototype.K;
2561
2562 var Methods = { }, ByTag = Element.Methods.ByTag;
2563
2564 var extend = Object.extend(function(element) {
2565 if (!element || element._extendedByPrototype ||
2566 element.nodeType != 1 || element == window) return element;
2567
2568 var methods = Object.clone(Methods),
2569 tagName = element.tagName.toUpperCase(), property, value;
2570
2571 // extend methods for specific tags
2572 if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
2573
2574 for (property in methods) {
2575 value = methods[property];
2576 if (Object.isFunction(value) && !(property in element))
2577 element[property] = value.methodize();
2578 }
2579
2580 element._extendedByPrototype = Prototype.emptyFunction;
2581 return element;
2582
2583 }, {
2584 refresh: function() {
2585 // extend methods for all tags (Safari doesn't need this)
2586 if (!Prototype.BrowserFeatures.ElementExtensions) {
2587 Object.extend(Methods, Element.Methods);
2588 Object.extend(Methods, Element.Methods.Simulated);
2589 }
2590 }
2591 });
2592
2593 extend.refresh();
2594 return extend;
2595})();
2596
2597Element.hasAttribute = function(element, attribute) {
2598 if (element.hasAttribute) return element.hasAttribute(attribute);
2599 return Element.Methods.Simulated.hasAttribute(element, attribute);
2600};
2601
2602Element.addMethods = function(methods) {
2603 var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
2604
2605 if (!methods) {
2606 Object.extend(Form, Form.Methods);
2607 Object.extend(Form.Element, Form.Element.Methods);
2608 Object.extend(Element.Methods.ByTag, {
2609 "FORM": Object.clone(Form.Methods),
2610 "INPUT": Object.clone(Form.Element.Methods),
2611 "SELECT": Object.clone(Form.Element.Methods),
2612 "TEXTAREA": Object.clone(Form.Element.Methods)
2613 });
2614 }
2615
2616 if (arguments.length == 2) {
2617 var tagName = methods;
2618 methods = arguments[1];
2619 }
2620
2621 if (!tagName) Object.extend(Element.Methods, methods || { });
2622 else {
2623 if (Object.isArray(tagName)) tagName.each(extend);
2624 else extend(tagName);
2625 }
2626
2627 function extend(tagName) {
2628 tagName = tagName.toUpperCase();
2629 if (!Element.Methods.ByTag[tagName])
2630 Element.Methods.ByTag[tagName] = { };
2631 Object.extend(Element.Methods.ByTag[tagName], methods);
2632 }
2633
2634 function copy(methods, destination, onlyIfAbsent) {
2635 onlyIfAbsent = onlyIfAbsent || false;
2636 for (var property in methods) {
2637 var value = methods[property];
2638 if (!Object.isFunction(value)) continue;
2639 if (!onlyIfAbsent || !(property in destination))
2640 destination[property] = value.methodize();
2641 }
2642 }
2643
2644 function findDOMClass(tagName) {
2645 var klass;
2646 var trans = {
2647 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
2648 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
2649 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
2650 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
2651 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
2652 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
2653 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
2654 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
2655 "FrameSet", "IFRAME": "IFrame"
2656 };
2657 if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
2658 if (window[klass]) return window[klass];
2659 klass = 'HTML' + tagName + 'Element';
2660 if (window[klass]) return window[klass];
2661 klass = 'HTML' + tagName.capitalize() + 'Element';
2662 if (window[klass]) return window[klass];
2663
2664 window[klass] = { };
2665 window[klass].prototype = document.createElement(tagName)['__proto__'];
2666 return window[klass];
2667 }
2668
2669 if (F.ElementExtensions) {
2670 copy(Element.Methods, HTMLElement.prototype);
2671 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
2672 }
2673
2674 if (F.SpecificElementExtensions) {
2675 for (var tag in Element.Methods.ByTag) {
2676 var klass = findDOMClass(tag);
2677 if (Object.isUndefined(klass)) continue;
2678 copy(T[tag], klass.prototype);
2679 }
2680 }
2681
2682 Object.extend(Element, Element.Methods);
2683 delete Element.ByTag;
2684
2685 if (Element.extend.refresh) Element.extend.refresh();
2686 Element.cache = { };
2687};
2688
2689document.viewport = {
2690 getDimensions: function() {
2691 var dimensions = { }, B = Prototype.Browser;
2692 $w('width height').each(function(d) {
2693 var D = d.capitalize();
2694 if (B.WebKit && !document.evaluate) {
2695 // Safari <3.0 needs self.innerWidth/Height
2696 dimensions[d] = self['inner' + D];
2697 } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
2698 // Opera <9.5 needs document.body.clientWidth/Height
2699 dimensions[d] = document.body['client' + D]
2700 } else {
2701 dimensions[d] = document.documentElement['client' + D];
2702 }
2703 });
2704 return dimensions;
2705 },
2706
2707 getWidth: function() {
2708 return this.getDimensions().width;
2709 },
2710
2711 getHeight: function() {
2712 return this.getDimensions().height;
2713 },
2714
2715 getScrollOffsets: function() {
2716 return Element._returnOffset(
2717 window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
2718 window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
2719 }
2720};
2721/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
2722 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2723 * license. Please see http://www.yui-ext.com/ for more information. */
2724
2725var Selector = Class.create({
2726 initialize: function(expression) {
2727 this.expression = expression.strip();
2728
2729 if (this.shouldUseSelectorsAPI()) {
2730 this.mode = 'selectorsAPI';
2731 } else if (this.shouldUseXPath()) {
2732 this.mode = 'xpath';
2733 this.compileXPathMatcher();
2734 } else {
2735 this.mode = "normal";
2736 this.compileMatcher();
2737 }
2738
2739 },
2740
2741 shouldUseXPath: function() {
2742 if (!Prototype.BrowserFeatures.XPath) return false;
2743
2744 var e = this.expression;
2745
2746 // Safari 3 chokes on :*-of-type and :empty
2747 if (Prototype.Browser.WebKit &&
2748 (e.include("-of-type") || e.include(":empty")))
2749 return false;
2750
2751 // XPath can't do namespaced attributes, nor can it read
2752 // the "checked" property from DOM nodes
2753 if ((/(\[[\w-]*?:|:checked)/).test(e))
2754 return false;
2755
2756 return true;
2757 },
2758
2759 shouldUseSelectorsAPI: function() {
2760 if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
2761
2762 if (!Selector._div) Selector._div = new Element('div');
2763
2764 // Make sure the browser treats the selector as valid. Test on an
2765 // isolated element to minimize cost of this check.
2766 try {
2767 Selector._div.querySelector(this.expression);
2768 } catch(e) {
2769 return false;
2770 }
2771
2772 return true;
2773 },
2774
2775 compileMatcher: function() {
2776 var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2777 c = Selector.criteria, le, p, m;
2778
2779 if (Selector._cache[e]) {
2780 this.matcher = Selector._cache[e];
2781 return;
2782 }
2783
2784 this.matcher = ["this.matcher = function(root) {",
2785 "var r = root, h = Selector.handlers, c = false, n;"];
2786
2787 while (e && le != e && (/\S/).test(e)) {
2788 le = e;
2789 for (var i in ps) {
2790 p = ps[i];
2791 if (m = e.match(p)) {
2792 this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
2793 new Template(c[i]).evaluate(m));
2794 e = e.replace(m[0], '');
2795 break;
2796 }
2797 }
2798 }
2799
2800 this.matcher.push("return h.unique(n);\n}");
2801 eval(this.matcher.join('\n'));
2802 Selector._cache[this.expression] = this.matcher;
2803 },
2804
2805 compileXPathMatcher: function() {
2806 var e = this.expression, ps = Selector.patterns,
2807 x = Selector.xpath, le, m;
2808
2809 if (Selector._cache[e]) {
2810 this.xpath = Selector._cache[e]; return;
2811 }
2812
2813 this.matcher = ['.//*'];
2814 while (e && le != e && (/\S/).test(e)) {
2815 le = e;
2816 for (var i in ps) {
2817 if (m = e.match(ps[i])) {
2818 this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
2819 new Template(x[i]).evaluate(m));
2820 e = e.replace(m[0], '');
2821 break;
2822 }
2823 }
2824 }
2825
2826 this.xpath = this.matcher.join('');
2827 Selector._cache[this.expression] = this.xpath;
2828 },
2829
2830 findElements: function(root) {
2831 root = root || document;
2832 var e = this.expression, results;
2833
2834 switch (this.mode) {
2835 case 'selectorsAPI':
2836 // querySelectorAll queries document-wide, then filters to descendants
2837 // of the context element. That's not what we want.
2838 // Add an explicit context to the selector if necessary.
2839 if (root !== document) {
2840 var oldId = root.id, id = $(root).identify();
2841 e = "#" + id + " " + e;
2842 }
2843
2844 results = $A(root.querySelectorAll(e)).map(Element.extend);
2845 root.id = oldId;
2846
2847 return results;
2848 case 'xpath':
2849 return document._getElementsByXPath(this.xpath, root);
2850 default:
2851 return this.matcher(root);
2852 }
2853 },
2854
2855 match: function(element) {
2856 this.tokens = [];
2857
2858 var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
2859 var le, p, m;
2860
2861 while (e && le !== e && (/\S/).test(e)) {
2862 le = e;
2863 for (var i in ps) {
2864 p = ps[i];
2865 if (m = e.match(p)) {
2866 // use the Selector.assertions methods unless the selector
2867 // is too complex.
2868 if (as[i]) {
2869 this.tokens.push([i, Object.clone(m)]);
2870 e = e.replace(m[0], '');
2871 } else {
2872 // reluctantly do a document-wide search
2873 // and look for a match in the array
2874 return this.findElements(document).include(element);
2875 }
2876 }
2877 }
2878 }
2879
2880 var match = true, name, matches;
2881 for (var i = 0, token; token = this.tokens[i]; i++) {
2882 name = token[0], matches = token[1];
2883 if (!Selector.assertions[name](element, matches)) {
2884 match = false; break;
2885 }
2886 }
2887
2888 return match;
2889 },
2890
2891 toString: function() {
2892 return this.expression;
2893 },
2894
2895 inspect: function() {
2896 return "#<Selector:" + this.expression.inspect() + ">";
2897 }
2898});
2899
2900Object.extend(Selector, {
2901 _cache: { },
2902
2903 xpath: {
2904 descendant: "//*",
2905 child: "/*",
2906 adjacent: "/following-sibling::*[1]",
2907 laterSibling: '/following-sibling::*',
2908 tagName: function(m) {
2909 if (m[1] == '*') return '';
2910 return "[local-name()='" + m[1].toLowerCase() +
2911 "' or local-name()='" + m[1].toUpperCase() + "']";
2912 },
2913 className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2914 id: "[@id='#{1}']",
2915 attrPresence: function(m) {
2916 m[1] = m[1].toLowerCase();
2917 return new Template("[@#{1}]").evaluate(m);
2918 },
2919 attr: function(m) {
2920 m[1] = m[1].toLowerCase();
2921 m[3] = m[5] || m[6];
2922 return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2923 },
2924 pseudo: function(m) {
2925 var h = Selector.xpath.pseudos[m[1]];
2926 if (!h) return '';
2927 if (Object.isFunction(h)) return h(m);
2928 return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2929 },
2930 operators: {
2931 '=': "[@#{1}='#{3}']",
2932 '!=': "[@#{1}!='#{3}']",
2933 '^=': "[starts-with(@#{1}, '#{3}')]",
2934 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2935 '*=': "[contains(@#{1}, '#{3}')]",
2936 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2937 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2938 },
2939 pseudos: {
2940 'first-child': '[not(preceding-sibling::*)]',
2941 'last-child': '[not(following-sibling::*)]',
2942 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2943 'empty': "[count(*) = 0 and (count(text()) = 0)]",
2944 'checked': "[@checked]",
2945 'disabled': "[(@disabled) and (@type!='hidden')]",
2946 'enabled': "[not(@disabled) and (@type!='hidden')]",
2947 'not': function(m) {
2948 var e = m[6], p = Selector.patterns,
2949 x = Selector.xpath, le, v;
2950
2951 var exclusion = [];
2952 while (e && le != e && (/\S/).test(e)) {
2953 le = e;
2954 for (var i in p) {
2955 if (m = e.match(p[i])) {
2956 v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
2957 exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2958 e = e.replace(m[0], '');
2959 break;
2960 }
2961 }
2962 }
2963 return "[not(" + exclusion.join(" and ") + ")]";
2964 },
2965 'nth-child': function(m) {
2966 return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2967 },
2968 'nth-last-child': function(m) {
2969 return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2970 },
2971 'nth-of-type': function(m) {
2972 return Selector.xpath.pseudos.nth("position() ", m);
2973 },
2974 'nth-last-of-type': function(m) {
2975 return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2976 },
2977 'first-of-type': function(m) {
2978 m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2979 },
2980 'last-of-type': function(m) {
2981 m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2982 },
2983 'only-of-type': function(m) {
2984 var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2985 },
2986 nth: function(fragment, m) {
2987 var mm, formula = m[6], predicate;
2988 if (formula == 'even') formula = '2n+0';
2989 if (formula == 'odd') formula = '2n+1';
2990 if (mm = formula.match(/^(\d+)$/)) // digit only
2991 return '[' + fragment + "= " + mm[1] + ']';
2992 if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2993 if (mm[1] == "-") mm[1] = -1;
2994 var a = mm[1] ? Number(mm[1]) : 1;
2995 var b = mm[2] ? Number(mm[2]) : 0;
2996 predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2997 "((#{fragment} - #{b}) div #{a} >= 0)]";
2998 return new Template(predicate).evaluate({
2999 fragment: fragment, a: a, b: b });
3000 }
3001 }
3002 }
3003 },
3004
3005 criteria: {
3006 tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
3007 className: 'n = h.className(n, r, "#{1}", c); c = false;',
3008 id: 'n = h.id(n, r, "#{1}", c); c = false;',
3009 attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
3010 attr: function(m) {
3011 m[3] = (m[5] || m[6]);
3012 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
3013 },
3014 pseudo: function(m) {
3015 if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
3016 return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
3017 },
3018 descendant: 'c = "descendant";',
3019 child: 'c = "child";',
3020 adjacent: 'c = "adjacent";',
3021 laterSibling: 'c = "laterSibling";'
3022 },
3023
3024 patterns: {
3025 // combinators must be listed first
3026 // (and descendant needs to be last combinator)
3027 laterSibling: /^\s*~\s*/,
3028 child: /^\s*>\s*/,
3029 adjacent: /^\s*\+\s*/,
3030 descendant: /^\s/,
3031
3032 // selectors follow
3033 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
3034 id: /^#([\w\-\*]+)(\b|$)/,
3035 className: /^\.([\w\-\*]+)(\b|$)/,
3036 pseudo:
3037/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
3038 attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
3039 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
3040 },
3041
3042 // for Selector.match and Element#match
3043 assertions: {
3044 tagName: function(element, matches) {
3045 return matches[1].toUpperCase() == element.tagName.toUpperCase();
3046 },
3047
3048 className: function(element, matches) {
3049 return Element.hasClassName(element, matches[1]);
3050 },
3051
3052 id: function(element, matches) {
3053 return element.id === matches[1];
3054 },
3055
3056 attrPresence: function(element, matches) {
3057 return Element.hasAttribute(element, matches[1]);
3058 },
3059
3060 attr: function(element, matches) {
3061 var nodeValue = Element.readAttribute(element, matches[1]);
3062 return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
3063 }
3064 },
3065
3066 handlers: {
3067 // UTILITY FUNCTIONS
3068 // joins two collections
3069 concat: function(a, b) {
3070 for (var i = 0, node; node = b[i]; i++)
3071 a.push(node);
3072 return a;
3073 },
3074
3075 // marks an array of nodes for counting
3076 mark: function(nodes) {
3077 var _true = Prototype.emptyFunction;
3078 for (var i = 0, node; node = nodes[i]; i++)
3079 node._countedByPrototype = _true;
3080 return nodes;
3081 },
3082
3083 unmark: function(nodes) {
3084 for (var i = 0, node; node = nodes[i]; i++)
3085 node._countedByPrototype = undefined;
3086 return nodes;
3087 },
3088
3089 // mark each child node with its position (for nth calls)
3090 // "ofType" flag indicates whether we're indexing for nth-of-type
3091 // rather than nth-child
3092 index: function(parentNode, reverse, ofType) {
3093 parentNode._countedByPrototype = Prototype.emptyFunction;
3094 if (reverse) {
3095 for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
3096 var node = nodes[i];
3097 if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
3098 }
3099 } else {
3100 for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
3101 if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
3102 }
3103 },
3104
3105 // filters out duplicates and extends all nodes
3106 unique: function(nodes) {
3107 if (nodes.length == 0) return nodes;
3108 var results = [], n;
3109 for (var i = 0, l = nodes.length; i < l; i++)
3110 if (!(n = nodes[i])._countedByPrototype) {
3111 n._countedByPrototype = Prototype.emptyFunction;
3112 results.push(Element.extend(n));
3113 }
3114 return Selector.handlers.unmark(results);
3115 },
3116
3117 // COMBINATOR FUNCTIONS
3118 descendant: function(nodes) {
3119 var h = Selector.handlers;
3120 for (var i = 0, results = [], node; node = nodes[i]; i++)
3121 h.concat(results, node.getElementsByTagName('*'));
3122 return results;
3123 },
3124
3125 child: function(nodes) {
3126 var h = Selector.handlers;
3127 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3128 for (var j = 0, child; child = node.childNodes[j]; j++)
3129 if (child.nodeType == 1 && child.tagName != '!') results.push(child);
3130 }
3131 return results;
3132 },
3133
3134 adjacent: function(nodes) {
3135 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3136 var next = this.nextElementSibling(node);
3137 if (next) results.push(next);
3138 }
3139 return results;
3140 },
3141
3142 laterSibling: function(nodes) {
3143 var h = Selector.handlers;
3144 for (var i = 0, results = [], node; node = nodes[i]; i++)
3145 h.concat(results, Element.nextSiblings(node));
3146 return results;
3147 },
3148
3149 nextElementSibling: function(node) {
3150 while (node = node.nextSibling)
3151 if (node.nodeType == 1) return node;
3152 return null;
3153 },
3154
3155 previousElementSibling: function(node) {
3156 while (node = node.previousSibling)
3157 if (node.nodeType == 1) return node;
3158 return null;
3159 },
3160
3161 // TOKEN FUNCTIONS
3162 tagName: function(nodes, root, tagName, combinator) {
3163 var uTagName = tagName.toUpperCase();
3164 var results = [], h = Selector.handlers;
3165 if (nodes) {
3166 if (combinator) {
3167 // fastlane for ordinary descendant combinators
3168 if (combinator == "descendant") {
3169 for (var i = 0, node; node = nodes[i]; i++)
3170 h.concat(results, node.getElementsByTagName(tagName));
3171 return results;
3172 } else nodes = this[combinator](nodes);
3173 if (tagName == "*") return nodes;
3174 }
3175 for (var i = 0, node; node = nodes[i]; i++)
3176 if (node.tagName.toUpperCase() === uTagName) results.push(node);
3177 return results;
3178 } else return root.getElementsByTagName(tagName);
3179 },
3180
3181 id: function(nodes, root, id, combinator) {
3182 var targetNode = $(id), h = Selector.handlers;
3183 if (!targetNode) return [];
3184 if (!nodes && root == document) return [targetNode];
3185 if (nodes) {
3186 if (combinator) {
3187 if (combinator == 'child') {
3188 for (var i = 0, node; node = nodes[i]; i++)
3189 if (targetNode.parentNode == node) return [targetNode];
3190 } else if (combinator == 'descendant') {
3191 for (var i = 0, node; node = nodes[i]; i++)
3192 if (Element.descendantOf(targetNode, node)) return [targetNode];
3193 } else if (combinator == 'adjacent') {
3194 for (var i = 0, node; node = nodes[i]; i++)
3195 if (Selector.handlers.previousElementSibling(targetNode) == node)
3196 return [targetNode];
3197 } else nodes = h[combinator](nodes);
3198 }
3199 for (var i = 0, node; node = nodes[i]; i++)
3200 if (node == targetNode) return [targetNode];
3201 return [];
3202 }
3203 return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
3204 },
3205
3206 className: function(nodes, root, className, combinator) {
3207 if (nodes && combinator) nodes = this[combinator](nodes);
3208 return Selector.handlers.byClassName(nodes, root, className);
3209 },
3210
3211 byClassName: function(nodes, root, className) {
3212 if (!nodes) nodes = Selector.handlers.descendant([root]);
3213 var needle = ' ' + className + ' ';
3214 for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
3215 nodeClassName = node.className;
3216 if (nodeClassName.length == 0) continue;
3217 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
3218 results.push(node);
3219 }
3220 return results;
3221 },
3222
3223 attrPresence: function(nodes, root, attr, combinator) {
3224 if (!nodes) nodes = root.getElementsByTagName("*");
3225 if (nodes && combinator) nodes = this[combinator](nodes);
3226 var results = [];
3227 for (var i = 0, node; node = nodes[i]; i++)
3228 if (Element.hasAttribute(node, attr)) results.push(node);
3229 return results;
3230 },
3231
3232 attr: function(nodes, root, attr, value, operator, combinator) {
3233 if (!nodes) nodes = root.getElementsByTagName("*");
3234 if (nodes && combinator) nodes = this[combinator](nodes);
3235 var handler = Selector.operators[operator], results = [];
3236 for (var i = 0, node; node = nodes[i]; i++) {
3237 var nodeValue = Element.readAttribute(node, attr);
3238 if (nodeValue === null) continue;
3239 if (handler(nodeValue, value)) results.push(node);
3240 }
3241 return results;
3242 },
3243
3244 pseudo: function(nodes, name, value, root, combinator) {
3245 if (nodes && combinator) nodes = this[combinator](nodes);
3246 if (!nodes) nodes = root.getElementsByTagName("*");
3247 return Selector.pseudos[name](nodes, value, root);
3248 }
3249 },
3250
3251 pseudos: {
3252 'first-child': function(nodes, value, root) {
3253 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3254 if (Selector.handlers.previousElementSibling(node)) continue;
3255 results.push(node);
3256 }
3257 return results;
3258 },
3259 'last-child': function(nodes, value, root) {
3260 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3261 if (Selector.handlers.nextElementSibling(node)) continue;
3262 results.push(node);
3263 }
3264 return results;
3265 },
3266 'only-child': function(nodes, value, root) {
3267 var h = Selector.handlers;
3268 for (var i = 0, results = [], node; node = nodes[i]; i++)
3269 if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
3270 results.push(node);
3271 return results;
3272 },
3273 'nth-child': function(nodes, formula, root) {
3274 return Selector.pseudos.nth(nodes, formula, root);
3275 },
3276 'nth-last-child': function(nodes, formula, root) {
3277 return Selector.pseudos.nth(nodes, formula, root, true);
3278 },
3279 'nth-of-type': function(nodes, formula, root) {
3280 return Selector.pseudos.nth(nodes, formula, root, false, true);
3281 },
3282 'nth-last-of-type': function(nodes, formula, root) {
3283 return Selector.pseudos.nth(nodes, formula, root, true, true);
3284 },
3285 'first-of-type': function(nodes, formula, root) {
3286 return Selector.pseudos.nth(nodes, "1", root, false, true);
3287 },
3288 'last-of-type': function(nodes, formula, root) {
3289 return Selector.pseudos.nth(nodes, "1", root, true, true);
3290 },
3291 'only-of-type': function(nodes, formula, root) {
3292 var p = Selector.pseudos;
3293 return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
3294 },
3295
3296 // handles the an+b logic
3297 getIndices: function(a, b, total) {
3298 if (a == 0) return b > 0 ? [b] : [];
3299 return $R(1, total).inject([], function(memo, i) {
3300 if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
3301 return memo;
3302 });
3303 },
3304
3305 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
3306 nth: function(nodes, formula, root, reverse, ofType) {
3307 if (nodes.length == 0) return [];
3308 if (formula == 'even') formula = '2n+0';
3309 if (formula == 'odd') formula = '2n+1';
3310 var h = Selector.handlers, results = [], indexed = [], m;
3311 h.mark(nodes);
3312 for (var i = 0, node; node = nodes[i]; i++) {
3313 if (!node.parentNode._countedByPrototype) {
3314 h.index(node.parentNode, reverse, ofType);
3315 indexed.push(node.parentNode);
3316 }
3317 }
3318 if (formula.match(/^\d+$/)) { // just a number
3319 formula = Number(formula);
3320 for (var i = 0, node; node = nodes[i]; i++)
3321 if (node.nodeIndex == formula) results.push(node);
3322 } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
3323 if (m[1] == "-") m[1] = -1;
3324 var a = m[1] ? Number(m[1]) : 1;
3325 var b = m[2] ? Number(m[2]) : 0;
3326 var indices = Selector.pseudos.getIndices(a, b, nodes.length);
3327 for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
3328 for (var j = 0; j < l; j++)
3329 if (node.nodeIndex == indices[j]) results.push(node);
3330 }
3331 }
3332 h.unmark(nodes);
3333 h.unmark(indexed);
3334 return results;
3335 },
3336
3337 'empty': function(nodes, value, root) {
3338 for (var i = 0, results = [], node; node = nodes[i]; i++) {
3339 // IE treats comments as element nodes
3340 if (node.tagName == '!' || node.firstChild) continue;
3341 results.push(node);
3342 }
3343 return results;
3344 },
3345
3346 'not': function(nodes, selector, root) {
3347 var h = Selector.handlers, selectorType, m;
3348 var exclusions = new Selector(selector).findElements(root);
3349 h.mark(exclusions);
3350 for (var i = 0, results = [], node; node = nodes[i]; i++)
3351 if (!node._countedByPrototype) results.push(node);
3352 h.unmark(exclusions);
3353 return results;
3354 },
3355
3356 'enabled': function(nodes, value, root) {
3357 for (var i = 0, results = [], node; node = nodes[i]; i++)
3358 if (!node.disabled && (!node.type || node.type !== 'hidden'))
3359 results.push(node);
3360 return results;
3361 },
3362
3363 'disabled': function(nodes, value, root) {
3364 for (var i = 0, results = [], node; node = nodes[i]; i++)
3365 if (node.disabled) results.push(node);
3366 return results;
3367 },
3368
3369 'checked': function(nodes, value, root) {
3370 for (var i = 0, results = [], node; node = nodes[i]; i++)
3371 if (node.checked) results.push(node);
3372 return results;
3373 }
3374 },
3375
3376 operators: {
3377 '=': function(nv, v) { return nv == v; },
3378 '!=': function(nv, v) { return nv != v; },
3379 '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
3380 '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
3381 '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
3382 '$=': function(nv, v) { return nv.endsWith(v); },
3383 '*=': function(nv, v) { return nv.include(v); },
3384 '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
3385 '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
3386 '-').include('-' + (v || "").toUpperCase() + '-'); }
3387 },
3388
3389 split: function(expression) {
3390 var expressions = [];
3391 expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
3392 expressions.push(m[1].strip());
3393 });
3394 return expressions;
3395 },
3396
3397 matchElements: function(elements, expression) {
3398 var matches = $$(expression), h = Selector.handlers;
3399 h.mark(matches);
3400 for (var i = 0, results = [], element; element = elements[i]; i++)
3401 if (element._countedByPrototype) results.push(element);
3402 h.unmark(matches);
3403 return results;
3404 },
3405
3406 findElement: function(elements, expression, index) {
3407 if (Object.isNumber(expression)) {
3408 index = expression; expression = false;
3409 }
3410 return Selector.matchElements(elements, expression || '*')[index || 0];
3411 },
3412
3413 findChildElements: function(element, expressions) {
3414 expressions = Selector.split(expressions.join(','));
3415 var results = [], h = Selector.handlers;
3416 for (var i = 0, l = expressions.length, selector; i < l; i++) {
3417 selector = new Selector(expressions[i].strip());
3418 h.concat(results, selector.findElements(element));
3419 }
3420 return (l > 1) ? h.unique(results) : results;
3421 }
3422});
3423
3424if (Prototype.Browser.IE) {
3425 Object.extend(Selector.handlers, {
3426 // IE returns comment nodes on getElementsByTagName("*").
3427 // Filter them out.
3428 concat: function(a, b) {
3429 for (var i = 0, node; node = b[i]; i++)
3430 if (node.tagName !== "!") a.push(node);
3431 return a;
3432 },
3433
3434 // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
3435 unmark: function(nodes) {
3436 for (var i = 0, node; node = nodes[i]; i++)
3437 node.removeAttribute('_countedByPrototype');
3438 return nodes;
3439 }
3440 });
3441}
3442
3443function $$() {
3444 return Selector.findChildElements(document, $A(arguments));
3445}
3446var Form = {
3447 reset: function(form) {
3448 $(form).reset();
3449 return form;
3450 },
3451
3452 serializeElements: function(elements, options) {
3453 if (typeof options != 'object') options = { hash: !!options };
3454 else if (Object.isUndefined(options.hash)) options.hash = true;
3455 var key, value, submitted = false, submit = options.submit;
3456
3457 var data = elements.inject({ }, function(result, element) {
3458 if (!element.disabled && element.name) {
3459 key = element.name; value = $(element).getValue();
3460 if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
3461 submit !== false && (!submit || key == submit) && (submitted = true)))) {
3462 if (key in result) {
3463 // a key is already present; construct an array of values
3464 if (!Object.isArray(result[key])) result[key] = [result[key]];
3465 result[key].push(value);
3466 }
3467 else result[key] = value;
3468 }
3469 }
3470 return result;
3471 });
3472
3473 return options.hash ? data : Object.toQueryString(data);
3474 }
3475};
3476
3477Form.Methods = {
3478 serialize: function(form, options) {
3479 return Form.serializeElements(Form.getElements(form), options);
3480 },
3481
3482 getElements: function(form) {
3483 return $A($(form).getElementsByTagName('*')).inject([],
3484 function(elements, child) {
3485 if (Form.Element.Serializers[child.tagName.toLowerCase()])
3486 elements.push(Element.extend(child));
3487 return elements;
3488 }
3489 );
3490 },
3491
3492 getInputs: function(form, typeName, name) {
3493 form = $(form);
3494 var inputs = form.getElementsByTagName('input');
3495
3496 if (!typeName && !name) return $A(inputs).map(Element.extend);
3497
3498 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
3499 var input = inputs[i];
3500 if ((typeName && input.type != typeName) || (name && input.name != name))
3501 continue;
3502 matchingInputs.push(Element.extend(input));
3503 }
3504
3505 return matchingInputs;
3506 },
3507
3508 disable: function(form) {
3509 form = $(form);
3510 Form.getElements(form).invoke('disable');
3511 return form;
3512 },
3513
3514 enable: function(form) {
3515 form = $(form);
3516 Form.getElements(form).invoke('enable');
3517 return form;
3518 },
3519
3520 findFirstElement: function(form) {
3521 var elements = $(form).getElements().findAll(function(element) {
3522 return 'hidden' != element.type && !element.disabled;
3523 });
3524 var firstByIndex = elements.findAll(function(element) {
3525 return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
3526 }).sortBy(function(element) { return element.tabIndex }).first();
3527
3528 return firstByIndex ? firstByIndex : elements.find(function(element) {
3529 return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
3530 });
3531 },
3532
3533 focusFirstElement: function(form) {
3534 form = $(form);
3535 form.findFirstElement().activate();
3536 return form;
3537 },
3538
3539 request: function(form, options) {
3540 form = $(form), options = Object.clone(options || { });
3541
3542 var params = options.parameters, action = form.readAttribute('action') || '';
3543 if (action.blank()) action = window.location.href;
3544 options.parameters = form.serialize(true);
3545
3546 if (params) {
3547 if (Object.isString(params)) params = params.toQueryParams();
3548 Object.extend(options.parameters, params);
3549 }
3550
3551 if (form.hasAttribute('method') && !options.method)
3552 options.method = form.method;
3553
3554 return new Ajax.Request(action, options);
3555 }
3556};
3557
3558/*--------------------------------------------------------------------------*/
3559
3560Form.Element = {
3561 focus: function(element) {
3562 $(element).focus();
3563 return element;
3564 },
3565
3566 select: function(element) {
3567 $(element).select();
3568 return element;
3569 }
3570};
3571
3572Form.Element.Methods = {
3573 serialize: function(element) {
3574 element = $(element);
3575 if (!element.disabled && element.name) {
3576 var value = element.getValue();
3577 if (value != undefined) {
3578 var pair = { };
3579 pair[element.name] = value;
3580 return Object.toQueryString(pair);
3581 }
3582 }
3583 return '';
3584 },
3585
3586 getValue: function(element) {
3587 element = $(element);
3588 var method = element.tagName.toLowerCase();
3589 return Form.Element.Serializers[method](element);
3590 },
3591
3592 setValue: function(element, value) {
3593 element = $(element);
3594 var method = element.tagName.toLowerCase();
3595 Form.Element.Serializers[method](element, value);
3596 return element;
3597 },
3598
3599 clear: function(element) {
3600 $(element).value = '';
3601 return element;
3602 },
3603
3604 present: function(element) {
3605 return $(element).value != '';
3606 },
3607
3608 activate: function(element) {
3609 element = $(element);
3610 try {
3611 element.focus();
3612 if (element.select && (element.tagName.toLowerCase() != 'input' ||
3613 !['button', 'reset', 'submit'].include(element.type)))
3614 element.select();
3615 } catch (e) { }
3616 return element;
3617 },
3618
3619 disable: function(element) {
3620 element = $(element);
3621 element.disabled = true;
3622 return element;
3623 },
3624
3625 enable: function(element) {
3626 element = $(element);
3627 element.disabled = false;
3628 return element;
3629 }
3630};
3631
3632/*--------------------------------------------------------------------------*/
3633
3634var Field = Form.Element;
3635var $F = Form.Element.Methods.getValue;
3636
3637/*--------------------------------------------------------------------------*/
3638
3639Form.Element.Serializers = {
3640 input: function(element, value) {
3641 switch (element.type.toLowerCase()) {
3642 case 'checkbox':
3643 case 'radio':
3644 return Form.Element.Serializers.inputSelector(element, value);
3645 default:
3646 return Form.Element.Serializers.textarea(element, value);
3647 }
3648 },
3649
3650 inputSelector: function(element, value) {
3651 if (Object.isUndefined(value)) return element.checked ? element.value : null;
3652 else element.checked = !!value;
3653 },
3654
3655 textarea: function(element, value) {
3656 if (Object.isUndefined(value)) return element.value;
3657 else element.value = value;
3658 },
3659
3660 select: function(element, value) {
3661 if (Object.isUndefined(value))
3662 return this[element.type == 'select-one' ?
3663 'selectOne' : 'selectMany'](element);
3664 else {
3665 var opt, currentValue, single = !Object.isArray(value);
3666 for (var i = 0, length = element.length; i < length; i++) {
3667 opt = element.options[i];
3668 currentValue = this.optionValue(opt);
3669 if (single) {
3670 if (currentValue == value) {
3671 opt.selected = true;
3672 return;
3673 }
3674 }
3675 else opt.selected = value.include(currentValue);
3676 }
3677 }
3678 },
3679
3680 selectOne: function(element) {
3681 var index = element.selectedIndex;
3682 return index >= 0 ? this.optionValue(element.options[index]) : null;
3683 },
3684
3685 selectMany: function(element) {
3686 var values, length = element.length;
3687 if (!length) return null;
3688
3689 for (var i = 0, values = []; i < length; i++) {
3690 var opt = element.options[i];
3691 if (opt.selected) values.push(this.optionValue(opt));
3692 }
3693 return values;
3694 },
3695
3696 optionValue: function(opt) {
3697 // extend element because hasAttribute may not be native
3698 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
3699 }
3700};
3701
3702/*--------------------------------------------------------------------------*/
3703
3704Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
3705 initialize: function($super, element, frequency, callback) {
3706 $super(callback, frequency);
3707 this.element = $(element);
3708 this.lastValue = this.getValue();
3709 },
3710
3711 execute: function() {
3712 var value = this.getValue();
3713 if (Object.isString(this.lastValue) && Object.isString(value) ?
3714 this.lastValue != value : String(this.lastValue) != String(value)) {
3715 this.callback(this.element, value);
3716 this.lastValue = value;
3717 }
3718 }
3719});
3720
3721Form.Element.Observer = Class.create(Abstract.TimedObserver, {
3722 getValue: function() {
3723 return Form.Element.getValue(this.element);
3724 }
3725});
3726
3727Form.Observer = Class.create(Abstract.TimedObserver, {
3728 getValue: function() {
3729 return Form.serialize(this.element);
3730 }
3731});
3732
3733/*--------------------------------------------------------------------------*/
3734
3735Abstract.EventObserver = Class.create({
3736 initialize: function(element, callback) {
3737 this.element = $(element);
3738 this.callback = callback;
3739
3740 this.lastValue = this.getValue();
3741 if (this.element.tagName.toLowerCase() == 'form')
3742 this.registerFormCallbacks();
3743 else
3744 this.registerCallback(this.element);
3745 },
3746
3747 onElementEvent: function() {
3748 var value = this.getValue();
3749 if (this.lastValue != value) {
3750 this.callback(this.element, value);
3751 this.lastValue = value;
3752 }
3753 },
3754
3755 registerFormCallbacks: function() {
3756 Form.getElements(this.element).each(this.registerCallback, this);
3757 },
3758
3759 registerCallback: function(element) {
3760 if (element.type) {
3761 switch (element.type.toLowerCase()) {
3762 case 'checkbox':
3763 case 'radio':
3764 Event.observe(element, 'click', this.onElementEvent.bind(this));
3765 break;
3766 default:
3767 Event.observe(element, 'change', this.onElementEvent.bind(this));
3768 break;
3769 }
3770 }
3771 }
3772});
3773
3774Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
3775 getValue: function() {
3776 return Form.Element.getValue(this.element);
3777 }
3778});
3779
3780Form.EventObserver = Class.create(Abstract.EventObserver, {
3781 getValue: function() {
3782 return Form.serialize(this.element);
3783 }
3784});
3785if (!window.Event) var Event = { };
3786
3787Object.extend(Event, {
3788 KEY_BACKSPACE: 8,
3789 KEY_TAB: 9,
3790 KEY_RETURN: 13,
3791 KEY_ESC: 27,
3792 KEY_LEFT: 37,
3793 KEY_UP: 38,
3794 KEY_RIGHT: 39,
3795 KEY_DOWN: 40,
3796 KEY_DELETE: 46,
3797 KEY_HOME: 36,
3798 KEY_END: 35,
3799 KEY_PAGEUP: 33,
3800 KEY_PAGEDOWN: 34,
3801 KEY_INSERT: 45,
3802
3803 cache: { },
3804
3805 relatedTarget: function(event) {
3806 var element;
3807 switch(event.type) {
3808 case 'mouseover': element = event.fromElement; break;
3809 case 'mouseout': element = event.toElement; break;
3810 default: return null;
3811 }
3812 return Element.extend(element);
3813 }
3814});
3815
3816Event.Methods = (function() {
3817 var isButton;
3818
3819 if (Prototype.Browser.IE) {
3820 var buttonMap = { 0: 1, 1: 4, 2: 2 };
3821 isButton = function(event, code) {
3822 return event.button == buttonMap[code];
3823 };
3824
3825 } else if (Prototype.Browser.WebKit) {
3826 isButton = function(event, code) {
3827 switch (code) {
3828 case 0: return event.which == 1 && !event.metaKey;
3829 case 1: return event.which == 1 && event.metaKey;
3830 default: return false;
3831 }
3832 };
3833
3834 } else {
3835 isButton = function(event, code) {
3836 return event.which ? (event.which === code + 1) : (event.button === code);
3837 };
3838 }
3839
3840 return {
3841 isLeftClick: function(event) { return isButton(event, 0) },
3842 isMiddleClick: function(event) { return isButton(event, 1) },
3843 isRightClick: function(event) { return isButton(event, 2) },
3844
3845 element: function(event) {
3846 event = Event.extend(event);
3847
3848 var node = event.target,
3849 type = event.type,
3850 currentTarget = event.currentTarget;
3851
3852 if (currentTarget && currentTarget.tagName) {
3853 // Firefox screws up the "click" event when moving between radio buttons
3854 // via arrow keys. It also screws up the "load" and "error" events on images,
3855 // reporting the document as the target instead of the original image.
3856 if (type === 'load' || type === 'error' ||
3857 (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
3858 && currentTarget.type === 'radio'))
3859 node = currentTarget;
3860 }
3861 if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
3862 return Element.extend(node);
3863 },
3864
3865 findElement: function(event, expression) {
3866 var element = Event.element(event);
3867 if (!expression) return element;
3868 var elements = [element].concat(element.ancestors());
3869 return Selector.findElement(elements, expression, 0);
3870 },
3871
3872 pointer: function(event) {
3873 var docElement = document.documentElement,
3874 body = document.body || { scrollLeft: 0, scrollTop: 0 };
3875 return {
3876 x: event.pageX || (event.clientX +
3877 (docElement.scrollLeft || body.scrollLeft) -
3878 (docElement.clientLeft || 0)),
3879 y: event.pageY || (event.clientY +
3880 (docElement.scrollTop || body.scrollTop) -
3881 (docElement.clientTop || 0))
3882 };
3883 },
3884
3885 pointerX: function(event) { return Event.pointer(event).x },
3886 pointerY: function(event) { return Event.pointer(event).y },
3887
3888 stop: function(event) {
3889 Event.extend(event);
3890 event.preventDefault();
3891 event.stopPropagation();
3892 event.stopped = true;
3893 }
3894 };
3895})();
3896
3897Event.extend = (function() {
3898 var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
3899 m[name] = Event.Methods[name].methodize();
3900 return m;
3901 });
3902
3903 if (Prototype.Browser.IE) {
3904 Object.extend(methods, {
3905 stopPropagation: function() { this.cancelBubble = true },
3906 preventDefault: function() { this.returnValue = false },
3907 inspect: function() { return "[object Event]" }
3908 });
3909
3910 return function(event) {
3911 if (!event) return false;
3912 if (event._extendedByPrototype) return event;
3913
3914 event._extendedByPrototype = Prototype.emptyFunction;
3915 var pointer = Event.pointer(event);
3916 Object.extend(event, {
3917 target: event.srcElement,
3918 relatedTarget: Event.relatedTarget(event),
3919 pageX: pointer.x,
3920 pageY: pointer.y
3921 });
3922 return Object.extend(event, methods);
3923 };
3924
3925 } else {
3926 Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
3927 Object.extend(Event.prototype, methods);
3928 return Prototype.K;
3929 }
3930})();
3931
3932Object.extend(Event, (function() {
3933 var cache = Event.cache;
3934
3935 function getEventID(element) {
3936 if (element._prototypeEventID) return element._prototypeEventID[0];
3937 arguments.callee.id = arguments.callee.id || 1;
3938 return element._prototypeEventID = [++arguments.callee.id];
3939 }
3940
3941 function getDOMEventName(eventName) {
3942 if (eventName && eventName.include(':')) return "dataavailable";
3943 return eventName;
3944 }
3945
3946 function getCacheForID(id) {
3947 return cache[id] = cache[id] || { };
3948 }
3949
3950 function getWrappersForEventName(id, eventName) {
3951 var c = getCacheForID(id);
3952 return c[eventName] = c[eventName] || [];
3953 }
3954
3955 function createWrapper(element, eventName, handler) {
3956 var id = getEventID(element);
3957 var c = getWrappersForEventName(id, eventName);
3958 if (c.pluck("handler").include(handler)) return false;
3959
3960 var wrapper = function(event) {
3961 if (!Event || !Event.extend ||
3962 (event.eventName && event.eventName != eventName))
3963 return false;
3964
3965 Event.extend(event);
3966 handler.call(element, event);
3967 };
3968
3969 wrapper.handler = handler;
3970 c.push(wrapper);
3971 return wrapper;
3972 }
3973
3974 function findWrapper(id, eventName, handler) {
3975 var c = getWrappersForEventName(id, eventName);
3976 return c.find(function(wrapper) { return wrapper.handler == handler });
3977 }
3978
3979 function destroyWrapper(id, eventName, handler) {
3980 var c = getCacheForID(id);
3981 if (!c[eventName]) return false;
3982 c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
3983 }
3984
3985 function destroyCache() {
3986 for (var id in cache)
3987 for (var eventName in cache[id])
3988 cache[id][eventName] = null;
3989 }
3990
3991
3992 // Internet Explorer needs to remove event handlers on page unload
3993 // in order to avoid memory leaks.
3994 if (window.attachEvent) {
3995 window.attachEvent("onunload", destroyCache);
3996 }
3997
3998 // Safari has a dummy event handler on page unload so that it won't
3999 // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
4000 // object when page is returned to via the back button using its bfcache.
4001 if (Prototype.Browser.WebKit) {
4002 window.addEventListener('unload', Prototype.emptyFunction, false);
4003 }
4004
4005 return {
4006 observe: function(element, eventName, handler) {
4007 element = $(element);
4008 var name = getDOMEventName(eventName);
4009
4010 var wrapper = createWrapper(element, eventName, handler);
4011 if (!wrapper) return element;
4012
4013 if (element.addEventListener) {
4014 element.addEventListener(name, wrapper, false);
4015 } else {
4016 element.attachEvent("on" + name, wrapper);
4017 }
4018
4019 return element;
4020 },
4021
4022 stopObserving: function(element, eventName, handler) {
4023 element = $(element);
4024 var id = getEventID(element), name = getDOMEventName(eventName);
4025
4026 if (!handler && eventName) {
4027 getWrappersForEventName(id, eventName).each(function(wrapper) {
4028 element.stopObserving(eventName, wrapper.handler);
4029 });
4030 return element;
4031
4032 } else if (!eventName) {
4033 Object.keys(getCacheForID(id)).each(function(eventName) {
4034 element.stopObserving(eventName);
4035 });
4036 return element;
4037 }
4038
4039 var wrapper = findWrapper(id, eventName, handler);
4040 if (!wrapper) return element;
4041
4042 if (element.removeEventListener) {
4043 element.removeEventListener(name, wrapper, false);
4044 } else {
4045 element.detachEvent("on" + name, wrapper);
4046 }
4047
4048 destroyWrapper(id, eventName, handler);
4049
4050 return element;
4051 },
4052
4053 fire: function(element, eventName, memo) {
4054 element = $(element);
4055 if (element == document && document.createEvent && !element.dispatchEvent)
4056 element = document.documentElement;
4057
4058 var event;
4059 if (document.createEvent) {
4060 event = document.createEvent("HTMLEvents");
4061 event.initEvent("dataavailable", true, true);
4062 } else {
4063 event = document.createEventObject();
4064 event.eventType = "ondataavailable";
4065 }
4066
4067 event.eventName = eventName;
4068 event.memo = memo || { };
4069
4070 if (document.createEvent) {
4071 element.dispatchEvent(event);
4072 } else {
4073 element.fireEvent(event.eventType, event);
4074 }
4075
4076 return Event.extend(event);
4077 }
4078 };
4079})());
4080
4081Object.extend(Event, Event.Methods);
4082
4083Element.addMethods({
4084 fire: Event.fire,
4085 observe: Event.observe,
4086 stopObserving: Event.stopObserving
4087});
4088
4089Object.extend(document, {
4090 fire: Element.Methods.fire.methodize(),
4091 observe: Element.Methods.observe.methodize(),
4092 stopObserving: Element.Methods.stopObserving.methodize(),
4093 loaded: false
4094});
4095
4096(function() {
4097 /* Support for the DOMContentLoaded event is based on work by Dan Webb,
4098 Matthias Miller, Dean Edwards and John Resig. */
4099
4100 var timer;
4101
4102 function fireContentLoadedEvent() {
4103 if (document.loaded) return;
4104 if (timer) window.clearInterval(timer);
4105 document.fire("dom:loaded");
4106 document.loaded = true;
4107 }
4108
4109 if (document.addEventListener) {
4110 if (Prototype.Browser.WebKit) {
4111 timer = window.setInterval(function() {
4112 if (/loaded|complete/.test(document.readyState))
4113 fireContentLoadedEvent();
4114 }, 0);
4115
4116 Event.observe(window, "load", fireContentLoadedEvent);
4117
4118 } else {
4119 document.addEventListener("DOMContentLoaded",
4120 fireContentLoadedEvent, false);
4121 }
4122
4123 } else {
4124 document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
4125 $("__onDOMContentLoaded").onreadystatechange = function() {
4126 if (this.readyState == "complete") {
4127 this.onreadystatechange = null;
4128 fireContentLoadedEvent();
4129 }
4130 };
4131 }
4132})();
4133/*------------------------------- DEPRECATED -------------------------------*/
4134
4135Hash.toQueryString = Object.toQueryString;
4136
4137var Toggle = { display: Element.toggle };
4138
4139Element.Methods.childOf = Element.Methods.descendantOf;
4140
4141var Insertion = {
4142 Before: function(element, content) {
4143 return Element.insert(element, {before:content});
4144 },
4145
4146 Top: function(element, content) {
4147 return Element.insert(element, {top:content});
4148 },
4149
4150 Bottom: function(element, content) {
4151 return Element.insert(element, {bottom:content});
4152 },
4153
4154 After: function(element, content) {
4155 return Element.insert(element, {after:content});
4156 }
4157};
4158
4159var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
4160
4161// This should be moved to script.aculo.us; notice the deprecated methods
4162// further below, that map to the newer Element methods.
4163var Position = {
4164 // set to true if needed, warning: firefox performance problems
4165 // NOT neeeded for page scrolling, only if draggable contained in
4166 // scrollable elements
4167 includeScrollOffsets: false,
4168
4169 // must be called before calling withinIncludingScrolloffset, every time the
4170 // page is scrolled
4171 prepare: function() {
4172 this.deltaX = window.pageXOffset
4173 || document.documentElement.scrollLeft
4174 || document.body.scrollLeft
4175 || 0;
4176 this.deltaY = window.pageYOffset
4177 || document.documentElement.scrollTop
4178 || document.body.scrollTop
4179 || 0;
4180 },
4181
4182 // caches x/y coordinate pair to use with overlap
4183 within: function(element, x, y) {
4184 if (this.includeScrollOffsets)
4185 return this.withinIncludingScrolloffsets(element, x, y);
4186 this.xcomp = x;
4187 this.ycomp = y;
4188 this.offset = Element.cumulativeOffset(element);
4189
4190 return (y >= this.offset[1] &&
4191 y < this.offset[1] + element.offsetHeight &&
4192 x >= this.offset[0] &&
4193 x < this.offset[0] + element.offsetWidth);
4194 },
4195
4196 withinIncludingScrolloffsets: function(element, x, y) {
4197 var offsetcache = Element.cumulativeScrollOffset(element);
4198
4199 this.xcomp = x + offsetcache[0] - this.deltaX;
4200 this.ycomp = y + offsetcache[1] - this.deltaY;
4201 this.offset = Element.cumulativeOffset(element);
4202
4203 return (this.ycomp >= this.offset[1] &&
4204 this.ycomp < this.offset[1] + element.offsetHeight &&
4205 this.xcomp >= this.offset[0] &&
4206 this.xcomp < this.offset[0] + element.offsetWidth);
4207 },
4208
4209 // within must be called directly before
4210 overlap: function(mode, element) {
4211 if (!mode) return 0;
4212 if (mode == 'vertical')
4213 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
4214 element.offsetHeight;
4215 if (mode == 'horizontal')
4216 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
4217 element.offsetWidth;
4218 },
4219
4220 // Deprecation layer -- use newer Element methods now (1.5.2).
4221
4222 cumulativeOffset: Element.Methods.cumulativeOffset,
4223
4224 positionedOffset: Element.Methods.positionedOffset,
4225
4226 absolutize: function(element) {
4227 Position.prepare();
4228 return Element.absolutize(element);
4229 },
4230
4231 relativize: function(element) {
4232 Position.prepare();
4233 return Element.relativize(element);
4234 },
4235
4236 realOffset: Element.Methods.cumulativeScrollOffset,
4237
4238 offsetParent: Element.Methods.getOffsetParent,
4239
4240 page: Element.Methods.viewportOffset,
4241
4242 clone: function(source, target, options) {
4243 options = options || { };
4244 return Element.clonePosition(target, source, options);
4245 }
4246};
4247
4248/*--------------------------------------------------------------------------*/
4249
4250if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
4251 function iter(name) {
4252 return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
4253 }
4254
4255 instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
4256 function(element, className) {
4257 className = className.toString().strip();
4258 var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
4259 return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
4260 } : function(element, className) {
4261 className = className.toString().strip();
4262 var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
4263 if (!classNames && !className) return elements;
4264
4265 var nodes = $(element).getElementsByTagName('*');
4266 className = ' ' + className + ' ';
4267
4268 for (var i = 0, child, cn; child = nodes[i]; i++) {
4269 if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
4270 (classNames && classNames.all(function(name) {
4271 return !name.toString().blank() && cn.include(' ' + name + ' ');
4272 }))))
4273 elements.push(Element.extend(child));
4274 }
4275 return elements;
4276 };
4277
4278 return function(className, parentElement) {
4279 return $(parentElement || document.body).getElementsByClassName(className);
4280 };
4281}(Element.Methods);
4282
4283/*--------------------------------------------------------------------------*/
4284
4285Element.ClassNames = Class.create();
4286Element.ClassNames.prototype = {
4287 initialize: function(element) {
4288 this.element = $(element);
4289 },
4290
4291 _each: function(iterator) {
4292 this.element.className.split(/\s+/).select(function(name) {
4293 return name.length > 0;
4294 })._each(iterator);
4295 },
4296
4297 set: function(className) {
4298 this.element.className = className;
4299 },
4300
4301 add: function(classNameToAdd) {
4302 if (this.include(classNameToAdd)) return;
4303 this.set($A(this).concat(classNameToAdd).join(' '));
4304 },
4305
4306 remove: function(classNameToRemove) {
4307 if (!this.include(classNameToRemove)) return;
4308 this.set($A(this).without(classNameToRemove).join(' '));
4309 },
4310
4311 toString: function() {
4312 return $A(this).join(' ');
4313 }
4314};
4315
4316Object.extend(Element.ClassNames.prototype, Enumerable);
4317
4318/*--------------------------------------------------------------------------*/
4319
4320Element.addMethods();
  
1# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2#
3# To ban all spiders from the entire site uncomment the next two lines:
4# User-Agent: *
5# Disallow: /
  
1/**** Default styling for Rapid ****/
2
3#ajax_progress {
4 color: gray;
5 float: right;
6 margin: 20px;
7 position: fixed;
8 background: white;
9 font-family: tahoma, sans-serif;
10 display: none;
11 z-index: 10;
12}
13
14#ajax_progress div {
15 margin: 10px;
16 padding: 3px;
17/* padding-top: -15px;*/
18}
19
20#ajax_progress img {
21 padding-left: 6px;
22 vertical-align: middle;
23}
24
25
26/* Scriptaculous Autocompleter ---*/
27
28div.completions_popup {
29 position:absolute;
30 width:250px;
31 background-color:white;
32 border:1px solid #888;
33 margin:0px;
34 padding:0px;
35 z-index:100;
36}
37div.completions_popup ul {
38 list-style-type:none;
39 margin:0px;
40 padding:0px;
41}
42div.completions_popup ul li.selected { background-color: #ffb;}
43div.completions_popup ul li {
44 list-style-type:none;
45 display:block;
46 margin:0;
47 padding:2px;
48 cursor:pointer;
49}
50
51
52.field_list { width:95%; }
53.field_list td { padding: 5px; vertical-align: middle; }
54.field_list td.field_label {
55 text-align: left; width: 1px; white-space: nowrap; vertical-align: top;
56 padding-top: 10px; padding-bottom: 10px;
57}
58.field_list input[type=text] { width: 100%; }
59.field_list input, .field_list textarea { margin: -2px 0 0 0; }
60.field_list textarea { width: 100%; margin: 0; }
61/*
62td span.in_place_textfield_bhv, td span.in_place_textarea_bhv, td span.in_place_html_textarea_bhv {
63 display: block; border: 1px solid #ddd;
64 padding: 4px; background: #fafafa;
65}
66*/
67table.login-table, table.login-table td {border: none;}
68.login_table td.field_label { vertical-align: middle; }
69/*table.login-table input {font-size: 16px; color: black;}*/
70
71input[type=text].wide { width: 100%; }
72textarea { height: 200px; }
73textarea.wide { width: 100%; }
74textarea.tall { height: 350px; }
75
76.field_list input.percentage {width: 25px; display: inline; margin-right: 5px; padding: 1px 3px;}
77
78/* rails error message */
79.error_messages {
80 font-family: "Lucida Grande", arial, sans-serif;
81 background: #9d0018;
82 border: 1px solid #7a0013;
83 padding: 20px;
84 color: white;
85 margin-bottom: 20px;
86}
87.error_messages h2 {
88 text-transform: none;
89 letter-spacing: normal;
90 color: white;
91}
92.error_messages li {
93 margin-left: 20px;
94}
  
1/******** Reset default browser CSS styles. *********/
2/* Based on Blueprint */
3
4html, body, div, span, applet, object, iframe,
5h1, h2, h3, h4, h5, h6, p, blockquote, pre,
6a, abbr, acronym, address, big, cite,
7del, dfn, font, img, ins, kbd, q, s, samp,
8small, strike, sub, sup, tt, var,
9dl, dt, dd, ol, ul, li,
10fieldset, form, label, legend,
11table, caption, tbody, tfoot, thead, tr, th, td {
12 margin: 0;
13 padding: 0;
14 border: 0;
15 outline: 0;
16 font-weight: inherit;
17 font-style: inherit;
18 font-size: 100%;
19 font-family: inherit;
20 vertical-align: baseline;
21}
22
23code {
24 margin: 0;
25 padding: 0;
26 border: 0;
27 outline: 0;
28 font-weight: inherit;
29 font-style: inherit;
30 font-size: 100%;
31 vertical-align: baseline;
32}
33
34em {
35 margin: 0;
36 padding: 0;
37 border: 0;
38 outline: 0;
39 font-weight: inherit;
40 font-size: 100%;
41 font-family: inherit;
42 vertical-align: baseline;
43}
44
45strong {
46 margin: 0;
47 padding: 0;
48 border: 0;
49 outline: 0;
50 font-style: inherit;
51 font-size: 100%;
52 font-family: inherit;
53 vertical-align: baseline;
54}
55
56/* Remember to define focus styles! */
57:focus {
58 outline: 0;
59}
60body {
61 line-height: 1;
62 color: black;
63 background: white;
64}
65
66/* Tables still need 'cellspacing="0"' in the markup. */
67table {
68 border-collapse: separate;
69 border-spacing: 0;
70}
71caption, th, td {
72 text-align: left;
73 font-weight: normal;
74}
75
76/* Remove possible quote marks (") from <q>, <blockquote>. */
77blockquote:before, blockquote:after,
78q:before, q:after {
79 content: "";
80}
81blockquote, q {
82 quotes: "" "";
83}
84
85body {
86 font-family: "Lucida Grande", Helvetica, Arial, Verdana, sans-serif;
87 font-size: 12px;
88}
89h1,h2,h3,h4,h5,h6 { font-weight: bold; }
90h1 { font-size: 36px;}
91h2 { font-size: 28px;}
92h3 { font-size: 18px;}
93h4 { font-size: 14px;}
94h5 { font-size: 12px;}
95h6 { font-size: 10px;}
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
4require 'commands/about'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/console'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/dbconsole'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/destroy'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/generate'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../../config/boot'
3require 'commands/performance/benchmarker'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../../config/boot'
3require 'commands/performance/profiler'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/plugin'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/runner'
  
1#!/usr/bin/env ruby
2require File.dirname(__FILE__) + '/../config/boot'
3require 'commands/server'
  
1# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
3# one:
4# column: value
5#
6# two:
7# column: value
  
1require File.dirname(__FILE__) + '/../test_helper'
2
3class FrontControllerTest < ActionController::TestCase
4 # Replace this with your real tests.
5 def test_truth
6 assert true
7 end
8end
  
1require File.dirname(__FILE__) + '/../test_helper'
2
3class UsersControllerTest < ActionController::TestCase
4 # Replace this with your real tests.
5 def test_truth
6 assert true
7 end
8end
  
1require 'test_helper'
2require 'performance_test_help'
3
4# Profiling results for each test method are written to tmp/performance.
5class BrowsingTest < ActionController::PerformanceTest
6 def test_homepage
7 get '/'
8 end
9end
  
1ENV["RAILS_ENV"] = "test"
2require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3require 'test_help'
4
5class ActiveSupport::TestCase
6 # Transactional fixtures accelerate your tests by wrapping each test method
7 # in a transaction that's rolled back on completion. This ensures that the
8 # test database remains unchanged so your fixtures don't have to be reloaded
9 # between every test method. Fewer database queries means faster tests.
10 #
11 # Read Mike Clark's excellent walkthrough at
12 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
13 #
14 # Every Active Record database supports transactions except MyISAM tables
15 # in MySQL. Turn off transactional fixtures in this case; however, if you
16 # don't care one way or the other, switching from MyISAM to InnoDB tables
17 # is recommended.
18 #
19 # The only drawback to using transactional fixtures is when you actually
20 # need to test transactions. Since your test is bracketed by a transaction,
21 # any transactions started in your code will be automatically rolled back.
22 self.use_transactional_fixtures = true
23
24 # Instantiated fixtures are slow, but give you @david where otherwise you
25 # would need people(:david). If you don't want to migrate your existing
26 # test cases which use the @david style and don't mind the speed hit (each
27 # instantiated fixtures translates to a database query per test method),
28 # then set this back to true.
29 self.use_instantiated_fixtures = false
30
31 # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
32 #
33 # Note: You'll currently still have to declare fixtures explicitly in integration tests
34 # -- they do not yet inherit this setting
35 fixtures :all
36
37 # Add more helper methods to be used by all tests here...
38end
  
1require File.dirname(__FILE__) + '/../test_helper'
2
3class UserTest < ActiveSupport::TestCase
4 # Replace this with your real tests.
5 def test_truth
6 assert true
7 end
8end