From 9867ca01ad65827d23c0bf6cc14d8a64137e927d Mon Sep 17 00:00:00 2001 From: bsag Date: Sun, 30 Oct 2005 12:21:50 +0000 Subject: [PATCH] Updated to work with Rails 0.14.1 (1.0 Release candidate) - fixes #145. The bug where project associations didn't seem to be immediately displayed when next actions were added from the Context page is magically fixed! Fixes #142. git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@156 a4c988fc-2ded-0310-b66e-134b36920a42 --- tracks/CHANGELOG | 105 ++ tracks/{README_RAILS => README} | 53 +- tracks/Rakefile | 236 +---- tracks/config/boot.rb | 17 + tracks/config/environment.rb | 92 +- tracks/config/environments/development.rb | 20 +- tracks/config/environments/production.rb | 21 +- tracks/config/environments/test.rb | 18 +- tracks/config/routes.rb | 8 +- tracks/doc/CHANGELOG | 2 +- tracks/doc/README_FOR_APP | 2 +- tracks/lib/tasks/migrate.rake | 4 + tracks/lib/tasks/setup_tracks.rake | 29 + tracks/public/.htaccess | 30 +- tracks/public/dispatch.fcgi | 2 +- tracks/public/dispatch.rb | 2 +- tracks/public/javascripts/controls.js | 592 ++++++++--- tracks/public/javascripts/dragdrop.js | 537 +++++----- tracks/public/javascripts/effects.js | 1051 +++++++++++++----- tracks/public/javascripts/prototype.js | 1120 ++++++++++++++++---- tracks/public/javascripts/scriptaculous.js | 47 + tracks/public/javascripts/slider.js | 258 +++++ tracks/public/robots.txt | 1 + tracks/script/breakpointer | 7 +- tracks/script/console | 26 +- tracks/script/destroy | 10 +- tracks/script/generate | 10 +- tracks/script/performance/benchmarker | 3 + tracks/script/performance/profiler | 3 + tracks/script/process/reaper | 3 + tracks/script/process/spawner | 3 + tracks/script/process/spinner | 3 + tracks/script/runner | 32 +- tracks/script/server | 52 +- tracks/test/test_helper.rb | 27 +- 35 files changed, 3007 insertions(+), 1419 deletions(-) rename tracks/{README_RAILS => README} (85%) create mode 100644 tracks/config/boot.rb create mode 100644 tracks/lib/tasks/migrate.rake create mode 100644 tracks/lib/tasks/setup_tracks.rake create mode 100644 tracks/public/javascripts/scriptaculous.js create mode 100644 tracks/public/javascripts/slider.js create mode 100644 tracks/public/robots.txt create mode 100755 tracks/script/performance/benchmarker create mode 100755 tracks/script/performance/profiler create mode 100755 tracks/script/process/reaper create mode 100755 tracks/script/process/spawner create mode 100755 tracks/script/process/spinner diff --git a/tracks/CHANGELOG b/tracks/CHANGELOG index cc0cabe9..269b908f 100644 --- a/tracks/CHANGELOG +++ b/tracks/CHANGELOG @@ -1,5 +1,110 @@ +*0.14.1* (October 19th, 2005) + +* Don't clean RAILS_ROOT on windows + +* Remove trailing '/' from RAILS_ROOT [Nicholas Seckar] + +* Upgraded to Active Record 1.12.1 and Action Pack 1.10.1 + + +*0.14.0* (October 16th, 2005) + +* Moved generator folder from RAILS_ROOT/generators to RAILS_ROOT/lib/generators [Tobias Luetke] + +* Fix rake dev and related commands [Nicholas Seckar] + +* The rails command tries to deduce your MySQL socket by running `mysql_config +--socket`. If it fails, default to /path/to/your/mysql.sock + +* Made the rails command use the application name for database names in the tailored database.yml file. Example: "rails ~/projects/blog" will use "blog_development" instead of "rails_development". [Florian Weber] + +* Added Rails framework freezing tasks: freeze_gems (freeze to current gems), freeze_edge (freeze to Rails SVN trunk), unfreeze_rails (float with newest gems on system) + +* Added update_javascripts task which will fetch all the latest js files from your current rails install. Use after updating rails. [Tobias Luetke] + +* Added cleaning of RAILS_ROOT to useless elements such as '../non-dot-dot/'. Provides cleaner backtraces and error messages. [Nicholas Seckar] + +* Made the instantiated/transactional fixtures settings be controlled through Rails::Initializer. Transactional and non-instantiated fixtures are default from now on. [Florian Weber] + +* Support using different database adapters for development and test with ActiveRecord::Base.schema_format = :ruby [Sam Stephenson] + +* Make webrick work with session(:off) + +* Add --version, -v option to the Rails command. Closes #1840. [stancell] + +* Update Prototype to V1.4.0_pre11, script.aculo.us to V1.5_rc3 [2504] and fix the rails generator to include the new .js files [Thomas Fuchs] + +* Make the generator skip a file if it already exists and is identical to the new file. + +* Add experimental plugin support #2335 + +* Made Rakefile aware of new .js files in script.aculo.us [Thomas Fuchs] + +* Make table_name and controller_name in generators honor AR::Base.pluralize_table_names. #1216 #2213 [kazuhiko@fdiary.net] + +* Clearly label functional and unit tests in rake stats output. #2297 [lasse.koskela@gmail.com] + +* Make the migration generator only check files ending in *.rb when calculating the next file name #2317 [Chad Fowler] + +* Added prevention of duplicate migrations from the generator #2240 [fbeausoleil@ftml.net] + +* Add db_schema_dump and db_schema_import rake tasks to work with the new ActiveRecord::SchemaDumper (for dumping a schema to and reading a schema from a ruby file). + +* Reformed all the config/environments/* files to conform to the new Rails::Configuration approach. Fully backwards compatible. + +* Added create_sessions_table, drop_sessions_table, and purge_sessions_table as rake tasks for databases that supports migrations (MySQL, PostgreSQL, SQLite) to get a table for use with CGI::Session::ActiveRecordStore + +* Added dump of schema version to the db_structure_dump task for databases that support migrations #1835 [Rick Olson] + +* Fixed script/profiler for Ruby 1.8.2 #1863 [Rick Olson] + +* Fixed clone_structure_to_test task for SQLite #1864 [jon@burningbush.us] + +* Added -m/--mime-types option to the WEBrick server, so you can specify a Apache-style mime.types file to load #2059 [ask@develooper.com] + +* Added -c/--svn option to the generator that'll add new files and remove destroyed files using svn add/revert/remove as appropriate #2064 [kevin.clark@gmail.com] + +* Added -c/--charset option to WEBrick server, so you can specify a default charset (which without changes is UTF-8) #2084 [wejn@box.cz] + +* Make the default stats task extendable by modifying the STATS_DIRECTORIES constant + +* Allow the selected environment to define RAILS_DEFAULT_LOGGER, and have Rails::Initializer use it if it exists. + +* Moved all the shared tasks from Rakefile into Rails, so that the Rakefile is empty and doesn't require updating. + +* Added Rails::Initializer and Rails::Configuration to abstract all of the common setup out of config/environment.rb (uses config/boot.rb to bootstrap the initializer and paths) + +* Fixed the scaffold generator to fail right away if the database isn't accessible instead of in mid-air #1169 [Chad Fowler] + +* Corrected project-local generator location in scripts.rb #2010 [Michael Schuerig] + +* Don't require the environment just to clear the logs #2093 [Scott Barron] + +* Make the default rakefile read *.rake files from config/tasks (for easy extension of the rakefile by e.g. generators) + +* Only load breakpoint in development mode and when BREAKPOINT_SERVER_PORT is defined. + +* Allow the --toggle-spin switch on process/reaper to be negated + +* Replace render_partial with render :partial in scaffold generator [Nicholas Seckar] + +* Added -w flag to ps in process/reaper #1934 [Scott Barron] + +* Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 [Duane Johnson] + +* Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help. + +* Added load_fixtures task to the Rakefile, which will load all the fixtures into the database for the current environment #1791 [Marcel Molina] + +* Added an empty robots.txt to public/, so that web servers asking for it won't trigger a dynamic call, like favicon.ico #1738 [michael@schubert] + +* Dropped the 'immediate close-down' of FCGI processes since it didn't work consistently and produced bad responses when it didn't. So now a TERM ensures exit after the next request (just as if the process is handling a request when it receives the signal). This means that you'll have to 'nudge' all FCGI processes with a request in order to ensure that they have all reloaded. This can be done by something like ./script/process/repear --nudge 'http://www.myapp.com' --instances 10, which will load the myapp site 10 times (and thus hit all of the 10 FCGI processes once, enough to shut down). + + *0.13.1* (11 July, 2005) +* Look for app-specific generators in RAILS_ROOT/generators rather than the clunky old RAILS_ROOT/script/generators. Nobody really uses this feature except for the unit tests, so it's a negligible-impact change. If you want to work with third-party generators, drop them in ~/.rails/generators or simply install gems. + * Fixed that each request with the WEBrick adapter would open a new database connection #1685 [Sam Stephenson] * Added support for SQL Server in the database rake tasks #1652 [ken.barker@gmail.com] Note: osql and scptxfr may need to be installed on your development environment. This involves getting the .exes and a .rll (scptxfr) from a production SQL Server (not developer level SQL Server). Add their location to your Environment PATH and you are all set. diff --git a/tracks/README_RAILS b/tracks/README similarity index 85% rename from tracks/README_RAILS rename to tracks/README index ca5f3737..7860e2bf 100644 --- a/tracks/README_RAILS +++ b/tracks/README @@ -14,7 +14,7 @@ In Rails, the model is handled by what's called a object-relational mapping layer entitled Active Record. This layer allows you to present the data from database rows as objects and embellish these data objects with business logic methods. You can read more about Active Record in -link:files/vendor/activerecord/README.html. +link:files/vendor/rails/activerecord/README.html. The controller and view is handled by the Action Pack, which handles both layers by its two parts: Action View and Action Controller. These two layers @@ -22,7 +22,7 @@ are bundled in a single package due to their heavy interdependence. This is unlike the relationship between the Active Record and Action Pack that is much more separate. Each of these packages can be used independently outside of Rails. You can read more about Action Pack in -link:files/vendor/actionpack/README.html. +link:files/vendor/rails/actionpack/README.html. == Requirements @@ -67,30 +67,30 @@ goes through FCGI (or mod_ruby) that requires restart to show changes. == Example for lighttpd conf (with FastCGI) -server.port = 8080 -server.bind = "127.0.0.1" -# server.event-handler = "freebsd-kqueue" # needed on OS X - -server.modules = ( "mod_rewrite", "mod_fastcgi" ) - -url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" ) -server.error-handler-404 = "/dispatch.fcgi" - -server.document-root = "/path/application/public" -server.errorlog = "/path/application/log/server.log" - -fastcgi.server = ( ".fcgi" => - ( "localhost" => - ( - "min-procs" => 1, - "max-procs" => 5, - "socket" => "/tmp/application.fcgi.socket", - "bin-path" => "/path/application/public/dispatch.fcgi", - "bin-environment" => ( "RAILS_ENV" => "development" ) - ) + server.port = 8080 + server.bind = "127.0.0.1" + # server.event-handler = "freebsd-kqueue" # needed on OS X + + server.modules = ( "mod_rewrite", "mod_fastcgi" ) + + url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" ) + server.error-handler-404 = "/dispatch.fcgi" + + server.document-root = "/path/application/public" + server.errorlog = "/path/application/log/server.log" + + fastcgi.server = ( ".fcgi" => + ( "localhost" => + ( + "min-procs" => 1, + "max-procs" => 5, + "socket" => "/tmp/application.fcgi.socket", + "bin-path" => "/path/application/public/dispatch.fcgi", + "bin-environment" => ( "RAILS_ENV" => "development" ) + ) + ) ) -) - + == Debugging Rails @@ -139,7 +139,8 @@ Finally, when you're ready to resume execution, you press CTRL-D You can interact with the domain model by starting the console through script/console. Here you'll have all parts of the application configured, just like it is when the application is running. You can inspect domain models, change values, and save to the -database. Start the script without arguments to see the options. +database. Start the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like console production. == Description of contents diff --git a/tracks/Rakefile b/tracks/Rakefile index 0fa2775d..cffd19f0 100644 --- a/tracks/Rakefile +++ b/tracks/Rakefile @@ -1,234 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + require 'rake' require 'rake/testtask' require 'rake/rdoctask' -require 'fileutils' -include FileUtils::Verbose -$VERBOSE = nil -TEST_CHANGES_SINCE = Time.now - 600 - -desc "Run all the tests on a fresh test database" -task :default => [ :test_units, :test_functional ] - - -desc 'Require application environment.' -task :environment do - unless defined? RAILS_ROOT - require File.dirname(__FILE__) + '/config/environment' - end -end - -desc "Generate API documentation, show coding stats" -task :doc => [ :appdoc, :stats ] - - -# Look up tests for recently modified sources. -def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) - FileList[source_pattern].map do |path| - if File.mtime(path) > touched_since - test = "#{test_path}/#{File.basename(path, '.rb')}_test.rb" - test if File.exists?(test) - end - end.compact -end - -desc 'Test recent changes.' -Rake::TestTask.new(:recent => [ :clone_structure_to_test ]) do |t| - since = TEST_CHANGES_SINCE - touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + - recent_tests('app/models/*.rb', 'test/unit', since) + - recent_tests('app/controllers/*.rb', 'test/functional', since) - - t.libs << 'test' - t.verbose = true - t.test_files = touched.uniq -end -task :test_recent => [ :clone_structure_to_test ] - -desc "Run the unit tests in test/unit" -Rake::TestTask.new("test_units") { |t| - t.libs << "test" - t.pattern = 'test/unit/**/*_test.rb' - t.verbose = true -} -task :test_units => [ :clone_structure_to_test ] - -desc "Run the functional tests in test/functional" -Rake::TestTask.new("test_functional") { |t| - t.libs << "test" - t.pattern = 'test/functional/**/*_test.rb' - t.verbose = true -} -task :test_functional => [ :clone_structure_to_test ] - -desc "Generate documentation for the application" -Rake::RDocTask.new("appdoc") { |rdoc| - rdoc.rdoc_dir = 'doc/app' - rdoc.title = "Tracks Documentation" - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('doc/README_FOR_APP') - rdoc.rdoc_files.include('app/**/*.rb') -} - -desc "Generate documentation for the Rails framework" -Rake::RDocTask.new("apidoc") { |rdoc| - rdoc.rdoc_dir = 'doc/api' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] - rdoc.title = "Rails Framework Documentation" - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') - rdoc.rdoc_files.include('vendor/rails/activerecord/README') - rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') - rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') - rdoc.rdoc_files.include('vendor/rails/actionpack/README') - rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionmailer/README') - rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb') - rdoc.rdoc_files.include('vendor/rails/activesupport/README') - rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') -} - -desc "Report code statistics (KLOCs, etc) from the application" -task :stats => [ :environment ] do - require 'code_statistics' - CodeStatistics.new( - ["Helpers", "app/helpers"], - ["Controllers", "app/controllers"], - ["APIs", "app/apis"], - ["Components", "components"], - ["Functionals", "test/functional"], - ["Models", "app/models"], - ["Units", "test/unit"] - ).to_s -end - -desc "Recreate the test databases from the development structure" -task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| - ActiveRecord::Base.connection.execute(table) - end - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` - when "sqlite", "sqlite3" - `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Dump the database structure to a SQL file" -task :db_structure_dump => :environment do - abcs = ActiveRecord::Base.configurations - case abcs[RAILS_ENV]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) - File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } - when "postgresql" - ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] - ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] - ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] - `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}` - when "sqlite", "sqlite3" - `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Empty the test database" -task :purge_test_database => :environment do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"]) - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` - `createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` - when "sqlite","sqlite3" - File.delete(abcs["test"]["dbfile"]) if File.exist?(abcs["test"]["dbfile"]) - when "sqlserver" - dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Clears all *.log files in log/" -task :clear_logs => :environment do - FileList["log/*.log"].each do |log_file| - f = File.open(log_file, "w") - f.close - end -end - -desc "Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x" -task :migrate => :environment do - ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil) -end - -desc "Initialises the installation, copy the *.tmpl files and directories to versions named without the .tmpl extension. It won't overwrite the files and directories if you've already copied them. You need to manually copy database.yml.tmpl -> database.yml and fill in the details before you run this task." -task :setup_tracks => :environment do - # Check the root directory for template files - FileList["*.tmpl"].each do |template_file| - f = File.basename(template_file) # with suffix - f_only = File.basename(template_file,".tmpl") # without suffix - if File.exists?(f_only) - puts f_only + " already exists" - else - cp_r(f, f_only) - puts f_only + " created" - end - end - # Check the config dir for template files - # We can't convert the database.yml.tmpl file, because database.yml needs - # to exist for rake to run tasks! - cd("config") do - FileList["settings.yml.tmpl"].each do |template_file| - f = File.basename(template_file) # with suffix - f_only = File.basename(template_file,".tmpl") # without suffix - if File.exists?(f_only) - puts f_only + " already exists" - else - cp(f, f_only) - puts f_only + " created" - end - end - end -end +require 'tasks/rails' \ No newline at end of file diff --git a/tracks/config/boot.rb b/tracks/config/boot.rb new file mode 100644 index 00000000..b829644d --- /dev/null +++ b/tracks/config/boot.rb @@ -0,0 +1,17 @@ +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath.to_s + end + RAILS_ROOT = root_path +end + +if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" +else + require 'rubygems' + require 'initializer' +end + +Rails::Initializer.run(:set_load_path) \ No newline at end of file diff --git a/tracks/config/environment.rb b/tracks/config/environment.rb index 0064f751..eaed51a4 100644 --- a/tracks/config/environment.rb +++ b/tracks/config/environment.rb @@ -1,68 +1,54 @@ -RAILS_ROOT = File.dirname(__FILE__) + "/../" -RAILS_ENV = ENV['RAILS_ENV'] || 'production' +# Be sure to restart your webserver when you modify this file. +# Uncomment below to force Rails into production mode +# (Use only when you can't set environment variables through your web/app server) +# ENV['RAILS_ENV'] = 'production' -# Mocks first. -ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') -# Then model subdirectories. -ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) -ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) +Rails::Initializer.run do |config| + # Skip frameworks you're not going to use + # config.frameworks -= [ :action_web_service, :action_mailer ] -# Followed by the standard includes. -ADDITIONAL_LOAD_PATHS.concat %w( - app - app/models - app/controllers - app/helpers - app/apis - config - components - lib - vendor -).map { |dir| "#{RAILS_ROOT}/#{dir}" } + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/app/services ) -# Prepend to $LOAD_PATH -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + # Use the database for sessions instead of the file system + # (create the session table with 'rake create_sessions_table') + # config.action_controller.session_store = :active_record_store -# Require Rails gems. -require 'rubygems' -require_gem 'activesupport' -require_gem 'activerecord' -require_gem 'actionpack' -require_gem 'actionmailer' -require_gem 'actionwebservice' -require_gem 'rails' + # Enable page/fragment caching by setting a file-based store + # (remember to create the caching directory and make it readable to the application) + # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" -# Environment-specific configuration. -require_dependency "environments/#{RAILS_ENV}" -ActiveRecord::Base.configurations = YAML::load(File.open("#{RAILS_ROOT}/config/database.yml")) -ActiveRecord::Base.establish_connection + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # Use Active Record's schema dumper instead of SQL when creating the test database + # (enables use of different database adapters for development and test environments) + # config.active_record.schema_format = :ruby -# Configure defaults if the included environment did not. -begin - RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log") -rescue StandardError - RAILS_DEFAULT_LOGGER = Logger.new(STDERR) - RAILS_DEFAULT_LOGGER.level = Logger::WARN - RAILS_DEFAULT_LOGGER.warn( - "Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0666. " + - "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." - ) + # See Rails::Configuration for more options end -[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER } -[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" } -ActionController::Routing::Routes.reload +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end -Controllers = Dependencies::LoadingModule.root( - File.expand_path(File.join(RAILS_ROOT, 'app', 'controllers')), - File.expand_path(File.join(RAILS_ROOT, 'components')) -) - -# Include your app's configuration here: +# Include your application configuration below def app_configurations YAML::load(File.open("#{RAILS_ROOT}/config/settings.yml")) -end +end \ No newline at end of file diff --git a/tracks/config/environments/development.rb b/tracks/config/environments/development.rb index 8b8bcb79..66f9c060 100644 --- a/tracks/config/environments/development.rb +++ b/tracks/config/environments/development.rb @@ -1,19 +1,21 @@ # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the webserver when you make code changes. +config.cache_classes = false # Log error messages when you accidentally call methods on nil. -require 'active_support/whiny_nil' +config.whiny_nils = true -# Reload code; show full error reports; disable caching. -Dependencies.mechanism = :load -ActionController::Base.consider_all_requests_local = true -ActionController::Base.perform_caching = false +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true -# The breakpoint server port that script/breakpointer connects to. -BREAKPOINT_SERVER_PORT = 42531 +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false # Unique cookies ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = "TrackDev" -ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:tmpdir] = "#{RAILS_ROOT}/tmp" - +ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:tmpdir] = "#{RAILS_ROOT}/tmp" \ No newline at end of file diff --git a/tracks/config/environments/production.rb b/tracks/config/environments/production.rb index 4fc13b05..f2b9ed68 100644 --- a/tracks/config/environments/production.rb +++ b/tracks/config/environments/production.rb @@ -1,8 +1,17 @@ # The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests, full error reports are disabled, -# and caching is turned on. +# Code is not reloaded between requests +config.cache_classes = true -# Don't reload code; don't show full error reports; enable caching. -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = false -ActionController::Base.perform_caching = true +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors if you bad email addresses should just be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/tracks/config/environments/test.rb b/tracks/config/environments/test.rb index fea6fdff..66b823dc 100644 --- a/tracks/config/environments/test.rb +++ b/tracks/config/environments/test.rb @@ -2,16 +2,22 @@ # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! +config.cache_classes = true # Log error messages when you accidentally call methods on nil. -require 'active_support/whiny_nil' +config.whiny_nils = true -# Don't reload code; show full error reports; disable caching. -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = true -ActionController::Base.perform_caching = false +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false # Tell ActionMailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. -ActionMailer::Base.delivery_method = :test +config.action_mailer.delivery_method = :test + +# Overwrite the default settings for fixtures in tests. See Fixtures +# for more details about these settings. +# config.transactional_fixtures = true +# config.instantiated_fixtures = false +# config.pre_loaded_fixtures = false \ No newline at end of file diff --git a/tracks/config/routes.rb b/tracks/config/routes.rb index 99ad6f66..46263ff7 100644 --- a/tracks/config/routes.rb +++ b/tracks/config/routes.rb @@ -6,10 +6,14 @@ ActionController::Routing::Routes.draw do |map| # map.connect 'products/:id', :controller => 'catalog', :action => 'view' # Keep in mind you can assign values other than :controller and :action + # You can have the root of your site routed by hooking up '' + # -- just remember to delete public/index.html. + # map.connect '', :controller => "welcome" + # Allow downloading Web Service WSDL as a file with an extension # instead of a file named 'wsdl' #map.connect ':controller/service.wsdl', :action => 'wsdl' - + # Index Route map.connect '', :controller => 'todo', :action => 'list' @@ -52,6 +56,6 @@ ActionController::Routing::Routes.draw do |map| map.connect 'add_item', :controller => 'todo', :action => 'add_item' # Install the default route as the lowest priority. - map.connect ':controller/:action' map.connect ':controller/:action/:id' + end diff --git a/tracks/doc/CHANGELOG b/tracks/doc/CHANGELOG index 5bb8581d..0ae124f6 100644 --- a/tracks/doc/CHANGELOG +++ b/tracks/doc/CHANGELOG @@ -2,7 +2,7 @@ * Homepage: http://www.rousette.org.uk/projects/ * Author: bsag (http://www.rousette.org.uk/) -* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke +* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke, John Leonard (for great installation tutorials on Windows XP) * Version: 1.03 * Copyright: (cc) 2004-2005 rousette.org.uk * License: GNU GPL diff --git a/tracks/doc/README_FOR_APP b/tracks/doc/README_FOR_APP index 879fc047..90727d79 100644 --- a/tracks/doc/README_FOR_APP +++ b/tracks/doc/README_FOR_APP @@ -2,7 +2,7 @@ * Homepage: http://www.rousette.org.uk/projects/ * Author: bsag (http://www.rousette.org.uk/) -* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke +* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke, John Leonard (for great installation tutorials on Windows XP) * Version: 1.031 * Copyright: (cc) 2004-2005 rousette.org.uk * License: GNU GPL diff --git a/tracks/lib/tasks/migrate.rake b/tracks/lib/tasks/migrate.rake new file mode 100644 index 00000000..22bb4d77 --- /dev/null +++ b/tracks/lib/tasks/migrate.rake @@ -0,0 +1,4 @@ +desc "Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x" +task :migrate => :environment do + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil) +end \ No newline at end of file diff --git a/tracks/lib/tasks/setup_tracks.rake b/tracks/lib/tasks/setup_tracks.rake new file mode 100644 index 00000000..8006ff88 --- /dev/null +++ b/tracks/lib/tasks/setup_tracks.rake @@ -0,0 +1,29 @@ +desc "Initialises the installation, copy the *.tmpl files and directories to versions named without the .tmpl extension. It won't overwrite the files and directories if you've already copied them. You need to manually copy database.yml.tmpl -> database.yml and fill in the details before you run this task." +task :setup_tracks => :environment do + # Check the root directory for template files + FileList["*.tmpl"].each do |template_file| + f = File.basename(template_file) # with suffix + f_only = File.basename(template_file,".tmpl") # without suffix + if File.exists?(f_only) + puts f_only + " already exists" + else + cp_r(f, f_only) + puts f_only + " created" + end + end + # Check the config dir for template files + # We can't convert the database.yml.tmpl file, because database.yml needs + # to exist for rake to run tasks! + cd("config") do + FileList["settings.yml.tmpl"].each do |template_file| + f = File.basename(template_file) # with suffix + f_only = File.basename(template_file,".tmpl") # without suffix + if File.exists?(f_only) + puts f_only + " already exists" + else + cp(f, f_only) + puts f_only + " created" + end + end + end +end \ No newline at end of file diff --git a/tracks/public/.htaccess b/tracks/public/.htaccess index 499477c5..d3c99834 100644 --- a/tracks/public/.htaccess +++ b/tracks/public/.htaccess @@ -3,12 +3,38 @@ AddHandler fastcgi-script .fcgi AddHandler cgi-script .cgi Options +FollowSymLinks +ExecCGI +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + # Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^(.*)$ /dispatch.cgi?$1 [QSA,L] +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] # In case Rails experiences terminal errors -ErrorDocument 500 /500.html \ No newline at end of file +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

Application error

Rails application failed to start properly" \ No newline at end of file diff --git a/tracks/public/dispatch.fcgi b/tracks/public/dispatch.fcgi index 664dbbbe..42b02205 100755 --- a/tracks/public/dispatch.fcgi +++ b/tracks/public/dispatch.fcgi @@ -21,4 +21,4 @@ require File.dirname(__FILE__) + "/../config/environment" require 'fcgi_handler' -RailsFCGIHandler.process! +RailsFCGIHandler.process! \ No newline at end of file diff --git a/tracks/public/dispatch.rb b/tracks/public/dispatch.rb index 3848806d..ce705d36 100755 --- a/tracks/public/dispatch.rb +++ b/tracks/public/dispatch.rb @@ -1,4 +1,4 @@ -#!/usr/bin/ruby1.8 +#!/usr/bin/env ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) diff --git a/tracks/public/javascripts/controls.js b/tracks/public/javascripts/controls.js index cece0a91..a7436bcf 100644 --- a/tracks/public/javascripts/controls.js +++ b/tracks/public/javascripts/controls.js @@ -1,41 +1,12 @@ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills // -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { - var children = $(element).childNodes; - var text = ""; - var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); - - for (var i = 0; i < children.length; i++) { - if(children[i].nodeType==3) { - text+=children[i].nodeValue; - } else { - if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) - text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); - } - } - - return text; -} +// See scriptaculous.js for full license. // Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This @@ -46,7 +17,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { // a getUpdatedChoices function that will be invoked every time // the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by -// invoking this.getEntry(), NOT by directly accessing +// invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized // autocompletion. Specific auto-completion logic (AJAX, etc) // belongs in getUpdatedChoices. @@ -57,7 +28,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with -// a token array, e.g. { tokens: new Array (',', '\n') } which +// a token array, e.g. { tokens: [',', '\n'] } which // enables autocompletion on multiple tokens. This is most // useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. @@ -65,79 +36,79 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { var Autocompleter = {} Autocompleter.Base = function() {}; Autocompleter.Base.prototype = { - base_initialize: function(element, update, options) { + baseInitialize: function(element, update, options) { this.element = $(element); this.update = $(update); - this.has_focus = false; + this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; - this.entry_count = 0; + this.entryCount = 0; if (this.setOptions) this.setOptions(options); else - this.options = {} - - this.options.tokens = this.options.tokens || new Array(); + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; - this.options.min_chars = this.options.min_chars || 1; + this.options.minChars = this.options.minChars || 1; this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; - var offsets = Position.cumulativeOffset(element); - update.style.left = offsets[0] + 'px'; - update.style.top = (offsets[1] + element.offsetHeight) + 'px'; - update.style.width = element.offsetWidth + 'px'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); } - new Effect.Appear(update,{duration:0.15}); + Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - - if(this.options.indicator) - this.indicator = $(this.options.indicator); if (typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); - + this.observer = null; + this.element.setAttribute('autocomplete','off'); + Element.hide(this.update); - + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); }, show: function() { - if(this.update.style.display=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, ''); this.iefix = $(this.update.id+'_iefix'); } - if(this.iefix) { - Position.clone(this.update, this.iefix); - this.iefix.style.zIndex = 1; - this.update.style.zIndex = 2; - Element.show(this.iefix); - } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + hide: function() { - if(this.update.style.display=='') this.options.onHide(this.element, this.update); + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); }, - + startIndicator: function() { - if(this.indicator) Element.show(this.indicator); + if(this.options.indicator) Element.show(this.options.indicator); }, - + stopIndicator: function() { - if(this.indicator) Element.hide(this.indicator); + if(this.options.indicator) Element.hide(this.options.indicator); }, onKeyPress: function(event) { @@ -145,22 +116,23 @@ Autocompleter.Base.prototype = { switch(event.keyCode) { case Event.KEY_TAB: case Event.KEY_RETURN: - this.select_entry(); + this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; + Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: - this.mark_previous(); + this.markPrevious(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); return; case Event.KEY_DOWN: - this.mark_next(); + this.markNext(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); return; @@ -168,15 +140,15 @@ Autocompleter.Base.prototype = { else if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) return; - + this.changed = true; - this.has_focus = true; - + this.hasFocus = true; + if(this.observer) clearTimeout(this.observer); this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, - + onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) @@ -190,92 +162,97 @@ Autocompleter.Base.prototype = { onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; - this.select_entry(); - Event.stop(event); + this.selectEntry(); + this.hide(); }, onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); - this.has_focus = false; + this.hasFocus = false; this.active = false; }, render: function() { - if(this.entry_count > 0) { - for (var i = 0; i < this.entry_count; i++) + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) this.index==i ? - Element.addClassName(this.get_entry(i),"selected") : - Element.removeClassName(this.get_entry(i),"selected"); - - if(this.has_focus) { - if(this.get_current_entry().scrollIntoView) - this.get_current_entry().scrollIntoView(false); + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { this.show(); this.active = true; } } else this.hide(); }, - mark_previous: function() { + markPrevious: function() { if(this.index > 0) this.index-- - else this.index = this.entry_count-1; + else this.index = this.entryCount-1; }, - mark_next: function() { - if(this.index < this.entry_count-1) this.index++ + markNext: function() { + if(this.index < this.entryCount-1) this.index++ else this.index = 0; }, - get_entry: function(index) { + getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, - get_current_entry: function() { - return this.get_entry(this.index); + getCurrentEntry: function() { + return this.getEntry(this.index); }, - select_entry: function() { + selectEntry: function() { this.active = false; - value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); - this.updateElement(value); - this.element.focus(); + this.updateElement(this.getCurrentEntry()); }, - updateElement: function(value) { - var last_token_pos = this.findLastToken(); - if (last_token_pos != -1) { - var new_value = this.element.value.substr(0, last_token_pos + 1); - var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); if (whitespace) - new_value += whitespace[0]; - this.element.value = new_value + value; + newValue += whitespace[0]; + this.element.value = newValue + value; } else { this.element.value = value; - } + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); }, - + updateChoices: function(choices) { - if(!this.changed && this.has_focus) { + if(!this.changed && this.hasFocus) { this.update.innerHTML = choices; Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update.firstChild); if(this.update.firstChild && this.update.firstChild.childNodes) { - this.entry_count = + this.entryCount = this.update.firstChild.childNodes.length; - for (var i = 0; i < this.entry_count; i++) { - entry = this.get_entry(i); + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } } else { - this.entry_count = 0; + this.entryCount = 0; } - + this.stopIndicator(); - + this.index = 0; this.render(); } @@ -288,7 +265,7 @@ Autocompleter.Base.prototype = { onObserverEvent: function() { this.changed = false; - if(this.getEntry().length>=this.options.min_chars) { + if(this.getToken().length>=this.options.minChars) { this.startIndicator(); this.getUpdatedChoices(); } else { @@ -297,58 +274,56 @@ Autocompleter.Base.prototype = { } }, - getEntry: function() { - var token_pos = this.findLastToken(); - if (token_pos != -1) - var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); else var ret = this.element.value; - + return /\n/.test(ret) ? '' : ret; }, findLastToken: function() { - var last_token_pos = -1; + var lastTokenPos = -1; for (var i=0; i last_token_pos) - last_token_pos = this_token_pos; + var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); + if (thisTokenPos > lastTokenPos) + lastTokenPos = thisTokenPos; } - return last_token_pos; + return lastTokenPos; } } Ajax.Autocompleter = Class.create(); -Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), -Object.extend(new Ajax.Base(), { +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { initialize: function(element, update, url, options) { - this.base_initialize(element, update, options); + this.baseInitialize(element, update, options); this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this) - this.options.method = 'post'; + this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; }, - + getUpdatedChoices: function() { - entry = encodeURIComponent(this.element.name) + '=' + - encodeURIComponent(this.getEntry()); - + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; - + if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; - + new Ajax.Request(this.url, this.options); }, - + onComplete: function(request) { this.updateChoices(request.responseText); } -})); +}); // The local array autocompleter. Used when you'd prefer to // inject an array of autocompletion options into the page, rather @@ -362,22 +337,22 @@ Object.extend(new Ajax.Base(), { // Extra local autocompletion options: // - choices - How many autocompletion choices to offer // -// - partial_search - If false, the autocompleter will match entered +// - partialSearch - If false, the autocompleter will match entered // text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to // search anywhere in the string, additionally set -// the option full_search to true (default: off). +// the option fullSearch to true (default: off). // -// - full_search - Search anywhere in autocomplete array strings. +// - fullSsearch - Search anywhere in autocomplete array strings. // -// - partial_chars - How many characters to enter before triggering -// a partial match (unlike min_chars, which defines +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines // how many characters are required to do any match // at all). Defaults to 2. // -// - ignore_case - Whether to ignore case when autocompleting. +// - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // // It's possible to pass in a custom function as the 'selector' @@ -388,7 +363,7 @@ Object.extend(new Ajax.Base(), { Autocompleter.Local = Class.create(); Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { initialize: function(element, update, array, options) { - this.base_initialize(element, update, options); + this.baseInitialize(element, update, options); this.options.array = array; }, @@ -399,41 +374,42 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { setOptions: function(options) { this.options = Object.extend({ choices: 10, - partial_search: true, - partial_chars: 2, - ignore_case: true, - full_search: false, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, selector: function(instance) { - var ret = new Array(); // Beginning matches - var partial = new Array(); // Inside matches - var entry = instance.getEntry(); + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); var count = 0; - + for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; - var found_pos = instance.options.ignore_case ? + var foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); - while (found_pos != -1) { - if (found_pos == 0 && elem.length != entry.length) { + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; - } else if (entry.length >= instance.options.partial_chars && - instance.options.partial_search && found_pos != -1) { - if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) { - partial.push("
  • " + elem.substr(0, found_pos) + "" + - elem.substr(found_pos, entry.length) + "" + elem.substr( - found_pos + entry.length) + "
  • "); + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); break; } } - found_pos = instance.options.ignore_case ? - elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : - elem.indexOf(entry, found_pos + 1); + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); } } @@ -444,3 +420,289 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { }, options || {}); } }); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okText: "ok", + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + ajaxOptions: {} + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function() { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.focus(this.editField); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + this.form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + this.form.appendChild(cancelLink); + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + new Ajax.Updater( + { + success: this.element, + // don't update on failure (this could be an option) + failure: null + }, + this.url, + Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions) + ); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; \ No newline at end of file diff --git a/tracks/public/javascripts/dragdrop.js b/tracks/public/javascripts/dragdrop.js index c0fd1d1e..5445d748 100644 --- a/tracks/public/javascripts/dragdrop.js +++ b/tracks/public/javascripts/dragdrop.js @@ -2,193 +2,79 @@ // // Element.Class part Copyright (c) 2005 by Rick Olson // -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Element.Class = { - // Element.toggleClass(element, className) toggles the class being on/off - // Element.toggleClass(element, className1, className2) toggles between both classes, - // defaulting to className1 if neither exist - toggle: function(element, className) { - if(Element.Class.has(element, className)) { - Element.Class.remove(element, className); - if(arguments.length == 3) Element.Class.add(element, arguments[2]); - } else { - Element.Class.add(element, className); - if(arguments.length == 3) Element.Class.remove(element, arguments[2]); - } - }, - - // gets space-delimited classnames of an element as an array - get: function(element) { - element = $(element); - return element.className.split(' '); - }, - - // functions adapted from original functions by Gavin Kistner - remove: function(element) { - element = $(element); - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); - element.className = element.className.replace(regEx, '') - } - }, - - add: function(element) { - element = $(element); - for(var i = 1; i < arguments.length; i++) { - Element.Class.remove(element, arguments[i]); - element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; - } - }, - - // returns true if all given classes exist in said element - has: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(!regEx.test(element.className)) return false; - } - return true; - }, - - // expects arrays of strings and/or strings as optional paramters - // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') - has_any: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - if((typeof arguments[i] == 'object') && - (arguments[i].constructor == Array)) { - for(var j = 0; j < arguments[i].length; j++) { - regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); - if(regEx.test(element.className)) return true; - } - } else { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(regEx.test(element.className)) return true; - } - } - return false; - }, - - childrenWith: function(element, className) { - var children = $(element).getElementsByTagName('*'); - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - if (Element.Class.has(children[i], className)) { - elements.push(children[i]); - break; - } - } - - return elements; - } -} +// See scriptaculous.js for full license. /*--------------------------------------------------------------------------*/ var Droppables = { - drops: false, - + drops: [], + remove: function(element) { - for(var i = 0; i < this.drops.length; i++) - if(this.drops[i].element == element) - this.drops.splice(i,1); + this.drops = this.drops.reject(function(d) { return d.element==element }); }, - + add: function(element) { - var element = $(element); + element = $(element); var options = Object.extend({ greedy: true, hoverclass: null }, arguments[1] || {}); - + // cache containers if(options.containment) { - options._containers = new Array(); + options._containers = []; var containment = options.containment; if((typeof containment == 'object') && (containment.constructor == Array)) { - for(var i=0; i0) window.scrollBy(0,0); - + Event.stop(event); } } @@ -381,7 +308,7 @@ Draggable.prototype = { /*--------------------------------------------------------------------------*/ -SortableObserver = Class.create(); +var SortableObserver = Class.create(); SortableObserver.prototype = { initialize: function(element, observer) { this.element = $(element); @@ -391,147 +318,199 @@ SortableObserver.prototype = { onStart: function() { this.lastValue = Sortable.serialize(this.element); }, - onEnd: function() { + onEnd: function() { + Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } } -Sortable = { +var Sortable = { sortables: new Array(), options: function(element){ - var element = $(element); - for(var i=0;i0.5) { - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } else { - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } - } + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty } // fix for gecko engine Element.cleanWhitespace(element); - + options.draggables = []; options.droppables = []; - - // make it so - var elements = element.childNodes; - for (var i = 0; i < elements.length; i++) - if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && - (!options.only || (Element.Class.has(elements[i], options.only)))) { - - // handles are per-draggable - var handle = options.handle ? - Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; - - options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); - - Droppables.add(elements[i], options_for_droppable); - options.droppables.push(elements[i]); - - } - + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.Class.childrenWith(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + // keep reference this.sortables.push(options); - + // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName==options.tag.toUpperCase() && + (!options.only || (Element.Class.has(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + dropon.appendChild(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.Class.add(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.top = offsets[1] + 'px'; + if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + Sortable._marker.style.left = offsets[0] + 'px'; + Element.show(Sortable._marker); + }, + serialize: function(element) { - var element = $(element); + element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, only: sortableOptions.only, - name: element.id + name: element.id, + format: sortableOptions.format || /^[^_]*_(.*)$/ }, arguments[1] || {}); - - var items = $(element).childNodes; - var queryComponents = new Array(); - - for(var i=0; i ' + + (typeof obj[property] == "string" ? + '"' + obj[property] + '"' : + obj[property])); + } + + return ("'" + obj + "' #" + typeof obj + + ": {" + info.join(", ") + "}"); +} -Effect = {} -Effect2 = Effect; // deprecated +/*--------------------------------------------------------------------------*/ + +var Builder = { + NODEMAP: { + AREA: 'map', + CAPTION: 'table', + COL: 'table', + COLGROUP: 'table', + LEGEND: 'fieldset', + OPTGROUP: 'select', + OPTION: 'select', + PARAM: 'object', + TBODY: 'table', + TD: 'table', + TFOOT: 'table', + TH: 'table', + THEAD: 'table', + TR: 'table' + }, + // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, + // due to a Firefox bug + node: function(elementName) { + elementName = elementName.toUpperCase(); + + // try innerHTML approach + var parentTag = this.NODEMAP[elementName] || 'div'; + var parentElement = document.createElement(parentTag); + parentElement.innerHTML = "<" + elementName + ">"; + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array)) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute=='className' ? 'class' : attribute) + + '="' + attributes[attribute].toString().escapeHTML() + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + } +} + +/* ------------- element ext -------------- */ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + color = "#"; + if(this.slice(0,4) == "rgb(") { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ""; + var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.style.fontSize = (percent/100) + "em"; + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, "opacity")) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + var els = element.style; + if (value == 1){ + els.opacity = '0.999999'; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + els.opacity = value; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + "alpha(opacity="+value*100+")"; + } +} + +Element.getInlineOpacity = function(element){ + element= $(element); + var op; + op = element.style.opacity; + if (typeof op != "undefined" && op != "") return op; + return ""; +} + +Element.setInlineOpacity = function(element, value){ + element= $(element); + var els = element.style; + els.opacity = value; +} + +/*--------------------------------------------------------------------------*/ + +Element.Class = { + // Element.toggleClass(element, className) toggles the class being on/off + // Element.toggleClass(element, className1, className2) toggles between both classes, + // defaulting to className1 if neither exist + toggle: function(element, className) { + if(Element.Class.has(element, className)) { + Element.Class.remove(element, className); + if(arguments.length == 3) Element.Class.add(element, arguments[2]); + } else { + Element.Class.add(element, className); + if(arguments.length == 3) Element.Class.remove(element, arguments[2]); + } + }, + + // gets space-delimited classnames of an element as an array + get: function(element) { + return $(element).className.split(' '); + }, + + // functions adapted from original functions by Gavin Kistner + remove: function(element) { + element = $(element); + var removeClasses = arguments; + $R(1,arguments.length-1).each( function(index) { + element.className = + element.className.split(' ').reject( + function(klass) { return (klass == removeClasses[index]) } ).join(' '); + }); + }, + + add: function(element) { + element = $(element); + for(var i = 1; i < arguments.length; i++) { + Element.Class.remove(element, arguments[i]); + element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; + } + }, + + // returns true if all given classes exist in said element + has: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } + return true; + }, + + // expects arrays of strings and/or strings as optional paramters + // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') + has_any: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } + return false; + }, + + childrenWith: function(element, className) { + var children = $(element).getElementsByTagName('*'); + var elements = new Array(); + + for (var i = 0; i < children.length; i++) + if (Element.Class.has(children[i], className)) + elements.push(children[i]); + + return elements; + } +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = "position:relative"; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1"; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == " " ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var speed = options.speed; + var delay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: delay + index * speed })); + }); + } +}; + +var Effect2 = Effect; // deprecated /* ------------- transitions ------------- */ @@ -40,7 +351,7 @@ Effect.Transitions.reverse = function(pos) { return 1-pos; } Effect.Transitions.flicker = function(pos) { - return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25); + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; } Effect.Transitions.wobble = function(pos) { return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; @@ -56,73 +367,111 @@ Effect.Transitions.full = function(pos) { return 1; } -/* ------------- element ext -------------- */ - -Element.makePositioned = function(element) { - element = $(element); - if(element.style.position == "") - element.style.position = "relative"; -} - -Element.makeClipping = function(element) { - element = $(element); - element._overflow = element.style.overflow || 'visible'; - if(element._overflow!='hidden') element.style.overflow = 'hidden'; -} - -Element.undoClipping = function(element) { - element = $(element); - if(element._overflow!='hidden') element.style.overflow = element._overflow; -} - /* ------------- core effects ------------- */ +Effect.Queue = { + effects: [], + interval: null, + add: function(effect) { + var timestamp = new Date().getTime(); + + switch(effect.options.queue) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + this.effects.push(effect); + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +} + Effect.Base = function() {}; Effect.Base.prototype = { + position: null, setOptions: function(options) { this.options = Object.extend({ transition: Effect.Transitions.sinoidal, duration: 1.0, // seconds - fps: 25.0, // max. 100fps + fps: 25.0, // max. 25fps due to Effect.Queue implementation sync: false, // true for combining from: 0.0, - to: 1.0 + to: 1.0, + delay: 0.0, + queue: 'parallel' }, options || {}); }, start: function(options) { this.setOptions(options || {}); this.currentFrame = 0; - this.startOn = new Date().getTime(); + this.state = 'idle'; + this.startOn = this.options.delay*1000; this.finishOn = this.startOn + (this.options.duration*1000); - if(this.options.beforeStart) this.options.beforeStart(this); - if(!this.options.sync) this.loop(); + this.event('beforeStart'); + if(!this.options.sync) Effect.Queue.add(this); }, - loop: function() { - var timePos = new Date().getTime(); - if(timePos >= this.finishOn) { - this.render(this.options.to); - if(this.finish) this.finish(); - if(this.options.afterFinish) this.options.afterFinish(this); - return; + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } } - var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); - var frame = Math.round(pos * this.options.fps * this.options.duration); - if(frame > this.currentFrame) { - this.render(pos); - this.currentFrame = frame; - } - this.timeout = setTimeout(this.loop.bind(this), 10); }, render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } if(this.options.transition) pos = this.options.transition(pos); pos *= (this.options.to-this.options.from); - pos += this.options.from; - if(this.options.beforeUpdate) this.options.beforeUpdate(this); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); if(this.update) this.update(pos); - if(this.options.afterUpdate) this.options.afterUpdate(this); + this.event('afterUpdate'); }, cancel: function() { - if(this.timeout) clearTimeout(this.timeout); + if(!this.options.sync) Effect.Queue.remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); } } @@ -133,35 +482,34 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { this.start(arguments[1]); }, update: function(position) { - for (var i = 0; i < this.effects.length; i++) - this.effects[i].render(position); + this.effects.invoke('render', position); }, finish: function(position) { - for (var i = 0; i < this.effects.length; i++) - if(this.effects[i].finish) this.effects[i].finish(position); + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); } }); -// Internet Explorer caveat: works only on elements the have -// a 'layout', meaning having a given width or height. -// There is no way to safely set this automatically. Effect.Opacity = Class.create(); Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); - options = Object.extend({ - from: 0.0, + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + this.element.style.zoom = 1; + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, to: 1.0 }, arguments[1] || {}); this.start(options); }, update: function(position) { - this.setOpacity(position); - }, - setOpacity: function(opacity) { - opacity = (opacity == 1) ? 0.99999 : opacity; - this.element.style.opacity = opacity; - this.element.style.filter = "alpha(opacity:"+opacity*100+")"; + Element.setOpacity(this.element, position); } }); @@ -169,16 +517,23 @@ Effect.MoveBy = Class.create(); Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { initialize: function(element, toTop, toLeft) { this.element = $(element); - this.originalTop = parseFloat(this.element.style.top || '0'); - this.originalLeft = parseFloat(this.element.style.left || '0'); this.toTop = toTop; this.toLeft = toLeft; - Element.makePositioned(this.element); this.start(arguments[3]); }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + + Element.makePositioned(this.element); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + }, update: function(position) { - topd = this.toTop * position + this.originalTop; - leftd = this.toLeft * position + this.originalLeft; + var topd = this.toTop * position + this.originalTop; + var leftd = this.toLeft * position + this.originalLeft; this.setPosition(topd, leftd); }, setPosition: function(topd, leftd) { @@ -191,55 +546,77 @@ Effect.Scale = Class.create(); Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { initialize: function(element, percent) { this.element = $(element) - options = Object.extend({ + var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or {} with provided values - scaleFrom: 100.0 + scaleFrom: 100.0, + scaleTo: percent }, arguments[2] || {}); - this.originalTop = this.element.offsetTop; - this.originalLeft = this.element.offsetLeft; - if(this.element.style.fontSize=="") this.sizeEm = 1.0; - if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - this.factor = (percent/100.0) - (options.scaleFrom/100.0); - if(options.scaleMode=='box') { - this.originalHeight = this.element.clientHeight; - this.originalWidth = this.element.clientWidth; - } else - if(options.scaleMode=='contents') { - this.originalHeight = this.element.scrollHeight; - this.originalWidth = this.element.scrollWidth; - } else { - this.originalHeight = options.scaleMode.originalHeight; - this.originalWidth = options.scaleMode.originalWidth; - } this.start(options); }, - - update: function(position) { - currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if(this.options.scaleContent && this.sizeEm) - this.element.style.fontSize = this.sizeEm*currentScale + "em"; - this.setDimensions( - this.originalWidth * currentScale, - this.originalHeight * currentScale); + setup: function() { + var effect = this; + + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + + effect.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + effect.originalStyle[k] = effect.element.style[k]; + }); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = Element.getStyle(this.element,'font-size') || "100%"; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + effect.fontSize = parseFloat(fontSize); + effect.fontSizeType = fontSizeType; + } + }); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.clientHeight, this.element.clientWidth]; + if(this.options.scaleMode=='content') + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; }, - - setDimensions: function(width, height) { - if(this.options.scaleX) this.element.style.width = width + 'px'; - if(this.options.scaleY) this.element.style.height = height + 'px'; + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType; + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) { + var effect = this; + ['top','left','width','height','fontSize'].each( function(k) { + effect.element.style[k] = effect.originalStyle[k]; + }); + } + }, + setDimensions: function(height, width) { + var els = this.element.style; + if(this.options.scaleX) els.width = width + 'px'; + if(this.options.scaleY) els.height = height + 'px'; if(this.options.scaleFromCenter) { - topd = (height - this.originalHeight)/2; - leftd = (width - this.originalWidth)/2; - if(this.element.style.position=='absolute') { - if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; - if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) els.top = this.originalTop-topd + "px"; + if(this.options.scaleX) els.left = this.originalLeft-leftd + "px"; } else { - if(this.options.scaleY) this.element.style.top = -topd + "px"; - if(this.options.scaleX) this.element.style.left = -leftd + "px"; + if(this.options.scaleY) els.top = -topd + "px"; + if(this.options.scaleX) els.left = -leftd + "px"; } } } @@ -249,44 +626,39 @@ Effect.Highlight = Class.create(); Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); - - // try to parse current background color as default for endcolor - // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format - var endcolor = "#ffffff"; - var current = this.element.style.backgroundColor; - if(current && current.slice(0,4) == "rgb(") { - endcolor = "#"; - var cols = current.slice(4,current.length-1).split(','); - var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } - var options = Object.extend({ - startcolor: "#ffff99", - endcolor: endcolor, - restorecolor: current + startcolor: "#ffff99" }, arguments[1] || {}); - - // init color calculations - this.colors_base = [ - parseInt(options.startcolor.slice(1,3),16), - parseInt(options.startcolor.slice(3,5),16), - parseInt(options.startcolor.slice(5),16) ]; - this.colors_delta = [ - parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], - parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], - parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; - this.start(options); }, + setup: function() { + // Disable background image during the effect + this.oldBgImage = this.element.style.backgroundImage; + this.element.style.backgroundImage = "none"; + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if (typeof this.options.restorecolor == "undefined") + this.options.restorecolor = this.element.style.backgroundColor; + // init color calculations + this.colors_base = [ + parseInt(this.options.startcolor.slice(1,3),16), + parseInt(this.options.startcolor.slice(3,5),16), + parseInt(this.options.startcolor.slice(5),16) ]; + this.colors_delta = [ + parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0], + parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1], + parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]]; + }, update: function(position) { - var colors = [ - Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), - Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), - Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; + var effect = this; var colors = $R(0,2).map( function(i){ + return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position)) + }); this.element.style.backgroundColor = "#" + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); }, finish: function() { this.element.style.backgroundColor = this.options.restorecolor; + this.element.style.backgroundImage = this.oldBgImage; } }); @@ -294,6 +666,9 @@ Effect.ScrollTo = Class.create(); Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { Position.prepare(); var offsets = Position.cumulativeOffset(this.element); var max = window.innerHeight ? @@ -302,8 +677,7 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight); this.scrollStart = Position.deltaY; - this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; - this.start(arguments[1] || {}); + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; }, update: function(position) { Position.prepare(); @@ -312,51 +686,61 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { } }); -/* ------------- prepackaged effects ------------- */ +/* ------------- combination effects ------------- */ Effect.Fade = function(element) { - options = Object.extend({ - from: 1.0, + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, to: 0.0, - afterFinish: function(effect) - { Element.hide(effect.element); - effect.setOpacity(1); } + afterFinishInternal: function(effect) + { if (effect.options.to == 0) { + Element.hide(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + } }, arguments[1] || {}); - new Effect.Opacity(element,options); + return new Effect.Opacity(element,options); } Effect.Appear = function(element) { - options = Object.extend({ - from: 0.0, + var options = Object.extend({ + from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0), to: 1.0, - beforeStart: function(effect) - { effect.setOpacity(0); - Element.show(effect.element); }, - afterUpdate: function(effect) - { Element.show(effect.element); } + beforeSetup: function(effect) + { Element.setOpacity(effect.element, effect.options.from); + Element.show(effect.element); } }, arguments[1] || {}); - new Effect.Opacity(element,options); + return new Effect.Opacity(element,options); } Effect.Puff = function(element) { - new Effect.Parallel( - [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), - new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 1.0, - afterUpdate: function(effect) + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + var oldPosition = element.style.position; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { effect.effects[0].element.style.position = 'absolute'; }, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - } + afterFinishInternal: function(effect) + { Element.hide(effect.effects[0].element); + effect.effects[0].element.style.position = oldPosition; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {}) ); } Effect.BlindUp = function(element) { + element = $(element); Element.makeClipping(element); - new Effect.Scale(element, 0, + return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, - afterFinish: function(effect) + restoreAfterFinish: true, + afterFinishInternal: function(effect) { Element.hide(effect.element); Element.undoClipping(effect.element); @@ -366,120 +750,179 @@ Effect.BlindUp = function(element) { } Effect.BlindDown = function(element) { - $(element).style.height = '0px'; - Element.makeClipping(element); - Element.show(element); - new Effect.Scale(element, 100, + element = $(element); + var oldHeight = element.style.height; + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, - scaleX: false, - scaleMode: 'contents', + scaleX: false, scaleFrom: 0, - afterFinish: function(effect) { + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + Element.makeClipping(effect.element); + effect.element.style.height = "0px"; + Element.show(effect.element); + }, + afterFinishInternal: function(effect) { Element.undoClipping(effect.element); + effect.element.style.height = oldHeight; } }, arguments[1] || {}) ); } Effect.SwitchOff = function(element) { - new Effect.Appear(element, - { duration: 0.4, - transition: Effect.Transitions.flicker, - afterFinish: function(effect) - { effect.element.style.overflow = 'hidden'; - new Effect.Scale(effect.element, 1, - { duration: 0.3, scaleFromCenter: true, - scaleX: false, scaleContent: false, - afterUpdate: function(effect) { - if(effect.element.style.position=="") - effect.element.style.position = 'relative'; }, - afterFinish: function(effect) { Element.hide(effect.element); } - } ) - } - } ); + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makePositioned(effect.element); + Element.makeClipping(effect.element); + }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + }) + } + }); } Effect.DropOut = function(element) { - new Effect.Parallel( + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Parallel( [ new Effect.MoveBy(element, 100, 0, { sync: true }), - new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 0.5, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - }); + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + Element.makePositioned(effect.effects[0].element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.effects[0].element); + Element.undoPositioned(effect.effects[0].element); + effect.effects[0].element.style.left = oldLeft; + effect.effects[0].element.style.top = oldTop; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {})); } Effect.Shake = function(element) { - new Effect.MoveBy(element, 0, 20, - { duration: 0.05, afterFinish: function(effect) { + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + return new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, -20, - { duration: 0.05, afterFinish: function(effect) { + { duration: 0.05, afterFinishInternal: function(effect) { + Element.undoPositioned(effect.element); + effect.element.style.left = oldLeft; + effect.element.style.top = oldTop; }}) }}) }}) }}) }}) }}); } Effect.SlideDown = function(element) { element = $(element); - element.style.height = '0px'; - Element.makeClipping(element); Element.cleanWhitespace(element); - Element.makePositioned(element.firstChild); - Element.show(element); - new Effect.Scale(element, 100, + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.firstChild.style.bottom; + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, - scaleMode: 'contents', scaleFrom: 0, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { Element.undoClipping(effect.element); } + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + element.style.height = '0'; + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } }, arguments[1] || {}) ); } Effect.SlideUp = function(element) { element = $(element); - Element.makeClipping(element); Element.cleanWhitespace(element); - Element.makePositioned(element.firstChild); - Element.show(element); - new Effect.Scale(element, 0, + var oldInnerBottom = element.firstChild.style.bottom; + return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { Element.hide(effect.element); - Element.undoClipping(effect.element); - } + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } }, arguments[1] || {}) ); } Effect.Squish = function(element) { - new Effect.Scale(element, 0, - { afterFinish: function(effect) { Element.hide(effect.element); } }); + // Bug in opera makes the TD containing this element expand for a instance after finish + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); } + }); } Effect.Grow = function(element) { element = $(element); var options = arguments[1] || {}; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - element.style.overflow = 'hidden'; - Element.show(element); + var elementDimensions = Element.getDimensions(element); + var originalWidth = elementDimensions.width; + var originalHeight = elementDimensions.height; + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); var direction = options.direction || 'center'; var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; @@ -517,18 +960,40 @@ Effect.Grow = function(element) { break; } - new Effect.MoveBy(element, initialMoveY, initialMoveX, { + return new Effect.MoveBy(element, initialMoveY, initialMoveX, { duration: 0.01, - beforeUpdate: function(effect) { $(element).style.height = '0px'; }, - afterFinish: function(effect) { + beforeSetup: function(effect) { + Element.hide(effect.element); + Element.makeClipping(effect.element); + Element.makePositioned(effect.element); + }, + afterFinishInternal: function(effect) { new Effect.Parallel( - [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), - new Effect.Scale(element, 100, { + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), + new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }), + new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, - sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], - options); } - }); + sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.style.height = 0; + Element.show(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = originalWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ) + } + }); } Effect.Shrink = function(element) { @@ -537,8 +1002,11 @@ Effect.Shrink = function(element) { var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; - element.style.overflow = 'hidden'; - Element.show(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); var direction = options.direction || 'center'; var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; @@ -569,44 +1037,65 @@ Effect.Shrink = function(element) { break; } - new Effect.Parallel( + return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), - new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], - options); + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + Element.makePositioned(effect.effects[0].element); + Element.makeClipping(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.hide(el); + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = oldWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ); } Effect.Pulsate = function(element) { + element = $(element); var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); var transition = options.transition || Effect.Transitions.sinoidal; var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; reverser.bind(transition); - new Effect.Opacity(element, - Object.extend(Object.extend({ duration: 3.0, - afterFinish: function(effect) { Element.show(effect.element); } + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); } }, options), {transition: reverser})); } Effect.Fold = function(element) { - $(element).style.overflow = 'hidden'; - new Effect.Scale(element, 5, Object.extend({ - scaleContent: false, - scaleTo: 100, - scaleX: false, - afterFinish: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, - scaleTo: 0, - scaleY: false, - afterFinish: function(effect) { Element.hide(effect.element) } }); - }}, arguments[1] || {})); -} - -// old: new Effect.ContentZoom(element, percent) -// new: Element.setContentZoom(element, percent) - -Element.setContentZoom = function(element, percent) { - var element = $(element); - element.style.fontSize = (percent/100) + "em"; - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + element = $(element); + var originalTop = element.style.top; + var originalLeft = element.style.left; + var originalWidth = element.style.width; + var originalHeight = element.style.height; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + effect.element.style.top = originalTop; + effect.element.style.left = originalLeft; + effect.element.style.width = originalWidth; + effect.element.style.height = originalHeight; + } }); + }}, arguments[1] || {})); } diff --git a/tracks/public/javascripts/prototype.js b/tracks/public/javascripts/prototype.js index 37635ccf..120f4cb9 100644 --- a/tracks/public/javascripts/prototype.js +++ b/tracks/public/javascripts/prototype.js @@ -1,8 +1,8 @@ -/* Prototype JavaScript framework, version 1.3.1 +/* Prototype JavaScript framework, version 1.4.0_rc0 * (c) 2005 Sam Stephenson * * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff - * against the source tree, available from the Prototype darcs repository. + * against the source tree, available from the Prototype darcs repository. * * Prototype is freely distributable under the terms of an MIT-style license. * @@ -11,13 +11,15 @@ /*--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.3.1', - emptyFunction: function() {} + Version: '1.4.0_rc0', + + emptyFunction: function() {}, + K: function(x) {return x} } var Class = { create: function() { - return function() { + return function() { this.initialize.apply(this, arguments); } } @@ -32,29 +34,47 @@ Object.extend = function(destination, source) { return destination; } -Object.prototype.extend = function(object) { - return Object.extend.apply(this, [this, object]); +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } } Function.prototype.bind = function(object) { var __method = this; return function() { - __method.apply(object, arguments); + return __method.apply(object, arguments); } } Function.prototype.bindAsEventListener = function(object) { var __method = this; return function(event) { - __method.call(object, event || window.event); + return __method.call(object, event || window.event); } } -Number.prototype.toColorPart = function() { - var digits = this.toString(16); - if (this < 16) return '0' + digits; - return digits; -} +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); var Try = { these: function() { @@ -90,10 +110,10 @@ PeriodicalExecuter.prototype = { onTimerEvent: function() { if (!this.currentlyExecuting) { - try { + try { this.currentlyExecuting = true; - this.callback(); - } finally { + this.callback(); + } finally { this.currentlyExecuting = false; } } @@ -110,7 +130,7 @@ function $() { if (typeof element == 'string') element = document.getElementById(element); - if (arguments.length == 1) + if (arguments.length == 1) return element; elements.push(element); @@ -118,36 +138,7 @@ function $() { return elements; } - -if (!Array.prototype.push) { - Array.prototype.push = function() { - var startLength = this.length; - for (var i = 0; i < arguments.length; i++) - this[startLength + i] = arguments[i]; - return this.length; - } -} - -if (!Function.prototype.apply) { - // Based on code from http://www.youngpup.net/ - Function.prototype.apply = function(object, parameters) { - var parameterStrings = new Array(); - if (!object) object = window; - if (!parameters) parameters = new Array(); - - for (var i = 0; i < parameters.length; i++) - parameterStrings[i] = 'parameters[' + i + ']'; - - object.__apply__ = this; - var result = eval('object.__apply__(' + - parameterStrings[i].join(', ') + ')'); - object.__apply__ = null; - - return result; - } -} - -String.prototype.extend({ +Object.extend(String.prototype, { stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, @@ -162,10 +153,369 @@ String.prototype.extend({ unescapeHTML: function() { var div = document.createElement('div'); div.innerHTML = this.stripTags(); - return div.childNodes[0].nodeValue; + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; } }); +String.prototype.parseQuery = String.prototype.toQueryParams; + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + if (!(result &= (iterator || Prototype.K)(value, index))) + throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result &= (iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return false; + }, + + reverse: function() { + var result = []; + for (var i = this.length; i > 0; i--) + result.push(this[i-1]); + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +var Range = Class.create(); +Object.extend(Range.prototype, Enumerable); +Object.extend(Range.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new Range(start, end, exclusive); +} + var Ajax = { getTransport: function() { return Try.these( @@ -173,9 +523,51 @@ var Ajax = { function() {return new ActiveXObject('Microsoft.XMLHTTP')}, function() {return new XMLHttpRequest()} ) || false; - } + }, + + activeRequestCount: 0 } +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { + } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + Ajax.Base = function() {}; Ajax.Base.prototype = { setOptions: function(options) { @@ -183,12 +575,13 @@ Ajax.Base.prototype = { method: 'post', asynchronous: true, parameters: '' - }.extend(options || {}); + } + Object.extend(this.options, options || {}); }, responseIsSuccess: function() { return this.transport.status == undefined - || this.transport.status == 0 + || this.transport.status == 0 || (this.transport.status >= 200 && this.transport.status < 300); }, @@ -198,10 +591,10 @@ Ajax.Base.prototype = { } Ajax.Request = Class.create(); -Ajax.Request.Events = +Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; -Ajax.Request.prototype = (new Ajax.Base()).extend({ +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); @@ -213,10 +606,13 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({ if (parameters.length > 0) parameters += '&_='; try { + this.url = url; if (this.options.method == 'get') - url += '?' + parameters; + this.url += '?' + parameters; - this.transport.open(this.options.method, url, + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, this.options.asynchronous); if (this.options.asynchronous) { @@ -234,17 +630,17 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({ }, setRequestHeaders: function() { - var requestHeaders = + var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', 'X-Prototype-Version', Prototype.Version]; if (this.options.method == 'post') { - requestHeaders.push('Content-type', + requestHeaders.push('Content-type', 'application/x-www-form-urlencoded'); /* Force "Connection: close" for Mozilla browsers to work around * a bug where XMLHttpReqeuest sends an incorrect Content-length - * header. See Mozilla Bugzilla #246651. + * header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType) requestHeaders.push('Connection', 'close'); @@ -263,15 +659,26 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({ this.respondToReadyState(this.transport.readyState); }, + evalJSON: function() { + try { + var json = this.transport.getResponseHeader('X-JSON'), object; + object = eval(json); + return object; + } catch (e) { + } + }, + respondToReadyState: function(readyState) { var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); if (event == 'Complete') (this.options['on' + this.transport.status] - || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] - || Prototype.emptyFunction)(this.transport); + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); - (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ if (event == 'Complete') @@ -282,7 +689,7 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({ Ajax.Updater = Class.create(); Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; -Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { this.containers = { success: container.success ? $(container.success) : $(container), @@ -294,9 +701,9 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; - this.options.onComplete = (function() { + this.options.onComplete = (function(transport, object) { this.updateContent(); - onComplete(this.transport); + onComplete(transport, object); }).bind(this); this.request(url); @@ -320,8 +727,7 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ if (this.responseIsSuccess()) { if (this.onComplete) - setTimeout((function() {this.onComplete( - this.transport)}).bind(this), 10); + setTimeout(this.onComplete.bind(this), 10); } if (this.options.evalScripts && scripts) { @@ -335,13 +741,13 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ }); Ajax.PeriodicalUpdater = Class.create(); -Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize: function(container, url, options) { this.setOptions(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); - this.decay = 1; + this.decay = (this.options.decay || 1); this.updater = {}; this.container = container; @@ -358,17 +764,17 @@ Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ stop: function() { this.updater.onComplete = undefined; clearTimeout(this.timer); - (this.onComplete || Ajax.emptyFunction).apply(this, arguments); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(request) { if (this.options.decay) { - this.decay = (request.responseText == this.lastText ? + this.decay = (request.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = request.responseText; } - this.timer = setTimeout(this.onTimerEvent.bind(this), + this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); }, @@ -376,23 +782,13 @@ Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); - -document.getElementsByClassName = function(className) { - var children = document.getElementsByTagName('*') || document.all; - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var classNames = child.className.split(' '); - for (var j = 0; j < classNames.length; j++) { - if (classNames[j] == className) { - elements.push(child); - break; - } - } - } - - return elements; +document.getElementsByClassName = function(className, parentElement) { + var children = (document.body || $(parentElement)).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (Element.hasClassName(child, className)) + elements.push(child); + return elements; + }); } /*--------------------------------------------------------------------------*/ @@ -402,11 +798,14 @@ if (!window.Element) { } Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + toggle: function() { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); - element.style.display = - (element.style.display == 'none' ? '' : 'none'); + Element[Element.visible(element) ? 'hide' : 'show'](element); } }, @@ -428,54 +827,131 @@ Object.extend(Element, { element = $(element); element.parentNode.removeChild(element); }, - + getHeight: function(element) { element = $(element); - return element.offsetHeight; + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); }, hasClassName: function(element, className) { - element = $(element); - if (!element) - return; - var a = element.className.split(' '); - for (var i = 0; i < a.length; i++) { - if (a[i] == className) - return true; - } - return false; + if (!(element = $(element))) return; + return Element.classNames(element).include(className); }, addClassName: function(element, className) { - element = $(element); - Element.removeClassName(element, className); - element.className += ' ' + className; + if (!(element = $(element))) return; + return Element.classNames(element).add(className); }, removeClassName: function(element, className) { - element = $(element); - if (!element) - return; - var newClassName = ''; - var a = element.className.split(' '); - for (var i = 0; i < a.length; i++) { - if (a[i] != className) { - if (i > 0) - newClassName += ' '; - newClassName += a[i]; - } - } - element.className = newClassName; + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); }, - + // removes whitespace-only text node children cleanWhitespace: function(element) { - var element = $(element); + element = $(element); for (var i = 0; i < element.childNodes.length; i++) { var node = element.childNodes[i]; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) Element.remove(node); } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; } }); @@ -492,67 +968,124 @@ Abstract.Insertion.prototype = { initialize: function(element, content) { this.element = $(element); this.content = content; - + if (this.adjacency && this.element.insertAdjacentHTML) { - this.element.insertAdjacentHTML(this.adjacency, this.content); + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } } else { this.range = this.element.ownerDocument.createRange(); if (this.initializeRange) this.initializeRange(); - this.fragment = this.range.createContextualFragment(this.content); - this.insertContent(); + this.insertContent([this.range.createContextualFragment(this.content)]); } + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); } } var Insertion = new Object(); Insertion.Before = Class.create(); -Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange: function() { this.range.setStartBefore(this.element); }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, this.element); + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); } }); Insertion.Top = Class.create(); -Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(true); }, - - insertContent: function() { - this.element.insertBefore(this.fragment, this.element.firstChild); + + insertContent: function(fragments) { + fragments.reverse().each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); } }); Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(this.element); }, - - insertContent: function() { - this.element.appendChild(this.fragment); + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); } }); Insertion.After = Class.create(); -Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange: function() { this.range.setStartAfter(this.element); }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, - this.element.nextSibling); + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); } }); +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + })); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); var Field = { clear: function() { for (var i = 0; i < arguments.length; i++) @@ -562,17 +1095,17 @@ var Field = { focus: function(element) { $(element).focus(); }, - + present: function() { for (var i = 0; i < arguments.length; i++) if ($(arguments[i]).value == '') return false; return true; }, - + select: function(element) { $(element).select(); }, - + activate: function(element) { $(element).focus(); $(element).select(); @@ -585,16 +1118,16 @@ var Form = { serialize: function(form) { var elements = Form.getElements($(form)); var queryComponents = new Array(); - + for (var i = 0; i < elements.length; i++) { var queryComponent = Form.Element.serialize(elements[i]); if (queryComponent) queryComponents.push(queryComponent); } - + return queryComponents.join('&'); }, - + getElements: function(form) { var form = $(form); var elements = new Array(); @@ -606,19 +1139,19 @@ var Form = { } return elements; }, - + getInputs: function(form, typeName, name) { var form = $(form); var inputs = form.getElementsByTagName('input'); - + if (!typeName && !name) return inputs; - + var matchingInputs = new Array(); for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || - (name && input.name != name)) + (name && input.name != name)) continue; matchingInputs.push(input); } @@ -665,18 +1198,18 @@ Form.Element = { var element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); - + if (parameter) - return encodeURIComponent(parameter[0]) + '=' + - encodeURIComponent(parameter[1]); + return encodeURIComponent(parameter[0]) + '=' + + encodeURIComponent(parameter[1]); }, - + getValue: function(element) { var element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); - - if (parameter) + + if (parameter) return parameter[1]; } } @@ -689,7 +1222,7 @@ Form.Element.Serializers = { case 'password': case 'text': return Form.Element.Serializers.textarea(element); - case 'checkbox': + case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); } @@ -706,17 +1239,30 @@ Form.Element.Serializers = { }, select: function(element) { - var value = ''; - if (element.type == 'select-one') { - var index = element.selectedIndex; - if (index >= 0) - value = element.options[index].value || element.options[index].text; - } else { - value = new Array(); - for (var i = 0; i < element.length; i++) { - var opt = element.options[i]; - if (opt.selected) - value.push(opt.value || opt.text); + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); } } return [element.name, value]; @@ -735,15 +1281,15 @@ Abstract.TimedObserver.prototype = { this.frequency = frequency; this.element = $(element); this.callback = callback; - + this.lastValue = this.getValue(); this.registerCallback(); }, - + registerCallback: function() { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, - + onTimerEvent: function() { var value = this.getValue(); if (this.lastValue != value) { @@ -754,14 +1300,14 @@ Abstract.TimedObserver.prototype = { } Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.serialize(this.element); } @@ -774,14 +1320,14 @@ Abstract.EventObserver.prototype = { initialize: function(element, callback) { this.element = $(element); this.callback = callback; - + this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, - + onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { @@ -789,22 +1335,22 @@ Abstract.EventObserver.prototype = { this.lastValue = value; } }, - + registerFormCallbacks: function() { var elements = Form.getElements(this.element); for (var i = 0; i < elements.length; i++) this.registerCallback(elements[i]); }, - + registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { - case 'checkbox': + case 'checkbox': case 'radio': element.target = this; element.prev_onclick = element.onclick || Prototype.emptyFunction; element.onclick = function() { - this.prev_onclick(); + this.prev_onclick(); this.target.onElementEvent(); } break; @@ -816,30 +1362,28 @@ Abstract.EventObserver.prototype = { element.target = this; element.prev_onchange = element.onchange || Prototype.emptyFunction; element.onchange = function() { - this.prev_onchange(); + this.prev_onchange(); this.target.onElementEvent(); } break; } - } + } } } Form.Element.EventObserver = Class.create(); -Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(); -Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.serialize(this.element); } }); - - if (!window.Event) { var Event = new Object(); } @@ -865,21 +1409,22 @@ Object.extend(Event, { }, pointerX: function(event) { - return event.pageX || (event.clientX + + return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); }, pointerY: function(event) { - return event.pageY || (event.clientY + + return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); }, stop: function(event) { - if (event.preventDefault) { - event.preventDefault(); - event.stopPropagation(); + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); } else { event.returnValue = false; + event.cancelBubble = true; } }, @@ -894,7 +1439,7 @@ Object.extend(Event, { }, observers: false, - + _observeAndCache: function(element, name, observer, useCapture) { if (!this.observers) this.observers = []; if (element.addEventListener) { @@ -905,7 +1450,7 @@ Object.extend(Event, { element.attachEvent('on' + name, observer); } }, - + unloadCache: function() { if (!Event.observers) return; for (var i = 0; i < Event.observers.length; i++) { @@ -918,24 +1463,24 @@ Object.extend(Event, { observe: function(element, name, observer, useCapture) { var element = $(element); useCapture = useCapture || false; - + if (name == 'keypress' && - ((navigator.appVersion.indexOf('AppleWebKit') > 0) + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) name = 'keydown'; - + this._observeAndCache(element, name, observer, useCapture); }, stopObserving: function(element, name, observer, useCapture) { var element = $(element); useCapture = useCapture || false; - + if (name == 'keypress' && - ((navigator.appVersion.indexOf('AppleWebKit') > 0) + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown'; - + if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { @@ -946,24 +1491,22 @@ Object.extend(Event, { /* prevent memory leaks in IE */ Event.observe(window, 'unload', Event.unloadCache, false); - var Position = { - // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in // scrollable elements - includeScrollOffsets: false, + includeScrollOffsets: false, // must be called before calling withinIncludingScrolloffset, every time the // page is scrolled prepare: function() { - this.deltaX = window.pageXOffset - || document.documentElement.scrollLeft - || document.body.scrollLeft + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft || 0; - this.deltaY = window.pageYOffset - || document.documentElement.scrollTop - || document.body.scrollTop + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop || 0; }, @@ -971,7 +1514,7 @@ var Position = { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; + valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return [valueL, valueT]; @@ -987,6 +1530,31 @@ var Position = { return [valueL, valueT]; }, + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + // caches x/y coordinate pair to use with overlap within: function(element, x, y) { if (this.includeScrollOffsets) @@ -997,7 +1565,7 @@ var Position = { return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && - x >= this.offset[0] && + x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, @@ -1010,18 +1578,18 @@ var Position = { return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && - this.xcomp >= this.offset[0] && + this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, // within must be called directly before - overlap: function(mode, element) { - if (!mode) return 0; - if (mode == 'vertical') - return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') - return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, @@ -1034,5 +1602,123 @@ var Position = { target.style.left = offsets[0] + 'px'; target.style.width = source.offsetWidth + 'px'; target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; } } + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} \ No newline at end of file diff --git a/tracks/public/javascripts/scriptaculous.js b/tracks/public/javascripts/scriptaculous.js new file mode 100644 index 00000000..cd0e3417 --- /dev/null +++ b/tracks/public/javascripts/scriptaculous.js @@ -0,0 +1,47 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var Scriptaculous = { + Version: '1.5_rc3', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + load: function() { + if((typeof Prototype=='undefined') || + parseFloat(Prototype.Version.split(".")[0] + "." + + Prototype.Version.split(".")[1]) < 1.4) + throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0"); + var scriptTags = document.getElementsByTagName("script"); + for(var i=0;i this.maximum) sliderValue = this.maximum; + if(sliderValue < this.minimum) sliderValue = this.minimum; + var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment; + + if(this.isVertical()){ + this.setCurrentTop(offsetDiff + this.currentTop()); + } else { + this.setCurrentLeft(offsetDiff + this.currentLeft()); + } + this.value = sliderValue; + this.updateFinished(); + }, + minimumOffset: function(){ + return(this.isVertical() ? + this.trackTop() + this.alignY : + this.trackLeft() + this.alignX); + }, + maximumOffset: function(){ + return(this.isVertical() ? + this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment : + this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment); + }, + isVertical: function(){ + return (this.axis == 'vertical'); + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.handle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + } + Event.stop(event); + } + }, + update: function(event) { + if(this.active) { + if(!this.dragging) { + var style = this.handle.style; + this.dragging = true; + if(style.position=="") style.position = "relative"; + style.zIndex = this.options.zindex; + } + this.draw(event); + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); + } + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.handle); + + offsets[0] -= this.currentLeft(); + offsets[1] -= this.currentTop(); + + // Adjust for the pointer's position on the handle + pointer[0] -= this.offsetX; + pointer[1] -= this.offsetY; + var style = this.handle.style; + + if(this.isVertical()){ + if(pointer[1] > this.maximumOffset()) + pointer[1] = this.maximumOffset(); + if(pointer[1] < this.minimumOffset()) + pointer[1] = this.minimumOffset(); + + // Increment by values + if(this.values){ + this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum); + pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment; + } else { + this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum; + } + style.top = pointer[1] - offsets[1] + "px"; + } else { + if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset(); + if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset(); + // Increment by values + if(this.values){ + this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum); + pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment; + } else { + this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum; + } + style.left = (pointer[0] - offsets[0]) + "px"; + } + if(this.options.onSlide) this.options.onSlide(this.value); + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.handle.style.zIndex = this.originalZ; + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + this.updateFinished(); + }, + updateFinished: function() { + if(this.options.onChange) this.options.onChange(this.value); + }, + keyPress: function(event) { + if(this.active && !this.disabled) { + switch(event.keyCode) { + case Event.KEY_ESC: + this.finishDrag(event, false); + Event.stop(event); + break; + } + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + } + } +} diff --git a/tracks/public/robots.txt b/tracks/public/robots.txt new file mode 100644 index 00000000..4ab9e89f --- /dev/null +++ b/tracks/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/tracks/script/breakpointer b/tracks/script/breakpointer index 6375616b..5ab881b8 100755 --- a/tracks/script/breakpointer +++ b/tracks/script/breakpointer @@ -1,4 +1,3 @@ -#!/usr/bin/env ruby -require 'rubygems' -require_gem 'rails' -require 'breakpoint_client' +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/tracks/script/console b/tracks/script/console index e23abda3..1a16c389 100755 --- a/tracks/script/console +++ b/tracks/script/console @@ -1,23 +1,3 @@ -#!/usr/bin/env ruby -irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' - -require 'optparse' -options = { :sandbox => false, :irb => irb } -OptionParser.new do |opt| - opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| } - opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |options[:irb]| } - opt.parse!(ARGV) -end - -libs = " -r irb/completion" -libs << " -r #{File.dirname(__FILE__)}/../config/environment" -libs << " -r console_sandbox" if options[:sandbox] - -ENV['RAILS_ENV'] = ARGV.first || 'development' -if options[:sandbox] - puts "Loading #{ENV['RAILS_ENV']} environment in sandbox." - puts "Any modifications you make will be rolled back on exit." -else - puts "Loading #{ENV['RAILS_ENV']} environment." -end -exec "#{options[:irb]} #{libs} --prompt-mode simple" +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/tracks/script/destroy b/tracks/script/destroy index 46cc786e..cabbfb35 100755 --- a/tracks/script/destroy +++ b/tracks/script/destroy @@ -1,7 +1,3 @@ -#!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'rails_generator' -require 'rails_generator/scripts/destroy' - -ARGV.shift if ['--help', '-h'].include?(ARGV[0]) -Rails::Generator::Scripts::Destroy.new.run(ARGV) +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/tracks/script/generate b/tracks/script/generate index 26447804..3fbf0b75 100755 --- a/tracks/script/generate +++ b/tracks/script/generate @@ -1,7 +1,3 @@ -#!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'rails_generator' -require 'rails_generator/scripts/generate' - -ARGV.shift if ['--help', '-h'].include?(ARGV[0]) -Rails::Generator::Scripts::Generate.new.run(ARGV) +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/tracks/script/performance/benchmarker b/tracks/script/performance/benchmarker new file mode 100755 index 00000000..51ac7b65 --- /dev/null +++ b/tracks/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/tracks/script/performance/profiler b/tracks/script/performance/profiler new file mode 100755 index 00000000..4b16ed67 --- /dev/null +++ b/tracks/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/tracks/script/process/reaper b/tracks/script/process/reaper new file mode 100755 index 00000000..5235cf05 --- /dev/null +++ b/tracks/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/tracks/script/process/spawner b/tracks/script/process/spawner new file mode 100755 index 00000000..1a710812 --- /dev/null +++ b/tracks/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/tracks/script/process/spinner b/tracks/script/process/spinner new file mode 100755 index 00000000..51650254 --- /dev/null +++ b/tracks/script/process/spinner @@ -0,0 +1,3 @@ +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spinner' diff --git a/tracks/script/runner b/tracks/script/runner index 009fe79c..0b130def 100755 --- a/tracks/script/runner +++ b/tracks/script/runner @@ -1,29 +1,3 @@ -#!/usr/bin/env ruby -require 'optparse' - -options = { :environment => "development" } - -ARGV.options do |opts| - script_name = File.basename($0) - opts.banner = "Usage: runner 'puts Person.find(1).name' [options]" - - opts.separator "" - - opts.on("-e", "--environment=name", String, - "Specifies the environment for the runner to operate under (test/development/production).", - "Default: development") { |options[:environment]| } - - opts.separator "" - - opts.on("-h", "--help", - "Show this help message.") { puts opts; exit } - - opts.parse! -end - -ENV["RAILS_ENV"] = options[:environment] - -#!/usr/local/bin/ruby - -require File.dirname(__FILE__) + '/../config/environment' -eval(ARGV.first) \ No newline at end of file +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/tracks/script/server b/tracks/script/server index b7e3c801..2bfbdad8 100755 --- a/tracks/script/server +++ b/tracks/script/server @@ -1,49 +1,3 @@ -#!/usr/bin/env ruby - -require 'webrick' -require 'optparse' - -OPTIONS = { - :port => 3000, - :ip => "0.0.0.0", - :environment => "development", - :server_root => File.expand_path(File.dirname(__FILE__) + "/../public/"), - :server_type => WEBrick::SimpleServer -} - -ARGV.options do |opts| - script_name = File.basename($0) - opts.banner = "Usage: ruby #{script_name} [options]" - - opts.separator "" - - opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", - "Default: 3000") { |OPTIONS[:port]| } - opts.on("-b", "--binding=ip", String, - "Binds Rails to the specified ip.", - "Default: 0.0.0.0") { |OPTIONS[:ip]| } - opts.on("-e", "--environment=name", String, - "Specifies the environment to run this server under (test/development/production).", - "Default: development") { |OPTIONS[:environment]| } - opts.on("-d", "--daemon", - "Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)." - ) { OPTIONS[:server_type] = WEBrick::Daemon } - - opts.separator "" - - opts.on("-h", "--help", - "Show this help message.") { puts opts; exit } - - opts.parse! -end - -ENV["RAILS_ENV"] = OPTIONS[:environment] -require File.dirname(__FILE__) + "/../config/environment" -require 'webrick_server' - -OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT) - -puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" -puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer -DispatchServlet.dispatch(OPTIONS) +#!/usr/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/tracks/test/test_helper.rb b/tracks/test/test_helper.rb index cea2c8d1..e20b90cc 100644 --- a/tracks/test/test_helper.rb +++ b/tracks/test/test_helper.rb @@ -1,27 +1,15 @@ ENV["RAILS_ENV"] = "test" - -# Expand the path to environment so that Ruby does not load it multiple times -# File.expand_path can be removed if Ruby 1.9 is in use. require File.expand_path(File.dirname(__FILE__) + "/../config/environment") -require 'application' - -require 'test/unit' -require 'active_record/fixtures' -require 'action_controller/test_process' -require 'action_web_service/test_invoke' -require 'breakpoint' - -Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" +require 'test_help' class Test::Unit::TestCase - # Turn these on to use transactional fixtures with table_name(:fixture_name) instantiation of fixtures - # self.use_transactional_fixtures = true - # self.use_instantiated_fixtures = false - - def create_fixtures(*table_names) - Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names) - end + # Turn off transactional fixtures if you're working with MyISAM tables in MySQL + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david) + self.use_instantiated_fixtures = false + # Add more helper methods to be used by all tests here... # Logs in a user and returns the user object found in the session object # def login(login,password) @@ -43,5 +31,4 @@ class Test::Unit::TestCase end return string end - end \ No newline at end of file