rails 1.2.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rails might be problematic. Click here for more details.

Files changed (148) hide show
  1. data/CHANGELOG +491 -12
  2. data/MIT-LICENSE +1 -1
  3. data/README +17 -25
  4. data/Rakefile +41 -18
  5. data/bin/about +1 -1
  6. data/bin/console +1 -1
  7. data/bin/destroy +1 -1
  8. data/bin/generate +1 -1
  9. data/bin/performance/request +3 -0
  10. data/bin/plugin +1 -1
  11. data/bin/runner +1 -1
  12. data/bin/server +1 -1
  13. data/builtin/rails_info/rails/info.rb +2 -2
  14. data/configs/apache.conf +1 -1
  15. data/configs/databases/mysql.yml +9 -3
  16. data/configs/databases/postgresql.yml +16 -12
  17. data/configs/initializers/inflections.rb +10 -0
  18. data/configs/initializers/mime_types.rb +5 -0
  19. data/configs/routes.rb +23 -11
  20. data/doc/README_FOR_APP +1 -1
  21. data/environments/boot.rb +95 -26
  22. data/environments/development.rb +2 -5
  23. data/environments/environment.rb +24 -25
  24. data/environments/test.rb +4 -1
  25. data/helpers/application.rb +5 -2
  26. data/helpers/test_helper.rb +10 -0
  27. data/html/422.html +30 -0
  28. data/html/500.html +1 -1
  29. data/html/index.html +2 -2
  30. data/html/javascripts/controls.js +484 -354
  31. data/html/javascripts/dragdrop.js +88 -58
  32. data/html/javascripts/effects.js +396 -364
  33. data/html/javascripts/prototype.js +2817 -1107
  34. data/html/robots.txt +5 -1
  35. data/lib/commands/console.rb +12 -5
  36. data/lib/commands/performance/request.rb +6 -0
  37. data/lib/commands/plugin.rb +15 -10
  38. data/lib/commands/process/spawner.rb +14 -4
  39. data/lib/commands/servers/base.rb +12 -0
  40. data/lib/commands/servers/mongrel.rb +5 -1
  41. data/lib/commands/servers/webrick.rb +14 -7
  42. data/lib/console_app.rb +5 -2
  43. data/lib/console_with_helpers.rb +5 -2
  44. data/lib/dispatcher.rb +3 -151
  45. data/lib/fcgi_handler.rb +79 -81
  46. data/lib/initializer.rb +125 -169
  47. data/lib/rails/plugin.rb +84 -0
  48. data/lib/rails/plugin/loader.rb +150 -0
  49. data/lib/rails/plugin/locator.rb +78 -0
  50. data/lib/rails/version.rb +3 -3
  51. data/lib/rails_generator/base.rb +11 -9
  52. data/lib/rails_generator/commands.rb +20 -10
  53. data/lib/rails_generator/generators/applications/app/USAGE +0 -7
  54. data/lib/rails_generator/generators/applications/app/app_generator.rb +25 -6
  55. data/lib/rails_generator/generators/components/controller/USAGE +11 -12
  56. data/lib/rails_generator/generators/components/controller/controller_generator.rb +2 -2
  57. data/lib/rails_generator/generators/components/controller/templates/functional_test.rb +1 -11
  58. data/lib/rails_generator/generators/components/controller/templates/{view.rhtml → view.html.erb} +0 -0
  59. data/lib/rails_generator/generators/components/integration_test/USAGE +5 -11
  60. data/lib/rails_generator/generators/components/mailer/USAGE +8 -10
  61. data/lib/rails_generator/generators/components/mailer/mailer_generator.rb +3 -3
  62. data/lib/rails_generator/generators/components/mailer/templates/fixture.erb +3 -0
  63. data/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml +0 -3
  64. data/lib/rails_generator/generators/components/mailer/templates/unit_test.rb +8 -24
  65. data/lib/rails_generator/generators/components/mailer/templates/view.erb +3 -0
  66. data/lib/rails_generator/generators/components/mailer/templates/view.rhtml +0 -3
  67. data/lib/rails_generator/generators/components/migration/USAGE +23 -8
  68. data/lib/rails_generator/generators/components/migration/migration_generator.rb +15 -2
  69. data/lib/rails_generator/generators/components/migration/templates/migration.rb +6 -2
  70. data/lib/rails_generator/generators/components/model/USAGE +15 -14
  71. data/lib/rails_generator/generators/components/model/model_generator.rb +10 -3
  72. data/lib/rails_generator/generators/components/model/templates/fixtures.yml +11 -3
  73. data/lib/rails_generator/generators/components/model/templates/migration.rb +4 -1
  74. data/lib/rails_generator/generators/components/model/templates/unit_test.rb +1 -3
  75. data/lib/rails_generator/generators/components/observer/USAGE +5 -7
  76. data/lib/rails_generator/generators/components/observer/templates/unit_test.rb +0 -2
  77. data/lib/rails_generator/generators/components/plugin/USAGE +8 -18
  78. data/lib/rails_generator/generators/components/plugin/plugin_generator.rb +1 -0
  79. data/lib/rails_generator/generators/components/plugin/templates/MIT-LICENSE +20 -0
  80. data/lib/rails_generator/generators/components/plugin/templates/README +10 -1
  81. data/lib/rails_generator/generators/components/plugin/templates/USAGE +1 -1
  82. data/lib/rails_generator/generators/components/plugin/templates/init.rb +1 -1
  83. data/lib/rails_generator/generators/components/plugin/templates/plugin.rb +1 -1
  84. data/lib/rails_generator/generators/components/plugin/templates/tasks.rake +1 -1
  85. data/lib/rails_generator/generators/components/resource/USAGE +23 -0
  86. data/lib/rails_generator/generators/components/resource/resource_generator.rb +13 -15
  87. data/lib/rails_generator/generators/components/resource/templates/controller.rb +1 -1
  88. data/lib/rails_generator/generators/components/resource/templates/functional_test.rb +2 -14
  89. data/lib/rails_generator/generators/components/scaffold/USAGE +24 -31
  90. data/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +45 -146
  91. data/lib/rails_generator/generators/components/scaffold/templates/controller.rb +64 -37
  92. data/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb +23 -80
  93. data/lib/rails_generator/generators/components/scaffold/templates/{layout.rhtml → layout.html.erb} +0 -0
  94. data/lib/rails_generator/generators/components/scaffold/templates/style.css +1 -1
  95. data/lib/rails_generator/generators/components/{scaffold_resource/templates/view_edit.rhtml → scaffold/templates/view_edit.html.erb} +4 -4
  96. data/lib/rails_generator/generators/components/{scaffold_resource/templates/view_index.rhtml → scaffold/templates/view_index.html.erb} +4 -4
  97. data/lib/rails_generator/generators/components/{scaffold_resource/templates/view_new.rhtml → scaffold/templates/view_new.html.erb} +3 -3
  98. data/lib/rails_generator/generators/components/{scaffold_resource/templates/view_show.rhtml → scaffold/templates/view_show.html.erb} +1 -1
  99. data/lib/rails_generator/generators/components/session_migration/USAGE +6 -11
  100. data/lib/rails_generator/generators/components/session_migration/templates/migration.rb +3 -3
  101. data/lib/rails_generator/lookup.rb +45 -10
  102. data/lib/rails_generator/scripts.rb +6 -3
  103. data/lib/rails_generator/scripts/destroy.rb +23 -0
  104. data/lib/rails_generator/secret_key_generator.rb +160 -0
  105. data/lib/rails_generator/spec.rb +1 -1
  106. data/lib/source_annotation_extractor.rb +62 -0
  107. data/lib/tasks/annotations.rake +23 -0
  108. data/lib/tasks/databases.rake +249 -83
  109. data/lib/tasks/documentation.rake +11 -13
  110. data/lib/tasks/framework.rake +1 -1
  111. data/lib/tasks/rails.rb +1 -1
  112. data/lib/tasks/testing.rake +5 -7
  113. data/lib/test_help.rb +4 -3
  114. data/lib/webrick_server.rb +3 -4
  115. metadata +31 -49
  116. data/bin/breakpointer +0 -3
  117. data/lib/binding_of_caller.rb +0 -85
  118. data/lib/breakpoint.rb +0 -553
  119. data/lib/breakpoint_client.rb +0 -196
  120. data/lib/commands/breakpointer.rb +0 -1
  121. data/lib/rails_generator/generators/components/resource/templates/USAGE +0 -18
  122. data/lib/rails_generator/generators/components/resource/templates/fixtures.yml +0 -11
  123. data/lib/rails_generator/generators/components/resource/templates/migration.rb +0 -13
  124. data/lib/rails_generator/generators/components/resource/templates/model.rb +0 -2
  125. data/lib/rails_generator/generators/components/resource/templates/unit_test.rb +0 -10
  126. data/lib/rails_generator/generators/components/scaffold/templates/form.rhtml +0 -3
  127. data/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml +0 -1
  128. data/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml +0 -9
  129. data/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml +0 -27
  130. data/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml +0 -8
  131. data/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml +0 -8
  132. data/lib/rails_generator/generators/components/scaffold_resource/USAGE +0 -29
  133. data/lib/rails_generator/generators/components/scaffold_resource/scaffold_resource_generator.rb +0 -93
  134. data/lib/rails_generator/generators/components/scaffold_resource/templates/controller.rb +0 -79
  135. data/lib/rails_generator/generators/components/scaffold_resource/templates/fixtures.yml +0 -11
  136. data/lib/rails_generator/generators/components/scaffold_resource/templates/functional_test.rb +0 -57
  137. data/lib/rails_generator/generators/components/scaffold_resource/templates/helper.rb +0 -2
  138. data/lib/rails_generator/generators/components/scaffold_resource/templates/layout.rhtml +0 -17
  139. data/lib/rails_generator/generators/components/scaffold_resource/templates/migration.rb +0 -13
  140. data/lib/rails_generator/generators/components/scaffold_resource/templates/model.rb +0 -2
  141. data/lib/rails_generator/generators/components/scaffold_resource/templates/style.css +0 -74
  142. data/lib/rails_generator/generators/components/scaffold_resource/templates/unit_test.rb +0 -10
  143. data/lib/rails_generator/generators/components/web_service/USAGE +0 -28
  144. data/lib/rails_generator/generators/components/web_service/templates/api_definition.rb +0 -5
  145. data/lib/rails_generator/generators/components/web_service/templates/controller.rb +0 -8
  146. data/lib/rails_generator/generators/components/web_service/templates/functional_test.rb +0 -19
  147. data/lib/rails_generator/generators/components/web_service/web_service_generator.rb +0 -29
  148. data/lib/tasks/pre_namespace_aliases.rake +0 -53
@@ -1,2 +1,2 @@
1
1
  Use this README file to introduce your application and point to useful places in the API for learning more.
2
- Run "rake appdoc" to generate API documentation for your models and controllers.
2
+ Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
@@ -1,39 +1,108 @@
1
- # Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
1
+ # Don't change this file!
2
+ # Configure your app in config/environment.rb and config/environments/*.rb
2
3
 
3
4
  RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
4
5
 
5
- unless defined?(Rails::Initializer)
6
- if File.directory?("#{RAILS_ROOT}/vendor/rails")
7
- require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
8
- else
9
- require 'rubygems'
6
+ module Rails
7
+ class << self
8
+ def boot!
9
+ unless booted?
10
+ preinitialize
11
+ pick_boot.run
12
+ end
13
+ end
14
+
15
+ def booted?
16
+ defined? Rails::Initializer
17
+ end
18
+
19
+ def pick_boot
20
+ (vendor_rails? ? VendorBoot : GemBoot).new
21
+ end
22
+
23
+ def vendor_rails?
24
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
25
+ end
26
+
27
+ def preinitialize
28
+ load(preinitializer_path) if File.exists?(preinitializer_path)
29
+ end
10
30
 
11
- rails_gem_version =
12
- if defined? RAILS_GEM_VERSION
13
- RAILS_GEM_VERSION
31
+ def preinitializer_path
32
+ "#{RAILS_ROOT}/config/preinitializer.rb"
33
+ end
34
+ end
35
+
36
+ class Boot
37
+ def run
38
+ load_initializer
39
+ Rails::Initializer.run(:set_load_path)
40
+ end
41
+ end
42
+
43
+ class VendorBoot < Boot
44
+ def load_initializer
45
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
46
+ end
47
+ end
48
+
49
+ class GemBoot < Boot
50
+ def load_initializer
51
+ self.class.load_rubygems
52
+ load_rails_gem
53
+ require 'initializer'
54
+ end
55
+
56
+ def load_rails_gem
57
+ if version = self.class.gem_version
58
+ gem 'rails', version
14
59
  else
15
- File.read("#{File.dirname(__FILE__)}/environment.rb") =~ /^[^#]*RAILS_GEM_VERSION\s+=\s+'([\d.]+)'/
16
- $1
60
+ gem 'rails'
17
61
  end
62
+ rescue Gem::LoadError => load_error
63
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
64
+ exit 1
65
+ end
18
66
 
19
- if rails_gem_version
20
- rails_gem = Gem.cache.search('rails', "=#{rails_gem_version}.0").sort_by { |g| g.version.version }.last
67
+ class << self
68
+ def rubygems_version
69
+ Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
70
+ end
21
71
 
22
- if rails_gem
23
- gem "rails", "=#{rails_gem.version.version}"
24
- require rails_gem.full_gem_path + '/lib/initializer'
25
- else
26
- STDERR.puts %(Cannot find gem for Rails =#{rails_gem_version}.0:
27
- Install the missing gem with 'gem install -v=#{rails_gem_version} rails', or
28
- change environment.rb to define RAILS_GEM_VERSION with your desired version.
29
- )
72
+ def gem_version
73
+ if defined? RAILS_GEM_VERSION
74
+ RAILS_GEM_VERSION
75
+ elsif ENV.include?('RAILS_GEM_VERSION')
76
+ ENV['RAILS_GEM_VERSION']
77
+ else
78
+ parse_gem_version(read_environment_rb)
79
+ end
80
+ end
81
+
82
+ def load_rubygems
83
+ require 'rubygems'
84
+
85
+ unless rubygems_version >= '0.9.4'
86
+ $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
87
+ exit 1
88
+ end
89
+
90
+ rescue LoadError
91
+ $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
30
92
  exit 1
31
93
  end
32
- else
33
- gem "rails"
34
- require 'initializer'
94
+
95
+ def parse_gem_version(text)
96
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*'([!~<>=]*\s*[\d.]+)'/
97
+ end
98
+
99
+ private
100
+ def read_environment_rb
101
+ File.read("#{RAILS_ROOT}/config/environment.rb")
102
+ end
35
103
  end
36
104
  end
37
-
38
- Rails::Initializer.run(:set_load_path)
39
105
  end
106
+
107
+ # All that for this:
108
+ Rails.boot!
@@ -8,14 +8,11 @@ config.cache_classes = false
8
8
  # Log error messages when you accidentally call methods on nil.
9
9
  config.whiny_nils = true
10
10
 
11
- # Enable the breakpoint server that script/breakpointer connects to
12
- config.breakpoint_server = true
13
-
14
11
  # Show full error reports and disable caching
15
12
  config.action_controller.consider_all_requests_local = true
13
+ config.action_view.debug_rjs = true
16
14
  config.action_controller.perform_caching = false
17
15
  config.action_view.cache_template_extensions = false
18
- config.action_view.debug_rjs = true
19
16
 
20
17
  # Don't care if the mailer can't send
21
- config.action_mailer.raise_delivery_errors = false
18
+ config.action_mailer.raise_delivery_errors = false
@@ -1,4 +1,4 @@
1
- # Be sure to restart your web server when you modify this file.
1
+ # Be sure to restart your server when you modify this file
2
2
 
3
3
  # Uncomment below to force Rails into production mode when
4
4
  # you don't control web/app server and can't set it the proper way
@@ -11,13 +11,19 @@
11
11
  require File.join(File.dirname(__FILE__), 'boot')
12
12
 
13
13
  Rails::Initializer.run do |config|
14
- # Settings in config/environments/* take precedence over those specified here
14
+ # Settings in config/environments/* take precedence over those specified here.
15
+ # Application configuration should go into files in config/initializers
16
+ # -- all .rb files in that directory are automatically loaded.
17
+ # See Rails::Configuration for more options.
15
18
 
16
- # Skip frameworks you're not going to use (only works if using vendor/rails)
17
- # config.frameworks -= [ :action_web_service, :action_mailer ]
19
+ # Skip frameworks you're not going to use (only works if using vendor/rails).
20
+ # To use Rails without a database, you must remove the Active Record framework
21
+ # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
18
22
 
19
- # Only load the plugins named here, by default all plugins in vendor/plugins are loaded
20
- # config.plugins = %W( exception_notification ssl_requirement )
23
+ # Only load the plugins named here, in the order given. By default, all plugins
24
+ # in vendor/plugins are loaded in alphabetical order.
25
+ # :all can be used as a placeholder for all plugins not explicitly named
26
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
21
27
 
22
28
  # Add additional load paths for your own custom dirs
23
29
  # config.load_paths += %W( #{RAILS_ROOT}/extras )
@@ -26,7 +32,17 @@ Rails::Initializer.run do |config|
26
32
  # (by default production uses :info, the others :debug)
27
33
  # config.log_level = :debug
28
34
 
29
- # Use the database for sessions instead of the file system
35
+ # Your secret key for verifying cookie session data integrity.
36
+ # If you change this key, all old sessions will become invalid!
37
+ # Make sure the secret is at least 30 characters and all random,
38
+ # no regular words or you'll be exposed to dictionary attacks.
39
+ config.action_controller.session = {
40
+ :session_key => '_<%= app_name %>_session',
41
+ :secret => '<%= app_secret %>'
42
+ }
43
+
44
+ # Use the database for sessions instead of the cookie-based default,
45
+ # which shouldn't be used to store highly confidential information
30
46
  # (create the session table with 'rake db:sessions:create')
31
47
  # config.action_controller.session_store = :active_record_store
32
48
 
@@ -40,21 +56,4 @@ Rails::Initializer.run do |config|
40
56
 
41
57
  # Make Active Record use UTC-base instead of local time
42
58
  # config.active_record.default_timezone = :utc
43
-
44
- # Add new inflection rules using the following format
45
- # (all these examples are active by default):
46
- # Inflector.inflections do |inflect|
47
- # inflect.plural /^(ox)$/i, '\1en'
48
- # inflect.singular /^(ox)en/i, '\1'
49
- # inflect.irregular 'person', 'people'
50
- # inflect.uncountable %w( fish sheep )
51
- # end
52
-
53
- # See Rails::Configuration for more options
54
- end
55
-
56
- # Add new mime types for use in respond_to blocks:
57
- # Mime::Type.register "text/richtext", :rtf
58
- # Mime::Type.register "application/x-mobile", :mobile
59
-
60
- # Include your application configuration below
59
+ end
@@ -13,7 +13,10 @@ config.whiny_nils = true
13
13
  config.action_controller.consider_all_requests_local = true
14
14
  config.action_controller.perform_caching = false
15
15
 
16
+ # Disable request forgery protection in test environment
17
+ config.action_controller.allow_forgery_protection = false
18
+
16
19
  # Tell ActionMailer not to deliver emails to the real world.
17
20
  # The :test delivery method accumulates sent emails in the
18
21
  # ActionMailer::Base.deliveries array.
19
- config.action_mailer.delivery_method = :test
22
+ config.action_mailer.delivery_method = :test
@@ -2,6 +2,9 @@
2
2
  # Likewise, all the methods added will be available for all controllers.
3
3
 
4
4
  class ApplicationController < ActionController::Base
5
- # Pick a unique cookie name to distinguish our session data from others'
6
- session :session_key => '_<%= app_name %>_session_id'
5
+ helper :all # include all helpers, all the time
6
+
7
+ # See ActionController::RequestForgeryProtection for details
8
+ # Uncomment the :secret if you're not using the cookie session store
9
+ protect_from_forgery # :secret => '<%= app_secret %>'
7
10
  end
@@ -15,6 +15,10 @@ class Test::Unit::TestCase
15
15
  # in MySQL. Turn off transactional fixtures in this case; however, if you
16
16
  # don't care one way or the other, switching from MyISAM to InnoDB tables
17
17
  # is recommended.
18
+ #
19
+ # The only drawback to using transactional fixtures is when you actually
20
+ # need to test transactions. Since your test is bracketed by a transaction,
21
+ # any transactions started in your code will be automatically rolled back.
18
22
  self.use_transactional_fixtures = true
19
23
 
20
24
  # Instantiated fixtures are slow, but give you @david where otherwise you
@@ -24,5 +28,11 @@ class Test::Unit::TestCase
24
28
  # then set this back to true.
25
29
  self.use_instantiated_fixtures = false
26
30
 
31
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
32
+ #
33
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
34
+ # -- they do not yet inherit this setting
35
+ fixtures :all
36
+
27
37
  # Add more helper methods to be used by all tests here...
28
38
  end
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+
6
+ <head>
7
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
8
+ <title>The change you wanted was rejected (422)</title>
9
+ <style type="text/css">
10
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
11
+ div.dialog {
12
+ width: 25em;
13
+ padding: 0 4em;
14
+ margin: 4em auto 0 auto;
15
+ border: 1px solid #ccc;
16
+ border-right-color: #999;
17
+ border-bottom-color: #999;
18
+ }
19
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
20
+ </style>
21
+ </head>
22
+
23
+ <body>
24
+ <!-- This file lives in public/422.html -->
25
+ <div class="dialog">
26
+ <h1>The change you wanted was rejected.</h1>
27
+ <p>Maybe you tried to change something you didn't have access to.</p>
28
+ </div>
29
+ </body>
30
+ </html>
@@ -5,7 +5,7 @@
5
5
 
6
6
  <head>
7
7
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
8
- <title>We're sorry, but something went wrong</title>
8
+ <title>We're sorry, but something went wrong (500)</title>
9
9
  <style type="text/css">
10
10
  body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
11
11
  div.dialog {
@@ -240,7 +240,7 @@
240
240
  <div id="content">
241
241
  <div id="header">
242
242
  <h1>Welcome aboard</h1>
243
- <h2>You&rsquo;re riding the Rails!</h2>
243
+ <h2>You&rsquo;re riding Ruby on Rails!</h2>
244
244
  </div>
245
245
 
246
246
  <div id="about">
@@ -265,7 +265,7 @@
265
265
 
266
266
  <li>
267
267
  <h2>Set up a default route and remove or rename this file</h2>
268
- <p>Routes are setup in config/routes.rb.</p>
268
+ <p>Routes are set up in config/routes.rb.</p>
269
269
  </li>
270
270
  </ol>
271
271
  </div>
@@ -1,6 +1,6 @@
1
- // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
- // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
- // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
1
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2
+ // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
3
+ // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
4
4
  // Contributors:
5
5
  // Richard Livsey
6
6
  // Rahul Bhargava
@@ -37,22 +37,23 @@
37
37
  if(typeof Effect == 'undefined')
38
38
  throw("controls.js requires including script.aculo.us' effects.js library");
39
39
 
40
- var Autocompleter = {}
41
- Autocompleter.Base = function() {};
42
- Autocompleter.Base.prototype = {
40
+ var Autocompleter = { }
41
+ Autocompleter.Base = Class.create({
43
42
  baseInitialize: function(element, update, options) {
44
- this.element = $(element);
43
+ element = $(element)
44
+ this.element = element;
45
45
  this.update = $(update);
46
46
  this.hasFocus = false;
47
47
  this.changed = false;
48
48
  this.active = false;
49
49
  this.index = 0;
50
50
  this.entryCount = 0;
51
+ this.oldElementValue = this.element.value;
51
52
 
52
53
  if(this.setOptions)
53
54
  this.setOptions(options);
54
55
  else
55
- this.options = options || {};
56
+ this.options = options || { };
56
57
 
57
58
  this.options.paramName = this.options.paramName || this.element.name;
58
59
  this.options.tokens = this.options.tokens || [];
@@ -74,6 +75,9 @@ Autocompleter.Base.prototype = {
74
75
 
75
76
  if(typeof(this.options.tokens) == 'string')
76
77
  this.options.tokens = new Array(this.options.tokens);
78
+ // Force carriage returns as token delimiters anyway
79
+ if (!this.options.tokens.include('\n'))
80
+ this.options.tokens.push('\n');
77
81
 
78
82
  this.observer = null;
79
83
 
@@ -81,15 +85,14 @@ Autocompleter.Base.prototype = {
81
85
 
82
86
  Element.hide(this.update);
83
87
 
84
- Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
85
- Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
88
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
89
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
86
90
  },
87
91
 
88
92
  show: function() {
89
93
  if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
90
94
  if(!this.iefix &&
91
- (navigator.appVersion.indexOf('MSIE')>0) &&
92
- (navigator.userAgent.indexOf('Opera')<0) &&
95
+ (Prototype.Browser.IE) &&
93
96
  (Element.getStyle(this.update, 'position')=='absolute')) {
94
97
  new Insertion.After(this.update,
95
98
  '<iframe id="' + this.update.id + '_iefix" '+
@@ -139,17 +142,17 @@ Autocompleter.Base.prototype = {
139
142
  case Event.KEY_UP:
140
143
  this.markPrevious();
141
144
  this.render();
142
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
145
+ Event.stop(event);
143
146
  return;
144
147
  case Event.KEY_DOWN:
145
148
  this.markNext();
146
149
  this.render();
147
- if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
150
+ Event.stop(event);
148
151
  return;
149
152
  }
150
153
  else
151
154
  if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
152
- (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
155
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
153
156
 
154
157
  this.changed = true;
155
158
  this.hasFocus = true;
@@ -195,7 +198,6 @@ Autocompleter.Base.prototype = {
195
198
  this.index==i ?
196
199
  Element.addClassName(this.getEntry(i),"selected") :
197
200
  Element.removeClassName(this.getEntry(i),"selected");
198
-
199
201
  if(this.hasFocus) {
200
202
  this.show();
201
203
  this.active = true;
@@ -238,21 +240,22 @@ Autocompleter.Base.prototype = {
238
240
  }
239
241
  var value = '';
240
242
  if (this.options.select) {
241
- var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
243
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
242
244
  if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
243
245
  } else
244
246
  value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
245
247
 
246
- var lastTokenPos = this.findLastToken();
247
- if (lastTokenPos != -1) {
248
- var newValue = this.element.value.substr(0, lastTokenPos + 1);
249
- var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
248
+ var bounds = this.getTokenBounds();
249
+ if (bounds[0] != -1) {
250
+ var newValue = this.element.value.substr(0, bounds[0]);
251
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
250
252
  if (whitespace)
251
253
  newValue += whitespace[0];
252
- this.element.value = newValue + value;
254
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
253
255
  } else {
254
256
  this.element.value = value;
255
257
  }
258
+ this.oldElementValue = this.element.value;
256
259
  this.element.focus();
257
260
 
258
261
  if (this.options.afterUpdateElement)
@@ -296,39 +299,48 @@ Autocompleter.Base.prototype = {
296
299
 
297
300
  onObserverEvent: function() {
298
301
  this.changed = false;
302
+ this.tokenBounds = null;
299
303
  if(this.getToken().length>=this.options.minChars) {
300
- this.startIndicator();
301
304
  this.getUpdatedChoices();
302
305
  } else {
303
306
  this.active = false;
304
307
  this.hide();
305
308
  }
309
+ this.oldElementValue = this.element.value;
306
310
  },
307
311
 
308
312
  getToken: function() {
309
- var tokenPos = this.findLastToken();
310
- if (tokenPos != -1)
311
- var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
312
- else
313
- var ret = this.element.value;
314
-
315
- return /\n/.test(ret) ? '' : ret;
316
- },
317
-
318
- findLastToken: function() {
319
- var lastTokenPos = -1;
320
-
321
- for (var i=0; i<this.options.tokens.length; i++) {
322
- var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
323
- if (thisTokenPos > lastTokenPos)
324
- lastTokenPos = thisTokenPos;
313
+ var bounds = this.getTokenBounds();
314
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
315
+ },
316
+
317
+ getTokenBounds: function() {
318
+ if (null != this.tokenBounds) return this.tokenBounds;
319
+ var value = this.element.value;
320
+ if (value.strip().empty()) return [-1, 0];
321
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
322
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
323
+ var prevTokenPos = -1, nextTokenPos = value.length;
324
+ var tp;
325
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
326
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
327
+ if (tp > prevTokenPos) prevTokenPos = tp;
328
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
329
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
325
330
  }
326
- return lastTokenPos;
331
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
327
332
  }
328
- }
333
+ });
334
+
335
+ Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
336
+ var boundary = Math.min(newS.length, oldS.length);
337
+ for (var index = 0; index < boundary; ++index)
338
+ if (newS[index] != oldS[index])
339
+ return index;
340
+ return boundary;
341
+ };
329
342
 
330
- Ajax.Autocompleter = Class.create();
331
- Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
343
+ Ajax.Autocompleter = Class.create(Autocompleter.Base, {
332
344
  initialize: function(element, update, url, options) {
333
345
  this.baseInitialize(element, update, options);
334
346
  this.options.asynchronous = true;
@@ -338,7 +350,9 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
338
350
  },
339
351
 
340
352
  getUpdatedChoices: function() {
341
- entry = encodeURIComponent(this.options.paramName) + '=' +
353
+ this.startIndicator();
354
+
355
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
342
356
  encodeURIComponent(this.getToken());
343
357
 
344
358
  this.options.parameters = this.options.callback ?
@@ -346,14 +360,13 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
346
360
 
347
361
  if(this.options.defaultParams)
348
362
  this.options.parameters += '&' + this.options.defaultParams;
349
-
363
+
350
364
  new Ajax.Request(this.url, this.options);
351
365
  },
352
366
 
353
367
  onComplete: function(request) {
354
368
  this.updateChoices(request.responseText);
355
369
  }
356
-
357
370
  });
358
371
 
359
372
  // The local array autocompleter. Used when you'd prefer to
@@ -391,8 +404,7 @@ Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.pro
391
404
  // In that case, the other options above will not apply unless
392
405
  // you support them.
393
406
 
394
- Autocompleter.Local = Class.create();
395
- Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
407
+ Autocompleter.Local = Class.create(Autocompleter.Base, {
396
408
  initialize: function(element, update, array, options) {
397
409
  this.baseInitialize(element, update, options);
398
410
  this.options.array = array;
@@ -448,13 +460,12 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
448
460
  ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
449
461
  return "<ul>" + ret.join('') + "</ul>";
450
462
  }
451
- }, options || {});
463
+ }, options || { });
452
464
  }
453
465
  });
454
466
 
455
- // AJAX in-place editor
456
- //
457
- // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
467
+ // AJAX in-place editor and collection editor
468
+ // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
458
469
 
459
470
  // Use this if you notice weird scrolling problems on some browsers,
460
471
  // the DOM might be a bit confused when this gets called so do this
@@ -465,353 +476,472 @@ Field.scrollFreeActivate = function(field) {
465
476
  }, 1);
466
477
  }
467
478
 
468
- Ajax.InPlaceEditor = Class.create();
469
- Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
470
- Ajax.InPlaceEditor.prototype = {
479
+ Ajax.InPlaceEditor = Class.create({
471
480
  initialize: function(element, url, options) {
472
481
  this.url = url;
473
- this.element = $(element);
474
-
475
- this.options = Object.extend({
476
- paramName: "value",
477
- okButton: true,
478
- okText: "ok",
479
- cancelLink: true,
480
- cancelText: "cancel",
481
- savingText: "Saving...",
482
- clickToEditText: "Click to edit",
483
- okText: "ok",
484
- rows: 1,
485
- onComplete: function(transport, element) {
486
- new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
487
- },
488
- onFailure: function(transport) {
489
- alert("Error communicating with the server: " + transport.responseText.stripTags());
490
- },
491
- callback: function(form) {
492
- return Form.serialize(form);
493
- },
494
- handleLineBreaks: true,
495
- loadingText: 'Loading...',
496
- savingClassName: 'inplaceeditor-saving',
497
- loadingClassName: 'inplaceeditor-loading',
498
- formClassName: 'inplaceeditor-form',
499
- highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
500
- highlightendcolor: "#FFFFFF",
501
- externalControl: null,
502
- submitOnBlur: false,
503
- ajaxOptions: {},
504
- evalScripts: false
505
- }, options || {});
506
-
507
- if(!this.options.formId && this.element.id) {
508
- this.options.formId = this.element.id + "-inplaceeditor";
509
- if ($(this.options.formId)) {
510
- // there's already a form with that name, don't specify an id
511
- this.options.formId = null;
512
- }
482
+ this.element = element = $(element);
483
+ this.prepareOptions();
484
+ this._controls = { };
485
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
486
+ Object.extend(this.options, options || { });
487
+ if (!this.options.formId && this.element.id) {
488
+ this.options.formId = this.element.id + '-inplaceeditor';
489
+ if ($(this.options.formId))
490
+ this.options.formId = '';
513
491
  }
514
-
515
- if (this.options.externalControl) {
492
+ if (this.options.externalControl)
516
493
  this.options.externalControl = $(this.options.externalControl);
517
- }
518
-
519
- this.originalBackground = Element.getStyle(this.element, 'background-color');
520
- if (!this.originalBackground) {
521
- this.originalBackground = "transparent";
522
- }
523
-
494
+ if (!this.options.externalControl)
495
+ this.options.externalControlOnly = false;
496
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
524
497
  this.element.title = this.options.clickToEditText;
525
-
526
- this.onclickListener = this.enterEditMode.bindAsEventListener(this);
527
- this.mouseoverListener = this.enterHover.bindAsEventListener(this);
528
- this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
529
- Event.observe(this.element, 'click', this.onclickListener);
530
- Event.observe(this.element, 'mouseover', this.mouseoverListener);
531
- Event.observe(this.element, 'mouseout', this.mouseoutListener);
532
- if (this.options.externalControl) {
533
- Event.observe(this.options.externalControl, 'click', this.onclickListener);
534
- Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
535
- Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
498
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
499
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
500
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
501
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
502
+ this._boundWrapperHandler = this.wrapUp.bind(this);
503
+ this.registerListeners();
504
+ },
505
+ checkForEscapeOrReturn: function(e) {
506
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
507
+ if (Event.KEY_ESC == e.keyCode)
508
+ this.handleFormCancellation(e);
509
+ else if (Event.KEY_RETURN == e.keyCode)
510
+ this.handleFormSubmission(e);
511
+ },
512
+ createControl: function(mode, handler, extraClasses) {
513
+ var control = this.options[mode + 'Control'];
514
+ var text = this.options[mode + 'Text'];
515
+ if ('button' == control) {
516
+ var btn = document.createElement('input');
517
+ btn.type = 'submit';
518
+ btn.value = text;
519
+ btn.className = 'editor_' + mode + '_button';
520
+ if ('cancel' == mode)
521
+ btn.onclick = this._boundCancelHandler;
522
+ this._form.appendChild(btn);
523
+ this._controls[mode] = btn;
524
+ } else if ('link' == control) {
525
+ var link = document.createElement('a');
526
+ link.href = '#';
527
+ link.appendChild(document.createTextNode(text));
528
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
529
+ link.className = 'editor_' + mode + '_link';
530
+ if (extraClasses)
531
+ link.className += ' ' + extraClasses;
532
+ this._form.appendChild(link);
533
+ this._controls[mode] = link;
536
534
  }
537
535
  },
538
- enterEditMode: function(evt) {
539
- if (this.saving) return;
540
- if (this.editing) return;
541
- this.editing = true;
542
- this.onEnterEditMode();
543
- if (this.options.externalControl) {
544
- Element.hide(this.options.externalControl);
545
- }
546
- Element.hide(this.element);
547
- this.createForm();
548
- this.element.parentNode.insertBefore(this.form, this.element);
549
- if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
550
- // stop the event to avoid a page refresh in Safari
551
- if (evt) {
552
- Event.stop(evt);
553
- }
554
- return false;
555
- },
556
- createForm: function() {
557
- this.form = document.createElement("form");
558
- this.form.id = this.options.formId;
559
- Element.addClassName(this.form, this.options.formClassName)
560
- this.form.onsubmit = this.onSubmit.bind(this);
561
-
562
- this.createEditField();
563
-
564
- if (this.options.textarea) {
565
- var br = document.createElement("br");
566
- this.form.appendChild(br);
567
- }
568
-
569
- if (this.options.okButton) {
570
- okButton = document.createElement("input");
571
- okButton.type = "submit";
572
- okButton.value = this.options.okText;
573
- okButton.className = 'editor_ok_button';
574
- this.form.appendChild(okButton);
575
- }
576
-
577
- if (this.options.cancelLink) {
578
- cancelLink = document.createElement("a");
579
- cancelLink.href = "#";
580
- cancelLink.appendChild(document.createTextNode(this.options.cancelText));
581
- cancelLink.onclick = this.onclickCancel.bind(this);
582
- cancelLink.className = 'editor_cancel';
583
- this.form.appendChild(cancelLink);
584
- }
585
- },
586
- hasHTMLLineBreaks: function(string) {
587
- if (!this.options.handleLineBreaks) return false;
588
- return string.match(/<br/i) || string.match(/<p>/i);
589
- },
590
- convertHTMLLineBreaks: function(string) {
591
- return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
592
- },
593
536
  createEditField: function() {
594
- var text;
595
- if(this.options.loadTextURL) {
596
- text = this.options.loadingText;
597
- } else {
598
- text = this.getText();
599
- }
600
-
601
- var obj = this;
602
-
603
- if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
604
- this.options.textarea = false;
605
- var textField = document.createElement("input");
606
- textField.obj = this;
607
- textField.type = "text";
608
- textField.name = this.options.paramName;
609
- textField.value = text;
610
- textField.style.backgroundColor = this.options.highlightcolor;
611
- textField.className = 'editor_field';
537
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
538
+ var fld;
539
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
540
+ fld = document.createElement('input');
541
+ fld.type = 'text';
612
542
  var size = this.options.size || this.options.cols || 0;
613
- if (size != 0) textField.size = size;
614
- if (this.options.submitOnBlur)
615
- textField.onblur = this.onSubmit.bind(this);
616
- this.editField = textField;
543
+ if (0 < size) fld.size = size;
617
544
  } else {
618
- this.options.textarea = true;
619
- var textArea = document.createElement("textarea");
620
- textArea.obj = this;
621
- textArea.name = this.options.paramName;
622
- textArea.value = this.convertHTMLLineBreaks(text);
623
- textArea.rows = this.options.rows;
624
- textArea.cols = this.options.cols || 40;
625
- textArea.className = 'editor_field';
626
- if (this.options.submitOnBlur)
627
- textArea.onblur = this.onSubmit.bind(this);
628
- this.editField = textArea;
545
+ fld = document.createElement('textarea');
546
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
547
+ fld.cols = this.options.cols || 40;
629
548
  }
630
-
631
- if(this.options.loadTextURL) {
549
+ fld.name = this.options.paramName;
550
+ fld.value = text; // No HTML breaks conversion anymore
551
+ fld.className = 'editor_field';
552
+ if (this.options.submitOnBlur)
553
+ fld.onblur = this._boundSubmitHandler;
554
+ this._controls.editor = fld;
555
+ if (this.options.loadTextURL)
632
556
  this.loadExternalText();
633
- }
634
- this.form.appendChild(this.editField);
557
+ this._form.appendChild(this._controls.editor);
558
+ },
559
+ createForm: function() {
560
+ var ipe = this;
561
+ function addText(mode, condition) {
562
+ var text = ipe.options['text' + mode + 'Controls'];
563
+ if (!text || condition === false) return;
564
+ ipe._form.appendChild(document.createTextNode(text));
565
+ };
566
+ this._form = $(document.createElement('form'));
567
+ this._form.id = this.options.formId;
568
+ this._form.addClassName(this.options.formClassName);
569
+ this._form.onsubmit = this._boundSubmitHandler;
570
+ this.createEditField();
571
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
572
+ this._form.appendChild(document.createElement('br'));
573
+ if (this.options.onFormCustomization)
574
+ this.options.onFormCustomization(this, this._form);
575
+ addText('Before', this.options.okControl || this.options.cancelControl);
576
+ this.createControl('ok', this._boundSubmitHandler);
577
+ addText('Between', this.options.okControl && this.options.cancelControl);
578
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
579
+ addText('After', this.options.okControl || this.options.cancelControl);
580
+ },
581
+ destroy: function() {
582
+ if (this._oldInnerHTML)
583
+ this.element.innerHTML = this._oldInnerHTML;
584
+ this.leaveEditMode();
585
+ this.unregisterListeners();
586
+ },
587
+ enterEditMode: function(e) {
588
+ if (this._saving || this._editing) return;
589
+ this._editing = true;
590
+ this.triggerCallback('onEnterEditMode');
591
+ if (this.options.externalControl)
592
+ this.options.externalControl.hide();
593
+ this.element.hide();
594
+ this.createForm();
595
+ this.element.parentNode.insertBefore(this._form, this.element);
596
+ if (!this.options.loadTextURL)
597
+ this.postProcessEditField();
598
+ if (e) Event.stop(e);
599
+ },
600
+ enterHover: function(e) {
601
+ if (this.options.hoverClassName)
602
+ this.element.addClassName(this.options.hoverClassName);
603
+ if (this._saving) return;
604
+ this.triggerCallback('onEnterHover');
635
605
  },
636
606
  getText: function() {
637
607
  return this.element.innerHTML;
638
608
  },
639
- loadExternalText: function() {
640
- Element.addClassName(this.form, this.options.loadingClassName);
641
- this.editField.disabled = true;
642
- new Ajax.Request(
643
- this.options.loadTextURL,
644
- Object.extend({
645
- asynchronous: true,
646
- onComplete: this.onLoadedExternalText.bind(this)
647
- }, this.options.ajaxOptions)
648
- );
649
- },
650
- onLoadedExternalText: function(transport) {
651
- Element.removeClassName(this.form, this.options.loadingClassName);
652
- this.editField.disabled = false;
653
- this.editField.value = transport.responseText.stripTags();
654
- Field.scrollFreeActivate(this.editField);
655
- },
656
- onclickCancel: function() {
657
- this.onComplete();
658
- this.leaveEditMode();
659
- return false;
660
- },
661
- onFailure: function(transport) {
662
- this.options.onFailure(transport);
663
- if (this.oldInnerHTML) {
664
- this.element.innerHTML = this.oldInnerHTML;
665
- this.oldInnerHTML = null;
609
+ handleAJAXFailure: function(transport) {
610
+ this.triggerCallback('onFailure', transport);
611
+ if (this._oldInnerHTML) {
612
+ this.element.innerHTML = this._oldInnerHTML;
613
+ this._oldInnerHTML = null;
666
614
  }
667
- return false;
668
615
  },
669
- onSubmit: function() {
670
- // onLoading resets these so we need to save them away for the Ajax call
671
- var form = this.form;
672
- var value = this.editField.value;
673
-
674
- // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
675
- // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
676
- // to be displayed indefinitely
677
- this.onLoading();
678
-
679
- if (this.options.evalScripts) {
680
- new Ajax.Request(
681
- this.url, Object.extend({
682
- parameters: this.options.callback(form, value),
683
- onComplete: this.onComplete.bind(this),
684
- onFailure: this.onFailure.bind(this),
685
- asynchronous:true,
686
- evalScripts:true
687
- }, this.options.ajaxOptions));
688
- } else {
689
- new Ajax.Updater(
690
- { success: this.element,
691
- // don't update on failure (this could be an option)
692
- failure: null },
693
- this.url, Object.extend({
694
- parameters: this.options.callback(form, value),
695
- onComplete: this.onComplete.bind(this),
696
- onFailure: this.onFailure.bind(this)
697
- }, this.options.ajaxOptions));
698
- }
699
- // stop the event to avoid a page refresh in Safari
700
- if (arguments.length > 1) {
701
- Event.stop(arguments[0]);
616
+ handleFormCancellation: function(e) {
617
+ this.wrapUp();
618
+ if (e) Event.stop(e);
619
+ },
620
+ handleFormSubmission: function(e) {
621
+ var form = this._form;
622
+ var value = $F(this._controls.editor);
623
+ this.prepareSubmission();
624
+ var params = this.options.callback(form, value) || '';
625
+ if (Object.isString(params))
626
+ params = params.toQueryParams();
627
+ params.editorId = this.element.id;
628
+ if (this.options.htmlResponse) {
629
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
630
+ Object.extend(options, {
631
+ parameters: params,
632
+ onComplete: this._boundWrapperHandler,
633
+ onFailure: this._boundFailureHandler
634
+ });
635
+ new Ajax.Updater({ success: this.element }, this.url, options);
636
+ } else {
637
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
638
+ Object.extend(options, {
639
+ parameters: params,
640
+ onComplete: this._boundWrapperHandler,
641
+ onFailure: this._boundFailureHandler
642
+ });
643
+ new Ajax.Request(this.url, options);
702
644
  }
703
- return false;
645
+ if (e) Event.stop(e);
646
+ },
647
+ leaveEditMode: function() {
648
+ this.element.removeClassName(this.options.savingClassName);
649
+ this.removeForm();
650
+ this.leaveHover();
651
+ this.element.style.backgroundColor = this._originalBackground;
652
+ this.element.show();
653
+ if (this.options.externalControl)
654
+ this.options.externalControl.show();
655
+ this._saving = false;
656
+ this._editing = false;
657
+ this._oldInnerHTML = null;
658
+ this.triggerCallback('onLeaveEditMode');
659
+ },
660
+ leaveHover: function(e) {
661
+ if (this.options.hoverClassName)
662
+ this.element.removeClassName(this.options.hoverClassName);
663
+ if (this._saving) return;
664
+ this.triggerCallback('onLeaveHover');
704
665
  },
705
- onLoading: function() {
706
- this.saving = true;
666
+ loadExternalText: function() {
667
+ this._form.addClassName(this.options.loadingClassName);
668
+ this._controls.editor.disabled = true;
669
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
670
+ Object.extend(options, {
671
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
672
+ onComplete: Prototype.emptyFunction,
673
+ onSuccess: function(transport) {
674
+ this._form.removeClassName(this.options.loadingClassName);
675
+ var text = transport.responseText;
676
+ if (this.options.stripLoadedTextTags)
677
+ text = text.stripTags();
678
+ this._controls.editor.value = text;
679
+ this._controls.editor.disabled = false;
680
+ this.postProcessEditField();
681
+ }.bind(this),
682
+ onFailure: this._boundFailureHandler
683
+ });
684
+ new Ajax.Request(this.options.loadTextURL, options);
685
+ },
686
+ postProcessEditField: function() {
687
+ var fpc = this.options.fieldPostCreation;
688
+ if (fpc)
689
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
690
+ },
691
+ prepareOptions: function() {
692
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
693
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
694
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
695
+ Object.extend(this.options, defs);
696
+ }.bind(this));
697
+ },
698
+ prepareSubmission: function() {
699
+ this._saving = true;
707
700
  this.removeForm();
708
701
  this.leaveHover();
709
702
  this.showSaving();
710
703
  },
704
+ registerListeners: function() {
705
+ this._listeners = { };
706
+ var listener;
707
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
708
+ listener = this[pair.value].bind(this);
709
+ this._listeners[pair.key] = listener;
710
+ if (!this.options.externalControlOnly)
711
+ this.element.observe(pair.key, listener);
712
+ if (this.options.externalControl)
713
+ this.options.externalControl.observe(pair.key, listener);
714
+ }.bind(this));
715
+ },
716
+ removeForm: function() {
717
+ if (!this._form) return;
718
+ this._form.remove();
719
+ this._form = null;
720
+ this._controls = { };
721
+ },
711
722
  showSaving: function() {
712
- this.oldInnerHTML = this.element.innerHTML;
723
+ this._oldInnerHTML = this.element.innerHTML;
713
724
  this.element.innerHTML = this.options.savingText;
714
- Element.addClassName(this.element, this.options.savingClassName);
715
- this.element.style.backgroundColor = this.originalBackground;
716
- Element.show(this.element);
725
+ this.element.addClassName(this.options.savingClassName);
726
+ this.element.style.backgroundColor = this._originalBackground;
727
+ this.element.show();
717
728
  },
718
- removeForm: function() {
719
- if(this.form) {
720
- if (this.form.parentNode) Element.remove(this.form);
721
- this.form = null;
729
+ triggerCallback: function(cbName, arg) {
730
+ if ('function' == typeof this.options[cbName]) {
731
+ this.options[cbName](this, arg);
722
732
  }
723
733
  },
724
- enterHover: function() {
725
- if (this.saving) return;
726
- this.element.style.backgroundColor = this.options.highlightcolor;
727
- if (this.effect) {
728
- this.effect.cancel();
729
- }
730
- Element.addClassName(this.element, this.options.hoverClassName)
734
+ unregisterListeners: function() {
735
+ $H(this._listeners).each(function(pair) {
736
+ if (!this.options.externalControlOnly)
737
+ this.element.stopObserving(pair.key, pair.value);
738
+ if (this.options.externalControl)
739
+ this.options.externalControl.stopObserving(pair.key, pair.value);
740
+ }.bind(this));
731
741
  },
732
- leaveHover: function() {
733
- if (this.options.backgroundColor) {
734
- this.element.style.backgroundColor = this.oldBackground;
735
- }
736
- Element.removeClassName(this.element, this.options.hoverClassName)
737
- if (this.saving) return;
738
- this.effect = new Effect.Highlight(this.element, {
739
- startcolor: this.options.highlightcolor,
740
- endcolor: this.options.highlightendcolor,
741
- restorecolor: this.originalBackground
742
+ wrapUp: function(transport) {
743
+ this.leaveEditMode();
744
+ // Can't use triggerCallback due to backward compatibility: requires
745
+ // binding + direct element
746
+ this._boundComplete(transport, this.element);
747
+ }
748
+ });
749
+
750
+ Object.extend(Ajax.InPlaceEditor.prototype, {
751
+ dispose: Ajax.InPlaceEditor.prototype.destroy
752
+ });
753
+
754
+ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
755
+ initialize: function($super, element, url, options) {
756
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
757
+ $super(element, url, options);
758
+ },
759
+
760
+ createEditField: function() {
761
+ var list = document.createElement('select');
762
+ list.name = this.options.paramName;
763
+ list.size = 1;
764
+ this._controls.editor = list;
765
+ this._collection = this.options.collection || [];
766
+ if (this.options.loadCollectionURL)
767
+ this.loadCollection();
768
+ else
769
+ this.checkForExternalText();
770
+ this._form.appendChild(this._controls.editor);
771
+ },
772
+
773
+ loadCollection: function() {
774
+ this._form.addClassName(this.options.loadingClassName);
775
+ this.showLoadingText(this.options.loadingCollectionText);
776
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
777
+ Object.extend(options, {
778
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
779
+ onComplete: Prototype.emptyFunction,
780
+ onSuccess: function(transport) {
781
+ var js = transport.responseText.strip();
782
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
783
+ throw 'Server returned an invalid collection representation.';
784
+ this._collection = eval(js);
785
+ this.checkForExternalText();
786
+ }.bind(this),
787
+ onFailure: this.onFailure
742
788
  });
789
+ new Ajax.Request(this.options.loadCollectionURL, options);
743
790
  },
744
- leaveEditMode: function() {
745
- Element.removeClassName(this.element, this.options.savingClassName);
746
- this.removeForm();
747
- this.leaveHover();
748
- this.element.style.backgroundColor = this.originalBackground;
749
- Element.show(this.element);
750
- if (this.options.externalControl) {
751
- Element.show(this.options.externalControl);
791
+
792
+ showLoadingText: function(text) {
793
+ this._controls.editor.disabled = true;
794
+ var tempOption = this._controls.editor.firstChild;
795
+ if (!tempOption) {
796
+ tempOption = document.createElement('option');
797
+ tempOption.value = '';
798
+ this._controls.editor.appendChild(tempOption);
799
+ tempOption.selected = true;
752
800
  }
753
- this.editing = false;
754
- this.saving = false;
755
- this.oldInnerHTML = null;
756
- this.onLeaveEditMode();
801
+ tempOption.update((text || '').stripScripts().stripTags());
757
802
  },
758
- onComplete: function(transport) {
759
- this.leaveEditMode();
760
- this.options.onComplete.bind(this)(transport, this.element);
803
+
804
+ checkForExternalText: function() {
805
+ this._text = this.getText();
806
+ if (this.options.loadTextURL)
807
+ this.loadExternalText();
808
+ else
809
+ this.buildOptionList();
761
810
  },
762
- onEnterEditMode: function() {},
763
- onLeaveEditMode: function() {},
764
- dispose: function() {
765
- if (this.oldInnerHTML) {
766
- this.element.innerHTML = this.oldInnerHTML;
767
- }
768
- this.leaveEditMode();
769
- Event.stopObserving(this.element, 'click', this.onclickListener);
770
- Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
771
- Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
772
- if (this.options.externalControl) {
773
- Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
774
- Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
775
- Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
776
- }
811
+
812
+ loadExternalText: function() {
813
+ this.showLoadingText(this.options.loadingText);
814
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
815
+ Object.extend(options, {
816
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
817
+ onComplete: Prototype.emptyFunction,
818
+ onSuccess: function(transport) {
819
+ this._text = transport.responseText.strip();
820
+ this.buildOptionList();
821
+ }.bind(this),
822
+ onFailure: this.onFailure
823
+ });
824
+ new Ajax.Request(this.options.loadTextURL, options);
825
+ },
826
+
827
+ buildOptionList: function() {
828
+ this._form.removeClassName(this.options.loadingClassName);
829
+ this._collection = this._collection.map(function(entry) {
830
+ return 2 === entry.length ? entry : [entry, entry].flatten();
831
+ });
832
+ var marker = ('value' in this.options) ? this.options.value : this._text;
833
+ var textFound = this._collection.any(function(entry) {
834
+ return entry[0] == marker;
835
+ }.bind(this));
836
+ this._controls.editor.update('');
837
+ var option;
838
+ this._collection.each(function(entry, index) {
839
+ option = document.createElement('option');
840
+ option.value = entry[0];
841
+ option.selected = textFound ? entry[0] == marker : 0 == index;
842
+ option.appendChild(document.createTextNode(entry[1]));
843
+ this._controls.editor.appendChild(option);
844
+ }.bind(this));
845
+ this._controls.editor.disabled = false;
846
+ Field.scrollFreeActivate(this._controls.editor);
777
847
  }
778
- };
848
+ });
779
849
 
780
- Ajax.InPlaceCollectionEditor = Class.create();
781
- Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
782
- Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
783
- createEditField: function() {
784
- if (!this.cached_selectTag) {
785
- var selectTag = document.createElement("select");
786
- var collection = this.options.collection || [];
787
- var optionTag;
788
- collection.each(function(e,i) {
789
- optionTag = document.createElement("option");
790
- optionTag.value = (e instanceof Array) ? e[0] : e;
791
- if((typeof this.options.value == 'undefined') &&
792
- ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
793
- if(this.options.value==optionTag.value) optionTag.selected = true;
794
- optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
795
- selectTag.appendChild(optionTag);
796
- }.bind(this));
797
- this.cached_selectTag = selectTag;
798
- }
850
+ //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
851
+ //**** This only exists for a while, in order to let ****
852
+ //**** users adapt to the new API. Read up on the new ****
853
+ //**** API and convert your code to it ASAP! ****
854
+
855
+ Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
856
+ if (!options) return;
857
+ function fallback(name, expr) {
858
+ if (name in options || expr === undefined) return;
859
+ options[name] = expr;
860
+ };
861
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
862
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
863
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
864
+ options.okLink == options.okButton == false ? false : undefined)));
865
+ fallback('highlightColor', options.highlightcolor);
866
+ fallback('highlightEndColor', options.highlightendcolor);
867
+ };
799
868
 
800
- this.editField = this.cached_selectTag;
801
- if(this.options.loadTextURL) this.loadExternalText();
802
- this.form.appendChild(this.editField);
803
- this.options.callback = function(form, value) {
804
- return "value=" + encodeURIComponent(value);
869
+ Object.extend(Ajax.InPlaceEditor, {
870
+ DefaultOptions: {
871
+ ajaxOptions: { },
872
+ autoRows: 3, // Use when multi-line w/ rows == 1
873
+ cancelControl: 'link', // 'link'|'button'|false
874
+ cancelText: 'cancel',
875
+ clickToEditText: 'Click to edit',
876
+ externalControl: null, // id|elt
877
+ externalControlOnly: false,
878
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
879
+ formClassName: 'inplaceeditor-form',
880
+ formId: null, // id|elt
881
+ highlightColor: '#ffff99',
882
+ highlightEndColor: '#ffffff',
883
+ hoverClassName: '',
884
+ htmlResponse: true,
885
+ loadingClassName: 'inplaceeditor-loading',
886
+ loadingText: 'Loading...',
887
+ okControl: 'button', // 'link'|'button'|false
888
+ okText: 'ok',
889
+ paramName: 'value',
890
+ rows: 1, // If 1 and multi-line, uses autoRows
891
+ savingClassName: 'inplaceeditor-saving',
892
+ savingText: 'Saving...',
893
+ size: 0,
894
+ stripLoadedTextTags: false,
895
+ submitOnBlur: false,
896
+ textAfterControls: '',
897
+ textBeforeControls: '',
898
+ textBetweenControls: ''
899
+ },
900
+ DefaultCallbacks: {
901
+ callback: function(form) {
902
+ return Form.serialize(form);
903
+ },
904
+ onComplete: function(transport, element) {
905
+ // For backward compatibility, this one is bound to the IPE, and passes
906
+ // the element directly. It was too often customized, so we don't break it.
907
+ new Effect.Highlight(element, {
908
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
909
+ },
910
+ onEnterEditMode: null,
911
+ onEnterHover: function(ipe) {
912
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
913
+ if (ipe._effect)
914
+ ipe._effect.cancel();
915
+ },
916
+ onFailure: function(transport, ipe) {
917
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
918
+ },
919
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
920
+ onLeaveEditMode: null,
921
+ onLeaveHover: function(ipe) {
922
+ ipe._effect = new Effect.Highlight(ipe.element, {
923
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
924
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
925
+ });
805
926
  }
927
+ },
928
+ Listeners: {
929
+ click: 'enterEditMode',
930
+ keydown: 'checkForEscapeOrReturn',
931
+ mouseover: 'enterHover',
932
+ mouseout: 'leaveHover'
806
933
  }
807
934
  });
808
935
 
936
+ Ajax.InPlaceCollectionEditor.DefaultOptions = {
937
+ loadingCollectionText: 'Loading options...'
938
+ };
939
+
809
940
  // Delayed observer, like Form.Element.Observer,
810
941
  // but waits for delay after last key input
811
942
  // Ideal for live-search fields
812
943
 
813
- Form.Element.DelayedObserver = Class.create();
814
- Form.Element.DelayedObserver.prototype = {
944
+ Form.Element.DelayedObserver = Class.create({
815
945
  initialize: function(element, delay, callback) {
816
946
  this.delay = delay || 0.5;
817
947
  this.element = $(element);
@@ -830,4 +960,4 @@ Form.Element.DelayedObserver.prototype = {
830
960
  this.timer = null;
831
961
  this.callback(this.element, $F(this.element));
832
962
  }
833
- };
963
+ });