From johnwilger at gmail.com Sun Sep 25 17:14:51 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 25 Sep 2005 17:14:51 -0400 Subject: [eXPlainPMT Developers] New Post to Weblog: Subversion or Darcs? Message-ID: <4337134bd704f_73df..fdbfc2bcc18a@porkchop.wilger.local.tmail> ------------------------------------------------------------------------------ Visit the eXPlainPMT Weblog at http://explainpmt.com to see this post on the web! ------------------------------------------------------------------------------ As I said earlier today, I'm on the fence about whether to use [Subversion](http://subversion.tigris.org) or [Darcs](http://abridgegame.org/darcs/) as the SCM as we finally get started on eXPlainPMT again. Here are the advantages I see for Subversion: * I have a lot of experience with it---both as a developer and an administrator * I can provide commit access to the repository without having to give system accounts on the server Advantages of Darcs: * Since this is an open-source project, Darcs would make it easier for people to maintain independent repositories with their own customizations to the code while still sharing code back and forth with the main branch. * It makes it easy for people without "commit access" to send patches that can be applied to the "main" repository. I guess at this point I'm leaning towards Subversion. Unless I can figure out some way to allow everyone on the core team to commit to a Darcs repository _without_ having to give everyone shell accounts on the server---I don't want to have to manually apply patches all the time---Subversion will require less administrative overhead. That said, if you think Darcs would be a better choice, go ahead and convince me. From johnwilger at gmail.com Mon Sep 26 12:44:00 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Mon, 26 Sep 2005 16:44:00 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r6 - /tags/r1.3.2/ Message-ID: <200509261644.j8QGi0aY009807@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 12:43:57 2005 New Revision: 6 Log: Tagging eXPlainPMT 1.3.2 release. Added: tags/r1.3.2/ - copied from r5, branches/1.x/trunk/ From johnwilger at gmail.com Mon Sep 26 12:52:31 2005 From: johnwilger at gmail.com (John Wilger) Date: Mon, 26 Sep 2005 12:52:31 -0400 Subject: [eXPlainPMT Developers] New Post to Weblog: eXPlainPMT 1.3.2 Released Message-ID: <4338274fc0e3e_73de..fdbfe9bcc183@porkchop.wilger.local.tmail> ------------------------------------------------------------------------------ Visit the eXPlainPMT Weblog at http://explainpmt.com to see this post on the web! ------------------------------------------------------------------------------ I just released [eXPlainPMT 1.3.2](http://rubyforge.org/frs/?group_id=617&release_id=3007) on RubyForge. Nothing major has changed---this is just the long-awaited patch for Rails 0.13.1 compatibility. (I've also actually included the Rails libs in the "vendor" directory, so that it won't matter if future releases of Rails break compatibility.) From johnwilger at gmail.com Mon Sep 26 12:42:38 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Mon, 26 Sep 2005 16:42:38 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r5 - in /branches/1.x/trunk: ./ app/controllers/ app/views/iterations/ app/views/stories/ config/ config/environments/ public/ public/javascripts/ script/ test/ test/functional/ vendor/ Message-ID: <200509261642.j8QGgerJ009802@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 12:42:17 2005 New Revision: 5 Log: Updated to work with latest Rails release (0.13.1). Added: branches/1.x/trunk/public/javascripts/controls.js branches/1.x/trunk/public/javascripts/dragdrop.js branches/1.x/trunk/public/javascripts/effects.js Modified: branches/1.x/trunk/README branches/1.x/trunk/Rakefile branches/1.x/trunk/app/controllers/application.rb branches/1.x/trunk/app/controllers/iterations_controller.rb branches/1.x/trunk/app/controllers/stories_controller.rb branches/1.x/trunk/app/views/iterations/select_stories.rhtml branches/1.x/trunk/app/views/iterations/show.rhtml branches/1.x/trunk/app/views/stories/index.rhtml branches/1.x/trunk/config/environment.rb branches/1.x/trunk/config/environments/development.rb branches/1.x/trunk/config/environments/production.rb branches/1.x/trunk/config/environments/test.rb branches/1.x/trunk/config/routes.rb branches/1.x/trunk/public/404.html branches/1.x/trunk/public/500.html branches/1.x/trunk/public/dispatch.cgi branches/1.x/trunk/public/dispatch.fcgi branches/1.x/trunk/public/dispatch.rb branches/1.x/trunk/public/javascripts/prototype.js branches/1.x/trunk/script/benchmarker branches/1.x/trunk/script/breakpointer branches/1.x/trunk/script/console branches/1.x/trunk/script/destroy branches/1.x/trunk/script/generate branches/1.x/trunk/script/profiler branches/1.x/trunk/script/runner branches/1.x/trunk/script/server branches/1.x/trunk/test/functional/iterations_controller_test.rb branches/1.x/trunk/test/functional/stories_controller_test.rb branches/1.x/trunk/test/test_helper.rb branches/1.x/trunk/vendor/ (props changed) Modified: branches/1.x/trunk/README ============================================================================== --- branches/1.x/trunk/README (original) +++ branches/1.x/trunk/README Mon Sep 26 12:42:17 2005 @@ -41,9 +41,8 @@ wish to run the test suite and/or work in the development environment. Use the database and username from step 2. -4. Run "rake install" in the application root directory to install the database - schema to the production database. You could also run - "rake install_with_sample" if you would like to install some sample data. +4. Import the db/schema.sql file (and optionally the db/sample_data.sql file) into + the database. 5. Run "./script/create_admin" to create an admin user for logging in to the site. Modified: branches/1.x/trunk/Rakefile ============================================================================== --- branches/1.x/trunk/Rakefile (original) +++ branches/1.x/trunk/Rakefile Mon Sep 26 12:42:17 2005 @@ -1,7 +1,6 @@ require 'rake' require 'rake/testtask' require 'rake/rdoctask' -require 'rake/packagetask' $VERBOSE = nil TEST_CHANGES_SINCE = Time.now - 600 @@ -17,7 +16,7 @@ end end -desc "Generate API documentatio, show coding stats" +desc "Generate API documentation, show coding stats" task :doc => [ :appdoc, :stats ] @@ -62,20 +61,57 @@ desc "Generate documentation for the application" Rake::RDocTask.new("appdoc") { |rdoc| - rdoc.rdoc_dir = 'doc' - rdoc.title = "eXPlain Project Management Tool: Developer Documentation" - rdoc.options << '--line-numbers --inline-source --all' + rdoc.rdoc_dir = 'doc/app' + rdoc.title = "Rails Application 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('LICENSE') - rdoc.rdoc_files.include('app/**/*.rb') + 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 do +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"] @@ -92,10 +128,15 @@ IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end - when "postgresql" - `psql -U #{abcs["test"]["username"]} -f db/#{RAILS_ENV}_structure.sql #{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"] + `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 @@ -108,10 +149,16 @@ 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" - `pg_dump -U #{abcs[RAILS_ENV]["username"]} -s -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}` + 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 @@ -122,77 +169,34 @@ abcs = ActiveRecord::Base.configurations case abcs["test"]["adapter"] when "mysql" - ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) + ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"]) when "postgresql" - `dropdb -U #{abcs["test"]["username"]} #{abcs["test"]["database"]}` - `createdb -U #{abcs["test"]["username"]} #{abcs["test"]["database"]}` + 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 "Install db/schema.sql to development database" -task :install_dev => :environment do - db = ActiveRecord::Base.configurations['development']['database'] - ActiveRecord::Base.establish_connection(:development) - ActiveRecord::Base.connection.execute("DROP DATABASE #{db}") - ActiveRecord::Base.connection.execute("CREATE DATABASE #{db}") - ActiveRecord::Base.connection.execute("USE #{db}") - IO.readlines('db/schema.sql').join.split("\n\n").each do |table| - table.gsub!(/;$/,'') - ActiveRecord::Base.connection.execute(table) - end -end - -desc "Create development database with sample data" -task :install_dev_with_sample => :install_dev do - ActiveRecord::Base.establish_connection(:development) - IO.readlines('db/sample_data.sql').join.split("\n\n").each do |record| - record.gsub!(/;$/,'') - ActiveRecord::Base.connection.execute(record) - end -end - -desc "Install db/schema.sql to production database" -task :install => :environment do - db = ActiveRecord::Base.configurations['production']['database'] - ActiveRecord::Base.establish_connection(:production) - ActiveRecord::Base.connection.execute("DROP DATABASE #{db}") - ActiveRecord::Base.connection.execute("CREATE DATABASE #{db}") - ActiveRecord::Base.connection.execute("USE #{db}") - IO.readlines('db/schema.sql').join.split("\n\n").each do |table| - table.gsub!(/;$/,'') - ActiveRecord::Base.connection.execute(table) - end -end - -desc "Create production database with sample data" -task :install_with_sample => :install do - ActiveRecord::Base.establish_connection(:production) - IO.readlines('db/sample_data.sql').join.split("\n\n").each do |record| - record.gsub!(/;$/,'') - ActiveRecord::Base.connection.execute(record) - end -end - -PKG_VERSION = ENV['PKG_VERSION'] ? ENV['PKG_VERSION'] : :noversion - -Rake::PackageTask.new('eXPlainPMT', PKG_VERSION) do |p| - p.need_tar = true - p.package_files.include('./**/*') -end - -desc "Cleans the project directory to prepare for packaging." -task :clean do - system('touch pkg') - system('rm -rf pkg') - system('touch config/database.yml') - system('rm config/database.yml') - system('mv config/database.yml.orig config/database.yml') - system('rm -rf log/*') - system('touch db/development_structure.sql') - system('rm db/development_structure.sql') -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 Modified: branches/1.x/trunk/app/controllers/application.rb ============================================================================== --- branches/1.x/trunk/app/controllers/application.rb (original) +++ branches/1.x/trunk/app/controllers/application.rb Mon Sep 26 12:42:17 2005 @@ -27,6 +27,7 @@ model :milestone helper :sort + helper :collection_table layout :choose_layout before_filter :check_authentication Modified: branches/1.x/trunk/app/controllers/iterations_controller.rb ============================================================================== --- branches/1.x/trunk/app/controllers/iterations_controller.rb (original) +++ branches/1.x/trunk/app/controllers/iterations_controller.rb Mon Sep 26 12:42:17 2005 @@ -35,8 +35,8 @@ unless iteration.nil? keep_flash redirect_to :controller => 'iterations', :action => 'show', - :id => iteration.id, - :project_id => @project.id + :id => iteration.id.to_s, + :project_id => @project.id.to_s else @page_title = 'Iterations' end @@ -66,7 +66,7 @@ else @session[:new_iteration] = iteration redirect_to :controller => 'iterations', :action => 'new', - :project_id => @project.id + :project_id => @project.id.to_s end end @@ -91,7 +91,7 @@ else @session[:edit_iteration] = iteration redirect_to :controller => 'iterations', :action => 'edit', - :id => iteration.id, :project_id => @project.id + :id => iteration.id.to_s, :project_id => @project.id.to_s end end @@ -107,7 +107,7 @@ "iteration (if any) have been moved to the project " + "backlog." redirect_to :controller => 'iterations', :action => 'index', - :project_id => @project.id + :project_id => @project.id.to_s end # Displays a summary of the iteration and shows the list of story cards that @@ -131,10 +131,10 @@ change_story_assignment if @params['id'] redirect_to :controller => 'iterations', :action => 'show', - :id => @params['id'], :project_id => @project.id + :id => @params['id'], :project_id => @project.id.to_s else redirect_to :controller => 'stories', :action => 'index', - :project_id => @project.id + :project_id => @project.id.to_s end end Modified: branches/1.x/trunk/app/controllers/stories_controller.rb ============================================================================== --- branches/1.x/trunk/app/controllers/stories_controller.rb (original) +++ branches/1.x/trunk/app/controllers/stories_controller.rb Mon Sep 26 12:42:17 2005 @@ -66,7 +66,7 @@ else @session[:new_story] = story redirect_to :controller => 'stories', :action => 'new', - :project_id => @project.id + :project_id => @project.id.to_s end end @@ -92,7 +92,7 @@ else @session[:edit_story] = story redirect_to :controller => 'stories', :action => 'edit', - :id => story.id, :project_id => @project.id + :id => story.id.to_s, :project_id => @project.id.to_s end end @@ -103,10 +103,10 @@ if @params['iteration_id'] redirect_to :controller => 'iterations', :action => 'show', :id => @params['iteration_id'], - :project_id => @project.id + :project_id => @project.id.to_s else redirect_to :controller => 'stories', :action => 'index', - :project_id => @project.id + :project_id => @project.id.to_s end end @@ -124,7 +124,7 @@ story.save flash[:status] = "SC#{story.scid} has been updated." redirect_to :controller => 'iterations', :action => 'show', - :id => story.iteration.id, :project_id => @project.id + :id => story.iteration.id.to_s, :project_id => @project.id.to_s end # Sets the story's owner to nil @@ -134,7 +134,7 @@ story.save flash[:status] = "SC#{story.scid} has been updated." redirect_to :controller => 'iterations', :action => 'show', - :id => story.iteration.id, :project_id => @project.id + :id => story.iteration.id.to_s, :project_id => @project.id.to_s end protected Modified: branches/1.x/trunk/app/views/iterations/select_stories.rhtml ============================================================================== --- branches/1.x/trunk/app/views/iterations/select_stories.rhtml (original) +++ branches/1.x/trunk/app/views/iterations/select_stories.rhtml Mon Sep 26 12:42:17 2005 @@ -12,7 +12,7 @@ :id => @iteration.id, :project_id => @project.id } -stories_table = CollectionTable.new(@stories, +stories_table = CollectionTableHelper::CollectionTable.new(@stories, [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], Modified: branches/1.x/trunk/app/views/iterations/show.rhtml ============================================================================== --- branches/1.x/trunk/app/views/iterations/show.rhtml (original) +++ branches/1.x/trunk/app/views/iterations/show.rhtml Mon Sep 26 12:42:17 2005 @@ -166,7 +166,7 @@ :controller => 'iterations', :action => 'show', :id => @iteration.id, :project_id => @project.id } -stories_table = CollectionTable.new(@stories, +stories_table = CollectionTableHelper::CollectionTable.new(@stories, [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], Modified: branches/1.x/trunk/app/views/stories/index.rhtml ============================================================================== --- branches/1.x/trunk/app/views/stories/index.rhtml (original) +++ branches/1.x/trunk/app/views/stories/index.rhtml Mon Sep 26 12:42:17 2005 @@ -35,7 +35,7 @@ <% sort_header_params = { :controller => 'stories', :action => 'index', :project_id => @project.id } - stories_table = CollectionTable.new(@stories, + stories_table = CollectionTableHelper::CollectionTable.new(@stories, [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], [:title, link_to_sort_by('Title', 'title', sort_header_params)], [:points, link_to_sort_by('Points', 'points', sort_header_params)], Modified: branches/1.x/trunk/config/environment.rb ============================================================================== --- branches/1.x/trunk/config/environment.rb (original) +++ branches/1.x/trunk/config/environment.rb Mon Sep 26 12:42:17 2005 @@ -1,70 +1,87 @@ -RAILS_ROOT = File.dirname(__FILE__) + "/../" -RAILS_ENV = ENV['RAILS_ENV'] || 'development' - - -# Mocks first. -ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] - -# Then model subdirectories. -ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) -ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) - -# Followed by the standard includes. -ADDITIONAL_LOAD_PATHS.concat %w( - app - app/models - app/controllers - app/helpers - app/apis - components - config - lib - vendor - vendor/rails/railties - vendor/rails/railties/lib - vendor/rails/actionpack/lib - vendor/rails/activesupport/lib - vendor/rails/activerecord/lib - vendor/rails/actionmailer/lib - vendor/rails/actionwebservice/lib -).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } - -# Prepend to $LOAD_PATH -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } - -# Require Rails libraries. -require 'rubygems' - -require_gem 'activerecord', '= 1.10.1' -require_gem 'actionpack', '= 1.8.1' -require_gem 'actionmailer', '= 0.9.1' -require_gem 'rails', '= 0.12.1' - -# Environment-specific configuration. -require_dependency "environments/#{RAILS_ENV}" -ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } -ActiveRecord::Base.establish_connection - - -# 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." - ) -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 - -Controllers = Dependencies::LoadingModule.root( - File.join(RAILS_ROOT, 'app', 'controllers'), - File.join(RAILS_ROOT, 'components') -) - -# Include your app's configuration here: +# Load the Rails framework and configure your application. +# You can include your own configuration at the end of this file. +# +# Be sure to restart your webserver when you modify this file. + +# The path to the root directory of your application. +RAILS_ROOT = File.join(File.dirname(__FILE__), '..') + +# The environment your application is currently running. Don't set +# this here; put it in your webserver's configuration as the RAILS_ENV +# environment variable instead. +# +# See config/environments/*.rb for environment-specific configuration. +RAILS_ENV = ENV['RAILS_ENV'] || 'development' + + +# Load the Rails framework. Mock classes for testing come first. +ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] + +# Then model subdirectories. +ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) +ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) + +# Followed by the standard includes. +ADDITIONAL_LOAD_PATHS.concat %w( + app + app/models + app/controllers + app/helpers + app/apis + components + config + lib + vendor + vendor/rails/railties + vendor/rails/railties/lib + vendor/rails/actionpack/lib + vendor/rails/activesupport/lib + vendor/rails/activerecord/lib + vendor/rails/actionmailer/lib + vendor/rails/actionwebservice/lib +).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } + +# Prepend to $LOAD_PATH +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } + +# Require Rails libraries. +require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails") + +require 'active_support' +require 'active_record' +require 'action_controller' +require 'action_mailer' +require 'action_web_service' + +# Environment-specific configuration. +require_dependency "environments/#{RAILS_ENV}" +ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } +ActiveRecord::Base.establish_connection + + +# Configure defaults if the included environment did not. +begin + RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log") + RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG) +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." + ) +end + +[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER } +[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" } + +# Set up routes. +ActionController::Routing::Routes.reload + +Controllers = Dependencies::LoadingModule.root( + File.join(RAILS_ROOT, 'app', 'controllers'), + File.join(RAILS_ROOT, 'components') +) + +# Include your app's configuration here: + Modified: branches/1.x/trunk/config/environments/development.rb ============================================================================== --- branches/1.x/trunk/config/environments/development.rb (original) +++ branches/1.x/trunk/config/environments/development.rb Mon Sep 26 12:42:17 2005 @@ -1,4 +1,14 @@ -Dependencies.mechanism = :load -ActionController::Base.consider_all_requests_local = true -ActionController::Base.perform_caching = false -BREAKPOINT_SERVER_PORT = 42531 +# 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. + +# Log error messages when you accidentally call methods on nil. +require 'active_support/whiny_nil' + +# Reload code; show full error reports; disable caching. +Dependencies.mechanism = :load +ActionController::Base.consider_all_requests_local = true +ActionController::Base.perform_caching = false + +# The breakpoint server port that script/breakpointer connects to. +BREAKPOINT_SERVER_PORT = 42531 Modified: branches/1.x/trunk/config/environments/production.rb ============================================================================== --- branches/1.x/trunk/config/environments/production.rb (original) +++ branches/1.x/trunk/config/environments/production.rb Mon Sep 26 12:42:17 2005 @@ -1,3 +1,8 @@ -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = false -ActionController::Base.perform_caching = true +# 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. + +# 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 Modified: branches/1.x/trunk/config/environments/test.rb ============================================================================== --- branches/1.x/trunk/config/environments/test.rb (original) +++ branches/1.x/trunk/config/environments/test.rb Mon Sep 26 12:42:17 2005 @@ -1,4 +1,17 @@ -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = true -ActionController::Base.perform_caching = false -ActionMailer::Base.delivery_method = :test +# The test environment is used exclusively to run your application's +# 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! + +# Log error messages when you accidentally call methods on nil. +require 'active_support/whiny_nil' + +# 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 + +# 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 Modified: branches/1.x/trunk/config/routes.rb ============================================================================== --- branches/1.x/trunk/config/routes.rb (original) +++ branches/1.x/trunk/config/routes.rb Mon Sep 26 12:42:17 2005 @@ -12,7 +12,7 @@ :controller => 'stories', :action => 'index' map.connect 'project/:project_id/stories/show_cancelled', - :controller => 'stories', :action => 'index', :show_cancelled => 1 + :controller => 'stories', :action => 'index', :show_cancelled => '1' map.connect 'project/:project_id/:controller/:action/:id', :controller => 'dashboard' Modified: branches/1.x/trunk/public/404.html ============================================================================== --- branches/1.x/trunk/public/404.html (original) +++ branches/1.x/trunk/public/404.html Mon Sep 26 12:42:17 2005 @@ -1,6 +1,8 @@ - - -

File not found

-

Change this error message for pages not found in public/404.html

- + + + +

File not found

+

Change this error message for pages not found in public/404.html

+ Modified: branches/1.x/trunk/public/500.html ============================================================================== --- branches/1.x/trunk/public/500.html (original) +++ branches/1.x/trunk/public/500.html Mon Sep 26 12:42:17 2005 @@ -1,6 +1,8 @@ - - -

Application error (Apache)

-

Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html

- + + + +

Application error (Apache)

+

Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html

+ Modified: branches/1.x/trunk/public/dispatch.cgi ============================================================================== --- branches/1.x/trunk/public/dispatch.cgi (original) +++ branches/1.x/trunk/public/dispatch.cgi Mon Sep 26 12:42:17 2005 @@ -1,10 +1,10 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) - -# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: -# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired -require "dispatcher" - -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) -Dispatcher.dispatch +#!/usr/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch Modified: branches/1.x/trunk/public/dispatch.fcgi ============================================================================== --- branches/1.x/trunk/public/dispatch.fcgi (original) +++ branches/1.x/trunk/public/dispatch.fcgi Mon Sep 26 12:42:17 2005 @@ -1,27 +1,24 @@ -#!/bin/env ruby - -def dispatcher_error(path,e,msg="") - error_message = - "[#{Time.now}] Dispatcher failed to catch: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}\n#{msg}" - Logger.new(path).fatal(error_message) -rescue Object => log_error - STDERR << "Couldn't write to #{path} (#{e} [#{e.class}])\n" << error_message -end - -begin - require File.dirname(__FILE__) + "/../config/environment" - require 'dispatcher' - require 'fcgi' - - log_file_path = "#{RAILS_ROOT}/log/fastcgi.crash.log" - - FCGI.each_cgi do |cgi| - begin - Dispatcher.dispatch(cgi) - rescue Object => rails_error - dispatcher_error(log_file_path, rails_error) - end - end -rescue Object => fcgi_error - dispatcher_error(log_file_path, fcgi_error, "FCGI process #{$$} killed by this error\n") -end +#!/usr/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! Modified: branches/1.x/trunk/public/dispatch.rb ============================================================================== --- branches/1.x/trunk/public/dispatch.rb (original) +++ branches/1.x/trunk/public/dispatch.rb Mon Sep 26 12:42:17 2005 @@ -1,10 +1,10 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) - -# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: -# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired -require "dispatcher" - -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) -Dispatcher.dispatch +#!/usr/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch Added: branches/1.x/trunk/public/javascripts/controls.js ============================================================================== --- branches/1.x/trunk/public/javascripts/controls.js (added) +++ branches/1.x/trunk/public/javascripts/controls.js Mon Sep 26 12:42:17 2005 @@ -1,0 +1,446 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// +// 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; +} + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// 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 +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// 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 +// 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. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + base_initialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.has_focus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entry_count = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = {} + + this.options.tokens = this.options.tokens || new Array(); + this.options.frequency = this.options.frequency || 0.4; + this.options.min_chars = this.options.min_chars || 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'; + } + new 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; + + 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') { + 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); + } + }, + + hide: function() { + if(this.update.style.display=='') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.indicator) Element.show(this.indicator); + }, + + stopIndicator: function() { + if(this.indicator) Element.hide(this.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.select_entry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.mark_previous(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.mark_next(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.has_focus = 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) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.select_entry(); + Event.stop(event); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.has_focus = false; + this.active = false; + }, + + render: function() { + if(this.entry_count > 0) { + for (var i = 0; i < this.entry_count; 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); + + this.show(); + this.active = true; + } + } else this.hide(); + }, + + mark_previous: function() { + if(this.index > 0) this.index-- + else this.index = this.entry_count-1; + }, + + mark_next: function() { + if(this.index < this.entry_count-1) this.index++ + else this.index = 0; + }, + + get_entry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + get_current_entry: function() { + return this.get_entry(this.index); + }, + + select_entry: function() { + this.active = false; + value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); + this.updateElement(value); + this.element.focus(); + }, + + 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+/); + if (whitespace) + new_value += whitespace[0]; + this.element.value = new_value + value; + } else { + this.element.value = value; + } + }, + + updateChoices: function(choices) { + if(!this.changed && this.has_focus) { + 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.update.firstChild.childNodes.length; + for (var i = 0; i < this.entry_count; i++) { + entry = this.get_entry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entry_count = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getEntry().length>=this.options.min_chars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getEntry: function() { + var token_pos = this.findLastToken(); + if (token_pos != -1) + var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var last_token_pos = -1; + + for (var i=0; i last_token_pos) + last_token_pos = this_token_pos; + } + return last_token_pos; + } +} + +Ajax.Autocompleter = Class.create(); +Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), +Object.extend(new Ajax.Base(), { + initialize: function(element, update, url, options) { + this.base_initialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this) + this.options.method = 'post'; + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.element.name) + '=' + + encodeURIComponent(this.getEntry()); + + 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 +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partial_search - 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). +// +// - full_search - Search anywhere in autocomplete array strings. +// +// - partial_chars - How many characters to enter before triggering +// a partial match (unlike min_chars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignore_case - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +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.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partial_search: true, + partial_chars: 2, + ignore_case: true, + full_search: false, + selector: function(instance) { + var ret = new Array(); // Beginning matches + var partial = new Array(); // Inside matches + var entry = instance.getEntry(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; + var found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (found_pos != -1) { + if (found_pos == 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) + "
  • "); + break; + } + } + + found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : + elem.indexOf(entry, found_pos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return ""; + } + }, options || {}); + } +}); Added: branches/1.x/trunk/public/javascripts/dragdrop.js ============================================================================== --- branches/1.x/trunk/public/javascripts/dragdrop.js (added) +++ branches/1.x/trunk/public/javascripts/dragdrop.js Mon Sep 26 12:42:17 2005 @@ -1,0 +1,537 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// 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; + } +} + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: false, + + remove: function(element) { + for(var i = 0; i < this.drops.length; i++) + if(this.drops[i].element == element) + this.drops.splice(i,1); + }, + + add: function(element) { + var element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = new Array(); + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + for(var i=0; i0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +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); + } + } + } + } + + // 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]); + + } + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + serialize: function(element) { + var element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id + }, arguments[1] || {}); + + var items = $(element).childNodes; + var queryComponents = new Array(); + + for(var i=0; i= this.finishOn) { + this.render(this.options.to); + if(this.finish) this.finish(); + if(this.options.afterFinish) this.options.afterFinish(this); + 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; + } + this.timeout = setTimeout(this.loop.bind(this), 10); + }, + render: function(pos) { + 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); + if(this.update) this.update(pos); + if(this.options.afterUpdate) this.options.afterUpdate(this); + }, + cancel: function() { + if(this.timeout) clearTimeout(this.timeout); + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + for (var i = 0; i < this.effects.length; i++) + this.effects[i].render(position); + }, + finish: function(position) { + for (var i = 0; i < this.effects.length; i++) + if(this.effects[i].finish) this.effects[i].finish(position); + } +}); + +// 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, + 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+")"; + } +}); + +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]); + }, + update: function(position) { + topd = this.toTop * position + this.originalTop; + leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0 + }, 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); + }, + + setDimensions: function(width, height) { + if(this.options.scaleX) this.element.style.width = width + 'px'; + if(this.options.scaleY) this.element.style.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"; + } else { + if(this.options.scaleY) this.element.style.top = -topd + "px"; + if(this.options.scaleX) this.element.style.left = -leftd + "px"; + } + } + } +}); + +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 + }, 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); + }, + 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)) ]; + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + }, + finish: function() { + this.element.style.backgroundColor = this.options.restorecolor; + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (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] || {}); + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- prepackaged effects ------------- */ + +Effect.Fade = function(element) { + options = Object.extend({ + from: 1.0, + to: 0.0, + afterFinish: function(effect) + { Element.hide(effect.element); + effect.setOpacity(1); } + }, arguments[1] || {}); + new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + options = Object.extend({ + from: 0.0, + to: 1.0, + beforeStart: function(effect) + { effect.setOpacity(0); + Element.show(effect.element); }, + afterUpdate: function(effect) + { Element.show(effect.element); } + }, arguments[1] || {}); + 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) + { effect.effects[0].element.style.position = 'absolute'; }, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + } + ); +} + +Effect.BlindUp = function(element) { + Element.makeClipping(element); + new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + $(element).style.height = '0px'; + Element.makeClipping(element); + Element.show(element); + new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterFinish: function(effect) { + Element.undoClipping(effect.element); + } + }, 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); } + } ) + } + } ); +} + +Effect.DropOut = function(element) { + 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); } + }); +} + +Effect.Shake = function(element) { + new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinish: function(effect) { + }}) }}) }}) }}) }}) }}); +} + +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, + 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); } + }, 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, + Object.extend({ scaleContent: false, + scaleX: false, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.Squish = function(element) { + new Effect.Scale(element, 0, + { afterFinish: function(effect) { Element.hide(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 direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeUpdate: function(effect) { $(element).style.height = '0px'; }, + afterFinish: 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, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], + options); } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + 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); +} + +Effect.Pulsate = function(element) { + var options = arguments[1] || {}; + 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); } + }, 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); +} Modified: branches/1.x/trunk/public/javascripts/prototype.js ============================================================================== --- branches/1.x/trunk/public/javascripts/prototype.js (original) +++ branches/1.x/trunk/public/javascripts/prototype.js Mon Sep 26 12:42:17 2005 @@ -1,765 +1,1038 @@ -/* Prototype: an object-oriented Javascript library, version 1.2.1 - * (c) 2005 Sam Stephenson - * - * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff - * against the source tree, available from the Prototype darcs repository. - * - * Prototype is freely distributable under the terms of an MIT-style license. - * - * For details, see the Prototype web site: http://prototype.conio.net/ - * -/*--------------------------------------------------------------------------*/ - -var Prototype = { - Version: '1.2.1' -} - -var Class = { - create: function() { - return function() { - this.initialize.apply(this, arguments); - } - } -} - -var Abstract = new Object(); - -Object.prototype.extend = function(object) { - for (property in object) { - this[property] = object[property]; - } - return this; -} - -Function.prototype.bind = function(object) { - var method = this; - return function() { - method.apply(object, arguments); - } -} - -Function.prototype.bindAsEventListener = function(object) { - var method = this; - return function(event) { - method.call(object, event || window.event); - } -} - -Number.prototype.toColorPart = function() { - var digits = this.toString(16); - if (this < 16) return '0' + digits; - return digits; -} - -var Try = { - these: function() { - var returnValue; - - for (var i = 0; i < arguments.length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) {} - } - - return returnValue; - } -} - -/*--------------------------------------------------------------------------*/ - -var PeriodicalExecuter = Class.create(); -PeriodicalExecuter.prototype = { - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.callback(); - } finally { - this.currentlyExecuting = false; - } - } - - this.registerCallback(); - } -} - -/*--------------------------------------------------------------------------*/ - -function $() { - var elements = new Array(); - - for (var i = 0; i < arguments.length; i++) { - var element = arguments[i]; - if (typeof element == 'string') - element = document.getElementById(element); - - if (arguments.length == 1) - return element; - - elements.push(element); - } - - 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] = 'x[' + i + ']'; - - object.__apply__ = this; - var result = eval('obj.__apply__(' + - parameterStrings[i].join(', ') + ')'); - object.__apply__ = null; - - return result; - } -} - -/*--------------------------------------------------------------------------*/ - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')}, - function() {return new XMLHttpRequest()} - ) || false; - }, - - emptyFunction: function() {} -} - -Ajax.Base = function() {}; -Ajax.Base.prototype = { - setOptions: function(options) { - this.options = { - method: 'post', - asynchronous: true, - parameters: '' - }.extend(options || {}); - } -} - -Ajax.Request = Class.create(); -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Request.prototype = (new Ajax.Base()).extend({ - initialize: function(url, options) { - this.transport = Ajax.getTransport(); - this.setOptions(options); - - try { - if (this.options.method == 'get') - url += '?' + this.options.parameters + '&_='; - - this.transport.open(this.options.method, url, - this.options.asynchronous); - - if (this.options.asynchronous) { - this.transport.onreadystatechange = this.onStateChange.bind(this); - setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); - } - - this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version); - - if (this.options.method == 'post') { - this.transport.setRequestHeader('Connection', 'close'); - this.transport.setRequestHeader('Content-type', - 'application/x-www-form-urlencoded'); - } - - this.transport.send(this.options.method == 'post' ? - this.options.parameters + '&_=' : null); - - } catch (e) { - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState != 1) - this.respondToReadyState(this.transport.readyState); - }, - - respondToReadyState: function(readyState) { - var event = Ajax.Request.Events[readyState]; - (this.options['on' + event] || Ajax.emptyFunction)(this.transport); - } -}); - -Ajax.Updater = Class.create(); -Ajax.Updater.prototype = (new Ajax.Base()).extend({ - initialize: function(container, url, options) { - this.container = $(container); - this.setOptions(options); - - if (this.options.asynchronous) { - this.onComplete = this.options.onComplete; - this.options.onComplete = this.updateContent.bind(this); - } - - this.request = new Ajax.Request(url, this.options); - - if (!this.options.asynchronous) - this.updateContent(); - }, - - updateContent: function() { - if (this.options.insertion) { - new this.options.insertion(this.container, - this.request.transport.responseText); - } else { - this.container.innerHTML = this.request.transport.responseText; - } - - if (this.onComplete) { - setTimeout((function() {this.onComplete(this.request)}).bind(this), 10); - } - } -}); - -/*--------------------------------------------------------------------------*/ - -var Field = { - clear: function() { - for (var i = 0; i < arguments.length; i++) - $(arguments[i]).value = ''; - }, - - 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(); - } -} - -/*--------------------------------------------------------------------------*/ - -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) { - form = $(form); - var elements = new Array(); - - for (tagName in Form.Element.Serializers) { - var tagElements = form.getElementsByTagName(tagName); - for (var j = 0; j < tagElements.length; j++) - elements.push(tagElements[j]); - } - return elements; - }, - - disable: function(form) { - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - element.blur(); - element.disable = 'true'; - } - }, - - focusFirstElement: function(form) { - form = $(form); - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - if (element.type != 'hidden' && !element.disabled) { - Field.activate(element); - break; - } - } - }, - - reset: function(form) { - $(form).reset(); - } -} - -Form.Element = { - serialize: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return encodeURIComponent(parameter[0]) + '=' + - encodeURIComponent(parameter[1]); - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - var parameter = Form.Element.Serializers[method](element); - - if (parameter) - return parameter[1]; - } -} - -Form.Element.Serializers = { - input: function(element) { - switch (element.type.toLowerCase()) { - case 'hidden': - case 'password': - case 'text': - return Form.Element.Serializers.textarea(element); - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element); - } - return false; - }, - - inputSelector: function(element) { - if (element.checked) - return [element.name, element.value]; - }, - - textarea: function(element) { - return [element.name, element.value]; - }, - - select: function(element) { - var index = element.selectedIndex; - var value = element.options[index].value || element.options[index].text; - return [element.name, (index >= 0) ? value : '']; - } -} - -/*--------------------------------------------------------------------------*/ - -var $F = Form.Element.getValue; - -/*--------------------------------------------------------------------------*/ - -Abstract.TimedObserver = function() {} -Abstract.TimedObserver.prototype = { - initialize: function(element, frequency, callback) { - this.frequency = frequency; - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - this.registerCallback(); - }, - - registerCallback: function() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - - this.registerCallback(); - } -} - -Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ - getValue: function() { - return Form.serialize(this.element); - } -}); - - -/*--------------------------------------------------------------------------*/ - -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; -} - -/*--------------------------------------------------------------------------*/ - -var Element = { - toggle: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = - (element.style.display == 'none' ? '' : 'none'); - } - }, - - hide: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = 'none'; - } - }, - - show: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = ''; - } - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - }, - - getHeight: function(element) { - element = $(element); - return element.offsetHeight; - } -} - -var Toggle = new Object(); -Toggle.display = Element.toggle; - -/*--------------------------------------------------------------------------*/ - -Abstract.Insertion = function(adjacency) { - this.adjacency = adjacency; -} - -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); - } else { - this.range = this.element.ownerDocument.createRange(); - if (this.initializeRange) this.initializeRange(); - this.fragment = this.range.createContextualFragment(this.content); - this.insertContent(); - } - } -} - -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ - initializeRange: function() { - this.range.setStartBefore(this.element); - }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, this.element); - } -}); - -Insertion.Top = Class.create(); -Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); - }, - - insertContent: function() { - this.element.insertBefore(this.fragment, this.element.firstChild); - } -}); - -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); - }, - - insertContent: function() { - this.element.appendChild(this.fragment); - } -}); - -Insertion.After = Class.create(); -Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ - initializeRange: function() { - this.range.setStartAfter(this.element); - }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, - this.element.nextSibling); - } -}); - -/*--------------------------------------------------------------------------*/ - -var Effect = new Object(); - -Effect.Highlight = Class.create(); -Effect.Highlight.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 153; - this.finish = 255; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) return; - if (this.timer) clearTimeout(this.timer); - this.highlight(this.element, this.current); - this.current += 17; - this.timer = setTimeout(this.fade.bind(this), 250); - }, - - isFinished: function() { - return this.current > this.finish; - }, - - highlight: function(element, current) { - element.style.backgroundColor = "#ffff" + current.toColorPart(); - } -} - - -Effect.Fade = Class.create(); -Effect.Fade.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 100; - this.finish = 0; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) { this.element.style.display = 'none'; return; } - if (this.timer) clearTimeout(this.timer); - this.setOpacity(this.element, this.current); - this.current -= 10; - this.timer = setTimeout(this.fade.bind(this), 50); - }, - - isFinished: function() { - return this.current <= this.finish; - }, - - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - } -} - -Effect.Scale = Class.create(); -Effect.Scale.prototype = { - initialize: function(element, percent) { - this.element = $(element); - this.startScale = 1.0; - this.startHeight = this.element.offsetHeight; - this.startWidth = this.element.offsetWidth; - this.currentHeight = this.startHeight; - this.currentWidth = this.startWidth; - this.finishScale = (percent/100) /*//*/; - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - if(this.element.effect_scale) { - clearTimeout(this.element.effect_scale.timer); - this.startScale = this.element.effect_scale.currentScale; - this.startHeight = this.element.effect_scale.startHeight; - this.startWidth = this.element.effect_scale.startWidth; - if(this.element.effect_scale.sizeEm) - this.sizeEm = this.element.effect_scale.sizeEm; - } - this.element.effect_scale = this; - this.currentScale = this.startScale; - this.factor = this.finishScale - this.startScale; - this.options = arguments[2] || {}; - this.scale(); - }, - - scale: function() { - if (this.isFinished()) { - this.setDimensions(this.element, this.startWidth*this.finishScale, this.startHeight*this.finishScale); - if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.finishScale + "em"; - if(this.options.complete) this.options.complete(this); - return; - } - if (this.timer) clearTimeout(this.timer); - if (this.options.step) this.options.step(this); - this.setDimensions(this.element, this.currentWidth, this.currentHeight); - if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.currentScale + "em"; - this.currentScale += (this.factor/10) /*//*/; - this.currentWidth = this.startWidth * this.currentScale; - this.currentHeight = this.startHeight * this.currentScale; - this.timer = setTimeout(this.scale.bind(this), 50); - }, - - isFinished: function() { - return (this.factor < 0) ? - this.currentScale <= this.finishScale : this.currentScale >= this.finishScale; - }, - - setDimensions: function(element, width, height) { - element.style.width = width + 'px'; - element.style.height = height + 'px'; - } -} - -Effect.Squish = Class.create(); -Effect.Squish.prototype = { - initialize: function(element) { - this.element = $(element); - new Effect.Scale(this.element, 1, { complete: this.hide.bind(this) } ); - }, - hide: function() { - this.element.style.display = 'none'; - } -} - -Effect.Puff = Class.create(); -Effect.Puff.prototype = { - initialize: function(element) { - this.element = $(element); - this.opacity = 100; - this.startTop = this.element.top || this.element.offsetTop; - this.startLeft = this.element.left || this.element.offsetLeft; - new Effect.Scale(this.element, 200, { step: this.fade.bind(this), complete: this.hide.bind(this) } ); - }, - fade: function(effect) { - topd = (((effect.currentScale)*effect.startHeight) - effect.startHeight)/2; - leftd = (((effect.currentScale)*effect.startWidth) - effect.startWidth)/2; - this.element.style.position='absolute'; - this.element.style.top = this.startTop-topd + "px"; - this.element.style.left = this.startLeft-leftd + "px"; - this.opacity -= 10; - this.setOpacity(this.element, this.opacity); - if(navigator.appVersion.indexOf('AppleWebKit')>0) this.element.innerHTML += ''; //force redraw on safari - }, - hide: function() { - this.element.style.display = 'none'; - }, - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - } -} - -Effect.Appear = Class.create(); -Effect.Appear.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 0; - this.finish = 100; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) return; - if (this.timer) clearTimeout(this.timer); - this.setOpacity(this.element, this.current); - this.current += 10; - this.timer = setTimeout(this.fade.bind(this), 50); - }, - - isFinished: function() { - return this.current > this.finish; - }, - - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - element.style.display = ''; - } -} - -Effect.ContentZoom = Class.create(); -Effect.ContentZoom.prototype = { - initialize: function(element, percent) { - this.element = $(element); - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - if(this.element.effect_contentzoom) { - this.sizeEm = this.element.effect_contentzoom.sizeEm; - } - this.element.effect_contentzoom = this; - this.element.style.fontSize = this.sizeEm*(percent/100) + "em" /*//*/; - if(navigator.appVersion.indexOf('AppleWebKit')>0) { this.element.scrollTop -= 1; }; - } -} +/* Prototype JavaScript framework, version 1.3.1 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.3.1', + emptyFunction: function() {} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.prototype.extend = function(object) { + return Object.extend.apply(this, [this, object]); +} + +Function.prototype.bind = function(object) { + var __method = this; + return function() { + __method.apply(object, arguments); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + __method.call(object, event || window.event); + } +} + +Number.prototype.toColorPart = function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; +} + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + 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({ + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0].nodeValue; + } +}); + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + } +} + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + }.extend(options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = (new Ajax.Base()).extend({ + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + if (this.options.method == 'get') + url += '?' + parameters; + + this.transport.open(this.options.method, url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + 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. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] + || Prototype.emptyFunction)(this.transport); + + (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; + +Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function() { + this.updateContent(); + onComplete(this.transport); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var response = this.transport.responseText.replace(match, ''); + var scripts = this.transport.responseText.match(match); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + receiver.innerHTML = response; + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout((function() {this.onComplete( + this.transport)}).bind(this), 10); + } + + if (this.options.evalScripts && scripts) { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = 1; + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Ajax.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = + (element.style.display == 'none' ? '' : 'none'); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + 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; + }, + + addClassName: function(element, className) { + element = $(element); + Element.removeClassName(element, className); + element.className += ' ' + 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; + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + var element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +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); + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.fragment = this.range.createContextualFragment(this.content); + this.insertContent(); + } + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, this.element); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function() { + this.element.insertBefore(this.fragment, this.element.firstChild); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function() { + this.element.appendChild(this.fragment); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, + this.element.nextSibling); + } +}); + +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + 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(); + } +} + +/*--------------------------------------------------------------------------*/ + +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(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + 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)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + focusFirstElement: function(form) { + var form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(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]); + }, + + getValue: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + 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 [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + 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) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +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) { + this.callback(this.element, value); + 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 'radio': + element.target = this; + element.prev_onclick = element.onclick || Prototype.emptyFunction; + element.onclick = function() { + this.prev_onclick(); + this.target.onElementEvent(); + } + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + element.target = this; + element.prev_onchange = element.onchange || Prototype.emptyFunction; + element.onchange = function() { + this.prev_onchange(); + this.target.onElementEvent(); + } + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ + getValue: function() { + return Form.serialize(this.element); + } +}); + + +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + ((navigator.appVersion.indexOf('AppleWebKit') > 0) + || 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) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* 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, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + 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) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + } +} Modified: branches/1.x/trunk/script/benchmarker ============================================================================== --- branches/1.x/trunk/script/benchmarker (original) +++ branches/1.x/trunk/script/benchmarker Mon Sep 26 12:42:17 2005 @@ -1,19 +1,19 @@ -#!/bin/env ruby - -if ARGV.empty? - puts "Usage: benchmarker times 'Person.expensive_way' 'Person.another_expensive_way' ..." - exit -end - -require File.dirname(__FILE__) + '/../config/environment' -require 'benchmark' -include Benchmark - -# Don't include compilation in the benchmark -ARGV[1..-1].each { |expression| eval(expression) } - -bm(6) do |x| - ARGV[1..-1].each_with_index do |expression, idx| - x.report("##{idx + 1}") { ARGV[0].to_i.times { eval(expression) } } - end -end +#!/usr/bin/ruby + +if ARGV.empty? + puts "Usage: benchmarker times 'Person.expensive_way' 'Person.another_expensive_way' ..." + exit +end + +require File.dirname(__FILE__) + '/../config/environment' +require 'benchmark' +include Benchmark + +# Don't include compilation in the benchmark +ARGV[1..-1].each { |expression| eval(expression) } + +bm(6) do |x| + ARGV[1..-1].each_with_index do |expression, idx| + x.report("##{idx + 1}") { ARGV[0].to_i.times { eval(expression) } } + end +end Modified: branches/1.x/trunk/script/breakpointer ============================================================================== --- branches/1.x/trunk/script/breakpointer (original) +++ branches/1.x/trunk/script/breakpointer Mon Sep 26 12:42:17 2005 @@ -1,6 +1,4 @@ -#!/bin/env ruby - -require 'rubygems' -require_gem 'rails' -require 'breakpoint_client' - +#!/usr/bin/ruby +require 'rubygems' +require_gem 'rails' +require 'breakpoint_client' Modified: branches/1.x/trunk/script/console ============================================================================== --- branches/1.x/trunk/script/console (original) +++ branches/1.x/trunk/script/console Mon Sep 26 12:42:17 2005 @@ -1,24 +1,23 @@ -#!/bin/env ruby - -irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' - -require 'optparse' -options = {} -OptionParser.new do |opt| - opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| } - opt.parse!(ARGV) -end - -libs = " -r #{File.dirname(__FILE__)}/../config/environment" -libs << " -r #{File.dirname(__FILE__)}/console_sandbox" if options[:sandbox] -libs << " -r irb/completion" - -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 "#{irb} #{libs}" - +#!/usr/bin/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" Modified: branches/1.x/trunk/script/destroy ============================================================================== --- branches/1.x/trunk/script/destroy (original) +++ branches/1.x/trunk/script/destroy Mon Sep 26 12:42:17 2005 @@ -1,9 +1,7 @@ -#!/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/environment' +require 'rails_generator' +require 'rails_generator/scripts/destroy' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +Rails::Generator::Scripts::Destroy.new.run(ARGV) Modified: branches/1.x/trunk/script/generate ============================================================================== --- branches/1.x/trunk/script/generate (original) +++ branches/1.x/trunk/script/generate Mon Sep 26 12:42:17 2005 @@ -1,9 +1,7 @@ -#!/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/environment' +require 'rails_generator' +require 'rails_generator/scripts/generate' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +Rails::Generator::Scripts::Generate.new.run(ARGV) Modified: branches/1.x/trunk/script/profiler ============================================================================== --- branches/1.x/trunk/script/profiler (original) +++ branches/1.x/trunk/script/profiler Mon Sep 26 12:42:17 2005 @@ -1,17 +1,34 @@ -#!/bin/env ruby - -if ARGV.empty? - puts "Usage: profiler 'Person.expensive_method(10)' [times]" - exit -end - -require File.dirname(__FILE__) + '/../config/environment' -require "profiler" - -# Don't include compilation in the profile -eval(ARGV.first) - -Profiler__::start_profile -(ARGV[1] || 1).to_i.times { eval(ARGV.first) } -Profiler__::stop_profile -Profiler__::print_profile($stdout) +#!/usr/bin/ruby +if ARGV.empty? + $stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times]" + exit(1) +end + +# Keep the expensive require out of the profile. +$stderr.puts 'Loading Rails...' +require File.dirname(__FILE__) + '/../config/environment' + +# Define a method to profile. +if ARGV[1] and ARGV[1].to_i > 1 + eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" +else + eval "def profile_me() #{ARGV[0]} end" +end + +# Use the ruby-prof extension if available. Fall back to stdlib profiler. +begin + require 'prof' + $stderr.puts 'Using the ruby-prof extension.' + Prof.clock_mode = Prof::GETTIMEOFDAY + Prof.start + profile_me + results = Prof.stop + require 'rubyprof_ext' + Prof.print_profile(results, $stderr) +rescue LoadError + $stderr.puts 'Using the standard Ruby profiler.' + Profiler__.start_profile + profile_me + Profiler__.stop_profile + Profiler__.print_profile($stderr) +end Modified: branches/1.x/trunk/script/runner ============================================================================== --- branches/1.x/trunk/script/runner (original) +++ branches/1.x/trunk/script/runner Mon Sep 26 12:42:17 2005 @@ -1,4 +1,29 @@ -#!/bin/env ruby - -require File.dirname(__FILE__) + '/../config/environment' -eval(ARGV.first) +#!/usr/bin/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) Modified: branches/1.x/trunk/script/server ============================================================================== --- branches/1.x/trunk/script/server (original) +++ branches/1.x/trunk/script/server Mon Sep 26 12:42:17 2005 @@ -1,49 +1,49 @@ -#!/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]}" -DispatchServlet.dispatch(OPTIONS) - +#!/usr/bin/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) Modified: branches/1.x/trunk/test/functional/iterations_controller_test.rb ============================================================================== --- branches/1.x/trunk/test/functional/iterations_controller_test.rb (original) +++ branches/1.x/trunk/test/functional/iterations_controller_test.rb Mon Sep 26 12:42:17 2005 @@ -44,15 +44,15 @@ future = add_iteration(@project_one, Date.today + 30, 14) get :index, 'project_id' => @project_one.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => current.id, :project_id => @project_one.id + :id => current.id.to_s, :project_id => @project_one.id.to_s current.destroy get :index, 'project_id' => @project_one.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => past.id, :project_id => @project_one.id + :id => past.id.to_s, :project_id => @project_one.id.to_s past.destroy get :index, 'project_id' => @project_one.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => future.id, :project_id => @project_one.id + :id => future.id.to_s, :project_id => @project_one.id.to_s future.destroy get :index, 'project_id' => @project_one.id assert_response :success @@ -105,7 +105,7 @@ 'length' => 'foo', 'budget' => 'bar' } assert_redirected_to :controller => 'iterations', :action => 'new', - :project_id => @project_one.id + :project_id => @project_one.id.to_s assert session[:new_iteration] assert_equal it_count, Iteration.count end @@ -113,7 +113,7 @@ def test_delete get :delete, 'id' => @iteration_one.id, 'project_id' => @project_one.id assert_redirected_to :controller => 'iterations', :action => 'index', - :project_id => @project_one.id + :project_id => @project_one.id.to_s assert_equal "The #{@iteration_one.length}-day iteration scheduled to " + "start on #{@iteration_one.start_date.strftime('%m/%d/%Y')} " + "has been deleted. All stories assigned to the iteration " + @@ -153,8 +153,8 @@ post :update, 'id' => @iteration_one.id, 'project_id' => @project_one.id, 'iteration' => { 'length' => 'foo' } assert_redirected_to :controller => 'iterations', :action => 'edit', - :id => @iteration_one.id, - :project_id => @project_one.id + :id => @iteration_one.id.to_s, + :project_id => @project_one.id.to_s assert session[:edit_iteration] end @@ -163,8 +163,8 @@ 'project_id' => @project_one.id, 'selected_stories' => [ @story_one.id, @story_two.id ], 'move_to' => 0 assert_redirected_to :controller => 'iterations', :action => 'show', - :id => @iteration_one.id, - :project_id => @project_one.id + :id => @iteration_one.id.to_s, + :project_id => @project_one.id.to_s sc_one = Story.find(@story_one.id) assert_nil sc_one.iteration sc_two = Story.find(@story_two.id) @@ -182,8 +182,8 @@ 'selected_stories' => [@story_one.id, @story_two.id], 'move_to' => @iteration_two.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => @iteration_one.id, - :project_id => @project_one.id + :id => @iteration_one.id.to_s, + :project_id => @project_one.id.to_s sc_one = Story.find(@story_one.id) assert_equal @iteration_two, sc_one.iteration sc_two = Story.find(@story_two.id) @@ -223,7 +223,7 @@ post :move_stories, 'project_id' => @project_one.id, 'selected_stories' => [story.id], 'move_to' => @iteration_one.id assert_redirected_to :controller => 'stories', :action => 'index', - :project_id => @project_one.id + :project_id => @project_one.id.to_s assert_nil flash[:status] assert flash[:error] end Modified: branches/1.x/trunk/test/functional/stories_controller_test.rb ============================================================================== --- branches/1.x/trunk/test/functional/stories_controller_test.rb (original) +++ branches/1.x/trunk/test/functional/stories_controller_test.rb Mon Sep 26 12:42:17 2005 @@ -55,7 +55,7 @@ def test_delete get :delete, 'id' => @story_one.id, 'project_id' => @project_one.id assert_redirected_to :controller => 'stories', :action => 'index', - :project_id => @project_one.id + :project_id => @project_one.id.to_s assert_raise(ActiveRecord::RecordNotFound) { Story.find(@story_one.id) } end @@ -63,8 +63,8 @@ get :delete, 'id' => @story_one.id, 'project_id' => @project_one.id, :iteration_id => @iteration_one.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => @iteration_one.id, - :project_id => @project_one.id + :id => @iteration_one.id.to_s, + :project_id => @project_one.id.to_s assert_raise(ActiveRecord::RecordNotFound) { Story.find(@story_one.id) } end @@ -94,7 +94,7 @@ num = Story.count post :create, 'project_id' => @project_one.id, 'story' => { 'title' => '' } assert_redirected_to :controller => 'stories', :action => 'new', - :project_id => @project_one.id + :project_id => @project_one.id.to_s assert session[:new_story] end @@ -123,15 +123,16 @@ post :update, 'project_id' => @project_one.id, 'id' => @story_one.id, 'story' => { 'title' => '' } assert_redirected_to :controller => 'stories', :action => 'edit', - :project_id => @project_one.id, :id => @story_one.id + :project_id => @project_one.id.to_s, + :id => @story_one.id.to_s assert session[:edit_story] end def test_take_ownership get :take_ownership, 'id' => @story_one.id, 'project_id' => @project_one.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => @iteration_one.id, - :project_id => @project_one.id + :id => @iteration_one.id.to_s, + :project_id => @project_one.id.to_s assert flash[:status] assert_equal @request.session[:current_user], Story.find(@story_one.id).owner @@ -144,8 +145,8 @@ get :release_ownership, 'id' => @story_one.id, 'project_id' => @project_one.id assert_redirected_to :controller => 'iterations', :action => 'show', - :id => @iteration_one.id, - :project_id => @project_one.id + :id => @iteration_one.id.to_s, + :project_id => @project_one.id.to_s assert flash[:status] assert_nil Story.find(@story_one.id).owner end Modified: branches/1.x/trunk/test/test_helper.rb ============================================================================== --- branches/1.x/trunk/test/test_helper.rb (original) +++ branches/1.x/trunk/test/test_helper.rb Mon Sep 26 12:42:17 2005 @@ -1,10 +1,11 @@ ENV["RAILS_ENV"] = "test" -require File.dirname(__FILE__) + "/../config/environment" +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' def create_fixtures(*table_names) Propchange: branches/1.x/trunk/vendor/ ------------------------------------------------------------------------------ --- svn:externals (original) +++ svn:externals Mon Sep 26 12:42:17 2005 @@ -1,1 +1,1 @@ -rails http://dev.rubyonrails.org/svn/rails/tags/rel_0-13-0 +rails http://dev.rubyonrails.org/svn/rails/tags/rel_0-13-1 From johnwilger at gmail.com Mon Sep 26 13:04:01 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Mon, 26 Sep 2005 17:04:01 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r7 - in /trunk: ./ app/ app/apis/ app/controllers/ app/helpers/ app/models/ app/views/ app/views/layouts/ components/ config/ config/environments/ db/ doc/ lib/ log/ public/ public/images/ public/javascripts/ public/stylesheets/ script/ test/ test/fixtures/ test/functional/ test/mocks/ test/mocks/development/ test/mocks/test/ test/unit/ vendor/ Message-ID: <200509261704.j8QH41hx009884@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 13:03:48 2005 New Revision: 7 Log: Importing base Rails application (Rails 0.13.1). Added: trunk/ trunk/CHANGELOG trunk/README trunk/Rakefile trunk/app/ trunk/app/apis/ trunk/app/controllers/ trunk/app/controllers/application.rb trunk/app/helpers/ trunk/app/helpers/application_helper.rb trunk/app/models/ trunk/app/views/ trunk/app/views/layouts/ trunk/components/ trunk/config/ trunk/config/database.yml trunk/config/environment.rb trunk/config/environments/ trunk/config/environments/development.rb trunk/config/environments/production.rb trunk/config/environments/test.rb trunk/config/routes.rb trunk/db/ trunk/doc/ trunk/doc/README_FOR_APP trunk/lib/ trunk/log/ trunk/log/development.log trunk/log/production.log trunk/log/server.log trunk/log/test.log trunk/public/ trunk/public/.htaccess trunk/public/404.html trunk/public/500.html trunk/public/dispatch.cgi (with props) trunk/public/dispatch.fcgi (with props) trunk/public/dispatch.rb (with props) trunk/public/favicon.ico trunk/public/images/ trunk/public/index.html trunk/public/javascripts/ trunk/public/javascripts/controls.js trunk/public/javascripts/dragdrop.js trunk/public/javascripts/effects.js trunk/public/javascripts/prototype.js trunk/public/stylesheets/ trunk/script/ trunk/script/benchmarker (with props) trunk/script/breakpointer (with props) trunk/script/console (with props) trunk/script/destroy (with props) trunk/script/generate (with props) trunk/script/profiler (with props) trunk/script/runner (with props) trunk/script/server (with props) trunk/test/ trunk/test/fixtures/ trunk/test/functional/ trunk/test/mocks/ trunk/test/mocks/development/ trunk/test/mocks/test/ trunk/test/test_helper.rb trunk/test/unit/ trunk/vendor/ Added: trunk/CHANGELOG ============================================================================== --- trunk/CHANGELOG (added) +++ trunk/CHANGELOG Mon Sep 26 13:03:48 2005 @@ -1,0 +1,604 @@ +*0.13.1* (11 July, 2005) + +* 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 at 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. + +* Added a VERSION parameter to the migrate task that allows you to do "rake migrate VERSION=34" to migrate to the 34th version traveling up or down depending on the current version + +* Extend Ruby version check to include RUBY_RELEASE_DATE >= '2005-12-25', the final Ruby 1.8.2 release #1674 [court3nay at gmail.com] + +* Improved documentation for environment config files #1625 [court3nay at gmail.com] + + +*0.13.0* (6 July, 2005) + +* Changed the default logging level in config/environment.rb to INFO for production (so SQL statements won't be logged) + +* Added migration generator: ./script/generate migration add_system_settings + +* Added "migrate" as rake task to execute all the pending migrations from db/migrate + +* Fixed that model generator would make fixtures plural, even if ActiveRecord::Base.pluralize_table_names was false #1185 [Marcel Molina] + +* Added a DOCTYPE of HTML transitional to the HTML files generated by Rails #1124 [Michael Koziarski] + +* SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows. + +* Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi [skaes at web.de] + +* Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them. + +* RailsFCGIHandler (dispatch.fcgi) no longer tries to explicitly flush $stdout (CgiProcess#out always calls flush) + +* Fixed rakefile actions against PostgreSQL when the password is all numeric #1462 [michael at schubert.cx] + +* ActionMailer::Base subclasses are reloaded with the other rails components #1262 + +* Made the WEBrick adapter not use a mutex around action performance if ActionController::Base.allow_concurrency is true (default is false) + +* Fixed that mailer generator generated fixtures/plural while units expected fixtures/singular #1457 [Scott Barron] + +* Added a 'whiny nil' that's aim to ensure that when users pass nil to methods where that isn't appropriate, instead of NoMethodError? and the name of some method used by the framework users will see a message explaining what type of object was expected. Only active in test and development environments by default #1209 [Michael Koziarski] + +* Fixed the test_helper.rb to be safe for requiring controllers from multiple spots, like app/controllers/article_controller.rb and app/controllers/admin/article_controller.rb, without reloading the environment twice #1390 [Nicholas Seckar] + +* Fixed Webrick to escape + characters in URL's the same way that lighttpd and apache do #1397 [Nicholas Seckar] + +* Added -e/--environment option to script/runner #1408 [fbeausoleil at ftml.net] + +* Modernize the scaffold generator to use the simplified render and test methods and to change style from @params["id"] to params[:id]. #1367 + +* Added graceful exit from pressing CTRL-C during the run of the rails command #1150 [Caleb Tennis] + +* Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. [Jamis Buck] + +* Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. [Jamis Buck] + +* Added console --profile for profiling an IRB session #1154 [Jeremy Kemper] + +* Changed console_sandbox into console --sandbox #1154 [Jeremy Kemper] + + +*0.12.1* (20th April, 2005) + +* Upgraded to Active Record 1.10.1, Action Pack 1.8.1, Action Mailer 0.9.1, Action Web Service 0.7.1 + + +*0.12.0* (19th April, 2005) + +* Fixed that purge_test_database would use database settings from the development environment when recreating the test database #1122 [rails at cogentdude.com] + +* Added script/benchmarker to easily benchmark one or more statement a number of times from within the environment. Examples: + + # runs the one statement 10 times + script/benchmarker 10 'Person.expensive_method(10)' + + # pits the two statements against each other with 50 runs each + script/benchmarker 50 'Person.expensive_method(10)' 'Person.cheap_method(10)' + +* Added script/profiler to easily profile a single statement from within the environment. Examples: + + script/profiler 'Person.expensive_method(10)' + script/profiler 'Person.expensive_method(10)' 10 # runs the statement 10 times + +* Added Rake target clear_logs that'll truncate all the *.log files in log/ to zero #1079 [Lucas Carlson] + +* Added lazy typing for generate, such that ./script/generate cn == ./script/generate controller and the likes #1051 [k at v2studio.com] + +* Fixed that ownership is brought over in pg_dump during tests for PostgreSQL #1060 [pburleson at gmail.com] + +* Upgraded to Active Record 1.10.0, Action Pack 1.8.0, Action Mailer 0.9.0, Action Web Service 0.7.0, Active Support 1.0.4 + + +*0.11.1* (27th March, 2005) + +* Fixed the dispatch.fcgi use of a logger + +* Upgraded to Active Record 1.9.1, Action Pack 1.7.0, Action Mailer 0.8.1, Action Web Service 0.6.2, Active Support 1.0.3 + + +*0.11.0* (22th March, 2005) + +* Removed SCRIPT_NAME from the WEBrick environment to prevent conflicts with PATH_INFO #896 [Nicholas Seckar] + +* Removed ?$1 from the dispatch.f/cgi redirect line to get rid of 'complete/path/from/request.html' => nil being in the @params now that the ENV["REQUEST_URI"] is used to determine the path #895 [dblack/Nicholas Seckar] + +* Added additional error handling to the FastCGI dispatcher to catch even errors taking down the entire process + +* Improved the generated scaffold code a lot to take advantage of recent Rails developments #882 [Tobias Luetke] + +* Combined the script/environment.rb used for gems and regular files version. If vendor/rails/* has all the frameworks, then files version is used, otherwise gems #878 [Nicholas Seckar] + +* Changed .htaccess to allow dispatch.* to be called from a sub-directory as part of the push with Action Pack to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Luetke] + +* Added script/runner which can be used to run code inside the environment by eval'ing the first parameter. Examples: + + ./script/runner 'ReminderService.deliver' + ./script/runner 'Mailer.receive(STDIN.read)' + + This makes it easier to do CRON and postfix scripts without actually making a script just to trigger 1 line of code. + +* Fixed webrick_server cookie handling to allow multiple cookes to be set at once #800, #813 [dave at cherryville.org] + +* Fixed the Rakefile's interaction with postgresql to: + + 1. Use PGPASSWORD and PGHOST in the environment to fix prompting for + passwords when connecting to a remote db and local socket connections. + 2. Add a '-x' flag to pg_dump which stops it dumping privileges #807 [rasputnik] + 3. Quote the user name and use template0 when dumping so the functions doesn't get dumped too #855 [pburleson] + 4. Use the port if available #875 [madrobby] + +* Upgraded to Active Record 1.9.0, Action Pack 1.6.0, Action Mailer 0.8.0, Action Web Service 0.6.1, Active Support 1.0.2 + + +*0.10.1* (7th March, 2005) + +* Fixed rake stats to ignore editor backup files like model.rb~ #791 [skanthak] + +* Added exception shallowing if the DRb server can't be started (not worth making a fuss about to distract new users) #779 [Tobias Luetke] + +* Added an empty favicon.ico file to the public directory of new applications (so the logs are not spammed by its absence) + +* Fixed that scaffold generator new template should use local variable instead of instance variable #778 [Dan Peterson] + +* Allow unit tests to run on a remote server for PostgreSQL #781 [adamm at galacticasoftware.com] + +* Added web_service generator (run ./script/generate web_service for help) #776 [Leon Bredt] + +* Added app/apis and components to code statistics report #729 [Scott Barron] + +* Fixed WEBrick server to use ABSOLUTE_RAILS_ROOT instead of working_directory #687 [Nicholas Seckar] + +* Fixed rails_generator to be usable without RubyGems #686 [Cristi BALAN] + +* Fixed -h/--help for generate and destroy generators #331 + +* Added begin/rescue around the FCGI dispatcher so no uncaught exceptions can bubble up to kill the process (logs to log/fastcgi.crash.log) + +* Fixed that association#count would produce invalid sql when called sequentialy #659 [kanis at comcard.de] + +* Fixed test/mocks/testing to the correct test/mocks/test #740 + +* Added early failure if the Ruby version isn't 1.8.2 or above #735 + +* Removed the obsolete -i/--index option from the WEBrick servlet #743 + +* Upgraded to Active Record 1.8.0, Action Pack 1.5.1, Action Mailer 0.7.1, Action Web Service 0.6.0, Active Support 1.0.1 + + +*0.10.0* (24th February, 2005) + +* Changed default IP binding for WEBrick from 127.0.0.1 to 0.0.0.0 so that the server is accessible both locally and remotely #696 [Marcel] + +* Fixed that script/server -d was broken so daemon mode couldn't be used #687 [Nicholas Seckar] + +* Upgraded to breakpoint 92 which fixes: + + * overload IRB.parse_opts(), fixes #443 + => breakpoints in tests work even when running them via rake + * untaint handlers, might fix an issue discussed on the Rails ML + * added verbose mode to breakpoint_client + * less noise caused by breakpoint_client by default + * ignored TerminateLineInput exception in signal handler + => quiet exit on Ctrl-C + +* Added support for independent components residing in /components. Example: + + Controller: components/list/items_controller.rb + (holds a List::ItemsController class with uses_component_template_root called) + + Model : components/list/item.rb + (namespace is still shared, so an Item model in app/models will take precedence) + + Views : components/list/items/show.rhtml + + +* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 [Jeremy Kemper] + +* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 [Jeremy Kemper] + +* Changed script/console to default to development environment and drop --no-inspect #650 [Jeremy Kemper] + +* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 [Jeremy Kemper] + +* Added that running test_units and test_functional now performs the clone_structure_to_test as well #566 [rasputnik] + +* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [Jeremy Kemper] + +* Added Action Web Service as a new add-on framework for Action Pack [Leon Bredt] + +* Added Active Support as an independent utility and standard library extension bundle + +* Upgraded to Active Record 1.7.0, Action Pack 1.5.0, Action Mailer 0.7.0 + + +*0.9.5* (January 25th, 2005) + +* Fixed dependency reloading by switching to a remove_const approach where all Active Records, Active Record Observers, and Action Controllers are reloading by undefining their classes. This enables you to remove methods in all three types and see the change reflected immediately and it fixes #539. This also means that only those three types of classes will benefit from the const_missing and reloading approach. If you want other classes (like some in lib/) to reload, you must use require_dependency to do it. + +* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross] + +* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik] + +* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt at kurowski.net] + +* Fixed handling of syntax errors in models that had already been succesfully required once in the current interpreter + +* Fixed that models that weren't referenced in associations weren't being reloaded in the development mode by reinstating the reload + +* Fixed that generate scaffold would produce bad functional tests + +* Fixed that FCGI can also display SyntaxErrors + +* Upgraded to Active Record 1.6.0, Action Pack 1.4.0 + + +*0.9.4.1* (January 18th, 2005) + +* Added 5-second timeout to WordNet alternatives on creating reserved-word models #501 [Marcel Molina] + +* Fixed binding of caller #496 [Alexey] + +* Upgraded to Active Record 1.5.1, Action Pack 1.3.1, Action Mailer 0.6.1 + + +*0.9.4* (January 17th, 2005) + +* Added that ApplicationController will catch a ControllerNotFound exception if someone attempts to access a url pointing to an unexisting controller [Tobias Luetke] + +* Flipped code-to-test ratio around to be more readable #468 [Scott Baron] + +* Fixed log file permissions to be 666 instead of 777 (so they're not executable) #471 [Lucas Carlson] + +* Fixed that auto reloading would some times not work or would reload the models twice #475 [Tobias Luetke] + +* Added rewrite rules to deal with caching to public/.htaccess + +* Added the option to specify a controller name to "generate scaffold" and made the default controller name the plural form of the model. + +* Added that rake clone_structure_to_test, db_structure_dump, and purge_test_database tasks now pick up the source database to use from + RAILS_ENV instead of just forcing development #424 [Tobias Luetke] + +* Fixed script/console to work with Windows (that requires the use of irb.bat) #418 [octopod] + +* Fixed WEBrick servlet slowdown over time by restricting the load path reloading to mod_ruby + +* Removed Fancy Indexing as a default option on the WEBrick servlet as it made it harder to use various caching schemes + +* Upgraded to Active Record 1.5, Action Pack 1.3, Action Mailer 0.6 + + +*0.9.3* (January 4th, 2005) + +* Added support for SQLite in the auto-dumping/importing of schemas for development -> test #416 + +* Added automated rewriting of the shebang lines on installs through the gem rails command #379 [Manfred Stienstra] + +* Added ActionMailer::Base.deliver_method = :test to the test environment so that mail objects are available in ActionMailer::Base.deliveries + for functional testing. + +* Added protection for creating a model through the generators with a name of an existing class, like Thread or Date. + It'll even offer you a synonym using wordnet.princeton.edu as a look-up. No, I'm not kidding :) [Florian Gross] + +* Fixed dependency management to happen in a unified fashion for Active Record and Action Pack using the new Dependencies module. This means that + the environment options needs to change from: + + Before in development.rb: + ActionController::Base.reload_dependencies??= true ?? + ActiveRecord::Base.reload_associations?? ?? ??= true + + Now in development.rb: + Dependencies.mechanism = :load + + Before in production.rb and test.rb: + ActionController::Base.reload_dependencies??= false + ActiveRecord::Base.reload_associations?? ?? ??= false + + Now in production.rb and test.rb: + Dependencies.mechanism = :require + +* Fixed problems with dependency caching and controller hierarchies on Ruby 1.8.2 in development mode #351 + +* Fixed that generated action_mailers doesnt need to require the action_mailer since thats already done in the environment #382 [Lucas Carlson] + +* Upgraded to Action Pack 1.2.0 and Active Record 1.4.0 + + +*0.9.2* + +* Fixed CTRL-C exists from the Breakpointer to be a clean affair without error dumping [Kent Sibilev] + +* Fixed "rake stats" to work with sub-directories in models and controllers and to report the code to test ration [Scott Baron] + +* Added that Active Record associations are now reloaded instead of cleared to work with the new const_missing hook in Active Record. + +* Added graceful handling of an inaccessible log file by redirecting output to STDERR with a warning #330 [rainmkr] + +* Added support for a -h/--help parameter in the generator #331 [Ulysses] + +* Fixed that File.expand_path in config/environment.rb would fail when dealing with symlinked public directories [mjobin] + +* Upgraded to Action Pack 1.1.0 and Active Record 1.3.0 + + +*0.9.1* + +* Upgraded to Action Pack 1.0.1 for important bug fix + +* Updated gem dependencies + + +*0.9.0* + +* Renamed public/dispatch.servlet to script/server -- it wasn't really dispatching anyway as its delegating calls to public/dispatch.rb + +* Renamed AbstractApplicationController and abstract_application.rb to ApplicationController and application.rb, so that it will be possible + for the framework to automatically pick up on app/views/layouts/application.rhtml and app/helpers/application.rb + +* Added script/console that makes it even easier to start an IRB session for interacting with the domain model. Run with no-args to + see help. + +* Added breakpoint support through the script/breakpointer client. This means that you can break out of execution at any point in + the code, investigate and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find_all + breakpoint "Breaking out from the list" + end + end + + So the controller will accept the action, run the first line, then present you with a IRB prompt in the breakpointer window. + Here you can do things like: + + Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + + ...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + + Finally, when you're ready to resume execution, you press CTRL-D + +* Changed environments to be configurable through an environment variable. By default, the environment is "development", but you + can change that and set your own by configuring the Apache vhost with a string like (mod_env must be available on the server): + + SetEnv RAILS_ENV production + + ...if you're using WEBrick, you can pick the environment to use with the command-line parameters -e/--environment, like this: + + ruby public/dispatcher.servlet -e production + +* Added a new default environment called "development", which leaves the production environment to be tuned exclusively for that. + +* Added a start_server in the root of the Rails application to make it even easier to get started + +* Fixed public/.htaccess to use RewriteBase and share the same rewrite rules for all the dispatch methods + +* Fixed webrick_server to handle requests in a serialized manner (the Rails reloading infrastructure is not thread-safe) + +* Added support for controllers in directories. So you can have: + + app/controllers/account_controller.rb # URL: /account/ + app/controllers/admin/account_controller.rb # URL: /admin/account/ + + NOTE: You need to update your public/.htaccess with the new rules to pick it up + +* Added reloading for associations and dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use + those environments for development. This is turned on by default, but can be turned off with + ActiveRecord::Base.reload_associations = false and ActionController::Base.reload_dependencies = false in production environments. + +* Added support for sub-directories in app/models. So now you can have something like Basecamp with: + + app/models/accounting + app/models/project + app/models/participants + app/models/settings + + It's poor man's namespacing, but only for file-system organization. You still require files just like before. + Nothing changes inside the files themselves. + + +* Fixed a few references in the tests generated by new_mailer [Jeremy Kemper] + +* Added support for mocks in testing with test/mocks + +* Cleaned up the environments a bit and added global constant RAILS_ROOT + + +*0.8.5* (9) + +* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [Jeremy Kemper]: + + def test_complex_stuff + @david.projects << @new_project + breakpoint "Let's have a closer look at @david" + end + + You need to install dev-utils yourself for this to work ("gem install dev-util"). + +* Added shared generator behavior so future upgrades should be possible without manually copying over files [Jeremy Kemper] + +* Added the new helper style to both controller and helper templates [Jeremy Kemper] + +* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding [Jeremy Kemper] + +* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style + +* Fixed that new_model was generating singular table/fixture names + +* Upgraded to Action Mailer 0.4.0 + +* Upgraded to Action Pack 0.9.5 + +* Upgraded to Active Record 1.1.0 + + +*0.8.0 (15)* + +* Removed custom_table_name option for new_model now that the Inflector is as powerful as it is + +* Changed the default rake action to just do testing and separate API generation and coding statistics into a "doc" task. + +* Fixed WEBrick dispatcher to handle missing slashes in the URLs gracefully [alexey] + +* Added user option for all postgresql tool calls in the rakefile [elvstone] + +* Fixed problem with running "ruby public/dispatch.servlet" instead of "cd public; ruby dispatch.servlet" [alexey] + +* Fixed WEBrick server so that it no longer hardcodes the ruby interpreter used to "ruby" but will get the one used based + on the Ruby runtime configuration. [Marcel Molina Jr.] + +* Fixed Dispatcher so it'll route requests to magic_beans to MagicBeansController/magic_beans_controller.rb [Caio Chassot] + +* "new_controller MagicBeans" and "new_model SubscriptionPayments" will now both behave properly as they use the new Inflector. + +* Fixed problem with MySQL foreign key constraint checks in Rake :clone_production_structure_to_test target [Andreas Schwarz] + +* Changed WEBrick server to by default be auto-reloading, which is slower but makes source changes instant. + Class compilation cache can be turned on with "-c" or "--cache-classes". + +* Added "-b/--binding" option to WEBrick dispatcher to bind the server to a specific IP address (default: 127.0.0.1) [Kevin Temp] + +* dispatch.fcgi now DOESN'T set FCGI_PURE_RUBY as it was slowing things down for now reason [Andreas Schwarz] + +* Added new_mailer generator to work with Action Mailer + +* Included new framework: Action Mailer 0.3 + +* Upgraded to Action Pack 0.9.0 + +* Upgraded to Active Record 1.0.0 + + +*0.7.0* + +* Added an optional second argument to the new_model script that allows the programmer to specify the table name, + which will used to generate a custom table_name method in the model and will also be used in the creation of fixtures. + [Kevin Radloff] + +* script/new_model now turns AccountHolder into account_holder instead of accountholder [Kevin Radloff] + +* Fixed the faulty handleing of static files with WEBrick [Andreas Schwarz] + +* Unified function_test_helper and unit_test_helper into test_helper + +* Fixed bug with the automated production => test database dropping on PostgreSQL [dhawkins] + +* create_fixtures in both the functional and unit test helper now turns off the log during fixture generation + and can generate more than one fixture at a time. Which makes it possible for assignments like: + + @people, @projects, @project_access, @companies, @accounts = + create_fixtures "people", "projects", "project_access", "companies", "accounts" + +* Upgraded to Action Pack 0.8.5 (locally-scoped variables, partials, advanced send_file) + +* Upgraded to Active Record 0.9.5 (better table_name guessing, cloning, find_all_in_collection) + + +*0.6.5* + +* No longer specifies a template for rdoc, so it'll use whatever is default (you can change it in the rakefile) + +* The new_model generator will now use the same rules for plural wordings as Active Record + (so Category will give categories, not categorys) [Kevin Radloff] + +* dispatch.fcgi now sets FCGI_PURE_RUBY to true to ensure that it's the Ruby version that's loaded [danp] + +* Made the GEM work with Windows + +* Fixed bug where mod_ruby would "forget" the load paths added when switching between controllers + +* PostgreSQL are now supported for the automated production => test database dropping [Kevin Radloff] + +* Errors thrown by the dispatcher are now properly handled in FCGI. + +* Upgraded to Action Pack 0.8.0 (lots and lots and lots of fixes) + +* Upgraded to Active Record 0.9.4 (a bunch of fixes) + + +*0.6.0* + +* Added AbstractionApplicationController as a superclass for all controllers generated. This class can be used + to carry filters and methods that are to be shared by all. It has an accompanying ApplicationHelper that all + controllers will also automatically have available. + +* Added environments that can be included from any script to get the full Active Record and Action Controller + context running. This can be used by maintenance scripts or to interact with the model through IRB. Example: + + require 'config/environments/production' + + for account in Account.find_all + account.recalculate_interests + end + + A short migration script for an account model that had it's interest calculation strategy changed. + +* Accessing the index of a controller with "/weblog" will now redirect to "/weblog/" (only on Apache, not WEBrick) + +* Simplified the default Apache config so even remote requests are served off CGI as a default. + You'll now have to do something specific to activate mod_ruby and FCGI (like using the force urls). + This should make it easier for new comers that start on an external server. + +* Added more of the necessary Apache options to .htaccess to make it easier to setup + +* Upgraded to Action Pack 0.7.9 (lots of fixes) + +* Upgraded to Active Record 0.9.3 (lots of fixes) + + +*0.5.7* + +* Fixed bug in the WEBrick dispatcher that prevented it from getting parameters from the URL + (through GET requests or otherwise) + +* Added lib in root as a place to store app specific libraries + +* Added lib and vendor to load_path, so anything store within can be loaded directly. + Hence lib/redcloth.rb can be loaded with require "redcloth" + +* Upgraded to Action Pack 0.7.8 (lots of fixes) + +* Upgraded to Active Record 0.9.2 (minor upgrade) + + +*0.5.6* + +* Upgraded to Action Pack 0.7.7 (multipart form fix) + +* Updated the generated template stubs to valid XHTML files + +* Ensure that controllers generated are capitalized, so "new_controller TodoLists" + gives the same as "new_controller Todolists" and "new_controller todolists". + + +*0.5.5* + +* Works on Windows out of the box! (Dropped symlinks) + +* Added webrick dispatcher: Try "ruby public/dispatch.servlet --help" [Florian Gross] + +* Report errors about initialization to browser (instead of attempting to use uninitialized logger) + +* Upgraded to Action Pack 0.7.6 + +* Upgraded to Active Record 0.9.1 + +* Added distinct 500.html instead of reusing 404.html + +* Added MIT license + + +*0.5.0* + +* First public release Added: trunk/README ============================================================================== --- trunk/README (added) +++ trunk/README Mon Sep 26 13:03:48 2005 @@ -1,0 +1,190 @@ +== Welcome to Rails + +Rails is a web-application and persistance framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-build data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +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/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 +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/rails/actionpack/README.html. + + +== Requirements + +* Database and driver (MySQL, PostgreSQL, or SQLite) +* Rake[http://rake.rubyforge.org] for running tests and the generating documentation + +== Optionals + +* Apache 1.3.x or 2.x or lighttpd 1.3.11+ (or any FastCGI-capable webserver with a + mod_rewrite-like module) +* FastCGI (or mod_ruby) for better performance on Apache + +== Getting started + +1. Run the WEBrick servlet: ruby script/server + (run with --help for options) +2. Go to http://localhost:3000/ and get "Congratulations, you've put Ruby on Rails!" +3. Follow the guidelines on the "Congratulations, you've put Ruby on Rails!" screen + + +== Example for Apache conf + + + ServerName rails + DocumentRoot /path/application/public/ + ErrorLog /path/application/log/server.log + + + Options ExecCGI FollowSymLinks + AllowOverride all + Allow from all + Order allow,deny + + + +NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI +should be on and ".cgi" should respond. All requests from 127.0.0.1 goes +through CGI, so no Apache restart is necessary for changes. All other requests +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" ) + ) + ) + ) + + +== Debugging Rails + +Have "tail -f" commands running on both the server.log, production.log, and +test.log files. Rails will automatically display debugging and runtime +information to these files. Debugging info will also be shown in the browser +on requests from 127.0.0.1. + + +== Breakpoints + +Breakpoint support is available through the script/breakpointer client. This +means that you can break out of execution at any point in the code, investigate +and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find_all + breakpoint "Breaking out from the list" + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the breakpointer window. Here you can do things like: + +Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you press CTRL-D + + +== Console + +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 will launch it in the development environment. +Passing an argument will specify a different environment, like console production. + + +== Description of contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblog_controller.rb for + automated URL mapping. All controllers should descend from + ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descent from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblog/index.rhtml for the WeblogController#index action. All views uses eRuby + syntax. This directory can also be used to keep stylesheets, images, and so on + that can be symlinked to public. + +app/helpers + Holds view helpers that should be named like weblog_helper.rb. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +components + Self-contained mini-applications that can bundle controllers, models, and views together. + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains sub-directories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. + +vendor + External libraries that the application depend on. This directory is in the load path. Added: trunk/Rakefile ============================================================================== --- trunk/Rakefile (added) +++ trunk/Rakefile Mon Sep 26 13:03:48 2005 @@ -1,0 +1,202 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +$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 = "Rails Application 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 Added: trunk/app/controllers/application.rb ============================================================================== --- trunk/app/controllers/application.rb (added) +++ trunk/app/controllers/application.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,4 @@ +# The filters added to this controller will be run for all controllers in the application. +# Likewise will all the methods added be available for all controllers. +class ApplicationController < ActionController::Base +end Added: trunk/app/helpers/application_helper.rb ============================================================================== --- trunk/app/helpers/application_helper.rb (added) +++ trunk/app/helpers/application_helper.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,3 @@ +# The methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end Added: trunk/config/database.yml ============================================================================== --- trunk/config/database.yml (added) +++ trunk/config/database.yml Mon Sep 26 13:03:48 2005 @@ -1,0 +1,23 @@ +development: + adapter: mysql + database: rails_development + host: localhost + username: root + password: + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: rails_test + host: localhost + username: root + password: + +production: + adapter: mysql + database: rails_production + host: localhost + username: root + password: Added: trunk/config/environment.rb ============================================================================== --- trunk/config/environment.rb (added) +++ trunk/config/environment.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,87 @@ +# Load the Rails framework and configure your application. +# You can include your own configuration at the end of this file. +# +# Be sure to restart your webserver when you modify this file. + +# The path to the root directory of your application. +RAILS_ROOT = File.join(File.dirname(__FILE__), '..') + +# The environment your application is currently running. Don't set +# this here; put it in your webserver's configuration as the RAILS_ENV +# environment variable instead. +# +# See config/environments/*.rb for environment-specific configuration. +RAILS_ENV = ENV['RAILS_ENV'] || 'development' + + +# Load the Rails framework. Mock classes for testing come first. +ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] + +# Then model subdirectories. +ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) +ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) + +# Followed by the standard includes. +ADDITIONAL_LOAD_PATHS.concat %w( + app + app/models + app/controllers + app/helpers + app/apis + components + config + lib + vendor + vendor/rails/railties + vendor/rails/railties/lib + vendor/rails/actionpack/lib + vendor/rails/activesupport/lib + vendor/rails/activerecord/lib + vendor/rails/actionmailer/lib + vendor/rails/actionwebservice/lib +).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } + +# Prepend to $LOAD_PATH +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } + +# Require Rails libraries. +require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails") + +require 'active_support' +require 'active_record' +require 'action_controller' +require 'action_mailer' +require 'action_web_service' + +# Environment-specific configuration. +require_dependency "environments/#{RAILS_ENV}" +ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } +ActiveRecord::Base.establish_connection + + +# Configure defaults if the included environment did not. +begin + RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log") + RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG) +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." + ) +end + +[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER } +[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" } + +# Set up routes. +ActionController::Routing::Routes.reload + +Controllers = Dependencies::LoadingModule.root( + File.join(RAILS_ROOT, 'app', 'controllers'), + File.join(RAILS_ROOT, 'components') +) + +# Include your app's configuration here: + Added: trunk/config/environments/development.rb ============================================================================== --- trunk/config/environments/development.rb (added) +++ trunk/config/environments/development.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,14 @@ +# 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. + +# Log error messages when you accidentally call methods on nil. +require 'active_support/whiny_nil' + +# Reload code; show full error reports; disable caching. +Dependencies.mechanism = :load +ActionController::Base.consider_all_requests_local = true +ActionController::Base.perform_caching = false + +# The breakpoint server port that script/breakpointer connects to. +BREAKPOINT_SERVER_PORT = 42531 Added: trunk/config/environments/production.rb ============================================================================== --- trunk/config/environments/production.rb (added) +++ trunk/config/environments/production.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,8 @@ +# 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. + +# 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 Added: trunk/config/environments/test.rb ============================================================================== --- trunk/config/environments/test.rb (added) +++ trunk/config/environments/test.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,17 @@ +# The test environment is used exclusively to run your application's +# 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! + +# Log error messages when you accidentally call methods on nil. +require 'active_support/whiny_nil' + +# 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 + +# 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 Added: trunk/config/routes.rb ============================================================================== --- trunk/config/routes.rb (added) +++ trunk/config/routes.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,19 @@ +ActionController::Routing::Routes.draw do |map| + # Add your own custom routes here. + # The priority is based upon order of creation: first created -> highest priority. + + # Here's a sample route: + # 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' + + # Install the default route as the lowest priority. + map.connect ':controller/:action/:id' +end Added: trunk/doc/README_FOR_APP ============================================================================== --- trunk/doc/README_FOR_APP (added) +++ trunk/doc/README_FOR_APP Mon Sep 26 13:03:48 2005 @@ -1,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. Added: trunk/log/development.log ============================================================================== --- trunk/log/development.log (added) +++ trunk/log/development.log Mon Sep 26 13:03:48 2005 @@ -1,0 +1,1 @@ +nil Added: trunk/log/production.log ============================================================================== --- trunk/log/production.log (added) +++ trunk/log/production.log Mon Sep 26 13:03:48 2005 @@ -1,0 +1,1 @@ +nil Added: trunk/log/server.log ============================================================================== --- trunk/log/server.log (added) +++ trunk/log/server.log Mon Sep 26 13:03:48 2005 @@ -1,0 +1,1 @@ +nil Added: trunk/log/test.log ============================================================================== --- trunk/log/test.log (added) +++ trunk/log/test.log Mon Sep 26 13:03:48 2005 @@ -1,0 +1,1 @@ +nil Added: trunk/public/.htaccess ============================================================================== --- trunk/public/.htaccess (added) +++ trunk/public/.htaccess Mon Sep 26 13:03:48 2005 @@ -1,0 +1,32 @@ +# General Apache options +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 +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# 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" Added: trunk/public/404.html ============================================================================== --- trunk/public/404.html (added) +++ trunk/public/404.html Mon Sep 26 13:03:48 2005 @@ -1,0 +1,8 @@ + + + +

    File not found

    +

    Change this error message for pages not found in public/404.html

    + + Added: trunk/public/500.html ============================================================================== --- trunk/public/500.html (added) +++ trunk/public/500.html Mon Sep 26 13:03:48 2005 @@ -1,0 +1,8 @@ + + + +

    Application error (Apache)

    +

    Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html

    + + Added: trunk/public/dispatch.cgi ============================================================================== --- trunk/public/dispatch.cgi (added) +++ trunk/public/dispatch.cgi Mon Sep 26 13:03:48 2005 @@ -1,0 +1,10 @@ +#!/usr/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch Propchange: trunk/public/dispatch.cgi ------------------------------------------------------------------------------ svn:executable = Added: trunk/public/dispatch.fcgi ============================================================================== --- trunk/public/dispatch.fcgi (added) +++ trunk/public/dispatch.fcgi Mon Sep 26 13:03:48 2005 @@ -1,0 +1,24 @@ +#!/usr/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! Propchange: trunk/public/dispatch.fcgi ------------------------------------------------------------------------------ svn:executable = Added: trunk/public/dispatch.rb ============================================================================== --- trunk/public/dispatch.rb (added) +++ trunk/public/dispatch.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,10 @@ +#!/usr/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch Propchange: trunk/public/dispatch.rb ------------------------------------------------------------------------------ svn:executable = Added: trunk/public/favicon.ico ============================================================================== (empty) Added: trunk/public/index.html ============================================================================== --- trunk/public/index.html (added) +++ trunk/public/index.html Mon Sep 26 13:03:48 2005 @@ -1,0 +1,78 @@ + + + + Rails: Welcome on board + + + + +

    Congratulations, you've put Ruby on Rails!

    + +

    Before you move on, verify that the following conditions have been met:

    + +
      +
    1. The log and public directories must be writable to the web server (chmod -R 775 log and chmod -R 775 public). +
    2. + The shebang line in the public/dispatch* files must reference your Ruby installation.
      + You might need to change it to #!/usr/bin/env ruby or point directly at the installation. +
    3. +
    4. + Rails on Apache needs to have the cgi handler and mod_rewrite enabled.
      + Somewhere in your httpd.conf, you should have:
      + AddHandler cgi-script .cgi
      + LoadModule rewrite_module libexec/httpd/mod_rewrite.so
      + AddModule mod_rewrite.c +
    5. +
    + +

    Take the following steps to get started:

    + +
      +
    1. Create empty development and test databases for your application.
      + Recommendation: Use *_development and *_test names, such as basecamp_development and basecamp_test
      + Warning: Don't point your test database at your development database, it'll destroy the latter on test runs! +
    2. Edit config/database.yml with your database settings. +
    3. Create controllers and models using the generator in script/generate
      + Help: Run the generator with no arguments for documentation +
    4. See all the tests run by running rake. +
    5. Develop your Rails application! +
    6. Setup Apache with FastCGI (and Ruby bindings), if you need better performance +
    7. Remove the dispatches you don't use (so if you're on FastCGI, delete/move dispatch.rb, dispatch.cgi and gateway.cgi)
    8. +
    + +

    + Trying to setup a default page for Rails using Routes? You'll have to delete this file (public/index.html) to get under way. Then define a new route in config/routes.rb of the form: +

      map.connect '', :controller => 'wiki/page', :action => 'show', :title => 'Welcome'
    +

    + +

    + Having problems getting up and running? First try debugging it yourself by looking at the log files.
    + Then try the friendly Rails community on the web or on IRC + (FreeNode#rubyonrails). +

    + + + Added: trunk/public/javascripts/controls.js ============================================================================== --- trunk/public/javascripts/controls.js (added) +++ trunk/public/javascripts/controls.js Mon Sep 26 13:03:48 2005 @@ -1,0 +1,446 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// +// 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; +} + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// 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 +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// 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 +// 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. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + base_initialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.has_focus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entry_count = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = {} + + this.options.tokens = this.options.tokens || new Array(); + this.options.frequency = this.options.frequency || 0.4; + this.options.min_chars = this.options.min_chars || 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'; + } + new 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; + + 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') { + 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); + } + }, + + hide: function() { + if(this.update.style.display=='') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.indicator) Element.show(this.indicator); + }, + + stopIndicator: function() { + if(this.indicator) Element.hide(this.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.select_entry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.mark_previous(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.mark_next(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.has_focus = 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) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.select_entry(); + Event.stop(event); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.has_focus = false; + this.active = false; + }, + + render: function() { + if(this.entry_count > 0) { + for (var i = 0; i < this.entry_count; 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); + + this.show(); + this.active = true; + } + } else this.hide(); + }, + + mark_previous: function() { + if(this.index > 0) this.index-- + else this.index = this.entry_count-1; + }, + + mark_next: function() { + if(this.index < this.entry_count-1) this.index++ + else this.index = 0; + }, + + get_entry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + get_current_entry: function() { + return this.get_entry(this.index); + }, + + select_entry: function() { + this.active = false; + value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); + this.updateElement(value); + this.element.focus(); + }, + + 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+/); + if (whitespace) + new_value += whitespace[0]; + this.element.value = new_value + value; + } else { + this.element.value = value; + } + }, + + updateChoices: function(choices) { + if(!this.changed && this.has_focus) { + 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.update.firstChild.childNodes.length; + for (var i = 0; i < this.entry_count; i++) { + entry = this.get_entry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entry_count = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getEntry().length>=this.options.min_chars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getEntry: function() { + var token_pos = this.findLastToken(); + if (token_pos != -1) + var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var last_token_pos = -1; + + for (var i=0; i last_token_pos) + last_token_pos = this_token_pos; + } + return last_token_pos; + } +} + +Ajax.Autocompleter = Class.create(); +Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), +Object.extend(new Ajax.Base(), { + initialize: function(element, update, url, options) { + this.base_initialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this) + this.options.method = 'post'; + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.element.name) + '=' + + encodeURIComponent(this.getEntry()); + + 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 +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partial_search - 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). +// +// - full_search - Search anywhere in autocomplete array strings. +// +// - partial_chars - How many characters to enter before triggering +// a partial match (unlike min_chars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignore_case - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +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.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partial_search: true, + partial_chars: 2, + ignore_case: true, + full_search: false, + selector: function(instance) { + var ret = new Array(); // Beginning matches + var partial = new Array(); // Inside matches + var entry = instance.getEntry(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; + var found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (found_pos != -1) { + if (found_pos == 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) + "
  • "); + break; + } + } + + found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : + elem.indexOf(entry, found_pos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); Added: trunk/public/javascripts/dragdrop.js ============================================================================== --- trunk/public/javascripts/dragdrop.js (added) +++ trunk/public/javascripts/dragdrop.js Mon Sep 26 13:03:48 2005 @@ -1,0 +1,537 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// 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; + } +} + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: false, + + remove: function(element) { + for(var i = 0; i < this.drops.length; i++) + if(this.drops[i].element == element) + this.drops.splice(i,1); + }, + + add: function(element) { + var element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = new Array(); + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + for(var i=0; i0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +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); + } + } + } + } + + // 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]); + + } + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + serialize: function(element) { + var element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id + }, arguments[1] || {}); + + var items = $(element).childNodes; + var queryComponents = new Array(); + + for(var i=0; i= this.finishOn) { + this.render(this.options.to); + if(this.finish) this.finish(); + if(this.options.afterFinish) this.options.afterFinish(this); + 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; + } + this.timeout = setTimeout(this.loop.bind(this), 10); + }, + render: function(pos) { + 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); + if(this.update) this.update(pos); + if(this.options.afterUpdate) this.options.afterUpdate(this); + }, + cancel: function() { + if(this.timeout) clearTimeout(this.timeout); + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + for (var i = 0; i < this.effects.length; i++) + this.effects[i].render(position); + }, + finish: function(position) { + for (var i = 0; i < this.effects.length; i++) + if(this.effects[i].finish) this.effects[i].finish(position); + } +}); + +// 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, + 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+")"; + } +}); + +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]); + }, + update: function(position) { + topd = this.toTop * position + this.originalTop; + leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0 + }, 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); + }, + + setDimensions: function(width, height) { + if(this.options.scaleX) this.element.style.width = width + 'px'; + if(this.options.scaleY) this.element.style.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"; + } else { + if(this.options.scaleY) this.element.style.top = -topd + "px"; + if(this.options.scaleX) this.element.style.left = -leftd + "px"; + } + } + } +}); + +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 + }, 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); + }, + 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)) ]; + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + }, + finish: function() { + this.element.style.backgroundColor = this.options.restorecolor; + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (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] || {}); + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- prepackaged effects ------------- */ + +Effect.Fade = function(element) { + options = Object.extend({ + from: 1.0, + to: 0.0, + afterFinish: function(effect) + { Element.hide(effect.element); + effect.setOpacity(1); } + }, arguments[1] || {}); + new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + options = Object.extend({ + from: 0.0, + to: 1.0, + beforeStart: function(effect) + { effect.setOpacity(0); + Element.show(effect.element); }, + afterUpdate: function(effect) + { Element.show(effect.element); } + }, arguments[1] || {}); + 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) + { effect.effects[0].element.style.position = 'absolute'; }, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + } + ); +} + +Effect.BlindUp = function(element) { + Element.makeClipping(element); + new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + $(element).style.height = '0px'; + Element.makeClipping(element); + Element.show(element); + new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterFinish: function(effect) { + Element.undoClipping(effect.element); + } + }, 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); } + } ) + } + } ); +} + +Effect.DropOut = function(element) { + 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); } + }); +} + +Effect.Shake = function(element) { + new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinish: function(effect) { + }}) }}) }}) }}) }}) }}); +} + +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, + 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); } + }, 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, + Object.extend({ scaleContent: false, + scaleX: false, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.Squish = function(element) { + new Effect.Scale(element, 0, + { afterFinish: function(effect) { Element.hide(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 direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeUpdate: function(effect) { $(element).style.height = '0px'; }, + afterFinish: 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, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], + options); } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + 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); +} + +Effect.Pulsate = function(element) { + var options = arguments[1] || {}; + 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); } + }, 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); +} Added: trunk/public/javascripts/prototype.js ============================================================================== --- trunk/public/javascripts/prototype.js (added) +++ trunk/public/javascripts/prototype.js Mon Sep 26 13:03:48 2005 @@ -1,0 +1,1038 @@ +/* Prototype JavaScript framework, version 1.3.1 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.3.1', + emptyFunction: function() {} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.prototype.extend = function(object) { + return Object.extend.apply(this, [this, object]); +} + +Function.prototype.bind = function(object) { + var __method = this; + return function() { + __method.apply(object, arguments); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + __method.call(object, event || window.event); + } +} + +Number.prototype.toColorPart = function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; +} + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + 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({ + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0].nodeValue; + } +}); + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + } +} + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + }.extend(options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = (new Ajax.Base()).extend({ + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + if (this.options.method == 'get') + url += '?' + parameters; + + this.transport.open(this.options.method, url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + 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. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] + || Prototype.emptyFunction)(this.transport); + + (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; + +Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function() { + this.updateContent(); + onComplete(this.transport); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var response = this.transport.responseText.replace(match, ''); + var scripts = this.transport.responseText.match(match); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + receiver.innerHTML = response; + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout((function() {this.onComplete( + this.transport)}).bind(this), 10); + } + + if (this.options.evalScripts && scripts) { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = 1; + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Ajax.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = + (element.style.display == 'none' ? '' : 'none'); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + 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; + }, + + addClassName: function(element, className) { + element = $(element); + Element.removeClassName(element, className); + element.className += ' ' + 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; + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + var element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +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); + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.fragment = this.range.createContextualFragment(this.content); + this.insertContent(); + } + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, this.element); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function() { + this.element.insertBefore(this.fragment, this.element.firstChild); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function() { + this.element.appendChild(this.fragment); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, + this.element.nextSibling); + } +}); + +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + 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(); + } +} + +/*--------------------------------------------------------------------------*/ + +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(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + 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)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + focusFirstElement: function(form) { + var form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(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]); + }, + + getValue: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + 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 [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + 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) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +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) { + this.callback(this.element, value); + 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 'radio': + element.target = this; + element.prev_onclick = element.onclick || Prototype.emptyFunction; + element.onclick = function() { + this.prev_onclick(); + this.target.onElementEvent(); + } + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + element.target = this; + element.prev_onchange = element.onchange || Prototype.emptyFunction; + element.onchange = function() { + this.prev_onchange(); + this.target.onElementEvent(); + } + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ + getValue: function() { + return Form.serialize(this.element); + } +}); + + +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + ((navigator.appVersion.indexOf('AppleWebKit') > 0) + || 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) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* 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, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + 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) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + } +} Added: trunk/script/benchmarker ============================================================================== --- trunk/script/benchmarker (added) +++ trunk/script/benchmarker Mon Sep 26 13:03:48 2005 @@ -1,0 +1,19 @@ +#!/usr/bin/ruby + +if ARGV.empty? + puts "Usage: benchmarker times 'Person.expensive_way' 'Person.another_expensive_way' ..." + exit +end + +require File.dirname(__FILE__) + '/../config/environment' +require 'benchmark' +include Benchmark + +# Don't include compilation in the benchmark +ARGV[1..-1].each { |expression| eval(expression) } + +bm(6) do |x| + ARGV[1..-1].each_with_index do |expression, idx| + x.report("##{idx + 1}") { ARGV[0].to_i.times { eval(expression) } } + end +end Propchange: trunk/script/benchmarker ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/breakpointer ============================================================================== --- trunk/script/breakpointer (added) +++ trunk/script/breakpointer Mon Sep 26 13:03:48 2005 @@ -1,0 +1,4 @@ +#!/usr/bin/ruby +require 'rubygems' +require_gem 'rails' +require 'breakpoint_client' Propchange: trunk/script/breakpointer ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/console ============================================================================== --- trunk/script/console (added) +++ trunk/script/console Mon Sep 26 13:03:48 2005 @@ -1,0 +1,23 @@ +#!/usr/bin/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" Propchange: trunk/script/console ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/destroy ============================================================================== --- trunk/script/destroy (added) +++ trunk/script/destroy Mon Sep 26 13:03:48 2005 @@ -1,0 +1,7 @@ +#!/usr/bin/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) Propchange: trunk/script/destroy ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/generate ============================================================================== --- trunk/script/generate (added) +++ trunk/script/generate Mon Sep 26 13:03:48 2005 @@ -1,0 +1,7 @@ +#!/usr/bin/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) Propchange: trunk/script/generate ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/profiler ============================================================================== --- trunk/script/profiler (added) +++ trunk/script/profiler Mon Sep 26 13:03:48 2005 @@ -1,0 +1,34 @@ +#!/usr/bin/ruby +if ARGV.empty? + $stderr.puts "Usage: profiler 'Person.expensive_method(10)' [times]" + exit(1) +end + +# Keep the expensive require out of the profile. +$stderr.puts 'Loading Rails...' +require File.dirname(__FILE__) + '/../config/environment' + +# Define a method to profile. +if ARGV[1] and ARGV[1].to_i > 1 + eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" +else + eval "def profile_me() #{ARGV[0]} end" +end + +# Use the ruby-prof extension if available. Fall back to stdlib profiler. +begin + require 'prof' + $stderr.puts 'Using the ruby-prof extension.' + Prof.clock_mode = Prof::GETTIMEOFDAY + Prof.start + profile_me + results = Prof.stop + require 'rubyprof_ext' + Prof.print_profile(results, $stderr) +rescue LoadError + $stderr.puts 'Using the standard Ruby profiler.' + Profiler__.start_profile + profile_me + Profiler__.stop_profile + Profiler__.print_profile($stderr) +end Propchange: trunk/script/profiler ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/runner ============================================================================== --- trunk/script/runner (added) +++ trunk/script/runner Mon Sep 26 13:03:48 2005 @@ -1,0 +1,29 @@ +#!/usr/bin/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) Propchange: trunk/script/runner ------------------------------------------------------------------------------ svn:executable = Added: trunk/script/server ============================================================================== --- trunk/script/server (added) +++ trunk/script/server Mon Sep 26 13:03:48 2005 @@ -1,0 +1,49 @@ +#!/usr/bin/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) Propchange: trunk/script/server ------------------------------------------------------------------------------ svn:executable = Added: trunk/test/test_helper.rb ============================================================================== --- trunk/test/test_helper.rb (added) +++ trunk/test/test_helper.rb Mon Sep 26 13:03:48 2005 @@ -1,0 +1,26 @@ +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/" + +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 + + # Add more helper methods to be used by all tests here... +end From johnwilger at gmail.com Mon Sep 26 13:08:52 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Mon, 26 Sep 2005 17:08:52 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r8 - in /trunk: ./ CHANGELOG README config/ config/database.yml config/database.yml.orig db/ doc/ log/ log/development.log log/production.log log/server.log log/test.log public/index.html vendor/ Message-ID: <200509261708.j8QH8qqo009889@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 13:08:48 2005 New Revision: 8 Log: Set up repository properties for development environement and removed files from repo that are not to be versioned. Added: trunk/config/database.yml.orig - copied unchanged from r7, trunk/config/database.yml Removed: trunk/CHANGELOG trunk/README trunk/config/database.yml trunk/doc/ trunk/log/development.log trunk/log/production.log trunk/log/server.log trunk/log/test.log trunk/public/index.html Modified: trunk/ (props changed) trunk/config/ (props changed) trunk/db/ (props changed) trunk/log/ (props changed) trunk/vendor/ (props changed) Propchange: trunk/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Mon Sep 26 13:08:48 2005 @@ -1,0 +1,1 @@ +doc Propchange: trunk/config/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Mon Sep 26 13:08:48 2005 @@ -1,0 +1,1 @@ +database.yml Propchange: trunk/db/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Mon Sep 26 13:08:48 2005 @@ -1,0 +1,1 @@ +development_structure.sql Propchange: trunk/log/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Mon Sep 26 13:08:48 2005 @@ -1,0 +1,1 @@ +*.log Propchange: trunk/vendor/ ------------------------------------------------------------------------------ --- svn:externals (added) +++ svn:externals Mon Sep 26 13:08:48 2005 @@ -1,0 +1,1 @@ +rails http://dev.rubyonrails.org/svn/rails/tags/rel_0-13-1 From johnwilger at gmail.com Mon Sep 26 16:46:57 2005 From: johnwilger at gmail.com (John Wilger) Date: Mon, 26 Sep 2005 16:46:57 -0400 Subject: [eXPlainPMT Developers] New Post to Weblog: Subversion, for Now Message-ID: <43385e4165e_73de..fdbfe9bcc2a9@porkchop.wilger.local.tmail> ------------------------------------------------------------------------------ Visit the eXPlainPMT Weblog at http://explainpmt.com to see this post on the web! ------------------------------------------------------------------------------ OK, I think we'll go with Subversion for SCM at the moment (sorry Scott ;-) ). The repository is accessible at [https://johnwilger.com/explainpmt](https://johnwilger.com/explainpmt) (note that it's http**s**). The trunkline for the version 2.x rewrite is at [https://johnwilger.com/explainpmt/trunk](https://johnwilger.com/explainpmt/trunk) and the 1.x development branch is at [https://johnwilger.com/explainpmt/branches/1.x/trunk](https://johnwilger.com/explainpmt/branches/1.x/trunk). Those of you who will be needing commit access need to a) sign up for [the developers mailing list](http://rubyforge.org/mailman/listinfo/explainpmt-developers), and b) send me the username and password you want to use to access the repository as well as the email address you used to sign up for the mailing list (so that the list doesn't bounce your commit messages). From johnwilger at gmail.com Mon Sep 26 22:00:18 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Tue, 27 Sep 2005 02:00:18 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r9 - /branches/1.x/trunk/app/views/dashboard/project.rhtml Message-ID: <200509270200.j8R20JII011611@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 22:00:10 2005 New Revision: 9 Log: Fixed broken project dashboard. Modified: branches/1.x/trunk/app/views/dashboard/project.rhtml Modified: branches/1.x/trunk/app/views/dashboard/project.rhtml ============================================================================== --- branches/1.x/trunk/app/views/dashboard/project.rhtml (original) +++ branches/1.x/trunk/app/views/dashboard/project.rhtml Mon Sep 26 22:00:10 2005 @@ -11,7 +11,7 @@ :action => 'index', :project_id => @project.id } -stories_table = CollectionTable.new(@stories, +stories_table = CollectionTableHelper::CollectionTable.new(@stories, [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], From johnwilger at gmail.com Mon Sep 26 22:05:51 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Tue, 27 Sep 2005 02:05:51 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r10 - in /branches/1.x/trunk/app: helpers/application_helper.rb views/layouts/main.rhtml Message-ID: <200509270205.j8R25qJW011639@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 22:05:48 2005 New Revision: 10 Log: Fixed version number display in footer. Modified: branches/1.x/trunk/app/helpers/application_helper.rb branches/1.x/trunk/app/views/layouts/main.rhtml Modified: branches/1.x/trunk/app/helpers/application_helper.rb ============================================================================== --- branches/1.x/trunk/app/helpers/application_helper.rb (original) +++ branches/1.x/trunk/app/helpers/application_helper.rb Mon Sep 26 22:05:48 2005 @@ -19,7 +19,7 @@ # The methods added to this helper will be available to all templates in the application. module ApplicationHelper - VERSION = '1.3.1' + VERSION = '1.3.3' # Used to determine if the currently logged in user has administrative # privileges Modified: branches/1.x/trunk/app/views/layouts/main.rhtml ============================================================================== --- branches/1.x/trunk/app/views/layouts/main.rhtml (original) +++ branches/1.x/trunk/app/views/layouts/main.rhtml Mon Sep 26 22:05:48 2005 @@ -69,7 +69,7 @@ <%= @content_for_layout %> From johnwilger at gmail.com Mon Sep 26 22:06:50 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Tue, 27 Sep 2005 02:06:50 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r11 - /tags/r1.3.3/ Message-ID: <200509270206.j8R26ofr011647@porkchop.wilger.local> Author: jwilger Date: Mon Sep 26 22:06:49 2005 New Revision: 11 Log: tagging 1.3.3 release. Added: tags/r1.3.3/ - copied from r10, branches/1.x/trunk/ From johnwilger at gmail.com Mon Sep 26 22:33:32 2005 From: johnwilger at gmail.com (John Wilger) Date: Mon, 26 Sep 2005 22:33:32 -0400 Subject: [eXPlainPMT Developers] New Post to Weblog: eXPlainPMT 1.3.3 Released Message-ID: <4338af7ce6d70_2e5b..fdbfcabcc16a@porkchop.wilger.local.tmail> ------------------------------------------------------------------------------ Visit the eXPlainPMT Weblog at http://explainpmt.com to see this post on the web! ------------------------------------------------------------------------------ Looks like the last release (earlier today) missed a couple of things when it comes to Rails 0.13.1 compatibility. This one should take care of it. Go get [eXPlainPMT 1.3.3](http://rubyforge.org/frs/?group_id=617&release_id=3011). From johnwilger at gmail.com Tue Sep 27 12:58:13 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Tue, 27 Sep 2005 16:58:13 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r12 - in /trunk: app/controllers/main_controller.rb app/helpers/main_helper.rb app/views/main/ app/views/main/dashboard.rhtml config/routes.rb test/functional/main_controller_test.rb test/functional/routing_test.rb Message-ID: <200509271658.j8RGwD4L013634@porkchop.wilger.local> Author: jwilger Date: Tue Sep 27 12:58:05 2005 New Revision: 12 Log: Added static "Dashboard" view mockup. No CSS styling applied yet. Added: trunk/app/controllers/main_controller.rb trunk/app/helpers/main_helper.rb trunk/app/views/main/ trunk/app/views/main/dashboard.rhtml trunk/test/functional/main_controller_test.rb trunk/test/functional/routing_test.rb Modified: trunk/config/routes.rb Added: trunk/app/controllers/main_controller.rb ============================================================================== --- trunk/app/controllers/main_controller.rb (added) +++ trunk/app/controllers/main_controller.rb Tue Sep 27 12:58:05 2005 @@ -1,0 +1,4 @@ +class MainController < ApplicationController + def dashboard + end +end Added: trunk/app/helpers/main_helper.rb ============================================================================== --- trunk/app/helpers/main_helper.rb (added) +++ trunk/app/helpers/main_helper.rb Tue Sep 27 12:58:05 2005 @@ -1,0 +1,2 @@ +module MainHelper +end Added: trunk/app/views/main/dashboard.rhtml ============================================================================== --- trunk/app/views/main/dashboard.rhtml (added) +++ trunk/app/views/main/dashboard.rhtml Tue Sep 27 12:58:05 2005 @@ -1,0 +1,129 @@ + + + + + eXPlainPMT » Dashboard + + + + +
    +

    My Projects

    +
      +
    • <%= link_to 'Project One', :action => 'project_overview' %>
    • +
    • <%= link_to 'Project Two', :action => 'project_overview' %>
    • +
    • <%= link_to 'Project Three', :action => 'project_overview' %>
    • +
    • <%= link_to 'Project Four', :action => 'project_overview' %>
    • +
    • <%= link_to 'Project Five', :action => 'project_overview' %>
    • +
    +
    + +
    +
    +

    My Story Cards

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     ProjectIDNameStatus  
    <%= link_to 'Project Three', :action => 'project_overview' %><%= link_to 'SC4', :action => 'view_story_card' %><%= link_to 'check out', :action => 'view_story_card' %>In Progress<%= link_to 'release', :action => 'release_story_card' %><%= link_to 'edit', :action => 'edit_story_card' %>
    <%= link_to 'Project Three', :action => 'project_overview' %><%= link_to 'SC19', :action => 'view_story_card' %><%= link_to 'redeem a coupon', :action => 'view_story_card' %>Blocked<%= link_to 'release', :action => 'release_story_card' %><%= link_to 'edit', :action => 'edit_story_card' %>
    <%= link_to 'Project Two', :action => 'project_overview' %><%= link_to 'SC1', :action => 'view_story_card' %><%= link_to 'create new work-item', :action => 'view_story_card' %>Complete<%= link_to 'release', :action => 'release_story_card' %><%= link_to 'edit', :action => 'edit_story_card' %>
    +
    +
    + +
    +

    Upcoming Milstones

    + + + + + + + + + + + + + + + + +
    ProjectMilestoneDate
    <%= link_to 'Project Three', :action => 'project_overview' %><%= link_to 'Version 1.6 Release', :action => 'view_milestone' %>10/31/2005
    <%= link_to 'Project Two', :action => 'project_overview' %><%= link_to 'Project Post-Mortem', :action => 'view_milestone' %>10/15/2005
    +
    + +
    +

    Recent Activity

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     ProjectWhen?What?Who?
    <%= image_tag 'story_card_icon_small', :alt => 'SC', :size => '10x10' %><%= link_to 'Project Four', :action => 'project_overview' %>9/27/2005 12:33pmNew story card added: "<%= link_to 'calculate probability', :action => 'view_story_card' %>"<%= link_to 'J. Brown', :action => 'user_profile' %>
    <%= image_tag 'feature_icon_small', :alt => 'FE', :size => '10x10' %><%= link_to 'Project Three', :action => 'project_overview' %>9/27/2005 09:24amChanged feature: "<%= link_to 'Secure Checkout', :action => 'view_feature' %>"<%= link_to 'M. Rogers', :action => 'user_profile' %>
    <%= image_tag 'defect_icon_small', :alt => 'DF', :size => '10x10' %><%= link_to 'Project Two', :action => 'project_overview' %>9/26/2005 10:55pmNew defect reported: "<%= link_to 'DF82', :action => 'view_defect' %>"<%= link_to 'D. Vader', :action => 'user_profile' %>
    +
    + + Modified: trunk/config/routes.rb ============================================================================== --- trunk/config/routes.rb (original) +++ trunk/config/routes.rb Tue Sep 27 12:58:05 2005 @@ -8,7 +8,7 @@ # You can have the root of your site routed by hooking up '' # -- just remember to delete public/index.html. - # map.connect '', :controller => "welcome" + map.connect '', :controller => 'main', :action => 'dashboard' # Allow downloading Web Service WSDL as a file with an extension # instead of a file named 'wsdl' Added: trunk/test/functional/main_controller_test.rb ============================================================================== --- trunk/test/functional/main_controller_test.rb (added) +++ trunk/test/functional/main_controller_test.rb Tue Sep 27 12:58:05 2005 @@ -1,0 +1,19 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'main_controller' + +# Re-raise errors caught by the controller. +class MainController; def rescue_action(e) raise e end; end + +class MainControllerTest < Test::Unit::TestCase + def setup + @controller = MainController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_dashboard + get :dashboard + assert_response :success + assert_template 'dashboard' + end +end Added: trunk/test/functional/routing_test.rb ============================================================================== --- trunk/test/functional/routing_test.rb (added) +++ trunk/test/functional/routing_test.rb Tue Sep 27 12:58:05 2005 @@ -1,0 +1,7 @@ +require File.expand_path( File.dirname( __FILE__ ) + '/../test_helper' ) + +class RoutingTest < Test::Unit::TestCase + def test_root_points_to_main_dashboard + assert_routing '', :controller => 'main', :action => 'dashboard' + end +end