aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Gemfile10
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.rdoc16
-rwxr-xr-xRakefile16
-rw-r--r--actionmailer/lib/action_mailer/base.rb3
-rw-r--r--actionmailer/lib/action_mailer/old_api.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionpack/CHANGELOG32
-rw-r--r--actionpack/actionpack.gemspec9
-rw-r--r--actionpack/lib/abstract_controller/base.rb38
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb4
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb6
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb72
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb29
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb10
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb11
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb16
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb224
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb7
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb206
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb27
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb6
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/lib/action_view.rb3
-rw-r--r--actionpack/lib/action_view/base.rb80
-rw-r--r--actionpack/lib/action_view/context.rb44
-rw-r--r--actionpack/lib/action_view/flows.rb2
-rw-r--r--actionpack/lib/action_view/helpers.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb3
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb7
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/controller_helper.rb21
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb17
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb22
-rw-r--r--actionpack/lib/action_view/helpers/rendering_helper.rb90
-rw-r--r--actionpack/lib/action_view/helpers/sprockets_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb125
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb4
-rw-r--r--actionpack/lib/action_view/lookup_context.rb5
-rw-r--r--actionpack/lib/action_view/partials.rb226
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb5
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb290
-rw-r--r--actionpack/lib/action_view/renderer/renderer.rb54
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb62
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb8
-rw-r--r--actionpack/lib/action_view/rendering.rb114
-rw-r--r--actionpack/lib/action_view/template.rb14
-rw-r--r--actionpack/lib/action_view/test_case.rb21
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb11
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb54
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb13
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb246
-rw-r--r--actionpack/test/controller/routing_test.rb6
-rw-r--r--actionpack/test/controller/view_paths_test.rb4
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb53
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb22
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb38
-rw-r--r--actionpack/test/dispatch/request_test.rb43
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb16
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb11
-rw-r--r--actionpack/test/dispatch/static_test.rb8
-rw-r--r--actionpack/test/template/capture_helper_test.rb10
-rw-r--r--actionpack/test/template/date_helper_test.rb4
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb10
-rw-r--r--actionpack/test/template/log_subscriber_test.rb7
-rw-r--r--actionpack/test/template/lookup_context_test.rb6
-rw-r--r--actionpack/test/template/streaming_render_test.rb8
-rw-r--r--actionpack/test/template/template_test.rb10
-rw-r--r--actionpack/test/template/test_case_test.rb4
-rw-r--r--actionpack/test/template/text_helper_test.rb254
-rw-r--r--actionpack/test/template/url_helper_test.rb2
-rw-r--r--activemodel/CHANGELOG4
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/observer_array.rb45
-rw-r--r--activemodel/lib/active_model/observing.rb15
-rw-r--r--activemodel/lib/active_model/translation.rb4
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activerecord/CHANGELOG35
-rw-r--r--activerecord/activerecord.gemspec4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb16
-rw-r--r--activerecord/lib/active_record/base.rb203
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb57
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb119
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb141
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb19
-rw-r--r--activerecord/lib/active_record/fixtures.rb6
-rw-r--r--activerecord/lib/active_record/identity_map.rb40
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb9
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb10
-rw-r--r--activerecord/lib/active_record/query_cache.rb28
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/railties/console_sandbox.rb (renamed from railties/lib/rails/console/sandbox.rb)0
-rw-r--r--activerecord/lib/active_record/railties/databases.rake170
-rw-r--r--activerecord/lib/active_record/relation.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb13
-rw-r--r--activerecord/lib/active_record/validations.rb6
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb36
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb11
-rw-r--r--activerecord/test/cases/base_test.rb135
-rw-r--r--activerecord/test/cases/finder_test.rb23
-rw-r--r--activerecord/test/cases/fixtures_test.rb5
-rw-r--r--activerecord/test/cases/identity_map/middleware_test.rb71
-rw-r--r--activerecord/test/cases/identity_map_test.rb18
-rw-r--r--activerecord/test/cases/lifecycle_test.rb25
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb12
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb430
-rw-r--r--activerecord/test/cases/query_cache_test.rb64
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb20
-rw-r--r--activerecord/test/models/developer.rb24
-rw-r--r--activerecord/test/models/person.rb23
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activeresource/examples/performance.rb70
-rw-r--r--activeresource/lib/active_resource/base.rb39
-rw-r--r--activeresource/lib/active_resource/http_mock.rb14
-rw-r--r--activeresource/lib/active_resource/observing.rb8
-rw-r--r--activeresource/lib/active_resource/validations.rb10
-rw-r--r--activeresource/lib/active_resource/version.rb2
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/configurable.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb4
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb6
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb23
-rw-r--r--activesupport/lib/active_support/testing/performance.rb131
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/lib/active_support/whiny_nil.rb2
-rw-r--r--activesupport/test/configurable_test.rb7
-rw-r--r--railties/CHANGELOG10
-rwxr-xr-xrailties/Rakefile5
-rw-r--r--railties/guides/source/action_controller_overview.textile39
-rw-r--r--railties/guides/source/action_mailer_basics.textile6
-rw-r--r--railties/guides/source/action_view_overview.textile179
-rw-r--r--railties/guides/source/active_record_querying.textile35
-rw-r--r--railties/guides/source/active_support_core_extensions.textile18
-rw-r--r--railties/guides/source/association_basics.textile31
-rw-r--r--railties/guides/source/contributing_to_ruby_on_rails.textile81
-rw-r--r--railties/guides/source/debugging_rails_applications.textile14
-rw-r--r--railties/guides/source/getting_started.textile15
-rw-r--r--railties/guides/source/layouts_and_rendering.textile6
-rw-r--r--railties/guides/source/migrations.textile94
-rw-r--r--railties/guides/source/routing.textile4
-rw-r--r--railties/guides/source/security.textile26
-rw-r--r--railties/guides/source/testing.textile2
-rw-r--r--railties/lib/rails/application.rb5
-rw-r--r--railties/lib/rails/application/bootstrap.rb1
-rw-r--r--railties/lib/rails/application/configuration.rb3
-rw-r--r--railties/lib/rails/commands/console.rb4
-rw-r--r--railties/lib/rails/commands/runner.rb22
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/engine.rb32
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb44
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb20
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb20
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js965
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js974
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js1123
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js8176
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js148
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js6082
-rw-r--r--railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js208
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb13
-rw-r--r--railties/lib/rails/paths.rb38
-rw-r--r--railties/lib/rails/railtie.rb4
-rw-r--r--railties/lib/rails/tasks/routes.rake3
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/application/configuration_test.rb57
-rw-r--r--railties/test/application/console_test.rb18
-rw-r--r--railties/test/application/rake_test.rb22
-rw-r--r--railties/test/application/test_test.rb25
-rw-r--r--railties/test/generators/app_generator_test.rb68
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb14
-rw-r--r--railties/test/generators/shared_generator_tests.rb3
-rw-r--r--version.rb2
219 files changed, 4021 insertions, 19959 deletions
diff --git a/.gitignore b/.gitignore
index 8daa1e4dcd..be764143aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ railties/doc
railties/guides/output
railties/tmp
.rvmrc
+RDOC_MAIN.rdoc \ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 8b6ddbd7c9..23b1a1096b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,13 +5,9 @@ gemspec
if ENV['AREL']
gem "arel", :path => ENV['AREL']
else
- gem "arel", :git => "git://github.com/rails/arel.git"
+ gem "arel", '~> 2.1.0'
end
-gem "rack", :git => "git://github.com/rack/rack.git"
-gem "rack-test", :git => "git://github.com/brynary/rack-test.git"
-
-gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git"
gem "coffee-script"
gem "sass"
gem "uglifier"
@@ -31,7 +27,6 @@ gem "memcache-client", ">= 1.8.5"
platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
- gem 'ruby-prof'
gem "json"
end
@@ -48,6 +43,9 @@ platforms :ruby do
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.4"
+ group :test do
+ gem 'ruby-prof'
+ end
# AR
gem "sqlite3", "~> 1.3.3"
diff --git a/RAILS_VERSION b/RAILS_VERSION
index 30afce968e..b4e716a7c1 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-3.1.0.beta
+3.1.0.beta1
diff --git a/README.rdoc b/README.rdoc
index 216a122c66..143fdfeb75 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,6 +1,6 @@
-== Welcome to \Rails
+== Welcome to Rails
-\Rails is a web-application framework that includes everything needed to create
+Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the Model-View-Control pattern.
This pattern splits the view (also called the presentation) into "dumb"
@@ -11,7 +11,7 @@ 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 an object-relational mapping
+In Rails, the model is handled by what's called an 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 its
@@ -22,17 +22,17 @@ 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 its
+Rails. You can read more about Action Pack in its
{README}[link:files/actionpack/README_rdoc.html].
== Getting Started
-1. Install \Rails at the command prompt if you haven't yet:
+1. Install Rails at the command prompt if you haven't yet:
gem install rails
-2. At the command prompt, create a new \Rails application:
+2. At the command prompt, create a new Rails application:
rails new myapp
@@ -59,10 +59,10 @@ more separate. Each of these packages can be used independently outside of
== Contributing
-We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Rails
+We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails
guide}[http://edgeguides.rubyonrails.org/contributing_to_rails.html] for guidelines about how
to proceed. {Join us}[http://contributors.rubyonrails.org]!
== License
-Ruby on \Rails is released under the MIT license.
+Ruby on Rails is released under the MIT license.
diff --git a/Rakefile b/Rakefile
index 607ce01fd0..92b2e77963 100755
--- a/Rakefile
+++ b/Rakefile
@@ -49,14 +49,24 @@ end
desc "Generate documentation for the Rails framework"
RDoc::Task.new do |rdoc|
+ RDOC_MAIN = 'RDOC_MAIN.rdoc'
+
+ rdoc.before_running_rdoc do
+ rdoc_main = File.read('README.rdoc')
+ rdoc_main.gsub!(/\b(?=Rails)\b/) { '\\' }
+ File.open(RDOC_MAIN, 'w') do |f|
+ f.write(rdoc_main)
+ end
+
+ rdoc.rdoc_files.include(RDOC_MAIN)
+ end
+
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
rdoc.options << '-f' << 'horo'
rdoc.options << '-c' << 'utf-8'
- rdoc.options << '-m' << 'README.rdoc'
-
- rdoc.rdoc_files.include('README.rdoc')
+ rdoc.options << '-m' << RDOC_MAIN
rdoc.rdoc_files.include('railties/CHANGELOG')
rdoc.rdoc_files.include('railties/MIT-LICENSE')
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 1e91d62e24..f00a0c8ae0 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -349,8 +349,7 @@ module ActionMailer #:nodoc:
include AbstractController::Translation
include AbstractController::AssetPaths
- cattr_reader :protected_instance_variables
- @@protected_instance_variables = []
+ self.protected_instance_variables = %w(@_action_has_layout)
helper ActionMailer::MailHelper
include ActionMailer::OldApi
diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb
index 04728cafb0..bfa9499764 100644
--- a/actionmailer/lib/action_mailer/old_api.rb
+++ b/actionmailer/lib/action_mailer/old_api.rb
@@ -8,7 +8,7 @@ module ActionMailer
included do
extend ActionMailer::AdvAttrAccessor
- self.protected_instance_variables.concat %w(@parts @mail_was_called)
+ self.protected_instance_variables.concat %w(@parts @mail_was_called @headers)
# Specify the BCC addresses for the message
adv_attr_accessor :bcc
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 27eb6c2b9b..8cf3780fbc 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -3,7 +3,7 @@ module ActionMailer
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 76dbfe7895..2bb1461ec9 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,10 +1,36 @@
*Rails 3.1.0 (unreleased)*
+* Only show dump of regular env methods on exception screen (not all the rack crap) [DHH]
+
+* auto_link has been removed with no replacement. If you still use auto_link
+ please install the rails_autolink gem:
+ http://github.com/tenderlove/rails_autolink
+
+ [tenderlove]
+
+* Added streaming support, you can enable it with: [José Valim]
+
+ class PostsController < ActionController::Base
+ stream :only => :index
+ end
+
+ Please read the docs at `ActionController::Streaming` for more information.
+
+* Added `ActionDispatch::Request.ignore_accept_header` to ignore accept headers and only consider the format given as parameter [José Valim]
+
+* Created `ActionView::Renderer` and specified an API for `ActionView::Context`, check those objects for more information [José Valim]
+
+* Added `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist]
+
+ This can be customized by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb`
+
* RJS has been extracted out to a gem. [fxn]
-* Implicit actions named not_implemented can be rendered [Santiago Pastorino]
+* Implicit actions named not_implemented can be rendered. [Santiago Pastorino]
+
+* Wildcard route will always match the optional format segment by default. [Prem Sichanugrist]
-* Wildcard route will always matching the optional format segment by default. For example if you have this route:
+ For example if you have this route:
map '*pages' => 'pages#show'
@@ -88,8 +114,6 @@ tested.
Keys are dasherized. Values are JSON-encoded, except for strings and symbols. [Stephen Celis]
-* Added render :once. You can pass either a string or an array of strings and Rails will ensure they each of them are rendered just once. [José Valim]
-
* Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim]
* :rhtml and :rxml were finally removed as template handlers. [José Valim]
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 0d667a76a7..fdd894fe63 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -19,12 +19,13 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('rack-cache', '~> 1.0.0')
+ s.add_dependency('rack-cache', '~> 1.0.1')
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.6.0beta1')
- s.add_dependency('rack', '~> 1.2.1')
- s.add_dependency('rack-test', '~> 0.5.7')
+ s.add_dependency('rack', '~> 1.3.0.beta')
+ s.add_dependency('rack-test', '~> 0.6.0')
s.add_dependency('rack-mount', '~> 0.7.2')
- s.add_dependency('tzinfo', '~> 0.3.23')
+ s.add_dependency('sprockets', '~> 2.0.0.beta.2')
+ s.add_dependency('tzinfo', '~> 0.3.27')
s.add_dependency('erubis', '~> 2.7.0')
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 0951267fea..f67d0e558e 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -130,27 +130,39 @@ module AbstractController
self.class.action_methods
end
- # Returns true if the name can be considered an action. This can
- # be overridden in subclasses to modify the semantics of what
- # can be considered an action.
+ # Returns true if a method for the action is available and
+ # can be dispatched, false otherwise.
#
- # For instance, this is overriden by ActionController to add
- # the implicit rendering feature.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- def action_method?(name)
- self.class.action_methods.include?(name)
+ # Notice that <tt>action_methods.include?("foo")</tt> may return
+ # false and <tt>available_action?("foo")</tt> returns true because
+ # available action consider actions that are also available
+ # through other means, for example, implicit render ones.
+ def available_action?(action_name)
+ method_for_action(action_name).present?
end
private
+ # Returns true if the name can be considered an action because
+ # it has a method defined in the controller.
+ #
+ # ==== Parameters
+ # * <tt>name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
+ #
+ # :api: private
+ def action_method?(name)
+ self.class.action_methods.include?(name)
+ end
+
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
+ #
+ # Notice that the first argument is the method to be dispatched
+ # which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index f7b2b7ff53..e8426bc52b 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -13,8 +13,8 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
- def process_action(method_name, *args)
- run_callbacks(:process_action, method_name) do
+ def process_action(*args)
+ run_callbacks(:process_action, action_name) do
super
end
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 0ff1c0491a..dc9778a416 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -4,8 +4,6 @@ module AbstractController
module Helpers
extend ActiveSupport::Concern
- include Rendering
-
included do
class_attribute :_helpers
self._helpers = Module.new
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index d1b87b67ee..8f73e244d7 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -292,15 +292,15 @@ module AbstractController
end
end
- attr_writer :action_has_layout
+ attr_internal_writer :action_has_layout
def initialize(*)
- @action_has_layout = true
+ @_action_has_layout = true
super
end
def action_has_layout?
- @action_has_layout
+ @_action_has_layout
end
private
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 306bd41e2d..ab2c532859 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -32,9 +32,13 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
-
include AbstractController::ViewPaths
+ included do
+ config_accessor :protected_instance_variables, :instance_reader => false
+ self.protected_instance_variables = []
+ end
+
# Overwrite process to setup I18n proxy.
def process(*) #:nodoc:
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
@@ -46,46 +50,27 @@ module AbstractController
module ClassMethods
def view_context_class
@view_context_class ||= begin
- controller = self
- Class.new(ActionView::Base) do
- if controller.respond_to?(:_routes) && controller._routes
- include controller._routes.url_helpers
- include controller._routes.mounted_helpers
- end
-
- if controller.respond_to?(:_helpers)
- include controller._helpers
-
- # TODO: Fix RJS to not require this
- self.helpers = controller._helpers
- end
- end
+ routes = _routes if respond_to?(:_routes)
+ helpers = _helpers if respond_to?(:_helpers)
+ ActionView::Base.prepare(routes, helpers)
end
end
+ end
- def parent_prefixes
- @parent_prefixes ||= begin
- parent_controller = superclass
- prefixes = []
-
- until parent_controller.abstract?
- prefixes << parent_controller.controller_path
- parent_controller = parent_controller.superclass
- end
+ attr_internal_writer :view_context_class
- prefixes
- end
- end
+ # Explicitly define protected_instance_variables so it can be
+ # inherited and overwritten by other modules if needed.
+ def protected_instance_variables
+ config.protected_instance_variables
end
- attr_writer :view_context_class
-
def view_context_class
- @view_context_class || self.class.view_context_class
+ @_view_context_class || self.class.view_context_class
end
def initialize(*)
- @view_context_class = nil
+ @_view_context_class = nil
super
end
@@ -99,7 +84,12 @@ module AbstractController
#
# Override this method in a module to change the default behavior.
def view_context
- view_context_class.new(lookup_context, view_assigns, self)
+ view_context_class.new(view_renderer, view_assigns, self)
+ end
+
+ # Returns an object that is able to render templates.
+ def view_renderer
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
end
# Normalize arguments, options and then delegates render_to_body and
@@ -127,26 +117,24 @@ module AbstractController
# Find and renders a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
- view_context.render(options)
- end
-
- # The prefixes used in render "foo" shortcuts.
- def _prefixes
- @_prefixes ||= begin
- parent_prefixes = self.class.parent_prefixes
- parent_prefixes.dup.unshift(controller_path)
- end
+ view_renderer.render(view_context, options)
end
private
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
+ @_action_name @_response_body @_formats @_prefixes @_config
+ @_view_context_class @_view_renderer @_lookup_context
+ )
+
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# :api: public
def view_assigns
hash = {}
variables = instance_variable_names
- variables -= protected_instance_variables if respond_to?(:protected_instance_variables)
+ variables -= protected_instance_variables
+ variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
hash
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index cea0f5ad1e..6b7aae8c74 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -11,11 +11,36 @@ module AbstractController
delegate :find_template, :template_exists?, :view_paths, :formats, :formats=,
:locale, :locale=, :to => :lookup_context
+ module ClassMethods
+ def parent_prefixes
+ @parent_prefixes ||= begin
+ parent_controller = superclass
+ prefixes = []
+
+ until parent_controller.abstract?
+ prefixes << parent_controller.controller_path
+ parent_controller = parent_controller.superclass
+ end
+
+ prefixes
+ end
+ end
+ end
+
+ # The prefixes used in render "foo" shortcuts.
+ def _prefixes
+ @_prefixes ||= begin
+ parent_prefixes = self.class.parent_prefixes
+ parent_prefixes.dup.unshift(controller_path)
+ end
+ end
+
# LookupContext is the object responsible to hold all information required to lookup
# templates, i.e. view paths and details. Check ActionView::LookupContext for more
# information.
def lookup_context
- @lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup)
+ @_lookup_context ||=
+ ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
end
def details_for_lookup
@@ -67,4 +92,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index aab2b9dc25..eba5e9377b 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -23,6 +23,7 @@ module ActionController
autoload :ImplicitRender
autoload :Instrumentation
autoload :MimeResponds
+ autoload :ParamsWrapper
autoload :RackDelegation
autoload :Redirecting
autoload :Renderers
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index ca0dccf575..c03c77cb4a 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -206,13 +206,17 @@ module ActionController
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
+ # Before callbacks should also be executed the earliest as possible, so
+ # also include them at the bottom.
+ AbstractController::Callbacks,
+
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
Instrumentation,
- # Before callbacks should also be executed the earliest as possible, so
- # also include them at the bottom.
- AbstractController::Callbacks,
+ # Params wrapper should come before instrumentation so they are
+ # properly showed in logs
+ ParamsWrapper,
# The same with rescue, append it at the end to wrap as much as possible.
Rescue
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3fae697cc3..8d813a8e38 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -7,8 +7,10 @@ module ActionController
def start_processing(event)
payload = event.payload
params = payload[:params].except(*INTERNAL_PARAMS)
+ format = payload[:format]
+ format = format.to_s.upcase if format.is_a?(Symbol)
- info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}"
+ info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
info " Parameters: #{params.inspect}" unless params.empty?
end
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index 006b9fd456..05dca445a4 100644
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -18,13 +18,10 @@ module ActionController
delegate :default_charset=, :to => "ActionDispatch::Response"
end
- # TODO: Update protected instance variables list
- config_accessor :protected_instance_variables
- self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render
- @variables_added @request_origin @url
- @parent_controller @action_name
- @before_filter_chain_aborted @_headers @_params
- @_response)
+ self.protected_instance_variables = %w(
+ @_status @_headers @_params @_env @_response @_request
+ @_view_runtime @_stream @_url_options @_action_has_layout
+ )
def rescue_action(env)
raise env["action_dispatch.rescue.exception"]
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 3ec0c4c6a4..e8e465d3ba 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,21 +1,19 @@
module ActionController
module ImplicitRender
def send_action(method, *args)
- if respond_to?(method, true)
- ret = super
- default_render unless response_body
- ret
- else
- default_render
- end
+ ret = super
+ default_render unless response_body
+ ret
end
def default_render(*args)
render(*args)
end
- def action_method?(action_name)
- super || template_exists?(action_name.to_s, _prefixes)
+ def method_for_action(action_name)
+ super || if template_exists?(action_name.to_s, _prefixes)
+ "default_render"
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index dc3ea939e6..16cbbce2fb 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -14,12 +14,12 @@ module ActionController
attr_internal :view_runtime
- def process_action(action, *args)
+ def process_action(*args)
raw_payload = {
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :formats => request.formats.map(&:to_sym),
+ :format => request.format.ref,
:method => request.method,
:path => (request.fullpath rescue "unknown")
}
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
new file mode 100644
index 0000000000..881af74147
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -0,0 +1,224 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/array/wrap'
+require 'action_dispatch/http/mime_types'
+
+module ActionController
+ # Wraps parameters hash into nested hash. This will allow client to submit
+ # POST request without having to specify a root element in it.
+ #
+ # By default this functionality won't be enabled. You can enable
+ # it globally by setting +ActionController::Base.wrap_parameters+:
+ #
+ # ActionController::Base.wrap_parameters = [:json]
+ #
+ # You could also turn it on per controller by setting the format array to
+ # non-empty array:
+ #
+ # class UsersController < ApplicationController
+ # wrap_parameters :format => [:json, :xml]
+ # end
+ #
+ # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to
+ # send JSON parameters like this:
+ #
+ # {"user": {"name": "Konata"}}
+ #
+ # You can now just send a parameters like this:
+ #
+ # {"name": "Konata"}
+ #
+ # And it will be wrapped into a nested hash with the key name matching
+ # controller's name. For example, if you're posting to +UsersController+,
+ # your new +params+ hash will look like this:
+ #
+ # {"name" => "Konata", "user" => {"name" => "Konata"}}
+ #
+ # You can also specify the key in which the parameters should be wrapped to,
+ # and also the list of attributes it should wrap by using either +:only+ or
+ # +:except+ options like this:
+ #
+ # class UsersController < ApplicationController
+ # wrap_parameters :person, :only => [:username, :password]
+ # end
+ #
+ # If you're going to pass the parameters to an +ActiveModel+ object (such as
+ # +User.new(params[:user])+), you might consider passing the model class to
+ # the method instead. The +ParamsWrapper+ will actually try to determine the
+ # list of attribute names from the model and only wrap those attributes:
+ #
+ # class UsersController < ApplicationController
+ # wrap_parameters Person
+ # end
+ #
+ # You still could pass +:only+ and +:except+ to set the list of attributes
+ # you want to wrap.
+ #
+ # By default, if you don't specify the key in which the parameters would be
+ # wrapped to, +ParamsWrapper+ will actually try to determine if there's
+ # a model related to it or not. This controller, for example:
+ #
+ # class Admin::UsersController < ApplicationController
+ # end
+ #
+ # will try to check if +Admin::User+ or +User+ model exists, and use it to
+ # determine the wrapper key respectively. If both of the model doesn't exists,
+ # it will then fallback to use +user+ as the key.
+ module ParamsWrapper
+ extend ActiveSupport::Concern
+
+ EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
+
+ included do
+ class_attribute :_wrapper_options
+ self._wrapper_options = {:format => []}
+ end
+
+ module ClassMethods
+ # Sets the name of the wrapper key, or the model which +ParamsWrapper+
+ # would use to determine the attribute names from.
+ #
+ # ==== Examples
+ # wrap_parameters :format => :xml
+ # # enables the parmeter wrapper for XML format
+ #
+ # wrap_parameters :person
+ # # wraps parameters into +params[:person]+ hash
+ #
+ # wrap_parameters Person
+ # # wraps parameters by determine the wrapper key from Person class
+ # (+person+, in this case) and the list of attribute names
+ #
+ # wrap_parameters :only => [:username, :title]
+ # # wraps only +:username+ and +:title+ attributes from parameters.
+ #
+ # wrap_parameters false
+ # # disable parameters wrapping for this controller altogether.
+ #
+ # ==== Options
+ # * <tt>:format</tt> - The list of formats in which the parameters wrapper
+ # will be enabled.
+ # * <tt>:only</tt> - The list of attribute names which parameters wrapper
+ # will wrap into a nested hash.
+ # * <tt>:except</tt> - The list of attribute names which parameters wrapper
+ # will exclude from a nested hash.
+ def wrap_parameters(name_or_model_or_options, options = {})
+ model = nil
+
+ case name_or_model_or_options
+ when Hash
+ options = name_or_model_or_options
+ when false
+ options = options.merge(:format => [])
+ when Symbol, String
+ options = options.merge(:name => name_or_model_or_options)
+ else
+ model = name_or_model_or_options
+ end
+
+ _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
+ end
+
+ # Sets the default wrapper key or model which will be used to determine
+ # wrapper key and attribute names. Will be called automatically when the
+ # module is inherited.
+ def inherited(klass)
+ if klass._wrapper_options[:format].present?
+ klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
+ end
+ super
+ end
+
+ protected
+
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singularize name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ def _default_wrap_model
+ model_name = self.name.sub(/Controller$/, '').singularize
+
+ begin
+ model_klass = model_name.constantize
+ rescue NameError => e
+ unscoped_model_name = model_name.split("::", 2).last
+ break if unscoped_model_name == model_name
+ model_name = unscoped_model_name
+ end until model_klass
+
+ model_klass
+ end
+
+ def _set_wrapper_defaults(options, model=nil)
+ options = options.dup
+
+ unless options[:only] || options[:except]
+ model ||= _default_wrap_model
+ if model.respond_to?(:column_names)
+ options[:only] = model.column_names
+ end
+ end
+
+ unless options[:name]
+ model ||= _default_wrap_model
+ options[:name] = model ? model.to_s.demodulize.underscore :
+ controller_name.singularize
+ end
+
+ options[:only] = Array.wrap(options[:only]).collect(&:to_s) if options[:only]
+ options[:except] = Array.wrap(options[:except]).collect(&:to_s) if options[:except]
+ options[:format] = Array.wrap(options[:format])
+
+ self._wrapper_options = options
+ end
+ end
+
+ # Performs parameters wrapping upon the request. Will be called automatically
+ # by the metal call stack.
+ def process_action(*args)
+ if _wrapper_enabled?
+ wrapped_hash = _wrap_parameters request.request_parameters
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters
+
+ # This will make the wrapped hash accessible from controller and view
+ request.parameters.merge! wrapped_hash
+ request.request_parameters.merge! wrapped_hash
+
+ # This will make the wrapped hash displayed in the log file
+ request.filtered_parameters.merge! wrapped_filtered_hash
+ end
+ super
+ end
+
+ private
+
+ # Returns the wrapper key which will use to stored wrapped parameters.
+ def _wrapper_key
+ _wrapper_options[:name]
+ end
+
+ # Returns the list of enabled formats.
+ def _wrapper_formats
+ _wrapper_options[:format]
+ end
+
+ # Returns the list of parameters which will be selected for wrapped.
+ def _wrap_parameters(parameters)
+ value = if only = _wrapper_options[:only]
+ parameters.slice(*only)
+ else
+ except = _wrapper_options[:except] || []
+ parameters.except(*(except + EXCLUDE_PARAMETERS))
+ end
+
+ { _wrapper_key => value }
+ end
+
+ # Checks if we should perform parameters wrapping.
+ def _wrapper_enabled?
+ ref = request.content_mime_type.try(:ref)
+ _wrapper_formats.include?(ref) && !request.request_parameters[_wrapper_key]
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 59a3621f72..ebadb29ea7 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -68,7 +68,7 @@ module ActionController #:nodoc:
# respond_with(@project, @task)
# end
#
- # Giving an array of resources, you ensure that the responder will redirect to
+ # Giving several resources ensures that the responder will redirect to
# <code>project_task_url</code> instead of <code>task_url</code>.
#
# Namespaced and singleton resources require a symbol to be given, as in
@@ -77,6 +77,11 @@ module ActionController #:nodoc:
#
# respond_with(@project, :manager, @task)
#
+ # Note that if you give an array, it will be treated as a collection,
+ # so the following is not equivalent:
+ #
+ # respond_with [@project, :manager, @task]
+ #
# === Custom options
#
# <code>respond_with</code> also allow you to pass options that are forwarded
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index b9bd49f670..3892a12407 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -2,7 +2,209 @@ require 'active_support/core_ext/file/path'
require 'rack/chunked'
module ActionController #:nodoc:
- # Methods for sending streaming templates back to the client.
+ # Allows views to be streamed back to the client as they are rendered.
+ #
+ # The default way Rails renders views is by first rendering the template
+ # and then the layout. The response is sent to the client after the whole
+ # template is rendered, all queries are made, and the layout is processed.
+ #
+ # Streaming inverts the rendering flow by rendering the layout first and
+ # streaming each part of the layout as they are processed. This allows the
+ # header of the HTML (which is usually in the layout) to be streamed back
+ # to client very quickly, allowing JavaScripts and stylesheets to be loaded
+ # earlier than usual.
+ #
+ # This approach was introduced in Rails 3.1 and is still improving. Several
+ # Rack middlewares may not work and you need to be careful when streaming.
+ # Those points are going to be addressed soon.
+ #
+ # In order to use streaming, you will need to use a Ruby version that
+ # supports fibers (fibers are supported since version 1.9.2 of the main
+ # Ruby implementation).
+ #
+ # == Examples
+ #
+ # Streaming can be added to a controller easily, all you need to do is
+ # call +stream+ in the controller class:
+ #
+ # class PostsController
+ # stream
+ # end
+ #
+ # The +stream+ method accepts the same options as +before_filter+ and friends:
+ #
+ # class PostsController
+ # stream :only => :index
+ # end
+ #
+ # You can also selectively turn on streaming for specific actions:
+ #
+ # class PostsController
+ # def index
+ # @posts = Post.scoped
+ # render :stream => true
+ # end
+ # end
+ #
+ # == When to use streaming
+ #
+ # Streaming may be considered to be overkill for lightweight actions like
+ # +new+ or +edit+. The real benefit of streaming is on expensive actions
+ # that, for example, do a lot of queries on the database.
+ #
+ # In such actions, you want to delay queries execution as much as you can.
+ # For example, imagine the following +dashboard+ action:
+ #
+ # def dashboard
+ # @posts = Post.all
+ # @pages = Page.all
+ # @articles = Article.all
+ # end
+ #
+ # Most of the queries here are happening in the controller. In order to benefit
+ # from streaming you would want to rewrite it as:
+ #
+ # def dashboard
+ # # Allow lazy execution of the queries
+ # @posts = Post.scoped
+ # @pages = Page.scoped
+ # @articles = Article.scoped
+ # render :stream => true
+ # end
+ #
+ # == Communication between layout and template
+ #
+ # When streaming, rendering happens top-down instead of inside-out.
+ # Rails starts with the layout, and the template is rendered later,
+ # when its +yield+ is reached.
+ #
+ # This means that, if your application currently relies on instance
+ # variables set in the template to be used in the layout, they won't
+ # work once you move to streaming. The proper way to communicate
+ # between layout and template, regardless of whether you use streaming
+ # or not, is by using +content_for+, +provide+ and +yield+.
+ #
+ # Take a simple example where the layout expects the template to tell
+ # which title to use:
+ #
+ # <html>
+ # <head><title><%= yield :title %></title></head>
+ # <body><%= yield %></body>
+ # </html>
+ #
+ # You would use +content_for+ in your template to specify the title:
+ #
+ # <%= content_for :title, "Main" %>
+ # Hello
+ #
+ # And the final result would be:
+ #
+ # <html>
+ # <head><title>Main</title></head>
+ # <body>Hello</body>
+ # </html>
+ #
+ # However, if +content_for+ is called several times, the final result
+ # would have all calls concatenated. For instance, if we have the following
+ # template:
+ #
+ # <%= content_for :title, "Main" %>
+ # Hello
+ # <%= content_for :title, " page" %>
+ #
+ # The final result would be:
+ #
+ # <html>
+ # <head><title>Main page</title></head>
+ # <body>Hello</body>
+ # </html>
+ #
+ # This means that, if you have <code>yield :title</code> in your layout
+ # and you want to use streaming, you would have to render the whole template
+ # (and eventually trigger all queries) before streaming the title and all
+ # assets, which kills the purpose of streaming. For this reason Rails 3.1
+ # introduces a new helper called +provide+ that does the same as +content_for+
+ # but tells the layout to stop searching for other entries and continue rendering.
+ #
+ # For instance, the template above using +provide+ would be:
+ #
+ # <%= provide :title, "Main" %>
+ # Hello
+ # <%= content_for :title, " page" %>
+ #
+ # Giving:
+ #
+ # <html>
+ # <head><title>Main</title></head>
+ # <body>Hello</body>
+ # </html>
+ #
+ # That said, when streaming, you need to properly check your templates
+ # and choose when to use +provide+ and +content_for+.
+ #
+ # == Headers, cookies, session and flash
+ #
+ # When streaming, the HTTP headers are sent to the client right before
+ # it renders the first line. This means that, modifying headers, cookies,
+ # session or flash after the template starts rendering will not propagate
+ # to the client.
+ #
+ # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # will be raised, showing those objects are closed for modification.
+ #
+ # == Middlewares
+ #
+ # Middlewares that need to manipulate the body won't work with streaming.
+ # You should disable those middlewares whenever streaming in development
+ # or production. For instance, +Rack::Bug+ won't work when streaming as it
+ # needs to inject contents in the HTML body.
+ #
+ # Also +Rack::Cache+ won't work with streaming as it does not support
+ # streaming bodies yet. Whenever streaming Cache-Control is automatically
+ # set to "no-cache".
+ #
+ # == Errors
+ #
+ # When it comes to streaming, exceptions get a bit more complicated. This
+ # happens because part of the template was already rendered and streamed to
+ # the client, making it impossible to render a whole exception page.
+ #
+ # Currently, when an exception happens in development or production, Rails
+ # will automatically stream to the client:
+ #
+ # "><script type="text/javascript">window.location = "/500.html"</script></html>
+ #
+ # The first two characters (">) are required in case the exception happens
+ # while rendering attributes for a given tag. You can check the real cause
+ # for the exception in your logger.
+ #
+ # == Web server support
+ #
+ # Not all web servers support streaming out-of-the-box. You need to check
+ # the instructions for each of them.
+ #
+ # ==== Unicorn
+ #
+ # Unicorn supports streaming but it needs to be configured. For this, you
+ # need to create a config file as follow:
+ #
+ # # unicorn.config.rb
+ # listen 3000, :tcp_nopush => false
+ #
+ # And use it on initialization:
+ #
+ # unicorn_rails --config-file unicorn.config.rb
+ #
+ # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
+ # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
+ #
+ # If you are using Unicorn with Nginx, you may need to tweak Nginx.
+ # Streaming should work out of the box on Rainbows.
+ #
+ # ==== Passenger
+ #
+ # To be described.
+ #
module Streaming
extend ActiveSupport::Concern
@@ -51,7 +253,7 @@ module ActionController #:nodoc:
# Call render_to_body if we are streaming instead of usual +render+.
def _render_template(options) #:nodoc:
if options.delete(:stream)
- Rack::Chunked::Body.new view_context.render_body(options)
+ Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
else
super
end
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
index 0fe2e6d1a6..1eadfc0390 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb
@@ -128,6 +128,8 @@ module HTML
# (no parent element).
# * <tt>:empty</tt> -- Match the element only if it has no child elements,
# and no text content.
+ # * <tt>:content(string)</tt> -- Match the element only if it has <tt>string</tt>
+ # as its text content (ignoring leading and trailing whitespace).
# * <tt>:only-child</tt> -- Match the element if it is the only child (element)
# of its parent element.
# * <tt>:only-of-type</tt> -- Match the element if it is the only child (element)
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 68ba1a81b5..980c658ab7 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -1,6 +1,13 @@
module ActionDispatch
module Http
module MimeNegotiation
+ extend ActiveSupport::Concern
+
+ included do
+ mattr_accessor :ignore_accept_header
+ self.ignore_accept_header = false
+ end
+
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
@@ -42,16 +49,14 @@ module ActionDispatch
formats.first
end
- BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
-
def formats
- accept = @env['HTTP_ACCEPT']
-
@env["action_dispatch.request.formats"] ||=
if parameters[:format]
Array(Mime[parameters[:format]])
- elsif xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS)
+ elsif use_accept_header && valid_accept_header
accepts
+ elsif xhr?
+ [Mime::JS]
else
[Mime::HTML]
end
@@ -87,6 +92,18 @@ module ActionDispatch
order.include?(Mime::ALL) ? formats.first : nil
end
+
+ protected
+
+ BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
+
+ def valid_accept_header
+ xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS)
+ end
+
+ def use_accept_header
+ !self.class.ignore_accept_header
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index f07ac44f7a..ccb866f4f7 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -17,16 +17,17 @@ module ActionDispatch
include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
- LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
-
- %w[ AUTH_TYPE GATEWAY_INTERFACE
+ LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
+ ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
- HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env|
+ HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
+
+ ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase}
@env["#{env}"]
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index ac0fd9607d..d9c07d6ca3 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -165,7 +165,7 @@ module ActionDispatch
# such as 2 to catch <tt>["www"]</tt> instead of <tt>"www.rubyonrails"</tt>
# in "www.rubyonrails.co.uk".
def subdomain(tld_length = @@tld_length)
- subdomains(tld_length)
+ subdomains(tld_length).join(".")
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 9c9ccc62f5..8ebf870b95 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -59,7 +59,7 @@ module ActionDispatch
end
def set_session(env, sid, session_data, options)
- persistent_session_id!(session_data, sid)
+ session_data.merge("session_id" => sid)
end
def set_cookie(env, session_id, cookie)
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index b1adf3d2d1..c17c746096 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -116,7 +116,7 @@ module ActionDispatch
end
def render(status, body)
- [status, {'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s}, [body]]
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
end
def public_path
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 348f7b86b8..404943d720 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -2,10 +2,10 @@ require 'rack/utils'
module ActionDispatch
class FileHandler
- def initialize(root)
+ def initialize(root, cache_control)
@root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
- @file_server = ::Rack::File.new(@root)
+ @file_server = ::Rack::File.new(@root, cache_control)
end
def match?(path)
@@ -35,18 +35,15 @@ module ActionDispatch
end
class Static
- FILE_METHODS = %w(GET HEAD).freeze
-
- def initialize(app, path)
+ def initialize(app, path, cache_control=nil)
@app = app
- @file_handler = FileHandler.new(path)
+ @file_handler = FileHandler.new(path, cache_control)
end
def call(env)
- path = env['PATH_INFO'].chomp('/')
- method = env['REQUEST_METHOD']
-
- if FILE_METHODS.include?(method)
+ case env['REQUEST_METHOD']
+ when 'GET', 'HEAD'
+ path = env['PATH_INFO'].chomp('/')
if match = @file_handler.match?(path)
env["PATH_INFO"] = match
return @file_handler.call(env)
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 97f7cf0bbe..0c5bafa666 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -24,7 +24,7 @@
<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
-<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env %></pre></div>
+<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
<h2 style="margin-top: 30px">Response</h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 2099fd069a..4b9d3141d5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,7 +1,7 @@
<h1>
<%=h @exception.class.to_s %>
<% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
+ in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.message %></pre>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 6c32fb17b8..6e71fd7ddc 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -1,11 +1,13 @@
-<html xmlns="http://www.w3.org/1999/xhtml">
+<!DOCTYPE html>
+<html lang="en">
<head>
+ <meta charset="utf-8" />
<title>Action Controller: Exception caught</title>
<style>
body { background-color: #fff; color: #333; }
body, p, ol, ul, td {
- font-family: verdana, arial, helvetica, sans-serif;
+ font-family: helvetica, verdana, arial, sans-serif;
font-size: 13px;
line-height: 18px;
}
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 0a3bd5fe40..f51cc3711b 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -9,10 +9,12 @@ module ActionDispatch
config.action_dispatch.show_exceptions = true
config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
+ config.action_dispatch.ignore_accept_header = false
config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
+ ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 1d09091dc7..97e8ccc9a5 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -3,6 +3,7 @@ require 'forwardable'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/module/remove_method'
module ActionDispatch
module Routing
@@ -160,7 +161,7 @@ module ActionDispatch
# We use module_eval to avoid leaks
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_method :#{selector} if method_defined?(:#{selector})
+ remove_possible_method :#{selector}
def #{selector}(*args)
options = args.extract_options!
@@ -194,7 +195,7 @@ module ActionDispatch
hash_access_method = hash_access_name(name, kind)
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_method :#{selector} if method_defined?(:#{selector})
+ remove_possible_method :#{selector}
def #{selector}(*args)
url_for(#{hash_access_method}(*args))
end
@@ -240,6 +241,11 @@ module ActionDispatch
end
def eval_block(block)
+ if block.arity == 1
+ raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " <<
+ "or add the rails_legacy_mapper gem to your Gemfile"
+ end
mapper = Mapper.new(self)
if default_scope
mapper.with_default_scope(default_scope, &block)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index e209978fb7..3335742d47 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -42,7 +42,7 @@ module ActionDispatch
elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
assert_block("") { true } # to count the assertion
else
- assert(false, build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code))
+ flunk(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code))
end
end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 170ceb299a..584e5c3791 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -3,7 +3,7 @@ module ActionPack
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 4547aceb28..92b6f7c770 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -34,13 +34,12 @@ module ActionView
autoload :Context
autoload :Helpers
autoload :LookupContext
- autoload :Partials
autoload :PathSet
- autoload :Rendering
autoload :Template
autoload :TestCase
autoload_under "renderer" do
+ autoload :Renderer
autoload :AbstractRenderer
autoload :PartialRenderer
autoload :TemplateRenderer
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 87501d5b88..c98110353f 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -131,7 +131,7 @@ module ActionView #:nodoc:
#
# More builder documentation can be found at http://builder.rubyforge.org.
class Base
- include Helpers, Rendering, Partials, ::ERB::Util, Context
+ include Helpers, ::ERB::Util, Context
# Specify the proc used to decorate input tags that refer to attributes with errors.
cattr_accessor :field_error_proc
@@ -139,7 +139,6 @@ module ActionView #:nodoc:
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
- # Currently this is private API and may be changed at *any* time.
cattr_accessor :streaming_completion_on_exception
@@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
@@ -157,56 +156,61 @@ module ActionView #:nodoc:
def cache_template_loading=(value)
ActionView::Resolver.caching = value
end
- end
-
- attr_accessor :_template, :_view_flow
- attr_internal :request, :controller, :config, :assigns, :lookup_context
-
- delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
- delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
- :flash, :action_name, :controller_name, :to => :controller
+ def process_view_paths(value)
+ value.is_a?(PathSet) ?
+ value.dup : ActionView::PathSet.new(Array.wrap(value))
+ end
- delegate :logger, :to => :controller, :allow_nil => true
+ def xss_safe? #:nodoc:
+ true
+ end
- def self.xss_safe? #:nodoc:
- true
+ # This method receives routes and helpers from the controller
+ # and return a subclass ready to be used as view context.
+ def prepare(routes, helpers) #:nodoc:
+ Class.new(self) do
+ if routes
+ include routes.url_helpers
+ include routes.mounted_helpers
+ end
+
+ if helpers
+ include helpers
+ self.helpers = helpers
+ end
+ end
+ end
end
- def self.process_view_paths(value)
- value.is_a?(PathSet) ?
- value.dup : ActionView::PathSet.new(Array.wrap(value))
- end
+ attr_accessor :view_renderer
+ attr_internal :config, :assigns
+
+ delegate :lookup_context, :to => :view_renderer
+ delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
def assign(new_assigns) # :nodoc:
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
- def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
- assign(assigns_for_first_render)
- self.helpers = Module.new unless self.class.helpers
-
+ def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
@_config = {}
- @_virtual_path = nil
- @_view_flow = OutputFlow.new
- @output_buffer = nil
- if @_controller = controller
- @_request = controller.request if controller.respond_to?(:request)
- @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
+ # Handle all these for backwards compatibility.
+ # TODO Provide a new API for AV::Base and deprecate this one.
+ if context.is_a?(ActionView::Renderer)
+ @view_renderer = context
+ elsif
+ lookup_context = context.is_a?(ActionView::LookupContext) ?
+ context : ActionView::LookupContext.new(context)
+ lookup_context.formats = formats if formats
+ lookup_context.prefixes = controller._prefixes if controller
+ @view_renderer = ActionView::Renderer.new(lookup_context)
end
- @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ?
- lookup_context : ActionView::LookupContext.new(lookup_context)
- @_lookup_context.formats = formats if formats
- end
-
- def controller_path
- @controller_path ||= controller && controller.controller_path
- end
-
- def controller_prefixes
- @controller_prefixes ||= controller && controller._prefixes
+ assign(assigns)
+ assign_controller(controller)
+ _prepare_context
end
ActiveSupport.run_load_hooks(:action_view, self)
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index a2a64de206..083856b2ca 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -8,33 +8,29 @@ module ActionView
# Action View contexts are supplied to Action Controller to render template.
# The default Action View context is ActionView::Base.
#
- # In order to work with ActionController, a Context must implement:
- #
- # Context#render_partial[options]
- # - responsible for setting options[:_template]
- # - Returns String with the rendered partial
- # options<Hash>:: see _render_partial in ActionView::Base
- # Context#render_template[template, layout, options, partial]
- # - Returns String with the rendered template
- # template<ActionView::Template>:: The template to render
- # layout<ActionView::Template>:: The layout to render around the template
- # options<Hash>:: See _render_template_with_layout in ActionView::Base
- # partial<Boolean>:: Whether or not the template to render is a partial
- #
- # An Action View context can also mix in Action View's helpers. In order to
- # mix in helpers, a context must implement:
- #
- # Context#controller
- # - Returns an instance of AbstractController
- #
- # In any case, a context must mix in ActionView::Context, which stores compiled
- # template and provides the output buffer.
+ # In order to work with ActionController, a Context must just include this module.
+ # The initialization of the variables used by the context (@output_buffer, @view_flow,
+ # and @virtual_path) is responsibility of the object that includes this module
+ # (although you can call _prepare_context defined below).
module Context
include CompiledTemplates
- attr_accessor :output_buffer
+ attr_accessor :output_buffer, :view_flow
+
+ # Prepares the context by setting the appropriate instance variables.
+ # :api: plugin
+ def _prepare_context
+ @view_flow = OutputFlow.new
+ @output_buffer = nil
+ @virtual_path = nil
+ end
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
+ # Encapsulates the interaction with the view flow so it
+ # returns the correct buffer on yield. This is usually
+ # overwriten by helpers to add more behavior.
+ # :api: plugin
+ def _layout_for(name=nil)
+ name ||= :layout
+ view_flow.get(name).html_safe
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
index 386a06511f..a8f740713f 100644
--- a/actionpack/lib/action_view/flows.rb
+++ b/actionpack/lib/action_view/flows.rb
@@ -34,7 +34,7 @@ module ActionView
@view = view
@parent = nil
@child = view.output_buffer
- @content = view._view_flow.content
+ @content = view.view_flow.content
@fiber = fiber
@root = Fiber.current.object_id
end
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 205116f610..78a68db282 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -9,6 +9,7 @@ module ActionView #:nodoc:
autoload :AtomFeedHelper
autoload :CacheHelper
autoload :CaptureHelper
+ autoload :ControllerHelper
autoload :CsrfHelper
autoload :DateHelper
autoload :DebugHelper
@@ -19,6 +20,7 @@ module ActionView #:nodoc:
autoload :NumberHelper
autoload :OutputSafetyHelper
autoload :RecordTagHelper
+ autoload :RenderingHelper
autoload :SanitizeHelper
autoload :SprocketsHelper
autoload :TagHelper
@@ -38,6 +40,7 @@ module ActionView #:nodoc:
include AtomFeedHelper
include CacheHelper
include CaptureHelper
+ include ControllerHelper
include CsrfHelper
include DateHelper
include DebugHelper
@@ -48,6 +51,7 @@ module ActionView #:nodoc:
include NumberHelper
include OutputSafetyHelper
include RecordTagHelper
+ include RenderingHelper
include SanitizeHelper
include SprocketsHelper
include TagHelper
diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb
index cb6737b94e..958f0e0a10 100644
--- a/actionpack/lib/action_view/helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_paths.rb
@@ -21,7 +21,6 @@ module ActionView
return source if is_uri?(source)
source = rewrite_extension(source, dir, ext) if ext
- source = "/#{dir}/#{source}" unless source[0] == ?/
source = rewrite_asset_path(source, dir)
if controller && include_host
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
index 38860431b4..cd0f8c8878 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -41,7 +41,8 @@ module ActionView
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
- def rewrite_asset_path(source, path = nil)
+ def rewrite_asset_path(source, dir)
+ source = "/#{dir}/#{source}" unless source[0] == ?/
path = config.asset_path
if path && path.respond_to?(:call)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 3d815b5e1f..e1ee0d0e1a 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -99,10 +99,9 @@ module ActionView
#
# When passing paths, the ".js" extension is optional.
#
- # To include the default JavaScript expansion pass <tt>:defaults</tt> as source.
- # By default, <tt>:defaults</tt> loads jQuery. If the application was generated
- # with "-j prototype" the libraries Prototype and Scriptaculous are loaded instead.
- # In any case, the defaults can be overridden in <tt>config/application.rb</tt>:
+ # If the application is not using the asset pipeline, to include the default JavaScript
+ # expansion pass <tt>:defaults</tt> as source. By default, <tt>:defaults</tt> loads jQuery,
+ # and that can be overridden in <tt>config/application.rb</tt>:
#
# config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
#
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 0139714240..ead7feb091 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -135,7 +135,7 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- result = @_view_flow.append(name, content) if content
+ result = @view_flow.append(name, content) if content
result unless content
end
@@ -146,7 +146,7 @@ module ActionView
# the layout to stop looking for more contents.
def provide(name, content = nil, &block)
content = capture(&block) if block_given?
- result = @_view_flow.append!(name, content) if content
+ result = @view_flow.append!(name, content) if content
result unless content
end
@@ -169,7 +169,7 @@ module ActionView
# </body>
# </html>
def content_for?(name)
- @_view_flow.get(name).present?
+ @view_flow.get(name).present?
end
# Use an alternate output buffer for the duration of the block.
diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb
new file mode 100644
index 0000000000..e22331cb3c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/controller_helper.rb
@@ -0,0 +1,21 @@
+module ActionView
+ module Helpers
+ # This module keeps all methods and behavior in ActionView
+ # that simply delegates to the controller.
+ module ControllerHelper #:nodoc:
+ attr_internal :controller, :request
+
+ delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
+ :flash, :action_name, :controller_name, :controller_path, :to => :controller
+
+ delegate :logger, :to => :controller, :allow_nil => true
+
+ def assign_controller(controller)
+ if @_controller = controller
+ @_request = controller.request if controller.respond_to?(:request)
+ @_config = controller.config.inheritable_copy if controller.respond_to?(:config)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 9277359d5c..eb8c96a6ae 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -94,9 +94,20 @@ module ActionView
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
- distance_in_years = distance_in_minutes / 525600
- minute_offset_for_leap_year = (distance_in_years / 4) * 1440
- remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ remainder = (minutes_with_offset % 525600)
+ distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index efe30441b1..b6bb90a3ce 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -102,6 +102,11 @@ module ActionView
include FormTagHelper
include UrlHelper
+ # Converts the given object to an ActiveModel compliant one.
+ def convert_to_model(object)
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+
# Creates a form and a scope around a specific model object that is used
# as a base for questioning about values for the fields.
#
@@ -1167,7 +1172,7 @@ module ActionView
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
+ self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
attr_accessor :object_name, :object, :options
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 49aa434020..9e0f8f32b7 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -74,6 +74,8 @@ module ActionView
# ==== Options
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:include_blank</tt> - If set to true, an empty option will be create
+ # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
@@ -99,18 +101,26 @@ module ActionView
# # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
# # <option>Write</option></select>
#
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), :include_blank => true
+ # # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select>
+ #
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), :prompt => "Select something"
+ # # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
+ #
# select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
# # <option>Paris</option><option>Rome</option></select>
def select_tag(name, option_tags = nil, options = {})
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
- if blank = options.delete(:include_blank)
- if blank.kind_of?(String)
- option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags
- else
- option_tags = "<option value=\"\"></option>".html_safe + option_tags
- end
+
+ if options.delete(:include_blank)
+ option_tags = "<option value=\"\"></option>".html_safe + option_tags
+ end
+
+ if prompt = options.delete(:prompt)
+ option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags
end
+
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
end
diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb
new file mode 100644
index 0000000000..47efdded42
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/rendering_helper.rb
@@ -0,0 +1,90 @@
+module ActionView
+ module Helpers
+ # = Action View Rendering
+ #
+ # Implements methods that allow rendering from a view context.
+ # In order to use this module, all you need is to implement
+ # view_renderer that returns an ActionView::Renderer object.
+ module RenderingHelper
+ # Returns the result of a render that's dictated by the options hash. The primary options are:
+ #
+ # * <tt>:partial</tt> - See ActionView::Partials.
+ # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
+ # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
+ # * <tt>:text</tt> - Renders the text passed in out.
+ #
+ # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
+ # as the locals hash.
+ def render(options = {}, locals = {}, &block)
+ case options
+ when Hash
+ if block_given?
+ view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block)
+ else
+ view_renderer.render(self, options)
+ end
+ else
+ view_renderer.render_partial(self, :partial => options, :locals => locals)
+ end
+ end
+
+ # Overwrites _layout_for in the context object so it supports the case a block is
+ # passed to a partial. Returns the contents that are yielded to a layout, given a
+ # name or a block.
+ #
+ # You can think of a layout as a method that is called with a block. If the user calls
+ # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
+ # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
+ #
+ # The user can override this default by passing a block to the layout:
+ #
+ # # The template
+ # <%= render :layout => "my_layout" do %>
+ # Content
+ # <% end %>
+ #
+ # # The layout
+ # <html>
+ # <%= yield %>
+ # </html>
+ #
+ # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
+ # this method returns the block that was passed in to <tt>render :layout</tt>, and the response
+ # would be
+ #
+ # <html>
+ # Content
+ # </html>
+ #
+ # Finally, the block can take block arguments, which can be passed in by +yield+:
+ #
+ # # The template
+ # <%= render :layout => "my_layout" do |customer| %>
+ # Hello <%= customer.name %>
+ # <% end %>
+ #
+ # # The layout
+ # <html>
+ # <%= yield Struct.new(:name).new("David") %>
+ # </html>
+ #
+ # In this case, the layout would receive the block passed into <tt>render :layout</tt>,
+ # and the struct specified would be passed into the block as an argument. The result
+ # would be
+ #
+ # <html>
+ # Hello David
+ # </html>
+ #
+ def _layout_for(*args, &block)
+ name = args.first
+
+ if block && !name.is_a?(Symbol)
+ capture(*args, &block)
+ else
+ super
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb
index b43b91178c..ab98da9624 100644
--- a/actionpack/lib/action_view/helpers/sprockets_helper.rb
+++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb
@@ -40,10 +40,10 @@ module ActionView
class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc:
def rewrite_asset_path(source, dir)
- if source =~ /^\/#{dir}\/(.+)/
- assets.path($1, performing_caching?, dir)
- else
+ if source[0] == ?/
source
+ else
+ assets.path(source, performing_caching?, dir)
end
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 06e2b027da..ca09c77b5c 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -265,60 +265,6 @@ module ActionView
text.html_safe.safe_concat("</p>")
end
- # Turns all URLs and e-mail addresses into clickable links. The <tt>:link</tt> option
- # will limit what should be linked. You can add HTML attributes to the links using
- # <tt>:html</tt>. Possible values for <tt>:link</tt> are <tt>:all</tt> (default),
- # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and
- # e-mail address is yielded and the result is used as the link text.
- #
- # ==== Examples
- # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com")
- # # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and
- # # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
- #
- # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :link => :urls)
- # # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a>
- # # or e-mail david@loudthinking.com"
- #
- # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :link => :email_addresses)
- # # => "Visit http://www.loudthinking.com/ or e-mail <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>"
- #
- # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
- # auto_link(post_body, :html => { :target => '_blank' }) do |text|
- # truncate(text, :length => 15)
- # end
- # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>.
- # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
- #
- #
- # You can still use <tt>auto_link</tt> with the old API that accepts the
- # +link+ as its optional second parameter and the +html_options+ hash
- # as its optional third parameter:
- # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
- # auto_link(post_body, :urls)
- # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>.
- # Please e-mail me at me@email.com."
- #
- # auto_link(post_body, :all, :target => "_blank")
- # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
- # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
- def auto_link(text, *args, &block)#link = :all, html = {}, &block)
- return '' if text.blank?
-
- options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
- unless args.empty?
- options[:link] = args[0] || :all
- options[:html] = args[1] || {}
- end
- options.reverse_merge!(:link => :all, :html => {})
-
- case options[:link].to_sym
- when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], options, &block), options[:html], &block)
- when :email_addresses then auto_link_email_addresses(text, options[:html], &block)
- when :urls then auto_link_urls(text, options[:html], options, &block)
- end
- end
-
# Creates a Cycle object whose _to_s_ method cycles through elements of an
# array every time it is called. This can be used for example, to alternate
# classes for table rows. You can use named cycles to allow nesting in loops.
@@ -464,77 +410,6 @@ module ActionView
@_cycles = Hash.new unless defined?(@_cycles)
@_cycles[name] = cycle_object
end
-
- AUTO_LINK_RE = %r{
- (?: ([0-9A-Za-z+.:-]+:)// | www\. )
- [^\s<]+
- }x
-
- # regexps for determining context, used high-volume
- AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
-
- AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/
-
- BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
-
- # Turns all urls into clickable links. If a block is given, each url
- # is yielded and the result is used as the link text.
- def auto_link_urls(text, html_options = {}, options = {})
- link_attributes = html_options.stringify_keys
- text.gsub(AUTO_LINK_RE) do
- scheme, href = $1, $&
- punctuation = []
-
- if auto_linked?($`, $')
- # do not change string; URL is already linked
- href
- else
- # don't include trailing punctuation character as part of the URL
- while href.sub!(/[^\w\/-]$/, '')
- punctuation.push $&
- if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
- href << punctuation.pop
- break
- end
- end
-
- link_text = block_given?? yield(href) : href
- href = 'http://' + href unless scheme
-
- unless options[:sanitize] == false
- link_text = sanitize(link_text)
- href = sanitize(href)
- end
- content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
- end
- end
- end
-
- # Turns all email addresses into clickable links. If a block is given,
- # each email is yielded and the result is used as the link text.
- def auto_link_email_addresses(text, html_options = {}, options = {})
- text.gsub(AUTO_EMAIL_RE) do
- text = $&
-
- if auto_linked?($`, $')
- text.html_safe
- else
- display_text = (block_given?) ? yield(text) : text
-
- unless options[:sanitize] == false
- text = sanitize(text)
- display_text = sanitize(display_text) unless text == display_text
- end
- mail_to text, display_text, html_options
- end
- end
- end
-
- # Detects already linked context or position in the middle of a tag
- def auto_linked?(left, right)
- (left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
- (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 26ebae6546..ec9bdd5320 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -63,8 +63,8 @@ module ActionView
private
def scope_key_by_partial(key)
if key.to_s.first == "."
- if (path = @_template && @_template.virtual_path)
- path.gsub(%r{/_?}, ".") + key.to_s
+ if @virtual_path
+ @virtual_path.gsub(%r{/_?}, ".") + key.to_s
else
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
end
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 06c607931d..06975ffa2f 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -9,6 +9,8 @@ module ActionView
# generate a key, given to view paths, used in the resolver cache lookup. Since
# this key is generated just once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
+ attr_accessor :prefixes
+
mattr_accessor :fallbacks
@@fallbacks = FallbackFileSystemResolver.instances
@@ -58,10 +60,11 @@ module ActionView
end
end
- def initialize(view_paths, details = {})
+ def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = { :handlers => default_handlers }, nil
@frozen_formats, @skip_default_locale = false, false
@cache = true
+ @prefixes = prefixes
self.view_paths = view_paths
self.registered_detail_setters.each do |key, setter|
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
deleted file mode 100644
index c181689e62..0000000000
--- a/actionpack/lib/action_view/partials.rb
+++ /dev/null
@@ -1,226 +0,0 @@
-require 'active_support/core_ext/object/blank'
-
-module ActionView
- # = Action View Partials
- #
- # There's also a convenience method for rendering sub templates within the current controller that depends on a
- # single object (we call this kind of sub templates for partials). It relies on the fact that partials should
- # follow the naming convention of being prefixed with an underscore -- as to separate them from regular
- # templates that could be rendered on their own.
- #
- # In a template for Advertiser#account:
- #
- # <%= render :partial => "account" %>
- #
- # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable
- # +account+ to the template for display.
- #
- # In another template for Advertiser#buy, we could have:
- #
- # <%= render :partial => "account", :locals => { :account => @buyer } %>
- #
- # <% @advertisements.each do |ad| %>
- # <%= render :partial => "ad", :locals => { :ad => ad } %>
- # <% end %>
- #
- # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
- # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
- #
- # == The :as and :object options
- #
- # By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same
- # name as the template. So, given
- #
- # <%= render :partial => "contract" %>
- #
- # within contract we'll get <tt>@contract</tt> in the local variable +contract+, as if we had written
- #
- # <%= render :partial => "contract", :locals => { :contract => @contract } %>
- #
- # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
- # wanted it to be +agreement+ instead of +contract+ we'd do:
- #
- # <%= render :partial => "contract", :as => 'agreement' %>
- #
- # The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;
- # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
- #
- # Revisiting a previous example we could have written this code:
- #
- # <%= render :partial => "account", :object => @buyer %>
- #
- # <% @advertisements.each do |ad| %>
- # <%= render :partial => "ad", :object => ad %>
- # <% end %>
- #
- # The <tt>:object</tt> and <tt>:as</tt> options can be used together.
- #
- # == Rendering a collection of partials
- #
- # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
- # render a sub template for each of the elements. This pattern has been implemented as a single method that
- # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
- # example in "Using partials" can be rewritten with a single line:
- #
- # <%= render :partial => "ad", :collection => @advertisements %>
- #
- # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
- # iteration counter will automatically be made available to the template with a name of the form
- # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
- #
- # The <tt>:as</tt> option may be used when rendering partials.
- #
- # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
- # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
- #
- # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %>
- #
- # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
- # to specify a text which will displayed instead by using this form:
- #
- # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %>
- #
- # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
- # just keep domain objects, like Active Records, in there.
- #
- # == Rendering shared partials
- #
- # Two controllers can share a set of partials and render them like this:
- #
- # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
- #
- # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
- #
- # == Rendering objects with the RecordIdentifier
- #
- # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if
- # you're following its conventions for RecordIdentifier#partial_path. Examples:
- #
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
- # <%= render :partial => @account %>
- #
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render :partial => @posts %>
- #
- # == Rendering the default case
- #
- # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
- # defaults of render to render partials. Examples:
- #
- # # Instead of <%= render :partial => "account" %>
- # <%= render "account" %>
- #
- # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
- # <%= render "account", :account => @buyer %>
- #
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
- # <%= render(@account) %>
- #
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render(@posts) %>
- #
- # == Rendering partials with layouts
- #
- # Partials can have their own layouts applied to them. These layouts are different than the ones that are
- # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
- # of users:
- #
- # <%# app/views/users/index.html.erb &>
- # Here's the administrator:
- # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
- #
- # Here's the editor:
- # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
- #
- # <%# app/views/users/_user.html.erb &>
- # Name: <%= user.name %>
- #
- # <%# app/views/users/_administrator.html.erb &>
- # <div id="administrator">
- # Budget: $<%= user.budget %>
- # <%= yield %>
- # </div>
- #
- # <%# app/views/users/_editor.html.erb &>
- # <div id="editor">
- # Deadline: <%= user.deadline %>
- # <%= yield %>
- # </div>
- #
- # ...this will return:
- #
- # Here's the administrator:
- # <div id="administrator">
- # Budget: $<%= user.budget %>
- # Name: <%= user.name %>
- # </div>
- #
- # Here's the editor:
- # <div id="editor">
- # Deadline: <%= user.deadline %>
- # Name: <%= user.name %>
- # </div>
- #
- # You can also apply a layout to a block within any template:
- #
- # <%# app/views/users/_chief.html.erb &>
- # <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
- # Title: <%= chief.title %>
- # <% end %>
- #
- # ...this will return:
- #
- # <div id="administrator">
- # Budget: $<%= user.budget %>
- # Title: <%= chief.name %>
- # </div>
- #
- # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
- #
- # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
- # an array to layout and treat it as an enumerable.
- #
- # <%# app/views/users/_user.html.erb &>
- # <div class="user">
- # Budget: $<%= user.budget %>
- # <%= yield user %>
- # </div>
- #
- # <%# app/views/users/index.html.erb &>
- # <%= render :layout => @users do |user| %>
- # Title: <%= user.title %>
- # <% end %>
- #
- # This will render the layout for each user and yield to the block, passing the user, each time.
- #
- # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
- #
- # <%# app/views/users/_user.html.erb &>
- # <div class="user">
- # <%= yield user, :header %>
- # Budget: $<%= user.budget %>
- # <%= yield user, :footer %>
- # </div>
- #
- # <%# app/views/users/index.html.erb &>
- # <%= render :layout => @users do |user, section| %>
- # <%- case section when :header -%>
- # Title: <%= user.title %>
- # <%- when :footer -%>
- # Deadline: <%= user.deadline %>
- # <%- end -%>
- # <% end %>
- module Partials
- def _render_partial(options, &block) #:nodoc:
- _partial_renderer.setup(options, block).render
- end
-
- def _partial_renderer #:nodoc:
- @_partial_renderer ||= PartialRenderer.new(self)
- end
- end
-end
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index f20ba7e6d3..80391d72cc 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -6,7 +6,7 @@ module ActionView
class Railtie < Rails::Railtie
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
- config.action_view.javascript_expansions = { :defaults => %w(jquery rails) }
+ config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index 4a52b3172e..60c527beeb 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -3,9 +3,8 @@ module ActionView
delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
:with_layout_format, :formats, :freeze_formats, :to => :@lookup_context
- def initialize(view)
- @view = view
- @lookup_context = view.lookup_context
+ def initialize(lookup_context)
+ @lookup_context = lookup_context
end
def render
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 10cd37d56f..a351fbc04f 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -1,44 +1,230 @@
+require 'active_support/core_ext/object/blank'
+
module ActionView
+ # = Action View Partials
+ #
+ # There's also a convenience method for rendering sub templates within the current controller that depends on a
+ # single object (we call this kind of sub templates for partials). It relies on the fact that partials should
+ # follow the naming convention of being prefixed with an underscore -- as to separate them from regular
+ # templates that could be rendered on their own.
+ #
+ # In a template for Advertiser#account:
+ #
+ # <%= render :partial => "account" %>
+ #
+ # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable
+ # +account+ to the template for display.
+ #
+ # In another template for Advertiser#buy, we could have:
+ #
+ # <%= render :partial => "account", :locals => { :account => @buyer } %>
+ #
+ # <% @advertisements.each do |ad| %>
+ # <%= render :partial => "ad", :locals => { :ad => ad } %>
+ # <% end %>
+ #
+ # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then
+ # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.
+ #
+ # == The :as and :object options
+ #
+ # By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same
+ # name as the template. So, given
+ #
+ # <%= render :partial => "contract" %>
+ #
+ # within contract we'll get <tt>@contract</tt> in the local variable +contract+, as if we had written
+ #
+ # <%= render :partial => "contract", :locals => { :contract => @contract } %>
+ #
+ # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
+ # wanted it to be +agreement+ instead of +contract+ we'd do:
+ #
+ # <%= render :partial => "contract", :as => 'agreement' %>
+ #
+ # The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;
+ # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
+ #
+ # Revisiting a previous example we could have written this code:
+ #
+ # <%= render :partial => "account", :object => @buyer %>
+ #
+ # <% @advertisements.each do |ad| %>
+ # <%= render :partial => "ad", :object => ad %>
+ # <% end %>
+ #
+ # The <tt>:object</tt> and <tt>:as</tt> options can be used together.
+ #
+ # == Rendering a collection of partials
+ #
+ # The example of partial use describes a familiar pattern where a template needs to iterate over an array and
+ # render a sub template for each of the elements. This pattern has been implemented as a single method that
+ # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
+ # example in "Using partials" can be rewritten with a single line:
+ #
+ # <%= render :partial => "ad", :collection => @advertisements %>
+ #
+ # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
+ # iteration counter will automatically be made available to the template with a name of the form
+ # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
+ #
+ # The <tt>:as</tt> option may be used when rendering partials.
+ #
+ # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
+ # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
+ #
+ # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %>
+ #
+ # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
+ # to specify a text which will displayed instead by using this form:
+ #
+ # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %>
+ #
+ # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
+ # just keep domain objects, like Active Records, in there.
+ #
+ # == Rendering shared partials
+ #
+ # Two controllers can share a set of partials and render them like this:
+ #
+ # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
+ #
+ # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
+ #
+ # == Rendering objects with the RecordIdentifier
+ #
+ # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if
+ # you're following its conventions for RecordIdentifier#partial_path. Examples:
+ #
+ # # @account is an Account instance, so it uses the RecordIdentifier to replace
+ # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
+ # <%= render :partial => @account %>
+ #
+ # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # <%= render :partial => "posts/post", :collection => @posts %>
+ # <%= render :partial => @posts %>
+ #
+ # == Rendering the default case
+ #
+ # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
+ # defaults of render to render partials. Examples:
+ #
+ # # Instead of <%= render :partial => "account" %>
+ # <%= render "account" %>
+ #
+ # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
+ # <%= render "account", :account => @buyer %>
+ #
+ # # @account is an Account instance, so it uses the RecordIdentifier to replace
+ # # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
+ # <%= render(@account) %>
+ #
+ # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # <%= render :partial => "posts/post", :collection => @posts %>
+ # <%= render(@posts) %>
+ #
+ # == Rendering partials with layouts
+ #
+ # Partials can have their own layouts applied to them. These layouts are different than the ones that are
+ # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
+ # of users:
+ #
+ # <%# app/views/users/index.html.erb &>
+ # Here's the administrator:
+ # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %>
+ #
+ # Here's the editor:
+ # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %>
+ #
+ # <%# app/views/users/_user.html.erb &>
+ # Name: <%= user.name %>
+ #
+ # <%# app/views/users/_administrator.html.erb &>
+ # <div id="administrator">
+ # Budget: $<%= user.budget %>
+ # <%= yield %>
+ # </div>
+ #
+ # <%# app/views/users/_editor.html.erb &>
+ # <div id="editor">
+ # Deadline: <%= user.deadline %>
+ # <%= yield %>
+ # </div>
+ #
+ # ...this will return:
+ #
+ # Here's the administrator:
+ # <div id="administrator">
+ # Budget: $<%= user.budget %>
+ # Name: <%= user.name %>
+ # </div>
+ #
+ # Here's the editor:
+ # <div id="editor">
+ # Deadline: <%= user.deadline %>
+ # Name: <%= user.name %>
+ # </div>
+ #
+ # You can also apply a layout to a block within any template:
+ #
+ # <%# app/views/users/_chief.html.erb &>
+ # <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
+ # Title: <%= chief.title %>
+ # <% end %>
+ #
+ # ...this will return:
+ #
+ # <div id="administrator">
+ # Budget: $<%= user.budget %>
+ # Title: <%= chief.name %>
+ # </div>
+ #
+ # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
+ #
+ # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
+ # an array to layout and treat it as an enumerable.
+ #
+ # <%# app/views/users/_user.html.erb &>
+ # <div class="user">
+ # Budget: $<%= user.budget %>
+ # <%= yield user %>
+ # </div>
+ #
+ # <%# app/views/users/index.html.erb &>
+ # <%= render :layout => @users do |user| %>
+ # Title: <%= user.title %>
+ # <% end %>
+ #
+ # This will render the layout for each user and yield to the block, passing the user, each time.
+ #
+ # You can also yield multiple times in one layout and use block arguments to differentiate the sections.
+ #
+ # <%# app/views/users/_user.html.erb &>
+ # <div class="user">
+ # <%= yield user, :header %>
+ # Budget: $<%= user.budget %>
+ # <%= yield user, :footer %>
+ # </div>
+ #
+ # <%# app/views/users/index.html.erb &>
+ # <%= render :layout => @users do |user, section| %>
+ # <%- case section when :header -%>
+ # Title: <%= user.title %>
+ # <%- when :footer -%>
+ # Deadline: <%= user.deadline %>
+ # <%- end -%>
+ # <% end %>
class PartialRenderer < AbstractRenderer #:nodoc:
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
- def initialize(view)
+ def initialize(*)
super
- @partial_names = PARTIAL_NAMES[@view.controller.class.name]
+ @partial_names = PARTIAL_NAMES[@lookup_context.prefixes.first]
end
- def setup(options, block)
- partial = options[:partial]
-
- @options = options
- @locals = options[:locals] || {}
- @block = block
-
- if String === partial
- @object = options[:object]
- @path = partial
- @collection = collection
- else
- @object = partial
-
- if @collection = collection_from_object || collection
- paths = @collection_data = @collection.map { |o| partial_path(o) }
- @path = paths.uniq.size == 1 ? paths.first : nil
- else
- @path = partial_path
- end
- end
-
- if @path
- @variable, @variable_counter = retrieve_variable(@path)
- else
- paths.map! { |path| retrieve_variable(path).unshift(path) }
- end
-
- self
- end
+ def render(context, options, block)
+ setup(context, options, block)
- def render
wrap_formats(@path) do
identifier = ((@template = find_partial) ? @template.identifier : @path)
@@ -77,7 +263,7 @@ module ActionView
locals[as] = object
content = @template.render(view, locals) do |*name|
- view._block_layout_for(*name, &block)
+ view._layout_for(*name, &block)
end
content = layout.render(view, locals){ content } if layout
@@ -86,6 +272,38 @@ module ActionView
private
+ def setup(context, options, block)
+ @view = context
+ partial = options[:partial]
+
+ @options = options
+ @locals = options[:locals] || {}
+ @block = block
+
+ if String === partial
+ @object = options[:object]
+ @path = partial
+ @collection = collection
+ else
+ @object = partial
+
+ if @collection = collection_from_object || collection
+ paths = @collection_data = @collection.map { |o| partial_path(o) }
+ @path = paths.uniq.size == 1 ? paths.first : nil
+ else
+ @path = partial_path
+ end
+ end
+
+ if @path
+ @variable, @variable_counter = retrieve_variable(@path)
+ else
+ paths.map! { |path| retrieve_variable(path).unshift(path) }
+ end
+
+ self
+ end
+
def collection
if @options.key?(:collection)
collection = @options[:collection]
@@ -109,7 +327,7 @@ module ActionView
end
def find_template(path=@path, locals=@locals.keys)
- prefixes = path.include?(?/) ? [] : @view.controller_prefixes
+ prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals)
end
@@ -150,7 +368,7 @@ module ActionView
object = object.to_model if object.respond_to?(:to_model)
object.class.model_name.partial_path.dup.tap do |partial|
- path = @view.controller_prefixes.first
+ path = @lookup_context.prefixes.first
partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
end
end
diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb
new file mode 100644
index 0000000000..bf1b5a7d22
--- /dev/null
+++ b/actionpack/lib/action_view/renderer/renderer.rb
@@ -0,0 +1,54 @@
+module ActionView
+ # This is the main entry point for rendering. It basically delegates
+ # to other objects like TemplateRenderer and PartialRenderer which
+ # actually renders the template.
+ class Renderer
+ attr_accessor :lookup_context
+
+ def initialize(lookup_context)
+ @lookup_context = lookup_context
+ end
+
+ # Main render entry point shared by AV and AC.
+ def render(context, options)
+ if options.key?(:partial)
+ render_partial(context, options)
+ else
+ render_template(context, options)
+ end
+ end
+
+ # Render but returns a valid Rack body. If fibers are defined, we return
+ # a streaming body that renders the template piece by piece.
+ #
+ # Note that partials are not supported to be rendered with streaming,
+ # so in such cases, we just wrap them in an array.
+ def render_body(context, options)
+ if options.key?(:partial)
+ [render_partial(context, options)]
+ else
+ StreamingTemplateRenderer.new(@lookup_context).render(context, options)
+ end
+ end
+
+ # Direct accessor to template rendering.
+ def render_template(context, options) #:nodoc:
+ _template_renderer.render(context, options)
+ end
+
+ # Direct access to partial rendering.
+ def render_partial(context, options, &block) #:nodoc:
+ _partial_renderer.render(context, options, block)
+ end
+
+ private
+
+ def _template_renderer #:nodoc:
+ @_template_renderer ||= TemplateRenderer.new(@lookup_context)
+ end
+
+ def _partial_renderer #:nodoc:
+ @_partial_renderer ||= PartialRenderer.new(@lookup_context)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
index 03aab444f8..1ccf5a8ddb 100644
--- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
@@ -4,50 +4,11 @@
require 'fiber' if defined?(Fiber)
module ActionView
- # Consider the following layout:
- #
- # <%= yield :header %>
- # 2
- # <%= yield %>
- # 5
- # <%= yield :footer %>
- #
- # And template:
- #
- # <%= provide :header, "1" %>
- # 3
- # 4
- # <%= provide :footer, "6" %>
- #
- # It will stream:
- #
- # "1\n", "2\n", "3\n4\n", "5\n", "6\n"
- #
- # Notice that once you <%= yield %>, it will render the whole template
- # before streaming again. In the future, we can also support streaming
- # from the template and not only the layout.
- #
- # Also, notice we use +provide+ instead of +content_for+, as +provide+
- # gives the control back to the layout as soon as it is called.
- # With +content_for+, it would render all the template to find all
- # +content_for+ calls. For instance, consider this layout:
- #
- # <%= yield :header %>
- #
- # With this template:
- #
- # <%= content_for :header, "1" %>
- # <%= provide :header, "2" %>
- # <%= provide :header, "3" %>
- #
- # It will return "12\n" because +content_for+ continues rendering the
- # template but it is returns back to the layout as soon as it sees the
- # first +provide+.
- #
# == TODO
#
# * Support streaming from child templates, partials and so on.
# * Integrate exceptions with exceptron
+ # * Rack::Cache needs to support streaming bodies
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
# A valid Rack::Body (i.e. it responds to each).
# It is initialized with a block that, when called, starts
@@ -60,11 +21,26 @@ module ActionView
def each(&block)
begin
@start.call(block)
- rescue
+ rescue Exception => exception
+ log_error(exception)
block.call ActionView::Base.streaming_completion_on_exception
end
self
end
+
+ private
+
+ # This is the same logging logic as in ShowExceptions middleware.
+ # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
+ def log_error(exception) #:nodoc:
+ logger = ActionController::Base.logger
+ return unless logger
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
end
# For streaming, instead of rendering a given a template, we return a Body
@@ -106,7 +82,7 @@ module ActionView
# Set the view flow to support streaming. It will be aware
# when to stop rendering the layout because it needs to search
# something in the template and vice-versa.
- view._view_flow = StreamingFlow.new(view, fiber)
+ view.view_flow = StreamingFlow.new(view, fiber)
# Yo! Start the fiber!
fiber.resume
@@ -118,7 +94,7 @@ module ActionView
content = template.render(view, locals, &yielder)
# Once rendering the template is done, sets its content in the :layout key.
- view._view_flow.set(:layout, content)
+ view.view_flow.set(:layout, content)
# In case the layout continues yielding, we need to resume
# the fiber until all yields are handled.
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index 6b5ead463f..a09cef8fef 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -3,7 +3,9 @@ require 'active_support/core_ext/array/wrap'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
- def render(options)
+ def render(context, options)
+ @view = context
+
wrap_formats(options[:template] || options[:file]) do
template = determine_template(options)
freeze_formats(template.formats, true)
@@ -18,7 +20,7 @@ module ActionView
if options.key?(:text)
Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
- with_fallbacks { find_template(options[:file], options[:prefixes], false, keys) }
+ with_fallbacks { find_template(options[:file], nil, false, keys) }
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, :locals => keys)
@@ -46,7 +48,7 @@ module ActionView
if layout
view = @view
- view._view_flow.set(:layout, content)
+ view.view_flow.set(:layout, content)
layout.render(view, locals){ |*name| view._layout_for(*name) }
else
content
diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb
deleted file mode 100644
index 2bce2fb045..0000000000
--- a/actionpack/lib/action_view/rendering.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-require 'active_support/core_ext/object/try'
-
-module ActionView
- # = Action View Rendering
- module Rendering
- # Returns the result of a render that's dictated by the options hash. The primary options are:
- #
- # * <tt>:partial</tt> - See ActionView::Partials.
- # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
- # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
- # * <tt>:text</tt> - Renders the text passed in out.
- #
- # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
- # as the locals hash.
- def render(options = {}, locals = {}, &block)
- case options
- when Hash
- if block_given?
- _render_partial(options.merge(:partial => options[:layout]), &block)
- elsif options.key?(:partial)
- _render_partial(options)
- else
- _render_template(options)
- end
- else
- _render_partial(:partial => options, :locals => locals)
- end
- end
-
- # Render but returns a valid Rack body. If fibers are defined, we return
- # a streaming body that renders the template piece by piece.
- #
- # Note that partials are not supported to be rendered with streaming,
- # so in such cases, we just wrap them in an array.
- def render_body(options)
- if options.key?(:partial)
- [_render_partial(options)]
- else
- StreamingTemplateRenderer.new(self).render(options)
- end
- end
-
- # Returns the contents that are yielded to a layout, given a name or a block.
- #
- # You can think of a layout as a method that is called with a block. If the user calls
- # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
- # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
- #
- # The user can override this default by passing a block to the layout:
- #
- # # The template
- # <%= render :layout => "my_layout" do %>
- # Content
- # <% end %>
- #
- # # The layout
- # <html>
- # <%= yield %>
- # </html>
- #
- # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
- # this method returns the block that was passed in to <tt>render :layout</tt>, and the response
- # would be
- #
- # <html>
- # Content
- # </html>
- #
- # Finally, the block can take block arguments, which can be passed in by +yield+:
- #
- # # The template
- # <%= render :layout => "my_layout" do |customer| %>
- # Hello <%= customer.name %>
- # <% end %>
- #
- # # The layout
- # <html>
- # <%= yield Struct.new(:name).new("David") %>
- # </html>
- #
- # In this case, the layout would receive the block passed into <tt>render :layout</tt>,
- # and the struct specified would be passed into the block as an argument. The result
- # would be
- #
- # <html>
- # Hello David
- # </html>
- #
- def _layout_for(*args)
- name = args.first
- name = :layout unless name.is_a?(Symbol)
- @_view_flow.get(name).html_safe
- end
-
- # Handle layout for calls from partials that supports blocks.
- def _block_layout_for(*args, &block)
- name = args.first
-
- if !name.is_a?(Symbol) && block
- capture(*args, &block)
- else
- _layout_for(*args)
- end
- end
-
- def _render_template(options) #:nodoc:
- _template_renderer.render(options)
- end
-
- def _template_renderer #:nodoc:
- @_template_renderer ||= TemplateRenderer.new(self)
- end
- end
-end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 6dfc4f68ae..98ecd15aa0 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -139,15 +139,12 @@ module ActionView
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
- old_template, view._template = view._template, self
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
rescue Exception => e
handle_render_error(view, e)
- ensure
- view._template = old_template
end
def mime_type
@@ -174,12 +171,7 @@ module ActionView
end
def inspect
- @inspect ||=
- if defined?(Rails.root)
- identifier.sub("#{Rails.root}/", '')
- else
- identifier
- end
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
end
protected
@@ -264,9 +256,9 @@ module ActionView
# encoding of the code
source = <<-end_src
def #{method_name}(local_assigns, output_buffer)
- _old_output_buffer = @output_buffer;#{locals_code};#{code}
+ _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
- @output_buffer = _old_output_buffer
+ @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
end
end_src
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 5c74bf843a..d0317a148b 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -1,4 +1,6 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/remove_method'
require 'action_controller'
require 'action_controller/test_case'
require 'action_view'
@@ -43,6 +45,7 @@ module ActionView
include AbstractController::Helpers
include ActionView::Helpers
+ delegate :lookup_context, :to => :controller
attr_accessor :controller, :output_buffer, :rendered
module ClassMethods
@@ -127,7 +130,7 @@ module ActionView
def say_no_to_protect_against_forgery!
_helpers.module_eval do
- remove_method :protect_against_forgery? if method_defined?(:protect_against_forgery?)
+ remove_possible_method :protect_against_forgery?
def protect_against_forgery?
false
end
@@ -147,9 +150,19 @@ module ActionView
module Locals
attr_accessor :locals
- def _render_partial(options)
- locals[options[:partial]] = options[:locals]
- super(options)
+ def render(options = {}, local_assigns = {})
+ case options
+ when Hash
+ if block_given?
+ locals[options[:layout]] = options[:locals]
+ elsif options.key?(:partial)
+ locals[options[:partial]] = options[:locals]
+ end
+ else
+ locals[options] = local_assigns
+ end
+
+ super
end
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index ddfa3df552..5d7a51e902 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -4,6 +4,8 @@ require "action_controller/log_subscriber"
module Another
class LogSubscribersController < ActionController::Base
+ wrap_parameters :person, :only => :name, :format => :json
+
def show
render :nothing => true
end
@@ -95,6 +97,15 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_equal 'Parameters: {"id"=>"10"}', logs[1]
end
+ def test_process_action_with_wrapped_parameters
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :show, :id => '10', :name => 'jose'
+ wait
+
+ assert_equal 3, logs.size
+ assert_match '"person"=>{"name"=>"jose"}', logs[1]
+ end
+
def test_process_action_with_view_runtime
get :show
wait
diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb
new file mode 100644
index 0000000000..f41b14d5d6
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_context_test.rb
@@ -0,0 +1,54 @@
+require 'abstract_unit'
+
+# This is testing the decoupling of view renderer and view context
+# by allowing the controller to be used as view context. This is
+# similar to the way sinatra renders templates.
+module RenderContext
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>",
+ "layouts/basic.html.erb" => "?<%= yield %>?"
+ )]
+
+ # 1) Include ActionView::Context to bring the required dependencies
+ include ActionView::Context
+
+ # 2) Call _prepare_context that will do the required initialization
+ before_filter :_prepare_context
+
+ def hello_world
+ @value = "Hello"
+ render :action => "hello_world", :layout => false
+ end
+
+ def with_layout
+ @value = "Hello"
+ render :action => "hello_world", :layout => "basic"
+ end
+
+ protected
+
+ # 3) Set view_context to self
+ def view_context
+ self
+ end
+
+ def __controller_method__
+ "controller context!"
+ end
+ end
+
+ class RenderContextTest < Rack::TestCase
+ test "rendering using the controller as context" do
+ get "/render_context/basic/hello_world"
+ assert_body "Hello from controller context!"
+ assert_status 200
+ end
+
+ test "rendering using the controller as context with layout" do
+ get "/render_context/basic/with_layout"
+ assert_body "?Hello from controller context!?"
+ assert_status 200
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 3bb3016fdb..1e2191d417 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -33,10 +33,10 @@ module RenderImplicitAction
assert_status 200
end
- test "action_method? returns true for implicit actions" do
- assert SimpleController.new.action_method?(:hello_world)
- assert SimpleController.new.action_method?(:"hyphen-ated")
- assert SimpleController.new.action_method?(:not_implemented)
+ test "available_action? returns true for implicit actions" do
+ assert SimpleController.new.available_action?(:hello_world)
+ assert SimpleController.new.available_action?(:"hyphen-ated")
+ assert SimpleController.new.available_action?(:not_implemented)
end
end
end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index fed8d40b47..48cf0ab9cb 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -83,6 +83,19 @@ module RenderStreaming
assert_streaming!
end
+ test "rendering with template exception logs the exception" do
+ io = StringIO.new
+ _old, ActionController::Base.logger = ActionController::Base.logger, Logger.new(io)
+
+ begin
+ get "/render_streaming/basic/template_exception"
+ io.rewind
+ assert_match "(undefined method `invalid!' for nil:NilClass)", io.read
+ ensure
+ ActionController::Base.logger = _old
+ end
+ end
+
test "do not stream on HTTP/1.0" do
get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0"
assert_body "Hello world, I'm here!"
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
new file mode 100644
index 0000000000..548cd02dc0
--- /dev/null
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -0,0 +1,246 @@
+require 'abstract_unit'
+
+module Admin; class User; end; end
+
+class ParamsWrapperTest < ActionController::TestCase
+ class UsersController < ActionController::Base
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end
+
+ class User; end
+ class Person; end
+
+ tests UsersController
+
+ def teardown
+ UsersController.last_parameters = nil
+ end
+
+ def test_derived_name_from_controller
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_wrapper_name
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_wrapper_model
+ with_default_wrapper_options do
+ UsersController.wrap_parameters Person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_only_option
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :only => :username
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_except_option
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :except => :title
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_both_wrapper_name_and_only_option
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :person, :only => :username
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_not_enabled_format
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/xml'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
+ end
+ end
+
+ def test_wrap_parameters_false
+ with_default_wrapper_options do
+ UsersController.wrap_parameters false
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
+ end
+ end
+
+ def test_specify_format
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :format => :xml
+
+ @request.env['CONTENT_TYPE'] = 'application/xml'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
+ end
+ end
+
+ def test_not_wrap_reserved_parameters
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu' }
+ assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_no_double_wrap_if_key_exists
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'user' => { 'username' => 'sikachu' }}
+ assert_parameters({ 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_nested_params
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'person' => { 'username' => 'sikachu' }}
+ assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}})
+ end
+ end
+
+ def test_derived_wrapped_keys_from_matching_model
+ User.expects(:respond_to?).with(:column_names).returns(true)
+ User.expects(:column_names).returns(["username"])
+
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_derived_wrapped_keys_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:column_names).returns(true)
+ Person.expects(:column_names).returns(["username"])
+
+ UsersController.wrap_parameters Person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ private
+ def with_default_wrapper_options(&block)
+ @controller.class._wrapper_options = {:format => [:json]}
+ @controller.class.inherited(@controller.class)
+ yield
+ end
+
+ def assert_parameters(expected)
+ assert_equal expected, UsersController.last_parameters
+ end
+end
+
+class NamespacedParamsWrapperTest < ActionController::TestCase
+ module Admin
+ class UsersController < ActionController::Base
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end
+ end
+
+ class Sample
+ def self.column_names
+ ["username"]
+ end
+ end
+
+ tests Admin::UsersController
+
+ def teardown
+ Admin::UsersController.last_parameters = nil
+ end
+
+ def test_derived_name_from_controller
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_namespace_lookup_from_model
+ Admin.const_set(:User, Class.new(Sample))
+ begin
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ ensure
+ Admin.send :remove_const, :User
+ end
+ end
+
+ def test_hierarchy_namespace_lookup_from_model
+ # Make sure that we cleanup ::Admin::User
+ admin_user_constant = ::Admin::User
+ ::Admin.send :remove_const, :User
+
+ Object.const_set(:User, Class.new(Sample))
+ begin
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ ensure
+ Object.send :remove_const, :User
+ ::Admin.const_set(:User, admin_user_constant)
+ end
+ end
+
+ private
+ def with_default_wrapper_options(&block)
+ @controller.class._wrapper_options = {:format => [:json]}
+ @controller.class.inherited(@controller.class)
+ yield
+ end
+
+ def assert_parameters(expected)
+ assert_equal expected, Admin::UsersController.last_parameters
+ end
+end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 18cf944f46..aa9d193436 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -92,6 +92,12 @@ class LegacyRouteSetTests < Test::Unit::TestCase
@rs.clear!
end
+ def test_draw_with_block_arity_one_raises
+ assert_raise(RuntimeError) do
+ @rs.draw { |map| map.match '/:controller(/:action(/:id))' }
+ end
+ end
+
def test_default_setup
@rs.draw { match '/:controller(/:action(/:id))' }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index 42356be1ea..3de1849db8 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -179,4 +179,8 @@ class ViewLoadPathsTest < ActionController::TestCase
assert_nothing_raised { C.append_view_path 'c/path' }
assert_paths C, "c/path"
end
+
+ def test_lookup_context_accessor
+ assert_equal ["test"], TestController.new.lookup_context.prefixes
+ end
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index 34db7a4c66..d854d55173 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -63,3 +63,56 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
end
end
end
+
+class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
+ class UsersController < ActionController::Base
+ wrap_parameters :format => :json
+
+ class << self
+ attr_accessor :last_request_parameters, :last_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ self.class.last_parameters = params
+ head :ok
+ end
+ end
+
+ def teardown
+ UsersController.last_request_parameters = nil
+ end
+
+ test "parses json params for application json" do
+ assert_parses(
+ {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ test "parses json params for application jsonrequest" do
+ assert_parses(
+ {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/jsonrequest' }
+ )
+ end
+
+ private
+ def assert_parses(expected, actual, headers = {})
+ with_test_routing(UsersController) do
+ post "/parse", actual, headers
+ assert_response :ok
+ assert_equal(expected, UsersController.last_request_parameters)
+ assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters)
+ end
+ end
+
+ def with_test_routing(controller)
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => controller
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 3ff558ec5a..560ea00923 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -82,21 +82,15 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
assert_equal 19512, file.size
end
- # Pending fix in Rack 1.2.2
- # http://rack.lighthouseapp.com/projects/22435-rack/tickets/79-multipart-handling-incorrectly-assuming-file-upload
test "parses mixed files" do
- if Rack.release <= '1.2.1'
- $stderr.puts 'multipart/mixed parsing pending fix in Rack 1.2.2'
- else
- params = parse_multipart('mixed_files')
- assert_equal %w(files foo), params.keys.sort
- assert_equal 'bar', params['foo']
-
- # Rack doesn't handle multipart/mixed for us.
- files = params['files']
- files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding)
- assert_equal 19756, files.size
- end
+ params = parse_multipart('mixed_files')
+ assert_equal %w(files foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ # Rack doesn't handle multipart/mixed for us.
+ files = params['files']
+ files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding)
+ assert_equal 19756, files.size
end
test "does not create tempfile if no file has been selected" do
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index ad9de02eb4..38453dfe48 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -115,3 +115,41 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest
{'HTTP_X_POST_DATA_FORMAT' => 'xml'}
end
end
+
+class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ wrap_parameters :person, :format => :xml
+
+ class << self
+ attr_accessor :last_request_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ head :ok
+ end
+ end
+
+ def teardown
+ TestController.last_request_parameters = nil
+ end
+
+ test "parses hash params" do
+ with_test_routing do
+ xml = "<name>David</name>"
+ post "/parse", xml, {'CONTENT_TYPE' => 'application/xml'}
+ assert_response :ok
+ assert_equal({"name" => "David", "person" => {"name" => "David"}}, TestController.last_request_parameters)
+ end
+ end
+
+ private
+ def with_test_routing
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => ::RootLessXmlParamsParsingTest::TestController
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f03ae7f2b3..d128006404 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -137,30 +137,39 @@ class RequestTest < ActiveSupport::TestCase
test "subdomains" do
request = stub_request 'HTTP_HOST' => "www.rubyonrails.org"
assert_equal %w( www ), request.subdomains
+ assert_equal "www", request.subdomain
request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk"
assert_equal %w( www ), request.subdomains(2)
+ assert_equal "www", request.subdomain(2)
request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk"
assert_equal %w( dev www ), request.subdomains(2)
+ assert_equal "dev.www", request.subdomain(2)
request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2
assert_equal %w( dev www ), request.subdomains
+ assert_equal "dev.www", request.subdomain
request = stub_request 'HTTP_HOST' => "foobar.foobar.com"
assert_equal %w( foobar ), request.subdomains
+ assert_equal "foobar", request.subdomain
request = stub_request 'HTTP_HOST' => "192.168.1.200"
assert_equal [], request.subdomains
+ assert_equal "", request.subdomain
request = stub_request 'HTTP_HOST' => "foo.192.168.1.200"
assert_equal [], request.subdomains
+ assert_equal "", request.subdomain
request = stub_request 'HTTP_HOST' => "192.168.1.200.com"
assert_equal %w( 192 168 1 ), request.subdomains
+ assert_equal "192.168.1", request.subdomain
request = stub_request 'HTTP_HOST' => nil
assert_equal [], request.subdomains
+ assert_equal "", request.subdomain
end
test "standard_port" do
@@ -452,6 +461,40 @@ class RequestTest < ActiveSupport::TestCase
assert request.formats.empty?
end
+ test "ignore_accept_header" do
+ ActionDispatch::Request.ignore_accept_header = true
+
+ begin
+ request = stub_request 'HTTP_ACCEPT' => 'application/xml'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'application/jxw'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'application/xml',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::JS ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'application/xml',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({:format => :json})
+ assert_equal [ Mime::JSON ], request.formats
+ ensure
+ ActionDispatch::Request.ignore_accept_header = false
+ end
+ end
+
test "negotiate_mime" do
request = stub_request 'HTTP_ACCEPT' => 'text/html',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 27f55fd7ab..b0efbcef4a 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -50,6 +50,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
get_session_id
end
+ def renew_session_id
+ request.session_options[:renew] = true
+ head :ok
+ end
+
def rescue_action(e) raise end
end
@@ -102,6 +107,17 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
end
+ def test_properly_renew_cookies
+ with_test_route_set do
+ get '/set_session_value'
+ get '/persistent_session_id'
+ session_id = response.body
+ get '/renew_session_id'
+ get '/persistent_session_id'
+ assert_not_equal response.body, session_id
+ end
+ end
+
def test_does_set_secure_cookies_over_https
with_test_route_set(:secure => true) do
get '/set_session_value', nil, 'HTTPS' => 'on'
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index e453dd11ce..42f6c7f79f 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -131,10 +131,17 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
'action_dispatch.request.parameters' => {
'action' => 'show',
'id' => 'unknown',
- 'controller' => 'featured_tiles'
+ 'controller' => 'featured_tile'
}
})
assert_response 500
- assert_match(/RuntimeError\n in FeaturedTilesController/, body)
+ assert_match(/RuntimeError\n in FeaturedTileController/, body)
+ end
+
+ test "sets the HTTP charset parameter" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 2ebbed4414..9f3cbd19ef 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -5,6 +5,12 @@ module StaticTests
assert_equal "Hello, World!", get("/nofile").body
end
+ def test_sets_cache_control
+ response = get("/index.html")
+ assert_html "/index.html", response
+ assert_equal "public, max-age=60", response.headers["Cache-Control"]
+ end
+
def test_serves_static_index_at_root
assert_html "/index.html", get("/index.html")
assert_html "/index.html", get("/index")
@@ -40,7 +46,7 @@ class StaticTest < ActiveSupport::TestCase
DummyApp = lambda { |env|
[200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
}
- App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public")
+ App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60")
def setup
@app = App
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index a9a36e6e6b..592c7da060 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase
def setup
super
@av = ActionView::Base.new
- @_view_flow = ActionView::OutputFlow.new
+ @view_flow = ActionView::OutputFlow.new
end
def test_capture_captures_the_temporary_output_buffer_in_its_block
@@ -49,14 +49,14 @@ class CaptureHelperTest < ActionView::TestCase
assert !content_for?(:title)
provide :title, "hi"
assert content_for?(:title)
- assert_equal "hi", @_view_flow.get(:title)
+ assert_equal "hi", @view_flow.get(:title)
provide :title, "<p>title</p>"
- assert_equal "hi&lt;p&gt;title&lt;/p&gt;", @_view_flow.get(:title)
+ assert_equal "hi&lt;p&gt;title&lt;/p&gt;", @view_flow.get(:title)
- @_view_flow = ActionView::OutputFlow.new
+ @view_flow = ActionView::OutputFlow.new
provide :title, "hi"
provide :title, "<p>title</p>".html_safe
- assert_equal "hi<p>title</p>", @_view_flow.get(:title)
+ assert_equal "hi<p>title</p>", @view_flow.get(:title)
end
def test_with_output_buffer_swaps_the_output_buffer_given_no_argument
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 12d2410f49..3dd400026c 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -121,6 +121,10 @@ class DateHelperTest < ActionView::TestCase
start_date = Date.new 1975, 1, 31
end_date = Date.new 1977, 1, 31
assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date))
+
+ start_date = Date.new 1982, 12, 3
+ end_date = Date.new 2010, 11, 30
+ assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date))
end
def test_distance_in_words_with_integers
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 656fa0356b..f95308b847 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -200,12 +200,18 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
- def test_select_tag_with_include_blank_with_string
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => "string"
+ def test_select_tag_with_prompt
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string"
expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
+ def test_select_tag_with_prompt_and_include_blank
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string", :include_blank => true
+ expected = %(<select name="places" id="places"><option value="">string</option><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
+ assert_dom_equal expected, actual
+ end
+
def test_text_area_tag_size_string
actual = text_area_tag "body", "hello world", "size" => "20x40"
expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb
index 8b8b005a1d..50e1cccd3b 100644
--- a/actionpack/test/template/log_subscriber_test.rb
+++ b/actionpack/test/template/log_subscriber_test.rb
@@ -9,7 +9,9 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def setup
super
@old_logger = ActionController::Base.logger
- @view = ActionView::Base.new(ActionController::Base.view_paths, {})
+ @controller = Object.new
+ @controller.stubs(:_prefixes).returns(%w(test))
+ @view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH))
ActionView::LogSubscriber.attach_to :action_view
end
@@ -57,7 +59,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
def test_render_partial_with_implicit_path
- @view.stubs(:controller_prefixes).returns(%w(test))
@view.render(Customer.new("david"), :greeting => "hi")
wait
@@ -74,7 +75,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
def test_render_collection_with_implicit_path
- @view.stubs(:controller_prefixes).returns(%w(test))
@view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi")
wait
@@ -83,7 +83,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
def test_render_collection_template_without_path
- @view.stubs(:controller_prefixes).returns(%w(test))
@view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi")
wait
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index ff94cba59f..5fb1fdc044 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -180,6 +180,12 @@ class LookupContextTest < ActiveSupport::TestCase
assert_not_equal template, old_template
end
+
+ test "responds to #prefixes" do
+ assert_equal [], @lookup_context.prefixes
+ @lookup_context.prefixes = ["foo"]
+ assert_equal ["foo"], @lookup_context.prefixes
+ end
end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb
index 4d69081570..b2df8efee3 100644
--- a/actionpack/test/template/streaming_render_test.rb
+++ b/actionpack/test/template/streaming_render_test.rb
@@ -13,8 +13,12 @@ class FiberedTest < ActiveSupport::TestCase
@controller_view = TestController.new.view_context
end
+ def render_body(options)
+ @view.view_renderer.render_body(@view, options)
+ end
+
def buffered_render(options)
- body = @view.render_body(options)
+ body = render_body(options)
string = ""
body.each do |piece|
string << piece
@@ -24,7 +28,7 @@ class FiberedTest < ActiveSupport::TestCase
def test_streaming_works
content = []
- body = @view.render_body(:template => "test/hello_world.erb", :layout => "layouts/yield")
+ body = render_body(:template => "test/hello_world.erb", :layout => "layouts/yield")
body.each do |piece|
content << piece
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 5c655d5b69..81fb34b80f 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -11,11 +11,9 @@ class TestERBTemplate < ActiveSupport::TestCase
end
class Context
- attr_accessor :_template
-
def initialize
@output_buffer = "original"
- @_virtual_path = nil
+ @virtual_path = nil
end
def hello
@@ -24,7 +22,7 @@ class TestERBTemplate < ActiveSupport::TestCase
def partial
ActionView::Template.new(
- "<%= @_template.virtual_path %>",
+ "<%= @virtual_path %>",
"partial",
ERBHandler,
:virtual_path => "partial"
@@ -86,9 +84,9 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_virtual_path
- @template = new_template("<%= @_template.virtual_path %>" \
+ @template = new_template("<%= @virtual_path %>" \
"<%= partial.render(self, {}) %>" \
- "<%= @_template.virtual_path %>")
+ "<%= @virtual_path %>")
assert_equal "hellopartialhello", render
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index 11c355dc6d..cd4618a505 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -73,6 +73,10 @@ module ActionView
view.request.flash.expects(:alert).with("this message")
view.alert("this message")
end
+
+ test "uses controller lookup context" do
+ assert_equal self.lookup_context, @controller.lookup_context
+ end
end
class ClassMethodsTest < ActionView::TestCase
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index a4fcff5167..740f577a6e 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -278,260 +278,6 @@ class TextHelperTest < ActionView::TestCase
assert_equal("12 berries", pluralize(12, "berry"))
end
- def test_auto_link_parsing
- urls = %w(
- http://www.rubyonrails.com
- http://www.rubyonrails.com:80
- http://www.rubyonrails.com/~minam
- https://www.rubyonrails.com/~minam
- http://www.rubyonrails.com/~minam/url%20with%20spaces
- http://www.rubyonrails.com/foo.cgi?something=here
- http://www.rubyonrails.com/foo.cgi?something=here&and=here
- http://www.rubyonrails.com/contact;new
- http://www.rubyonrails.com/contact;new%20with%20spaces
- http://www.rubyonrails.com/contact;new?with=query&string=params
- http://www.rubyonrails.com/~minam/contact;new?with=query&string=params
- http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007
- http://www.mail-archive.com/rails@lists.rubyonrails.org/
- http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1
- http://en.wikipedia.org/wiki/Texas_hold'em
- https://www.google.com/doku.php?id=gps:resource:scs:start
- http://connect.oraclecorp.com/search?search[q]=green+france&search[type]=Group
- http://of.openfoundry.org/projects/492/download#4th.Release.3
- http://maps.google.co.uk/maps?f=q&q=the+london+eye&ie=UTF8&ll=51.503373,-0.11939&spn=0.007052,0.012767&z=16&iwloc=A
- )
-
- urls.each do |url|
- assert_equal generate_result(url), auto_link(url)
- end
- end
-
- def generate_result(link_text, href = nil, escape = false)
- href ||= link_text
- if escape
- %{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>}
- else
- %{<a href="#{href}">#{link_text}</a>}
- end
- end
-
- def test_auto_link_should_not_be_html_safe
- email_raw = 'santiago@wyeworks.com'
- link_raw = 'http://www.rubyonrails.org'
-
- assert !auto_link(nil).html_safe?, 'should not be html safe'
- assert !auto_link('').html_safe?, 'should not be html safe'
- assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe'
- assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe'
- end
-
- def test_auto_link_email_address
- email_raw = 'aaron@tenderlovemaking.com'
- email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
- assert !auto_link_email_addresses(email_result).html_safe?, 'should not be html safe'
- end
-
- def test_auto_link
- email_raw = 'david@loudthinking.com'
- email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
- link_raw = 'http://www.rubyonrails.com'
- link_result = generate_result(link_raw)
- link_result_with_options = %{<a href="#{link_raw}" target="_blank">#{link_raw}</a>}
-
- assert_equal '', auto_link(nil)
- assert_equal '', auto_link('')
- assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}")
-
- assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses)
- assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls)
- assert_equal %(Go to #{link_raw}), auto_link("Go to #{link_raw}", :email_addresses)
- assert_equal %(Go to #{link_result} and say hello to #{email_result}), auto_link("Go to #{link_raw} and say hello to #{email_raw}")
- assert_equal %(<p>Link #{link_result}</p>), auto_link("<p>Link #{link_raw}</p>")
- assert_equal %(<p>#{link_result} Link</p>), auto_link("<p>#{link_raw} Link</p>")
- assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"})
- assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.))
- assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>))
- assert_equal %(#{link_result} #{link_result}), auto_link(%(#{link_result} #{link_raw}))
-
- email2_raw = '+david@loudthinking.com'
- email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>}
- assert_equal email2_result, auto_link(email2_raw)
-
- email3_raw = '+david@loudthinking.com'
- email3_result = %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;+%64%61%76%69%64@%6c%6f%75%64%74%68%69%6e%6b%69%6e%67.%63%6f%6d">#{email3_raw}</a>}
- assert_equal email3_result, auto_link(email3_raw, :all, :encode => :hex)
- assert_equal email3_result, auto_link(email3_raw, :email_addresses, :encode => :hex)
-
- link2_raw = 'www.rubyonrails.com'
- link2_result = generate_result(link2_raw, "http://#{link2_raw}")
- assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls)
- assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses)
- assert_equal %(<p>Link #{link2_result}</p>), auto_link("<p>Link #{link2_raw}</p>")
- assert_equal %(<p>#{link2_result} Link</p>), auto_link("<p>#{link2_raw} Link</p>")
- assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.))
- assert_equal %(<p>Say hello to #{email_result}, then go to #{link2_result}.</p>), auto_link(%(<p>Say hello to #{email_raw}, then go to #{link2_raw}.</p>))
-
- link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281'
- link3_result = generate_result(link3_raw)
- assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls)
- assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses)
- assert_equal %(<p>Link #{link3_result}</p>), auto_link("<p>Link #{link3_raw}</p>")
- assert_equal %(<p>#{link3_result} Link</p>), auto_link("<p>#{link3_raw} Link</p>")
- assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.))
- assert_equal %(<p>Go to #{link3_result}. Seriously, #{link3_result}? I think I'll say hello to #{email_result}. Instead.</p>),
- auto_link(%(<p>Go to #{link3_raw}. Seriously, #{link3_raw}? I think I'll say hello to #{email_raw}. Instead.</p>))
-
- link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123'
- link4_result = generate_result(link4_raw)
- assert_equal %(<p>Link #{link4_result}</p>), auto_link("<p>Link #{link4_raw}</p>")
- assert_equal %(<p>#{link4_result} Link</p>), auto_link("<p>#{link4_raw} Link</p>")
-
- link5_raw = 'http://foo.example.com:3000/controller/action'
- link5_result = generate_result(link5_raw)
- assert_equal %(<p>#{link5_result} Link</p>), auto_link("<p>#{link5_raw} Link</p>")
-
- link6_raw = 'http://foo.example.com:3000/controller/action+pack'
- link6_result = generate_result(link6_raw)
- assert_equal %(<p>#{link6_result} Link</p>), auto_link("<p>#{link6_raw} Link</p>")
-
- link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123'
- link7_result = generate_result(link7_raw)
- assert_equal %(<p>#{link7_result} Link</p>), auto_link("<p>#{link7_raw} Link</p>")
-
- link8_raw = 'http://foo.example.com:3000/controller/action.html'
- link8_result = generate_result(link8_raw)
- assert_equal %(Go to #{link8_result}), auto_link("Go to #{link8_raw}", :urls)
- assert_equal %(Go to #{link8_raw}), auto_link("Go to #{link8_raw}", :email_addresses)
- assert_equal %(<p>Link #{link8_result}</p>), auto_link("<p>Link #{link8_raw}</p>")
- assert_equal %(<p>#{link8_result} Link</p>), auto_link("<p>#{link8_raw} Link</p>")
- assert_equal %(Go to #{link8_result}.), auto_link(%(Go to #{link8_raw}.))
- assert_equal %(<p>Go to #{link8_result}. Seriously, #{link8_result}? I think I'll say hello to #{email_result}. Instead.</p>),
- auto_link(%(<p>Go to #{link8_raw}. Seriously, #{link8_raw}? I think I'll say hello to #{email_raw}. Instead.</p>))
-
- link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html'
- link9_result = generate_result(link9_raw)
- assert_equal %(Go to #{link9_result}), auto_link("Go to #{link9_raw}", :urls)
- assert_equal %(Go to #{link9_raw}), auto_link("Go to #{link9_raw}", :email_addresses)
- assert_equal %(<p>Link #{link9_result}</p>), auto_link("<p>Link #{link9_raw}</p>")
- assert_equal %(<p>#{link9_result} Link</p>), auto_link("<p>#{link9_raw} Link</p>")
- assert_equal %(Go to #{link9_result}.), auto_link(%(Go to #{link9_raw}.))
- assert_equal %(<p>Go to #{link9_result}. Seriously, #{link9_result}? I think I'll say hello to #{email_result}. Instead.</p>),
- auto_link(%(<p>Go to #{link9_raw}. Seriously, #{link9_raw}? I think I'll say hello to #{email_raw}. Instead.</p>))
-
- link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/'
- link10_result = generate_result(link10_raw)
- assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>")
-
- link11_raw = 'http://asakusa.rubyist.net/'
- link11_result = generate_result(link11_raw)
- with_kcode 'u' do
- assert_equal %(浅草.rbの公式サイトはこちら#{link11_result}), auto_link("浅草.rbの公式サイトはこちら#{link11_raw}")
- end
- end
-
- def test_auto_link_should_sanitize_input_when_sanitize_option_is_not_false
- link_raw = %{http://www.rubyonrails.com?id=1&num=2}
- assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw)
- end
-
- def test_auto_link_should_not_sanitize_input_when_sanitize_option_is_false
- link_raw = %{http://www.rubyonrails.com?id=1&num=2}
- assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw, :sanitize => false)
- end
-
- def test_auto_link_other_protocols
- ftp_raw = 'ftp://example.com/file.txt'
- assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}")
-
- file_scheme = 'file:///home/username/RomeoAndJuliet.pdf'
- z39_scheme = 'z39.50r://host:696/db'
- chrome_scheme = 'chrome://package/section/path'
- view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme'
- assert_equal generate_result(file_scheme), auto_link(file_scheme)
- assert_equal generate_result(z39_scheme), auto_link(z39_scheme)
- assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme)
- assert_equal generate_result(view_source), auto_link(view_source)
- end
-
- def test_auto_link_already_linked
- linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com')
- linked2 = %('<a href="http://www.example.com">www.example.com</a>')
- linked3 = %('<a href="http://www.example.com" rel="nofollow">www.example.com</a>')
- linked4 = %('<a href="http://www.example.com"><b>www.example.com</b></a>')
- linked5 = %('<a href="#close">close</a> <a href="http://www.example.com"><b>www.example.com</b></a>')
- assert_equal linked1, auto_link(linked1)
- assert_equal linked2, auto_link(linked2)
- assert_equal linked3, auto_link(linked3)
- assert_equal linked4, auto_link(linked4)
- assert_equal linked5, auto_link(linked5)
-
- linked_email = %Q(<a href="mailto:david@loudthinking.com">Mail me</a>)
- assert_equal linked_email, auto_link(linked_email)
- end
-
- def test_auto_link_within_tags
- link_raw = 'http://www.rubyonrails.org/images/rails.png'
- link_result = %Q(<img src="#{link_raw}" />)
- assert_equal link_result, auto_link(link_result)
- end
-
- def test_auto_link_with_brackets
- link1_raw = 'http://en.wikipedia.org/wiki/Sprite_(computer_graphics)'
- link1_result = generate_result(link1_raw)
- assert_equal link1_result, auto_link(link1_raw)
- assert_equal "(link: #{link1_result})", auto_link("(link: #{link1_raw})")
-
- link2_raw = 'http://en.wikipedia.org/wiki/Sprite_[computer_graphics]'
- link2_result = generate_result(link2_raw)
- assert_equal link2_result, auto_link(link2_raw)
- assert_equal "[link: #{link2_result}]", auto_link("[link: #{link2_raw}]")
-
- link3_raw = 'http://en.wikipedia.org/wiki/Sprite_{computer_graphics}'
- link3_result = generate_result(link3_raw)
- assert_equal link3_result, auto_link(link3_raw)
- assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}")
- end
-
- def test_auto_link_at_eol
- url1 = "http://api.rubyonrails.com/Foo.html"
- url2 = "http://www.ruby-doc.org/core/Bar.html"
-
- assert_equal %(<p><a href="#{url1}">#{url1}</a><br /><a href="#{url2}">#{url2}</a><br /></p>), auto_link("<p>#{url1}<br />#{url2}<br /></p>")
- end
-
- def test_auto_link_with_block
- url = "http://api.rubyonrails.com/Foo.html"
- email = "fantabulous@shiznadel.ic"
-
- assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |_url| truncate(_url, :length => 10) }
- end
-
- def test_auto_link_with_block_with_html
- pic = "http://example.com/pic.png"
- url = "http://example.com/album?a&b=c"
-
- assert_equal %(My pic: <a href="#{pic}"><img src="#{pic}" width="160px"></a> -- full album here #{generate_result(url)}), auto_link("My pic: #{pic} -- full album here #{url}") { |link|
- if link =~ /\.(jpg|gif|png|bmp|tif)$/i
- raw %(<img src="#{link}" width="160px">)
- else
- link
- end
- }
- end
-
- def test_auto_link_with_options_hash
- assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com" class="menu" target="_blank">me@email.com</a>.',
- auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.",
- :link => :all, :html => { :class => "menu", :target => "_blank" })
- end
-
- def test_auto_link_with_multiple_trailing_punctuations
- url = "http://youtube.com"
- url_result = generate_result(url)
- assert_equal url_result, auto_link(url)
- assert_equal "(link: #{url_result}).", auto_link("(link: #{url}).")
- end
-
def test_cycle_class
value = Cycle.new("one", 2, "3")
assert_equal("one", value.to_s)
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index fc330f7a73..8d0f0124c2 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -9,7 +9,7 @@ class UrlHelperTest < ActiveSupport::TestCase
# or request.
#
# In those cases, we'll set up a simple mock
- attr_accessor :controller, :request, :_template
+ attr_accessor :controller, :request
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 03287fac7a..c26924cf09 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis]
+
* Add support for proc or lambda as an option for InclusionValidator,
ExclusionValidator, and FormatValidator [Prem Sichanugrist]
@@ -13,6 +15,8 @@
* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov]
+* Add support for selectively enabling/disabling observers [Myron Marston]
+
*Rails 3.0.2 (unreleased)*
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 5ede78617a..3b412d3dd7 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -93,7 +93,7 @@ module ActiveModel
attribute_method_affix :prefix => 'reset_', :suffix => '!'
end
- # Do any attributes have unsaved changes?
+ # Returns true if any attribute have unsaved changes, false otherwise.
# person.changed? # => false
# person.name = 'bob'
# person.changed? # => true
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 315ccd40e9..74708692af 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -68,7 +68,7 @@ module ActiveModel
# BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
- # is required to pass the Active Model Lint test. So either extending the provided
+ # is required to pass the Active Model Lint test. So either extending the provided
# method below, or rolling your own is required.
module Naming
# Returns an ActiveModel::Name object for module. It can be
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index ab7f86007f..5fb73f1c78 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -2,7 +2,7 @@ require 'set'
module ActiveModel
# Stores the enabled/disabled state of individual observers for
- # a particular model classes.
+ # a particular model class.
class ObserverArray < Array
attr_reader :model_class
def initialize(model_class, *args)
@@ -10,14 +10,57 @@ module ActiveModel
super(*args)
end
+ # Returns true if the given observer is disabled for the model class.
def disabled_for?(observer)
disabled_observers.include?(observer.class)
end
+ # Disables one or more observers. This supports multiple forms:
+ #
+ # ORM.observers.disable :user_observer
+ # # => disables the UserObserver
+ #
+ # User.observers.disable AuditTrail
+ # # => disables the AuditTrail observer for User notifications.
+ # # Other models will still notify the AuditTrail observer.
+ #
+ # ORM.observers.disable :observer_1, :observer_2
+ # # => disables Observer1 and Observer2 for all models.
+ #
+ # ORM.observers.disable :all
+ # # => disables all observers for all models.
+ #
+ # User.observers.disable :all do
+ # # all user observers are disabled for
+ # # just the duration of the block
+ # end
def disable(*observers, &block)
set_enablement(false, observers, &block)
end
+ # Enables one or more observers. This supports multiple forms:
+ #
+ # ORM.observers.enable :user_observer
+ # # => enables the UserObserver
+ #
+ # User.observers.enable AuditTrail
+ # # => enables the AuditTrail observer for User notifications.
+ # # Other models will not be affected (i.e. they will not
+ # # trigger notifications to AuditTrail if previously disabled)
+ #
+ # ORM.observers.enable :observer_1, :observer_2
+ # # => enables Observer1 and Observer2 for all models.
+ #
+ # ORM.observers.enable :all
+ # # => enables all observers for all models.
+ #
+ # User.observers.enable :all do
+ # # all user observers are enabled for
+ # # just the duration of the block
+ # end
+ #
+ # Note: all observers are enabled by default. This method is only
+ # useful when you have previously disabled one or more observers.
def enable(*observers, &block)
set_enablement(true, observers, &block)
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index c1ac4eb4af..4682ae07ef 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -40,7 +40,11 @@ module ActiveModel
observers.replace(values.flatten)
end
- # Gets the current observers.
+ # Gets an array of observers observing this model.
+ # The array also provides +enable+ and +disable+ methods
+ # that allow you to selectively enable and disable observers.
+ # (see <tt>ActiveModel::ObserverArray.enable</tt> and
+ # <tt>ActiveModel::ObserverArray.disable</tt> for more on this)
def observers
@observers ||= ObserverArray.new(self)
end
@@ -56,6 +60,8 @@ module ActiveModel
end
# Add a new observer to the pool.
+ # The new observer needs to respond to 'update', otherwise it
+ # raises an +ArgumentError+ exception.
def add_observer(observer)
unless observer.respond_to? :update
raise ArgumentError, "observer needs to respond to `update'"
@@ -144,8 +150,8 @@ module ActiveModel
# Observers will by default be mapped to the class with which they share a
# name. So CommentObserver will be tied to observing Comment, ProductManagerObserver
# to ProductManager, and so on. If you want to name your observer differently than
- # the class you're interested in observing, you can use the Observer.observe class
- # method which takes either the concrete class (Product) or a symbol for that
+ # the class you're interested in observing, you can use the <tt>Observer.observe</tt>
+ # class method which takes either the concrete class (Product) or a symbol for that
# class (:product):
#
# class AuditObserver < ActiveModel::Observer
@@ -220,7 +226,8 @@ module ActiveModel
self.class.observed_classes
end
- # Send observed_method(object) if the method exists.
+ # Send observed_method(object) if the method exists and
+ # the observer is enabled for the given object's class.
def update(observed_method, object) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 920a133159..6d64c81b5f 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -18,12 +18,12 @@ module ActiveModel
#
# This also provides the required class methods for hooking into the
# Rails internationalization API, including being able to define a
- # class based i18n_scope and lookup_ancestors to find translations in
+ # class based +i18n_scope+ and +lookup_ancestors+ to find translations in
# parent classes.
module Translation
include ActiveModel::Naming
- # Returns the i18n_scope for the class. Overwrite if you want custom lookup.
+ # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
def i18n_scope
:activemodel
end
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 23ba42bf35..68c138da84 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module ActiveModel
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9ff29f1155..a03751a6c1 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,20 @@
*Rails 3.1.0 (unreleased)*
+* AR#new, AR#create and AR#update_attributes all accept a second hash as option that allows you
+ to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
+ new mass assignment capabilities:
+
+ class Post < ActiveRecord::Base
+ attr_accessible :title
+ attr_accessible :title, :published_at, :as => :admin
+ end
+
+ Post.new(params[:post], :as => :admin)
+
+ assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
+
+ [Josh Kalderimis]
+
* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
evaluation:
@@ -22,25 +37,7 @@
[Jon Leighton]
-* Calling 'default_scope' multiple times in a class (including when a superclass calls
- 'default_scope') is deprecated. The current behavior is that this will merge the default
- scopes together:
-
- class Post < ActiveRecord::Base # Rails 3.1
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:published => true, :hidden => false)
- end
-
- In Rails 3.2, the behavior will be changed to overwrite previous scopes:
-
- class Post < ActiveRecord::Base # Rails 3.2
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:hidden => false)
- end
-
- If you wish to merge default scopes in special ways, it is recommended to define your default
+* If you wish to merge default scopes in special ways, it is recommended to define your default
scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
etc.):
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index c3cd76a714..3a5035305b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -22,6 +22,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 2.0.2')
- s.add_dependency('tzinfo', '~> 0.3.23')
+ s.add_dependency('arel', '~> 2.1.0')
+ s.add_dependency('tzinfo', '~> 0.3.27')
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 33a184d48d..6cdec8c487 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -93,20 +93,20 @@ module ActiveRecord
first_or_last(:last, *args)
end
- def build(attributes = {}, &block)
- build_or_create(attributes, :build, &block)
+ def build(attributes = {}, options = {}, &block)
+ build_or_create(:build, attributes, options, &block)
end
- def create(attributes = {}, &block)
+ def create(attributes = {}, options = {}, &block)
unless owner.persisted?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
- build_or_create(attributes, :create, &block)
+ build_or_create(:create, attributes, options, &block)
end
- def create!(attrs = {}, &block)
- record = create(attrs, &block)
+ def create!(attrs = {}, options = {}, &block)
+ record = create(attrs, options, &block)
Array.wrap(record).each(&:save!)
record
end
@@ -403,9 +403,9 @@ module ActiveRecord
end + existing
end
- def build_or_create(attributes, method)
+ def build_or_create(method, attributes, options)
records = Array.wrap(attributes).map do |attrs|
- record = build_record(attrs)
+ record = build_record(attrs, options)
add_to_target(record) do
yield(record) if block_given?
@@ -421,8 +421,8 @@ module ActiveRecord
raise NotImplementedError
end
- def build_record(attributes)
- reflection.build_association(scoped.scope_for_create.merge(attributes))
+ def build_record(attributes, options)
+ reflection.build_association(scoped.scope_for_create.merge(attributes), options)
end
def delete_or_destroy(records, method)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 388173c1fb..adfc71d435 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -64,9 +64,12 @@ module ActiveRecord
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
- if match && match.creator?
- attributes = match.attribute_names
- return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
+ if match && match.instantiator?
+ record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
+ @association.send :set_owner_attributes, r
+ @association.send :add_to_target, r
+ yield(r) if block_given?
+ end
end
if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 9d2b29685b..7708228d23 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -60,10 +60,10 @@ module ActiveRecord
through_record
end
- def build_record(attributes)
+ def build_record(attributes, options = {})
ensure_not_nested
- record = super(attributes)
+ record = super(attributes, options)
inverse = source_reflection.inverse_of
if inverse
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0a666598ed..c32753782f 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -91,12 +91,12 @@ module ActiveRecord
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
- relation.from(join(table, constraint))
-
unless conditions[i].empty?
- relation.where(sanitize(conditions[i], table))
+ constraint = constraint.and(sanitize(conditions[i], table))
end
+ relation.from(join(table, constraint))
+
# The current table in this iteration becomes the foreign table in the next
foreign_table = table
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 4edbe216be..ea4d73d414 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -17,16 +17,16 @@ module ActiveRecord
replace(record)
end
- def create(attributes = {})
- new_record(:create, attributes)
+ def create(attributes = {}, options = {})
+ new_record(:create, attributes, options)
end
- def create!(attributes = {})
- build(attributes).tap { |record| record.save! }
+ def create!(attributes = {}, options = {})
+ build(attributes, options).tap { |record| record.save! }
end
- def build(attributes = {})
- new_record(:build, attributes)
+ def build(attributes = {}, options = {})
+ new_record(:build, attributes, options)
end
private
@@ -44,9 +44,9 @@ module ActiveRecord
replace(record)
end
- def new_record(method, attributes)
+ def new_record(method, attributes, options)
attributes = scoped.scope_for_create.merge(attributes || {})
- record = reflection.send("#{method}_association", attributes)
+ record = reflection.send("#{method}_association", attributes, options)
set_new_record(record)
record
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 04c12f86b6..6149865f80 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -83,7 +83,7 @@ module ActiveRecord #:nodoc:
#
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
- # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
# before inserting them in the query, which will ensure that an attacker can't escape the
# query and fake the login (or worse).
@@ -406,10 +406,10 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
# Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
# ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
+ # supports migrations. Use :ruby if you want to have different database
# adapters for, e.g., your development and test environments.
cattr_accessor :schema_format , :instance_writer => false
@@schema_format = :ruby
@@ -443,17 +443,17 @@ module ActiveRecord #:nodoc:
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
- # Executes a custom SQL query against your database and returns all the results. The results will
+ # Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
# a Product object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
# table.
#
- # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
- # no database agnostic conversions performed. This should be a last resort because using, for example,
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
# MySQL specific terms will lock you to using that particular database engine or require you to
# change your call if you switch engines.
#
@@ -472,13 +472,22 @@ module ActiveRecord #:nodoc:
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
# attributes on the objects that are to be created.
#
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
# ==== Examples
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
+ # # Create a single new object using the :admin mass-assignment security scope
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Create a single new object bypassing mass-assignment security
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ #
# # Create an Array of new objects
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
#
@@ -491,11 +500,11 @@ module ActiveRecord #:nodoc:
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
# u.is_admin = false
# end
- def create(attributes = nil, &block)
+ def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| create(attr, &block) }
+ attributes.collect { |attr| create(attr, options, &block) }
else
- object = new(attributes)
+ object = new(attributes, options)
yield(object) if block_given?
object.save
object
@@ -504,7 +513,7 @@ module ActiveRecord #:nodoc:
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# The use of this method should be restricted to complicated SQL queries that can't be executed
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
#
# ==== Parameters
#
@@ -581,7 +590,7 @@ module ActiveRecord #:nodoc:
# invoice/lineitem.rb Invoice::Lineitem lineitems
#
# Additionally, the class-level +table_name_prefix+ is prepended and the
- # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
# the table name guess for an Invoice class becomes "myapp_invoices".
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
#
@@ -615,7 +624,7 @@ module ActiveRecord #:nodoc:
@inheritance_column ||= "type"
end
- # Lazy-set the sequence name to the connection's default. This method
+ # Lazy-set the sequence name to the connection's default. This method
# is only ever called once since set_sequence_name overrides it.
def sequence_name #:nodoc:
reset_sequence_name
@@ -627,7 +636,7 @@ module ActiveRecord #:nodoc:
default
end
- # Sets the table name. If the value is nil or false then the value returned by the given
+ # Sets the table name. If the value is nil or false then the value returned by the given
# block is used.
#
# class Project < ActiveRecord::Base
@@ -1077,7 +1086,7 @@ module ActiveRecord #:nodoc:
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
#
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
- # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
+ # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
# array of strings format for your joins.
#
# class Article < ActiveRecord::Base
@@ -1180,19 +1189,15 @@ MSG
# Use this macro in your model to set a default scope for all operations on
# the model.
#
- # class Person < ActiveRecord::Base
- # default_scope order('last_name, first_name')
+ # class Article < ActiveRecord::Base
+ # default_scope where(:published => true)
# end
#
- # Person.all # => SELECT * FROM people ORDER BY last_name, first_name
+ # Article.all # => SELECT * FROM articles WHERE published = true
#
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
# applied while updating a record.
#
- # class Article < ActiveRecord::Base
- # default_scope where(:published => true)
- # end
- #
# Article.new.published # => true
# Article.create.published # => true
#
@@ -1205,6 +1210,19 @@ MSG
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
# macro, and it will be called when building the default scope.)
#
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
+ # be merged together:
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope where(:published => true)
+ # default_scope where(:rating => 'G')
+ # end
+ #
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
+ #
+ # This is also the case with inheritance and module includes where the parent or module
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
+ #
# If you need to do more complex things with a default scope, you can alternatively
# define it as a class method:
#
@@ -1214,36 +1232,8 @@ MSG
# end
# end
def default_scope(scope = {})
- if default_scopes.length != 0
- ActiveSupport::Deprecation.warn <<-WARN
-Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together:
-
-class Post < ActiveRecord::Base # Rails 3.1
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:published => true, :hidden => false)
-end
-
-In Rails 3.2, the behavior will be changed to overwrite previous scopes:
-
-class Post < ActiveRecord::Base # Rails 3.2
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:hidden => false)
-end
-
-If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.):
-
-class Post < ActiveRecord::Base
- def self.default_scope
- where(:published => true).where(:hidden => false)
- end
-end
- WARN
- end
-
scope = Proc.new if block_given?
- self.default_scopes = default_scopes.dup << scope
+ self.default_scopes = default_scopes + [scope]
end
def build_default_scope #:nodoc:
@@ -1400,7 +1390,7 @@ end
end.join(', ')
end
- # Accepts an array of conditions. The array has each value
+ # Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
@@ -1484,7 +1474,20 @@ end
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
# In both instances, valid attribute keys are determined by the column names of the associated table --
# hence you can't have attributes that aren't part of the table columns.
- def initialize(attributes = nil)
+ #
+ # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
+ # ==== Examples
+ # # Instantiates a single new object
+ # User.new(:first_name => 'Jamie')
+ #
+ # # Instantiates a single new object using the :admin mass-assignment security scope
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Instantiates a single new object bypassing mass-assignment security
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ def initialize(attributes = nil, options = {})
@attributes = attributes_from_column_definition
@association_cache = {}
@aggregation_cache = {}
@@ -1500,7 +1503,8 @@ end
set_serialized_attributes
populate_with_current_scope_attributes
- self.attributes = attributes unless attributes.nil?
+
+ assign_attributes(attributes, options) if attributes
result = yield self if block_given?
run_callbacks :initialize
@@ -1508,7 +1512,7 @@ end
end
# Populate +coder+ with attributes about this record that should be
- # serialized. The structure of +coder+ defined in this method is
+ # serialized. The structure of +coder+ defined in this method is
# guaranteed to match the structure of +coder+ passed to the +init_with+
# method.
#
@@ -1523,8 +1527,8 @@ end
coder['attributes'] = attributes
end
- # Initialize an empty model object from +coder+. +coder+ must contain
- # the attributes necessary for initializing an empty model object. For
+ # Initialize an empty model object from +coder+. +coder+ must contain
+ # the attributes necessary for initializing an empty model object. For
# example:
#
# class Post < ActiveRecord::Base
@@ -1939,32 +1943,9 @@ end
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- # in order to allow a date to be set without a year, we must keep the empty values.
- # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
- values = values_with_empty_parameters.reject { |v| v.nil? }
-
- if values.empty?
- send(name + "=", nil)
- else
-
- value = if Time == klass
- instantiate_time_object(name, values)
- elsif Date == klass
- begin
- values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
- Date.new(*values)
- rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- else
- klass.new(*values)
- end
-
- send(name + "=", value)
- end
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -1972,19 +1953,65 @@ end
end
end
+ def read_value_from_parameter(name, values_hash_from_param)
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values_hash_from_param.values.all?{|v|v.nil?}
+ nil
+ elsif klass == Time
+ read_time_parameter_value(name, values_hash_from_param)
+ elsif klass == Date
+ read_date_parameter_value(name, values_hash_from_param)
+ else
+ read_other_parameter_value(klass, name, values_hash_from_param)
+ end
+ end
+
+ def read_time_parameter_value(name, values_hash_from_param)
+ # If Date bits were not provided, error
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ # If Date bits were provided but blank, then default to 1
+ # If Time bits are not there, then default to 0
+ [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
+ instantiate_time_object(name, set_values)
+ end
+
+ def read_date_parameter_value(name, values_hash_from_param)
+ set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other_parameter_value(klass, name, values_hash_from_param)
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
+ values = (1..max_position).collect do |position|
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
+ values_hash_from_param[position]
+ end
+ klass.new(*values)
+ end
+
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
+ [values_hash_from_param.keys.max,upper_cap].min
+ end
+
def extract_callstack_for_multiparameter_attributes(pairs)
attributes = { }
for pair in pairs
multiparameter_name, value = pair
attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = [] unless attributes.include?(attribute_name)
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
- attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
end
- attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
+ attributes
end
def type_cast_attribute_value(multiparameter_name, value)
@@ -1992,7 +2019,7 @@ end
end
def find_parameter_position(multiparameter_name)
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index b4db1eed18..6f21cea288 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- # A cached lookup for table existence
+ # A cached lookup for table existence.
def table_exists?(name)
return true if @tables.key? name
@@ -135,7 +135,7 @@ module ActiveRecord
@tables.clear
end
- # Clear out internal caches for table with +table_name+
+ # Clear out internal caches for table with +table_name+.
def clear_table_cache!(table_name)
@columns.delete table_name
@columns_hash.delete table_name
@@ -193,7 +193,7 @@ module ActiveRecord
@connections = []
end
- # Clears the cache which maps classes
+ # Clears the cache which maps classes.
def clear_reloadable_connections!
@reserved_connections.each do |name, conn|
checkin conn
@@ -365,7 +365,7 @@ module ActiveRecord
@connection_pools.each_value {|pool| pool.release_connection }
end
- # Clears the cache which maps classes
+ # Clears the cache which maps classes.
def clear_reloadable_connections!
@connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 29ac9341ec..30ccb8f0a4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -2,52 +2,53 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseLimits
- # the maximum length of a table alias
+ # Returns the maximum length of a table alias.
def table_alias_length
255
end
- # the maximum length of a column name
+ # Returns the maximum length of a column name.
def column_name_length
64
end
- # the maximum length of a table name
+ # Returns the maximum length of a table name.
def table_name_length
64
end
- # the maximum length of an index name
+ # Returns the maximum length of an index name.
def index_name_length
64
end
- # the maximum number of columns per table
+ # Returns the maximum number of columns per table.
def columns_per_table
1024
end
- # the maximum number of indexes per table
+ # Returns the maximum number of indexes per table.
def indexes_per_table
16
end
- # the maximum number of columns in a multicolumn index
+ # Returns the maximum number of columns in a multicolumn index.
def columns_per_multicolumn_index
16
end
- # the maximum number of elements in an IN (x,y,z) clause. nil means no limit
+ # Returns the maximum number of elements in an IN (x,y,z) clause.
+ # nil means no limit.
def in_clause_length
nil
end
- # the maximum length of an SQL query
+ # Returns the maximum length of an SQL query.
def sql_query_length
1048575
end
- # maximum number of joins in a single query
+ # Returns maximum number of joins in a single query.
def joins_per_query
256
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 70da9d5f1e..3045e30407 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -55,13 +55,27 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [])
end
- # Executes insert +sql+ statement in the context of this connection using
+ # Executes insert +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is the logged along with
# the executed +sql+ statement.
def exec_insert(sql, name, binds)
exec_query(sql, name, binds)
end
+ # Executes delete +sql+ statement in the context of this connection using
+ # +binds+ as the bind substitutes. +name+ is the logged along with
+ # the executed +sql+ statement.
+ def exec_delete(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
+ # Executes update +sql+ statement in the context of this connection using
+ # +binds+ as the bind substitutes. +name+ is the logged along with
+ # the executed +sql+ statement.
+ def exec_update(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
# Returns the last auto-generated ID from the affected table.
#
# +id_value+ will be returned unless the value is nil, in
@@ -77,13 +91,13 @@ module ActiveRecord
end
# Executes the update statement and returns the number of rows affected.
- def update(sql, name = nil)
- update_sql(sql, name)
+ def update(sql, name = nil, binds = [])
+ exec_update(sql, name, binds)
end
# Executes the delete statement and returns the number of rows affected.
- def delete(sql, name = nil)
- delete_sql(sql, name)
+ def delete(sql, name = nil, binds = [])
+ exec_delete(sql, name, binds)
end
# Checks whether there is currently no transaction active. This is done
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 1db397f584..093c30aa42 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -29,6 +29,14 @@ module ActiveRecord
@query_cache_enabled = old
end
+ def enable_query_cache!
+ @query_cache_enabled = true
+ end
+
+ def disable_query_cache!
+ @query_cache_enabled = false
+ end
+
# Disable the query cache within the block.
def uncached
old, @query_cache_enabled = @query_cache_enabled, false
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7ac48c6646..70a8f6bb58 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -104,7 +104,7 @@ module ActiveRecord
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
# Requests a maximum column length. This is number of characters for <tt>:string</tt> and
- # <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
+ # <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
# * <tt>:default</tt> -
# The column's default value. Use nil for NULL.
# * <tt>:null</tt> -
@@ -153,7 +153,7 @@ module ActiveRecord
# This method returns <tt>self</tt>.
#
# == Examples
- # # Assuming td is an instance of TableDefinition
+ # # Assuming +td+ is an instance of TableDefinition
# td.column(:granted, :boolean)
# # granted BOOLEAN
#
@@ -204,7 +204,7 @@ module ActiveRecord
# end
#
# There's a short-hand method for each of the type values declared at the top. And then there's
- # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
+ # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
@@ -351,7 +351,7 @@ module ActiveRecord
@base.index_exists?(@table_name, column_name, options)
end
- # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
# ===== Example
# t.timestamps
def timestamps
@@ -398,7 +398,7 @@ module ActiveRecord
@base.remove_index(@table_name, options)
end
- # Removes the timestamp columns (created_at and updated_at) from the table.
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
# ===== Example
# t.remove_timestamps
def remove_timestamps
@@ -412,7 +412,7 @@ module ActiveRecord
@base.rename_column(@table_name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column.
+ # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
# ===== Examples
# t.references(:goat)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 8bae50885f..9f9c2c42cb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -17,6 +17,10 @@ module ActiveRecord
# def tables(name = nil) end
+ # Checks to see if the table +table_name+ exists on the database.
+ #
+ # === Example
+ # table_exists?(:developers)
def table_exists?(table_name)
tables.include?(table_name.to_s)
end
@@ -24,7 +28,7 @@ module ActiveRecord
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
- # Checks to see if an index exists on a table for a given index definition
+ # Checks to see if an index exists on a table for a given index definition.
#
# === Examples
# # Check an index exists
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 468a2b106b..65024d76f8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -46,34 +46,34 @@ module ActiveRecord
@instrumenter = ActiveSupport::Notifications.instrumenter
end
- # Returns the human-readable name of the adapter. Use mixed case - one
+ # Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_name
'Abstract'
end
- # Does this adapter support migrations? Backend specific, as the
+ # Does this adapter support migrations? Backend specific, as the
# abstract adapter always returns +false+.
def supports_migrations?
false
end
# Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables? Backend specific, as
+ # to an Active Record class, such as join tables? Backend specific, as
# the abstract adapter always returns +false+.
def supports_primary_key?
false
end
- # Does this adapter support using DISTINCT within COUNT? This is +true+
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?
true
end
- # Does this adapter support DDL rollbacks in transactions? That is, would
- # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
- # SQL Server, and others support this. MySQL and others do not.
+ # Does this adapter support DDL rollbacks in transactions? That is, would
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
+ # SQL Server, and others support this. MySQL and others do not.
def supports_ddl_transactions?
false
end
@@ -89,7 +89,7 @@ module ActiveRecord
end
# Should primary key values be selected from their corresponding
- # sequence before the insert statement? If true, next_sequence_value
+ # sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
# This is false for all adapters but Firebird.
def prefetch_primary_key?(table_name = nil)
@@ -149,7 +149,7 @@ module ActiveRecord
###
# Clear any caching the database adapter may be doing, for example
- # clearing the prepared statement cache. This is database specific.
+ # clearing the prepared statement cache. This is database specific.
def clear_cache!
# this should be overridden by concrete adapters
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index cf68ddc2da..8af22fe9f5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -5,6 +5,7 @@ require 'mysql2'
module ActiveRecord
class Base
+ # Establishes a connection to the database that's used by all Active Record objects.
def self.mysql2_connection(config)
config[:username] = 'root' if config[:username].nil?
@@ -132,6 +133,7 @@ module ActiveRecord
ADAPTER_NAME
end
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
end
@@ -140,6 +142,7 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints?
true
end
@@ -211,6 +214,8 @@ module ActiveRecord
false
end
+ # Disconnects from the database if already connected.
+ # Otherwise, this method does nothing.
def disconnect!
unless @connection.nil?
@connection.close
@@ -290,6 +295,15 @@ module ActiveRecord
execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
end
+ def exec_delete(sql, name, binds)
+ binds = binds.dup
+
+ # Pretend to support bind parameters
+ execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ @connection.affected_rows
+ end
+ alias :exec_update :exec_delete
+
def last_inserted_id(result)
@connection.last_id
end
@@ -357,6 +371,8 @@ module ActiveRecord
end
end
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
def recreate_database(name, options = {})
drop_database(name)
create_database(name, options)
@@ -377,6 +393,10 @@ module ActiveRecord
end
end
+ # Drops a MySQL database.
+ #
+ # Example:
+ # drop_database('sebastian_development')
def drop_database(name) #:nodoc:
execute "DROP DATABASE IF EXISTS `#{name}`"
end
@@ -395,22 +415,36 @@ module ActiveRecord
show_variable 'collation_database'
end
- def tables(name = nil)
- tables = []
- execute("SHOW TABLES", name).each do |field|
- tables << field.first
+ def tables(name = nil, database = nil) #:nodoc:
+ sql = ["SHOW TABLES", database].compact.join(' IN ')
+ execute(sql, 'SCHEMA').collect do |field|
+ field.first
end
- tables
+ end
+
+ def table_exists?(name)
+ return true if super
+
+ name = name.to_s
+ schema, table = name.split('.', 2)
+
+ unless table # A table was provided without a schema
+ table = schema
+ schema = nil
+ end
+
+ tables(nil, schema).include? table
end
def drop_table(table_name, options = {})
super(table_name, options)
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
indexes = []
current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) do |row|
if current_index != row[:Key_name]
next if row[:Key_name] == PRIMARY # skip the primary key
@@ -424,10 +458,11 @@ module ActiveRecord
indexes
end
+ # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
def columns(table_name, name = nil)
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- result = execute(sql)
+ result = execute(sql, 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) { |field|
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
}
@@ -438,6 +473,10 @@ module ActiveRecord
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
end
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
end
@@ -517,14 +556,16 @@ module ActiveRecord
end
end
+ # SHOW VARIABLES LIKE 'name'.
def show_variable(name)
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
variables.first['Value'] unless variables.empty?
end
+ # Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table)
keys = []
- result = execute("describe #{quote_table_name(table)}")
+ result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) do |row|
keys << row[:Field] if row[:Key] == "PRI"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 1136dbc45e..a9f4c08348 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -3,15 +3,8 @@ require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/object/blank'
require 'set'
-begin
- require 'mysql'
-rescue LoadError
- raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql'"
-end
-
-unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash)
- raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'"
-end
+gem 'mysql', '~> 2.8.1'
+require 'mysql'
class Mysql
class Time
@@ -208,13 +201,13 @@ module ActiveRecord
true
end
- # Returns +true+, since this connection adapter supports prepared statement
+ # Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
true
end
- # Returns true.
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations? #:nodoc:
true
end
@@ -224,6 +217,7 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints? #:nodoc:
true
end
@@ -401,44 +395,9 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [])
log(sql, name, binds) do
- result = nil
-
- cache = {}
- if binds.empty?
- stmt = @connection.prepare(sql)
- else
- cache = @statements[sql] ||= {
- :stmt => @connection.prepare(sql)
- }
- stmt = cache[:stmt]
- end
-
-
- begin
- stmt.execute(*binds.map { |col, val| type_cast(val, col) })
- rescue Mysql::Error => e
- # Older versions of MySQL leave the prepared statement in a bad
- # place when an error occurs. To support older mysql versions, we
- # need to close the statement and delete the statement from the
- # cache.
- stmt.close
- @statements.delete sql
- raise e
+ exec_stmt(sql, name, binds) do |cols, stmt|
+ ActiveRecord::Result.new(cols, stmt.to_a) if cols
end
-
- if metadata = stmt.result_metadata
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
- field.name
- }
-
- metadata.free
- result = ActiveRecord::Result.new(cols, stmt.to_a)
- end
-
- stmt.free_result
- stmt.close if binds.empty?
-
- result
end
end
@@ -490,6 +449,15 @@ module ActiveRecord
@connection.affected_rows
end
+ def exec_delete(sql, name, binds)
+ log(sql, name, binds) do
+ exec_stmt(sql, name, binds) do |cols, stmt|
+ stmt.affected_rows
+ end
+ end
+ end
+ alias :exec_update :exec_delete
+
def begin_db_transaction #:nodoc:
exec_without_stmt "BEGIN"
rescue Mysql::Error
@@ -549,6 +517,8 @@ module ActiveRecord
end.join("")
end
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
def recreate_database(name, options = {}) #:nodoc:
drop_database(name)
create_database(name, options)
@@ -592,9 +562,8 @@ module ActiveRecord
end
def tables(name = nil, database = nil) #:nodoc:
- tables = []
result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
- result.each { |field| tables << field[0] }
+ tables = result.collect { |field| field[0] }
result.free
tables
end
@@ -617,6 +586,7 @@ module ActiveRecord
super(table_name, options)
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)#:nodoc:
indexes = []
current_index = nil
@@ -635,11 +605,11 @@ module ActiveRecord
indexes
end
+ # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- columns = []
result = execute(sql, 'SCHEMA')
- result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result.free
columns
end
@@ -648,6 +618,10 @@ module ActiveRecord
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
end
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
end
@@ -843,6 +817,46 @@ module ActiveRecord
end
private
+ def exec_stmt(sql, name, binds)
+ cache = {}
+ if binds.empty?
+ stmt = @connection.prepare(sql)
+ else
+ cache = @statements[sql] ||= {
+ :stmt => @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ end
+
+
+ begin
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
+ rescue Mysql::Error => e
+ # Older versions of MySQL leave the prepared statement in a bad
+ # place when an error occurs. To support older mysql versions, we
+ # need to close the statement and delete the statement from the
+ # cache.
+ stmt.close
+ @statements.delete sql
+ raise e
+ end
+
+ cols = nil
+ if metadata = stmt.result_metadata
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
+ field.name
+ }
+ end
+
+ result = yield [cols, stmt]
+
+ stmt.result_metadata.free if cols
+ stmt.free_result
+ stmt.close if binds.empty?
+
+ result
+ end
+
def connect
encoding = @config[:encoding]
if encoding
@@ -885,6 +899,7 @@ module ActiveRecord
version[0] >= 5
end
+ # Returns the version of the connected MySQL server.
def version
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0c2afc180b..37db2be7a9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -122,6 +122,14 @@ module ActiveRecord
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ when NilClass
+ nil
# Numeric types
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
$1
@@ -237,7 +245,6 @@ module ActiveRecord
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@table_alias_length = nil
- @postgresql_version = nil
@statements = {}
connect
@@ -259,28 +266,16 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- if @connection.respond_to?(:status)
- @connection.status == PGconn::CONNECTION_OK
- else
- # We're asking the driver, not Active Record, so use @connection.query instead of #query
- @connection.query 'SELECT 1'
- true
- end
- # postgres-pr raises a NoMethodError when querying if no connection is available.
- rescue PGError, NoMethodError
+ @connection.status == PGconn::CONNECTION_OK
+ rescue PGError
false
end
# Close then reopen the connection.
def reconnect!
- if @connection.respond_to?(:reset)
- clear_cache!
- @connection.reset
- configure_connection
- else
- disconnect!
- connect
- end
+ clear_cache!
+ @connection.reset
+ configure_connection
end
def reset!
@@ -299,7 +294,7 @@ module ActiveRecord
NATIVE_DATABASE_TYPES
end
- # Does PostgreSQL support migrations?
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
end
@@ -325,6 +320,7 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints?
true
end
@@ -433,17 +429,17 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
- def supports_disable_referential_integrity?() #:nodoc:
+ def supports_disable_referential_integrity? #:nodoc:
true
end
def disable_referential_integrity #:nodoc:
- if supports_disable_referential_integrity?() then
+ if supports_disable_referential_integrity? then
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
end
yield
ensure
- if supports_disable_referential_integrity?() then
+ if supports_disable_referential_integrity? then
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
end
end
@@ -517,12 +513,7 @@ module ActiveRecord
# Queries the database and returns the results in an Array-like object
def query(sql, name = nil) #:nodoc:
log(sql, name) do
- if @async
- res = @connection.async_exec(sql)
- else
- res = @connection.exec(sql)
- end
- return result_as_array(res)
+ result_as_array @connection.async_exec(sql)
end
end
@@ -530,11 +521,7 @@ module ActiveRecord
# or raising a PGError exception otherwise.
def execute(sql, name = nil)
log(sql, name) do
- if @async
- @connection.async_exec(sql)
- else
- @connection.exec(sql)
- end
+ @connection.async_exec(sql)
end
end
@@ -543,30 +530,27 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- return exec_no_cache(sql, name) if binds.empty?
-
log(sql, name, binds) do
- unless @statements.key? sql
- nextkey = "a#{@statements.length + 1}"
- @connection.prepare nextkey, sql
- @statements[sql] = nextkey
- end
+ result = binds.empty? ? exec_no_cache(sql, binds) :
+ exec_cache(sql, binds)
- key = @statements[sql]
-
- # Clear the queue
- @connection.get_last_result
- @connection.send_query_prepared(key, binds.map { |col, val|
- type_cast(val, col)
- })
- @connection.block
- result = @connection.get_last_result
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
result.clear
return ret
end
end
+ def exec_delete(sql, name = 'SQL', binds = [])
+ log(sql, name, binds) do
+ result = binds.empty? ? exec_no_cache(sql, binds) :
+ exec_cache(sql, binds)
+ affected = result.cmd_tuples
+ result.clear
+ affected
+ end
+ end
+ alias :exec_update :exec_delete
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
unless pk
_, table = extract_schema_and_table(sql.split(" ", 4)[2])
@@ -699,7 +683,7 @@ module ActiveRecord
[schema, table]
end
- # Returns the list of all indexes for a table.
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
result = query(<<-SQL, name)
@@ -864,6 +848,9 @@ module ActiveRecord
end
# Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(name, new_name)
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
@@ -948,24 +935,9 @@ module ActiveRecord
end
protected
- # Returns the version of the connected PostgreSQL version.
+ # Returns the version of the connected PostgreSQL server.
def postgresql_version
- @postgresql_version ||=
- if @connection.respond_to?(:server_version)
- @connection.server_version
- else
- # Mimic PGconn.server_version behavior
- begin
- if query('SELECT version()')[0][0] =~ /PostgreSQL ([0-9.]+)/
- major, minor, tiny = $1.split(".")
- (major.to_i * 10000) + (minor.to_i * 100) + tiny.to_i
- else
- 0
- end
- rescue
- 0
- end
- end
+ @connection.server_version
end
def translate_exception(exception, message)
@@ -980,13 +952,26 @@ module ActiveRecord
end
private
- def exec_no_cache(sql, name)
- log(sql, name) do
- result = @connection.async_exec(sql)
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
- result.clear
- ret
+ def exec_no_cache(sql, binds)
+ @connection.async_exec(sql)
+ end
+
+ def exec_cache(sql, binds)
+ unless @statements.key? sql
+ nextkey = "a#{@statements.length + 1}"
+ @connection.prepare nextkey, sql
+ @statements[sql] = nextkey
end
+
+ key = @statements[sql]
+
+ # Clear the queue
+ @connection.get_last_result
+ @connection.send_query_prepared(key, binds.map { |col, val|
+ type_cast(val, col)
+ })
+ @connection.block
+ @connection.get_last_result
end
# The internal PostgreSQL identifier of the money data type.
@@ -998,10 +983,6 @@ module ActiveRecord
# connected server's characteristics.
def connect
@connection = PGconn.connect(*@connection_parameters)
- PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
-
- # Ignore async_exec and async_query when using postgres-pr.
- @async = @connection.respond_to?(:async_exec)
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
@@ -1015,11 +996,7 @@ module ActiveRecord
# This is called by #connect and should not be called manually.
def configure_connection
if @config[:encoding]
- if @connection.respond_to?(:set_client_encoding)
- @connection.set_client_encoding(@config[:encoding])
- else
- execute("SET client_encoding TO '#{@config[:encoding]}'")
- end
+ @connection.set_client_encoding(@config[:encoding])
end
self.client_min_messages = @config[:min_messages] if @config[:min_messages]
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index ed5006dcec..d2785b234a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -58,10 +58,12 @@ module ActiveRecord
'SQLite'
end
+ # Returns true if SQLite version is '2.0.0' or greater, false otherwise.
def supports_ddl_transactions?
sqlite_version >= '2.0.0'
end
+ # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
def supports_savepoints?
sqlite_version >= '3.6.8'
end
@@ -72,7 +74,7 @@ module ActiveRecord
true
end
- # Returns true.
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations? #:nodoc:
true
end
@@ -86,6 +88,7 @@ module ActiveRecord
true
end
+ # Returns true if SQLite version is '3.1.6' or greater, false otherwise.
def supports_add_column?
sqlite_version >= '3.1.6'
end
@@ -103,10 +106,12 @@ module ActiveRecord
@statements.clear
end
+ # Returns true if SQLite version is '3.2.6' or greater, false otherwise.
def supports_count_distinct? #:nodoc:
sqlite_version >= '3.2.6'
end
+ # Returns true if SQLite version is '3.1.0' or greater, false otherwise.
def supports_autoincrement? #:nodoc:
sqlite_version >= '3.1.0'
end
@@ -178,6 +183,12 @@ module ActiveRecord
end
end
+ def exec_delete(sql, name = 'SQL', binds = [])
+ exec_query(sql, name, binds)
+ @connection.changes
+ end
+ alias :exec_update :exec_delete
+
def last_inserted_id(result)
@connection.last_insert_row_id
end
@@ -244,6 +255,7 @@ module ActiveRecord
end
end
+ # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
def columns(table_name, name = nil) #:nodoc:
table_structure(table_name).map do |field|
case field["dflt_value"]
@@ -259,6 +271,7 @@ module ActiveRecord
end
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
IndexDefinition.new(
@@ -282,6 +295,10 @@ module ActiveRecord
exec_query "DROP INDEX #{quote_column_name(index_name)}"
end
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(name, new_name)
exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 0939ec2626..96fea741e0 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -509,7 +509,9 @@ class Fixtures
# FIXME: Apparently JK uses this.
connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
+ files_to_read = table_names.reject { |table_name|
+ fixture_is_cached?(connection, table_name)
+ }
unless files_to_read.empty?
connection.disable_referential_integrity do
@@ -521,7 +523,7 @@ class Fixtures
fixtures_map[path] = Fixtures.new(
connection,
table_name,
- class_names[table_name.to_sym],
+ class_names[table_name.to_sym] || table_name.classify,
File.join(fixtures_directory, path))
end
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 95a8e5cff7..9eb47ad99f 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -49,12 +49,15 @@ module ActiveRecord
end
def get(klass, primary_key)
- obj = repository[klass.symbolized_base_class][primary_key]
- if obj.is_a?(klass)
- if ActiveRecord::Base.logger
- ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map"
- end
- obj
+ record = repository[klass.symbolized_base_class][primary_key]
+
+ if record.is_a?(klass)
+ ActiveSupport::Notifications.instrument("identity.active_record",
+ :line => "From Identity Map (id: #{primary_key})",
+ :name => "#{klass} Loaded",
+ :connection_id => object_id)
+
+ record
else
nil
end
@@ -95,14 +98,33 @@ module ActiveRecord
end
class Middleware
+ class Body #:nodoc:
+ def initialize(target, original)
+ @target = target
+ @original = original
+ end
+
+ def each(&block)
+ @target.each(&block)
+ end
+
+ def close
+ @target.close if @target.respond_to?(:close)
+ ensure
+ IdentityMap.enabled = @original
+ IdentityMap.clear
+ end
+ end
+
def initialize(app)
@app = app
end
def call(env)
- ActiveRecord::IdentityMap.use do
- @app.call(env)
- end
+ enabled = IdentityMap.enabled
+ IdentityMap.enabled = true
+ status, headers, body = @app.call(env)
+ [status, headers, Body.new(body, enabled)]
end
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index d31e321440..3a015ee8c2 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -46,6 +46,15 @@ module ActiveRecord
debug " #{name} #{sql}#{binds}"
end
+ def identity(event)
+ return unless logger.debug?
+
+ name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
+ line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
+
+ debug " #{name} #{line}"
+ end
+
def odd?
@odd_or_even = !@odd_or_even
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0893d7e337..c723436330 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -110,8 +110,8 @@ module ActiveRecord
next unless respond_to?(callback)
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
- klass.send(:define_method, callback_meth) do
- observer.send(callback, self)
+ klass.send(:define_method, callback_meth) do |&block|
+ observer.send(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 787ac977e0..b4531ed35f 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -77,7 +77,15 @@ module ActiveRecord
def destroy
if persisted?
IdentityMap.remove(self) if IdentityMap.enabled?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
+ pk = self.class.primary_key
+ column = self.class.columns_hash[pk]
+ substitute = connection.substitute_at(column, 0)
+
+ relation = self.class.unscoped.where(
+ self.class.arel_table[pk].eq(substitute))
+
+ relation.bind_values = [[column, id]]
+ relation.delete_all
end
@destroyed = true
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index d9f85a4e5e..4e61671473 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -27,10 +27,32 @@ module ActiveRecord
@app = app
end
- def call(env)
- ActiveRecord::Base.cache do
- @app.call(env)
+ class BodyProxy # :nodoc:
+ def initialize(original_cache_value, target)
+ @original_cache_value = original_cache_value
+ @target = target
+ end
+
+ def each(&block)
+ @target.each(&block)
+ end
+
+ def close
+ @target.close if @target.respond_to?(:close)
+ ensure
+ ActiveRecord::Base.connection.clear_query_cache
+ unless @original_cache_value
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
end
end
+
+ def call(env)
+ old = ActiveRecord::Base.connection.query_cache_enabled
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ status, headers, body = @app.call(env)
+ [status, headers, BodyProxy.new(old, body)]
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index d38588519b..bae2ded244 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -26,10 +26,12 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
- # When loading console, force ActiveRecord to be loaded to avoid cross
- # references when loading a constant for the first time.
- console do
- ActiveRecord::Base
+ # When loading console, force ActiveRecord::Base to be loaded
+ # to avoid cross references when loading a constant for the
+ # first time. Also, make it output to STDERR.
+ console do |sandbox|
+ require "active_record/railties/console_sandbox" if sandbox
+ ActiveRecord::Base.logger = Logger.new(STDERR)
end
initializer "active_record.initialize_timezone" do
diff --git a/railties/lib/rails/console/sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
index 65a3d68619..65a3d68619 100644
--- a/railties/lib/rails/console/sandbox.rb
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 7d76d7a19f..5703fac033 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -4,11 +4,11 @@ db_namespace = namespace :db do
task :load_config => :rails_env do
require 'active_record'
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
- ActiveRecord::Migrator.migrations_paths = Rails.application.paths["db/migrate"].to_a
+ ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
- if engine.paths["db/migrate"].existent
- ActiveRecord::Migrator.migrations_paths += engine.paths["db/migrate"].to_a
+ if engine.paths['db/migrate'].existent
+ ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a
end
end
end
@@ -143,7 +143,7 @@ db_namespace = namespace :db do
end
def local_database?(config, &block)
- if config['host'].in?(["127.0.0.1", "localhost"]) || config['host'].blank?
+ if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank?
yield
else
$stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
@@ -161,35 +161,35 @@ db_namespace = namespace :db do
namespace :migrate do
# desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
task :redo => [:environment, :load_config] do
- if ENV["VERSION"]
- db_namespace["migrate:down"].invoke
- db_namespace["migrate:up"].invoke
+ if ENV['VERSION']
+ db_namespace['migrate:down'].invoke
+ db_namespace['migrate:up'].invoke
else
- db_namespace["rollback"].invoke
- db_namespace["migrate"].invoke
+ db_namespace['rollback'].invoke
+ db_namespace['migrate'].invoke
end
end
# desc 'Resets your database using your migrations for the current environment'
- task :reset => ["db:drop", "db:create", "db:migrate"]
+ task :reset => ['db:drop', 'db:create', 'db:migrate']
# desc 'Runs the "up" for a given migration VERSION.'
task :up => [:environment, :load_config] do
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required" unless version
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
+ raise 'VERSION is required' unless version
ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
# desc 'Runs the "down" for a given migration VERSION.'
task :down => [:environment, :load_config] do
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required" unless version
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
+ raise 'VERSION is required' unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
- desc "Display status of migrations"
+ desc 'Display status of migrations'
task :status => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
ActiveRecord::Base.establish_connection(config)
@@ -208,7 +208,7 @@ db_namespace = namespace :db do
end
# output
puts "\ndatabase: #{config['database']}\n\n"
- puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name"
+ puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
file_list.each do |file|
puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
@@ -224,14 +224,14 @@ db_namespace = namespace :db do
task :rollback => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
task :forward => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
@@ -244,10 +244,10 @@ db_namespace = namespace :db do
when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
- when 'postgresql'
+ when /postgresql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.encoding
- when 'sqlite3'
+ when /sqlite/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.encoding
else
@@ -267,7 +267,7 @@ db_namespace = namespace :db do
end
end
- desc "Retrieves the current schema version number"
+ desc 'Retrieves the current schema version number'
task :version => :environment do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
@@ -301,8 +301,8 @@ db_namespace = namespace :db do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
- base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
- fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
+ base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
Fixtures.create_fixtures(fixtures_dir, fixture_file)
@@ -313,8 +313,8 @@ db_namespace = namespace :db do
task :identify => :environment do
require 'active_record/fixtures'
- label, id = ENV["LABEL"], ENV["ID"]
- raise "LABEL or ID required" if label.blank? && id.blank?
+ label, id = ENV['LABEL'], ENV['ID']
+ raise 'LABEL or ID required' if label.blank? && id.blank?
puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label
@@ -334,16 +334,16 @@ db_namespace = namespace :db do
end
namespace :schema do
- desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
+ desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
task :dump => :load_config do
require 'active_record/schema_dumper'
File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
- db_namespace["schema:dump"].reenable
+ db_namespace['schema:dump'].reenable
end
- desc "Load a schema.rb file into the database"
+ desc 'Load a schema.rb file into the database'
task :load => :environment do
file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
@@ -355,29 +355,29 @@ db_namespace = namespace :db do
end
namespace :structure do
- desc "Dump the database structure to an SQL file"
+ desc 'Dump the database structure to an SQL file'
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
- case abcs[Rails.env]["adapter"]
- when /mysql/, "oci", "oracle"
+ case abcs[Rails.env]['adapter']
+ when /mysql/, 'oci', 'oracle'
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open("#{Rails.root}/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"]
- search_path = abcs[Rails.env]["schema_search_path"]
+ 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']
+ search_path = abcs[Rails.env]['schema_search_path']
unless search_path.blank?
search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
end
- `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}`
- raise "Error dumping database" if $?.exitstatus == 1
- when "sqlite", "sqlite3"
- dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"]
- `#{abcs[Rails.env]["adapter"]} #{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`
+ `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}`
+ raise 'Error dumping database' if $?.exitstatus == 1
+ when /sqlite/
+ dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile']
+ `sqlite3 #{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`
when "firebird"
set_firebird_env(abcs[Rails.env])
db_string = firebird_db_string(abcs[Rails.env])
@@ -397,81 +397,81 @@ db_namespace = namespace :db do
task :load => 'db:test:purge' do
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
ActiveRecord::Schema.verbose = false
- db_namespace["schema:load"].invoke
+ db_namespace['schema:load'].invoke
end
# desc "Recreate the test database from the current environment's database schema"
task :clone => %w(db:schema:dump db:test:load)
# desc "Recreate the test databases from the development structure"
- task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
+ task :clone_structure => [ 'db:structure:dump', 'db:test:purge' ] do
abcs = ActiveRecord::Base.configurations
- case abcs["test"]["adapter"]
+ case abcs['test']['adapter']
when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{Rails.root}/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 #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]} #{abcs["test"]["template"]}`
- when "sqlite", "sqlite3"
- dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
- `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
- when "sqlserver"
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
- when "oci", "oracle"
+ 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 #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs['test']['database']} #{abcs['test']['template']}`
+ when /sqlite/
+ dbfile = abcs['test']['database'] || abcs['test']['dbfile']
+ `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
+ when 'sqlserver'
+ `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql`
+ when 'oci', 'oracle'
ActiveRecord::Base.establish_connection(:test)
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
- when "firebird"
- set_firebird_env(abcs["test"])
- db_string = firebird_db_string(abcs["test"])
+ when 'firebird'
+ set_firebird_env(abcs['test'])
+ db_string = firebird_db_string(abcs['test'])
sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}"
else
- raise "Task not supported by '#{abcs["test"]["adapter"]}'"
+ raise "Task not supported by '#{abcs['test']['adapter']}'"
end
end
# desc "Empty the test database"
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
- case abcs["test"]["adapter"]
+ case abcs['test']['adapter']
when /mysql/
ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
- when "postgresql"
+ ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test'])
+ when /postgresql/
ActiveRecord::Base.clear_active_connections!
drop_database(abcs['test'])
create_database(abcs['test'])
- when "sqlite","sqlite3"
- dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
+ when /sqlite/
+ dbfile = abcs['test']['database'] || abcs['test']['dbfile']
File.delete(dbfile) if File.exist?(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`
+ 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`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
- when "firebird"
+ when 'firebird'
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database!
else
- raise "Task not supported by '#{abcs["test"]["adapter"]}'"
+ raise "Task not supported by '#{abcs['test']['adapter']}'"
end
end
# desc 'Check for pending migrations and load the test schema'
task :prepare => 'db:abort_if_pending_migrations' do
if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
- db_namespace[{ :sql => "test:clone_structure", :ruby => "test:load" }[ActiveRecord::Base.schema_format]].invoke
+ db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke
end
end
end
@@ -479,11 +479,11 @@ db_namespace = namespace :db do
namespace :sessions do
# desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
task :create => :environment do
- raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations?
+ raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
require 'rails/generators'
Rails::Generators.configure!
require 'rails/generators/rails/session_migration/session_migration_generator'
- Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ]
+ Rails::Generators::SessionMigrationGenerator.start [ ENV['MIGRATION'] || 'add_sessions_table' ]
end
# desc "Clear the sessions table"
@@ -496,13 +496,13 @@ end
namespace :railties do
namespace :install do
# desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2"
- task :migrations => :"db:load_config" do
- to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip }
+ task :migrations => :'db:load_config' do
+ to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
railties = {}
Rails.application.railties.all do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
- if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first)
+ if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first)
railties[railtie.railtie_name] = path
end
end
@@ -528,13 +528,13 @@ def drop_database(config)
when /mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
- when /^sqlite/
+ when /sqlite/
require 'pathname'
path = Pathname.new(config['database'])
file = path.absolute? ? path.to_s : File.join(Rails.root, path)
FileUtils.rm(file)
- when 'postgresql'
+ when /postgresql/
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
ActiveRecord::Base.connection.drop_database config['database']
end
@@ -545,8 +545,8 @@ def session_table_name
end
def set_firebird_env(config)
- ENV["ISC_USER"] = config["username"].to_s if config["username"]
- ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"]
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
end
def firebird_db_string(config)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 8e5f66ec1d..ae9afad48a 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -220,7 +220,7 @@ module ActiveRecord
stmt.take limit if limit
stmt.order(*order)
stmt.key = table[primary_key]
- @klass.connection.update stmt.to_sql
+ @klass.connection.update stmt.to_sql, 'SQL', bind_values
end
end
@@ -338,7 +338,9 @@ module ActiveRecord
where(conditions).delete_all
else
statement = arel.compile_delete
- affected = @klass.connection.delete statement.to_sql
+ affected = @klass.connection.delete(
+ statement.to_sql, 'SQL', bind_values)
+
reset
affected
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 982b3d7e9f..2814771002 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -25,7 +25,18 @@ module ActiveRecord
values = value.to_a.map { |x|
x.is_a?(ActiveRecord::Base) ? x.id : x
}
- attribute.in(values)
+
+ if values.include?(nil)
+ values = values.compact
+ if values.empty?
+ attribute.eq nil
+ else
+ attribute.in(values.compact).or attribute.eq(nil)
+ end
+ else
+ attribute.in(values)
+ end
+
when Range, Arel::Relation
attribute.in(value)
when ActiveRecord::Base
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index d73fce9fd0..de36dd20b3 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -30,7 +30,7 @@ module ActiveRecord
include ActiveModel::Validations
module ClassMethods
- # Creates an object just like Base.create but calls save! instead of save
+ # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
# so an exception is raised if the record is invalid.
def create!(attributes = nil, &block)
if attributes.is_a?(Array)
@@ -44,13 +44,13 @@ module ActiveRecord
end
end
- # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is
+ # The validation process on save can be skipped by passing <tt>:validate => false</tt>. The regular Base#save method is
# replaced with this when the validations module is mixed in, which it is by default.
def save(options={})
perform_validations(options) ? super : false
end
- # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
+ # Attempts to save the record just like Base#save but will raise a +RecordInvalid+ exception instead of returning false
# if the record is not valid.
def save!(options={})
perform_validations(options) ? super : raise(RecordInvalid.new(self))
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 0667be7d23..2c20dd997f 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index c6c1d1dad5..a2155d1dd1 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -31,6 +31,6 @@ module ActiveRecord
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
- end if current_adapter?(:MysqlAdapter)
+ end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
new file mode 100644
index 0000000000..858d1da2dd
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -0,0 +1,36 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2SchemaTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ db = Post.connection_pool.spec.config[:database]
+ table = Post.table_name
+ @db_name = db
+
+ @omgpost = Class.new(Post) do
+ set_table_name "#{db}.#{table}"
+ def self.name; 'Post'; end
+ end
+ end
+
+ def test_schema
+ assert @omgpost.find(:first)
+ end
+
+ def test_table_exists?
+ name = @omgpost.table_name
+ assert @connection.table_exists?(name), "#{name} table should exist"
+ end
+
+ def test_table_exists_wrong_schema
+ assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 9006914508..ddcc36c841 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -580,7 +580,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
post = posts(:welcome)
comment = comments(:greetings)
- assert_difference 'post.reload.taggings_count', -1 do
+ assert_difference lambda { post.reload.taggings_count }, -1 do
assert_difference 'comment.reload.taggings_count', +1 do
tagging.taggable = comment
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 9bc7910fc6..3e92a77830 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -170,10 +170,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [comment], category.posts[0].comments
end
end
-
+
def test_associations_loaded_for_all_records
post = Post.create!(:title => 'foo', :body => "I like cars!")
- comment = SpecialComment.create!(:body => 'Come on!', :post => post)
+ SpecialComment.create!(:body => 'Come on!', :post => post)
first_category = Category.create! :name => 'First!', :posts => [post]
second_category = Category.create! :name => 'Second!', :posts => [post]
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 007f11b535..247decc67b 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -605,6 +605,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
end
+ def test_find_or_initialize_updates_collection_size
+ number_of_clients = companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
+ end
+
+ def test_find_or_create_with_hash
+ post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_with_one_attribute_followed_by_hash
+ post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block
+ post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert post.persisted?
+ end
+
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 70a4e06dbe..89117593fd 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -760,7 +760,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_primary_key_option_on_source
post = posts(:welcome)
category = categories(:general)
- categorization = Categorization.create!(:post_id => post.id, :named_category_name => category.name)
+ Categorization.create!(:post_id => post.id, :named_category_name => category.name)
assert_equal [category], post.named_categories
assert_equal [category.name], post.named_category_ids # checks when target loaded
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index e2228228a3..124693f7c9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -34,6 +34,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_no_match(/JOIN/i, sql)
end
+ def test_join_conditions_added_to_join_clause
+ sql = Author.joins(:essays).to_sql
+ assert_match(/writer_type.*?=.*?Author/i, sql)
+ assert_no_match(/WHERE/i, sql)
+ end
+
+ def test_join_conditions_allow_nil_associations
+ authors = Author.includes(:essays).where(:essays => {:id => nil})
+ assert_equal 2, authors.count
+ end
+
def test_find_with_implicit_inner_joins_honors_readonly_without_select
authors = Author.joins(:posts).to_a
assert !authors.empty?, "expected authors to be non-empty"
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5ee3b2d776..1775ba9999 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -575,6 +575,29 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
+ def test_multiparameter_attributes_on_time_with_no_date
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_invalid_time_params
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
def test_multiparameter_attributes_on_time_with_old_date
attributes = {
"written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
@@ -586,6 +609,82 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
end
+ def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 0, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 2, topic.written_on.sec
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 24, topic.written_on.min
+ assert_equal 0, topic.written_on.sec
+ end
+ def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 02, topic.written_on.sec
+ end
+
def test_multiparameter_attributes_on_time_with_utc
ActiveRecord::Base.default_timezone = :utc
attributes = {
@@ -692,6 +791,42 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal address, customer.address
end
+ def test_multiparameter_assignment_of_aggregation_out_of_order
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_missing_values
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_blank_values
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal Address.new(nil, "The City", "The Country"), customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_large_index
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 655437318f..be4ba18555 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1045,6 +1045,29 @@ class FinderTest < ActiveRecord::TestCase
:order => ' author_addresses_authors.id DESC ', :limit => 3).size
end
+ def test_find_with_nil_inside_set_passed_for_one_attribute
+ client_of = Company.find(
+ :all,
+ :conditions => {
+ :client_of => [2, 1, nil],
+ :name => ['37signals', 'Summit', 'Microsoft'] },
+ :order => 'client_of DESC'
+ ).map { |x| x.client_of }
+
+ assert client_of.include?(nil)
+ assert_equal [2, 1].sort, client_of.compact.sort
+ end
+
+ def test_find_with_nil_inside_set_passed_for_attribute
+ client_of = Company.find(
+ :all,
+ :conditions => { :client_of => [nil] },
+ :order => 'client_of DESC'
+ ).map { |x| x.client_of }
+
+ assert_equal [], client_of.compact
+ end
+
def test_with_limiting_with_custom_select
posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id')
assert_equal 3, posts.size
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index fa40fad56d..3e20155210 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -45,6 +45,11 @@ class FixturesTest < ActiveRecord::TestCase
end
end
+ def test_create_fixtures
+ Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
+ assert Parrot.find_by_name('Curious George'), 'George is in the database'
+ end
+
def test_multiple_clean_fixtures
fixtures_array = nil
assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb
new file mode 100644
index 0000000000..60dcad4586
--- /dev/null
+++ b/activerecord/test/cases/identity_map/middleware_test.rb
@@ -0,0 +1,71 @@
+require "cases/helper"
+
+module ActiveRecord
+ module IdentityMap
+ class MiddlewareTest < ActiveRecord::TestCase
+ def setup
+ super
+ @enabled = IdentityMap.enabled
+ IdentityMap.enabled = false
+ end
+
+ def teardown
+ super
+ IdentityMap.enabled = @enabled
+ IdentityMap.clear
+ end
+
+ def test_delegates
+ called = false
+ mw = Middleware.new lambda { |env|
+ called = true
+ }
+ mw.call({})
+ assert called, 'middleware delegated'
+ end
+
+ def test_im_enabled_during_delegation
+ mw = Middleware.new lambda { |env|
+ assert IdentityMap.enabled?, 'identity map should be enabled'
+ }
+ mw.call({})
+ end
+
+ class Enum < Struct.new(:iter)
+ def each(&b)
+ iter.call(&b)
+ end
+ end
+
+ def test_im_enabled_during_body_each
+ mw = Middleware.new lambda { |env|
+ [200, {}, Enum.new(lambda { |&b|
+ assert IdentityMap.enabled?, 'identity map should be enabled'
+ b.call "hello"
+ })]
+ }
+ body = mw.call({}).last
+ body.each { |x| assert_equal 'hello', x }
+ end
+
+ def test_im_disabled_after_body_close
+ mw = Middleware.new lambda { |env| [200, {}, []] }
+ body = mw.call({}).last
+ assert IdentityMap.enabled?, 'identity map should be enabled'
+ body.close
+ assert !IdentityMap.enabled?, 'identity map should be disabled'
+ end
+
+ def test_im_cleared_after_body_close
+ mw = Middleware.new lambda { |env| [200, {}, []] }
+ body = mw.call({}).last
+
+ IdentityMap.repository['hello'] = 'world'
+ assert !IdentityMap.repository.empty?, 'repo should not be empty'
+
+ body.close
+ assert IdentityMap.repository.empty?, 'repo should be empty'
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 199e59657d..649715fbb5 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+
require 'models/developer'
require 'models/project'
require 'models/company'
@@ -137,8 +138,6 @@ class IdentityMapTest < ActiveRecord::TestCase
assert_equal(["name"], swistak.changed)
assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
- s = Subscriber.find('swistak')
-
assert swistak.name_changed?
assert_equal("Swistak Sreberkowiec", swistak.name)
end
@@ -149,8 +148,6 @@ class IdentityMapTest < ActiveRecord::TestCase
Subscriber.update_all({:name => "Raczkowski Marcin"}, {:name => "Marcin Raczkowski"})
- s = Subscriber.find('swistak')
-
assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
assert_equal("Swistak Sreberkowiec", swistak.name)
end
@@ -163,8 +160,6 @@ class IdentityMapTest < ActiveRecord::TestCase
Subscriber.update_all({:name => "Swistak Sreberkowiec"}, {:name => "Marcin Raczkowski"})
- s = Subscriber.find('swistak')
-
assert_equal("Swistak Sreberkowiec", swistak.name)
assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
assert swistak.name_changed?
@@ -175,7 +170,7 @@ class IdentityMapTest < ActiveRecord::TestCase
pirate.birds.create!(:name => 'Posideons Killer')
pirate.birds.create!(:name => 'Killer bandita Dionne')
- posideons, killer = pirate.birds
+ posideons, _ = pirate.birds
pirate.reload
@@ -388,15 +383,6 @@ class IdentityMapTest < ActiveRecord::TestCase
assert_not_nil post.title
end
- def test_log
- log = StringIO.new
- ActiveRecord::Base.logger = Logger.new(log)
- ActiveRecord::Base.logger.level = Logger::DEBUG
- Post.find 1
- Post.find 1
- assert_match(/Post with ID = 1 loaded from Identity Map/, log.string)
- end
-
# Currently AR is not allowing changing primary key (see Persistence#update)
# So we ignore it. If this changes, this test needs to be uncommented.
# def test_updating_of_pkey
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 6cd8494c9e..643e949087 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -107,6 +107,23 @@ class ValidatedCommentObserver < ActiveRecord::Observer
end
end
+
+class AroundTopic < Topic
+end
+
+class AroundTopicObserver < ActiveRecord::Observer
+ observe :around_topic
+ def topic_ids
+ @topic_ids ||= []
+ end
+
+ def around_save(topic)
+ topic_ids << topic.id
+ yield(topic)
+ topic_ids << topic.id
+ end
+end
+
class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers, :minimalistics
@@ -206,6 +223,14 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal developer, SalaryChecker.instance.last_saved
end
+ test "around filter from observer should accept block" do
+ observer = AroundTopicObserver.instance
+ topic = AroundTopic.new
+ topic.save
+ assert_nil observer.topic_ids.first
+ assert_not_nil observer.topic_ids.last
+ end
+
def test_observer_is_called_once
observer = DeveloperObserver.instance # activate
observer.calls.clear
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 5f55299065..c6c6079490 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -1,11 +1,14 @@
require "cases/helper"
require "models/developer"
+require "models/post"
require "active_support/log_subscriber/test_helper"
class LogSubscriberTest < ActiveRecord::TestCase
include ActiveSupport::LogSubscriber::TestHelper
include ActiveSupport::BufferedLogger::Severity
+ fixtures :posts
+
def setup
@old_logger = ActiveRecord::Base.logger
@using_identity_map = ActiveRecord::IdentityMap.enabled?
@@ -91,4 +94,13 @@ class LogSubscriberTest < ActiveRecord::TestCase
def test_initializes_runtime
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
end
+
+ def test_log
+ ActiveRecord::IdentityMap.use do
+ Post.find 1
+ Post.find 1
+ end
+ wait
+ assert_match(/From Identity Map/, @logger.logged(:debug).last)
+ end
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 2c051bff84..fbbae99e8b 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -5,7 +5,63 @@ require 'models/keyboard'
require 'models/task'
require 'models/person'
+
+module MassAssignmentTestHelpers
+ def setup
+ # another AR test modifies the columns which causes issues with create calls
+ TightPerson.reset_column_information
+ LoosePerson.reset_column_information
+ end
+
+ def attributes_hash
+ {
+ :id => 5,
+ :first_name => 'Josh',
+ :gender => 'm',
+ :comments => 'rides a sweet bike'
+ }
+ end
+
+ def assert_default_attributes(person, create = false)
+ unless create
+ assert_nil person.id
+ else
+ assert !!person.id
+ end
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_nil person.comments
+ end
+
+ def assert_admin_attributes(person, create = false)
+ unless create
+ assert_nil person.id
+ else
+ assert !!person.id
+ end
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'rides a sweet bike', person.comments
+ end
+
+ def assert_all_attributes(person)
+ assert_equal 5, person.id
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
+ assert_equal 'rides a sweet bike', person.comments
+ end
+end
+
+module MassAssignmentRelationTestHelpers
+ def setup
+ super
+ @person = LoosePerson.create(attributes_hash)
+ end
+end
+
+
class MassAssignmentSecurityTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
def test_customized_primary_key_remains_protected
subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
@@ -35,60 +91,114 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
p = LoosePerson.new
p.assign_attributes(attributes_hash)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal nil, p.comments
+ assert_default_attributes(p)
end
def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used
p = LoosePerson.new
p.assign_attributes(attributes_hash, :without_protection => true)
- assert_equal 5, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal 'rides a sweet bike', p.comments
+ assert_all_attributes(p)
end
def test_assign_attributes_with_default_scope_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :default)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal nil, p.comments
+ assert_default_attributes(p)
end
def test_assign_attributes_with_admin_scope_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :admin)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal 'rides a sweet bike', p.comments
+ assert_admin_attributes(p)
end
def test_assign_attributes_with_default_scope_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :default)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal nil, p.comments
+ assert_default_attributes(p)
end
def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :admin)
- assert_equal nil, p.id
- assert_equal 'Josh', p.first_name
- assert_equal 'm', p.gender
- assert_equal 'rides a sweet bike', p.comments
+ assert_admin_attributes(p)
+ end
+
+ def test_new_with_attr_accessible_attributes
+ p = TightPerson.new(attributes_hash)
+
+ assert_default_attributes(p)
+ end
+
+ def test_new_with_attr_protected_attributes
+ p = LoosePerson.new(attributes_hash)
+
+ assert_default_attributes(p)
+ end
+
+ def test_create_with_attr_accessible_attributes
+ p = TightPerson.create(attributes_hash)
+
+ assert_default_attributes(p, true)
+ end
+
+ def test_create_with_attr_protected_attributes
+ p = LoosePerson.create(attributes_hash)
+
+ assert_default_attributes(p, true)
+ end
+
+ def test_new_with_admin_scope_with_attr_accessible_attributes
+ p = TightPerson.new(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p)
+ end
+
+ def test_new_with_admin_scope_with_attr_protected_attributes
+ p = LoosePerson.new(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p)
+ end
+
+ def test_create_with_admin_scope_with_attr_accessible_attributes
+ p = TightPerson.create(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
+ def test_create_with_admin_scope_with_attr_protected_attributes
+ p = LoosePerson.create(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
+ def test_new_with_without_protection_with_attr_accessible_attributes
+ p = TightPerson.new(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_new_with_without_protection_with_attr_protected_attributes
+ p = LoosePerson.new(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_create_with_without_protection_with_attr_accessible_attributes
+ p = TightPerson.create(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_create_with_without_protection_with_attr_protected_attributes
+ p = LoosePerson.create(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
end
def test_protection_against_class_attribute_writers
@@ -101,14 +211,268 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
- private
+end
- def attributes_hash
- {
- :id => 5,
- :first_name => 'Josh',
- :gender => 'm',
- :comments => 'rides a sweet bike'
- }
+
+class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+ include MassAssignmentRelationTestHelpers
+
+ # build
+
+ def test_has_one_build_with_attr_protected_attributes
+ best_friend = @person.build_best_friend(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_without_protection
+ best_friend = @person.build_best_friend(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ best_friend = @person.create_best_friend(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
end
-end \ No newline at end of file
+
+ def test_has_one_create_without_protection
+ best_friend = @person.create_best_friend(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+end
+
+
+class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+ include MassAssignmentRelationTestHelpers
+
+ # build
+
+ def test_has_one_build_with_attr_protected_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_without_protection
+ best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_without_protection
+ best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+end
+
+
+class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+ include MassAssignmentRelationTestHelpers
+
+ # build
+
+ def test_has_one_build_with_attr_protected_attributes
+ best_friend = @person.best_friends.build(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_attr_accessible_attributes
+ best_friend = @person.best_friends.build(attributes_hash)
+ assert_default_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend)
+ end
+
+ def test_has_one_build_without_protection
+ best_friend = @person.best_friends.build(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create
+
+ def test_has_one_create_with_attr_protected_attributes
+ best_friend = @person.best_friends.create(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_without_protection
+ best_friend = @person.best_friends.create(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+ # create!
+
+ def test_has_one_create_with_bang_with_attr_protected_attributes
+ best_friend = @person.best_friends.create!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create!(attributes_hash)
+ assert_default_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
+ assert_admin_attributes(best_friend, true)
+ end
+
+ def test_has_one_create_with_bang_without_protection
+ best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true)
+ assert_all_attributes(best_friend)
+ end
+
+end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 287f7e255b..a61180cfaf 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -10,6 +10,70 @@ class QueryCacheTest < ActiveRecord::TestCase
def setup
Task.connection.clear_query_cache
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
+ def test_middleware_delegates
+ called = false
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ called = true
+ }
+ mw.call({})
+ assert called, 'middleware should delegate'
+ end
+
+ def test_middleware_caches
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Task.find 1
+ Task.find 1
+ assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ }
+ mw.call({})
+ end
+
+ def test_cache_enabled_during_call
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
+ }
+ mw.call({})
+ end
+
+ def test_cache_on_during_body_write
+ streaming = Class.new do
+ def each
+ yield ActiveRecord::Base.connection.query_cache_enabled
+ end
+ end
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ [200, {}, streaming.new]
+ }
+ body = mw.call({}).last
+ body.each { |x| assert x, 'cache should be on' }
+ body.close
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
+ end
+
+ def test_cache_off_after_close
+ mw = ActiveRecord::QueryCache.new lambda { |env| }
+ body = mw.call({}).last
+
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
+ body.close
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
+ end
+
+ def test_cache_clear_after_close
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Post.find(:first)
+ }
+ body = mw.call({}).last
+
+ assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
+ body.close
+ assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
end
def test_find_queries
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 2ed676fe69..864b3d4846 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -355,6 +355,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 50000, wheres[:salary]
end
+ def test_default_scope_with_module_includes
+ wheres = ModuleIncludedPoorDeveloperCalledJamis.scoped.where_values_hash
+ assert_equal "Jamis", wheres[:name]
+ assert_equal 50000, wheres[:salary]
+ end
+
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash
assert_equal "Jamis", wheres[:name]
@@ -456,18 +462,4 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
end
-
- def test_multiple_default_scope_calls_are_deprecated
- klass = Class.new(ActiveRecord::Base)
-
- assert_not_deprecated do
- klass.send(:default_scope, :foo => :bar)
- end
-
- assert_deprecated do
- klass.send(:default_scope, :foo => :bar)
- end
-
- assert_equal 2, klass.default_scopes.length
- end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 10701dd6fd..152f804e16 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -129,28 +129,40 @@ end
class DeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
+
default_scope where(:name => 'Jamis')
scope :poor, where('salary < 150000')
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
+
default_scope where(:name => 'Jamis', :salary => 50000)
end
class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis
self.table_name = 'developers'
- ActiveSupport::Deprecation.silence do
- default_scope where(:salary => 50000)
- end
+ default_scope where(:salary => 50000)
end
class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
+
default_scope where(:name => 'Jamis')
+ default_scope where(:salary => 50000)
+end
- ActiveSupport::Deprecation.silence do
- default_scope where(:salary => 50000)
- end
+module SalaryDefaultScope
+ extend ActiveSupport::Concern
+
+ included { default_scope where(:salary => 50000) }
end
+
+class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis
+ self.table_name = 'developers'
+
+ include SalaryDefaultScope
+end
+
+
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 9c4794902d..a58c9bf572 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -1,6 +1,6 @@
class Person < ActiveRecord::Base
has_many :readers
- has_one :reader
+ has_one :reader
has_many :posts, :through => :readers
has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null'
@@ -8,23 +8,23 @@ class Person < ActiveRecord::Base
has_many :references
has_many :bad_references
has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference'
- has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
+ has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
has_many :jobs, :through => :references
- has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy
+ has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy
has_many :jobs_with_dependent_delete_all, :source => :job, :through => :references, :dependent => :delete_all
- has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify
+ has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify
belongs_to :primary_contact, :class_name => 'Person'
has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
has_many :agents_of_agents, :through => :agents, :source => :agents
belongs_to :number1_fan, :class_name => 'Person'
- has_many :agents_posts, :through => :agents, :source => :posts
+ has_many :agents_posts, :through => :agents, :source => :posts
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
- scope :males, :conditions => { :gender => 'M' }
+ scope :males, :conditions => { :gender => 'M' }
scope :females, :conditions => { :gender => 'F' }
end
@@ -56,14 +56,25 @@ class LoosePerson < ActiveRecord::Base
attr_protected :comments
attr_protected :as => :admin
+
+ has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
+ belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id
+
+ has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
end
class LooseDescendant < LoosePerson; end
class TightPerson < ActiveRecord::Base
self.table_name = 'people'
+
attr_accessible :first_name, :gender
attr_accessible :first_name, :gender, :comments, :as => :admin
+
+ has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id
+ belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id
+
+ has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id
end
class TightDescendant < TightPerson; end \ No newline at end of file
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ceadb05644..9479242e4f 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -438,6 +438,8 @@ ActiveRecord::Schema.define do
t.references :number1_fan
t.integer :lock_version, :null => false, :default => 0
t.string :comments
+ t.references :best_friend
+ t.references :best_friend_of
t.timestamps
end
diff --git a/activeresource/examples/performance.rb b/activeresource/examples/performance.rb
new file mode 100644
index 0000000000..e4df7a38a4
--- /dev/null
+++ b/activeresource/examples/performance.rb
@@ -0,0 +1,70 @@
+require 'rubygems'
+require 'active_resource'
+require 'benchmark'
+
+TIMES = (ENV['N'] || 10_000).to_i
+
+# deep nested resource
+attrs = {
+ :id => 1,
+ :name => 'Luis',
+ :age => 21,
+ :friends => [
+ {
+ :name => 'JK',
+ :age => 24,
+ :colors => ['red', 'green', 'blue'],
+ :brothers => [
+ {
+ :name => 'Mateo',
+ :age => 35,
+ :children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }]
+ },
+ {
+ :name => 'Felipe',
+ :age => 33,
+ :children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }]
+ }
+ ]
+ },
+ {
+ :name => 'Eduardo',
+ :age => 20,
+ :colors => [],
+ :brothers => [
+ {
+ :name => 'Sebas',
+ :age => 23,
+ :children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }]
+ },
+ {
+ :name => 'Elsa',
+ :age => 19,
+ :children => [{ :name => 'Natacha', :age => 1 }]
+ },
+ {
+ :name => 'Milena',
+ :age => 16,
+ :children => []
+ }
+ ]
+ }
+ ]
+}
+
+class Customer < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000"
+end
+
+module Nested
+ class Customer < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000"
+ end
+end
+
+Benchmark.bm(40) do |x|
+ x.report('Model.new (instantiation)') { TIMES.times { Customer.new } }
+ x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } }
+ x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } }
+ x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } }
+end
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 160763779e..7f2a844723 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -1239,9 +1239,10 @@ module ActiveResource
@attributes[key.to_s] =
case value
when Array
- resource = find_or_create_resource_for_collection(key)
+ resource = nil
value.map do |attrs|
if attrs.is_a?(Hash)
+ resource ||= find_or_create_resource_for_collection(key)
resource.new(attrs)
else
attrs.duplicable? ? attrs.dup : attrs
@@ -1251,7 +1252,7 @@ module ActiveResource
resource = find_or_create_resource_for(key)
resource.new(value)
else
- value.dup rescue value
+ value.duplicable? ? value.dup : value
end
end
self
@@ -1367,36 +1368,44 @@ module ActiveResource
end
# Tries to find a resource in a non empty list of nested modules
- # Raises a NameError if it was not found in any of the given nested modules
- def find_resource_in_modules(resource_name, module_names)
+ # if it fails, then the resource is created
+ def find_or_create_resource_in_modules(resource_name, module_names)
receiver = Object
namespaces = module_names[0, module_names.size-1].map do |module_name|
receiver = receiver.const_get(module_name)
end
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
- return namespace.const_get(*const_args)
+ namespace.const_get(*const_args)
else
- raise NameError
+ create_resource_for(resource_name)
end
end
# Tries to find a resource for a given name; if it fails, then the resource is created
def find_or_create_resource_for(name)
resource_name = name.to_s.camelize
- ancestors = self.class.name.split("::")
- if ancestors.size > 1
- find_resource_in_modules(resource_name, ancestors)
- else
- self.class.const_get(resource_name)
- end
- rescue NameError
+
const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
if self.class.const_defined?(*const_args)
- resource = self.class.const_get(*const_args)
+ self.class.const_get(*const_args)
else
- resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
+ ancestors = self.class.name.split("::")
+ if ancestors.size > 1
+ find_or_create_resource_in_modules(resource_name, ancestors)
+ else
+ if Object.const_defined?(*const_args)
+ Object.const_get(*const_args)
+ else
+ create_resource_for(resource_name)
+ end
+ end
end
+ end
+
+ # Create and return a class definition for a resource inside the current resource
+ def create_resource_for(resource_name)
+ resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
resource.prefix = self.class.prefix
resource.site = self.class.site
resource
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index e085a05f6d..3bfd536b29 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -9,8 +9,8 @@ module ActiveResource
# requests.
#
# To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
- # method with an attached block. The block declares a set of URIs with expected input, and the output
- # each request should return. The passed in block has any number of entries in the following generalized
+ # method with an attached block. The block declares a set of URIs with expected input, and the output
+ # each request should return. The passed in block has any number of entries in the following generalized
# format:
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
@@ -29,7 +29,7 @@ module ActiveResource
# <tt>request_headers</tt> listed above.
#
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
- # +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
+ # +path+ and <tt>request_headers</tt>. If no match is found an +InvalidRequestError+ exception
# will be raised showing you what request it could not find a response for and also what requests and response
# pairs have been recorded so you can create a new mock for that request.
#
@@ -80,7 +80,7 @@ module ActiveResource
class << self
- # Returns an array of all request objects that have been sent to the mock. You can use this to check
+ # Returns an array of all request objects that have been sent to the mock. You can use this to check
# if your model actually sent an HTTP request.
#
# ==== Example
@@ -105,7 +105,7 @@ module ActiveResource
end
# Returns the list of requests and their mocked responses. Look up a
- # response for a request using responses.assoc(request).
+ # response for a request using <tt>responses.assoc(request)</tt>.
def responses
@@responses ||= []
end
@@ -299,6 +299,8 @@ module ActiveResource
end
end
+ # Returns true if code is 2xx,
+ # false otherwise.
def success?
code.in?(200..299)
end
@@ -311,6 +313,8 @@ module ActiveResource
headers[key] = value
end
+ # Returns true if the other is a Response with an equal body, equal message
+ # and equal headers. Otherwise it returns false.
def ==(other)
if (other.is_a?(Response))
other.body == body && other.message == message && other.headers == headers
diff --git a/activeresource/lib/active_resource/observing.rb b/activeresource/lib/active_resource/observing.rb
index 3c74d49c80..1bfceb8dc8 100644
--- a/activeresource/lib/active_resource/observing.rb
+++ b/activeresource/lib/active_resource/observing.rb
@@ -5,6 +5,14 @@ module ActiveResource
included do
%w( create save update destroy ).each do |method|
+ # def create_with_notifications(*args, &block)
+ # notify_observers(:before_create)
+ # if result = create_without_notifications(*args, &block)
+ # notify_observers(:after_create)
+ # end
+ # result
+ # end
+ # alias_method_chain(create, :notifications)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{method}_with_notifications(*args, &block)
notify_observers(:before_#{method})
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index a373e53f11..ca265d053e 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -8,9 +8,9 @@ module ActiveResource
# Active Resource validation is reported to and from this object, which is used by Base#save
# to determine whether the object in a valid state to be saved. See usage example in Validations.
class Errors < ActiveModel::Errors
- # Grabs errors from an array of messages (like ActiveRecord::Validations)
+ # Grabs errors from an array of messages (like ActiveRecord::Validations).
# The second parameter directs the errors cache to be cleared (default)
- # or not (by passing true)
+ # or not (by passing true).
def from_array(messages, save_cache = false)
clear unless save_cache
humanized_attributes = Hash[@base.attributes.keys.map { |attr_name| [attr_name.humanize, attr_name] }]
@@ -73,7 +73,7 @@ module ActiveResource
# clear the remote validations so they don't interfere with the local
# ones. Otherwise we get an endless loop and can never change the
- # fields so as to make the resource valid
+ # fields so as to make the resource valid.
@remote_errors = nil
if perform_validation && valid? || !perform_validation
save_without_validation
@@ -84,7 +84,7 @@ module ActiveResource
rescue ResourceInvalid => error
# cache the remote errors because every call to <tt>valid?</tt> clears
# all errors. We must keep a copy to add these back after local
- # validations
+ # validations.
@remote_errors = error
load_remote_errors(@remote_errors, true)
false
@@ -92,7 +92,7 @@ module ActiveResource
# Loads the set of remote errors into the object's Errors based on the
- # content-type of the error-block received
+ # content-type of the error-block received.
def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
case self.class.format
when ActiveResource::Formats[:xml]
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
index 82dcb5d575..f26e2312b9 100644
--- a/activeresource/lib/active_resource/version.rb
+++ b/activeresource/lib/active_resource/version.rb
@@ -3,7 +3,7 @@ module ActiveResource
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 37a74a9e62..c5b5b57dec 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -17,5 +17,5 @@ Gem::Specification.new do |s|
s.files = Dir['CHANGELOG', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
- s.add_dependency('multi_json', '~> 1.0.0')
+ s.add_dependency('multi_json', '~> 1.0')
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 8c56a21ef7..a94446acde 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -2,6 +2,7 @@ require 'active_support/concern'
require 'active_support/ordered_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Configurable provides a <tt>config</tt> method to store and retrieve
@@ -51,14 +52,16 @@ module ActiveSupport
# user.allowed_access # => true
#
def config_accessor(*names)
+ options = names.extract_options!
+
names.each do |name|
- code, line = <<-RUBY, __LINE__ + 1
- def #{name}; config.#{name}; end
- def #{name}=(value); config.#{name} = value; end
- RUBY
+ reader, line = "def #{name}; config.#{name}; end", __LINE__
+ writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
- singleton_class.class_eval code, __FILE__, line
- class_eval code, __FILE__, line
+ singleton_class.class_eval reader, __FILE__, line
+ singleton_class.class_eval writer, __FILE__, line
+ class_eval reader, __FILE__, line unless options[:instance_reader] == false
+ class_eval writer, __FILE__, line unless options[:instance_writer] == false
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 5414b3a18f..7baba75ad3 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -84,7 +84,7 @@ class Class
val
end
- remove_method :#{name} if method_defined?(:#{name})
+ remove_possible_method :#{name}
def #{name}
defined?(@#{name}) ? @#{name} : self.class.#{name}
end
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index ced08b8783..5d7e241d1a 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -5,10 +5,8 @@ module ActiveSupport
# Outputs a deprecation warning to the output configured by <tt>ActiveSupport::Deprecation.behavior</tt>
#
- # Example:
- #
# ActiveSupport::Deprecation.warn("something broke!")
- # #=> "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
+ # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)"
def warn(message = nil, callstack = caller)
return if silenced
deprecation_message(callstack, message).tap do |m|
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index adc34f3286..a9aa5464e9 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module Notifications
- # This is a default queue implementation that ships with Notifications. It
- # just pushes events to all registered log subscribers.
+ # This is a default queue implementation that ships with Notifications.
+ # It just pushes events to all registered log subscribers.
class Fanout
def initialize
@subscribers = []
@@ -33,7 +33,7 @@ module ActiveSupport
listeners_for(name).any?
end
- # This is a sync queue, so there is not waiting.
+ # This is a sync queue, so there is no waiting.
def wait
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index a4e0361a32..05da50e150 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -29,22 +29,33 @@ module ActiveSupport
# post :create, :article => {...}
# end
#
+ # A lambda or a list of lambdas can be passed in and evaluated:
+ #
+ # assert_difference lambda { Article.count }, 2 do
+ # post :create, :article => {...}
+ # end
+ #
+ # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
+ # post :create, :article => {...}
+ # end
+ #
# A error message can be specified.
#
# assert_difference 'Article.count', -1, "An Article should be destroyed" do
# post :delete, :id => ...
# end
def assert_difference(expression, difference = 1, message = nil, &block)
- b = block.send(:binding)
- exps = Array.wrap(expression)
- before = exps.map { |e| eval(e, b) }
+ exps = Array.wrap(expression).map { |e|
+ e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
+ }
+ before = exps.map { |e| e.call }
yield
exps.each_with_index do |e, i|
- error = "#{e.inspect} didn't change by #{difference}"
- error = "#{message}.\n#{error}" if message
- assert_equal(before[i] + difference, eval(e, b), error)
+ error = "#{e.inspect} didn't change by #{difference}"
+ error = "#{message}.\n#{error}" if message
+ assert_equal(before[i] + difference, e.call, error)
end
end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 8c91a061fb..7cd9bfa947 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -3,12 +3,100 @@ begin
require 'fileutils'
require 'rails/version'
+ require 'active_support/concern'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/string/inflections'
module ActiveSupport
module Testing
module Performance
+ extend ActiveSupport::Concern
+
+ included do
+ superclass_delegating_accessor :profile_options
+ self.profile_options = DEFAULTS
+
+ if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
+ include ForMiniTest
+ else
+ include ForClassicTestUnit
+ end
+ end
+
+ module ForMiniTest
+ def run(runner)
+ @runner = runner
+
+ run_warmup
+ if profile_options && metrics = profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
+ end
+ end
+ end
+ end
+
+ def run_test(metric, mode)
+ result = '.'
+ begin
+ run_callbacks :setup
+ setup
+ metric.send(mode) { __send__ method_name }
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
+ ensure
+ begin
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
+ end
+ end
+ result
+ end
+ end
+
+ module ForClassicTestUnit
+ def run(result)
+ return if method_name =~ /^default_test$/
+
+ yield(self.class::STARTED, name)
+ @_result = result
+
+ run_warmup
+ if profile_options && metrics = profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
+ result.add_run
+ end
+ end
+ end
+
+ yield(self.class::FINISHED, name)
+ end
+
+ def run_test(metric, mode)
+ run_callbacks :setup
+ setup
+ metric.send(mode) { __send__ @method_name }
+ rescue ::Test::Unit::AssertionFailedError => e
+ add_failure(e.message, e.backtrace)
+ rescue StandardError, ScriptError => e
+ add_error(e)
+ ensure
+ begin
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
+ rescue ::Test::Unit::AssertionFailedError => e
+ add_failure(e.message, e.backtrace)
+ rescue StandardError, ScriptError => e
+ add_error(e)
+ end
+ end
+ end
+
DEFAULTS =
if benchmark = ARGV.include?('--benchmark') # HAX for rake test
{ :benchmark => true,
@@ -24,53 +112,10 @@ begin
:output => 'tmp/performance' }
end.freeze
- def self.included(base)
- base.superclass_delegating_accessor :profile_options
- base.profile_options = DEFAULTS
- end
-
def full_test_name
"#{self.class.name}##{method_name}"
end
- def run(result)
- return if method_name =~ /^default_test$/
-
- yield(self.class::STARTED, name)
- @_result = result
-
- run_warmup
- if profile_options && metrics = profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- result.add_run
- end
- end
- end
-
- yield(self.class::FINISHED, name)
- end
-
- def run_test(metric, mode)
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ @method_name }
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- end
- end
-
protected
def run_warmup
GC.start
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 690fc7f0fc..c2cf39e391 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -3,7 +3,7 @@ module ActiveSupport
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb
index bcedbfd57a..577db5018e 100644
--- a/activesupport/lib/active_support/whiny_nil.rb
+++ b/activesupport/lib/active_support/whiny_nil.rb
@@ -41,7 +41,7 @@ class NilClass
end
private
- def method_missing(method, *args, &block)
+ def method_missing(method, *args)
if klass = METHOD_CLASS_MAP[method]
raise_nil_warning_for klass, method, caller
else
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 2b28e61815..c6d8191298 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -5,6 +5,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
class Parent
include ActiveSupport::Configurable
config_accessor :foo
+ config_accessor :bar, :instance_reader => false, :instance_writer => false
end
class Child < Parent
@@ -36,6 +37,12 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
assert_equal :bar, Parent.config.foo
end
+ test "configuration accessors is not available on instance" do
+ instance = Parent.new
+ assert !instance.respond_to?(:bar)
+ assert !instance.respond_to?(:bar=)
+ end
+
test "configuration hash is available on instance" do
instance = Parent.new
assert_equal :bar, instance.config.foo
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index a4f0d31971..0def5bcf6c 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,15 @@
*Rails 3.1.0 (unreleased)*
+* The -j option of the application generator accepts an arbitrary string. If passed "foo",
+the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest
+requires "foo" and "foo_ujs". As of this writing "prototype-rails" and "jquery-rails"
+exist and provide those files via the asset pipeline. Default is "jquery". [fxn]
+
+* jQuery is no longer vendored, it is provided from now on by the jquery-rails gem. [fxn]
+
+* Prototype and Scriptaculous are no longer vendored, they are provided from now on
+by the prototype-rails gem. [fxn]
+
* The scaffold controller will now produce SCSS file if Sass is available [Prem Sichanugrist]
* The controller and resource generators will now automatically produce asset stubs (this can be turned off with --skip-assets). These stubs will use Coffee and Sass, if those libraries are available. [DHH]
diff --git a/railties/Rakefile b/railties/Rakefile
index 26fa0bf6a5..827b2ba0cd 100755
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -45,11 +45,6 @@ task :generate_guides do
ruby "guides/rails_guides.rb"
end
-task :update_ujs do
- system "curl https://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js"
- system "curl https://github.com/rails/jquery-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js"
-end
-
# Validate guides -------------------------------------------------------------------------
desc 'Validate guides, use ONLY=foo to process just "foo.html"'
task :validate_guides do
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index f8b586c151..3a1a4ee66e 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -488,45 +488,6 @@ end
Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method +filter+ which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same +filter+ method, which will get run in the same way. The method must +yield+ to execute the action. Alternatively, it can have both a +before+ and an +after+ method that are run before and after the action.
-h3. Verification
-
-Verifications make sure certain criteria are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the +params+, +session+ or +flash+ hashes or that a certain HTTP method was used or that the request was made using +XMLHttpRequest+ (Ajax). The default action taken when these criteria are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response.
-
-Here's an example of using verification to make sure the user supplies a username and a password in order to log in:
-
-<ruby>
-class LoginsController < ApplicationController
- verify :params => [:username, :password],
- :render => {:action => "new"},
- :add_flash => {
- :error => "Username and password required to log in"
- }
-
- def create
- @user = User.authenticate(params[:username], params[:password])
- if @user
- flash[:notice] = "You're logged in"
- redirect_to root_url
- else
- render :action => "new"
- end
- end
-end
-</ruby>
-
-Now the +create+ action won't run unless the "username" and "password" parameters are present, and if they're not, an error message will be added to the flash and the +new+ action will be rendered. But there's something rather important missing from the verification above: It will be used for *every* action in LoginsController, which is not what we want. You can limit which actions it will be used for with the +:only+ and +:except+ options just like a filter:
-
-<ruby>
-class LoginsController < ApplicationController
- verify :params => [:username, :password],
- :render => {:action => "new"},
- :add_flash => {
- :error => "Username and password required to log in"
- },
- :only => :create # Run only for the "create" action
-end
-</ruby>
-
h3. Request Forgery Protection
Cross-site request forgery is a type of attack in which a site tricks a user into making requests on another site, possibly adding, modifying or deleting data on that site without the user's knowledge or permission.
diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile
index 56da360972..a6ff8f877d 100644
--- a/railties/guides/source/action_mailer_basics.textile
+++ b/railties/guides/source/action_mailer_basics.textile
@@ -120,7 +120,7 @@ Now that we have a user model to play with, we will just edit the +app/controlle
<ruby>
class UsersController < ApplicationController
# POST /users
- # POST /users.xml
+ # POST /users.json
def create
@user = User.new(params[:user])
@@ -130,10 +130,10 @@ class UsersController < ApplicationController
UserMailer.welcome_email(@user).deliver
format.html { redirect_to(@user, :notice => 'User was successfully created.') }
- format.xml { render :xml => @user, :status => :created, :location => @user }
+ format.json { render :json => @user, :status => :created, :location => @user }
else
format.html { render :action => "new" }
- format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ format.json { render :json => @user.errors, :status => :unprocessable_entity }
end
end
end
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index 172932fdab..2b2c197f46 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -526,7 +526,7 @@ javascript_include_tag "common" # =>
<script type="text/javascript" src="/javascripts/common.js"></script>
</ruby>
-To include the Prototype and Scriptaculous JavaScript libraries in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well.
+If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well.
<ruby>
javascript_include_tag :defaults
@@ -1301,8 +1301,6 @@ h4. JavaScriptHelper
Provides functionality for working with JavaScript in your views.
-Rails includes the Prototype JavaScript framework and the Scriptaculous JavaScript controls and visual effects library. If you wish to use these libraries and their helpers, make sure +&lt;%= javascript_include_tag :defaults, :cache => true %&gt;+ is in the HEAD section of your page. This function will include the necessary JavaScript files Rails generated in the +public/javascripts+ directory.
-
h5. button_to_function
Returns a button that'll trigger a JavaScript function using the onclick handler. Examples:
@@ -1402,16 +1400,6 @@ number_with_precision(111.2345) # => 111.235
number_with_precision(111.2345, 2) # => 111.23
</ruby>
-h4. PrototypeHelper
-
-Prototype is a JavaScript library that provides DOM manipulation, Ajax functionality, and more traditional object-oriented facilities for JavaScript. This module provides a set of helpers to make it more convenient to call functions from Prototype using Rails, including functionality to call remote Rails methods (that is, making a background request to a Rails action) using Ajax.
-
-To be able to use these helpers, you must first include the Prototype JavaScript framework in the HEAD of the pages with Prototype functions.
-
-<ruby>
-javascript_include_tag 'prototype'
-</ruby>
-
h5. evaluate_remote_response
Returns +eval(request.responseText)+ which is the JavaScript function that form_remote_tag can call in +:complete+ to evaluate a multiple update return document using +update_element_function+ calls.
@@ -1508,171 +1496,6 @@ would generate:
return false;" type="button" value="Create" />
</html>
-h5. update_page
-
-Yields a JavaScriptGenerator and returns the generated JavaScript code. Use this to update multiple elements on a page in an Ajax response.
-
-<ruby>
-update_page do |page|
- page.hide 'spinner'
-end
-</ruby>
-
-h5. update_page_tag
-
-Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERB template.
-
-h4. PrototypeHelper::JavaScriptGenerator::GeneratorMethods
-
-JavaScriptGenerator generates blocks of JavaScript code that allow you to change the content and presentation of multiple DOM elements. Use this in your Ajax response bodies, either in a +script+ tag or as plain JavaScript sent with a Content-type of "text/javascript".
-
-h5(#push). <<
-
-Writes raw JavaScript to the page.
-
-<ruby>
-page << "alert('JavaScript with Prototype.');"
-</ruby>
-
-h5(#at). []
-
-Returns a element reference by finding it through it's id in the DOM.
-
-<ruby>
-page['blank_slate'].show # => $('blank_slate').show();
-</ruby>
-
-h5. alert
-
-Displays an alert dialog with the given message.
-
-<ruby>
-page.alert('This message is from Rails!')
-</ruby>
-
-h5. assign
-
-Assigns the JavaScript variable the given value.
-
-<ruby>
-page.assign 'tabulated_total', @total_from_cart
-</ruby>
-
-h5. call
-
-Calls the JavaScript function, optionally with the given arguments.
-
-<ruby>
-page.call 'Element.replace', 'my_element', "My content to replace with."
-</ruby>
-
-h5. delay
-
-Executes the content of the block after a delay of the number of seconds provided.
-
-<ruby>
-page.delay(20) do
- page.visual_effect :fade, 'notice'
-end
-</ruby>
-
-h5. draggable
-
-Creates a script.aculo.us draggable element. See ActionView::Helpers::ScriptaculousHelper for more information.
-
-h5. drop_receiving
-
-Creates a script.aculo.us drop receiving element. See ActionView::Helpers::ScriptaculousHelper for more information.
-
-h5. hide
-
-Hides the visible DOM elements with the given ids.
-
-<ruby>
-page.hide 'person_29', 'person_9', 'person_0'
-</ruby>
-
-h5. insert_html
-
-Inserts HTML at the specified position relative to the DOM element identified by the given id.
-
-<ruby>
-page.insert_html :bottom, 'my_list', '<li>Last item</li>'
-</ruby>
-
-h5. literal
-
-Returns an object whose to_json evaluates to the code provided. Use this to pass a literal JavaScript expression as an argument to another JavaScriptGenerator method.
-
-h5. redirect_to
-
-Redirects the browser to the given location using JavaScript, in the same form as +url_for+.
-
-<ruby>
-page.redirect_to(:controller => 'accounts', :action => 'new')
-</ruby>
-
-h5. remove
-
-Removes the DOM elements with the given ids from the page.
-
-<ruby>
-page.remove 'person_23', 'person_9', 'person_2'
-</ruby>
-
-h5. replace
-
-Replaces the "outer HTML" (i.e., the entire element, not just its contents) of the DOM element with the given id.
-
-<ruby>
-page.replace 'person-45', :partial => 'person', :object => @person
-</ruby>
-
-h5. replace_html
-
-Replaces the inner HTML of the DOM element with the given id.
-
-<ruby>
-page.replace_html 'person-45', :partial => 'person', :object => @person
-</ruby>
-
-h5(#prototype-select). select
-
-Returns a collection reference by finding it through a CSS pattern in the DOM.
-
-<ruby>
-page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
-</ruby>
-
-h5. show
-
-Shows hidden DOM elements with the given ids.
-
-<ruby>
-page.show 'person_6', 'person_13', 'person_223'
-</ruby>
-
-h5. sortable
-
-Creates a script.aculo.us sortable element. Useful to recreate sortable elements after items get added or deleted. See ActionView::Helpers::ScriptaculousHelper for more information.
-
-h5. toggle
-
-Toggles the visibility of the DOM elements with the given ids. Example:
-
-<ruby>
-page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements
-page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
-</ruby>
-
-h5. visual_effect
-
-Starts a script.aculo.us visual effect. See ActionView::Helpers::ScriptaculousHelper for more information.
-
-
-TODO start from RecordIdentificationHelper
-
-
h3. Localized Views
Action View has the ability render different templates depending on the current locale.
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index 2f0a51e868..579a323d57 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -56,6 +56,7 @@ The methods are:
* +select+
* +group+
* +order+
+* +reorder+
* +limit+
* +offset+
* +joins+
@@ -495,9 +496,9 @@ This will return single order objects for each day, but only for the last month.
h3. Overriding Conditions
-You can specify certain conditions to be excepted by using the +except+ method.
+h4. +except+
-For example:
+You can specify certain conditions to be excepted by using the +except+ method. For example:
<ruby>
Post.where('id > 10').limit(20).order('id asc').except(:order)
@@ -509,9 +510,9 @@ The SQL that would be executed:
SELECT * FROM posts WHERE id > 10 LIMIT 20
</sql>
-You can also override conditions using the +only+ method.
+h4. +only+
-For example:
+You can also override conditions using the +only+ method. For example:
<ruby>
Post.where('id > 10').limit(20).order('id desc').only(:order, :where)
@@ -523,6 +524,32 @@ The SQL that would be executed:
SELECT * FROM posts WHERE id > 10 ORDER BY id DESC
</sql>
+h4. +reorder+
+
+The +reorder+ method overrides the default scope order. For example:
+
+<ruby>
+class Post < ActiveRecord::Base
+ ..
+ ..
+ has_many :comments, :order => 'posted_at DESC'
+end
+
+Post.find(10).comments.reorder('name')
+</ruby>
+
+The SQL that would be executed:
+
+<sql>
+SELECT * FROM posts WHERE id = 10 ORDER BY name
+</sql>
+
+In case the +reorder+ clause is not used, the SQL executed would be:
+
+<sql>
+SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC
+</sql>
+
h3. Readonly Objects
Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception.
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index f89c83e4cd..f2170e120b 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -449,9 +449,9 @@ The predicate +in?+ tests if an object is included in another object. An +Argume
Examples of +in?+:
<ruby>
- 1.in?([1,2]) # => true
- "lo".in?("hello") # => true
- 25.in?(30..50) # => false
+1.in?([1,2]) # => true
+"lo".in?("hello") # => true
+25.in?(30..50) # => false
</ruby>
NOTE: Defined in +active_support/core_ext/object/inclusion.rb+.
@@ -541,9 +541,9 @@ The default value can be also specified with a block, which is called in the con
<ruby>
class User
attr_accessor :name, :surname
- attr_accessor_with_default(:full_name) {
- [name, surname].compact.join(" ")
- }
+ attr_accessor_with_default(:full_name) do
+ [name, surname].compact.join(" ")
+ end
end
u = User.new
@@ -1223,7 +1223,7 @@ NOTE: Defined in +active_support/core_ext/string/output_safety.rb+.
h4. +squish+
-The method +String#squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each:
+The method +squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each:
<ruby>
" \n foo\n\r \t bar \n".squish # => "foo bar"
@@ -1996,11 +1996,11 @@ Active Support augments the API of arrays to ease certain ways of accessing them
[].to(7) # => []
</ruby>
-Similarly, +from+ returns the tail from the element at the passed index on:
+Similarly, +from+ returns the tail from the element at the passed index to the end. If the index is greater than the length of the array, it returns an empty array.
<ruby>
%w(a b c d).from(2) # => %w(c d)
-%w(a b c d).from(10) # => nil
+%w(a b c d).from(10) # => []
[].from(0) # => []
</ruby>
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index e5b8c73c43..94dce1b97b 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -229,7 +229,7 @@ The corresponding migration might look like this:
<ruby>
class CreateSuppliers < ActiveRecord::Migration
- def self.up
+ def change
create_table :suppliers do |t|
t.string :name
t.timestamps
@@ -241,11 +241,6 @@ class CreateSuppliers < ActiveRecord::Migration
t.timestamps
end
end
-
- def self.down
- drop_table :accounts
- drop_table :suppliers
- end
end
</ruby>
@@ -314,7 +309,7 @@ If you have an instance of the +Picture+ model, you can get to its parent via +@
<ruby>
class CreatePictures < ActiveRecord::Migration
- def self.up
+ def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
@@ -322,10 +317,6 @@ class CreatePictures < ActiveRecord::Migration
t.timestamps
end
end
-
- def self.down
- drop_table :pictures
- end
end
</ruby>
@@ -333,17 +324,13 @@ This migration can be simplified by using the +t.references+ form:
<ruby>
class CreatePictures < ActiveRecord::Migration
- def self.up
+ def change
create_table :pictures do |t|
t.string :name
t.references :imageable, :polymorphic => true
t.timestamps
end
end
-
- def self.down
- drop_table :pictures
- end
end
</ruby>
@@ -413,17 +400,13 @@ This declaration needs to be backed up by the proper foreign key declaration on
<ruby>
class CreateOrders < ActiveRecord::Migration
- def self.up
+ def change
create_table :orders do |t|
t.datetime :order_date
t.string :order_number
t.integer :customer_id
end
end
-
- def self.down
- drop_table :orders
- end
end
</ruby>
@@ -451,16 +434,12 @@ These need to be backed up by a migration to create the +assemblies_parts+ table
<ruby>
class CreateAssemblyPartJoinTable < ActiveRecord::Migration
- def self.up
+ def change
create_table :assemblies_parts, :id => false do |t|
t.integer :assembly_id
t.integer :part_id
end
end
-
- def self.down
- drop_table :assemblies_parts
- end
end
</ruby>
diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile
index cbc4acfeca..cb09b180a2 100644
--- a/railties/guides/source/contributing_to_ruby_on_rails.textile
+++ b/railties/guides/source/contributing_to_ruby_on_rails.textile
@@ -2,7 +2,7 @@ h2. Contributing to Ruby on Rails
This guide covers ways in which _you_ can become a part of the ongoing development of Ruby on Rails. After reading it, you should be familiar with:
-* Using Lighthouse to report issues
+* Using GitHub to report issues
* Cloning master and running the test suite
* Helping to resolve existing issues
* Contributing to the Ruby on Rails documentation
@@ -14,29 +14,25 @@ endprologue.
h3. Reporting an Issue
-Ruby on Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) Lighthouse account in order to comment on issues or to upload patches.
+Ruby on Rails uses "GitHub Issue Tracking":https://github.com/rails/rails/issues to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to either submit an issue, comment on them or create pull requests.
NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing.
h4. Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk do a search in Lighthouse in case it was already reported. If you find no ticket addressing it you can "add a new one":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new. (See the next section for reporting security issues.)
+If you've found a problem in Ruby on Rails which is not a security risk do a search in GitHub Issues in case it was already reported. If you find no issue addressing it you can "add a new one":https://github.com/rails/rails/issues/new. (See the next section for reporting security issues.)
-At the minimum, your ticket needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
+At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
-You shouldn't assign the bug to a particular core developer unless you know for sure which developer will be handling that issue. The core team periodically reviews issues and assigns developers and milestones to them.
-
-You should set tags for your issue. Use the "bug" tag for a bug report, and add the "patch" tag if you are attaching a patch. Try to find some relevant tags from the existing tag list (which will appear as soon as you start typing in the "Choose some tags" textbox), rather than creating new tags.
-
-Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment.
+Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating a issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment.
h4. Special Treatment for Security Issues
-WARNING: Please do not report security vulnerabilities on public Lighthouse tickets. The "Rails security policy page":http://rubyonrails.org/security details the procedure to follow for security issues.
+WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The "Rails security policy page":http://rubyonrails.org/security details the procedure to follow for security issues.
h4. What About Feature Requests?
-Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed.
+Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed.
h3. Running the Test Suite
@@ -48,7 +44,7 @@ Ruby on Rails uses git for source code control. The "git homepage":http://git-sc
* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
-* "GitHub":https://github.com/guides/home offers links to a variety of git resources.
+* "GitHub":http://help.github.com offers links to a variety of git resources.
* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
h4. Clone the Ruby on Rails Repository
@@ -216,11 +212,11 @@ TIP: You may want to "put your git branch name in your shell prompt":http://qugs
h3. Helping to Resolve Existing Issues
-As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "open tickets":https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets?q=state%3Aopen list in Lighthouse, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
+As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "Everyone's Issues":https://github.com/rails/rails/issues?sort=created&direction=desc&state=open&page=1 list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
h4. Verifying Bug Reports
-For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the ticket saying that you're seeing the same thing.
+For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing.
If something is very vague, can you help squish it down into something specific? Maybe you can provide additional information to help reproduce a bug, or eliminate needless steps that aren't required to help demonstrate the problem.
@@ -230,38 +226,39 @@ Anything you can do to make bug reports more succinct or easier to reproduce is
h4. Testing Patches
-You can also help out by examining patches that have been submitted to Ruby on Rails via Lighthouse. To apply someone's changes you need to first create a dedicated branch:
+You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need to first create a dedicated branch:
<shell>
$ git checkout -b testing_branch
</shell>
-Then you can apply their patch:
+Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to the master branch located at https://github.com/JohnSmith/rails.
<shell>
-$ git am their-patch-file.diff
+$ git remote add JohnSmith git://github.com/JohnSmith/rails.git
+$ git pull JohnSmith master
</shell>
-After applying a patch, test it out! Here are some things to think about:
+After applying their branch, test it out! Here are some things to think about:
-* Does the patch actually work?
+* Does the change actually work?
* Are you happy with the tests? Can you follow what they're testing? Are there any tests missing?
* Does it have proper documentation coverage? Should documentation elsewhere be updated?
* Do you like the implementation? Can you think of a nicer or faster way to implement a part of their change?
-Once you're happy that the patch contains a good change, comment on the Lighthouse ticket indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like:
+Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like:
<blockquote>
I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too.
</blockquote>
-If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the patch. Once three people have approved it, add the "verified" tag. This will bring it to the attention of a core team member who will review the changes looking for the same kinds of things.
+If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request.
h3. Contributing to the Rails Documentation
Ruby on Rails has two main sets of documentation: The guides help you to learn Ruby on Rails, and the API is a reference.
-You can create a ticket in Lighthouse to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":https://github.com/lifo/docrails/tree/master. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
+You can create an issue in GitHub issues to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":https://github.com/lifo/docrails/tree/master. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
When working with documentation, please take into account the "API Documentation Guidelines":api_documentation_guidelines.html and the "Ruby on Rails Guides Guidelines":ruby_on_rails_guides_guidelines.html.
@@ -314,20 +311,14 @@ You should not be the only person who looks at the code before you submit it. Yo
You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches.
-h4. Create a Lighthouse Ticket
-
-Now create a ticket for your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, as well as tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. Write down your ticket number, for you will need it in the following step.
-
h4. Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to git:
<shell>
-$ git commit -a -m "Here is a commit message [#ticket_number state:committed]"
+$ git commit -a -m "Here is a commit message on what I changed in this commit"
</shell>
-NOTE: By adding '[#ticket_number state:committed]' at the end of your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository.
-
h4. Update master
It’s pretty likely that other changes to master have happened while you were working. Go get them:
@@ -346,33 +337,29 @@ $ git rebase master
No conflicts? Tests still pass? Change still seems reasonable to you? Then move on.
-h4. Create a Patch
-
-Now you can create a patch file to share with other developers (and with the core team). Still in your branch, run
+h4. Fork
-<shell>
-$ git commit -a
-$ git format-patch master --stdout > my_new_patch.diff
-</shell>
-
-Open the diff file in your text editor of choice to sanity check the results, and make sure that no unintended changes crept in.
+Navigate to the Rails "GitHub repository":https://github.com/rails/rails and press "Fork" in the upper right hand corner.
-You can also perform an extra check by applying the patch to a different dedicated branch:
+Add the new remote to your local repository on your local machine:
<shell>
-$ git checkout -b testing_branch
-$ git apply --check my_new_patch.diff
+$ git remote add mine https://&lt;your user name&gt;@github.com/&lt;your user name&gt;/rails.git
</shell>
-Please make sure the patch does not introduce whitespace errors:
+Push to your remote:
<shell>
-$ git apply --whitespace=error-all my_new_patch.diff
+$ git push mine master
</shell>
-h4. Attach your Patch to the Lighthouse Ticket
+h4. Issue a Pull Request
+
+Navigate to the Rails repository you just pushed to (e.g. https://github.com/&lt;your user name&gt;/rails) and press "Pull Request" in the upper right hand corner.
+
+Ensure the changesets you introduced are included in the "Commits" tab and that the "Files Changed" incorporate all of your changes.
-Now you need to update the ticket by attaching the patch file you just created.
+Fill in some details about your potential patch including a meaningful title. When finished, press "Send pull request." Rails Core will be notified about your submission.
h4. Get Some Feedback
@@ -390,9 +377,9 @@ All contributions, either via master or docrails, get credit in "Rails Contribut
h3. Changelog
-* April 14, 2001: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com
+* April 29, 2011: Reflect GitHub Issues and Pull Request workflow by "Dan Pickett":http://www.enlightsolutions.com
+* April 14, 2011: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com
* December 28, 2010: Complete revision by "Xavier Noria":credits.html#fxn
* April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy
* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy
-
diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile
index 477ff29dbd..6f028805d6 100644
--- a/railties/guides/source/debugging_rails_applications.textile
+++ b/railties/guides/source/debugging_rails_applications.textile
@@ -279,14 +279,14 @@ This command shows you where you are in the code by printing 10 lines centered a
[1, 10] in /PathToProject/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
- 3 # GET /posts.xml
+ 3 # GET /posts.json
4 def index
5 debugger
=> 6 @posts = Post.all
7
8 respond_to do |format|
9 format.html # index.html.erb
- 10 format.xml { render :xml => @posts }
+ 10 format.json { render :json => @posts }
</shell>
If you repeat the +list+ command, this time using just +l+, the next ten lines of the file will be printed out.
@@ -298,7 +298,7 @@ If you repeat the +list+ command, this time using just +l+, the next ten lines o
12 end
13
14 # GET /posts/1
- 15 # GET /posts/1.xml
+ 15 # GET /posts/1.json
16 def show
17 @post = Post.find(params[:id])
18
@@ -315,14 +315,14 @@ On the other hand, to see the previous ten lines you should type +list-+ (or +l-
[1, 10] in /PathToProject/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
- 3 # GET /posts.xml
+ 3 # GET /posts.json
4 def index
5 debugger
6 @posts = Post.all
7
8 respond_to do |format|
9 format.html # index.html.erb
- 10 format.xml { render :xml => @posts }
+ 10 format.json { render :json => @posts }
</shell>
This way you can move inside the file, being able to see the code above and over the line you added the +debugger+.
@@ -333,14 +333,14 @@ Finally, to see where you are in the code again you can type +list=+
[1, 10] in /PathToProject/posts_controller.rb
1 class PostsController < ApplicationController
2 # GET /posts
- 3 # GET /posts.xml
+ 3 # GET /posts.json
4 def index
5 debugger
=> 6 @posts = Post.all
7
8 respond_to do |format|
9 format.html # index.html.erb
- 10 format.xml { render :xml => @posts }
+ 10 format.json { render :json => @posts }
</shell>
h4. The Context
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 366df9cd84..a826a33120 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -378,7 +378,7 @@ If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember, y
<ruby>
class CreatePosts < ActiveRecord::Migration
- def self.up
+ def change
create_table :posts do |t|
t.string :name
t.string :title
@@ -387,14 +387,10 @@ class CreatePosts < ActiveRecord::Migration
t.timestamps
end
end
-
- def self.down
- drop_table :posts
- end
end
</ruby>
-The above migration creates two methods, +up+, called when you run this migration into the database, and +down+ in case you need to reverse the changes made by this migration at a later date. The +up+ command in this case creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide.
+The above migration creates a method name +change+ which will be called when you run this migration. The action defined in that method is also reversible, which means Rails knows how to reverse the change made by this migration, in case you want to reverse it at later date. By default, when you run this migration it will creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide.
At this point, you can use a rake command to run the migration:
@@ -807,7 +803,7 @@ In addition to the model, Rails has also made a migration to create the correspo
<ruby>
class CreateComments < ActiveRecord::Migration
- def self.up
+ def change
create_table :comments do |t|
t.string :commenter
t.text :body
@@ -818,10 +814,6 @@ class CreateComments < ActiveRecord::Migration
add_index :comments, :post_id
end
-
- def self.down
- drop_table :comments
- end
end
</ruby>
@@ -1458,6 +1450,7 @@ Two very common sources of data that are not UTF-8:
h3. Changelog
+* April 26, 2011: Changed migration code from +up+, +down+ pair to +change+ method "Prem Sichanugrist":"http://sikachu.com"
* April 11, 2011: Changed scaffold_controller generator to create format block for JSON instead of XML "Sebastian Martinez":http://www.wyeworks.com
* August 30, 2010: Minor editing after Rails 3 release by "Joost Baaij":http://www.spacebabies.nl
* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index 620df970dc..d67668df91 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -695,13 +695,13 @@ To include +http://example.com/main.js+:
<%= javascript_include_tag "http://example.com/main.js" %>
</erb>
-The +:defaults+ option loads jQuery by default:
+If the application does not use the asset pipeline, the +:defaults+ option loads jQuery by default:
<erb>
<%= javascript_include_tag :defaults %>
</erb>
-If the application was generated with "-j prototype" <tt>:defaults</tt> loads Prototype and Scriptaculous. And you can in any case override the expansion in <tt>config/application.rb</tt>:
+And you can in any case override the expansion in <tt>config/application.rb</tt>:
<ruby>
config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
@@ -709,7 +709,7 @@ config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js)
When using <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in <tt>public/javascripts</tt> it will be included as well at then end.
-The +:all+ option loads every JavaScript file in +public/javascripts+:
+Also, the +:all+ option loads every JavaScript file in +public/javascripts+:
<erb>
<%= javascript_include_tag :all %>
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index bf5d4d3e4d..27f8a9303e 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -21,7 +21,7 @@ Before I dive into the details of a migration, here are a few examples of the so
<ruby>
class CreateProducts < ActiveRecord::Migration
- def self.up
+ def up
create_table :products do |t|
t.string :name
t.text :description
@@ -30,7 +30,7 @@ class CreateProducts < ActiveRecord::Migration
end
end
- def self.down
+ def down
drop_table :products
end
end
@@ -42,14 +42,14 @@ Migrations are not limited to changing the schema. You can also use them to fix
<ruby>
class AddReceiveNewsletterToUsers < ActiveRecord::Migration
- def self.up
+ def up
change_table :users do |t|
t.boolean :receive_newsletter, :default => false
end
User.update_all ["receive_newsletter = ?", true]
end
- def self.down
+ def down
remove_column :users, :receive_newsletter
end
end
@@ -58,6 +58,21 @@ end
This migration adds a +receive_newsletter+ column to the +users+ table. We want it to default to +false+ for new users, but existing users are considered
to have already opted in, so we use the User model to set the flag to +true+ for existing users.
+Rails 3.1 makes migrations smarter by providing a new <tt>change</tt> method. This method is preferred for writing constructive migrations (adding columns or tables). The migration knows how to migrate your database and reverse it when the migration is rolled back without the need to write a separate +down+ method.
+
+<ruby>
+class CreateProducts < ActiveRecord::Migration
+ def change
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+end
+</ruby>
+
NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations.
h4. Migrations are Classes
@@ -116,7 +131,7 @@ will create a migration that looks like this
<ruby>
class CreateProducts < ActiveRecord::Migration
- def self.up
+ def change
create_table :products do |t|
t.string :name
t.text :description
@@ -124,10 +139,6 @@ class CreateProducts < ActiveRecord::Migration
t.timestamps
end
end
-
- def self.down
- drop_table :products
- end
end
</ruby>
@@ -146,10 +157,7 @@ This will create an empty but appropriately named migration:
<ruby>
class AddPartNumberToProducts < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
+ def change
end
end
</ruby>
@@ -164,13 +172,9 @@ will generate
<ruby>
class AddPartNumberToProducts < ActiveRecord::Migration
- def self.up
+ def change
add_column :products, :part_number, :string
end
-
- def self.down
- remove_column :products, :part_number
- end
end
</ruby>
@@ -184,11 +188,11 @@ generates
<ruby>
class RemovePartNumberFromProducts < ActiveRecord::Migration
- def self.up
+ def up
remove_column :products, :part_number
end
- def self.down
+ def down
add_column :products, :part_number, :string
end
end
@@ -204,20 +208,17 @@ generates
<ruby>
class AddDetailsToProducts < ActiveRecord::Migration
- def self.up
+ def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
-
- def self.down
- remove_column :products, :price
- remove_column :products, :part_number
- end
end
</ruby>
As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit.
+NOTE: The generated migration file for destructive migrations will still be old-style using the +up+ and +down+ methods. This is because Rails doesn't know the original data types defined when you made the original changes.
+
h3. Writing a Migration
Once you have created your migration using one of the generators it's time to get to work!
@@ -337,6 +338,21 @@ If the helpers provided by Active Record aren't enough you can use the +execute+
For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
+h4. Writing Your +change+ Method
+
+The +change+ method removes the need to write both +up+ and +down+ methods in those cases that Rails know how to revert the changes automatically. Currently, the +change+ method supports only these migration definitions:
+
+* +add_column+
+* +add_index+
+* +add_timestamp+
+* +create_table+
+* +remove_timestamps+
+* +rename_column+
+* +rename_index+
+* +rename_table+
+
+If you're going to use other methods, you'll have to write the +up+ and +down+ methods normally.
+
h4. Writing Your +down+ Method
The +down+ method of your migration should revert the transformations done by the +up+ method. In other words the database schema should be unchanged if you do an +up+ followed by a +down+. For example if you create a table in the +up+ method you should drop it in the +down+ method. It is wise to do things in precisely the reverse order to in the +up+ method. For example
@@ -344,7 +360,7 @@ The +down+ method of your migration should revert the transformations done by th
<ruby>
class ExampleMigration < ActiveRecord::Migration
- def self.up
+ def up
create_table :products do |t|
t.references :category
end
@@ -361,7 +377,7 @@ class ExampleMigration < ActiveRecord::Migration
rename_column :users, :email, :email_address
end
- def self.down
+ def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories"
@@ -369,9 +385,8 @@ class ExampleMigration < ActiveRecord::Migration
end
end
</ruby>
-Sometimes your migration will do something which is just plain irreversible, for example it might destroy some data. In cases like those when you can't reverse the migration you can raise +IrreversibleMigration+ from your +down+ method. If someone tries to revert your migration an error message will be
-displayed saying that it can't be done.
+Sometimes your migration will do something which is just plain irreversible, for example it might destroy some data. In cases like those when you can't reverse the migration you can raise +IrreversibleMigration+ from your +down+ method. If someone tries to revert your migration an error message will be displayed saying that it can't be done.
h3. Running Migrations
@@ -449,7 +464,7 @@ For example, this migration
<ruby>
class CreateProducts < ActiveRecord::Migration
- def self.up
+ def change
suppress_messages do
create_table :products do |t|
t.string :name
@@ -465,10 +480,6 @@ class CreateProducts < ActiveRecord::Migration
250
end
end
-
- def self.down
- drop_table :products
- end
end
</ruby>
@@ -499,11 +510,7 @@ class AddPartNumberToProducts < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
- def self.up
- ...
- end
-
- def self.down
+ def change
...
end
end
@@ -519,15 +526,11 @@ class AddPartNumberToProducts < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
- def self.up
+ def change
add_column :product, :part_number, :string
Product.reset_column_information
...
end
-
- def self.down
- ...
- end
end
</ruby>
@@ -590,5 +593,6 @@ Although Active Record does not provide any tools for working directly with such
h3. Changelog
+* April 26, 2011: change generated +up+ and +down+ methods to +change+ method, and describe detail about +change+ method by "Prem Sichanugrist":http://sikachu.com
* July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com
* September 14, 2008: initial version by "Frederick Cheung":credits.html#fcheung
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 43c08165dc..99fdcee68a 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -391,7 +391,7 @@ NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment.
match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
</ruby>
-TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id => /[^\/]<plus>/+ allows anything except a slash.
+TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash.
h4. Static Segments
@@ -660,7 +660,7 @@ end
NOTE: Of course, you can use the more advanced constraints available in non-resourceful routes in this context.
-TIP: By default the +:id+ parameter doesn't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within an +:id+ add a constraint which overrides this - for example +:id => /[^\/]+/+ allows anything except a slash.
+TIP: By default the +:id+ parameter doesn't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within an +:id+ add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash.
h4. Overriding the Named Helpers
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index f87ffdb20d..8c408ec06b 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -211,15 +211,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST (
If your web application is RESTful, you might be used to additional HTTP verbs, such as PUT or DELETE. Most of today‘s web browsers, however do not support them - only GET and POST. Rails uses a hidden +_method+ field to handle this barrier.
-_(highlight)The verify method in a controller can make sure that specific actions may not be used over GET_. Here is an example to verify the use of the transfer action over POST. If the action comes in using any other verb, it redirects to the list action.
-
-<ruby>
-verify :method => :post, :only => [:transfer], :redirect_to => {:action => :list}
-</ruby>
-
-With this precaution, the attack from above will not work, because the browser sends a GET request for images, which will not be accepted by the web application.
-
-But this was only the first step, because _(highlight)POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request.
+_(highlight)POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request.
<html>
<a href="http://www.harmless.com/" onclick="
@@ -441,7 +433,7 @@ params[:user] # => {:name => "ow3ned", :admin => true}
@user.admin # => true
</ruby>
-When assigning attributes in Active Record using +new+, +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
+When assigning attributes in Active Record using +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
<ruby>
@user = User.new
@@ -459,7 +451,19 @@ When assigning attributes in Active Record using +new+, +attributes=+, or +updat
@user.is_admin # => true
</ruby>
-A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of:
+In a similar way, +new+, +create+ and <tt>create!</tt> methods respect mass-assignment security and accepts either +:as+ or +:without_protection+ options. For example:
+
+<ruby>
+@user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin)
+@user.name # => Sebastian
+@user.is_admin # => true
+
+@user = User.create({ :name => 'Sebastian', :is_admin => true }, :without_protection => true)
+@user.name # => Sebastian
+@user.is_admin # => true
+</ruby>
+
+A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of:
<ruby>
config.active_record.whitelist_attributes = true
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index 9b1ef22b97..efa2bbdca6 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -941,7 +941,7 @@ h3. Other Testing Approaches
The built-in +test/unit+ based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including:
* "NullDB":http://avdi.org/projects/nulldb/, a way to speed up testing by avoiding database use.
-* "Factory Girl":https://github.com/thoughtbot/factory_girl/tree/master, as replacement for fixtures.
+* "Factory Girl":https://github.com/thoughtbot/factory_girl/tree/master, a replacement for fixtures.
* "Machinist":https://github.com/notahat/machinist/tree/master, another replacement for fixtures.
* "Shoulda":http://www.thoughtbot.com/projects/shoulda, an extension to +test/unit+ with additional helpers, macros, and assertions.
* "RSpec":http://rspec.info/, a behavior-driven development framework
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 0c3c7737ea..dd01bbab1d 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -112,7 +112,7 @@ module Rails
def load_console(sandbox=false)
initialize_console(sandbox)
- railties.all { |r| r.load_console }
+ railties.all { |r| r.load_console(sandbox) }
super()
self
end
@@ -152,7 +152,7 @@ module Rails
end
if config.serve_static_assets
- middleware.use ::ActionDispatch::Static, paths["public"].first
+ middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
end
middleware.use ::Rack::Lock unless config.allow_concurrency
@@ -196,7 +196,6 @@ module Rails
def initialize_console(sandbox=false)
require "rails/console/app"
- require "rails/console/sandbox" if sandbox
require "rails/console/helpers"
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 9c9d85eed6..0c02e5758e 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -37,6 +37,7 @@ module Rails
)
logger
end
+ at_exit { Rails.logger.flush if Rails.logger.respond_to?(:flush) }
end
# Initialize cache early in the stack so railties can make use of it.
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index f818313955..29b9c27a13 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -9,7 +9,7 @@ module Rails
:dependency_loading, :encoding, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :preload_frameworks,
:reload_plugins, :secret_token, :serve_static_assets,
- :session_options, :time_zone, :whiny_nils
+ :static_cache_control, :session_options, :time_zone, :whiny_nils
attr_writer :log_level
@@ -22,6 +22,7 @@ module Rails
@helpers_paths = []
@dependency_loading = true
@serve_static_assets = true
+ @static_cache_control = nil
@force_ssl = false
@session_store = :cookie_store
@session_options = {}
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index dfd3c654ff..66dbb5d11e 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -34,10 +34,6 @@ module Rails
exit
end
end
-
- if defined?(ActiveRecord)
- ActiveRecord::Base.logger = Logger.new(STDERR)
- end
if options[:sandbox]
puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})"
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 1a91d477ec..ddd08a32ee 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -39,18 +39,12 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
-begin
- if code_or_file.nil?
- $stderr.puts "Run '#{$0} -h' for help."
- exit 1
- elsif File.exist?(code_or_file)
- $0 = code_or_file
- eval(File.read(code_or_file), nil, code_or_file)
- else
- eval(code_or_file)
- end
-ensure
- if defined? Rails
- Rails.logger.flush if Rails.logger.respond_to?(:flush)
- end
+if code_or_file.nil?
+ $stderr.puts "Run '#{$0} -h' for help."
+ exit 1
+elsif File.exist?(code_or_file)
+ $0 = code_or_file
+ eval(File.read(code_or_file), nil, code_or_file)
+else
+ eval(code_or_file)
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index e447209242..505a4ca2bd 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -55,8 +55,9 @@ module Rails
end
def start
+ url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}"
+ puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Call with -d to detach" unless options[:daemonize]
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 6c1064c609..2015a944f0 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -92,17 +92,17 @@ module Rails
# The available paths in an engine are:
#
# class MyEngine < Rails::Engine
- # paths["app"] #=> ["app"]
- # paths["app/controllers"] #=> ["app/controllers"]
- # paths["app/helpers"] #=> ["app/helpers"]
- # paths["app/models"] #=> ["app/models"]
- # paths["app/views"] #=> ["app/views"]
- # paths["lib"] #=> ["lib"]
- # paths["lib/tasks"] #=> ["lib/tasks"]
- # paths["config"] #=> ["config"]
- # paths["config/initializers"] #=> ["config/initializers"]
- # paths["config/locales"] #=> ["config/locales"]
- # paths["config/routes"] #=> ["config/routes.rb"]
+ # paths["app"] # => ["app"]
+ # paths["app/controllers"] # => ["app/controllers"]
+ # paths["app/helpers"] # => ["app/helpers"]
+ # paths["app/models"] # => ["app/models"]
+ # paths["app/views"] # => ["app/views"]
+ # paths["lib"] # => ["lib"]
+ # paths["lib/tasks"] # => ["lib/tasks"]
+ # paths["config"] # => ["config"]
+ # paths["config/initializers"] # => ["config/initializers"]
+ # paths["config/locales"] # => ["config/locales"]
+ # paths["config/routes"] # => ["config/routes.rb"]
# end
#
# Your <tt>Application</tt> class adds a couple more paths to this set. And as in your
@@ -234,14 +234,14 @@ module Rails
# use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and
# form fields for convenience.
#
- # polymorphic_url(MyEngine::Article.new) #=> "articles_path"
+ # polymorphic_url(MyEngine::Article.new) # => "articles_path"
#
# form_for(MyEngine::Article.new) do
- # text_field :title #=> <input type="text" name="article[title]" id="article_title" />
+ # text_field :title # => <input type="text" name="article[title]" id="article_title" />
# end
#
- # Additionally isolated engine will set its name according to namespace, so
- # MyEngine::Engine.engine_name #=> "my_engine". It will also set MyEngine.table_name_prefix
+ # Additionally an isolated engine will set its name according to namespace, so
+ # MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
# to "my_engine_", changing MyEngine::Article model to use my_engine_article table.
#
# == Using Engine's routes outside Engine
@@ -250,7 +250,7 @@ module Rails
# <tt>url_helpers</tt> inside +Application+. When you mount an engine in an application's routes, a special helper is
# created to allow you to do that. Consider such a scenario:
#
- # # APP/config/routes.rb
+ # # config/routes.rb
# MyApplication::Application.routes.draw do
# mount MyEngine::Engine => "/my_engine", :as => "my_engine"
# match "/foo" => "foo#index"
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 9be395e989..85c67af19a 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -52,13 +52,13 @@ module Rails
:integration_tool => nil,
:javascripts => true,
:javascript_engine => nil,
- :orm => nil,
+ :orm => false,
:performance_tool => nil,
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
:stylesheet_engine => nil,
- :test_framework => nil,
+ :test_framework => false,
:template_engine => :erb
},
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 520d2c6a3a..e8709b2ddd 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -12,7 +12,6 @@ module Rails
DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db )
JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql )
DATABASES.concat(JDBC_DATABASES)
- JAVASCRIPTS = %w( jquery prototype )
attr_accessor :rails_template
add_shebang_option!
@@ -38,8 +37,8 @@ module Rails
class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3",
:desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :javascript, :type => :string, :aliases => "-j", :default => "jquery",
- :desc => "Preconfigure for selected JavaScript library (options: #{JAVASCRIPTS.join('/')})"
+ class_option :javascript, :type => :string, :aliases => '-j', :default => 'jquery',
+ :desc => 'Preconfigure for selected JavaScript library'
class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false,
:desc => "Skip JavaScript files"
@@ -118,31 +117,25 @@ module Rails
end
def database_gemfile_entry
- entry = options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
- if options[:database] == 'mysql'
- if options.dev? || options.edge?
- entry += ", :git => 'git://github.com/brianmario/mysql2.git'"
- else
- entry += "\n# gem 'mysql2', :git => 'git://github.com/brianmario/mysql2.git'"
- end
- end
- entry + "\n"
+ options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n"
+ end
+
+ def include_all_railties?
+ !options[:skip_active_record] && !options[:skip_test_unit]
+ end
+
+ def comment_if(value)
+ options[value] ? '#' : ''
end
def rails_gemfile_entry
if options.dev?
<<-GEMFILE.strip_heredoc
gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'arel', :git => 'git://github.com/rails/arel.git'
- gem 'rack', :git => 'git://github.com/rack/rack.git'
- gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
gem 'rails', :git => 'git://github.com/rails/rails.git'
- gem 'arel', :git => 'git://github.com/rails/arel.git'
- gem 'rack', :git => 'git://github.com/rack/rack.git'
- gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
else
<<-GEMFILE.strip_heredoc
@@ -150,9 +143,6 @@ module Rails
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
- # gem 'arel', :git => 'git://github.com/rails/arel.git'
- # gem 'rack', :git => 'git://github.com/rack/rack.git'
- # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
end
end
@@ -170,7 +160,7 @@ module Rails
else options[:database]
end
end
-
+
def gem_for_ruby_debugger
if RUBY_VERSION < "1.9.2"
"gem 'ruby-debug'"
@@ -178,7 +168,7 @@ module Rails
"gem 'ruby-debug19', :require => 'ruby-debug'"
end
end
-
+
def gem_for_turn
unless RUBY_VERSION < "1.9.2"
<<-GEMFILE.strip_heredoc
@@ -190,6 +180,10 @@ module Rails
end
end
+ def gem_for_javascript
+ "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript]
+ end
+
def bundle_if_dev_or_edge
bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
run "#{bundle_command} install" if dev_or_edge?
@@ -203,7 +197,7 @@ module Rails
empty_directory(destination, config)
git_keep(destination)
end
-
+
def git_keep(destination)
create_file("#{destination}/.gitkeep") unless options[:skip_git]
end
@@ -219,4 +213,4 @@ module Rails
end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index bc55efa261..d79f76c799 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -111,26 +111,10 @@ module Rails
end
def vendor
- vendor_javascripts
vendor_stylesheets
vendor_plugins
end
- def vendor_javascripts
- if options[:skip_javascript]
- empty_directory_with_gitkeep "vendor/assets/javascripts"
- else
- copy_file "vendor/assets/javascripts/#{options[:javascript]}.js"
- copy_file "vendor/assets/javascripts/#{options[:javascript]}_ujs.js"
-
- if options[:javascript] == "prototype"
- copy_file "vendor/assets/javascripts/controls.js"
- copy_file "vendor/assets/javascripts/dragdrop.js"
- copy_file "vendor/assets/javascripts/effects.js"
- end
- end
- end
-
def vendor_stylesheets
empty_directory_with_gitkeep "vendor/assets/stylesheets"
end
@@ -162,10 +146,6 @@ module Rails
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
-
- if !options[:skip_javascript] && !JAVASCRIPTS.include?(options[:javascript])
- raise Error, "Invalid value for --javascript option. Supported for preconfiguration are: #{JAVASCRIPTS.join(", ")}."
- end
end
public_task :create_root
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 8ad64e38ed..a69efdf29d 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -4,12 +4,15 @@ source 'http://rubygems.org'
<%= database_gemfile_entry -%>
+<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) && JRUBY_VERSION < "1.6" -%>
# Asset template engines
<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
gem 'sass'
gem 'coffee-script'
gem 'uglifier'
+<%= gem_for_javascript %>
+
# Use unicorn as the web server
# gem 'unicorn'
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index fb5e91caf4..612c614f2e 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -1,11 +1,8 @@
// FIXME: Tell people that this is a manifest file, real code should go into discrete files
// FIXME: Tell people how Sprockets and CoffeeScript works
//
+<% unless options[:skip_javascript] -%>
//= require <%= options[:javascript] %>
//= require <%= options[:javascript] %>_ujs
-<% if options[:javascript] == "prototype" %>
-//= require controls
-//= require dragdrop
-//= require effects
<% end -%>
//= require_tree .
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index ccfff11a5d..f4b082ccc0 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -1,4 +1,5 @@
/*
* FIXME: Introduce SCSS & Sprockets
+ *= require_self
*= require_tree .
*/ \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index ff8e6e5f3e..e1946807b0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,14 +1,14 @@
require File.expand_path('../boot', __FILE__)
-<% unless options[:skip_active_record] -%>
+<% if include_all_railties? -%>
require 'rails/all'
<% else -%>
# Pick the frameworks you want:
-# require "active_record/railtie"
+<%= comment_if :skip_active_record %> require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
-require "rails/test_unit/railtie"
+<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie"
<% end -%>
# If you have a Gemfile, require the gems listed there, including any gems
@@ -39,17 +39,15 @@ module <%= app_const_base %>
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
+ # Please note that JavaScript expansions are *ignored altogether* if the asset
+ # pipeline is enabled (see config.assets.enabled below). Put your defaults in
+ # app/assets/javascripts/application.js in that case.
+ #
# JavaScript files you want as :defaults (application.js is always included).
<% if options[:skip_javascript] -%>
- config.action_view.javascript_expansions[:defaults] = %w()
-<% elsif options[:javascript] == 'prototype' -%>
- config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails)
+ # config.action_view.javascript_expansions[:defaults] = %w()
<% else -%>
- # config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails)
-<% end -%>
-
-<% if options[:skip_test_unit] -%>
- config.generators.test_framework = false
+ # config.action_view.javascript_expansions[:defaults] = %w(prototype prototype_ujs)
<% end -%>
# Configure the default encoding used in templates for Ruby 1.9.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index d8d1e55157..8d11377211 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -7,7 +7,11 @@
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
- # Log error messages when you accidentally call methods on nil.
+ # Configure static asset server for tests with Cache-Control for performance
+ config.serve_static_assets = true
+ config.static_cache_control = "public, max-age=3600"
+
+ # Log error messages when you accidentally call methods on nil
config.whiny_nils = true
# Show full error reports and disable caching
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
new file mode 100644
index 0000000000..32ffbee7a1
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -0,0 +1,12 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains the settings for ActionController::ParametersWrapper
+# which will be enabled by default in the upcoming version of Ruby on Rails.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActionController::Base.wrap_parameters :format => [:json]
+
+# Disable root element in JSON by default.
+if defined?(ActiveRecord)
+ ActiveRecord::Base.include_root_in_json = false
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js
deleted file mode 100644
index 7392fb664c..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js
+++ /dev/null
@@ -1,965 +0,0 @@
-// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
-
-// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
-// Contributors:
-// Richard Livsey
-// Rahul Bhargava
-// Rob Wills
-//
-// script.aculo.us is freely distributable under the terms of an MIT-style license.
-// For details, see the script.aculo.us web site: http://script.aculo.us/
-
-// 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.getToken(), 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: [',', '\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.
-
-if(typeof Effect == 'undefined')
- throw("controls.js requires including script.aculo.us' effects.js library");
-
-var Autocompleter = { };
-Autocompleter.Base = Class.create({
- baseInitialize: function(element, update, options) {
- element = $(element);
- this.element = element;
- this.update = $(update);
- this.hasFocus = false;
- this.changed = false;
- this.active = false;
- this.index = 0;
- this.entryCount = 0;
- this.oldElementValue = this.element.value;
-
- if(this.setOptions)
- this.setOptions(options);
- else
- this.options = options || { };
-
- this.options.paramName = this.options.paramName || this.element.name;
- this.options.tokens = this.options.tokens || [];
- this.options.frequency = this.options.frequency || 0.4;
- this.options.minChars = this.options.minChars || 1;
- this.options.onShow = this.options.onShow ||
- function(element, update){
- if(!update.style.position || update.style.position=='absolute') {
- update.style.position = 'absolute';
- Position.clone(element, update, {
- setHeight: false,
- offsetTop: element.offsetHeight
- });
- }
- Effect.Appear(update,{duration:0.15});
- };
- this.options.onHide = this.options.onHide ||
- function(element, update){ new Effect.Fade(update,{duration:0.15}) };
-
- if(typeof(this.options.tokens) == 'string')
- this.options.tokens = new Array(this.options.tokens);
- // Force carriage returns as token delimiters anyway
- if (!this.options.tokens.include('\n'))
- this.options.tokens.push('\n');
-
- this.observer = null;
-
- this.element.setAttribute('autocomplete','off');
-
- Element.hide(this.update);
-
- Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
- Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
- },
-
- show: function() {
- if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
- if(!this.iefix &&
- (Prototype.Browser.IE) &&
- (Element.getStyle(this.update, 'position')=='absolute')) {
- new Insertion.After(this.update,
- '<iframe id="' + this.update.id + '_iefix" '+
- 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
- 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
- this.iefix = $(this.update.id+'_iefix');
- }
- if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
- },
-
- fixIEOverlapping: function() {
- Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
- this.iefix.style.zIndex = 1;
- this.update.style.zIndex = 2;
- Element.show(this.iefix);
- },
-
- hide: function() {
- this.stopIndicator();
- if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
- if(this.iefix) Element.hide(this.iefix);
- },
-
- startIndicator: function() {
- if(this.options.indicator) Element.show(this.options.indicator);
- },
-
- stopIndicator: function() {
- if(this.options.indicator) Element.hide(this.options.indicator);
- },
-
- onKeyPress: function(event) {
- if(this.active)
- switch(event.keyCode) {
- case Event.KEY_TAB:
- case Event.KEY_RETURN:
- this.selectEntry();
- Event.stop(event);
- case Event.KEY_ESC:
- this.hide();
- this.active = false;
- Event.stop(event);
- return;
- case Event.KEY_LEFT:
- case Event.KEY_RIGHT:
- return;
- case Event.KEY_UP:
- this.markPrevious();
- this.render();
- Event.stop(event);
- return;
- case Event.KEY_DOWN:
- this.markNext();
- this.render();
- Event.stop(event);
- return;
- }
- else
- if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
- (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
-
- this.changed = true;
- this.hasFocus = true;
-
- if(this.observer) clearTimeout(this.observer);
- this.observer =
- setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
- },
-
- activate: function() {
- this.changed = false;
- this.hasFocus = true;
- this.getUpdatedChoices();
- },
-
- 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.selectEntry();
- this.hide();
- },
-
- onBlur: function(event) {
- // needed to make click events working
- setTimeout(this.hide.bind(this), 250);
- this.hasFocus = false;
- this.active = false;
- },
-
- render: function() {
- if(this.entryCount > 0) {
- for (var i = 0; i < this.entryCount; i++)
- this.index==i ?
- Element.addClassName(this.getEntry(i),"selected") :
- Element.removeClassName(this.getEntry(i),"selected");
- if(this.hasFocus) {
- this.show();
- this.active = true;
- }
- } else {
- this.active = false;
- this.hide();
- }
- },
-
- markPrevious: function() {
- if(this.index > 0) this.index--;
- else this.index = this.entryCount-1;
- this.getEntry(this.index).scrollIntoView(true);
- },
-
- markNext: function() {
- if(this.index < this.entryCount-1) this.index++;
- else this.index = 0;
- this.getEntry(this.index).scrollIntoView(false);
- },
-
- getEntry: function(index) {
- return this.update.firstChild.childNodes[index];
- },
-
- getCurrentEntry: function() {
- return this.getEntry(this.index);
- },
-
- selectEntry: function() {
- this.active = false;
- this.updateElement(this.getCurrentEntry());
- },
-
- updateElement: function(selectedElement) {
- if (this.options.updateElement) {
- this.options.updateElement(selectedElement);
- return;
- }
- var value = '';
- if (this.options.select) {
- var nodes = $(selectedElement).select('.' + this.options.select) || [];
- if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
- } else
- value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
-
- var bounds = this.getTokenBounds();
- if (bounds[0] != -1) {
- var newValue = this.element.value.substr(0, bounds[0]);
- var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
- if (whitespace)
- newValue += whitespace[0];
- this.element.value = newValue + value + this.element.value.substr(bounds[1]);
- } else {
- this.element.value = value;
- }
- this.oldElementValue = this.element.value;
- this.element.focus();
-
- if (this.options.afterUpdateElement)
- this.options.afterUpdateElement(this.element, selectedElement);
- },
-
- updateChoices: function(choices) {
- if(!this.changed && this.hasFocus) {
- this.update.innerHTML = choices;
- Element.cleanWhitespace(this.update);
- Element.cleanWhitespace(this.update.down());
-
- if(this.update.firstChild && this.update.down().childNodes) {
- this.entryCount =
- this.update.down().childNodes.length;
- for (var i = 0; i < this.entryCount; i++) {
- var entry = this.getEntry(i);
- entry.autocompleteIndex = i;
- this.addObservers(entry);
- }
- } else {
- this.entryCount = 0;
- }
-
- this.stopIndicator();
- this.index = 0;
-
- if(this.entryCount==1 && this.options.autoSelect) {
- this.selectEntry();
- this.hide();
- } else {
- 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;
- this.tokenBounds = null;
- if(this.getToken().length>=this.options.minChars) {
- this.getUpdatedChoices();
- } else {
- this.active = false;
- this.hide();
- }
- this.oldElementValue = this.element.value;
- },
-
- getToken: function() {
- var bounds = this.getTokenBounds();
- return this.element.value.substring(bounds[0], bounds[1]).strip();
- },
-
- getTokenBounds: function() {
- if (null != this.tokenBounds) return this.tokenBounds;
- var value = this.element.value;
- if (value.strip().empty()) return [-1, 0];
- var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
- var offset = (diff == this.oldElementValue.length ? 1 : 0);
- var prevTokenPos = -1, nextTokenPos = value.length;
- var tp;
- for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
- tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
- if (tp > prevTokenPos) prevTokenPos = tp;
- tp = value.indexOf(this.options.tokens[index], diff + offset);
- if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
- }
- return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
- }
-});
-
-Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
- var boundary = Math.min(newS.length, oldS.length);
- for (var index = 0; index < boundary; ++index)
- if (newS[index] != oldS[index])
- return index;
- return boundary;
-};
-
-Ajax.Autocompleter = Class.create(Autocompleter.Base, {
- initialize: function(element, update, url, options) {
- this.baseInitialize(element, update, options);
- this.options.asynchronous = true;
- this.options.onComplete = this.onComplete.bind(this);
- this.options.defaultParams = this.options.parameters || null;
- this.url = url;
- },
-
- getUpdatedChoices: function() {
- this.startIndicator();
-
- var entry = encodeURIComponent(this.options.paramName) + '=' +
- encodeURIComponent(this.getToken());
-
- this.options.parameters = this.options.callback ?
- this.options.callback(this.element, entry) : entry;
-
- if(this.options.defaultParams)
- this.options.parameters += '&' + this.options.defaultParams;
-
- new Ajax.Request(this.url, this.options);
- },
-
- onComplete: function(request) {
- this.updateChoices(request.responseText);
- }
-});
-
-// The local array autocompleter. Used when you'd prefer to
-// inject an array of autocompletion options into the page, rather
-// 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
-//
-// - partialSearch - If false, the autocompleter will match entered
-// text only at the beginning of strings in the
-// autocomplete array. Defaults to true, which will
-// match text at the beginning of any *word* in the
-// strings in the autocomplete array. If you want to
-// search anywhere in the string, additionally set
-// the option fullSearch to true (default: off).
-//
-// - fullSsearch - Search anywhere in autocomplete array strings.
-//
-// - partialChars - How many characters to enter before triggering
-// a partial match (unlike minChars, which defines
-// how many characters are required to do any match
-// at all). Defaults to 2.
-//
-// - ignoreCase - 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.Base, {
- initialize: function(element, update, array, options) {
- this.baseInitialize(element, update, options);
- this.options.array = array;
- },
-
- getUpdatedChoices: function() {
- this.updateChoices(this.options.selector(this));
- },
-
- setOptions: function(options) {
- this.options = Object.extend({
- choices: 10,
- partialSearch: true,
- partialChars: 2,
- ignoreCase: true,
- fullSearch: false,
- selector: function(instance) {
- var ret = []; // Beginning matches
- var partial = []; // Inside matches
- var entry = instance.getToken();
- var count = 0;
-
- for (var i = 0; i < instance.options.array.length &&
- ret.length < instance.options.choices ; i++) {
-
- var elem = instance.options.array[i];
- var foundPos = instance.options.ignoreCase ?
- elem.toLowerCase().indexOf(entry.toLowerCase()) :
- elem.indexOf(entry);
-
- while (foundPos != -1) {
- if (foundPos == 0 && elem.length != entry.length) {
- ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
- elem.substr(entry.length) + "</li>");
- break;
- } else if (entry.length >= instance.options.partialChars &&
- instance.options.partialSearch && foundPos != -1) {
- if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
- partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
- elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
- foundPos + entry.length) + "</li>");
- break;
- }
- }
-
- foundPos = instance.options.ignoreCase ?
- elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
- elem.indexOf(entry, foundPos + 1);
-
- }
- }
- if (partial.length)
- ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
- return "<ul>" + ret.join('') + "</ul>";
- }
- }, options || { });
- }
-});
-
-// AJAX in-place editor and collection editor
-// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
-
-// Use this if you notice weird scrolling problems on some browsers,
-// the DOM might be a bit confused when this gets called so do this
-// waits 1 ms (with setTimeout) until it does the activation
-Field.scrollFreeActivate = function(field) {
- setTimeout(function() {
- Field.activate(field);
- }, 1);
-};
-
-Ajax.InPlaceEditor = Class.create({
- initialize: function(element, url, options) {
- this.url = url;
- this.element = element = $(element);
- this.prepareOptions();
- this._controls = { };
- arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
- Object.extend(this.options, options || { });
- if (!this.options.formId && this.element.id) {
- this.options.formId = this.element.id + '-inplaceeditor';
- if ($(this.options.formId))
- this.options.formId = '';
- }
- if (this.options.externalControl)
- this.options.externalControl = $(this.options.externalControl);
- if (!this.options.externalControl)
- this.options.externalControlOnly = false;
- this._originalBackground = this.element.getStyle('background-color') || 'transparent';
- this.element.title = this.options.clickToEditText;
- this._boundCancelHandler = this.handleFormCancellation.bind(this);
- this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
- this._boundFailureHandler = this.handleAJAXFailure.bind(this);
- this._boundSubmitHandler = this.handleFormSubmission.bind(this);
- this._boundWrapperHandler = this.wrapUp.bind(this);
- this.registerListeners();
- },
- checkForEscapeOrReturn: function(e) {
- if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
- if (Event.KEY_ESC == e.keyCode)
- this.handleFormCancellation(e);
- else if (Event.KEY_RETURN == e.keyCode)
- this.handleFormSubmission(e);
- },
- createControl: function(mode, handler, extraClasses) {
- var control = this.options[mode + 'Control'];
- var text = this.options[mode + 'Text'];
- if ('button' == control) {
- var btn = document.createElement('input');
- btn.type = 'submit';
- btn.value = text;
- btn.className = 'editor_' + mode + '_button';
- if ('cancel' == mode)
- btn.onclick = this._boundCancelHandler;
- this._form.appendChild(btn);
- this._controls[mode] = btn;
- } else if ('link' == control) {
- var link = document.createElement('a');
- link.href = '#';
- link.appendChild(document.createTextNode(text));
- link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
- link.className = 'editor_' + mode + '_link';
- if (extraClasses)
- link.className += ' ' + extraClasses;
- this._form.appendChild(link);
- this._controls[mode] = link;
- }
- },
- createEditField: function() {
- var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
- var fld;
- if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
- fld = document.createElement('input');
- fld.type = 'text';
- var size = this.options.size || this.options.cols || 0;
- if (0 < size) fld.size = size;
- } else {
- fld = document.createElement('textarea');
- fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
- fld.cols = this.options.cols || 40;
- }
- fld.name = this.options.paramName;
- fld.value = text; // No HTML breaks conversion anymore
- fld.className = 'editor_field';
- if (this.options.submitOnBlur)
- fld.onblur = this._boundSubmitHandler;
- this._controls.editor = fld;
- if (this.options.loadTextURL)
- this.loadExternalText();
- this._form.appendChild(this._controls.editor);
- },
- createForm: function() {
- var ipe = this;
- function addText(mode, condition) {
- var text = ipe.options['text' + mode + 'Controls'];
- if (!text || condition === false) return;
- ipe._form.appendChild(document.createTextNode(text));
- };
- this._form = $(document.createElement('form'));
- this._form.id = this.options.formId;
- this._form.addClassName(this.options.formClassName);
- this._form.onsubmit = this._boundSubmitHandler;
- this.createEditField();
- if ('textarea' == this._controls.editor.tagName.toLowerCase())
- this._form.appendChild(document.createElement('br'));
- if (this.options.onFormCustomization)
- this.options.onFormCustomization(this, this._form);
- addText('Before', this.options.okControl || this.options.cancelControl);
- this.createControl('ok', this._boundSubmitHandler);
- addText('Between', this.options.okControl && this.options.cancelControl);
- this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
- addText('After', this.options.okControl || this.options.cancelControl);
- },
- destroy: function() {
- if (this._oldInnerHTML)
- this.element.innerHTML = this._oldInnerHTML;
- this.leaveEditMode();
- this.unregisterListeners();
- },
- enterEditMode: function(e) {
- if (this._saving || this._editing) return;
- this._editing = true;
- this.triggerCallback('onEnterEditMode');
- if (this.options.externalControl)
- this.options.externalControl.hide();
- this.element.hide();
- this.createForm();
- this.element.parentNode.insertBefore(this._form, this.element);
- if (!this.options.loadTextURL)
- this.postProcessEditField();
- if (e) Event.stop(e);
- },
- enterHover: function(e) {
- if (this.options.hoverClassName)
- this.element.addClassName(this.options.hoverClassName);
- if (this._saving) return;
- this.triggerCallback('onEnterHover');
- },
- getText: function() {
- return this.element.innerHTML.unescapeHTML();
- },
- handleAJAXFailure: function(transport) {
- this.triggerCallback('onFailure', transport);
- if (this._oldInnerHTML) {
- this.element.innerHTML = this._oldInnerHTML;
- this._oldInnerHTML = null;
- }
- },
- handleFormCancellation: function(e) {
- this.wrapUp();
- if (e) Event.stop(e);
- },
- handleFormSubmission: function(e) {
- var form = this._form;
- var value = $F(this._controls.editor);
- this.prepareSubmission();
- var params = this.options.callback(form, value) || '';
- if (Object.isString(params))
- params = params.toQueryParams();
- params.editorId = this.element.id;
- if (this.options.htmlResponse) {
- var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
- Object.extend(options, {
- parameters: params,
- onComplete: this._boundWrapperHandler,
- onFailure: this._boundFailureHandler
- });
- new Ajax.Updater({ success: this.element }, this.url, options);
- } else {
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
- Object.extend(options, {
- parameters: params,
- onComplete: this._boundWrapperHandler,
- onFailure: this._boundFailureHandler
- });
- new Ajax.Request(this.url, options);
- }
- if (e) Event.stop(e);
- },
- leaveEditMode: function() {
- this.element.removeClassName(this.options.savingClassName);
- this.removeForm();
- this.leaveHover();
- this.element.style.backgroundColor = this._originalBackground;
- this.element.show();
- if (this.options.externalControl)
- this.options.externalControl.show();
- this._saving = false;
- this._editing = false;
- this._oldInnerHTML = null;
- this.triggerCallback('onLeaveEditMode');
- },
- leaveHover: function(e) {
- if (this.options.hoverClassName)
- this.element.removeClassName(this.options.hoverClassName);
- if (this._saving) return;
- this.triggerCallback('onLeaveHover');
- },
- loadExternalText: function() {
- this._form.addClassName(this.options.loadingClassName);
- this._controls.editor.disabled = true;
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
- Object.extend(options, {
- parameters: 'editorId=' + encodeURIComponent(this.element.id),
- onComplete: Prototype.emptyFunction,
- onSuccess: function(transport) {
- this._form.removeClassName(this.options.loadingClassName);
- var text = transport.responseText;
- if (this.options.stripLoadedTextTags)
- text = text.stripTags();
- this._controls.editor.value = text;
- this._controls.editor.disabled = false;
- this.postProcessEditField();
- }.bind(this),
- onFailure: this._boundFailureHandler
- });
- new Ajax.Request(this.options.loadTextURL, options);
- },
- postProcessEditField: function() {
- var fpc = this.options.fieldPostCreation;
- if (fpc)
- $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
- },
- prepareOptions: function() {
- this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
- Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
- [this._extraDefaultOptions].flatten().compact().each(function(defs) {
- Object.extend(this.options, defs);
- }.bind(this));
- },
- prepareSubmission: function() {
- this._saving = true;
- this.removeForm();
- this.leaveHover();
- this.showSaving();
- },
- registerListeners: function() {
- this._listeners = { };
- var listener;
- $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
- listener = this[pair.value].bind(this);
- this._listeners[pair.key] = listener;
- if (!this.options.externalControlOnly)
- this.element.observe(pair.key, listener);
- if (this.options.externalControl)
- this.options.externalControl.observe(pair.key, listener);
- }.bind(this));
- },
- removeForm: function() {
- if (!this._form) return;
- this._form.remove();
- this._form = null;
- this._controls = { };
- },
- showSaving: function() {
- this._oldInnerHTML = this.element.innerHTML;
- this.element.innerHTML = this.options.savingText;
- this.element.addClassName(this.options.savingClassName);
- this.element.style.backgroundColor = this._originalBackground;
- this.element.show();
- },
- triggerCallback: function(cbName, arg) {
- if ('function' == typeof this.options[cbName]) {
- this.options[cbName](this, arg);
- }
- },
- unregisterListeners: function() {
- $H(this._listeners).each(function(pair) {
- if (!this.options.externalControlOnly)
- this.element.stopObserving(pair.key, pair.value);
- if (this.options.externalControl)
- this.options.externalControl.stopObserving(pair.key, pair.value);
- }.bind(this));
- },
- wrapUp: function(transport) {
- this.leaveEditMode();
- // Can't use triggerCallback due to backward compatibility: requires
- // binding + direct element
- this._boundComplete(transport, this.element);
- }
-});
-
-Object.extend(Ajax.InPlaceEditor.prototype, {
- dispose: Ajax.InPlaceEditor.prototype.destroy
-});
-
-Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
- initialize: function($super, element, url, options) {
- this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
- $super(element, url, options);
- },
-
- createEditField: function() {
- var list = document.createElement('select');
- list.name = this.options.paramName;
- list.size = 1;
- this._controls.editor = list;
- this._collection = this.options.collection || [];
- if (this.options.loadCollectionURL)
- this.loadCollection();
- else
- this.checkForExternalText();
- this._form.appendChild(this._controls.editor);
- },
-
- loadCollection: function() {
- this._form.addClassName(this.options.loadingClassName);
- this.showLoadingText(this.options.loadingCollectionText);
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
- Object.extend(options, {
- parameters: 'editorId=' + encodeURIComponent(this.element.id),
- onComplete: Prototype.emptyFunction,
- onSuccess: function(transport) {
- var js = transport.responseText.strip();
- if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
- throw('Server returned an invalid collection representation.');
- this._collection = eval(js);
- this.checkForExternalText();
- }.bind(this),
- onFailure: this.onFailure
- });
- new Ajax.Request(this.options.loadCollectionURL, options);
- },
-
- showLoadingText: function(text) {
- this._controls.editor.disabled = true;
- var tempOption = this._controls.editor.firstChild;
- if (!tempOption) {
- tempOption = document.createElement('option');
- tempOption.value = '';
- this._controls.editor.appendChild(tempOption);
- tempOption.selected = true;
- }
- tempOption.update((text || '').stripScripts().stripTags());
- },
-
- checkForExternalText: function() {
- this._text = this.getText();
- if (this.options.loadTextURL)
- this.loadExternalText();
- else
- this.buildOptionList();
- },
-
- loadExternalText: function() {
- this.showLoadingText(this.options.loadingText);
- var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
- Object.extend(options, {
- parameters: 'editorId=' + encodeURIComponent(this.element.id),
- onComplete: Prototype.emptyFunction,
- onSuccess: function(transport) {
- this._text = transport.responseText.strip();
- this.buildOptionList();
- }.bind(this),
- onFailure: this.onFailure
- });
- new Ajax.Request(this.options.loadTextURL, options);
- },
-
- buildOptionList: function() {
- this._form.removeClassName(this.options.loadingClassName);
- this._collection = this._collection.map(function(entry) {
- return 2 === entry.length ? entry : [entry, entry].flatten();
- });
- var marker = ('value' in this.options) ? this.options.value : this._text;
- var textFound = this._collection.any(function(entry) {
- return entry[0] == marker;
- }.bind(this));
- this._controls.editor.update('');
- var option;
- this._collection.each(function(entry, index) {
- option = document.createElement('option');
- option.value = entry[0];
- option.selected = textFound ? entry[0] == marker : 0 == index;
- option.appendChild(document.createTextNode(entry[1]));
- this._controls.editor.appendChild(option);
- }.bind(this));
- this._controls.editor.disabled = false;
- Field.scrollFreeActivate(this._controls.editor);
- }
-});
-
-//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
-//**** This only exists for a while, in order to let ****
-//**** users adapt to the new API. Read up on the new ****
-//**** API and convert your code to it ASAP! ****
-
-Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
- if (!options) return;
- function fallback(name, expr) {
- if (name in options || expr === undefined) return;
- options[name] = expr;
- };
- fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
- options.cancelLink == options.cancelButton == false ? false : undefined)));
- fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
- options.okLink == options.okButton == false ? false : undefined)));
- fallback('highlightColor', options.highlightcolor);
- fallback('highlightEndColor', options.highlightendcolor);
-};
-
-Object.extend(Ajax.InPlaceEditor, {
- DefaultOptions: {
- ajaxOptions: { },
- autoRows: 3, // Use when multi-line w/ rows == 1
- cancelControl: 'link', // 'link'|'button'|false
- cancelText: 'cancel',
- clickToEditText: 'Click to edit',
- externalControl: null, // id|elt
- externalControlOnly: false,
- fieldPostCreation: 'activate', // 'activate'|'focus'|false
- formClassName: 'inplaceeditor-form',
- formId: null, // id|elt
- highlightColor: '#ffff99',
- highlightEndColor: '#ffffff',
- hoverClassName: '',
- htmlResponse: true,
- loadingClassName: 'inplaceeditor-loading',
- loadingText: 'Loading...',
- okControl: 'button', // 'link'|'button'|false
- okText: 'ok',
- paramName: 'value',
- rows: 1, // If 1 and multi-line, uses autoRows
- savingClassName: 'inplaceeditor-saving',
- savingText: 'Saving...',
- size: 0,
- stripLoadedTextTags: false,
- submitOnBlur: false,
- textAfterControls: '',
- textBeforeControls: '',
- textBetweenControls: ''
- },
- DefaultCallbacks: {
- callback: function(form) {
- return Form.serialize(form);
- },
- onComplete: function(transport, element) {
- // For backward compatibility, this one is bound to the IPE, and passes
- // the element directly. It was too often customized, so we don't break it.
- new Effect.Highlight(element, {
- startcolor: this.options.highlightColor, keepBackgroundImage: true });
- },
- onEnterEditMode: null,
- onEnterHover: function(ipe) {
- ipe.element.style.backgroundColor = ipe.options.highlightColor;
- if (ipe._effect)
- ipe._effect.cancel();
- },
- onFailure: function(transport, ipe) {
- alert('Error communication with the server: ' + transport.responseText.stripTags());
- },
- onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
- onLeaveEditMode: null,
- onLeaveHover: function(ipe) {
- ipe._effect = new Effect.Highlight(ipe.element, {
- startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
- restorecolor: ipe._originalBackground, keepBackgroundImage: true
- });
- }
- },
- Listeners: {
- click: 'enterEditMode',
- keydown: 'checkForEscapeOrReturn',
- mouseover: 'enterHover',
- mouseout: 'leaveHover'
- }
-});
-
-Ajax.InPlaceCollectionEditor.DefaultOptions = {
- loadingCollectionText: 'Loading options...'
-};
-
-// Delayed observer, like Form.Element.Observer,
-// but waits for delay after last key input
-// Ideal for live-search fields
-
-Form.Element.DelayedObserver = Class.create({
- initialize: function(element, delay, callback) {
- this.delay = delay || 0.5;
- this.element = $(element);
- this.callback = callback;
- this.timer = null;
- this.lastValue = $F(this.element);
- Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
- },
- delayedListener: function(event) {
- if(this.lastValue == $F(this.element)) return;
- if(this.timer) clearTimeout(this.timer);
- this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
- this.lastValue = $F(this.element);
- },
- onTimerEvent: function() {
- this.timer = null;
- this.callback(this.element, $F(this.element));
- }
-}); \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js
deleted file mode 100644
index 15c6dbca68..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js
+++ /dev/null
@@ -1,974 +0,0 @@
-// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
-
-// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// script.aculo.us is freely distributable under the terms of an MIT-style license.
-// For details, see the script.aculo.us web site: http://script.aculo.us/
-
-if(Object.isUndefined(Effect))
- throw("dragdrop.js requires including script.aculo.us' effects.js library");
-
-var Droppables = {
- drops: [],
-
- remove: function(element) {
- this.drops = this.drops.reject(function(d) { return d.element==$(element) });
- },
-
- add: function(element) {
- element = $(element);
- var options = Object.extend({
- greedy: true,
- hoverclass: null,
- tree: false
- }, arguments[1] || { });
-
- // cache containers
- if(options.containment) {
- options._containers = [];
- var containment = options.containment;
- if(Object.isArray(containment)) {
- containment.each( function(c) { options._containers.push($(c)) });
- } else {
- options._containers.push($(containment));
- }
- }
-
- if(options.accept) options.accept = [options.accept].flatten();
-
- Element.makePositioned(element); // fix IE
- options.element = element;
-
- this.drops.push(options);
- },
-
- findDeepestChild: function(drops) {
- deepest = drops[0];
-
- for (i = 1; i < drops.length; ++i)
- if (Element.isParent(drops[i].element, deepest.element))
- deepest = drops[i];
-
- return deepest;
- },
-
- isContained: function(element, drop) {
- var containmentNode;
- if(drop.tree) {
- containmentNode = element.treeNode;
- } else {
- containmentNode = element.parentNode;
- }
- return drop._containers.detect(function(c) { return containmentNode == c });
- },
-
- isAffected: function(point, element, drop) {
- return (
- (drop.element!=element) &&
- ((!drop._containers) ||
- this.isContained(element, drop)) &&
- ((!drop.accept) ||
- (Element.classNames(element).detect(
- function(v) { return drop.accept.include(v) } ) )) &&
- Position.within(drop.element, point[0], point[1]) );
- },
-
- deactivate: function(drop) {
- if(drop.hoverclass)
- Element.removeClassName(drop.element, drop.hoverclass);
- this.last_active = null;
- },
-
- activate: function(drop) {
- if(drop.hoverclass)
- Element.addClassName(drop.element, drop.hoverclass);
- this.last_active = drop;
- },
-
- show: function(point, element) {
- if(!this.drops.length) return;
- var drop, affected = [];
-
- this.drops.each( function(drop) {
- if(Droppables.isAffected(point, element, drop))
- affected.push(drop);
- });
-
- if(affected.length>0)
- drop = Droppables.findDeepestChild(affected);
-
- if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
- if (drop) {
- Position.within(drop.element, point[0], point[1]);
- if(drop.onHover)
- drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
-
- if (drop != this.last_active) Droppables.activate(drop);
- }
- },
-
- fire: function(event, element) {
- if(!this.last_active) return;
- Position.prepare();
-
- if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
- if (this.last_active.onDrop) {
- this.last_active.onDrop(element, this.last_active.element, event);
- return true;
- }
- },
-
- reset: function() {
- if(this.last_active)
- this.deactivate(this.last_active);
- }
-};
-
-var Draggables = {
- drags: [],
- observers: [],
-
- register: function(draggable) {
- if(this.drags.length == 0) {
- this.eventMouseUp = this.endDrag.bindAsEventListener(this);
- this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
- this.eventKeypress = this.keyPress.bindAsEventListener(this);
-
- Event.observe(document, "mouseup", this.eventMouseUp);
- Event.observe(document, "mousemove", this.eventMouseMove);
- Event.observe(document, "keypress", this.eventKeypress);
- }
- this.drags.push(draggable);
- },
-
- unregister: function(draggable) {
- this.drags = this.drags.reject(function(d) { return d==draggable });
- if(this.drags.length == 0) {
- Event.stopObserving(document, "mouseup", this.eventMouseUp);
- Event.stopObserving(document, "mousemove", this.eventMouseMove);
- Event.stopObserving(document, "keypress", this.eventKeypress);
- }
- },
-
- activate: function(draggable) {
- if(draggable.options.delay) {
- this._timeout = setTimeout(function() {
- Draggables._timeout = null;
- window.focus();
- Draggables.activeDraggable = draggable;
- }.bind(this), draggable.options.delay);
- } else {
- window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
- this.activeDraggable = draggable;
- }
- },
-
- deactivate: function() {
- this.activeDraggable = null;
- },
-
- updateDrag: function(event) {
- if(!this.activeDraggable) return;
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- // Mozilla-based browsers fire successive mousemove events with
- // the same coordinates, prevent needless redrawing (moz bug?)
- if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
- this._lastPointer = pointer;
-
- this.activeDraggable.updateDrag(event, pointer);
- },
-
- endDrag: function(event) {
- if(this._timeout) {
- clearTimeout(this._timeout);
- this._timeout = null;
- }
- if(!this.activeDraggable) return;
- this._lastPointer = null;
- this.activeDraggable.endDrag(event);
- this.activeDraggable = null;
- },
-
- keyPress: function(event) {
- if(this.activeDraggable)
- this.activeDraggable.keyPress(event);
- },
-
- addObserver: function(observer) {
- this.observers.push(observer);
- this._cacheObserverCallbacks();
- },
-
- removeObserver: function(element) { // element instead of observer fixes mem leaks
- this.observers = this.observers.reject( function(o) { return o.element==element });
- this._cacheObserverCallbacks();
- },
-
- notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
- if(this[eventName+'Count'] > 0)
- this.observers.each( function(o) {
- if(o[eventName]) o[eventName](eventName, draggable, event);
- });
- if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
- },
-
- _cacheObserverCallbacks: function() {
- ['onStart','onEnd','onDrag'].each( function(eventName) {
- Draggables[eventName+'Count'] = Draggables.observers.select(
- function(o) { return o[eventName]; }
- ).length;
- });
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-var Draggable = Class.create({
- initialize: function(element) {
- var defaults = {
- handle: false,
- reverteffect: function(element, top_offset, left_offset) {
- var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
- new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
- queue: {scope:'_draggable', position:'end'}
- });
- },
- endeffect: function(element) {
- var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
- new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
- queue: {scope:'_draggable', position:'end'},
- afterFinish: function(){
- Draggable._dragging[element] = false
- }
- });
- },
- zindex: 1000,
- revert: false,
- quiet: false,
- scroll: false,
- scrollSensitivity: 20,
- scrollSpeed: 15,
- snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
- delay: 0
- };
-
- if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
- Object.extend(defaults, {
- starteffect: function(element) {
- element._opacity = Element.getOpacity(element);
- Draggable._dragging[element] = true;
- new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
- }
- });
-
- var options = Object.extend(defaults, arguments[1] || { });
-
- this.element = $(element);
-
- if(options.handle && Object.isString(options.handle))
- this.handle = this.element.down('.'+options.handle, 0);
-
- if(!this.handle) this.handle = $(options.handle);
- if(!this.handle) this.handle = this.element;
-
- if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
- options.scroll = $(options.scroll);
- this._isScrollChild = Element.childOf(this.element, options.scroll);
- }
-
- Element.makePositioned(this.element); // fix IE
-
- this.options = options;
- this.dragging = false;
-
- this.eventMouseDown = this.initDrag.bindAsEventListener(this);
- Event.observe(this.handle, "mousedown", this.eventMouseDown);
-
- Draggables.register(this);
- },
-
- destroy: function() {
- Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
- Draggables.unregister(this);
- },
-
- currentDelta: function() {
- return([
- parseInt(Element.getStyle(this.element,'left') || '0'),
- parseInt(Element.getStyle(this.element,'top') || '0')]);
- },
-
- initDrag: function(event) {
- if(!Object.isUndefined(Draggable._dragging[this.element]) &&
- Draggable._dragging[this.element]) return;
- if(Event.isLeftClick(event)) {
- // abort on form elements, fixes a Firefox issue
- var src = Event.element(event);
- if((tag_name = src.tagName.toUpperCase()) && (
- tag_name=='INPUT' ||
- tag_name=='SELECT' ||
- tag_name=='OPTION' ||
- tag_name=='BUTTON' ||
- tag_name=='TEXTAREA')) return;
-
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- var pos = this.element.cumulativeOffset();
- this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
-
- Draggables.activate(this);
- Event.stop(event);
- }
- },
-
- startDrag: function(event) {
- this.dragging = true;
- if(!this.delta)
- this.delta = this.currentDelta();
-
- if(this.options.zindex) {
- this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
- this.element.style.zIndex = this.options.zindex;
- }
-
- if(this.options.ghosting) {
- this._clone = this.element.cloneNode(true);
- this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
- if (!this._originallyAbsolute)
- Position.absolutize(this.element);
- this.element.parentNode.insertBefore(this._clone, this.element);
- }
-
- if(this.options.scroll) {
- if (this.options.scroll == window) {
- var where = this._getWindowScroll(this.options.scroll);
- this.originalScrollLeft = where.left;
- this.originalScrollTop = where.top;
- } else {
- this.originalScrollLeft = this.options.scroll.scrollLeft;
- this.originalScrollTop = this.options.scroll.scrollTop;
- }
- }
-
- Draggables.notify('onStart', this, event);
-
- if(this.options.starteffect) this.options.starteffect(this.element);
- },
-
- updateDrag: function(event, pointer) {
- if(!this.dragging) this.startDrag(event);
-
- if(!this.options.quiet){
- Position.prepare();
- Droppables.show(pointer, this.element);
- }
-
- Draggables.notify('onDrag', this, event);
-
- this.draw(pointer);
- if(this.options.change) this.options.change(this);
-
- if(this.options.scroll) {
- this.stopScrolling();
-
- var p;
- if (this.options.scroll == window) {
- with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
- } else {
- p = Position.page(this.options.scroll);
- p[0] += this.options.scroll.scrollLeft + Position.deltaX;
- p[1] += this.options.scroll.scrollTop + Position.deltaY;
- p.push(p[0]+this.options.scroll.offsetWidth);
- p.push(p[1]+this.options.scroll.offsetHeight);
- }
- var speed = [0,0];
- if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
- if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
- if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
- if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
- this.startScrolling(speed);
- }
-
- // fix AppleWebKit rendering
- if(Prototype.Browser.WebKit) window.scrollBy(0,0);
-
- Event.stop(event);
- },
-
- finishDrag: function(event, success) {
- this.dragging = false;
-
- if(this.options.quiet){
- Position.prepare();
- var pointer = [Event.pointerX(event), Event.pointerY(event)];
- Droppables.show(pointer, this.element);
- }
-
- if(this.options.ghosting) {
- if (!this._originallyAbsolute)
- Position.relativize(this.element);
- delete this._originallyAbsolute;
- Element.remove(this._clone);
- this._clone = null;
- }
-
- var dropped = false;
- if(success) {
- dropped = Droppables.fire(event, this.element);
- if (!dropped) dropped = false;
- }
- if(dropped && this.options.onDropped) this.options.onDropped(this.element);
- Draggables.notify('onEnd', this, event);
-
- var revert = this.options.revert;
- if(revert && Object.isFunction(revert)) revert = revert(this.element);
-
- var d = this.currentDelta();
- if(revert && this.options.reverteffect) {
- if (dropped == 0 || revert != 'failure')
- this.options.reverteffect(this.element,
- d[1]-this.delta[1], d[0]-this.delta[0]);
- } else {
- this.delta = d;
- }
-
- if(this.options.zindex)
- this.element.style.zIndex = this.originalZ;
-
- if(this.options.endeffect)
- this.options.endeffect(this.element);
-
- Draggables.deactivate(this);
- Droppables.reset();
- },
-
- keyPress: function(event) {
- if(event.keyCode!=Event.KEY_ESC) return;
- this.finishDrag(event, false);
- Event.stop(event);
- },
-
- endDrag: function(event) {
- if(!this.dragging) return;
- this.stopScrolling();
- this.finishDrag(event, true);
- Event.stop(event);
- },
-
- draw: function(point) {
- var pos = this.element.cumulativeOffset();
- if(this.options.ghosting) {
- var r = Position.realOffset(this.element);
- pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
- }
-
- var d = this.currentDelta();
- pos[0] -= d[0]; pos[1] -= d[1];
-
- if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
- pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
- pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
- }
-
- var p = [0,1].map(function(i){
- return (point[i]-pos[i]-this.offset[i])
- }.bind(this));
-
- if(this.options.snap) {
- if(Object.isFunction(this.options.snap)) {
- p = this.options.snap(p[0],p[1],this);
- } else {
- if(Object.isArray(this.options.snap)) {
- p = p.map( function(v, i) {
- return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
- } else {
- p = p.map( function(v) {
- return (v/this.options.snap).round()*this.options.snap }.bind(this));
- }
- }}
-
- var style = this.element.style;
- if((!this.options.constraint) || (this.options.constraint=='horizontal'))
- style.left = p[0] + "px";
- if((!this.options.constraint) || (this.options.constraint=='vertical'))
- style.top = p[1] + "px";
-
- if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
- },
-
- stopScrolling: function() {
- if(this.scrollInterval) {
- clearInterval(this.scrollInterval);
- this.scrollInterval = null;
- Draggables._lastScrollPointer = null;
- }
- },
-
- startScrolling: function(speed) {
- if(!(speed[0] || speed[1])) return;
- this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
- this.lastScrolled = new Date();
- this.scrollInterval = setInterval(this.scroll.bind(this), 10);
- },
-
- scroll: function() {
- var current = new Date();
- var delta = current - this.lastScrolled;
- this.lastScrolled = current;
- if(this.options.scroll == window) {
- with (this._getWindowScroll(this.options.scroll)) {
- if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
- var d = delta / 1000;
- this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
- }
- }
- } else {
- this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
- this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
- }
-
- Position.prepare();
- Droppables.show(Draggables._lastPointer, this.element);
- Draggables.notify('onDrag', this);
- if (this._isScrollChild) {
- Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
- Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
- Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
- if (Draggables._lastScrollPointer[0] < 0)
- Draggables._lastScrollPointer[0] = 0;
- if (Draggables._lastScrollPointer[1] < 0)
- Draggables._lastScrollPointer[1] = 0;
- this.draw(Draggables._lastScrollPointer);
- }
-
- if(this.options.change) this.options.change(this);
- },
-
- _getWindowScroll: function(w) {
- var T, L, W, H;
- with (w.document) {
- if (w.document.documentElement && documentElement.scrollTop) {
- T = documentElement.scrollTop;
- L = documentElement.scrollLeft;
- } else if (w.document.body) {
- T = body.scrollTop;
- L = body.scrollLeft;
- }
- if (w.innerWidth) {
- W = w.innerWidth;
- H = w.innerHeight;
- } else if (w.document.documentElement && documentElement.clientWidth) {
- W = documentElement.clientWidth;
- H = documentElement.clientHeight;
- } else {
- W = body.offsetWidth;
- H = body.offsetHeight;
- }
- }
- return { top: T, left: L, width: W, height: H };
- }
-});
-
-Draggable._dragging = { };
-
-/*--------------------------------------------------------------------------*/
-
-var SortableObserver = Class.create({
- 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() {
- Sortable.unmark();
- if(this.lastValue != Sortable.serialize(this.element))
- this.observer(this.element)
- }
-});
-
-var Sortable = {
- SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
-
- sortables: { },
-
- _findRootElement: function(element) {
- while (element.tagName.toUpperCase() != "BODY") {
- if(element.id && Sortable.sortables[element.id]) return element;
- element = element.parentNode;
- }
- },
-
- options: function(element) {
- element = Sortable._findRootElement($(element));
- if(!element) return;
- return Sortable.sortables[element.id];
- },
-
- destroy: function(element){
- element = $(element);
- var s = Sortable.sortables[element.id];
-
- if(s) {
- Draggables.removeObserver(s.element);
- s.droppables.each(function(d){ Droppables.remove(d) });
- s.draggables.invoke('destroy');
-
- delete Sortable.sortables[s.element.id];
- }
- },
-
- create: function(element) {
- element = $(element);
- var options = Object.extend({
- element: element,
- tag: 'li', // assumes li children, override with tag: 'tagname'
- dropOnEmpty: false,
- tree: false,
- treeTag: 'ul',
- overlap: 'vertical', // one of 'vertical', 'horizontal'
- constraint: 'vertical', // one of 'vertical', 'horizontal', false
- containment: element, // also takes array of elements (or id's); or false
- handle: false, // or a CSS class
- only: false,
- delay: 0,
- hoverclass: null,
- ghosting: false,
- quiet: false,
- scroll: false,
- scrollSensitivity: 20,
- scrollSpeed: 15,
- format: this.SERIALIZE_RULE,
-
- // these take arrays of elements or ids and can be
- // used for better initialization performance
- elements: false,
- handles: false,
-
- onChange: Prototype.emptyFunction,
- onUpdate: Prototype.emptyFunction
- }, arguments[1] || { });
-
- // clear any old sortable with same element
- this.destroy(element);
-
- // build options for the draggables
- var options_for_draggable = {
- revert: true,
- quiet: options.quiet,
- scroll: options.scroll,
- scrollSpeed: options.scrollSpeed,
- scrollSensitivity: options.scrollSensitivity,
- delay: options.delay,
- ghosting: options.ghosting,
- constraint: options.constraint,
- handle: options.handle };
-
- if(options.starteffect)
- options_for_draggable.starteffect = options.starteffect;
-
- if(options.reverteffect)
- options_for_draggable.reverteffect = options.reverteffect;
- else
- if(options.ghosting) options_for_draggable.reverteffect = function(element) {
- element.style.top = 0;
- element.style.left = 0;
- };
-
- if(options.endeffect)
- options_for_draggable.endeffect = options.endeffect;
-
- if(options.zindex)
- options_for_draggable.zindex = options.zindex;
-
- // build options for the droppables
- var options_for_droppable = {
- overlap: options.overlap,
- containment: options.containment,
- tree: options.tree,
- hoverclass: options.hoverclass,
- onHover: Sortable.onHover
- };
-
- var options_for_tree = {
- onHover: Sortable.onEmptyHover,
- overlap: options.overlap,
- containment: options.containment,
- hoverclass: options.hoverclass
- };
-
- // fix for gecko engine
- Element.cleanWhitespace(element);
-
- options.draggables = [];
- options.droppables = [];
-
- // drop on empty handling
- if(options.dropOnEmpty || options.tree) {
- Droppables.add(element, options_for_tree);
- options.droppables.push(element);
- }
-
- (options.elements || this.findElements(element, options) || []).each( function(e,i) {
- var handle = options.handles ? $(options.handles[i]) :
- (options.handle ? $(e).select('.' + options.handle)[0] : e);
- options.draggables.push(
- new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
- Droppables.add(e, options_for_droppable);
- if(options.tree) e.treeNode = element;
- options.droppables.push(e);
- });
-
- if(options.tree) {
- (Sortable.findTreeElements(element, options) || []).each( function(e) {
- Droppables.add(e, options_for_tree);
- e.treeNode = element;
- options.droppables.push(e);
- });
- }
-
- // keep reference
- this.sortables[element.identify()] = options;
-
- // for onupdate
- Draggables.addObserver(new SortableObserver(element, options.onUpdate));
-
- },
-
- // return all suitable-for-sortable elements in a guaranteed order
- findElements: function(element, options) {
- return Element.findChildren(
- element, options.only, options.tree ? true : false, options.tag);
- },
-
- findTreeElements: function(element, options) {
- return Element.findChildren(
- element, options.only, options.tree ? true : false, options.treeTag);
- },
-
- onHover: function(element, dropon, overlap) {
- if(Element.isParent(dropon, element)) return;
-
- if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
- return;
- } else if(overlap>0.5) {
- Sortable.mark(dropon, 'before');
- if(dropon.previousSibling != element) {
- var oldParentNode = element.parentNode;
- element.style.visibility = "hidden"; // fix gecko rendering
- dropon.parentNode.insertBefore(element, dropon);
- if(dropon.parentNode!=oldParentNode)
- Sortable.options(oldParentNode).onChange(element);
- Sortable.options(dropon.parentNode).onChange(element);
- }
- } else {
- Sortable.mark(dropon, 'after');
- var nextElement = dropon.nextSibling || null;
- if(nextElement != element) {
- var oldParentNode = element.parentNode;
- element.style.visibility = "hidden"; // fix gecko rendering
- dropon.parentNode.insertBefore(element, nextElement);
- if(dropon.parentNode!=oldParentNode)
- Sortable.options(oldParentNode).onChange(element);
- Sortable.options(dropon.parentNode).onChange(element);
- }
- }
- },
-
- onEmptyHover: function(element, dropon, overlap) {
- var oldParentNode = element.parentNode;
- var droponOptions = Sortable.options(dropon);
-
- if(!Element.isParent(dropon, element)) {
- var index;
-
- var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
- var child = null;
-
- if(children) {
- var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
-
- for (index = 0; index < children.length; index += 1) {
- if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
- offset -= Element.offsetSize (children[index], droponOptions.overlap);
- } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
- child = index + 1 < children.length ? children[index + 1] : null;
- break;
- } else {
- child = children[index];
- break;
- }
- }
- }
-
- dropon.insertBefore(element, child);
-
- Sortable.options(oldParentNode).onChange(element);
- droponOptions.onChange(element);
- }
- },
-
- unmark: function() {
- if(Sortable._marker) Sortable._marker.hide();
- },
-
- mark: function(dropon, position) {
- // mark on ghosting only
- var sortable = Sortable.options(dropon.parentNode);
- if(sortable && !sortable.ghosting) return;
-
- if(!Sortable._marker) {
- Sortable._marker =
- ($('dropmarker') || Element.extend(document.createElement('DIV'))).
- hide().addClassName('dropmarker').setStyle({position:'absolute'});
- document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
- }
- var offsets = dropon.cumulativeOffset();
- Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
-
- if(position=='after')
- if(sortable.overlap == 'horizontal')
- Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
- else
- Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
-
- Sortable._marker.show();
- },
-
- _tree: function(element, options, parent) {
- var children = Sortable.findElements(element, options) || [];
-
- for (var i = 0; i < children.length; ++i) {
- var match = children[i].id.match(options.format);
-
- if (!match) continue;
-
- var child = {
- id: encodeURIComponent(match ? match[1] : null),
- element: element,
- parent: parent,
- children: [],
- position: parent.children.length,
- container: $(children[i]).down(options.treeTag)
- };
-
- /* Get the element containing the children and recurse over it */
- if (child.container)
- this._tree(child.container, options, child);
-
- parent.children.push (child);
- }
-
- return parent;
- },
-
- tree: function(element) {
- element = $(element);
- var sortableOptions = this.options(element);
- var options = Object.extend({
- tag: sortableOptions.tag,
- treeTag: sortableOptions.treeTag,
- only: sortableOptions.only,
- name: element.id,
- format: sortableOptions.format
- }, arguments[1] || { });
-
- var root = {
- id: null,
- parent: null,
- children: [],
- container: element,
- position: 0
- };
-
- return Sortable._tree(element, options, root);
- },
-
- /* Construct a [i] index for a particular node */
- _constructIndex: function(node) {
- var index = '';
- do {
- if (node.id) index = '[' + node.position + ']' + index;
- } while ((node = node.parent) != null);
- return index;
- },
-
- sequence: function(element) {
- element = $(element);
- var options = Object.extend(this.options(element), arguments[1] || { });
-
- return $(this.findElements(element, options) || []).map( function(item) {
- return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
- });
- },
-
- setSequence: function(element, new_sequence) {
- element = $(element);
- var options = Object.extend(this.options(element), arguments[2] || { });
-
- var nodeMap = { };
- this.findElements(element, options).each( function(n) {
- if (n.id.match(options.format))
- nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
- n.parentNode.removeChild(n);
- });
-
- new_sequence.each(function(ident) {
- var n = nodeMap[ident];
- if (n) {
- n[1].appendChild(n[0]);
- delete nodeMap[ident];
- }
- });
- },
-
- serialize: function(element) {
- element = $(element);
- var options = Object.extend(Sortable.options(element), arguments[1] || { });
- var name = encodeURIComponent(
- (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
-
- if (options.tree) {
- return Sortable.tree(element, arguments[1]).children.map( function (item) {
- return [name + Sortable._constructIndex(item) + "[id]=" +
- encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
- }).flatten().join('&');
- } else {
- return Sortable.sequence(element, arguments[1]).map( function(item) {
- return name + "[]=" + encodeURIComponent(item);
- }).join('&');
- }
- }
-};
-
-// Returns true if child is contained within element
-Element.isParent = function(child, element) {
- if (!child.parentNode || child == element) return false;
- if (child.parentNode == element) return true;
- return Element.isParent(child.parentNode, element);
-};
-
-Element.findChildren = function(element, only, recursive, tagName) {
- if(!element.hasChildNodes()) return null;
- tagName = tagName.toUpperCase();
- if(only) only = [only].flatten();
- var elements = [];
- $A(element.childNodes).each( function(e) {
- if(e.tagName && e.tagName.toUpperCase()==tagName &&
- (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
- elements.push(e);
- if(recursive) {
- var grandchildren = Element.findChildren(e, only, recursive, tagName);
- if(grandchildren) elements.push(grandchildren);
- }
- });
-
- return (elements.length>0 ? elements.flatten() : []);
-};
-
-Element.offsetSize = function (element, type) {
- return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
-}; \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js
deleted file mode 100644
index c81e6c7d5f..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js
+++ /dev/null
@@ -1,1123 +0,0 @@
-// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
-
-// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// Contributors:
-// Justin Palmer (http://encytemedia.com/)
-// Mark Pilgrim (http://diveintomark.org/)
-// Martin Bialasinki
-//
-// script.aculo.us is freely distributable under the terms of an MIT-style license.
-// For details, see the script.aculo.us web site: http://script.aculo.us/
-
-// converts rgb() and #xxx to #xxxxxx format,
-// returns self (or first argument) if not convertable
-String.prototype.parseColor = function() {
- var color = '#';
- if (this.slice(0,4) == 'rgb(') {
- var cols = this.slice(4,this.length-1).split(',');
- var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
- } else {
- if (this.slice(0,1) == '#') {
- if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
- if (this.length==7) color = this.toLowerCase();
- }
- }
- return (color.length==7 ? color : (arguments[0] || this));
-};
-
-/*--------------------------------------------------------------------------*/
-
-Element.collectTextNodes = function(element) {
- return $A($(element).childNodes).collect( function(node) {
- return (node.nodeType==3 ? node.nodeValue :
- (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
- }).flatten().join('');
-};
-
-Element.collectTextNodesIgnoreClass = function(element, className) {
- return $A($(element).childNodes).collect( function(node) {
- return (node.nodeType==3 ? node.nodeValue :
- ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
- Element.collectTextNodesIgnoreClass(node, className) : ''));
- }).flatten().join('');
-};
-
-Element.setContentZoom = function(element, percent) {
- element = $(element);
- element.setStyle({fontSize: (percent/100) + 'em'});
- if (Prototype.Browser.WebKit) window.scrollBy(0,0);
- return element;
-};
-
-Element.getInlineOpacity = function(element){
- return $(element).style.opacity || '';
-};
-
-Element.forceRerendering = function(element) {
- try {
- element = $(element);
- var n = document.createTextNode(' ');
- element.appendChild(n);
- element.removeChild(n);
- } catch(e) { }
-};
-
-/*--------------------------------------------------------------------------*/
-
-var Effect = {
- _elementDoesNotExistError: {
- name: 'ElementDoesNotExistError',
- message: 'The specified DOM element does not exist, but is required for this effect to operate'
- },
- Transitions: {
- linear: Prototype.K,
- sinoidal: function(pos) {
- return (-Math.cos(pos*Math.PI)/2) + .5;
- },
- reverse: function(pos) {
- return 1-pos;
- },
- flicker: function(pos) {
- var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
- return pos > 1 ? 1 : pos;
- },
- wobble: function(pos) {
- return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
- },
- pulse: function(pos, pulses) {
- return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
- },
- spring: function(pos) {
- return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
- },
- none: function(pos) {
- return 0;
- },
- full: function(pos) {
- return 1;
- }
- },
- DefaultOptions: {
- duration: 1.0, // seconds
- fps: 100, // 100= assume 66fps max.
- sync: false, // true for combining
- from: 0.0,
- to: 1.0,
- delay: 0.0,
- queue: 'parallel'
- },
- tagifyText: function(element) {
- var tagifyStyle = 'position:relative';
- if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
-
- element = $(element);
- $A(element.childNodes).each( function(child) {
- if (child.nodeType==3) {
- child.nodeValue.toArray().each( function(character) {
- element.insertBefore(
- new Element('span', {style: tagifyStyle}).update(
- character == ' ' ? String.fromCharCode(160) : character),
- child);
- });
- Element.remove(child);
- }
- });
- },
- multiple: function(element, effect) {
- var elements;
- if (((typeof element == 'object') ||
- Object.isFunction(element)) &&
- (element.length))
- elements = element;
- else
- elements = $(element).childNodes;
-
- var options = Object.extend({
- speed: 0.1,
- delay: 0.0
- }, arguments[2] || { });
- var masterDelay = options.delay;
-
- $A(elements).each( function(element, index) {
- new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
- });
- },
- PAIRS: {
- 'slide': ['SlideDown','SlideUp'],
- 'blind': ['BlindDown','BlindUp'],
- 'appear': ['Appear','Fade']
- },
- toggle: function(element, effect, options) {
- element = $(element);
- effect = (effect || 'appear').toLowerCase();
-
- return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({
- queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
- }, options || {}));
- }
-};
-
-Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
-
-/* ------------- core effects ------------- */
-
-Effect.ScopedQueue = Class.create(Enumerable, {
- initialize: function() {
- this.effects = [];
- this.interval = null;
- },
- _each: function(iterator) {
- this.effects._each(iterator);
- },
- add: function(effect) {
- var timestamp = new Date().getTime();
-
- var position = Object.isString(effect.options.queue) ?
- effect.options.queue : effect.options.queue.position;
-
- switch(position) {
- case 'front':
- // move unstarted effects after this effect
- this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
- e.startOn += effect.finishOn;
- e.finishOn += effect.finishOn;
- });
- break;
- case 'with-last':
- timestamp = this.effects.pluck('startOn').max() || timestamp;
- break;
- case 'end':
- // start effect after last queued effect has finished
- timestamp = this.effects.pluck('finishOn').max() || timestamp;
- break;
- }
-
- effect.startOn += timestamp;
- effect.finishOn += timestamp;
-
- if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
- this.effects.push(effect);
-
- if (!this.interval)
- this.interval = setInterval(this.loop.bind(this), 15);
- },
- remove: function(effect) {
- this.effects = this.effects.reject(function(e) { return e==effect });
- if (this.effects.length == 0) {
- clearInterval(this.interval);
- this.interval = null;
- }
- },
- loop: function() {
- var timePos = new Date().getTime();
- for(var i=0, len=this.effects.length;i<len;i++)
- this.effects[i] && this.effects[i].loop(timePos);
- }
-});
-
-Effect.Queues = {
- instances: $H(),
- get: function(queueName) {
- if (!Object.isString(queueName)) return queueName;
-
- return this.instances.get(queueName) ||
- this.instances.set(queueName, new Effect.ScopedQueue());
- }
-};
-Effect.Queue = Effect.Queues.get('global');
-
-Effect.Base = Class.create({
- position: null,
- start: function(options) {
- if (options && options.transition === false) options.transition = Effect.Transitions.linear;
- this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
- this.currentFrame = 0;
- this.state = 'idle';
- this.startOn = this.options.delay*1000;
- this.finishOn = this.startOn+(this.options.duration*1000);
- this.fromToDelta = this.options.to-this.options.from;
- this.totalTime = this.finishOn-this.startOn;
- this.totalFrames = this.options.fps*this.options.duration;
-
- this.render = (function() {
- function dispatch(effect, eventName) {
- if (effect.options[eventName + 'Internal'])
- effect.options[eventName + 'Internal'](effect);
- if (effect.options[eventName])
- effect.options[eventName](effect);
- }
-
- return function(pos) {
- if (this.state === "idle") {
- this.state = "running";
- dispatch(this, 'beforeSetup');
- if (this.setup) this.setup();
- dispatch(this, 'afterSetup');
- }
- if (this.state === "running") {
- pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
- this.position = pos;
- dispatch(this, 'beforeUpdate');
- if (this.update) this.update(pos);
- dispatch(this, 'afterUpdate');
- }
- };
- })();
-
- this.event('beforeStart');
- if (!this.options.sync)
- Effect.Queues.get(Object.isString(this.options.queue) ?
- 'global' : this.options.queue.scope).add(this);
- },
- loop: function(timePos) {
- if (timePos >= this.startOn) {
- if (timePos >= this.finishOn) {
- this.render(1.0);
- this.cancel();
- this.event('beforeFinish');
- if (this.finish) this.finish();
- this.event('afterFinish');
- return;
- }
- var pos = (timePos - this.startOn) / this.totalTime,
- frame = (pos * this.totalFrames).round();
- if (frame > this.currentFrame) {
- this.render(pos);
- this.currentFrame = frame;
- }
- }
- },
- cancel: function() {
- if (!this.options.sync)
- Effect.Queues.get(Object.isString(this.options.queue) ?
- 'global' : this.options.queue.scope).remove(this);
- this.state = 'finished';
- },
- event: function(eventName) {
- if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
- if (this.options[eventName]) this.options[eventName](this);
- },
- inspect: function() {
- var data = $H();
- for(property in this)
- if (!Object.isFunction(this[property])) data.set(property, this[property]);
- return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
- }
-});
-
-Effect.Parallel = Class.create(Effect.Base, {
- initialize: function(effects) {
- this.effects = effects || [];
- this.start(arguments[1]);
- },
- update: function(position) {
- this.effects.invoke('render', position);
- },
- finish: function(position) {
- this.effects.each( function(effect) {
- effect.render(1.0);
- effect.cancel();
- effect.event('beforeFinish');
- if (effect.finish) effect.finish(position);
- effect.event('afterFinish');
- });
- }
-});
-
-Effect.Tween = Class.create(Effect.Base, {
- initialize: function(object, from, to) {
- object = Object.isString(object) ? $(object) : object;
- var args = $A(arguments), method = args.last(),
- options = args.length == 5 ? args[3] : null;
- this.method = Object.isFunction(method) ? method.bind(object) :
- Object.isFunction(object[method]) ? object[method].bind(object) :
- function(value) { object[method] = value };
- this.start(Object.extend({ from: from, to: to }, options || { }));
- },
- update: function(position) {
- this.method(position);
- }
-});
-
-Effect.Event = Class.create(Effect.Base, {
- initialize: function() {
- this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
- },
- update: Prototype.emptyFunction
-});
-
-Effect.Opacity = Class.create(Effect.Base, {
- initialize: function(element) {
- this.element = $(element);
- if (!this.element) throw(Effect._elementDoesNotExistError);
- // make this work on IE on elements without 'layout'
- if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
- this.element.setStyle({zoom: 1});
- var options = Object.extend({
- from: this.element.getOpacity() || 0.0,
- to: 1.0
- }, arguments[1] || { });
- this.start(options);
- },
- update: function(position) {
- this.element.setOpacity(position);
- }
-});
-
-Effect.Move = Class.create(Effect.Base, {
- initialize: function(element) {
- this.element = $(element);
- if (!this.element) throw(Effect._elementDoesNotExistError);
- var options = Object.extend({
- x: 0,
- y: 0,
- mode: 'relative'
- }, arguments[1] || { });
- this.start(options);
- },
- setup: function() {
- this.element.makePositioned();
- this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
- this.originalTop = parseFloat(this.element.getStyle('top') || '0');
- if (this.options.mode == 'absolute') {
- this.options.x = this.options.x - this.originalLeft;
- this.options.y = this.options.y - this.originalTop;
- }
- },
- update: function(position) {
- this.element.setStyle({
- left: (this.options.x * position + this.originalLeft).round() + 'px',
- top: (this.options.y * position + this.originalTop).round() + 'px'
- });
- }
-});
-
-// for backwards compatibility
-Effect.MoveBy = function(element, toTop, toLeft) {
- return new Effect.Move(element,
- Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
-};
-
-Effect.Scale = Class.create(Effect.Base, {
- initialize: function(element, percent) {
- this.element = $(element);
- if (!this.element) throw(Effect._elementDoesNotExistError);
- var options = Object.extend({
- scaleX: true,
- scaleY: true,
- scaleContent: true,
- scaleFromCenter: false,
- scaleMode: 'box', // 'box' or 'contents' or { } with provided values
- scaleFrom: 100.0,
- scaleTo: percent
- }, arguments[2] || { });
- this.start(options);
- },
- setup: function() {
- this.restoreAfterFinish = this.options.restoreAfterFinish || false;
- this.elementPositioning = this.element.getStyle('position');
-
- this.originalStyle = { };
- ['top','left','width','height','fontSize'].each( function(k) {
- this.originalStyle[k] = this.element.style[k];
- }.bind(this));
-
- this.originalTop = this.element.offsetTop;
- this.originalLeft = this.element.offsetLeft;
-
- var fontSize = this.element.getStyle('font-size') || '100%';
- ['em','px','%','pt'].each( function(fontSizeType) {
- if (fontSize.indexOf(fontSizeType)>0) {
- this.fontSize = parseFloat(fontSize);
- this.fontSizeType = fontSizeType;
- }
- }.bind(this));
-
- this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
-
- this.dims = null;
- if (this.options.scaleMode=='box')
- this.dims = [this.element.offsetHeight, this.element.offsetWidth];
- if (/^content/.test(this.options.scaleMode))
- this.dims = [this.element.scrollHeight, this.element.scrollWidth];
- if (!this.dims)
- this.dims = [this.options.scaleMode.originalHeight,
- this.options.scaleMode.originalWidth];
- },
- update: function(position) {
- var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
- if (this.options.scaleContent && this.fontSize)
- this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
- this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
- },
- finish: function(position) {
- if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
- },
- setDimensions: function(height, width) {
- var d = { };
- if (this.options.scaleX) d.width = width.round() + 'px';
- if (this.options.scaleY) d.height = height.round() + 'px';
- if (this.options.scaleFromCenter) {
- var topd = (height - this.dims[0])/2;
- var leftd = (width - this.dims[1])/2;
- if (this.elementPositioning == 'absolute') {
- if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
- if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
- } else {
- if (this.options.scaleY) d.top = -topd + 'px';
- if (this.options.scaleX) d.left = -leftd + 'px';
- }
- }
- this.element.setStyle(d);
- }
-});
-
-Effect.Highlight = Class.create(Effect.Base, {
- initialize: function(element) {
- this.element = $(element);
- if (!this.element) throw(Effect._elementDoesNotExistError);
- var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
- this.start(options);
- },
- setup: function() {
- // Prevent executing on elements not in the layout flow
- if (this.element.getStyle('display')=='none') { this.cancel(); return; }
- // Disable background image during the effect
- this.oldStyle = { };
- if (!this.options.keepBackgroundImage) {
- this.oldStyle.backgroundImage = this.element.getStyle('background-image');
- this.element.setStyle({backgroundImage: 'none'});
- }
- if (!this.options.endcolor)
- this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
- if (!this.options.restorecolor)
- this.options.restorecolor = this.element.getStyle('background-color');
- // init color calculations
- this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
- this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
- },
- update: function(position) {
- this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
- return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
- },
- finish: function() {
- this.element.setStyle(Object.extend(this.oldStyle, {
- backgroundColor: this.options.restorecolor
- }));
- }
-});
-
-Effect.ScrollTo = function(element) {
- var options = arguments[1] || { },
- scrollOffsets = document.viewport.getScrollOffsets(),
- elementOffsets = $(element).cumulativeOffset();
-
- if (options.offset) elementOffsets[1] += options.offset;
-
- return new Effect.Tween(null,
- scrollOffsets.top,
- elementOffsets[1],
- options,
- function(p){ scrollTo(scrollOffsets.left, p.round()); }
- );
-};
-
-/* ------------- combination effects ------------- */
-
-Effect.Fade = function(element) {
- element = $(element);
- var oldOpacity = element.getInlineOpacity();
- var options = Object.extend({
- from: element.getOpacity() || 1.0,
- to: 0.0,
- afterFinishInternal: function(effect) {
- if (effect.options.to!=0) return;
- effect.element.hide().setStyle({opacity: oldOpacity});
- }
- }, arguments[1] || { });
- return new Effect.Opacity(element,options);
-};
-
-Effect.Appear = function(element) {
- element = $(element);
- var options = Object.extend({
- from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
- to: 1.0,
- // force Safari to render floated elements properly
- afterFinishInternal: function(effect) {
- effect.element.forceRerendering();
- },
- beforeSetup: function(effect) {
- effect.element.setOpacity(effect.options.from).show();
- }}, arguments[1] || { });
- return new Effect.Opacity(element,options);
-};
-
-Effect.Puff = function(element) {
- element = $(element);
- var oldStyle = {
- opacity: element.getInlineOpacity(),
- position: element.getStyle('position'),
- top: element.style.top,
- left: element.style.left,
- width: element.style.width,
- height: element.style.height
- };
- return new Effect.Parallel(
- [ new Effect.Scale(element, 200,
- { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
- new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
- Object.extend({ duration: 1.0,
- beforeSetupInternal: function(effect) {
- Position.absolutize(effect.effects[0].element);
- },
- afterFinishInternal: function(effect) {
- effect.effects[0].element.hide().setStyle(oldStyle); }
- }, arguments[1] || { })
- );
-};
-
-Effect.BlindUp = function(element) {
- element = $(element);
- element.makeClipping();
- return new Effect.Scale(element, 0,
- Object.extend({ scaleContent: false,
- scaleX: false,
- restoreAfterFinish: true,
- afterFinishInternal: function(effect) {
- effect.element.hide().undoClipping();
- }
- }, arguments[1] || { })
- );
-};
-
-Effect.BlindDown = function(element) {
- element = $(element);
- var elementDimensions = element.getDimensions();
- return new Effect.Scale(element, 100, Object.extend({
- scaleContent: false,
- scaleX: false,
- scaleFrom: 0,
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
- restoreAfterFinish: true,
- afterSetup: function(effect) {
- effect.element.makeClipping().setStyle({height: '0px'}).show();
- },
- afterFinishInternal: function(effect) {
- effect.element.undoClipping();
- }
- }, arguments[1] || { }));
-};
-
-Effect.SwitchOff = function(element) {
- element = $(element);
- var oldOpacity = element.getInlineOpacity();
- return new Effect.Appear(element, Object.extend({
- duration: 0.4,
- from: 0,
- transition: Effect.Transitions.flicker,
- afterFinishInternal: function(effect) {
- new Effect.Scale(effect.element, 1, {
- duration: 0.3, scaleFromCenter: true,
- scaleX: false, scaleContent: false, restoreAfterFinish: true,
- beforeSetup: function(effect) {
- effect.element.makePositioned().makeClipping();
- },
- afterFinishInternal: function(effect) {
- effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
- }
- });
- }
- }, arguments[1] || { }));
-};
-
-Effect.DropOut = function(element) {
- element = $(element);
- var oldStyle = {
- top: element.getStyle('top'),
- left: element.getStyle('left'),
- opacity: element.getInlineOpacity() };
- return new Effect.Parallel(
- [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
- new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
- Object.extend(
- { duration: 0.5,
- beforeSetup: function(effect) {
- effect.effects[0].element.makePositioned();
- },
- afterFinishInternal: function(effect) {
- effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
- }
- }, arguments[1] || { }));
-};
-
-Effect.Shake = function(element) {
- element = $(element);
- var options = Object.extend({
- distance: 20,
- duration: 0.5
- }, arguments[1] || {});
- var distance = parseFloat(options.distance);
- var split = parseFloat(options.duration) / 10.0;
- var oldStyle = {
- top: element.getStyle('top'),
- left: element.getStyle('left') };
- return new Effect.Move(element,
- { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
- new Effect.Move(effect.element,
- { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
- new Effect.Move(effect.element,
- { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
- new Effect.Move(effect.element,
- { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
- new Effect.Move(effect.element,
- { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
- new Effect.Move(effect.element,
- { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
- effect.element.undoPositioned().setStyle(oldStyle);
- }}); }}); }}); }}); }}); }});
-};
-
-Effect.SlideDown = function(element) {
- element = $(element).cleanWhitespace();
- // SlideDown need to have the content of the element wrapped in a container element with fixed height!
- var oldInnerBottom = element.down().getStyle('bottom');
- var elementDimensions = element.getDimensions();
- return new Effect.Scale(element, 100, Object.extend({
- scaleContent: false,
- scaleX: false,
- scaleFrom: window.opera ? 0 : 1,
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
- restoreAfterFinish: true,
- afterSetup: function(effect) {
- effect.element.makePositioned();
- effect.element.down().makePositioned();
- if (window.opera) effect.element.setStyle({top: ''});
- effect.element.makeClipping().setStyle({height: '0px'}).show();
- },
- afterUpdateInternal: function(effect) {
- effect.element.down().setStyle({bottom:
- (effect.dims[0] - effect.element.clientHeight) + 'px' });
- },
- afterFinishInternal: function(effect) {
- effect.element.undoClipping().undoPositioned();
- effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
- }, arguments[1] || { })
- );
-};
-
-Effect.SlideUp = function(element) {
- element = $(element).cleanWhitespace();
- var oldInnerBottom = element.down().getStyle('bottom');
- var elementDimensions = element.getDimensions();
- return new Effect.Scale(element, window.opera ? 0 : 1,
- Object.extend({ scaleContent: false,
- scaleX: false,
- scaleMode: 'box',
- scaleFrom: 100,
- scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
- restoreAfterFinish: true,
- afterSetup: function(effect) {
- effect.element.makePositioned();
- effect.element.down().makePositioned();
- if (window.opera) effect.element.setStyle({top: ''});
- effect.element.makeClipping().show();
- },
- afterUpdateInternal: function(effect) {
- effect.element.down().setStyle({bottom:
- (effect.dims[0] - effect.element.clientHeight) + 'px' });
- },
- afterFinishInternal: function(effect) {
- effect.element.hide().undoClipping().undoPositioned();
- effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
- }
- }, arguments[1] || { })
- );
-};
-
-// Bug in opera makes the TD containing this element expand for a instance after finish
-Effect.Squish = function(element) {
- return new Effect.Scale(element, window.opera ? 1 : 0, {
- restoreAfterFinish: true,
- beforeSetup: function(effect) {
- effect.element.makeClipping();
- },
- afterFinishInternal: function(effect) {
- effect.element.hide().undoClipping();
- }
- });
-};
-
-Effect.Grow = function(element) {
- element = $(element);
- var options = Object.extend({
- direction: 'center',
- moveTransition: Effect.Transitions.sinoidal,
- scaleTransition: Effect.Transitions.sinoidal,
- opacityTransition: Effect.Transitions.full
- }, arguments[1] || { });
- var oldStyle = {
- top: element.style.top,
- left: element.style.left,
- height: element.style.height,
- width: element.style.width,
- opacity: element.getInlineOpacity() };
-
- var dims = element.getDimensions();
- var initialMoveX, initialMoveY;
- var moveX, moveY;
-
- switch (options.direction) {
- case 'top-left':
- initialMoveX = initialMoveY = moveX = moveY = 0;
- break;
- case 'top-right':
- initialMoveX = dims.width;
- initialMoveY = moveY = 0;
- moveX = -dims.width;
- break;
- case 'bottom-left':
- initialMoveX = moveX = 0;
- initialMoveY = dims.height;
- moveY = -dims.height;
- break;
- case 'bottom-right':
- initialMoveX = dims.width;
- initialMoveY = dims.height;
- moveX = -dims.width;
- moveY = -dims.height;
- break;
- case 'center':
- initialMoveX = dims.width / 2;
- initialMoveY = dims.height / 2;
- moveX = -dims.width / 2;
- moveY = -dims.height / 2;
- break;
- }
-
- return new Effect.Move(element, {
- x: initialMoveX,
- y: initialMoveY,
- duration: 0.01,
- beforeSetup: function(effect) {
- effect.element.hide().makeClipping().makePositioned();
- },
- afterFinishInternal: function(effect) {
- new Effect.Parallel(
- [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
- new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
- new Effect.Scale(effect.element, 100, {
- scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
- sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
- ], Object.extend({
- beforeSetup: function(effect) {
- effect.effects[0].element.setStyle({height: '0px'}).show();
- },
- afterFinishInternal: function(effect) {
- effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
- }
- }, options)
- );
- }
- });
-};
-
-Effect.Shrink = function(element) {
- element = $(element);
- var options = Object.extend({
- direction: 'center',
- moveTransition: Effect.Transitions.sinoidal,
- scaleTransition: Effect.Transitions.sinoidal,
- opacityTransition: Effect.Transitions.none
- }, arguments[1] || { });
- var oldStyle = {
- top: element.style.top,
- left: element.style.left,
- height: element.style.height,
- width: element.style.width,
- opacity: element.getInlineOpacity() };
-
- var dims = element.getDimensions();
- var moveX, moveY;
-
- switch (options.direction) {
- case 'top-left':
- moveX = moveY = 0;
- break;
- case 'top-right':
- moveX = dims.width;
- moveY = 0;
- break;
- case 'bottom-left':
- moveX = 0;
- moveY = dims.height;
- break;
- case 'bottom-right':
- moveX = dims.width;
- moveY = dims.height;
- break;
- case 'center':
- moveX = dims.width / 2;
- moveY = dims.height / 2;
- break;
- }
-
- return new Effect.Parallel(
- [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
- new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
- new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
- ], Object.extend({
- beforeStartInternal: function(effect) {
- effect.effects[0].element.makePositioned().makeClipping();
- },
- afterFinishInternal: function(effect) {
- effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
- }, options)
- );
-};
-
-Effect.Pulsate = function(element) {
- element = $(element);
- var options = arguments[1] || { },
- oldOpacity = element.getInlineOpacity(),
- transition = options.transition || Effect.Transitions.linear,
- reverser = function(pos){
- return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
- };
-
- return new Effect.Opacity(element,
- Object.extend(Object.extend({ duration: 2.0, from: 0,
- afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
- }, options), {transition: reverser}));
-};
-
-Effect.Fold = function(element) {
- element = $(element);
- var oldStyle = {
- top: element.style.top,
- left: element.style.left,
- width: element.style.width,
- height: element.style.height };
- element.makeClipping();
- return new Effect.Scale(element, 5, Object.extend({
- scaleContent: false,
- scaleX: false,
- afterFinishInternal: function(effect) {
- new Effect.Scale(element, 1, {
- scaleContent: false,
- scaleY: false,
- afterFinishInternal: function(effect) {
- effect.element.hide().undoClipping().setStyle(oldStyle);
- } });
- }}, arguments[1] || { }));
-};
-
-Effect.Morph = Class.create(Effect.Base, {
- initialize: function(element) {
- this.element = $(element);
- if (!this.element) throw(Effect._elementDoesNotExistError);
- var options = Object.extend({
- style: { }
- }, arguments[1] || { });
-
- if (!Object.isString(options.style)) this.style = $H(options.style);
- else {
- if (options.style.include(':'))
- this.style = options.style.parseStyle();
- else {
- this.element.addClassName(options.style);
- this.style = $H(this.element.getStyles());
- this.element.removeClassName(options.style);
- var css = this.element.getStyles();
- this.style = this.style.reject(function(style) {
- return style.value == css[style.key];
- });
- options.afterFinishInternal = function(effect) {
- effect.element.addClassName(effect.options.style);
- effect.transforms.each(function(transform) {
- effect.element.style[transform.style] = '';
- });
- };
- }
- }
- this.start(options);
- },
-
- setup: function(){
- function parseColor(color){
- if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
- color = color.parseColor();
- return $R(0,2).map(function(i){
- return parseInt( color.slice(i*2+1,i*2+3), 16 );
- });
- }
- this.transforms = this.style.map(function(pair){
- var property = pair[0], value = pair[1], unit = null;
-
- if (value.parseColor('#zzzzzz') != '#zzzzzz') {
- value = value.parseColor();
- unit = 'color';
- } else if (property == 'opacity') {
- value = parseFloat(value);
- if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
- this.element.setStyle({zoom: 1});
- } else if (Element.CSS_LENGTH.test(value)) {
- var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
- value = parseFloat(components[1]);
- unit = (components.length == 3) ? components[2] : null;
- }
-
- var originalValue = this.element.getStyle(property);
- return {
- style: property.camelize(),
- originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
- targetValue: unit=='color' ? parseColor(value) : value,
- unit: unit
- };
- }.bind(this)).reject(function(transform){
- return (
- (transform.originalValue == transform.targetValue) ||
- (
- transform.unit != 'color' &&
- (isNaN(transform.originalValue) || isNaN(transform.targetValue))
- )
- );
- });
- },
- update: function(position) {
- var style = { }, transform, i = this.transforms.length;
- while(i--)
- style[(transform = this.transforms[i]).style] =
- transform.unit=='color' ? '#'+
- (Math.round(transform.originalValue[0]+
- (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
- (Math.round(transform.originalValue[1]+
- (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
- (Math.round(transform.originalValue[2]+
- (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
- (transform.originalValue +
- (transform.targetValue - transform.originalValue) * position).toFixed(3) +
- (transform.unit === null ? '' : transform.unit);
- this.element.setStyle(style, true);
- }
-});
-
-Effect.Transform = Class.create({
- initialize: function(tracks){
- this.tracks = [];
- this.options = arguments[1] || { };
- this.addTracks(tracks);
- },
- addTracks: function(tracks){
- tracks.each(function(track){
- track = $H(track);
- var data = track.values().first();
- this.tracks.push($H({
- ids: track.keys().first(),
- effect: Effect.Morph,
- options: { style: data }
- }));
- }.bind(this));
- return this;
- },
- play: function(){
- return new Effect.Parallel(
- this.tracks.map(function(track){
- var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
- var elements = [$(ids) || $$(ids)].flatten();
- return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
- }).flatten(),
- this.options
- );
- }
-});
-
-Element.CSS_PROPERTIES = $w(
- 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
- 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
- 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
- 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
- 'fontSize fontWeight height left letterSpacing lineHeight ' +
- 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
- 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
- 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
- 'right textIndent top width wordSpacing zIndex');
-
-Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
-
-String.__parseStyleElement = document.createElement('div');
-String.prototype.parseStyle = function(){
- var style, styleRules = $H();
- if (Prototype.Browser.WebKit)
- style = new Element('div',{style:this}).style;
- else {
- String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
- style = String.__parseStyleElement.childNodes[0].style;
- }
-
- Element.CSS_PROPERTIES.each(function(property){
- if (style[property]) styleRules.set(property, style[property]);
- });
-
- if (Prototype.Browser.IE && this.include('opacity'))
- styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
-
- return styleRules;
-};
-
-if (document.defaultView && document.defaultView.getComputedStyle) {
- Element.getStyles = function(element) {
- var css = document.defaultView.getComputedStyle($(element), null);
- return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
- styles[property] = css[property];
- return styles;
- });
- };
-} else {
- Element.getStyles = function(element) {
- element = $(element);
- var css = element.currentStyle, styles;
- styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
- results[property] = css[property];
- return results;
- });
- if (!styles.opacity) styles.opacity = element.getOpacity();
- return styles;
- };
-}
-
-Effect.Methods = {
- morph: function(element, style) {
- element = $(element);
- new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
- return element;
- },
- visualEffect: function(element, effect, options) {
- element = $(element);
- var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
- new Effect[klass](element, options);
- return element;
- },
- highlight: function(element, options) {
- element = $(element);
- new Effect.Highlight(element, options);
- return element;
- }
-};
-
-$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
- 'pulsate shake puff squish switchOff dropOut').each(
- function(effect) {
- Effect.Methods[effect] = function(element, options){
- element = $(element);
- Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
- return element;
- };
- }
-);
-
-$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
- function(f) { Effect.Methods[f] = Element[f]; }
-);
-
-Element.addMethods(Effect.Methods); \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js
deleted file mode 100644
index aa3a4f34fd..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery.js
+++ /dev/null
@@ -1,8176 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.5
- * http://jquery.com/
- *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Mon Jan 31 08:31:29 2011 -0500
- */
-(function( window, undefined ) {
-
-// Use the correct document accordingly with window argument (sandbox)
-var document = window.document;
-var jQuery = (function() {
-
-// Define a local copy of jQuery
-var jQuery = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context, rootjQuery );
- },
-
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
-
- // Map over the $ in case of overwrite
- _$ = window.$,
-
- // A central reference to the root jQuery(document)
- rootjQuery,
-
- // A simple way to check for HTML strings or ID strings
- // (both of which we optimize for)
- quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,
-
- // Check if a string has a non-whitespace character in it
- rnotwhite = /\S/,
-
- // Used for trimming whitespace
- trimLeft = /^\s+/,
- trimRight = /\s+$/,
-
- // Check for digits
- rdigit = /\d/,
-
- // Match a standalone tag
- rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
-
- // JSON RegExp
- rvalidchars = /^[\],:{}\s]*$/,
- rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
- rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
- rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
-
- // Useragent RegExp
- rwebkit = /(webkit)[ \/]([\w.]+)/,
- ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
- rmsie = /(msie) ([\w.]+)/,
- rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
-
- // Keep a UserAgent string for use with jQuery.browser
- userAgent = navigator.userAgent,
-
- // For matching the engine and version of the browser
- browserMatch,
-
- // Has the ready events already been bound?
- readyBound = false,
-
- // The deferred used on DOM ready
- readyList,
-
- // Promise methods
- promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
-
- // The ready event handler
- DOMContentLoaded,
-
- // Save a reference to some core methods
- toString = Object.prototype.toString,
- hasOwn = Object.prototype.hasOwnProperty,
- push = Array.prototype.push,
- slice = Array.prototype.slice,
- trim = String.prototype.trim,
- indexOf = Array.prototype.indexOf,
-
- // [[Class]] -> type pairs
- class2type = {};
-
-jQuery.fn = jQuery.prototype = {
- constructor: jQuery,
- init: function( selector, context, rootjQuery ) {
- var match, elem, ret, doc;
-
- // Handle $(""), $(null), or $(undefined)
- if ( !selector ) {
- return this;
- }
-
- // Handle $(DOMElement)
- if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
- }
-
- // The body element only exists once, optimize finding it
- if ( selector === "body" && !context && document.body ) {
- this.context = document;
- this[0] = document.body;
- this.selector = "body";
- this.length = 1;
- return this;
- }
-
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- // Are we dealing with HTML string or an ID?
- match = quickExpr.exec( selector );
-
- // Verify a match, and that no context was specified for #id
- if ( match && (match[1] || !context) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[1] ) {
- context = context instanceof jQuery ? context[0] : context;
- doc = (context ? context.ownerDocument || context : document);
-
- // If a single string is passed in and it's a single tag
- // just do a createElement and skip the rest
- ret = rsingleTag.exec( selector );
-
- if ( ret ) {
- if ( jQuery.isPlainObject( context ) ) {
- selector = [ document.createElement( ret[1] ) ];
- jQuery.fn.attr.call( selector, context, true );
-
- } else {
- selector = [ doc.createElement( ret[1] ) ];
- }
-
- } else {
- ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
- selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
- }
-
- return jQuery.merge( this, selector );
-
- // HANDLE: $("#id")
- } else {
- elem = document.getElementById( match[2] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id !== match[2] ) {
- return rootjQuery.find( selector );
- }
-
- // Otherwise, we inject the element directly into the jQuery object
- this.length = 1;
- this[0] = elem;
- }
-
- this.context = document;
- this.selector = selector;
- return this;
- }
-
- // HANDLE: $(expr, $(...))
- } else if ( !context || context.jquery ) {
- return (context || rootjQuery).find( selector );
-
- // HANDLE: $(expr, context)
- // (which is just equivalent to: $(context).find(expr)
- } else {
- return this.constructor( context ).find( selector );
- }
-
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( jQuery.isFunction( selector ) ) {
- return rootjQuery.ready( selector );
- }
-
- if (selector.selector !== undefined) {
- this.selector = selector.selector;
- this.context = selector.context;
- }
-
- return jQuery.makeArray( selector, this );
- },
-
- // Start with an empty selector
- selector: "",
-
- // The current version of jQuery being used
- jquery: "1.5",
-
- // The default length of a jQuery object is 0
- length: 0,
-
- // The number of elements contained in the matched element set
- size: function() {
- return this.length;
- },
-
- toArray: function() {
- return slice.call( this, 0 );
- },
-
- // Get the Nth element in the matched element set OR
- // Get the whole matched element set as a clean array
- get: function( num ) {
- return num == null ?
-
- // Return a 'clean' array
- this.toArray() :
-
- // Return just the object
- ( num < 0 ? this[ this.length + num ] : this[ num ] );
- },
-
- // Take an array of elements and push it onto the stack
- // (returning the new matched element set)
- pushStack: function( elems, name, selector ) {
- // Build a new jQuery matched element set
- var ret = this.constructor();
-
- if ( jQuery.isArray( elems ) ) {
- push.apply( ret, elems );
-
- } else {
- jQuery.merge( ret, elems );
- }
-
- // Add the old object onto the stack (as a reference)
- ret.prevObject = this;
-
- ret.context = this.context;
-
- if ( name === "find" ) {
- ret.selector = this.selector + (this.selector ? " " : "") + selector;
- } else if ( name ) {
- ret.selector = this.selector + "." + name + "(" + selector + ")";
- }
-
- // Return the newly-formed element set
- return ret;
- },
-
- // Execute a callback for every element in the matched set.
- // (You can seed the arguments with an array of args, but this is
- // only used internally.)
- each: function( callback, args ) {
- return jQuery.each( this, callback, args );
- },
-
- ready: function( fn ) {
- // Attach the listeners
- jQuery.bindReady();
-
- // Add the callback
- readyList.done( fn );
-
- return this;
- },
-
- eq: function( i ) {
- return i === -1 ?
- this.slice( i ) :
- this.slice( i, +i + 1 );
- },
-
- first: function() {
- return this.eq( 0 );
- },
-
- last: function() {
- return this.eq( -1 );
- },
-
- slice: function() {
- return this.pushStack( slice.apply( this, arguments ),
- "slice", slice.call(arguments).join(",") );
- },
-
- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
- return callback.call( elem, i, elem );
- }));
- },
-
- end: function() {
- return this.prevObject || this.constructor(null);
- },
-
- // For internal use only.
- // Behaves like an Array's method, not like a jQuery method.
- push: push,
- sort: [].sort,
- splice: [].splice
-};
-
-// Give the init function the jQuery prototype for later instantiation
-jQuery.fn.init.prototype = jQuery.fn;
-
-jQuery.extend = jQuery.fn.extend = function() {
- var options, name, src, copy, copyIsArray, clone,
- target = arguments[0] || {},
- i = 1,
- length = arguments.length,
- deep = false;
-
- // Handle a deep copy situation
- if ( typeof target === "boolean" ) {
- deep = target;
- target = arguments[1] || {};
- // skip the boolean and the target
- i = 2;
- }
-
- // Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
- target = {};
- }
-
- // extend jQuery itself if only one argument is passed
- if ( length === i ) {
- target = this;
- --i;
- }
-
- for ( ; i < length; i++ ) {
- // Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null ) {
- // Extend the base object
- for ( name in options ) {
- src = target[ name ];
- copy = options[ name ];
-
- // Prevent never-ending loop
- if ( target === copy ) {
- continue;
- }
-
- // Recurse if we're merging plain objects or arrays
- if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
- if ( copyIsArray ) {
- copyIsArray = false;
- clone = src && jQuery.isArray(src) ? src : [];
-
- } else {
- clone = src && jQuery.isPlainObject(src) ? src : {};
- }
-
- // Never move original objects, clone them
- target[ name ] = jQuery.extend( deep, clone, copy );
-
- // Don't bring in undefined values
- } else if ( copy !== undefined ) {
- target[ name ] = copy;
- }
- }
- }
- }
-
- // Return the modified object
- return target;
-};
-
-jQuery.extend({
- noConflict: function( deep ) {
- window.$ = _$;
-
- if ( deep ) {
- window.jQuery = _jQuery;
- }
-
- return jQuery;
- },
-
- // Is the DOM ready to be used? Set to true once it occurs.
- isReady: false,
-
- // A counter to track how many items to wait for before
- // the ready event fires. See #6781
- readyWait: 1,
-
- // Handle when the DOM is ready
- ready: function( wait ) {
- // A third-party is pushing the ready event forwards
- if ( wait === true ) {
- jQuery.readyWait--;
- }
-
- // Make sure that the DOM is not already loaded
- if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( !document.body ) {
- return setTimeout( jQuery.ready, 1 );
- }
-
- // Remember that the DOM is ready
- jQuery.isReady = true;
-
- // If a normal DOM Ready event fired, decrement, and wait if need be
- if ( wait !== true && --jQuery.readyWait > 0 ) {
- return;
- }
-
- // If there are functions bound, to execute
- readyList.resolveWith( document, [ jQuery ] );
-
- // Trigger any bound ready events
- if ( jQuery.fn.trigger ) {
- jQuery( document ).trigger( "ready" ).unbind( "ready" );
- }
- }
- },
-
- bindReady: function() {
- if ( readyBound ) {
- return;
- }
-
- readyBound = true;
-
- // Catch cases where $(document).ready() is called after the
- // browser event has already occurred.
- if ( document.readyState === "complete" ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- return setTimeout( jQuery.ready, 1 );
- }
-
- // Mozilla, Opera and webkit nightlies currently support this event
- if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-
- // A fallback to window.onload, that will always work
- window.addEventListener( "load", jQuery.ready, false );
-
- // If IE event model is used
- } else if ( document.attachEvent ) {
- // ensure firing before onload,
- // maybe late but safe also for iframes
- document.attachEvent("onreadystatechange", DOMContentLoaded);
-
- // A fallback to window.onload, that will always work
- window.attachEvent( "onload", jQuery.ready );
-
- // If IE and not a frame
- // continually check to see if the document is ready
- var toplevel = false;
-
- try {
- toplevel = window.frameElement == null;
- } catch(e) {}
-
- if ( document.documentElement.doScroll && toplevel ) {
- doScrollCheck();
- }
- }
- },
-
- // See test/unit/core.js for details concerning isFunction.
- // Since version 1.3, DOM methods and functions like alert
- // aren't supported. They return false on IE (#2968).
- isFunction: function( obj ) {
- return jQuery.type(obj) === "function";
- },
-
- isArray: Array.isArray || function( obj ) {
- return jQuery.type(obj) === "array";
- },
-
- // A crude way of determining if an object is a window
- isWindow: function( obj ) {
- return obj && typeof obj === "object" && "setInterval" in obj;
- },
-
- isNaN: function( obj ) {
- return obj == null || !rdigit.test( obj ) || isNaN( obj );
- },
-
- type: function( obj ) {
- return obj == null ?
- String( obj ) :
- class2type[ toString.call(obj) ] || "object";
- },
-
- isPlainObject: function( obj ) {
- // Must be an Object.
- // Because of IE, we also have to check the presence of the constructor property.
- // Make sure that DOM nodes and window objects don't pass through, as well
- if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
- return false;
- }
-
- // Not own constructor property must be Object
- if ( obj.constructor &&
- !hasOwn.call(obj, "constructor") &&
- !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
- return false;
- }
-
- // Own properties are enumerated firstly, so to speed up,
- // if last one is own, then all properties are own.
-
- var key;
- for ( key in obj ) {}
-
- return key === undefined || hasOwn.call( obj, key );
- },
-
- isEmptyObject: function( obj ) {
- for ( var name in obj ) {
- return false;
- }
- return true;
- },
-
- error: function( msg ) {
- throw msg;
- },
-
- parseJSON: function( data ) {
- if ( typeof data !== "string" || !data ) {
- return null;
- }
-
- // Make sure leading/trailing whitespace is removed (IE can't handle it)
- data = jQuery.trim( data );
-
- // Make sure the incoming data is actual JSON
- // Logic borrowed from http://json.org/json2.js
- if ( rvalidchars.test(data.replace(rvalidescape, "@")
- .replace(rvalidtokens, "]")
- .replace(rvalidbraces, "")) ) {
-
- // Try to use the native JSON parser first
- return window.JSON && window.JSON.parse ?
- window.JSON.parse( data ) :
- (new Function("return " + data))();
-
- } else {
- jQuery.error( "Invalid JSON: " + data );
- }
- },
-
- // Cross-browser xml parsing
- // (xml & tmp used internally)
- parseXML: function( data , xml , tmp ) {
-
- if ( window.DOMParser ) { // Standard
- tmp = new DOMParser();
- xml = tmp.parseFromString( data , "text/xml" );
- } else { // IE
- xml = new ActiveXObject( "Microsoft.XMLDOM" );
- xml.async = "false";
- xml.loadXML( data );
- }
-
- tmp = xml.documentElement;
-
- if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
- jQuery.error( "Invalid XML: " + data );
- }
-
- return xml;
- },
-
- noop: function() {},
-
- // Evalulates a script in a global context
- globalEval: function( data ) {
- if ( data && rnotwhite.test(data) ) {
- // Inspired by code by Andrea Giammarchi
- // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
- var head = document.getElementsByTagName("head")[0] || document.documentElement,
- script = document.createElement("script");
-
- script.type = "text/javascript";
-
- if ( jQuery.support.scriptEval() ) {
- script.appendChild( document.createTextNode( data ) );
- } else {
- script.text = data;
- }
-
- // Use insertBefore instead of appendChild to circumvent an IE6 bug.
- // This arises when a base node is used (#2709).
- head.insertBefore( script, head.firstChild );
- head.removeChild( script );
- }
- },
-
- nodeName: function( elem, name ) {
- return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
- },
-
- // args is for internal usage only
- each: function( object, callback, args ) {
- var name, i = 0,
- length = object.length,
- isObj = length === undefined || jQuery.isFunction(object);
-
- if ( args ) {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.apply( object[ name ], args ) === false ) {
- break;
- }
- }
- } else {
- for ( ; i < length; ) {
- if ( callback.apply( object[ i++ ], args ) === false ) {
- break;
- }
- }
- }
-
- // A special, fast, case for the most common use of each
- } else {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
- break;
- }
- }
- } else {
- for ( var value = object[0];
- i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
- }
- }
-
- return object;
- },
-
- // Use native String.trim function wherever possible
- trim: trim ?
- function( text ) {
- return text == null ?
- "" :
- trim.call( text );
- } :
-
- // Otherwise use our own trimming functionality
- function( text ) {
- return text == null ?
- "" :
- text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
- },
-
- // results is for internal usage only
- makeArray: function( array, results ) {
- var ret = results || [];
-
- if ( array != null ) {
- // The window, strings (and functions) also have 'length'
- // The extra typeof function check is to prevent crashes
- // in Safari 2 (See: #3039)
- // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
- var type = jQuery.type(array);
-
- if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
- push.call( ret, array );
- } else {
- jQuery.merge( ret, array );
- }
- }
-
- return ret;
- },
-
- inArray: function( elem, array ) {
- if ( array.indexOf ) {
- return array.indexOf( elem );
- }
-
- for ( var i = 0, length = array.length; i < length; i++ ) {
- if ( array[ i ] === elem ) {
- return i;
- }
- }
-
- return -1;
- },
-
- merge: function( first, second ) {
- var i = first.length,
- j = 0;
-
- if ( typeof second.length === "number" ) {
- for ( var l = second.length; j < l; j++ ) {
- first[ i++ ] = second[ j ];
- }
-
- } else {
- while ( second[j] !== undefined ) {
- first[ i++ ] = second[ j++ ];
- }
- }
-
- first.length = i;
-
- return first;
- },
-
- grep: function( elems, callback, inv ) {
- var ret = [], retVal;
- inv = !!inv;
-
- // Go through the array, only saving the items
- // that pass the validator function
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- retVal = !!callback( elems[ i ], i );
- if ( inv !== retVal ) {
- ret.push( elems[ i ] );
- }
- }
-
- return ret;
- },
-
- // arg is for internal usage only
- map: function( elems, callback, arg ) {
- var ret = [], value;
-
- // Go through the array, translating each of the items to their
- // new value (or values).
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- value = callback( elems[ i ], i, arg );
-
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
-
- // Flatten any nested arrays
- return ret.concat.apply( [], ret );
- },
-
- // A global GUID counter for objects
- guid: 1,
-
- proxy: function( fn, proxy, thisObject ) {
- if ( arguments.length === 2 ) {
- if ( typeof proxy === "string" ) {
- thisObject = fn;
- fn = thisObject[ proxy ];
- proxy = undefined;
-
- } else if ( proxy && !jQuery.isFunction( proxy ) ) {
- thisObject = proxy;
- proxy = undefined;
- }
- }
-
- if ( !proxy && fn ) {
- proxy = function() {
- return fn.apply( thisObject || this, arguments );
- };
- }
-
- // Set the guid of unique handler to the same of original handler, so it can be removed
- if ( fn ) {
- proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
- }
-
- // So proxy can be declared as an argument
- return proxy;
- },
-
- // Mutifunctional method to get and set values to a collection
- // The value/s can be optionally by executed if its a function
- access: function( elems, key, value, exec, fn, pass ) {
- var length = elems.length;
-
- // Setting many attributes
- if ( typeof key === "object" ) {
- for ( var k in key ) {
- jQuery.access( elems, k, key[k], exec, fn, value );
- }
- return elems;
- }
-
- // Setting one attribute
- if ( value !== undefined ) {
- // Optionally, function values get executed if exec is true
- exec = !pass && exec && jQuery.isFunction(value);
-
- for ( var i = 0; i < length; i++ ) {
- fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
- }
-
- return elems;
- }
-
- // Getting an attribute
- return length ? fn( elems[0], key ) : undefined;
- },
-
- now: function() {
- return (new Date()).getTime();
- },
-
- // Create a simple deferred (one callbacks list)
- _Deferred: function() {
- var // callbacks list
- callbacks = [],
- // stored [ context , args ]
- fired,
- // to avoid firing when already doing so
- firing,
- // flag to know if the deferred has been cancelled
- cancelled,
- // the deferred itself
- deferred = {
-
- // done( f1, f2, ...)
- done: function() {
- if ( !cancelled ) {
- var args = arguments,
- i,
- length,
- elem,
- type,
- _fired;
- if ( fired ) {
- _fired = fired;
- fired = 0;
- }
- for ( i = 0, length = args.length; i < length; i++ ) {
- elem = args[ i ];
- type = jQuery.type( elem );
- if ( type === "array" ) {
- deferred.done.apply( deferred, elem );
- } else if ( type === "function" ) {
- callbacks.push( elem );
- }
- }
- if ( _fired ) {
- deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
- }
- }
- return this;
- },
-
- // resolve with given context and args
- resolveWith: function( context, args ) {
- if ( !cancelled && !fired && !firing ) {
- firing = 1;
- try {
- while( callbacks[ 0 ] ) {
- callbacks.shift().apply( context, args );
- }
- }
- finally {
- fired = [ context, args ];
- firing = 0;
- }
- }
- return this;
- },
-
- // resolve with this as context and given arguments
- resolve: function() {
- deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments );
- return this;
- },
-
- // Has this deferred been resolved?
- isResolved: function() {
- return !!( firing || fired );
- },
-
- // Cancel
- cancel: function() {
- cancelled = 1;
- callbacks = [];
- return this;
- }
- };
-
- return deferred;
- },
-
- // Full fledged deferred (two callbacks list)
- Deferred: function( func ) {
- var deferred = jQuery._Deferred(),
- failDeferred = jQuery._Deferred(),
- promise;
- // Add errorDeferred methods, then and promise
- jQuery.extend( deferred, {
- then: function( doneCallbacks, failCallbacks ) {
- deferred.done( doneCallbacks ).fail( failCallbacks );
- return this;
- },
- fail: failDeferred.done,
- rejectWith: failDeferred.resolveWith,
- reject: failDeferred.resolve,
- isRejected: failDeferred.isResolved,
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj , i /* internal */ ) {
- if ( obj == null ) {
- if ( promise ) {
- return promise;
- }
- promise = obj = {};
- }
- i = promiseMethods.length;
- while( i-- ) {
- obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ];
- }
- return obj;
- }
- } );
- // Make sure only one callback list will be used
- deferred.then( failDeferred.cancel, deferred.cancel );
- // Unexpose cancel
- delete deferred.cancel;
- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
- }
- return deferred;
- },
-
- // Deferred helper
- when: function( object ) {
- var args = arguments,
- length = args.length,
- deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ?
- object :
- jQuery.Deferred(),
- promise = deferred.promise(),
- resolveArray;
-
- if ( length > 1 ) {
- resolveArray = new Array( length );
- jQuery.each( args, function( index, element ) {
- jQuery.when( element ).then( function( value ) {
- resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
- if( ! --length ) {
- deferred.resolveWith( promise, resolveArray );
- }
- }, deferred.reject );
- } );
- } else if ( deferred !== object ) {
- deferred.resolve( object );
- }
- return promise;
- },
-
- // Use of jQuery.browser is frowned upon.
- // More details: http://docs.jquery.com/Utilities/jQuery.browser
- uaMatch: function( ua ) {
- ua = ua.toLowerCase();
-
- var match = rwebkit.exec( ua ) ||
- ropera.exec( ua ) ||
- rmsie.exec( ua ) ||
- ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
- [];
-
- return { browser: match[1] || "", version: match[2] || "0" };
- },
-
- sub: function() {
- function jQuerySubclass( selector, context ) {
- return new jQuerySubclass.fn.init( selector, context );
- }
- jQuery.extend( true, jQuerySubclass, this );
- jQuerySubclass.superclass = this;
- jQuerySubclass.fn = jQuerySubclass.prototype = this();
- jQuerySubclass.fn.constructor = jQuerySubclass;
- jQuerySubclass.subclass = this.subclass;
- jQuerySubclass.fn.init = function init( selector, context ) {
- if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
- context = jQuerySubclass(context);
- }
-
- return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
- };
- jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
- var rootjQuerySubclass = jQuerySubclass(document);
- return jQuerySubclass;
- },
-
- browser: {}
-});
-
-// Create readyList deferred
-readyList = jQuery._Deferred();
-
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
- class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-browserMatch = jQuery.uaMatch( userAgent );
-if ( browserMatch.browser ) {
- jQuery.browser[ browserMatch.browser ] = true;
- jQuery.browser.version = browserMatch.version;
-}
-
-// Deprecated, use jQuery.browser.webkit instead
-if ( jQuery.browser.webkit ) {
- jQuery.browser.safari = true;
-}
-
-if ( indexOf ) {
- jQuery.inArray = function( elem, array ) {
- return indexOf.call( array, elem );
- };
-}
-
-// IE doesn't match non-breaking spaces with \s
-if ( rnotwhite.test( "\xA0" ) ) {
- trimLeft = /^[\s\xA0]+/;
- trimRight = /[\s\xA0]+$/;
-}
-
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-
-// Cleanup functions for the document ready method
-if ( document.addEventListener ) {
- DOMContentLoaded = function() {
- document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
- jQuery.ready();
- };
-
-} else if ( document.attachEvent ) {
- DOMContentLoaded = function() {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( document.readyState === "complete" ) {
- document.detachEvent( "onreadystatechange", DOMContentLoaded );
- jQuery.ready();
- }
- };
-}
-
-// The DOM ready check for Internet Explorer
-function doScrollCheck() {
- if ( jQuery.isReady ) {
- return;
- }
-
- try {
- // If IE is used, use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- document.documentElement.doScroll("left");
- } catch(e) {
- setTimeout( doScrollCheck, 1 );
- return;
- }
-
- // and execute any waiting functions
- jQuery.ready();
-}
-
-// Expose jQuery to the global object
-return (window.jQuery = window.$ = jQuery);
-
-})();
-
-
-(function() {
-
- jQuery.support = {};
-
- var div = document.createElement("div");
-
- div.style.display = "none";
- div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
-
- var all = div.getElementsByTagName("*"),
- a = div.getElementsByTagName("a")[0],
- select = document.createElement("select"),
- opt = select.appendChild( document.createElement("option") );
-
- // Can't get basic test support
- if ( !all || !all.length || !a ) {
- return;
- }
-
- jQuery.support = {
- // IE strips leading whitespace when .innerHTML is used
- leadingWhitespace: div.firstChild.nodeType === 3,
-
- // Make sure that tbody elements aren't automatically inserted
- // IE will insert them into empty tables
- tbody: !div.getElementsByTagName("tbody").length,
-
- // Make sure that link elements get serialized correctly by innerHTML
- // This requires a wrapper element in IE
- htmlSerialize: !!div.getElementsByTagName("link").length,
-
- // Get the style information from getAttribute
- // (IE uses .cssText insted)
- style: /red/.test( a.getAttribute("style") ),
-
- // Make sure that URLs aren't manipulated
- // (IE normalizes it by default)
- hrefNormalized: a.getAttribute("href") === "/a",
-
- // Make sure that element opacity exists
- // (IE uses filter instead)
- // Use a regex to work around a WebKit issue. See #5145
- opacity: /^0.55$/.test( a.style.opacity ),
-
- // Verify style float existence
- // (IE uses styleFloat instead of cssFloat)
- cssFloat: !!a.style.cssFloat,
-
- // Make sure that if no value is specified for a checkbox
- // that it defaults to "on".
- // (WebKit defaults to "" instead)
- checkOn: div.getElementsByTagName("input")[0].value === "on",
-
- // Make sure that a selected-by-default option has a working selected property.
- // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
- optSelected: opt.selected,
-
- // Will be defined later
- deleteExpando: true,
- optDisabled: false,
- checkClone: false,
- _scriptEval: null,
- noCloneEvent: true,
- boxModel: null,
- inlineBlockNeedsLayout: false,
- shrinkWrapBlocks: false,
- reliableHiddenOffsets: true
- };
-
- // Make sure that the options inside disabled selects aren't marked as disabled
- // (WebKit marks them as diabled)
- select.disabled = true;
- jQuery.support.optDisabled = !opt.disabled;
-
- jQuery.support.scriptEval = function() {
- if ( jQuery.support._scriptEval === null ) {
- var root = document.documentElement,
- script = document.createElement("script"),
- id = "script" + jQuery.now();
-
- script.type = "text/javascript";
- try {
- script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
- } catch(e) {}
-
- root.insertBefore( script, root.firstChild );
-
- // Make sure that the execution of code works by injecting a script
- // tag with appendChild/createTextNode
- // (IE doesn't support this, fails, and uses .text instead)
- if ( window[ id ] ) {
- jQuery.support._scriptEval = true;
- delete window[ id ];
- } else {
- jQuery.support._scriptEval = false;
- }
-
- root.removeChild( script );
- // release memory in IE
- root = script = id = null;
- }
-
- return jQuery.support._scriptEval;
- };
-
- // Test to see if it's possible to delete an expando from an element
- // Fails in Internet Explorer
- try {
- delete div.test;
-
- } catch(e) {
- jQuery.support.deleteExpando = false;
- }
-
- if ( div.attachEvent && div.fireEvent ) {
- div.attachEvent("onclick", function click() {
- // Cloning a node shouldn't copy over any
- // bound event handlers (IE does this)
- jQuery.support.noCloneEvent = false;
- div.detachEvent("onclick", click);
- });
- div.cloneNode(true).fireEvent("onclick");
- }
-
- div = document.createElement("div");
- div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
-
- var fragment = document.createDocumentFragment();
- fragment.appendChild( div.firstChild );
-
- // WebKit doesn't clone checked state correctly in fragments
- jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
-
- // Figure out if the W3C box model works as expected
- // document.body must exist before we can do this
- jQuery(function() {
- var div = document.createElement("div"),
- body = document.getElementsByTagName("body")[0];
-
- // Frameset documents with no body should not run this code
- if ( !body ) {
- return;
- }
-
- div.style.width = div.style.paddingLeft = "1px";
- body.appendChild( div );
- jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
-
- if ( "zoom" in div.style ) {
- // Check if natively block-level elements act like inline-block
- // elements when setting their display to 'inline' and giving
- // them layout
- // (IE < 8 does this)
- div.style.display = "inline";
- div.style.zoom = 1;
- jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2;
-
- // Check if elements with layout shrink-wrap their children
- // (IE 6 does this)
- div.style.display = "";
- div.innerHTML = "<div style='width:4px;'></div>";
- jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2;
- }
-
- div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
- var tds = div.getElementsByTagName("td");
-
- // Check if table cells still have offsetWidth/Height when they are set
- // to display:none and there are still other visible table cells in a
- // table row; if so, offsetWidth/Height are not reliable for use when
- // determining if an element has been hidden directly using
- // display:none (it is still safe to use offsets if a parent element is
- // hidden; don safety goggles and see bug #4512 for more information).
- // (only IE 8 fails this test)
- jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0;
-
- tds[0].style.display = "";
- tds[1].style.display = "none";
-
- // Check if empty table cells still have offsetWidth/Height
- // (IE < 8 fail this test)
- jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
- div.innerHTML = "";
-
- body.removeChild( div ).style.display = "none";
- div = tds = null;
- });
-
- // Technique from Juriy Zaytsev
- // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
- var eventSupported = function( eventName ) {
- var el = document.createElement("div");
- eventName = "on" + eventName;
-
- // We only care about the case where non-standard event systems
- // are used, namely in IE. Short-circuiting here helps us to
- // avoid an eval call (in setAttribute) which can cause CSP
- // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
- if ( !el.attachEvent ) {
- return true;
- }
-
- var isSupported = (eventName in el);
- if ( !isSupported ) {
- el.setAttribute(eventName, "return;");
- isSupported = typeof el[eventName] === "function";
- }
- el = null;
-
- return isSupported;
- };
-
- jQuery.support.submitBubbles = eventSupported("submit");
- jQuery.support.changeBubbles = eventSupported("change");
-
- // release memory in IE
- div = all = a = null;
-})();
-
-
-
-var rbrace = /^(?:\{.*\}|\[.*\])$/;
-
-jQuery.extend({
- cache: {},
-
- // Please use with caution
- uuid: 0,
-
- // Unique for each copy of jQuery on the page
- // Non-digits removed to match rinlinejQuery
- expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
-
- // The following elements throw uncatchable exceptions if you
- // attempt to add expando properties to them.
- noData: {
- "embed": true,
- // Ban all objects except for Flash (which handle expandos)
- "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
- "applet": true
- },
-
- hasData: function( elem ) {
- elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
-
- return !!elem && !jQuery.isEmptyObject(elem);
- },
-
- data: function( elem, name, data, pvt /* Internal Use Only */ ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
-
- var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
-
- // We have to handle DOM nodes and JS objects differently because IE6-7
- // can't GC object references properly across the DOM-JS boundary
- isNode = elem.nodeType,
-
- // Only DOM nodes need the global jQuery cache; JS object data is
- // attached directly to the object so GC can occur automatically
- cache = isNode ? jQuery.cache : elem,
-
- // Only defining an ID for JS objects if its cache already exists allows
- // the code to shortcut on the same path as a DOM node with no cache
- id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
-
- // Avoid doing any more work than we need to when trying to get data on an
- // object that has no data at all
- if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
- return;
- }
-
- if ( !id ) {
- // Only DOM nodes need a new unique ID for each element since their data
- // ends up in the global cache
- if ( isNode ) {
- elem[ jQuery.expando ] = id = ++jQuery.uuid;
- } else {
- id = jQuery.expando;
- }
- }
-
- if ( !cache[ id ] ) {
- cache[ id ] = {};
- }
-
- // An object can be passed to jQuery.data instead of a key/value pair; this gets
- // shallow copied over onto the existing cache
- if ( typeof name === "object" ) {
- if ( pvt ) {
- cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
- } else {
- cache[ id ] = jQuery.extend(cache[ id ], name);
- }
- }
-
- thisCache = cache[ id ];
-
- // Internal jQuery data is stored in a separate object inside the object's data
- // cache in order to avoid key collisions between internal data and user-defined
- // data
- if ( pvt ) {
- if ( !thisCache[ internalKey ] ) {
- thisCache[ internalKey ] = {};
- }
-
- thisCache = thisCache[ internalKey ];
- }
-
- if ( data !== undefined ) {
- thisCache[ name ] = data;
- }
-
- // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
- // not attempt to inspect the internal events object using jQuery.data, as this
- // internal data object is undocumented and subject to change.
- if ( name === "events" && !thisCache[name] ) {
- return thisCache[ internalKey ] && thisCache[ internalKey ].events;
- }
-
- return getByName ? thisCache[ name ] : thisCache;
- },
-
- removeData: function( elem, name, pvt /* Internal Use Only */ ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
-
- var internalKey = jQuery.expando, isNode = elem.nodeType,
-
- // See jQuery.data for more information
- cache = isNode ? jQuery.cache : elem,
-
- // See jQuery.data for more information
- id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
-
- // If there is already no cache entry for this object, there is no
- // purpose in continuing
- if ( !cache[ id ] ) {
- return;
- }
-
- if ( name ) {
- var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
-
- if ( thisCache ) {
- delete thisCache[ name ];
-
- // If there is no data left in the cache, we want to continue
- // and let the cache object itself get destroyed
- if ( !jQuery.isEmptyObject(thisCache) ) {
- return;
- }
- }
- }
-
- // See jQuery.data for more information
- if ( pvt ) {
- delete cache[ id ][ internalKey ];
-
- // Don't destroy the parent cache unless the internal data object
- // had been the only thing left in it
- if ( !jQuery.isEmptyObject(cache[ id ]) ) {
- return;
- }
- }
-
- var internalCache = cache[ id ][ internalKey ];
-
- // Browsers that fail expando deletion also refuse to delete expandos on
- // the window, but it will allow it on all other JS objects; other browsers
- // don't care
- if ( jQuery.support.deleteExpando || cache != window ) {
- delete cache[ id ];
- } else {
- cache[ id ] = null;
- }
-
- // We destroyed the entire user cache at once because it's faster than
- // iterating through each key, but we need to continue to persist internal
- // data if it existed
- if ( internalCache ) {
- cache[ id ] = {};
- cache[ id ][ internalKey ] = internalCache;
-
- // Otherwise, we need to eliminate the expando on the node to avoid
- // false lookups in the cache for entries that no longer exist
- } else if ( isNode ) {
- // IE does not allow us to delete expando properties from nodes,
- // nor does it have a removeAttribute function on Document nodes;
- // we must handle all of these cases
- if ( jQuery.support.deleteExpando ) {
- delete elem[ jQuery.expando ];
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( jQuery.expando );
- } else {
- elem[ jQuery.expando ] = null;
- }
- }
- },
-
- // For internal use only.
- _data: function( elem, name, data ) {
- return jQuery.data( elem, name, data, true );
- },
-
- // A method for determining if a DOM node can handle the data expando
- acceptData: function( elem ) {
- if ( elem.nodeName ) {
- var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
-
- if ( match ) {
- return !(match === true || elem.getAttribute("classid") !== match);
- }
- }
-
- return true;
- }
-});
-
-jQuery.fn.extend({
- data: function( key, value ) {
- var data = null;
-
- if ( typeof key === "undefined" ) {
- if ( this.length ) {
- data = jQuery.data( this[0] );
-
- if ( this[0].nodeType === 1 ) {
- var attr = this[0].attributes, name;
- for ( var i = 0, l = attr.length; i < l; i++ ) {
- name = attr[i].name;
-
- if ( name.indexOf( "data-" ) === 0 ) {
- name = name.substr( 5 );
- dataAttr( this[0], name, data[ name ] );
- }
- }
- }
- }
-
- return data;
-
- } else if ( typeof key === "object" ) {
- return this.each(function() {
- jQuery.data( this, key );
- });
- }
-
- var parts = key.split(".");
- parts[1] = parts[1] ? "." + parts[1] : "";
-
- if ( value === undefined ) {
- data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
-
- // Try to fetch any internally stored data first
- if ( data === undefined && this.length ) {
- data = jQuery.data( this[0], key );
- data = dataAttr( this[0], key, data );
- }
-
- return data === undefined && parts[1] ?
- this.data( parts[0] ) :
- data;
-
- } else {
- return this.each(function() {
- var $this = jQuery( this ),
- args = [ parts[0], value ];
-
- $this.triggerHandler( "setData" + parts[1] + "!", args );
- jQuery.data( this, key, value );
- $this.triggerHandler( "changeData" + parts[1] + "!", args );
- });
- }
- },
-
- removeData: function( key ) {
- return this.each(function() {
- jQuery.removeData( this, key );
- });
- }
-});
-
-function dataAttr( elem, key, data ) {
- // If nothing was found internally, try to fetch any
- // data from the HTML5 data-* attribute
- if ( data === undefined && elem.nodeType === 1 ) {
- data = elem.getAttribute( "data-" + key );
-
- if ( typeof data === "string" ) {
- try {
- data = data === "true" ? true :
- data === "false" ? false :
- data === "null" ? null :
- !jQuery.isNaN( data ) ? parseFloat( data ) :
- rbrace.test( data ) ? jQuery.parseJSON( data ) :
- data;
- } catch( e ) {}
-
- // Make sure we set the data so it isn't changed later
- jQuery.data( elem, key, data );
-
- } else {
- data = undefined;
- }
- }
-
- return data;
-}
-
-
-
-
-jQuery.extend({
- queue: function( elem, type, data ) {
- if ( !elem ) {
- return;
- }
-
- type = (type || "fx") + "queue";
- var q = jQuery._data( elem, type );
-
- // Speed up dequeue by getting out quickly if this is just a lookup
- if ( !data ) {
- return q || [];
- }
-
- if ( !q || jQuery.isArray(data) ) {
- q = jQuery._data( elem, type, jQuery.makeArray(data) );
-
- } else {
- q.push( data );
- }
-
- return q;
- },
-
- dequeue: function( elem, type ) {
- type = type || "fx";
-
- var queue = jQuery.queue( elem, type ),
- fn = queue.shift();
-
- // If the fx queue is dequeued, always remove the progress sentinel
- if ( fn === "inprogress" ) {
- fn = queue.shift();
- }
-
- if ( fn ) {
- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if ( type === "fx" ) {
- queue.unshift("inprogress");
- }
-
- fn.call(elem, function() {
- jQuery.dequeue(elem, type);
- });
- }
-
- if ( !queue.length ) {
- jQuery.removeData( elem, type + "queue", true );
- }
- }
-});
-
-jQuery.fn.extend({
- queue: function( type, data ) {
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- }
-
- if ( data === undefined ) {
- return jQuery.queue( this[0], type );
- }
- return this.each(function( i ) {
- var queue = jQuery.queue( this, type, data );
-
- if ( type === "fx" && queue[0] !== "inprogress" ) {
- jQuery.dequeue( this, type );
- }
- });
- },
- dequeue: function( type ) {
- return this.each(function() {
- jQuery.dequeue( this, type );
- });
- },
-
- // Based off of the plugin by Clint Helfers, with permission.
- // http://blindsignals.com/index.php/2009/07/jquery-delay/
- delay: function( time, type ) {
- time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
- type = type || "fx";
-
- return this.queue( type, function() {
- var elem = this;
- setTimeout(function() {
- jQuery.dequeue( elem, type );
- }, time );
- });
- },
-
- clearQueue: function( type ) {
- return this.queue( type || "fx", [] );
- }
-});
-
-
-
-
-var rclass = /[\n\t\r]/g,
- rspaces = /\s+/,
- rreturn = /\r/g,
- rspecialurl = /^(?:href|src|style)$/,
- rtype = /^(?:button|input)$/i,
- rfocusable = /^(?:button|input|object|select|textarea)$/i,
- rclickable = /^a(?:rea)?$/i,
- rradiocheck = /^(?:radio|checkbox)$/i;
-
-jQuery.props = {
- "for": "htmlFor",
- "class": "className",
- readonly: "readOnly",
- maxlength: "maxLength",
- cellspacing: "cellSpacing",
- rowspan: "rowSpan",
- colspan: "colSpan",
- tabindex: "tabIndex",
- usemap: "useMap",
- frameborder: "frameBorder"
-};
-
-jQuery.fn.extend({
- attr: function( name, value ) {
- return jQuery.access( this, name, value, true, jQuery.attr );
- },
-
- removeAttr: function( name, fn ) {
- return this.each(function(){
- jQuery.attr( this, name, "" );
- if ( this.nodeType === 1 ) {
- this.removeAttribute( name );
- }
- });
- },
-
- addClass: function( value ) {
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- self.addClass( value.call(this, i, self.attr("class")) );
- });
- }
-
- if ( value && typeof value === "string" ) {
- var classNames = (value || "").split( rspaces );
-
- for ( var i = 0, l = this.length; i < l; i++ ) {
- var elem = this[i];
-
- if ( elem.nodeType === 1 ) {
- if ( !elem.className ) {
- elem.className = value;
-
- } else {
- var className = " " + elem.className + " ",
- setClass = elem.className;
-
- for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
- if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
- setClass += " " + classNames[c];
- }
- }
- elem.className = jQuery.trim( setClass );
- }
- }
- }
- }
-
- return this;
- },
-
- removeClass: function( value ) {
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- self.removeClass( value.call(this, i, self.attr("class")) );
- });
- }
-
- if ( (value && typeof value === "string") || value === undefined ) {
- var classNames = (value || "").split( rspaces );
-
- for ( var i = 0, l = this.length; i < l; i++ ) {
- var elem = this[i];
-
- if ( elem.nodeType === 1 && elem.className ) {
- if ( value ) {
- var className = (" " + elem.className + " ").replace(rclass, " ");
- for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
- className = className.replace(" " + classNames[c] + " ", " ");
- }
- elem.className = jQuery.trim( className );
-
- } else {
- elem.className = "";
- }
- }
- }
- }
-
- return this;
- },
-
- toggleClass: function( value, stateVal ) {
- var type = typeof value,
- isBool = typeof stateVal === "boolean";
-
- if ( jQuery.isFunction( value ) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
- });
- }
-
- return this.each(function() {
- if ( type === "string" ) {
- // toggle individual class names
- var className,
- i = 0,
- self = jQuery( this ),
- state = stateVal,
- classNames = value.split( rspaces );
-
- while ( (className = classNames[ i++ ]) ) {
- // check each className given, space seperated list
- state = isBool ? state : !self.hasClass( className );
- self[ state ? "addClass" : "removeClass" ]( className );
- }
-
- } else if ( type === "undefined" || type === "boolean" ) {
- if ( this.className ) {
- // store className if set
- jQuery._data( this, "__className__", this.className );
- }
-
- // toggle whole className
- this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
- }
- });
- },
-
- hasClass: function( selector ) {
- var className = " " + selector + " ";
- for ( var i = 0, l = this.length; i < l; i++ ) {
- if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
- return true;
- }
- }
-
- return false;
- },
-
- val: function( value ) {
- if ( !arguments.length ) {
- var elem = this[0];
-
- if ( elem ) {
- if ( jQuery.nodeName( elem, "option" ) ) {
- // attributes.value is undefined in Blackberry 4.7 but
- // uses .value. See #6932
- var val = elem.attributes.value;
- return !val || val.specified ? elem.value : elem.text;
- }
-
- // We need to handle select boxes special
- if ( jQuery.nodeName( elem, "select" ) ) {
- var index = elem.selectedIndex,
- values = [],
- options = elem.options,
- one = elem.type === "select-one";
-
- // Nothing was selected
- if ( index < 0 ) {
- return null;
- }
-
- // Loop through all the selected options
- for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
- var option = options[ i ];
-
- // Don't return options that are disabled or in a disabled optgroup
- if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
- (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
-
- // Get the specific value for the option
- value = jQuery(option).val();
-
- // We don't need an array for one selects
- if ( one ) {
- return value;
- }
-
- // Multi-Selects return an array
- values.push( value );
- }
- }
-
- return values;
- }
-
- // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
- if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
- return elem.getAttribute("value") === null ? "on" : elem.value;
- }
-
- // Everything else, we just grab the value
- return (elem.value || "").replace(rreturn, "");
-
- }
-
- return undefined;
- }
-
- var isFunction = jQuery.isFunction(value);
-
- return this.each(function(i) {
- var self = jQuery(this), val = value;
-
- if ( this.nodeType !== 1 ) {
- return;
- }
-
- if ( isFunction ) {
- val = value.call(this, i, self.val());
- }
-
- // Treat null/undefined as ""; convert numbers to string
- if ( val == null ) {
- val = "";
- } else if ( typeof val === "number" ) {
- val += "";
- } else if ( jQuery.isArray(val) ) {
- val = jQuery.map(val, function (value) {
- return value == null ? "" : value + "";
- });
- }
-
- if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
- this.checked = jQuery.inArray( self.val(), val ) >= 0;
-
- } else if ( jQuery.nodeName( this, "select" ) ) {
- var values = jQuery.makeArray(val);
-
- jQuery( "option", this ).each(function() {
- this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
- });
-
- if ( !values.length ) {
- this.selectedIndex = -1;
- }
-
- } else {
- this.value = val;
- }
- });
- }
-});
-
-jQuery.extend({
- attrFn: {
- val: true,
- css: true,
- html: true,
- text: true,
- data: true,
- width: true,
- height: true,
- offset: true
- },
-
- attr: function( elem, name, value, pass ) {
- // don't get/set attributes on text, comment and attribute nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) {
- return undefined;
- }
-
- if ( pass && name in jQuery.attrFn ) {
- return jQuery(elem)[name](value);
- }
-
- var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
- // Whether we are setting (or getting)
- set = value !== undefined;
-
- // Try to normalize/fix the name
- name = notxml && jQuery.props[ name ] || name;
-
- // Only do all the following if this is a node (faster for style)
- if ( elem.nodeType === 1 ) {
- // These attributes require special treatment
- var special = rspecialurl.test( name );
-
- // Safari mis-reports the default selected property of an option
- // Accessing the parent's selectedIndex property fixes it
- if ( name === "selected" && !jQuery.support.optSelected ) {
- var parent = elem.parentNode;
- if ( parent ) {
- parent.selectedIndex;
-
- // Make sure that it also works with optgroups, see #5701
- if ( parent.parentNode ) {
- parent.parentNode.selectedIndex;
- }
- }
- }
-
- // If applicable, access the attribute via the DOM 0 way
- // 'in' checks fail in Blackberry 4.7 #6931
- if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) {
- if ( set ) {
- // We can't allow the type property to be changed (since it causes problems in IE)
- if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
- jQuery.error( "type property can't be changed" );
- }
-
- if ( value === null ) {
- if ( elem.nodeType === 1 ) {
- elem.removeAttribute( name );
- }
-
- } else {
- elem[ name ] = value;
- }
- }
-
- // browsers index elements by id/name on forms, give priority to attributes.
- if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
- return elem.getAttributeNode( name ).nodeValue;
- }
-
- // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
- // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
- if ( name === "tabIndex" ) {
- var attributeNode = elem.getAttributeNode( "tabIndex" );
-
- return attributeNode && attributeNode.specified ?
- attributeNode.value :
- rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
- 0 :
- undefined;
- }
-
- return elem[ name ];
- }
-
- if ( !jQuery.support.style && notxml && name === "style" ) {
- if ( set ) {
- elem.style.cssText = "" + value;
- }
-
- return elem.style.cssText;
- }
-
- if ( set ) {
- // convert the value to a string (all browsers do this but IE) see #1070
- elem.setAttribute( name, "" + value );
- }
-
- // Ensure that missing attributes return undefined
- // Blackberry 4.7 returns "" from getAttribute #6938
- if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) {
- return undefined;
- }
-
- var attr = !jQuery.support.hrefNormalized && notxml && special ?
- // Some attributes require a special call on IE
- elem.getAttribute( name, 2 ) :
- elem.getAttribute( name );
-
- // Non-existent attributes return null, we normalize to undefined
- return attr === null ? undefined : attr;
- }
- // Handle everything which isn't a DOM element node
- if ( set ) {
- elem[ name ] = value;
- }
- return elem[ name ];
- }
-});
-
-
-
-
-var rnamespaces = /\.(.*)$/,
- rformElems = /^(?:textarea|input|select)$/i,
- rperiod = /\./g,
- rspace = / /g,
- rescape = /[^\w\s.|`]/g,
- fcleanup = function( nm ) {
- return nm.replace(rescape, "\\$&");
- },
- eventKey = "events";
-
-/*
- * A number of helper functions used for managing events.
- * Many of the ideas behind this code originated from
- * Dean Edwards' addEvent library.
- */
-jQuery.event = {
-
- // Bind an event to an element
- // Original by Dean Edwards
- add: function( elem, types, handler, data ) {
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
- }
-
- // For whatever reason, IE has trouble passing the window object
- // around, causing it to be cloned in the process
- if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
- elem = window;
- }
-
- if ( handler === false ) {
- handler = returnFalse;
- } else if ( !handler ) {
- // Fixes bug #7229. Fix recommended by jdalton
- return;
- }
-
- var handleObjIn, handleObj;
-
- if ( handler.handler ) {
- handleObjIn = handler;
- handler = handleObjIn.handler;
- }
-
- // Make sure that the function being executed has a unique ID
- if ( !handler.guid ) {
- handler.guid = jQuery.guid++;
- }
-
- // Init the element's event structure
- var elemData = jQuery._data( elem );
-
- // If no elemData is found then we must be trying to bind to one of the
- // banned noData elements
- if ( !elemData ) {
- return;
- }
-
- var events = elemData[ eventKey ],
- eventHandle = elemData.handle;
-
- if ( typeof events === "function" ) {
- // On plain objects events is a fn that holds the data
- // which prevents this data from being JSON serialized
- // the function does not need to be called, it just contains the data
- eventHandle = events.handle;
- events = events.events;
-
- } else if ( !events ) {
- if ( !elem.nodeType ) {
- // On plain objects, create a fn that acts as the holder
- // of the values to avoid JSON serialization of event data
- elemData[ eventKey ] = elemData = function(){};
- }
-
- elemData.events = events = {};
- }
-
- if ( !eventHandle ) {
- elemData.handle = eventHandle = function() {
- // Handle the second event of a trigger and when
- // an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
- jQuery.event.handle.apply( eventHandle.elem, arguments ) :
- undefined;
- };
- }
-
- // Add elem as a property of the handle function
- // This is to prevent a memory leak with non-native events in IE.
- eventHandle.elem = elem;
-
- // Handle multiple events separated by a space
- // jQuery(...).bind("mouseover mouseout", fn);
- types = types.split(" ");
-
- var type, i = 0, namespaces;
-
- while ( (type = types[ i++ ]) ) {
- handleObj = handleObjIn ?
- jQuery.extend({}, handleObjIn) :
- { handler: handler, data: data };
-
- // Namespaced event handlers
- if ( type.indexOf(".") > -1 ) {
- namespaces = type.split(".");
- type = namespaces.shift();
- handleObj.namespace = namespaces.slice(0).sort().join(".");
-
- } else {
- namespaces = [];
- handleObj.namespace = "";
- }
-
- handleObj.type = type;
- if ( !handleObj.guid ) {
- handleObj.guid = handler.guid;
- }
-
- // Get the current list of functions bound to this event
- var handlers = events[ type ],
- special = jQuery.event.special[ type ] || {};
-
- // Init the event handler queue
- if ( !handlers ) {
- handlers = events[ type ] = [];
-
- // Check for a special event handler
- // Only use addEventListener/attachEvent if the special
- // events handler returns false
- if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
- // Bind the global event handler to the element
- if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle, false );
-
- } else if ( elem.attachEvent ) {
- elem.attachEvent( "on" + type, eventHandle );
- }
- }
- }
-
- if ( special.add ) {
- special.add.call( elem, handleObj );
-
- if ( !handleObj.handler.guid ) {
- handleObj.handler.guid = handler.guid;
- }
- }
-
- // Add the function to the element's handler list
- handlers.push( handleObj );
-
- // Keep track of which events have been used, for global triggering
- jQuery.event.global[ type ] = true;
- }
-
- // Nullify elem to prevent memory leaks in IE
- elem = null;
- },
-
- global: {},
-
- // Detach an event or set of events from an element
- remove: function( elem, types, handler, pos ) {
- // don't do events on text and comment nodes
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
- }
-
- if ( handler === false ) {
- handler = returnFalse;
- }
-
- var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
- elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
- events = elemData && elemData[ eventKey ];
-
- if ( !elemData || !events ) {
- return;
- }
-
- if ( typeof events === "function" ) {
- elemData = events;
- events = events.events;
- }
-
- // types is actually an event object here
- if ( types && types.type ) {
- handler = types.handler;
- types = types.type;
- }
-
- // Unbind all events for the element
- if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
- types = types || "";
-
- for ( type in events ) {
- jQuery.event.remove( elem, type + types );
- }
-
- return;
- }
-
- // Handle multiple events separated by a space
- // jQuery(...).unbind("mouseover mouseout", fn);
- types = types.split(" ");
-
- while ( (type = types[ i++ ]) ) {
- origType = type;
- handleObj = null;
- all = type.indexOf(".") < 0;
- namespaces = [];
-
- if ( !all ) {
- // Namespaced event handlers
- namespaces = type.split(".");
- type = namespaces.shift();
-
- namespace = new RegExp("(^|\\.)" +
- jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
- }
-
- eventType = events[ type ];
-
- if ( !eventType ) {
- continue;
- }
-
- if ( !handler ) {
- for ( j = 0; j < eventType.length; j++ ) {
- handleObj = eventType[ j ];
-
- if ( all || namespace.test( handleObj.namespace ) ) {
- jQuery.event.remove( elem, origType, handleObj.handler, j );
- eventType.splice( j--, 1 );
- }
- }
-
- continue;
- }
-
- special = jQuery.event.special[ type ] || {};
-
- for ( j = pos || 0; j < eventType.length; j++ ) {
- handleObj = eventType[ j ];
-
- if ( handler.guid === handleObj.guid ) {
- // remove the given handler for the given type
- if ( all || namespace.test( handleObj.namespace ) ) {
- if ( pos == null ) {
- eventType.splice( j--, 1 );
- }
-
- if ( special.remove ) {
- special.remove.call( elem, handleObj );
- }
- }
-
- if ( pos != null ) {
- break;
- }
- }
- }
-
- // remove generic event handler if no more handlers exist
- if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
- if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
- jQuery.removeEvent( elem, type, elemData.handle );
- }
-
- ret = null;
- delete events[ type ];
- }
- }
-
- // Remove the expando if it's no longer used
- if ( jQuery.isEmptyObject( events ) ) {
- var handle = elemData.handle;
- if ( handle ) {
- handle.elem = null;
- }
-
- delete elemData.events;
- delete elemData.handle;
-
- if ( typeof elemData === "function" ) {
- jQuery.removeData( elem, eventKey, true );
-
- } else if ( jQuery.isEmptyObject( elemData ) ) {
- jQuery.removeData( elem, undefined, true );
- }
- }
- },
-
- // bubbling is internal
- trigger: function( event, data, elem /*, bubbling */ ) {
- // Event object or event type
- var type = event.type || event,
- bubbling = arguments[3];
-
- if ( !bubbling ) {
- event = typeof event === "object" ?
- // jQuery.Event object
- event[ jQuery.expando ] ? event :
- // Object literal
- jQuery.extend( jQuery.Event(type), event ) :
- // Just the event type (string)
- jQuery.Event(type);
-
- if ( type.indexOf("!") >= 0 ) {
- event.type = type = type.slice(0, -1);
- event.exclusive = true;
- }
-
- // Handle a global trigger
- if ( !elem ) {
- // Don't bubble custom events when global (to avoid too much overhead)
- event.stopPropagation();
-
- // Only trigger if we've ever bound an event for it
- if ( jQuery.event.global[ type ] ) {
- // XXX This code smells terrible. event.js should not be directly
- // inspecting the data cache
- jQuery.each( jQuery.cache, function() {
- // internalKey variable is just used to make it easier to find
- // and potentially change this stuff later; currently it just
- // points to jQuery.expando
- var internalKey = jQuery.expando,
- internalCache = this[ internalKey ];
- if ( internalCache && internalCache.events && internalCache.events[type] ) {
- jQuery.event.trigger( event, data, internalCache.handle.elem );
- }
- });
- }
- }
-
- // Handle triggering a single element
-
- // don't do events on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
- return undefined;
- }
-
- // Clean up in case it is reused
- event.result = undefined;
- event.target = elem;
-
- // Clone the incoming data, if any
- data = jQuery.makeArray( data );
- data.unshift( event );
- }
-
- event.currentTarget = elem;
-
- // Trigger the event, it is assumed that "handle" is a function
- var handle = elem.nodeType ?
- jQuery._data( elem, "handle" ) :
- (jQuery._data( elem, eventKey ) || {}).handle;
-
- if ( handle ) {
- handle.apply( elem, data );
- }
-
- var parent = elem.parentNode || elem.ownerDocument;
-
- // Trigger an inline bound script
- try {
- if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
- if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
- event.result = false;
- event.preventDefault();
- }
- }
-
- // prevent IE from throwing an error for some elements with some event types, see #3533
- } catch (inlineError) {}
-
- if ( !event.isPropagationStopped() && parent ) {
- jQuery.event.trigger( event, data, parent, true );
-
- } else if ( !event.isDefaultPrevented() ) {
- var old,
- target = event.target,
- targetType = type.replace( rnamespaces, "" ),
- isClick = jQuery.nodeName( target, "a" ) && targetType === "click",
- special = jQuery.event.special[ targetType ] || {};
-
- if ( (!special._default || special._default.call( elem, event ) === false) &&
- !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
-
- try {
- if ( target[ targetType ] ) {
- // Make sure that we don't accidentally re-trigger the onFOO events
- old = target[ "on" + targetType ];
-
- if ( old ) {
- target[ "on" + targetType ] = null;
- }
-
- jQuery.event.triggered = true;
- target[ targetType ]();
- }
-
- // prevent IE from throwing an error for some elements with some event types, see #3533
- } catch (triggerError) {}
-
- if ( old ) {
- target[ "on" + targetType ] = old;
- }
-
- jQuery.event.triggered = false;
- }
- }
- },
-
- handle: function( event ) {
- var all, handlers, namespaces, namespace_re, events,
- namespace_sort = [],
- args = jQuery.makeArray( arguments );
-
- event = args[0] = jQuery.event.fix( event || window.event );
- event.currentTarget = this;
-
- // Namespaced event handlers
- all = event.type.indexOf(".") < 0 && !event.exclusive;
-
- if ( !all ) {
- namespaces = event.type.split(".");
- event.type = namespaces.shift();
- namespace_sort = namespaces.slice(0).sort();
- namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
- }
-
- event.namespace = event.namespace || namespace_sort.join(".");
-
- events = jQuery._data(this, eventKey);
-
- if ( typeof events === "function" ) {
- events = events.events;
- }
-
- handlers = (events || {})[ event.type ];
-
- if ( events && handlers ) {
- // Clone the handlers to prevent manipulation
- handlers = handlers.slice(0);
-
- for ( var j = 0, l = handlers.length; j < l; j++ ) {
- var handleObj = handlers[ j ];
-
- // Filter the functions by class
- if ( all || namespace_re.test( handleObj.namespace ) ) {
- // Pass in a reference to the handler function itself
- // So that we can later remove it
- event.handler = handleObj.handler;
- event.data = handleObj.data;
- event.handleObj = handleObj;
-
- var ret = handleObj.handler.apply( this, args );
-
- if ( ret !== undefined ) {
- event.result = ret;
- if ( ret === false ) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- if ( event.isImmediatePropagationStopped() ) {
- break;
- }
- }
- }
- }
-
- return event.result;
- },
-
- props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
-
- fix: function( event ) {
- if ( event[ jQuery.expando ] ) {
- return event;
- }
-
- // store a copy of the original event object
- // and "clone" to set read-only properties
- var originalEvent = event;
- event = jQuery.Event( originalEvent );
-
- for ( var i = this.props.length, prop; i; ) {
- prop = this.props[ --i ];
- event[ prop ] = originalEvent[ prop ];
- }
-
- // Fix target property, if necessary
- if ( !event.target ) {
- // Fixes #1925 where srcElement might not be defined either
- event.target = event.srcElement || document;
- }
-
- // check if target is a textnode (safari)
- if ( event.target.nodeType === 3 ) {
- event.target = event.target.parentNode;
- }
-
- // Add relatedTarget, if necessary
- if ( !event.relatedTarget && event.fromElement ) {
- event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
- }
-
- // Calculate pageX/Y if missing and clientX/Y available
- if ( event.pageX == null && event.clientX != null ) {
- var doc = document.documentElement,
- body = document.body;
-
- event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
- event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
- }
-
- // Add which for key events
- if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
- event.which = event.charCode != null ? event.charCode : event.keyCode;
- }
-
- // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
- if ( !event.metaKey && event.ctrlKey ) {
- event.metaKey = event.ctrlKey;
- }
-
- // Add which for click: 1 === left; 2 === middle; 3 === right
- // Note: button is not normalized, so don't use it
- if ( !event.which && event.button !== undefined ) {
- event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
- }
-
- return event;
- },
-
- // Deprecated, use jQuery.guid instead
- guid: 1E8,
-
- // Deprecated, use jQuery.proxy instead
- proxy: jQuery.proxy,
-
- special: {
- ready: {
- // Make sure the ready event is setup
- setup: jQuery.bindReady,
- teardown: jQuery.noop
- },
-
- live: {
- add: function( handleObj ) {
- jQuery.event.add( this,
- liveConvert( handleObj.origType, handleObj.selector ),
- jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
- },
-
- remove: function( handleObj ) {
- jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
- }
- },
-
- beforeunload: {
- setup: function( data, namespaces, eventHandle ) {
- // We only want to do this special case on windows
- if ( jQuery.isWindow( this ) ) {
- this.onbeforeunload = eventHandle;
- }
- },
-
- teardown: function( namespaces, eventHandle ) {
- if ( this.onbeforeunload === eventHandle ) {
- this.onbeforeunload = null;
- }
- }
- }
- }
-};
-
-jQuery.removeEvent = document.removeEventListener ?
- function( elem, type, handle ) {
- if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle, false );
- }
- } :
- function( elem, type, handle ) {
- if ( elem.detachEvent ) {
- elem.detachEvent( "on" + type, handle );
- }
- };
-
-jQuery.Event = function( src ) {
- // Allow instantiation without the 'new' keyword
- if ( !this.preventDefault ) {
- return new jQuery.Event( src );
- }
-
- // Event object
- if ( src && src.type ) {
- this.originalEvent = src;
- this.type = src.type;
-
- // Events bubbling up the document may have been marked as prevented
- // by a handler lower down the tree; reflect the correct value.
- this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
- src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
-
- // Event type
- } else {
- this.type = src;
- }
-
- // timeStamp is buggy for some events on Firefox(#3843)
- // So we won't rely on the native value
- this.timeStamp = jQuery.now();
-
- // Mark it as fixed
- this[ jQuery.expando ] = true;
-};
-
-function returnFalse() {
- return false;
-}
-function returnTrue() {
- return true;
-}
-
-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
- preventDefault: function() {
- this.isDefaultPrevented = returnTrue;
-
- var e = this.originalEvent;
- if ( !e ) {
- return;
- }
-
- // if preventDefault exists run it on the original event
- if ( e.preventDefault ) {
- e.preventDefault();
-
- // otherwise set the returnValue property of the original event to false (IE)
- } else {
- e.returnValue = false;
- }
- },
- stopPropagation: function() {
- this.isPropagationStopped = returnTrue;
-
- var e = this.originalEvent;
- if ( !e ) {
- return;
- }
- // if stopPropagation exists run it on the original event
- if ( e.stopPropagation ) {
- e.stopPropagation();
- }
- // otherwise set the cancelBubble property of the original event to true (IE)
- e.cancelBubble = true;
- },
- stopImmediatePropagation: function() {
- this.isImmediatePropagationStopped = returnTrue;
- this.stopPropagation();
- },
- isDefaultPrevented: returnFalse,
- isPropagationStopped: returnFalse,
- isImmediatePropagationStopped: returnFalse
-};
-
-// Checks if an event happened on an element within another element
-// Used in jQuery.event.special.mouseenter and mouseleave handlers
-var withinElement = function( event ) {
- // Check if mouse(over|out) are still within the same parent element
- var parent = event.relatedTarget;
-
- // Firefox sometimes assigns relatedTarget a XUL element
- // which we cannot access the parentNode property of
- try {
- // Traverse up the tree
- while ( parent && parent !== this ) {
- parent = parent.parentNode;
- }
-
- if ( parent !== this ) {
- // set the correct event type
- event.type = event.data;
-
- // handle event if we actually just moused on to a non sub-element
- jQuery.event.handle.apply( this, arguments );
- }
-
- // assuming we've left the element since we most likely mousedover a xul element
- } catch(e) { }
-},
-
-// In case of event delegation, we only need to rename the event.type,
-// liveHandler will take care of the rest.
-delegate = function( event ) {
- event.type = event.data;
- jQuery.event.handle.apply( this, arguments );
-};
-
-// Create mouseenter and mouseleave events
-jQuery.each({
- mouseenter: "mouseover",
- mouseleave: "mouseout"
-}, function( orig, fix ) {
- jQuery.event.special[ orig ] = {
- setup: function( data ) {
- jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
- },
- teardown: function( data ) {
- jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
- }
- };
-});
-
-// submit delegation
-if ( !jQuery.support.submitBubbles ) {
-
- jQuery.event.special.submit = {
- setup: function( data, namespaces ) {
- if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) {
- jQuery.event.add(this, "click.specialSubmit", function( e ) {
- var elem = e.target,
- type = elem.type;
-
- if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
- e.liveFired = undefined;
- return trigger( "submit", this, arguments );
- }
- });
-
- jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
- var elem = e.target,
- type = elem.type;
-
- if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
- e.liveFired = undefined;
- return trigger( "submit", this, arguments );
- }
- });
-
- } else {
- return false;
- }
- },
-
- teardown: function( namespaces ) {
- jQuery.event.remove( this, ".specialSubmit" );
- }
- };
-
-}
-
-// change delegation, happens here so we have bind.
-if ( !jQuery.support.changeBubbles ) {
-
- var changeFilters,
-
- getVal = function( elem ) {
- var type = elem.type, val = elem.value;
-
- if ( type === "radio" || type === "checkbox" ) {
- val = elem.checked;
-
- } else if ( type === "select-multiple" ) {
- val = elem.selectedIndex > -1 ?
- jQuery.map( elem.options, function( elem ) {
- return elem.selected;
- }).join("-") :
- "";
-
- } else if ( elem.nodeName.toLowerCase() === "select" ) {
- val = elem.selectedIndex;
- }
-
- return val;
- },
-
- testChange = function testChange( e ) {
- var elem = e.target, data, val;
-
- if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
- return;
- }
-
- data = jQuery._data( elem, "_change_data" );
- val = getVal(elem);
-
- // the current data will be also retrieved by beforeactivate
- if ( e.type !== "focusout" || elem.type !== "radio" ) {
- jQuery._data( elem, "_change_data", val );
- }
-
- if ( data === undefined || val === data ) {
- return;
- }
-
- if ( data != null || val ) {
- e.type = "change";
- e.liveFired = undefined;
- return jQuery.event.trigger( e, arguments[1], elem );
- }
- };
-
- jQuery.event.special.change = {
- filters: {
- focusout: testChange,
-
- beforedeactivate: testChange,
-
- click: function( e ) {
- var elem = e.target, type = elem.type;
-
- if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
- return testChange.call( this, e );
- }
- },
-
- // Change has to be called before submit
- // Keydown will be called before keypress, which is used in submit-event delegation
- keydown: function( e ) {
- var elem = e.target, type = elem.type;
-
- if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
- (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
- type === "select-multiple" ) {
- return testChange.call( this, e );
- }
- },
-
- // Beforeactivate happens also before the previous element is blurred
- // with this event you can't trigger a change event, but you can store
- // information
- beforeactivate: function( e ) {
- var elem = e.target;
- jQuery._data( elem, "_change_data", getVal(elem) );
- }
- },
-
- setup: function( data, namespaces ) {
- if ( this.type === "file" ) {
- return false;
- }
-
- for ( var type in changeFilters ) {
- jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
- }
-
- return rformElems.test( this.nodeName );
- },
-
- teardown: function( namespaces ) {
- jQuery.event.remove( this, ".specialChange" );
-
- return rformElems.test( this.nodeName );
- }
- };
-
- changeFilters = jQuery.event.special.change.filters;
-
- // Handle when the input is .focus()'d
- changeFilters.focus = changeFilters.beforeactivate;
-}
-
-function trigger( type, elem, args ) {
- args[0].type = type;
- return jQuery.event.handle.apply( elem, args );
-}
-
-// Create "bubbling" focus and blur events
-if ( document.addEventListener ) {
- jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
- jQuery.event.special[ fix ] = {
- setup: function() {
- this.addEventListener( orig, handler, true );
- },
- teardown: function() {
- this.removeEventListener( orig, handler, true );
- }
- };
-
- function handler( e ) {
- e = jQuery.event.fix( e );
- e.type = fix;
- return jQuery.event.handle.call( this, e );
- }
- });
-}
-
-jQuery.each(["bind", "one"], function( i, name ) {
- jQuery.fn[ name ] = function( type, data, fn ) {
- // Handle object literals
- if ( typeof type === "object" ) {
- for ( var key in type ) {
- this[ name ](key, data, type[key], fn);
- }
- return this;
- }
-
- if ( jQuery.isFunction( data ) || data === false ) {
- fn = data;
- data = undefined;
- }
-
- var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
- jQuery( this ).unbind( event, handler );
- return fn.apply( this, arguments );
- }) : fn;
-
- if ( type === "unload" && name !== "one" ) {
- this.one( type, data, fn );
-
- } else {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- jQuery.event.add( this[i], type, handler, data );
- }
- }
-
- return this;
- };
-});
-
-jQuery.fn.extend({
- unbind: function( type, fn ) {
- // Handle object literals
- if ( typeof type === "object" && !type.preventDefault ) {
- for ( var key in type ) {
- this.unbind(key, type[key]);
- }
-
- } else {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- jQuery.event.remove( this[i], type, fn );
- }
- }
-
- return this;
- },
-
- delegate: function( selector, types, data, fn ) {
- return this.live( types, data, fn, selector );
- },
-
- undelegate: function( selector, types, fn ) {
- if ( arguments.length === 0 ) {
- return this.unbind( "live" );
-
- } else {
- return this.die( types, null, fn, selector );
- }
- },
-
- trigger: function( type, data ) {
- return this.each(function() {
- jQuery.event.trigger( type, data, this );
- });
- },
-
- triggerHandler: function( type, data ) {
- if ( this[0] ) {
- var event = jQuery.Event( type );
- event.preventDefault();
- event.stopPropagation();
- jQuery.event.trigger( event, data, this[0] );
- return event.result;
- }
- },
-
- toggle: function( fn ) {
- // Save reference to arguments for access in closure
- var args = arguments,
- i = 1;
-
- // link all the functions, so any of them can unbind this click handler
- while ( i < args.length ) {
- jQuery.proxy( fn, args[ i++ ] );
- }
-
- return this.click( jQuery.proxy( fn, function( event ) {
- // Figure out which function to execute
- var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
- jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
-
- // Make sure that clicks stop
- event.preventDefault();
-
- // and execute the function
- return args[ lastToggle ].apply( this, arguments ) || false;
- }));
- },
-
- hover: function( fnOver, fnOut ) {
- return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
- }
-});
-
-var liveMap = {
- focus: "focusin",
- blur: "focusout",
- mouseenter: "mouseover",
- mouseleave: "mouseout"
-};
-
-jQuery.each(["live", "die"], function( i, name ) {
- jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
- var type, i = 0, match, namespaces, preType,
- selector = origSelector || this.selector,
- context = origSelector ? this : jQuery( this.context );
-
- if ( typeof types === "object" && !types.preventDefault ) {
- for ( var key in types ) {
- context[ name ]( key, data, types[key], selector );
- }
-
- return this;
- }
-
- if ( jQuery.isFunction( data ) ) {
- fn = data;
- data = undefined;
- }
-
- types = (types || "").split(" ");
-
- while ( (type = types[ i++ ]) != null ) {
- match = rnamespaces.exec( type );
- namespaces = "";
-
- if ( match ) {
- namespaces = match[0];
- type = type.replace( rnamespaces, "" );
- }
-
- if ( type === "hover" ) {
- types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
- continue;
- }
-
- preType = type;
-
- if ( type === "focus" || type === "blur" ) {
- types.push( liveMap[ type ] + namespaces );
- type = type + namespaces;
-
- } else {
- type = (liveMap[ type ] || type) + namespaces;
- }
-
- if ( name === "live" ) {
- // bind live handler
- for ( var j = 0, l = context.length; j < l; j++ ) {
- jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
- { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
- }
-
- } else {
- // unbind live handler
- context.unbind( "live." + liveConvert( type, selector ), fn );
- }
- }
-
- return this;
- };
-});
-
-function liveHandler( event ) {
- var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
- elems = [],
- selectors = [],
- events = jQuery._data( this, eventKey );
-
- if ( typeof events === "function" ) {
- events = events.events;
- }
-
- // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
- if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
- return;
- }
-
- if ( event.namespace ) {
- namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
- }
-
- event.liveFired = this;
-
- var live = events.live.slice(0);
-
- for ( j = 0; j < live.length; j++ ) {
- handleObj = live[j];
-
- if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
- selectors.push( handleObj.selector );
-
- } else {
- live.splice( j--, 1 );
- }
- }
-
- match = jQuery( event.target ).closest( selectors, event.currentTarget );
-
- for ( i = 0, l = match.length; i < l; i++ ) {
- close = match[i];
-
- for ( j = 0; j < live.length; j++ ) {
- handleObj = live[j];
-
- if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) {
- elem = close.elem;
- related = null;
-
- // Those two events require additional checking
- if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
- event.type = handleObj.preType;
- related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
- }
-
- if ( !related || related !== elem ) {
- elems.push({ elem: elem, handleObj: handleObj, level: close.level });
- }
- }
- }
- }
-
- for ( i = 0, l = elems.length; i < l; i++ ) {
- match = elems[i];
-
- if ( maxLevel && match.level > maxLevel ) {
- break;
- }
-
- event.currentTarget = match.elem;
- event.data = match.handleObj.data;
- event.handleObj = match.handleObj;
-
- ret = match.handleObj.origHandler.apply( match.elem, arguments );
-
- if ( ret === false || event.isPropagationStopped() ) {
- maxLevel = match.level;
-
- if ( ret === false ) {
- stop = false;
- }
- if ( event.isImmediatePropagationStopped() ) {
- break;
- }
- }
- }
-
- return stop;
-}
-
-function liveConvert( type, selector ) {
- return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&");
-}
-
-jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
- "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
- "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
-
- // Handle event binding
- jQuery.fn[ name ] = function( data, fn ) {
- if ( fn == null ) {
- fn = data;
- data = null;
- }
-
- return arguments.length > 0 ?
- this.bind( name, data, fn ) :
- this.trigger( name );
- };
-
- if ( jQuery.attrFn ) {
- jQuery.attrFn[ name ] = true;
- }
-});
-
-
-/*!
- * Sizzle CSS Selector Engine
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- * More information: http://sizzlejs.com/
- */
-(function(){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
- done = 0,
- toString = Object.prototype.toString,
- hasDuplicate = false,
- baseHasDuplicate = true;
-
-// Here we check if the JavaScript engine is using some sort of
-// optimization where it does not always call our comparision
-// function. If that is the case, discard the hasDuplicate value.
-// Thus far that includes Google Chrome.
-[0, 0].sort(function() {
- baseHasDuplicate = false;
- return 0;
-});
-
-var Sizzle = function( selector, context, results, seed ) {
- results = results || [];
- context = context || document;
-
- var origContext = context;
-
- if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
- return [];
- }
-
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
-
- var m, set, checkSet, extra, ret, cur, pop, i,
- prune = true,
- contextXML = Sizzle.isXML( context ),
- parts = [],
- soFar = selector;
-
- // Reset the position of the chunker regexp (start from head)
- do {
- chunker.exec( "" );
- m = chunker.exec( soFar );
-
- if ( m ) {
- soFar = m[3];
-
- parts.push( m[1] );
-
- if ( m[2] ) {
- extra = m[3];
- break;
- }
- }
- } while ( m );
-
- if ( parts.length > 1 && origPOS.exec( selector ) ) {
-
- if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
- set = posProcess( parts[0] + parts[1], context );
-
- } else {
- set = Expr.relative[ parts[0] ] ?
- [ context ] :
- Sizzle( parts.shift(), context );
-
- while ( parts.length ) {
- selector = parts.shift();
-
- if ( Expr.relative[ selector ] ) {
- selector += parts.shift();
- }
-
- set = posProcess( selector, set );
- }
- }
-
- } else {
- // Take a shortcut and set the context if the root selector is an ID
- // (but not if it'll be faster if the inner selector is an ID)
- if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
- Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
-
- ret = Sizzle.find( parts.shift(), context, contextXML );
- context = ret.expr ?
- Sizzle.filter( ret.expr, ret.set )[0] :
- ret.set[0];
- }
-
- if ( context ) {
- ret = seed ?
- { expr: parts.pop(), set: makeArray(seed) } :
- Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
-
- set = ret.expr ?
- Sizzle.filter( ret.expr, ret.set ) :
- ret.set;
-
- if ( parts.length > 0 ) {
- checkSet = makeArray( set );
-
- } else {
- prune = false;
- }
-
- while ( parts.length ) {
- cur = parts.pop();
- pop = cur;
-
- if ( !Expr.relative[ cur ] ) {
- cur = "";
- } else {
- pop = parts.pop();
- }
-
- if ( pop == null ) {
- pop = context;
- }
-
- Expr.relative[ cur ]( checkSet, pop, contextXML );
- }
-
- } else {
- checkSet = parts = [];
- }
- }
-
- if ( !checkSet ) {
- checkSet = set;
- }
-
- if ( !checkSet ) {
- Sizzle.error( cur || selector );
- }
-
- if ( toString.call(checkSet) === "[object Array]" ) {
- if ( !prune ) {
- results.push.apply( results, checkSet );
-
- } else if ( context && context.nodeType === 1 ) {
- for ( i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
- results.push( set[i] );
- }
- }
-
- } else {
- for ( i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
- results.push( set[i] );
- }
- }
- }
-
- } else {
- makeArray( checkSet, results );
- }
-
- if ( extra ) {
- Sizzle( extra, origContext, results, seed );
- Sizzle.uniqueSort( results );
- }
-
- return results;
-};
-
-Sizzle.uniqueSort = function( results ) {
- if ( sortOrder ) {
- hasDuplicate = baseHasDuplicate;
- results.sort( sortOrder );
-
- if ( hasDuplicate ) {
- for ( var i = 1; i < results.length; i++ ) {
- if ( results[i] === results[ i - 1 ] ) {
- results.splice( i--, 1 );
- }
- }
- }
- }
-
- return results;
-};
-
-Sizzle.matches = function( expr, set ) {
- return Sizzle( expr, null, null, set );
-};
-
-Sizzle.matchesSelector = function( node, expr ) {
- return Sizzle( expr, null, null, [node] ).length > 0;
-};
-
-Sizzle.find = function( expr, context, isXML ) {
- var set;
-
- if ( !expr ) {
- return [];
- }
-
- for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
- var match,
- type = Expr.order[i];
-
- if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
- var left = match[1];
- match.splice( 1, 1 );
-
- if ( left.substr( left.length - 1 ) !== "\\" ) {
- match[1] = (match[1] || "").replace(/\\/g, "");
- set = Expr.find[ type ]( match, context, isXML );
-
- if ( set != null ) {
- expr = expr.replace( Expr.match[ type ], "" );
- break;
- }
- }
- }
- }
-
- if ( !set ) {
- set = typeof context.getElementsByTagName !== "undefined" ?
- context.getElementsByTagName( "*" ) :
- [];
- }
-
- return { set: set, expr: expr };
-};
-
-Sizzle.filter = function( expr, set, inplace, not ) {
- var match, anyFound,
- old = expr,
- result = [],
- curLoop = set,
- isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
-
- while ( expr && set.length ) {
- for ( var type in Expr.filter ) {
- if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
- var found, item,
- filter = Expr.filter[ type ],
- left = match[1];
-
- anyFound = false;
-
- match.splice(1,1);
-
- if ( left.substr( left.length - 1 ) === "\\" ) {
- continue;
- }
-
- if ( curLoop === result ) {
- result = [];
- }
-
- if ( Expr.preFilter[ type ] ) {
- match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
- if ( !match ) {
- anyFound = found = true;
-
- } else if ( match === true ) {
- continue;
- }
- }
-
- if ( match ) {
- for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
- if ( item ) {
- found = filter( item, match, i, curLoop );
- var pass = not ^ !!found;
-
- if ( inplace && found != null ) {
- if ( pass ) {
- anyFound = true;
-
- } else {
- curLoop[i] = false;
- }
-
- } else if ( pass ) {
- result.push( item );
- anyFound = true;
- }
- }
- }
- }
-
- if ( found !== undefined ) {
- if ( !inplace ) {
- curLoop = result;
- }
-
- expr = expr.replace( Expr.match[ type ], "" );
-
- if ( !anyFound ) {
- return [];
- }
-
- break;
- }
- }
- }
-
- // Improper expression
- if ( expr === old ) {
- if ( anyFound == null ) {
- Sizzle.error( expr );
-
- } else {
- break;
- }
- }
-
- old = expr;
- }
-
- return curLoop;
-};
-
-Sizzle.error = function( msg ) {
- throw "Syntax error, unrecognized expression: " + msg;
-};
-
-var Expr = Sizzle.selectors = {
- order: [ "ID", "NAME", "TAG" ],
-
- match: {
- ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
- CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
- NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
- ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
- TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
- CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
- POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
- PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
- },
-
- leftMatch: {},
-
- attrMap: {
- "class": "className",
- "for": "htmlFor"
- },
-
- attrHandle: {
- href: function( elem ) {
- return elem.getAttribute( "href" );
- }
- },
-
- relative: {
- "+": function(checkSet, part){
- var isPartStr = typeof part === "string",
- isTag = isPartStr && !/\W/.test( part ),
- isPartStrNotTag = isPartStr && !isTag;
-
- if ( isTag ) {
- part = part.toLowerCase();
- }
-
- for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
- if ( (elem = checkSet[i]) ) {
- while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
- checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
- elem || false :
- elem === part;
- }
- }
-
- if ( isPartStrNotTag ) {
- Sizzle.filter( part, checkSet, true );
- }
- },
-
- ">": function( checkSet, part ) {
- var elem,
- isPartStr = typeof part === "string",
- i = 0,
- l = checkSet.length;
-
- if ( isPartStr && !/\W/.test( part ) ) {
- part = part.toLowerCase();
-
- for ( ; i < l; i++ ) {
- elem = checkSet[i];
-
- if ( elem ) {
- var parent = elem.parentNode;
- checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
- }
- }
-
- } else {
- for ( ; i < l; i++ ) {
- elem = checkSet[i];
-
- if ( elem ) {
- checkSet[i] = isPartStr ?
- elem.parentNode :
- elem.parentNode === part;
- }
- }
-
- if ( isPartStr ) {
- Sizzle.filter( part, checkSet, true );
- }
- }
- },
-
- "": function(checkSet, part, isXML){
- var nodeCheck,
- doneName = done++,
- checkFn = dirCheck;
-
- if ( typeof part === "string" && !/\W/.test(part) ) {
- part = part.toLowerCase();
- nodeCheck = part;
- checkFn = dirNodeCheck;
- }
-
- checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
- },
-
- "~": function( checkSet, part, isXML ) {
- var nodeCheck,
- doneName = done++,
- checkFn = dirCheck;
-
- if ( typeof part === "string" && !/\W/.test( part ) ) {
- part = part.toLowerCase();
- nodeCheck = part;
- checkFn = dirNodeCheck;
- }
-
- checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
- }
- },
-
- find: {
- ID: function( match, context, isXML ) {
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [m] : [];
- }
- },
-
- NAME: function( match, context ) {
- if ( typeof context.getElementsByName !== "undefined" ) {
- var ret = [],
- results = context.getElementsByName( match[1] );
-
- for ( var i = 0, l = results.length; i < l; i++ ) {
- if ( results[i].getAttribute("name") === match[1] ) {
- ret.push( results[i] );
- }
- }
-
- return ret.length === 0 ? null : ret;
- }
- },
-
- TAG: function( match, context ) {
- if ( typeof context.getElementsByTagName !== "undefined" ) {
- return context.getElementsByTagName( match[1] );
- }
- }
- },
- preFilter: {
- CLASS: function( match, curLoop, inplace, result, not, isXML ) {
- match = " " + match[1].replace(/\\/g, "") + " ";
-
- if ( isXML ) {
- return match;
- }
-
- for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
- if ( elem ) {
- if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
- if ( !inplace ) {
- result.push( elem );
- }
-
- } else if ( inplace ) {
- curLoop[i] = false;
- }
- }
- }
-
- return false;
- },
-
- ID: function( match ) {
- return match[1].replace(/\\/g, "");
- },
-
- TAG: function( match, curLoop ) {
- return match[1].toLowerCase();
- },
-
- CHILD: function( match ) {
- if ( match[1] === "nth" ) {
- if ( !match[2] ) {
- Sizzle.error( match[0] );
- }
-
- match[2] = match[2].replace(/^\+|\s*/g, '');
-
- // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
- var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
- match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
- !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
- // calculate the numbers (first)n+(last) including if they are negative
- match[2] = (test[1] + (test[2] || 1)) - 0;
- match[3] = test[3] - 0;
- }
- else if ( match[2] ) {
- Sizzle.error( match[0] );
- }
-
- // TODO: Move to normal caching system
- match[0] = done++;
-
- return match;
- },
-
- ATTR: function( match, curLoop, inplace, result, not, isXML ) {
- var name = match[1] = match[1].replace(/\\/g, "");
-
- if ( !isXML && Expr.attrMap[name] ) {
- match[1] = Expr.attrMap[name];
- }
-
- // Handle if an un-quoted value was used
- match[4] = ( match[4] || match[5] || "" ).replace(/\\/g, "");
-
- if ( match[2] === "~=" ) {
- match[4] = " " + match[4] + " ";
- }
-
- return match;
- },
-
- PSEUDO: function( match, curLoop, inplace, result, not ) {
- if ( match[1] === "not" ) {
- // If we're dealing with a complex expression, or a simple one
- if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
- match[3] = Sizzle(match[3], null, null, curLoop);
-
- } else {
- var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
-
- if ( !inplace ) {
- result.push.apply( result, ret );
- }
-
- return false;
- }
-
- } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
- return true;
- }
-
- return match;
- },
-
- POS: function( match ) {
- match.unshift( true );
-
- return match;
- }
- },
-
- filters: {
- enabled: function( elem ) {
- return elem.disabled === false && elem.type !== "hidden";
- },
-
- disabled: function( elem ) {
- return elem.disabled === true;
- },
-
- checked: function( elem ) {
- return elem.checked === true;
- },
-
- selected: function( elem ) {
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- elem.parentNode.selectedIndex;
-
- return elem.selected === true;
- },
-
- parent: function( elem ) {
- return !!elem.firstChild;
- },
-
- empty: function( elem ) {
- return !elem.firstChild;
- },
-
- has: function( elem, i, match ) {
- return !!Sizzle( match[3], elem ).length;
- },
-
- header: function( elem ) {
- return (/h\d/i).test( elem.nodeName );
- },
-
- text: function( elem ) {
- return "text" === elem.type;
- },
- radio: function( elem ) {
- return "radio" === elem.type;
- },
-
- checkbox: function( elem ) {
- return "checkbox" === elem.type;
- },
-
- file: function( elem ) {
- return "file" === elem.type;
- },
- password: function( elem ) {
- return "password" === elem.type;
- },
-
- submit: function( elem ) {
- return "submit" === elem.type;
- },
-
- image: function( elem ) {
- return "image" === elem.type;
- },
-
- reset: function( elem ) {
- return "reset" === elem.type;
- },
-
- button: function( elem ) {
- return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
- },
-
- input: function( elem ) {
- return (/input|select|textarea|button/i).test( elem.nodeName );
- }
- },
- setFilters: {
- first: function( elem, i ) {
- return i === 0;
- },
-
- last: function( elem, i, match, array ) {
- return i === array.length - 1;
- },
-
- even: function( elem, i ) {
- return i % 2 === 0;
- },
-
- odd: function( elem, i ) {
- return i % 2 === 1;
- },
-
- lt: function( elem, i, match ) {
- return i < match[3] - 0;
- },
-
- gt: function( elem, i, match ) {
- return i > match[3] - 0;
- },
-
- nth: function( elem, i, match ) {
- return match[3] - 0 === i;
- },
-
- eq: function( elem, i, match ) {
- return match[3] - 0 === i;
- }
- },
- filter: {
- PSEUDO: function( elem, match, i, array ) {
- var name = match[1],
- filter = Expr.filters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
-
- } else if ( name === "contains" ) {
- return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
-
- } else if ( name === "not" ) {
- var not = match[3];
-
- for ( var j = 0, l = not.length; j < l; j++ ) {
- if ( not[j] === elem ) {
- return false;
- }
- }
-
- return true;
-
- } else {
- Sizzle.error( name );
- }
- },
-
- CHILD: function( elem, match ) {
- var type = match[1],
- node = elem;
-
- switch ( type ) {
- case "only":
- case "first":
- while ( (node = node.previousSibling) ) {
- if ( node.nodeType === 1 ) {
- return false;
- }
- }
-
- if ( type === "first" ) {
- return true;
- }
-
- node = elem;
-
- case "last":
- while ( (node = node.nextSibling) ) {
- if ( node.nodeType === 1 ) {
- return false;
- }
- }
-
- return true;
-
- case "nth":
- var first = match[2],
- last = match[3];
-
- if ( first === 1 && last === 0 ) {
- return true;
- }
-
- var doneName = match[0],
- parent = elem.parentNode;
-
- if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
- var count = 0;
-
- for ( node = parent.firstChild; node; node = node.nextSibling ) {
- if ( node.nodeType === 1 ) {
- node.nodeIndex = ++count;
- }
- }
-
- parent.sizcache = doneName;
- }
-
- var diff = elem.nodeIndex - last;
-
- if ( first === 0 ) {
- return diff === 0;
-
- } else {
- return ( diff % first === 0 && diff / first >= 0 );
- }
- }
- },
-
- ID: function( elem, match ) {
- return elem.nodeType === 1 && elem.getAttribute("id") === match;
- },
-
- TAG: function( elem, match ) {
- return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
- },
-
- CLASS: function( elem, match ) {
- return (" " + (elem.className || elem.getAttribute("class")) + " ")
- .indexOf( match ) > -1;
- },
-
- ATTR: function( elem, match ) {
- var name = match[1],
- result = Expr.attrHandle[ name ] ?
- Expr.attrHandle[ name ]( elem ) :
- elem[ name ] != null ?
- elem[ name ] :
- elem.getAttribute( name ),
- value = result + "",
- type = match[2],
- check = match[4];
-
- return result == null ?
- type === "!=" :
- type === "=" ?
- value === check :
- type === "*=" ?
- value.indexOf(check) >= 0 :
- type === "~=" ?
- (" " + value + " ").indexOf(check) >= 0 :
- !check ?
- value && result !== false :
- type === "!=" ?
- value !== check :
- type === "^=" ?
- value.indexOf(check) === 0 :
- type === "$=" ?
- value.substr(value.length - check.length) === check :
- type === "|=" ?
- value === check || value.substr(0, check.length + 1) === check + "-" :
- false;
- },
-
- POS: function( elem, match, i, array ) {
- var name = match[2],
- filter = Expr.setFilters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- }
- }
- }
-};
-
-var origPOS = Expr.match.POS,
- fescape = function(all, num){
- return "\\" + (num - 0 + 1);
- };
-
-for ( var type in Expr.match ) {
- Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
- Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
-}
-
-var makeArray = function( array, results ) {
- array = Array.prototype.slice.call( array, 0 );
-
- if ( results ) {
- results.push.apply( results, array );
- return results;
- }
-
- return array;
-};
-
-// Perform a simple check to determine if the browser is capable of
-// converting a NodeList to an array using builtin methods.
-// Also verifies that the returned array holds DOM nodes
-// (which is not the case in the Blackberry browser)
-try {
- Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
-
-// Provide a fallback method if it does not work
-} catch( e ) {
- makeArray = function( array, results ) {
- var i = 0,
- ret = results || [];
-
- if ( toString.call(array) === "[object Array]" ) {
- Array.prototype.push.apply( ret, array );
-
- } else {
- if ( typeof array.length === "number" ) {
- for ( var l = array.length; i < l; i++ ) {
- ret.push( array[i] );
- }
-
- } else {
- for ( ; array[i]; i++ ) {
- ret.push( array[i] );
- }
- }
- }
-
- return ret;
- };
-}
-
-var sortOrder, siblingCheck;
-
-if ( document.documentElement.compareDocumentPosition ) {
- sortOrder = function( a, b ) {
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
-
- if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
- return a.compareDocumentPosition ? -1 : 1;
- }
-
- return a.compareDocumentPosition(b) & 4 ? -1 : 1;
- };
-
-} else {
- sortOrder = function( a, b ) {
- var al, bl,
- ap = [],
- bp = [],
- aup = a.parentNode,
- bup = b.parentNode,
- cur = aup;
-
- // The nodes are identical, we can exit early
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
-
- // If the nodes are siblings (or identical) we can do a quick check
- } else if ( aup === bup ) {
- return siblingCheck( a, b );
-
- // If no parents were found then the nodes are disconnected
- } else if ( !aup ) {
- return -1;
-
- } else if ( !bup ) {
- return 1;
- }
-
- // Otherwise they're somewhere else in the tree so we need
- // to build up a full list of the parentNodes for comparison
- while ( cur ) {
- ap.unshift( cur );
- cur = cur.parentNode;
- }
-
- cur = bup;
-
- while ( cur ) {
- bp.unshift( cur );
- cur = cur.parentNode;
- }
-
- al = ap.length;
- bl = bp.length;
-
- // Start walking down the tree looking for a discrepancy
- for ( var i = 0; i < al && i < bl; i++ ) {
- if ( ap[i] !== bp[i] ) {
- return siblingCheck( ap[i], bp[i] );
- }
- }
-
- // We ended someplace up the tree so do a sibling check
- return i === al ?
- siblingCheck( a, bp[i], -1 ) :
- siblingCheck( ap[i], b, 1 );
- };
-
- siblingCheck = function( a, b, ret ) {
- if ( a === b ) {
- return ret;
- }
-
- var cur = a.nextSibling;
-
- while ( cur ) {
- if ( cur === b ) {
- return -1;
- }
-
- cur = cur.nextSibling;
- }
-
- return 1;
- };
-}
-
-// Utility function for retreiving the text value of an array of DOM nodes
-Sizzle.getText = function( elems ) {
- var ret = "", elem;
-
- for ( var i = 0; elems[i]; i++ ) {
- elem = elems[i];
-
- // Get the text from text nodes and CDATA nodes
- if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
- ret += elem.nodeValue;
-
- // Traverse everything else, except comment nodes
- } else if ( elem.nodeType !== 8 ) {
- ret += Sizzle.getText( elem.childNodes );
- }
- }
-
- return ret;
-};
-
-// Check to see if the browser returns elements by name when
-// querying by getElementById (and provide a workaround)
-(function(){
- // We're going to inject a fake input element with a specified name
- var form = document.createElement("div"),
- id = "script" + (new Date()).getTime(),
- root = document.documentElement;
-
- form.innerHTML = "<a name='" + id + "'/>";
-
- // Inject it into the root element, check its status, and remove it quickly
- root.insertBefore( form, root.firstChild );
-
- // The workaround has to do additional checks after a getElementById
- // Which slows things down for other browsers (hence the branching)
- if ( document.getElementById( id ) ) {
- Expr.find.ID = function( match, context, isXML ) {
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
-
- return m ?
- m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
- [m] :
- undefined :
- [];
- }
- };
-
- Expr.filter.ID = function( elem, match ) {
- var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
-
- return elem.nodeType === 1 && node && node.nodeValue === match;
- };
- }
-
- root.removeChild( form );
-
- // release memory in IE
- root = form = null;
-})();
-
-(function(){
- // Check to see if the browser returns only elements
- // when doing getElementsByTagName("*")
-
- // Create a fake element
- var div = document.createElement("div");
- div.appendChild( document.createComment("") );
-
- // Make sure no comments are found
- if ( div.getElementsByTagName("*").length > 0 ) {
- Expr.find.TAG = function( match, context ) {
- var results = context.getElementsByTagName( match[1] );
-
- // Filter out possible comments
- if ( match[1] === "*" ) {
- var tmp = [];
-
- for ( var i = 0; results[i]; i++ ) {
- if ( results[i].nodeType === 1 ) {
- tmp.push( results[i] );
- }
- }
-
- results = tmp;
- }
-
- return results;
- };
- }
-
- // Check to see if an attribute returns normalized href attributes
- div.innerHTML = "<a href='#'></a>";
-
- if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
- div.firstChild.getAttribute("href") !== "#" ) {
-
- Expr.attrHandle.href = function( elem ) {
- return elem.getAttribute( "href", 2 );
- };
- }
-
- // release memory in IE
- div = null;
-})();
-
-if ( document.querySelectorAll ) {
- (function(){
- var oldSizzle = Sizzle,
- div = document.createElement("div"),
- id = "__sizzle__";
-
- div.innerHTML = "<p class='TEST'></p>";
-
- // Safari can't handle uppercase or unicode characters when
- // in quirks mode.
- if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
- return;
- }
-
- Sizzle = function( query, context, extra, seed ) {
- context = context || document;
-
- // Only use querySelectorAll on non-XML documents
- // (ID selectors don't work in non-HTML documents)
- if ( !seed && !Sizzle.isXML(context) ) {
- // See if we find a selector to speed up
- var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
-
- if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
- // Speed-up: Sizzle("TAG")
- if ( match[1] ) {
- return makeArray( context.getElementsByTagName( query ), extra );
-
- // Speed-up: Sizzle(".CLASS")
- } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
- return makeArray( context.getElementsByClassName( match[2] ), extra );
- }
- }
-
- if ( context.nodeType === 9 ) {
- // Speed-up: Sizzle("body")
- // The body element only exists once, optimize finding it
- if ( query === "body" && context.body ) {
- return makeArray( [ context.body ], extra );
-
- // Speed-up: Sizzle("#ID")
- } else if ( match && match[3] ) {
- var elem = context.getElementById( match[3] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id === match[3] ) {
- return makeArray( [ elem ], extra );
- }
-
- } else {
- return makeArray( [], extra );
- }
- }
-
- try {
- return makeArray( context.querySelectorAll(query), extra );
- } catch(qsaError) {}
-
- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- var old = context.getAttribute( "id" ),
- nid = old || id,
- hasParent = context.parentNode,
- relativeHierarchySelector = /^\s*[+~]/.test( query );
-
- if ( !old ) {
- context.setAttribute( "id", nid );
- } else {
- nid = nid.replace( /'/g, "\\$&" );
- }
- if ( relativeHierarchySelector && hasParent ) {
- context = context.parentNode;
- }
-
- try {
- if ( !relativeHierarchySelector || hasParent ) {
- return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
- }
-
- } catch(pseudoError) {
- } finally {
- if ( !old ) {
- context.removeAttribute( "id" );
- }
- }
- }
- }
-
- return oldSizzle(query, context, extra, seed);
- };
-
- for ( var prop in oldSizzle ) {
- Sizzle[ prop ] = oldSizzle[ prop ];
- }
-
- // release memory in IE
- div = null;
- })();
-}
-
-(function(){
- var html = document.documentElement,
- matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector,
- pseudoWorks = false;
-
- try {
- // This should fail with an exception
- // Gecko does not error, returns false instead
- matches.call( document.documentElement, "[test!='']:sizzle" );
-
- } catch( pseudoError ) {
- pseudoWorks = true;
- }
-
- if ( matches ) {
- Sizzle.matchesSelector = function( node, expr ) {
- // Make sure that attribute selectors are quoted
- expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
-
- if ( !Sizzle.isXML( node ) ) {
- try {
- if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
- return matches.call( node, expr );
- }
- } catch(e) {}
- }
-
- return Sizzle(expr, null, null, [node]).length > 0;
- };
- }
-})();
-
-(function(){
- var div = document.createElement("div");
-
- div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
- // Opera can't find a second classname (in 9.6)
- // Also, make sure that getElementsByClassName actually exists
- if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
- return;
- }
-
- // Safari caches class attributes, doesn't catch changes (in 3.2)
- div.lastChild.className = "e";
-
- if ( div.getElementsByClassName("e").length === 1 ) {
- return;
- }
-
- Expr.order.splice(1, 0, "CLASS");
- Expr.find.CLASS = function( match, context, isXML ) {
- if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
- return context.getElementsByClassName(match[1]);
- }
- };
-
- // release memory in IE
- div = null;
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
-
- if ( elem ) {
- var match = false;
-
- elem = elem[dir];
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 && !isXML ){
- elem.sizcache = doneName;
- elem.sizset = i;
- }
-
- if ( elem.nodeName.toLowerCase() === cur ) {
- match = elem;
- break;
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
-
- if ( elem ) {
- var match = false;
-
- elem = elem[dir];
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 ) {
- if ( !isXML ) {
- elem.sizcache = doneName;
- elem.sizset = i;
- }
-
- if ( typeof cur !== "string" ) {
- if ( elem === cur ) {
- match = true;
- break;
- }
-
- } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
- match = elem;
- break;
- }
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-if ( document.documentElement.contains ) {
- Sizzle.contains = function( a, b ) {
- return a !== b && (a.contains ? a.contains(b) : true);
- };
-
-} else if ( document.documentElement.compareDocumentPosition ) {
- Sizzle.contains = function( a, b ) {
- return !!(a.compareDocumentPosition(b) & 16);
- };
-
-} else {
- Sizzle.contains = function() {
- return false;
- };
-}
-
-Sizzle.isXML = function( elem ) {
- // documentElement is verified for cases where it doesn't yet exist
- // (such as loading iframes in IE - #4833)
- var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
-
- return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-var posProcess = function( selector, context ) {
- var match,
- tmpSet = [],
- later = "",
- root = context.nodeType ? [context] : context;
-
- // Position selectors must be done after the filter
- // And so must :not(positional) so we move all PSEUDOs to the end
- while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
- later += match[0];
- selector = selector.replace( Expr.match.PSEUDO, "" );
- }
-
- selector = Expr.relative[selector] ? selector + "*" : selector;
-
- for ( var i = 0, l = root.length; i < l; i++ ) {
- Sizzle( selector, root[i], tmpSet );
- }
-
- return Sizzle.filter( later, tmpSet );
-};
-
-// EXPOSE
-jQuery.find = Sizzle;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.filters;
-jQuery.unique = Sizzle.uniqueSort;
-jQuery.text = Sizzle.getText;
-jQuery.isXMLDoc = Sizzle.isXML;
-jQuery.contains = Sizzle.contains;
-
-
-})();
-
-
-var runtil = /Until$/,
- rparentsprev = /^(?:parents|prevUntil|prevAll)/,
- // Note: This RegExp should be improved, or likely pulled from Sizzle
- rmultiselector = /,/,
- isSimple = /^.[^:#\[\.,]*$/,
- slice = Array.prototype.slice,
- POS = jQuery.expr.match.POS,
- // methods guaranteed to produce a unique set when starting from a unique set
- guaranteedUnique = {
- children: true,
- contents: true,
- next: true,
- prev: true
- };
-
-jQuery.fn.extend({
- find: function( selector ) {
- var ret = this.pushStack( "", "find", selector ),
- length = 0;
-
- for ( var i = 0, l = this.length; i < l; i++ ) {
- length = ret.length;
- jQuery.find( selector, this[i], ret );
-
- if ( i > 0 ) {
- // Make sure that the results are unique
- for ( var n = length; n < ret.length; n++ ) {
- for ( var r = 0; r < length; r++ ) {
- if ( ret[r] === ret[n] ) {
- ret.splice(n--, 1);
- break;
- }
- }
- }
- }
- }
-
- return ret;
- },
-
- has: function( target ) {
- var targets = jQuery( target );
- return this.filter(function() {
- for ( var i = 0, l = targets.length; i < l; i++ ) {
- if ( jQuery.contains( this, targets[i] ) ) {
- return true;
- }
- }
- });
- },
-
- not: function( selector ) {
- return this.pushStack( winnow(this, selector, false), "not", selector);
- },
-
- filter: function( selector ) {
- return this.pushStack( winnow(this, selector, true), "filter", selector );
- },
-
- is: function( selector ) {
- return !!selector && jQuery.filter( selector, this ).length > 0;
- },
-
- closest: function( selectors, context ) {
- var ret = [], i, l, cur = this[0];
-
- if ( jQuery.isArray( selectors ) ) {
- var match, selector,
- matches = {},
- level = 1;
-
- if ( cur && selectors.length ) {
- for ( i = 0, l = selectors.length; i < l; i++ ) {
- selector = selectors[i];
-
- if ( !matches[selector] ) {
- matches[selector] = jQuery.expr.match.POS.test( selector ) ?
- jQuery( selector, context || this.context ) :
- selector;
- }
- }
-
- while ( cur && cur.ownerDocument && cur !== context ) {
- for ( selector in matches ) {
- match = matches[selector];
-
- if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
- ret.push({ selector: selector, elem: cur, level: level });
- }
- }
-
- cur = cur.parentNode;
- level++;
- }
- }
-
- return ret;
- }
-
- var pos = POS.test( selectors ) ?
- jQuery( selectors, context || this.context ) : null;
-
- for ( i = 0, l = this.length; i < l; i++ ) {
- cur = this[i];
-
- while ( cur ) {
- if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
- ret.push( cur );
- break;
-
- } else {
- cur = cur.parentNode;
- if ( !cur || !cur.ownerDocument || cur === context ) {
- break;
- }
- }
- }
- }
-
- ret = ret.length > 1 ? jQuery.unique(ret) : ret;
-
- return this.pushStack( ret, "closest", selectors );
- },
-
- // Determine the position of an element within
- // the matched set of elements
- index: function( elem ) {
- if ( !elem || typeof elem === "string" ) {
- return jQuery.inArray( this[0],
- // If it receives a string, the selector is used
- // If it receives nothing, the siblings are used
- elem ? jQuery( elem ) : this.parent().children() );
- }
- // Locate the position of the desired element
- return jQuery.inArray(
- // If it receives a jQuery object, the first element is used
- elem.jquery ? elem[0] : elem, this );
- },
-
- add: function( selector, context ) {
- var set = typeof selector === "string" ?
- jQuery( selector, context ) :
- jQuery.makeArray( selector ),
- all = jQuery.merge( this.get(), set );
-
- return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
- all :
- jQuery.unique( all ) );
- },
-
- andSelf: function() {
- return this.add( this.prevObject );
- }
-});
-
-// A painfully simple check to see if an element is disconnected
-// from a document (should be improved, where feasible).
-function isDisconnected( node ) {
- return !node || !node.parentNode || node.parentNode.nodeType === 11;
-}
-
-jQuery.each({
- parent: function( elem ) {
- var parent = elem.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
- },
- parents: function( elem ) {
- return jQuery.dir( elem, "parentNode" );
- },
- parentsUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "parentNode", until );
- },
- next: function( elem ) {
- return jQuery.nth( elem, 2, "nextSibling" );
- },
- prev: function( elem ) {
- return jQuery.nth( elem, 2, "previousSibling" );
- },
- nextAll: function( elem ) {
- return jQuery.dir( elem, "nextSibling" );
- },
- prevAll: function( elem ) {
- return jQuery.dir( elem, "previousSibling" );
- },
- nextUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "nextSibling", until );
- },
- prevUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "previousSibling", until );
- },
- siblings: function( elem ) {
- return jQuery.sibling( elem.parentNode.firstChild, elem );
- },
- children: function( elem ) {
- return jQuery.sibling( elem.firstChild );
- },
- contents: function( elem ) {
- return jQuery.nodeName( elem, "iframe" ) ?
- elem.contentDocument || elem.contentWindow.document :
- jQuery.makeArray( elem.childNodes );
- }
-}, function( name, fn ) {
- jQuery.fn[ name ] = function( until, selector ) {
- var ret = jQuery.map( this, fn, until ),
- // The variable 'args' was introduced in
- // https://github.com/jquery/jquery/commit/52a0238
- // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
- // http://code.google.com/p/v8/issues/detail?id=1050
- args = slice.call(arguments);
-
- if ( !runtil.test( name ) ) {
- selector = until;
- }
-
- if ( selector && typeof selector === "string" ) {
- ret = jQuery.filter( selector, ret );
- }
-
- ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
-
- if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
- ret = ret.reverse();
- }
-
- return this.pushStack( ret, name, args.join(",") );
- };
-});
-
-jQuery.extend({
- filter: function( expr, elems, not ) {
- if ( not ) {
- expr = ":not(" + expr + ")";
- }
-
- return elems.length === 1 ?
- jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
- jQuery.find.matches(expr, elems);
- },
-
- dir: function( elem, dir, until ) {
- var matched = [],
- cur = elem[ dir ];
-
- while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
- if ( cur.nodeType === 1 ) {
- matched.push( cur );
- }
- cur = cur[dir];
- }
- return matched;
- },
-
- nth: function( cur, result, dir, elem ) {
- result = result || 1;
- var num = 0;
-
- for ( ; cur; cur = cur[dir] ) {
- if ( cur.nodeType === 1 && ++num === result ) {
- break;
- }
- }
-
- return cur;
- },
-
- sibling: function( n, elem ) {
- var r = [];
-
- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType === 1 && n !== elem ) {
- r.push( n );
- }
- }
-
- return r;
- }
-});
-
-// Implement the identical functionality for filter and not
-function winnow( elements, qualifier, keep ) {
- if ( jQuery.isFunction( qualifier ) ) {
- return jQuery.grep(elements, function( elem, i ) {
- var retVal = !!qualifier.call( elem, i, elem );
- return retVal === keep;
- });
-
- } else if ( qualifier.nodeType ) {
- return jQuery.grep(elements, function( elem, i ) {
- return (elem === qualifier) === keep;
- });
-
- } else if ( typeof qualifier === "string" ) {
- var filtered = jQuery.grep(elements, function( elem ) {
- return elem.nodeType === 1;
- });
-
- if ( isSimple.test( qualifier ) ) {
- return jQuery.filter(qualifier, filtered, !keep);
- } else {
- qualifier = jQuery.filter( qualifier, filtered );
- }
- }
-
- return jQuery.grep(elements, function( elem, i ) {
- return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
- });
-}
-
-
-
-
-var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
- rleadingWhitespace = /^\s+/,
- rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
- rtagName = /<([\w:]+)/,
- rtbody = /<tbody/i,
- rhtml = /<|&#?\w+;/,
- rnocache = /<(?:script|object|embed|option|style)/i,
- // checked="checked" or checked (html5)
- rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
- wrapMap = {
- option: [ 1, "<select multiple='multiple'>", "</select>" ],
- legend: [ 1, "<fieldset>", "</fieldset>" ],
- thead: [ 1, "<table>", "</table>" ],
- tr: [ 2, "<table><tbody>", "</tbody></table>" ],
- td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
- col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
- area: [ 1, "<map>", "</map>" ],
- _default: [ 0, "", "" ]
- };
-
-wrapMap.optgroup = wrapMap.option;
-wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-wrapMap.th = wrapMap.td;
-
-// IE can't serialize <link> and <script> tags normally
-if ( !jQuery.support.htmlSerialize ) {
- wrapMap._default = [ 1, "div<div>", "</div>" ];
-}
-
-jQuery.fn.extend({
- text: function( text ) {
- if ( jQuery.isFunction(text) ) {
- return this.each(function(i) {
- var self = jQuery( this );
-
- self.text( text.call(this, i, self.text()) );
- });
- }
-
- if ( typeof text !== "object" && text !== undefined ) {
- return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
- }
-
- return jQuery.text( this );
- },
-
- wrapAll: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapAll( html.call(this, i) );
- });
- }
-
- if ( this[0] ) {
- // The elements to wrap the target around
- var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
-
- if ( this[0].parentNode ) {
- wrap.insertBefore( this[0] );
- }
-
- wrap.map(function() {
- var elem = this;
-
- while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
- elem = elem.firstChild;
- }
-
- return elem;
- }).append(this);
- }
-
- return this;
- },
-
- wrapInner: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapInner( html.call(this, i) );
- });
- }
-
- return this.each(function() {
- var self = jQuery( this ),
- contents = self.contents();
-
- if ( contents.length ) {
- contents.wrapAll( html );
-
- } else {
- self.append( html );
- }
- });
- },
-
- wrap: function( html ) {
- return this.each(function() {
- jQuery( this ).wrapAll( html );
- });
- },
-
- unwrap: function() {
- return this.parent().each(function() {
- if ( !jQuery.nodeName( this, "body" ) ) {
- jQuery( this ).replaceWith( this.childNodes );
- }
- }).end();
- },
-
- append: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 ) {
- this.appendChild( elem );
- }
- });
- },
-
- prepend: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 ) {
- this.insertBefore( elem, this.firstChild );
- }
- });
- },
-
- before: function() {
- if ( this[0] && this[0].parentNode ) {
- return this.domManip(arguments, false, function( elem ) {
- this.parentNode.insertBefore( elem, this );
- });
- } else if ( arguments.length ) {
- var set = jQuery(arguments[0]);
- set.push.apply( set, this.toArray() );
- return this.pushStack( set, "before", arguments );
- }
- },
-
- after: function() {
- if ( this[0] && this[0].parentNode ) {
- return this.domManip(arguments, false, function( elem ) {
- this.parentNode.insertBefore( elem, this.nextSibling );
- });
- } else if ( arguments.length ) {
- var set = this.pushStack( this, "after", arguments );
- set.push.apply( set, jQuery(arguments[0]).toArray() );
- return set;
- }
- },
-
- // keepData is for internal use only--do not document
- remove: function( selector, keepData ) {
- for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
- if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
- if ( !keepData && elem.nodeType === 1 ) {
- jQuery.cleanData( elem.getElementsByTagName("*") );
- jQuery.cleanData( [ elem ] );
- }
-
- if ( elem.parentNode ) {
- elem.parentNode.removeChild( elem );
- }
- }
- }
-
- return this;
- },
-
- empty: function() {
- for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( elem.getElementsByTagName("*") );
- }
-
- // Remove any remaining nodes
- while ( elem.firstChild ) {
- elem.removeChild( elem.firstChild );
- }
- }
-
- return this;
- },
-
- clone: function( dataAndEvents, deepDataAndEvents ) {
- dataAndEvents = dataAndEvents == null ? true : dataAndEvents;
- deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
-
- return this.map( function () {
- return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
- });
- },
-
- html: function( value ) {
- if ( value === undefined ) {
- return this[0] && this[0].nodeType === 1 ?
- this[0].innerHTML.replace(rinlinejQuery, "") :
- null;
-
- // See if we can take a shortcut and just use innerHTML
- } else if ( typeof value === "string" && !rnocache.test( value ) &&
- (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
- !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
-
- value = value.replace(rxhtmlTag, "<$1></$2>");
-
- try {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( this[i].nodeType === 1 ) {
- jQuery.cleanData( this[i].getElementsByTagName("*") );
- this[i].innerHTML = value;
- }
- }
-
- // If using innerHTML throws an exception, use the fallback method
- } catch(e) {
- this.empty().append( value );
- }
-
- } else if ( jQuery.isFunction( value ) ) {
- this.each(function(i){
- var self = jQuery( this );
-
- self.html( value.call(this, i, self.html()) );
- });
-
- } else {
- this.empty().append( value );
- }
-
- return this;
- },
-
- replaceWith: function( value ) {
- if ( this[0] && this[0].parentNode ) {
- // Make sure that the elements are removed from the DOM before they are inserted
- // this can help fix replacing a parent with child elements
- if ( jQuery.isFunction( value ) ) {
- return this.each(function(i) {
- var self = jQuery(this), old = self.html();
- self.replaceWith( value.call( this, i, old ) );
- });
- }
-
- if ( typeof value !== "string" ) {
- value = jQuery( value ).detach();
- }
-
- return this.each(function() {
- var next = this.nextSibling,
- parent = this.parentNode;
-
- jQuery( this ).remove();
-
- if ( next ) {
- jQuery(next).before( value );
- } else {
- jQuery(parent).append( value );
- }
- });
- } else {
- return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
- }
- },
-
- detach: function( selector ) {
- return this.remove( selector, true );
- },
-
- domManip: function( args, table, callback ) {
- var results, first, fragment, parent,
- value = args[0],
- scripts = [];
-
- // We can't cloneNode fragments that contain checked, in WebKit
- if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
- return this.each(function() {
- jQuery(this).domManip( args, table, callback, true );
- });
- }
-
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- args[0] = value.call(this, i, table ? self.html() : undefined);
- self.domManip( args, table, callback );
- });
- }
-
- if ( this[0] ) {
- parent = value && value.parentNode;
-
- // If we're in a fragment, just use that instead of building a new one
- if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
- results = { fragment: parent };
-
- } else {
- results = jQuery.buildFragment( args, this, scripts );
- }
-
- fragment = results.fragment;
-
- if ( fragment.childNodes.length === 1 ) {
- first = fragment = fragment.firstChild;
- } else {
- first = fragment.firstChild;
- }
-
- if ( first ) {
- table = table && jQuery.nodeName( first, "tr" );
-
- for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
- callback.call(
- table ?
- root(this[i], first) :
- this[i],
- // Make sure that we do not leak memory by inadvertently discarding
- // the original fragment (which might have attached data) instead of
- // using it; in addition, use the original fragment object for the last
- // item instead of first because it can end up being emptied incorrectly
- // in certain situations (Bug #8070).
- // Fragments from the fragment cache must always be cloned and never used
- // in place.
- results.cacheable || (l > 1 && i < lastIndex) ?
- jQuery.clone( fragment, true, true ) :
- fragment
- );
- }
- }
-
- if ( scripts.length ) {
- jQuery.each( scripts, evalScript );
- }
- }
-
- return this;
- }
-});
-
-function root( elem, cur ) {
- return jQuery.nodeName(elem, "table") ?
- (elem.getElementsByTagName("tbody")[0] ||
- elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
- elem;
-}
-
-function cloneCopyEvent( src, dest ) {
-
- if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
- return;
- }
-
- var internalKey = jQuery.expando,
- oldData = jQuery.data( src ),
- curData = jQuery.data( dest, oldData );
-
- // Switch to use the internal data object, if it exists, for the next
- // stage of data copying
- if ( (oldData = oldData[ internalKey ]) ) {
- var events = oldData.events;
- curData = curData[ internalKey ] = jQuery.extend({}, oldData);
-
- if ( events ) {
- delete curData.handle;
- curData.events = {};
-
- for ( var type in events ) {
- for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
- jQuery.event.add( dest, type, events[ type ][ i ], events[ type ][ i ].data );
- }
- }
- }
- }
-}
-
-function cloneFixAttributes(src, dest) {
- // We do not need to do anything for non-Elements
- if ( dest.nodeType !== 1 ) {
- return;
- }
-
- var nodeName = dest.nodeName.toLowerCase();
-
- // clearAttributes removes the attributes, which we don't want,
- // but also removes the attachEvent events, which we *do* want
- dest.clearAttributes();
-
- // mergeAttributes, in contrast, only merges back on the
- // original attributes, not the events
- dest.mergeAttributes(src);
-
- // IE6-8 fail to clone children inside object elements that use
- // the proprietary classid attribute value (rather than the type
- // attribute) to identify the type of content to display
- if ( nodeName === "object" ) {
- dest.outerHTML = src.outerHTML;
-
- } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
- // IE6-8 fails to persist the checked state of a cloned checkbox
- // or radio button. Worse, IE6-7 fail to give the cloned element
- // a checked appearance if the defaultChecked value isn't also set
- if ( src.checked ) {
- dest.defaultChecked = dest.checked = src.checked;
- }
-
- // IE6-7 get confused and end up setting the value of a cloned
- // checkbox/radio button to an empty string instead of "on"
- if ( dest.value !== src.value ) {
- dest.value = src.value;
- }
-
- // IE6-8 fails to return the selected option to the default selected
- // state when cloning options
- } else if ( nodeName === "option" ) {
- dest.selected = src.defaultSelected;
-
- // IE6-8 fails to set the defaultValue to the correct value when
- // cloning other types of input fields
- } else if ( nodeName === "input" || nodeName === "textarea" ) {
- dest.defaultValue = src.defaultValue;
- }
-
- // Event data gets referenced instead of copied if the expando
- // gets copied too
- dest.removeAttribute( jQuery.expando );
-}
-
-jQuery.buildFragment = function( args, nodes, scripts ) {
- var fragment, cacheable, cacheresults,
- doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
-
- // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
- // Cloning options loses the selected state, so don't cache them
- // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
- // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
- if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
- args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
-
- cacheable = true;
- cacheresults = jQuery.fragments[ args[0] ];
- if ( cacheresults ) {
- if ( cacheresults !== 1 ) {
- fragment = cacheresults;
- }
- }
- }
-
- if ( !fragment ) {
- fragment = doc.createDocumentFragment();
- jQuery.clean( args, doc, fragment, scripts );
- }
-
- if ( cacheable ) {
- jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
- }
-
- return { fragment: fragment, cacheable: cacheable };
-};
-
-jQuery.fragments = {};
-
-jQuery.each({
- appendTo: "append",
- prependTo: "prepend",
- insertBefore: "before",
- insertAfter: "after",
- replaceAll: "replaceWith"
-}, function( name, original ) {
- jQuery.fn[ name ] = function( selector ) {
- var ret = [],
- insert = jQuery( selector ),
- parent = this.length === 1 && this[0].parentNode;
-
- if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
- insert[ original ]( this[0] );
- return this;
-
- } else {
- for ( var i = 0, l = insert.length; i < l; i++ ) {
- var elems = (i > 0 ? this.clone(true) : this).get();
- jQuery( insert[i] )[ original ]( elems );
- ret = ret.concat( elems );
- }
-
- return this.pushStack( ret, name, insert.selector );
- }
- };
-});
-
-jQuery.extend({
- clone: function( elem, dataAndEvents, deepDataAndEvents ) {
- var clone = elem.cloneNode(true),
- srcElements,
- destElements,
- i;
-
- if ( !jQuery.support.noCloneEvent && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
- // IE copies events bound via attachEvent when using cloneNode.
- // Calling detachEvent on the clone will also remove the events
- // from the original. In order to get around this, we use some
- // proprietary methods to clear the events. Thanks to MooTools
- // guys for this hotness.
-
- // Using Sizzle here is crazy slow, so we use getElementsByTagName
- // instead
- srcElements = elem.getElementsByTagName("*");
- destElements = clone.getElementsByTagName("*");
-
- // Weird iteration because IE will replace the length property
- // with an element if you are cloning the body and one of the
- // elements on the page has a name or id of "length"
- for ( i = 0; srcElements[i]; ++i ) {
- cloneFixAttributes( srcElements[i], destElements[i] );
- }
-
- cloneFixAttributes( elem, clone );
- }
-
- // Copy the events from the original to the clone
- if ( dataAndEvents ) {
-
- cloneCopyEvent( elem, clone );
-
- if ( deepDataAndEvents && "getElementsByTagName" in elem ) {
-
- srcElements = elem.getElementsByTagName("*");
- destElements = clone.getElementsByTagName("*");
-
- if ( srcElements.length ) {
- for ( i = 0; srcElements[i]; ++i ) {
- cloneCopyEvent( srcElements[i], destElements[i] );
- }
- }
- }
- }
- // Return the cloned set
- return clone;
- },
- clean: function( elems, context, fragment, scripts ) {
- context = context || document;
-
- // !context.createElement fails in IE with an error but returns typeof 'object'
- if ( typeof context.createElement === "undefined" ) {
- context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
- }
-
- var ret = [];
-
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- if ( typeof elem === "number" ) {
- elem += "";
- }
-
- if ( !elem ) {
- continue;
- }
-
- // Convert html string into DOM nodes
- if ( typeof elem === "string" && !rhtml.test( elem ) ) {
- elem = context.createTextNode( elem );
-
- } else if ( typeof elem === "string" ) {
- // Fix "XHTML"-style tags in all browsers
- elem = elem.replace(rxhtmlTag, "<$1></$2>");
-
- // Trim whitespace, otherwise indexOf won't work as expected
- var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
- wrap = wrapMap[ tag ] || wrapMap._default,
- depth = wrap[0],
- div = context.createElement("div");
-
- // Go to html and back, then peel off extra wrappers
- div.innerHTML = wrap[1] + elem + wrap[2];
-
- // Move to the right depth
- while ( depth-- ) {
- div = div.lastChild;
- }
-
- // Remove IE's autoinserted <tbody> from table fragments
- if ( !jQuery.support.tbody ) {
-
- // String was a <table>, *may* have spurious <tbody>
- var hasBody = rtbody.test(elem),
- tbody = tag === "table" && !hasBody ?
- div.firstChild && div.firstChild.childNodes :
-
- // String was a bare <thead> or <tfoot>
- wrap[1] === "<table>" && !hasBody ?
- div.childNodes :
- [];
-
- for ( var j = tbody.length - 1; j >= 0 ; --j ) {
- if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
- tbody[ j ].parentNode.removeChild( tbody[ j ] );
- }
- }
-
- }
-
- // IE completely kills leading whitespace when innerHTML is used
- if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
- div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
- }
-
- elem = div.childNodes;
- }
-
- if ( elem.nodeType ) {
- ret.push( elem );
- } else {
- ret = jQuery.merge( ret, elem );
- }
- }
-
- if ( fragment ) {
- for ( i = 0; ret[i]; i++ ) {
- if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
- scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
-
- } else {
- if ( ret[i].nodeType === 1 ) {
- ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
- }
- fragment.appendChild( ret[i] );
- }
- }
- }
-
- return ret;
- },
-
- cleanData: function( elems ) {
- var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
- deleteExpando = jQuery.support.deleteExpando;
-
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
- continue;
- }
-
- id = elem[ jQuery.expando ];
-
- if ( id ) {
- data = cache[ id ] && cache[ id ][ internalKey ];
-
- if ( data && data.events ) {
- for ( var type in data.events ) {
- if ( special[ type ] ) {
- jQuery.event.remove( elem, type );
-
- // This is a shortcut to avoid jQuery.event.remove's overhead
- } else {
- jQuery.removeEvent( elem, type, data.handle );
- }
- }
-
- // Null the DOM reference to avoid IE6/7/8 leak (#7054)
- if ( data.handle ) {
- data.handle.elem = null;
- }
- }
-
- if ( deleteExpando ) {
- delete elem[ jQuery.expando ];
-
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( jQuery.expando );
- }
-
- delete cache[ id ];
- }
- }
- }
-});
-
-function evalScript( i, elem ) {
- if ( elem.src ) {
- jQuery.ajax({
- url: elem.src,
- async: false,
- dataType: "script"
- });
- } else {
- jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
- }
-
- if ( elem.parentNode ) {
- elem.parentNode.removeChild( elem );
- }
-}
-
-
-
-
-var ralpha = /alpha\([^)]*\)/i,
- ropacity = /opacity=([^)]*)/,
- rdashAlpha = /-([a-z])/ig,
- rupper = /([A-Z])/g,
- rnumpx = /^-?\d+(?:px)?$/i,
- rnum = /^-?\d/,
-
- cssShow = { position: "absolute", visibility: "hidden", display: "block" },
- cssWidth = [ "Left", "Right" ],
- cssHeight = [ "Top", "Bottom" ],
- curCSS,
-
- getComputedStyle,
- currentStyle,
-
- fcamelCase = function( all, letter ) {
- return letter.toUpperCase();
- };
-
-jQuery.fn.css = function( name, value ) {
- // Setting 'undefined' is a no-op
- if ( arguments.length === 2 && value === undefined ) {
- return this;
- }
-
- return jQuery.access( this, name, value, true, function( elem, name, value ) {
- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- });
-};
-
-jQuery.extend({
- // Add in style property hooks for overriding the default
- // behavior of getting and setting a style property
- cssHooks: {
- opacity: {
- get: function( elem, computed ) {
- if ( computed ) {
- // We should always get a number back from opacity
- var ret = curCSS( elem, "opacity", "opacity" );
- return ret === "" ? "1" : ret;
-
- } else {
- return elem.style.opacity;
- }
- }
- }
- },
-
- // Exclude the following css properties to add px
- cssNumber: {
- "zIndex": true,
- "fontWeight": true,
- "opacity": true,
- "zoom": true,
- "lineHeight": true
- },
-
- // Add in properties whose names you wish to fix before
- // setting or getting the value
- cssProps: {
- // normalize float css property
- "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
- },
-
- // Get and set the style property on a DOM Node
- style: function( elem, name, value, extra ) {
- // Don't set styles on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
- return;
- }
-
- // Make sure that we're working with the right name
- var ret, origName = jQuery.camelCase( name ),
- style = elem.style, hooks = jQuery.cssHooks[ origName ];
-
- name = jQuery.cssProps[ origName ] || origName;
-
- // Check if we're setting a value
- if ( value !== undefined ) {
- // Make sure that NaN and null values aren't set. See: #7116
- if ( typeof value === "number" && isNaN( value ) || value == null ) {
- return;
- }
-
- // If a number was passed in, add 'px' to the (except for certain CSS properties)
- if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
- value += "px";
- }
-
- // If a hook was provided, use that value, otherwise just set the specified value
- if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
- // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
- // Fixes bug #5509
- try {
- style[ name ] = value;
- } catch(e) {}
- }
-
- } else {
- // If a hook was provided get the non-computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
- return ret;
- }
-
- // Otherwise just get the value from the style object
- return style[ name ];
- }
- },
-
- css: function( elem, name, extra ) {
- // Make sure that we're working with the right name
- var ret, origName = jQuery.camelCase( name ),
- hooks = jQuery.cssHooks[ origName ];
-
- name = jQuery.cssProps[ origName ] || origName;
-
- // If a hook was provided get the computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
- return ret;
-
- // Otherwise, if a way to get the computed value exists, use that
- } else if ( curCSS ) {
- return curCSS( elem, name, origName );
- }
- },
-
- // A method for quickly swapping in/out CSS properties to get correct calculations
- swap: function( elem, options, callback ) {
- var old = {};
-
- // Remember the old values, and insert the new ones
- for ( var name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
-
- callback.call( elem );
-
- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
- },
-
- camelCase: function( string ) {
- return string.replace( rdashAlpha, fcamelCase );
- }
-});
-
-// DEPRECATED, Use jQuery.css() instead
-jQuery.curCSS = jQuery.css;
-
-jQuery.each(["height", "width"], function( i, name ) {
- jQuery.cssHooks[ name ] = {
- get: function( elem, computed, extra ) {
- var val;
-
- if ( computed ) {
- if ( elem.offsetWidth !== 0 ) {
- val = getWH( elem, name, extra );
-
- } else {
- jQuery.swap( elem, cssShow, function() {
- val = getWH( elem, name, extra );
- });
- }
-
- if ( val <= 0 ) {
- val = curCSS( elem, name, name );
-
- if ( val === "0px" && currentStyle ) {
- val = currentStyle( elem, name, name );
- }
-
- if ( val != null ) {
- // Should return "auto" instead of 0, use 0 for
- // temporary backwards-compat
- return val === "" || val === "auto" ? "0px" : val;
- }
- }
-
- if ( val < 0 || val == null ) {
- val = elem.style[ name ];
-
- // Should return "auto" instead of 0, use 0 for
- // temporary backwards-compat
- return val === "" || val === "auto" ? "0px" : val;
- }
-
- return typeof val === "string" ? val : val + "px";
- }
- },
-
- set: function( elem, value ) {
- if ( rnumpx.test( value ) ) {
- // ignore negative width and height values #1599
- value = parseFloat(value);
-
- if ( value >= 0 ) {
- return value + "px";
- }
-
- } else {
- return value;
- }
- }
- };
-});
-
-if ( !jQuery.support.opacity ) {
- jQuery.cssHooks.opacity = {
- get: function( elem, computed ) {
- // IE uses filters for opacity
- return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ?
- (parseFloat(RegExp.$1) / 100) + "" :
- computed ? "1" : "";
- },
-
- set: function( elem, value ) {
- var style = elem.style;
-
- // IE has trouble with opacity if it does not have layout
- // Force it by setting the zoom level
- style.zoom = 1;
-
- // Set the alpha filter to set the opacity
- var opacity = jQuery.isNaN(value) ?
- "" :
- "alpha(opacity=" + value * 100 + ")",
- filter = style.filter || "";
-
- style.filter = ralpha.test(filter) ?
- filter.replace(ralpha, opacity) :
- style.filter + ' ' + opacity;
- }
- };
-}
-
-if ( document.defaultView && document.defaultView.getComputedStyle ) {
- getComputedStyle = function( elem, newName, name ) {
- var ret, defaultView, computedStyle;
-
- name = name.replace( rupper, "-$1" ).toLowerCase();
-
- if ( !(defaultView = elem.ownerDocument.defaultView) ) {
- return undefined;
- }
-
- if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
- ret = computedStyle.getPropertyValue( name );
- if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
- ret = jQuery.style( elem, name );
- }
- }
-
- return ret;
- };
-}
-
-if ( document.documentElement.currentStyle ) {
- currentStyle = function( elem, name ) {
- var left,
- ret = elem.currentStyle && elem.currentStyle[ name ],
- rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
- style = elem.style;
-
- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
- // If we're not dealing with a regular pixel number
- // but a number that has a weird ending, we need to convert it to pixels
- if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
- // Remember the original values
- left = style.left;
-
- // Put in the new values to get a computed value out
- if ( rsLeft ) {
- elem.runtimeStyle.left = elem.currentStyle.left;
- }
- style.left = name === "fontSize" ? "1em" : (ret || 0);
- ret = style.pixelLeft + "px";
-
- // Revert the changed values
- style.left = left;
- if ( rsLeft ) {
- elem.runtimeStyle.left = rsLeft;
- }
- }
-
- return ret === "" ? "auto" : ret;
- };
-}
-
-curCSS = getComputedStyle || currentStyle;
-
-function getWH( elem, name, extra ) {
- var which = name === "width" ? cssWidth : cssHeight,
- val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
-
- if ( extra === "border" ) {
- return val;
- }
-
- jQuery.each( which, function() {
- if ( !extra ) {
- val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0;
- }
-
- if ( extra === "margin" ) {
- val += parseFloat(jQuery.css( elem, "margin" + this )) || 0;
-
- } else {
- val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0;
- }
- });
-
- return val;
-}
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.hidden = function( elem ) {
- var width = elem.offsetWidth,
- height = elem.offsetHeight;
-
- return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
- };
-
- jQuery.expr.filters.visible = function( elem ) {
- return !jQuery.expr.filters.hidden( elem );
- };
-}
-
-
-
-
-var r20 = /%20/g,
- rbracket = /\[\]$/,
- rCRLF = /\r?\n/g,
- rhash = /#.*$/,
- rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
- rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
- rnoContent = /^(?:GET|HEAD)$/,
- rprotocol = /^\/\//,
- rquery = /\?/,
- rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
- rselectTextarea = /^(?:select|textarea)/i,
- rspacesAjax = /\s+/,
- rts = /([?&])_=[^&]*/,
- rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/,
-
- // Keep a copy of the old load method
- _load = jQuery.fn.load,
-
- /* Prefilters
- * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
- * 2) These are called:
- * - BEFORE asking for a transport
- * - AFTER param serialization (s.data is a string if s.processData is true)
- * 3) key is the dataType
- * 4) the catchall symbol "*" can be used
- * 5) execution will start with transport dataType and THEN continue down to "*" if needed
- */
- prefilters = {},
-
- /* Transports bindings
- * 1) key is the dataType
- * 2) the catchall symbol "*" can be used
- * 3) selection will start with transport dataType and THEN go to "*" if needed
- */
- transports = {};
-
-// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
-function addToPrefiltersOrTransports( structure ) {
-
- // dataTypeExpression is optional and defaults to "*"
- return function( dataTypeExpression, func ) {
-
- if ( typeof dataTypeExpression !== "string" ) {
- func = dataTypeExpression;
- dataTypeExpression = "*";
- }
-
- if ( jQuery.isFunction( func ) ) {
- var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
- i = 0,
- length = dataTypes.length,
- dataType,
- list,
- placeBefore;
-
- // For each dataType in the dataTypeExpression
- for(; i < length; i++ ) {
- dataType = dataTypes[ i ];
- // We control if we're asked to add before
- // any existing element
- placeBefore = /^\+/.test( dataType );
- if ( placeBefore ) {
- dataType = dataType.substr( 1 ) || "*";
- }
- list = structure[ dataType ] = structure[ dataType ] || [];
- // then we add to the structure accordingly
- list[ placeBefore ? "unshift" : "push" ]( func );
- }
- }
- };
-}
-
-//Base inspection function for prefilters and transports
-function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR,
- dataType /* internal */, inspected /* internal */ ) {
-
- dataType = dataType || options.dataTypes[ 0 ];
- inspected = inspected || {};
-
- inspected[ dataType ] = true;
-
- var list = structure[ dataType ],
- i = 0,
- length = list ? list.length : 0,
- executeOnly = ( structure === prefilters ),
- selection;
-
- for(; i < length && ( executeOnly || !selection ); i++ ) {
- selection = list[ i ]( options, originalOptions, jXHR );
- // If we got redirected to another dataType
- // we try there if not done already
- if ( typeof selection === "string" ) {
- if ( inspected[ selection ] ) {
- selection = undefined;
- } else {
- options.dataTypes.unshift( selection );
- selection = inspectPrefiltersOrTransports(
- structure, options, originalOptions, jXHR, selection, inspected );
- }
- }
- }
- // If we're only executing or nothing was selected
- // we try the catchall dataType if not done already
- if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
- selection = inspectPrefiltersOrTransports(
- structure, options, originalOptions, jXHR, "*", inspected );
- }
- // unnecessary when only executing (prefilters)
- // but it'll be ignored by the caller in that case
- return selection;
-}
-
-jQuery.fn.extend({
- load: function( url, params, callback ) {
- if ( typeof url !== "string" && _load ) {
- return _load.apply( this, arguments );
-
- // Don't do a request if no elements are being requested
- } else if ( !this.length ) {
- return this;
- }
-
- var off = url.indexOf( " " );
- if ( off >= 0 ) {
- var selector = url.slice( off, url.length );
- url = url.slice( 0, off );
- }
-
- // Default to a GET request
- var type = "GET";
-
- // If the second parameter was provided
- if ( params ) {
- // If it's a function
- if ( jQuery.isFunction( params ) ) {
- // We assume that it's the callback
- callback = params;
- params = null;
-
- // Otherwise, build a param string
- } else if ( typeof params === "object" ) {
- params = jQuery.param( params, jQuery.ajaxSettings.traditional );
- type = "POST";
- }
- }
-
- var self = this;
-
- // Request the remote document
- jQuery.ajax({
- url: url,
- type: type,
- dataType: "html",
- data: params,
- // Complete callback (responseText is used internally)
- complete: function( jXHR, status, responseText ) {
- // Store the response as specified by the jXHR object
- responseText = jXHR.responseText;
- // If successful, inject the HTML into all the matched elements
- if ( jXHR.isResolved() ) {
- // #4825: Get the actual response in case
- // a dataFilter is present in ajaxSettings
- jXHR.done(function( r ) {
- responseText = r;
- });
- // See if a selector was specified
- self.html( selector ?
- // Create a dummy div to hold the results
- jQuery("<div>")
- // inject the contents of the document in, removing the scripts
- // to avoid any 'Permission Denied' errors in IE
- .append(responseText.replace(rscript, ""))
-
- // Locate the specified elements
- .find(selector) :
-
- // If not, just inject the full result
- responseText );
- }
-
- if ( callback ) {
- self.each( callback, [ responseText, status, jXHR ] );
- }
- }
- });
-
- return this;
- },
-
- serialize: function() {
- return jQuery.param( this.serializeArray() );
- },
-
- serializeArray: function() {
- return this.map(function(){
- return this.elements ? jQuery.makeArray( this.elements ) : this;
- })
- .filter(function(){
- return this.name && !this.disabled &&
- ( this.checked || rselectTextarea.test( this.nodeName ) ||
- rinput.test( this.type ) );
- })
- .map(function( i, elem ){
- var val = jQuery( this ).val();
-
- return val == null ?
- null :
- jQuery.isArray( val ) ?
- jQuery.map( val, function( val, i ){
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }) :
- { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }).get();
- }
-});
-
-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
- jQuery.fn[ o ] = function( f ){
- return this.bind( o, f );
- };
-} );
-
-jQuery.each( [ "get", "post" ], function( i, method ) {
- jQuery[ method ] = function( url, data, callback, type ) {
- // shift arguments if data argument was omitted
- if ( jQuery.isFunction( data ) ) {
- type = type || callback;
- callback = data;
- data = null;
- }
-
- return jQuery.ajax({
- type: method,
- url: url,
- data: data,
- success: callback,
- dataType: type
- });
- };
-} );
-
-jQuery.extend({
-
- getScript: function( url, callback ) {
- return jQuery.get( url, null, callback, "script" );
- },
-
- getJSON: function( url, data, callback ) {
- return jQuery.get( url, data, callback, "json" );
- },
-
- ajaxSetup: function( settings ) {
- jQuery.extend( true, jQuery.ajaxSettings, settings );
- if ( settings.context ) {
- jQuery.ajaxSettings.context = settings.context;
- }
- },
-
- ajaxSettings: {
- url: location.href,
- global: true,
- type: "GET",
- contentType: "application/x-www-form-urlencoded",
- processData: true,
- async: true,
- /*
- timeout: 0,
- data: null,
- dataType: null,
- username: null,
- password: null,
- cache: null,
- traditional: false,
- headers: {},
- crossDomain: null,
- */
-
- accepts: {
- xml: "application/xml, text/xml",
- html: "text/html",
- text: "text/plain",
- json: "application/json, text/javascript",
- "*": "*/*"
- },
-
- contents: {
- xml: /xml/,
- html: /html/,
- json: /json/
- },
-
- responseFields: {
- xml: "responseXML",
- text: "responseText"
- },
-
- // List of data converters
- // 1) key format is "source_type destination_type" (a single space in-between)
- // 2) the catchall symbol "*" can be used for source_type
- converters: {
-
- // Convert anything to text
- "* text": window.String,
-
- // Text to html (true = no transformation)
- "text html": true,
-
- // Evaluate text as a json expression
- "text json": jQuery.parseJSON,
-
- // Parse text as xml
- "text xml": jQuery.parseXML
- }
- },
-
- ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
- ajaxTransport: addToPrefiltersOrTransports( transports ),
-
- // Main method
- ajax: function( url, options ) {
-
- // If options is not an object,
- // we simulate pre-1.5 signature
- if ( typeof options !== "object" ) {
- options = url;
- url = undefined;
- }
-
- // Force options to be an object
- options = options || {};
-
- var // Create the final options object
- s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ),
- // Callbacks contexts
- // We force the original context if it exists
- // or take it from jQuery.ajaxSettings otherwise
- // (plain objects used as context get extended)
- callbackContext =
- ( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s,
- globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ),
- // Deferreds
- deferred = jQuery.Deferred(),
- completeDeferred = jQuery._Deferred(),
- // Status-dependent callbacks
- statusCode = s.statusCode || {},
- // Headers (they are sent all at once)
- requestHeaders = {},
- // Response headers
- responseHeadersString,
- responseHeaders,
- // transport
- transport,
- // timeout handle
- timeoutTimer,
- // Cross-domain detection vars
- loc = document.location,
- protocol = loc.protocol || "http:",
- parts,
- // The jXHR state
- state = 0,
- // Loop variable
- i,
- // Fake xhr
- jXHR = {
-
- readyState: 0,
-
- // Caches the header
- setRequestHeader: function( name, value ) {
- if ( state === 0 ) {
- requestHeaders[ name.toLowerCase() ] = value;
- }
- return this;
- },
-
- // Raw string
- getAllResponseHeaders: function() {
- return state === 2 ? responseHeadersString : null;
- },
-
- // Builds headers hashtable if needed
- getResponseHeader: function( key ) {
- var match;
- if ( state === 2 ) {
- if ( !responseHeaders ) {
- responseHeaders = {};
- while( ( match = rheaders.exec( responseHeadersString ) ) ) {
- responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
- }
- }
- match = responseHeaders[ key.toLowerCase() ];
- }
- return match || null;
- },
-
- // Cancel the request
- abort: function( statusText ) {
- statusText = statusText || "abort";
- if ( transport ) {
- transport.abort( statusText );
- }
- done( 0, statusText );
- return this;
- }
- };
-
- // Callback for when everything is done
- // It is defined here because jslint complains if it is declared
- // at the end of the function (which would be more logical and readable)
- function done( status, statusText, responses, headers) {
-
- // Called once
- if ( state === 2 ) {
- return;
- }
-
- // State is "done" now
- state = 2;
-
- // Clear timeout if it exists
- if ( timeoutTimer ) {
- clearTimeout( timeoutTimer );
- }
-
- // Dereference transport for early garbage collection
- // (no matter how long the jXHR object will be used)
- transport = undefined;
-
- // Cache response headers
- responseHeadersString = headers || "";
-
- // Set readyState
- jXHR.readyState = status ? 4 : 0;
-
- var isSuccess,
- success,
- error,
- response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined,
- lastModified,
- etag;
-
- // If successful, handle type chaining
- if ( status >= 200 && status < 300 || status === 304 ) {
-
- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
-
- if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) {
- jQuery.lastModified[ s.url ] = lastModified;
- }
- if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) {
- jQuery.etag[ s.url ] = etag;
- }
- }
-
- // If not modified
- if ( status === 304 ) {
-
- statusText = "notmodified";
- isSuccess = true;
-
- // If we have data
- } else {
-
- try {
- success = ajaxConvert( s, response );
- statusText = "success";
- isSuccess = true;
- } catch(e) {
- // We have a parsererror
- statusText = "parsererror";
- error = e;
- }
- }
- } else {
- // We extract error from statusText
- // then normalize statusText and status for non-aborts
- error = statusText;
- if( status ) {
- statusText = "error";
- if ( status < 0 ) {
- status = 0;
- }
- }
- }
-
- // Set data for the fake xhr object
- jXHR.status = status;
- jXHR.statusText = statusText;
-
- // Success/Error
- if ( isSuccess ) {
- deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] );
- } else {
- deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] );
- }
-
- // Status-dependent callbacks
- jXHR.statusCode( statusCode );
- statusCode = undefined;
-
- if ( s.global ) {
- globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
- [ jXHR, s, isSuccess ? success : error ] );
- }
-
- // Complete
- completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] );
-
- if ( s.global ) {
- globalEventContext.trigger( "ajaxComplete", [ jXHR, s] );
- // Handle the global AJAX counter
- if ( !( --jQuery.active ) ) {
- jQuery.event.trigger( "ajaxStop" );
- }
- }
- }
-
- // Attach deferreds
- deferred.promise( jXHR );
- jXHR.success = jXHR.done;
- jXHR.error = jXHR.fail;
- jXHR.complete = completeDeferred.done;
-
- // Status-dependent callbacks
- jXHR.statusCode = function( map ) {
- if ( map ) {
- var tmp;
- if ( state < 2 ) {
- for( tmp in map ) {
- statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
- }
- } else {
- tmp = map[ jXHR.status ];
- jXHR.then( tmp, tmp );
- }
- }
- return this;
- };
-
- // Remove hash character (#7531: and string promotion)
- // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
- // We also use the url parameter if available
- s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" );
-
- // Extract dataTypes list
- s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
-
- // Determine if a cross-domain request is in order
- if ( !s.crossDomain ) {
- parts = rurl.exec( s.url.toLowerCase() );
- s.crossDomain = !!( parts &&
- ( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname ||
- ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
- ( loc.port || ( protocol === "http:" ? 80 : 443 ) ) )
- );
- }
-
- // Convert data if not already a string
- if ( s.data && s.processData && typeof s.data !== "string" ) {
- s.data = jQuery.param( s.data, s.traditional );
- }
-
- // Apply prefilters
- inspectPrefiltersOrTransports( prefilters, s, options, jXHR );
-
- // Uppercase the type
- s.type = s.type.toUpperCase();
-
- // Determine if request has content
- s.hasContent = !rnoContent.test( s.type );
-
- // Watch for a new set of requests
- if ( s.global && jQuery.active++ === 0 ) {
- jQuery.event.trigger( "ajaxStart" );
- }
-
- // More options handling for requests with no content
- if ( !s.hasContent ) {
-
- // If data is available, append data to url
- if ( s.data ) {
- s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
- }
-
- // Add anti-cache in url if needed
- if ( s.cache === false ) {
-
- var ts = jQuery.now(),
- // try replacing _= if it is there
- ret = s.url.replace( rts, "$1_=" + ts );
-
- // if nothing was replaced, add timestamp to the end
- s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
- }
- }
-
- // Set the correct header, if data is being sent
- if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
- requestHeaders[ "content-type" ] = s.contentType;
- }
-
- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
- if ( jQuery.lastModified[ s.url ] ) {
- requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ];
- }
- if ( jQuery.etag[ s.url ] ) {
- requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ];
- }
- }
-
- // Set the Accepts header for the server, depending on the dataType
- requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
- s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
- s.accepts[ "*" ];
-
- // Check for headers option
- for ( i in s.headers ) {
- requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
- }
-
- // Allow custom headers/mimetypes and early abort
- if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) {
- // Abort if not done already
- done( 0, "abort" );
- // Return false
- jXHR = false;
-
- } else {
-
- // Install callbacks on deferreds
- for ( i in { success: 1, error: 1, complete: 1 } ) {
- jXHR[ i ]( s[ i ] );
- }
-
- // Get transport
- transport = inspectPrefiltersOrTransports( transports, s, options, jXHR );
-
- // If no transport, we auto-abort
- if ( !transport ) {
- done( -1, "No Transport" );
- } else {
- // Set state as sending
- state = jXHR.readyState = 1;
- // Send global event
- if ( s.global ) {
- globalEventContext.trigger( "ajaxSend", [ jXHR, s ] );
- }
- // Timeout
- if ( s.async && s.timeout > 0 ) {
- timeoutTimer = setTimeout( function(){
- jXHR.abort( "timeout" );
- }, s.timeout );
- }
-
- try {
- transport.send( requestHeaders, done );
- } catch (e) {
- // Propagate exception as error if not done
- if ( status < 2 ) {
- done( -1, e );
- // Simply rethrow otherwise
- } else {
- jQuery.error( e );
- }
- }
- }
- }
- return jXHR;
- },
-
- // Serialize an array of form elements or a set of
- // key/values into a query string
- param: function( a, traditional ) {
- var s = [],
- add = function( key, value ) {
- // If value is a function, invoke it and return its value
- value = jQuery.isFunction( value ) ? value() : value;
- s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
- };
-
- // Set traditional to true for jQuery <= 1.3.2 behavior.
- if ( traditional === undefined ) {
- traditional = jQuery.ajaxSettings.traditional;
- }
-
- // If an array was passed in, assume that it is an array of form elements.
- if ( jQuery.isArray( a ) || a.jquery ) {
- // Serialize the form elements
- jQuery.each( a, function() {
- add( this.name, this.value );
- } );
-
- } else {
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for ( var prefix in a ) {
- buildParams( prefix, a[ prefix ], traditional, add );
- }
- }
-
- // Return the resulting serialization
- return s.join( "&" ).replace( r20, "+" );
- }
-});
-
-function buildParams( prefix, obj, traditional, add ) {
- if ( jQuery.isArray( obj ) && obj.length ) {
- // Serialize array item.
- jQuery.each( obj, function( i, v ) {
- if ( traditional || rbracket.test( prefix ) ) {
- // Treat each array item as a scalar.
- add( prefix, v );
-
- } else {
- // If array item is non-scalar (array or object), encode its
- // numeric index to resolve deserialization ambiguity issues.
- // Note that rack (as of 1.0.0) can't currently deserialize
- // nested arrays properly, and attempting to do so may cause
- // a server error. Possible fixes are to modify rack's
- // deserialization algorithm or to provide an option or flag
- // to force array serialization to be shallow.
- buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
- }
- });
-
- } else if ( !traditional && obj != null && typeof obj === "object" ) {
- // If we see an array here, it is empty and should be treated as an empty
- // object
- if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) {
- add( prefix, "" );
-
- // Serialize object item.
- } else {
- jQuery.each( obj, function( k, v ) {
- buildParams( prefix + "[" + k + "]", v, traditional, add );
- });
- }
-
- } else {
- // Serialize scalar item.
- add( prefix, obj );
- }
-}
-
-// This is still on the jQuery object... for now
-// Want to move this to jQuery.ajax some day
-jQuery.extend({
-
- // Counter for holding the number of active queries
- active: 0,
-
- // Last-Modified header cache for next request
- lastModified: {},
- etag: {}
-
-});
-
-/* Handles responses to an ajax request:
- * - sets all responseXXX fields accordingly
- * - finds the right dataType (mediates between content-type and expected dataType)
- * - returns the corresponding response
- */
-function ajaxHandleResponses( s, jXHR, responses ) {
-
- var contents = s.contents,
- dataTypes = s.dataTypes,
- responseFields = s.responseFields,
- ct,
- type,
- finalDataType,
- firstDataType;
-
- // Fill responseXXX fields
- for( type in responseFields ) {
- if ( type in responses ) {
- jXHR[ responseFields[type] ] = responses[ type ];
- }
- }
-
- // Remove auto dataType and get content-type in the process
- while( dataTypes[ 0 ] === "*" ) {
- dataTypes.shift();
- if ( ct === undefined ) {
- ct = jXHR.getResponseHeader( "content-type" );
- }
- }
-
- // Check if we're dealing with a known content-type
- if ( ct ) {
- for ( type in contents ) {
- if ( contents[ type ] && contents[ type ].test( ct ) ) {
- dataTypes.unshift( type );
- break;
- }
- }
- }
-
- // Check to see if we have a response for the expected dataType
- if ( dataTypes[ 0 ] in responses ) {
- finalDataType = dataTypes[ 0 ];
- } else {
- // Try convertible dataTypes
- for ( type in responses ) {
- if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
- finalDataType = type;
- break;
- }
- if ( !firstDataType ) {
- firstDataType = type;
- }
- }
- // Or just use first one
- finalDataType = finalDataType || firstDataType;
- }
-
- // If we found a dataType
- // We add the dataType to the list if needed
- // and return the corresponding response
- if ( finalDataType ) {
- if ( finalDataType !== dataTypes[ 0 ] ) {
- dataTypes.unshift( finalDataType );
- }
- return responses[ finalDataType ];
- }
-}
-
-// Chain conversions given the request and the original response
-function ajaxConvert( s, response ) {
-
- // Apply the dataFilter if provided
- if ( s.dataFilter ) {
- response = s.dataFilter( response, s.dataType );
- }
-
- var dataTypes = s.dataTypes,
- converters = s.converters,
- i,
- length = dataTypes.length,
- tmp,
- // Current and previous dataTypes
- current = dataTypes[ 0 ],
- prev,
- // Conversion expression
- conversion,
- // Conversion function
- conv,
- // Conversion functions (transitive conversion)
- conv1,
- conv2;
-
- // For each dataType in the chain
- for( i = 1; i < length; i++ ) {
-
- // Get the dataTypes
- prev = current;
- current = dataTypes[ i ];
-
- // If current is auto dataType, update it to prev
- if( current === "*" ) {
- current = prev;
- // If no auto and dataTypes are actually different
- } else if ( prev !== "*" && prev !== current ) {
-
- // Get the converter
- conversion = prev + " " + current;
- conv = converters[ conversion ] || converters[ "* " + current ];
-
- // If there is no direct converter, search transitively
- if ( !conv ) {
- conv2 = undefined;
- for( conv1 in converters ) {
- tmp = conv1.split( " " );
- if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
- conv2 = converters[ tmp[1] + " " + current ];
- if ( conv2 ) {
- conv1 = converters[ conv1 ];
- if ( conv1 === true ) {
- conv = conv2;
- } else if ( conv2 === true ) {
- conv = conv1;
- }
- break;
- }
- }
- }
- }
- // If we found no converter, dispatch an error
- if ( !( conv || conv2 ) ) {
- jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
- }
- // If found converter is not an equivalence
- if ( conv !== true ) {
- // Convert with 1 or 2 converters accordingly
- response = conv ? conv( response ) : conv2( conv1(response) );
- }
- }
- }
- return response;
-}
-
-
-
-
-var jsc = jQuery.now(),
- jsre = /(\=)\?(&|$)|()\?\?()/i;
-
-// Default jsonp settings
-jQuery.ajaxSetup({
- jsonp: "callback",
- jsonpCallback: function() {
- return jQuery.expando + "_" + ( jsc++ );
- }
-});
-
-// Detect, normalize options and install callbacks for jsonp requests
-jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) {
-
- dataIsString = ( typeof s.data === "string" );
-
- if ( s.dataTypes[ 0 ] === "jsonp" ||
- originalSettings.jsonpCallback ||
- originalSettings.jsonp != null ||
- s.jsonp !== false && ( jsre.test( s.url ) ||
- dataIsString && jsre.test( s.data ) ) ) {
-
- var responseContainer,
- jsonpCallback = s.jsonpCallback =
- jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
- previous = window[ jsonpCallback ],
- url = s.url,
- data = s.data,
- replace = "$1" + jsonpCallback + "$2";
-
- if ( s.jsonp !== false ) {
- url = url.replace( jsre, replace );
- if ( s.url === url ) {
- if ( dataIsString ) {
- data = data.replace( jsre, replace );
- }
- if ( s.data === data ) {
- // Add callback manually
- url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
- }
- }
- }
-
- s.url = url;
- s.data = data;
-
- window[ jsonpCallback ] = function( response ) {
- responseContainer = [ response ];
- };
-
- s.complete = [ function() {
-
- // Set callback back to previous value
- window[ jsonpCallback ] = previous;
-
- // Call if it was a function and we have a response
- if ( previous) {
- if ( responseContainer && jQuery.isFunction( previous ) ) {
- window[ jsonpCallback ] ( responseContainer[ 0 ] );
- }
- } else {
- // else, more memory leak avoidance
- try{
- delete window[ jsonpCallback ];
- } catch( e ) {}
- }
-
- }, s.complete ];
-
- // Use data converter to retrieve json after script execution
- s.converters["script json"] = function() {
- if ( ! responseContainer ) {
- jQuery.error( jsonpCallback + " was not called" );
- }
- return responseContainer[ 0 ];
- };
-
- // force json dataType
- s.dataTypes[ 0 ] = "json";
-
- // Delegate to script
- return "script";
- }
-} );
-
-
-
-
-// Install script dataType
-jQuery.ajaxSetup({
- accepts: {
- script: "text/javascript, application/javascript"
- },
- contents: {
- script: /javascript/
- },
- converters: {
- "text script": function( text ) {
- jQuery.globalEval( text );
- return text;
- }
- }
-});
-
-// Handle cache's special case and global
-jQuery.ajaxPrefilter( "script", function( s ) {
- if ( s.cache === undefined ) {
- s.cache = false;
- }
- if ( s.crossDomain ) {
- s.type = "GET";
- s.global = false;
- }
-} );
-
-// Bind script tag hack transport
-jQuery.ajaxTransport( "script", function(s) {
-
- // This transport only deals with cross domain requests
- if ( s.crossDomain ) {
-
- var script,
- head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement;
-
- return {
-
- send: function( _, callback ) {
-
- script = document.createElement( "script" );
-
- script.async = "async";
-
- if ( s.scriptCharset ) {
- script.charset = s.scriptCharset;
- }
-
- script.src = s.url;
-
- // Attach handlers for all browsers
- script.onload = script.onreadystatechange = function( _, isAbort ) {
-
- if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
-
- // Handle memory leak in IE
- script.onload = script.onreadystatechange = null;
-
- // Remove the script
- if ( head && script.parentNode ) {
- head.removeChild( script );
- }
-
- // Dereference the script
- script = undefined;
-
- // Callback if not abort
- if ( !isAbort ) {
- callback( 200, "success" );
- }
- }
- };
- // Use insertBefore instead of appendChild to circumvent an IE6 bug.
- // This arises when a base node is used (#2709 and #4378).
- head.insertBefore( script, head.firstChild );
- },
-
- abort: function() {
- if ( script ) {
- script.onload( 0, 1 );
- }
- }
- };
- }
-} );
-
-
-
-
-var // Next active xhr id
- xhrId = jQuery.now(),
-
- // active xhrs
- xhrs = {},
-
- // #5280: see below
- xhrUnloadAbortInstalled,
-
- // XHR used to determine supports properties
- testXHR;
-
-// Create the request object
-// (This is still attached to ajaxSettings for backward compatibility)
-jQuery.ajaxSettings.xhr = window.ActiveXObject ?
- /* Microsoft failed to properly
- * implement the XMLHttpRequest in IE7 (can't request local files),
- * so we use the ActiveXObject when it is available
- * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
- * we need a fallback.
- */
- function() {
- if ( window.location.protocol !== "file:" ) {
- try {
- return new window.XMLHttpRequest();
- } catch( xhrError ) {}
- }
-
- try {
- return new window.ActiveXObject("Microsoft.XMLHTTP");
- } catch( activeError ) {}
- } :
- // For all other browsers, use the standard XMLHttpRequest object
- function() {
- return new window.XMLHttpRequest();
- };
-
-// Test if we can create an xhr object
-try {
- testXHR = jQuery.ajaxSettings.xhr();
-} catch( xhrCreationException ) {}
-
-//Does this browser support XHR requests?
-jQuery.support.ajax = !!testXHR;
-
-// Does this browser support crossDomain XHR requests
-jQuery.support.cors = testXHR && ( "withCredentials" in testXHR );
-
-// No need for the temporary xhr anymore
-testXHR = undefined;
-
-// Create transport if the browser can provide an xhr
-if ( jQuery.support.ajax ) {
-
- jQuery.ajaxTransport(function( s ) {
- // Cross domain only allowed if supported through XMLHttpRequest
- if ( !s.crossDomain || jQuery.support.cors ) {
-
- var callback;
-
- return {
- send: function( headers, complete ) {
-
- // #5280: we need to abort on unload or IE will keep connections alive
- if ( !xhrUnloadAbortInstalled ) {
-
- xhrUnloadAbortInstalled = 1;
-
- jQuery(window).bind( "unload", function() {
-
- // Abort all pending requests
- jQuery.each( xhrs, function( _, xhr ) {
- if ( xhr.onreadystatechange ) {
- xhr.onreadystatechange( 1 );
- }
- } );
-
- } );
- }
-
- // Get a new xhr
- var xhr = s.xhr(),
- handle;
-
- // Open the socket
- // Passing null username, generates a login popup on Opera (#2865)
- if ( s.username ) {
- xhr.open( s.type, s.url, s.async, s.username, s.password );
- } else {
- xhr.open( s.type, s.url, s.async );
- }
-
- // Requested-With header
- // Not set for crossDomain requests with no content
- // (see why at http://trac.dojotoolkit.org/ticket/9486)
- // Won't change header if already provided
- if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) {
- headers[ "x-requested-with" ] = "XMLHttpRequest";
- }
-
- // Need an extra try/catch for cross domain requests in Firefox 3
- try {
- jQuery.each( headers, function( key, value ) {
- xhr.setRequestHeader( key, value );
- } );
- } catch( _ ) {}
-
- // Do send the request
- // This may raise an exception which is actually
- // handled in jQuery.ajax (so no try/catch here)
- xhr.send( ( s.hasContent && s.data ) || null );
-
- // Listener
- callback = function( _, isAbort ) {
-
- // Was never called and is aborted or complete
- if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
-
- // Only called once
- callback = 0;
-
- // Do not keep as active anymore
- if ( handle ) {
- xhr.onreadystatechange = jQuery.noop;
- delete xhrs[ handle ];
- }
-
- // If it's an abort
- if ( isAbort ) {
- // Abort it manually if needed
- if ( xhr.readyState !== 4 ) {
- xhr.abort();
- }
- } else {
- // Get info
- var status = xhr.status,
- statusText,
- responseHeaders = xhr.getAllResponseHeaders(),
- responses = {},
- xml = xhr.responseXML;
-
- // Construct response list
- if ( xml && xml.documentElement /* #4958 */ ) {
- responses.xml = xml;
- }
- responses.text = xhr.responseText;
-
- // Firefox throws an exception when accessing
- // statusText for faulty cross-domain requests
- try {
- statusText = xhr.statusText;
- } catch( e ) {
- // We normalize with Webkit giving an empty statusText
- statusText = "";
- }
-
- // Filter status for non standard behaviours
- status =
- // Opera returns 0 when it should be 304
- // Webkit returns 0 for failing cross-domain no matter the real status
- status === 0 ?
- (
- // Webkit, Firefox: filter out faulty cross-domain requests
- !s.crossDomain || statusText ?
- (
- // Opera: filter out real aborts #6060
- responseHeaders ?
- 304 :
- 0
- ) :
- // We assume 302 but could be anything cross-domain related
- 302
- ) :
- (
- // IE sometimes returns 1223 when it should be 204 (see #1450)
- status == 1223 ?
- 204 :
- status
- );
-
- // Call complete
- complete( status, statusText, responses, responseHeaders );
- }
- }
- };
-
- // if we're in sync mode or it's in cache
- // and has been retrieved directly (IE6 & IE7)
- // we need to manually fire the callback
- if ( !s.async || xhr.readyState === 4 ) {
- callback();
- } else {
- // Add to list of active xhrs
- handle = xhrId++;
- xhrs[ handle ] = xhr;
- xhr.onreadystatechange = callback;
- }
- },
-
- abort: function() {
- if ( callback ) {
- callback(0,1);
- }
- }
- };
- }
- });
-}
-
-
-
-
-var elemdisplay = {},
- rfxtypes = /^(?:toggle|show|hide)$/,
- rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
- timerId,
- fxAttrs = [
- // height animations
- [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
- // width animations
- [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
- // opacity animations
- [ "opacity" ]
- ];
-
-jQuery.fn.extend({
- show: function( speed, easing, callback ) {
- var elem, display;
-
- if ( speed || speed === 0 ) {
- return this.animate( genFx("show", 3), speed, easing, callback);
-
- } else {
- for ( var i = 0, j = this.length; i < j; i++ ) {
- elem = this[i];
- display = elem.style.display;
-
- // Reset the inline display of this element to learn if it is
- // being hidden by cascaded rules or not
- if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
- display = elem.style.display = "";
- }
-
- // Set elements which have been overridden with display: none
- // in a stylesheet to whatever the default browser style is
- // for such an element
- if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
- jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
- }
- }
-
- // Set the display of most of the elements in a second loop
- // to avoid the constant reflow
- for ( i = 0; i < j; i++ ) {
- elem = this[i];
- display = elem.style.display;
-
- if ( display === "" || display === "none" ) {
- elem.style.display = jQuery._data(elem, "olddisplay") || "";
- }
- }
-
- return this;
- }
- },
-
- hide: function( speed, easing, callback ) {
- if ( speed || speed === 0 ) {
- return this.animate( genFx("hide", 3), speed, easing, callback);
-
- } else {
- for ( var i = 0, j = this.length; i < j; i++ ) {
- var display = jQuery.css( this[i], "display" );
-
- if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
- jQuery._data( this[i], "olddisplay", display );
- }
- }
-
- // Set the display of the elements in a second loop
- // to avoid the constant reflow
- for ( i = 0; i < j; i++ ) {
- this[i].style.display = "none";
- }
-
- return this;
- }
- },
-
- // Save the old toggle function
- _toggle: jQuery.fn.toggle,
-
- toggle: function( fn, fn2, callback ) {
- var bool = typeof fn === "boolean";
-
- if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
- this._toggle.apply( this, arguments );
-
- } else if ( fn == null || bool ) {
- this.each(function() {
- var state = bool ? fn : jQuery(this).is(":hidden");
- jQuery(this)[ state ? "show" : "hide" ]();
- });
-
- } else {
- this.animate(genFx("toggle", 3), fn, fn2, callback);
- }
-
- return this;
- },
-
- fadeTo: function( speed, to, easing, callback ) {
- return this.filter(":hidden").css("opacity", 0).show().end()
- .animate({opacity: to}, speed, easing, callback);
- },
-
- animate: function( prop, speed, easing, callback ) {
- var optall = jQuery.speed(speed, easing, callback);
-
- if ( jQuery.isEmptyObject( prop ) ) {
- return this.each( optall.complete );
- }
-
- return this[ optall.queue === false ? "each" : "queue" ](function() {
- // XXX 'this' does not always have a nodeName when running the
- // test suite
-
- var opt = jQuery.extend({}, optall), p,
- isElement = this.nodeType === 1,
- hidden = isElement && jQuery(this).is(":hidden"),
- self = this;
-
- for ( p in prop ) {
- var name = jQuery.camelCase( p );
-
- if ( p !== name ) {
- prop[ name ] = prop[ p ];
- delete prop[ p ];
- p = name;
- }
-
- if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
- return opt.complete.call(this);
- }
-
- if ( isElement && ( p === "height" || p === "width" ) ) {
- // Make sure that nothing sneaks out
- // Record all 3 overflow attributes because IE does not
- // change the overflow attribute when overflowX and
- // overflowY are set to the same value
- opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
-
- // Set display property to inline-block for height/width
- // animations on inline elements that are having width/height
- // animated
- if ( jQuery.css( this, "display" ) === "inline" &&
- jQuery.css( this, "float" ) === "none" ) {
- if ( !jQuery.support.inlineBlockNeedsLayout ) {
- this.style.display = "inline-block";
-
- } else {
- var display = defaultDisplay(this.nodeName);
-
- // inline-level elements accept inline-block;
- // block-level elements need to be inline with layout
- if ( display === "inline" ) {
- this.style.display = "inline-block";
-
- } else {
- this.style.display = "inline";
- this.style.zoom = 1;
- }
- }
- }
- }
-
- if ( jQuery.isArray( prop[p] ) ) {
- // Create (if needed) and add to specialEasing
- (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
- prop[p] = prop[p][0];
- }
- }
-
- if ( opt.overflow != null ) {
- this.style.overflow = "hidden";
- }
-
- opt.curAnim = jQuery.extend({}, prop);
-
- jQuery.each( prop, function( name, val ) {
- var e = new jQuery.fx( self, opt, name );
-
- if ( rfxtypes.test(val) ) {
- e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
-
- } else {
- var parts = rfxnum.exec(val),
- start = e.cur() || 0;
-
- if ( parts ) {
- var end = parseFloat( parts[2] ),
- unit = parts[3] || "px";
-
- // We need to compute starting value
- if ( unit !== "px" ) {
- jQuery.style( self, name, (end || 1) + unit);
- start = ((end || 1) / e.cur()) * start;
- jQuery.style( self, name, start + unit);
- }
-
- // If a +=/-= token was provided, we're doing a relative animation
- if ( parts[1] ) {
- end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
- }
-
- e.custom( start, end, unit );
-
- } else {
- e.custom( start, val, "" );
- }
- }
- });
-
- // For JS strict compliance
- return true;
- });
- },
-
- stop: function( clearQueue, gotoEnd ) {
- var timers = jQuery.timers;
-
- if ( clearQueue ) {
- this.queue([]);
- }
-
- this.each(function() {
- // go in reverse order so anything added to the queue during the loop is ignored
- for ( var i = timers.length - 1; i >= 0; i-- ) {
- if ( timers[i].elem === this ) {
- if (gotoEnd) {
- // force the next step to be the last
- timers[i](true);
- }
-
- timers.splice(i, 1);
- }
- }
- });
-
- // start the next in the queue if the last step wasn't forced
- if ( !gotoEnd ) {
- this.dequeue();
- }
-
- return this;
- }
-
-});
-
-function genFx( type, num ) {
- var obj = {};
-
- jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
- obj[ this ] = type;
- });
-
- return obj;
-}
-
-// Generate shortcuts for custom animations
-jQuery.each({
- slideDown: genFx("show", 1),
- slideUp: genFx("hide", 1),
- slideToggle: genFx("toggle", 1),
- fadeIn: { opacity: "show" },
- fadeOut: { opacity: "hide" },
- fadeToggle: { opacity: "toggle" }
-}, function( name, props ) {
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return this.animate( props, speed, easing, callback );
- };
-});
-
-jQuery.extend({
- speed: function( speed, easing, fn ) {
- var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
- complete: fn || !fn && easing ||
- jQuery.isFunction( speed ) && speed,
- duration: speed,
- easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
- };
-
- opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
- opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
-
- // Queueing
- opt.old = opt.complete;
- opt.complete = function() {
- if ( opt.queue !== false ) {
- jQuery(this).dequeue();
- }
- if ( jQuery.isFunction( opt.old ) ) {
- opt.old.call( this );
- }
- };
-
- return opt;
- },
-
- easing: {
- linear: function( p, n, firstNum, diff ) {
- return firstNum + diff * p;
- },
- swing: function( p, n, firstNum, diff ) {
- return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
- }
- },
-
- timers: [],
-
- fx: function( elem, options, prop ) {
- this.options = options;
- this.elem = elem;
- this.prop = prop;
-
- if ( !options.orig ) {
- options.orig = {};
- }
- }
-
-});
-
-jQuery.fx.prototype = {
- // Simple function for setting a style value
- update: function() {
- if ( this.options.step ) {
- this.options.step.call( this.elem, this.now, this );
- }
-
- (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
- },
-
- // Get the current size
- cur: function() {
- if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
- return this.elem[ this.prop ];
- }
-
- var r = parseFloat( jQuery.css( this.elem, this.prop ) );
- return r || 0;
- },
-
- // Start an animation from one number to another
- custom: function( from, to, unit ) {
- var self = this,
- fx = jQuery.fx;
-
- this.startTime = jQuery.now();
- this.start = from;
- this.end = to;
- this.unit = unit || this.unit || "px";
- this.now = this.start;
- this.pos = this.state = 0;
-
- function t( gotoEnd ) {
- return self.step(gotoEnd);
- }
-
- t.elem = this.elem;
-
- if ( t() && jQuery.timers.push(t) && !timerId ) {
- timerId = setInterval(fx.tick, fx.interval);
- }
- },
-
- // Simple 'show' function
- show: function() {
- // Remember where we started, so that we can go back to it later
- this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
- this.options.show = true;
-
- // Begin the animation
- // Make sure that we start at a small width/height to avoid any
- // flash of content
- this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
-
- // Start by showing the element
- jQuery( this.elem ).show();
- },
-
- // Simple 'hide' function
- hide: function() {
- // Remember where we started, so that we can go back to it later
- this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
- this.options.hide = true;
-
- // Begin the animation
- this.custom(this.cur(), 0);
- },
-
- // Each step of an animation
- step: function( gotoEnd ) {
- var t = jQuery.now(), done = true;
-
- if ( gotoEnd || t >= this.options.duration + this.startTime ) {
- this.now = this.end;
- this.pos = this.state = 1;
- this.update();
-
- this.options.curAnim[ this.prop ] = true;
-
- for ( var i in this.options.curAnim ) {
- if ( this.options.curAnim[i] !== true ) {
- done = false;
- }
- }
-
- if ( done ) {
- // Reset the overflow
- if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
- var elem = this.elem,
- options = this.options;
-
- jQuery.each( [ "", "X", "Y" ], function (index, value) {
- elem.style[ "overflow" + value ] = options.overflow[index];
- } );
- }
-
- // Hide the element if the "hide" operation was done
- if ( this.options.hide ) {
- jQuery(this.elem).hide();
- }
-
- // Reset the properties, if the item has been hidden or shown
- if ( this.options.hide || this.options.show ) {
- for ( var p in this.options.curAnim ) {
- jQuery.style( this.elem, p, this.options.orig[p] );
- }
- }
-
- // Execute the complete function
- this.options.complete.call( this.elem );
- }
-
- return false;
-
- } else {
- var n = t - this.startTime;
- this.state = n / this.options.duration;
-
- // Perform the easing function, defaults to swing
- var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
- var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
- this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
- this.now = this.start + ((this.end - this.start) * this.pos);
-
- // Perform the next step of the animation
- this.update();
- }
-
- return true;
- }
-};
-
-jQuery.extend( jQuery.fx, {
- tick: function() {
- var timers = jQuery.timers;
-
- for ( var i = 0; i < timers.length; i++ ) {
- if ( !timers[i]() ) {
- timers.splice(i--, 1);
- }
- }
-
- if ( !timers.length ) {
- jQuery.fx.stop();
- }
- },
-
- interval: 13,
-
- stop: function() {
- clearInterval( timerId );
- timerId = null;
- },
-
- speeds: {
- slow: 600,
- fast: 200,
- // Default speed
- _default: 400
- },
-
- step: {
- opacity: function( fx ) {
- jQuery.style( fx.elem, "opacity", fx.now );
- },
-
- _default: function( fx ) {
- if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
- fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
- } else {
- fx.elem[ fx.prop ] = fx.now;
- }
- }
- }
-});
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.animated = function( elem ) {
- return jQuery.grep(jQuery.timers, function( fn ) {
- return elem === fn.elem;
- }).length;
- };
-}
-
-function defaultDisplay( nodeName ) {
- if ( !elemdisplay[ nodeName ] ) {
- var elem = jQuery("<" + nodeName + ">").appendTo("body"),
- display = elem.css("display");
-
- elem.remove();
-
- if ( display === "none" || display === "" ) {
- display = "block";
- }
-
- elemdisplay[ nodeName ] = display;
- }
-
- return elemdisplay[ nodeName ];
-}
-
-
-
-
-var rtable = /^t(?:able|d|h)$/i,
- rroot = /^(?:body|html)$/i;
-
-if ( "getBoundingClientRect" in document.documentElement ) {
- jQuery.fn.offset = function( options ) {
- var elem = this[0], box;
-
- if ( options ) {
- return this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- if ( !elem || !elem.ownerDocument ) {
- return null;
- }
-
- if ( elem === elem.ownerDocument.body ) {
- return jQuery.offset.bodyOffset( elem );
- }
-
- try {
- box = elem.getBoundingClientRect();
- } catch(e) {}
-
- var doc = elem.ownerDocument,
- docElem = doc.documentElement;
-
- // Make sure we're not dealing with a disconnected DOM node
- if ( !box || !jQuery.contains( docElem, elem ) ) {
- return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
- }
-
- var body = doc.body,
- win = getWindow(doc),
- clientTop = docElem.clientTop || body.clientTop || 0,
- clientLeft = docElem.clientLeft || body.clientLeft || 0,
- scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ),
- scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
- top = box.top + scrollTop - clientTop,
- left = box.left + scrollLeft - clientLeft;
-
- return { top: top, left: left };
- };
-
-} else {
- jQuery.fn.offset = function( options ) {
- var elem = this[0];
-
- if ( options ) {
- return this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- if ( !elem || !elem.ownerDocument ) {
- return null;
- }
-
- if ( elem === elem.ownerDocument.body ) {
- return jQuery.offset.bodyOffset( elem );
- }
-
- jQuery.offset.initialize();
-
- var computedStyle,
- offsetParent = elem.offsetParent,
- prevOffsetParent = elem,
- doc = elem.ownerDocument,
- docElem = doc.documentElement,
- body = doc.body,
- defaultView = doc.defaultView,
- prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
- top = elem.offsetTop,
- left = elem.offsetLeft;
-
- while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
- if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
- break;
- }
-
- computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
- top -= elem.scrollTop;
- left -= elem.scrollLeft;
-
- if ( elem === offsetParent ) {
- top += elem.offsetTop;
- left += elem.offsetLeft;
-
- if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevOffsetParent = offsetParent;
- offsetParent = elem.offsetParent;
- }
-
- if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevComputedStyle = computedStyle;
- }
-
- if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
- top += body.offsetTop;
- left += body.offsetLeft;
- }
-
- if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
- top += Math.max( docElem.scrollTop, body.scrollTop );
- left += Math.max( docElem.scrollLeft, body.scrollLeft );
- }
-
- return { top: top, left: left };
- };
-}
-
-jQuery.offset = {
- initialize: function() {
- var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
- html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
-
- jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
-
- container.innerHTML = html;
- body.insertBefore( container, body.firstChild );
- innerDiv = container.firstChild;
- checkDiv = innerDiv.firstChild;
- td = innerDiv.nextSibling.firstChild.firstChild;
-
- this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
- this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
-
- checkDiv.style.position = "fixed";
- checkDiv.style.top = "20px";
-
- // safari subtracts parent border width here which is 5px
- this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
- checkDiv.style.position = checkDiv.style.top = "";
-
- innerDiv.style.overflow = "hidden";
- innerDiv.style.position = "relative";
-
- this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
-
- this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
-
- body.removeChild( container );
- body = container = innerDiv = checkDiv = table = td = null;
- jQuery.offset.initialize = jQuery.noop;
- },
-
- bodyOffset: function( body ) {
- var top = body.offsetTop,
- left = body.offsetLeft;
-
- jQuery.offset.initialize();
-
- if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
- top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
- left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
- }
-
- return { top: top, left: left };
- },
-
- setOffset: function( elem, options, i ) {
- var position = jQuery.css( elem, "position" );
-
- // set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
-
- var curElem = jQuery( elem ),
- curOffset = curElem.offset(),
- curCSSTop = jQuery.css( elem, "top" ),
- curCSSLeft = jQuery.css( elem, "left" ),
- calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1),
- props = {}, curPosition = {}, curTop, curLeft;
-
- // need to be able to calculate position if either top or left is auto and position is absolute
- if ( calculatePosition ) {
- curPosition = curElem.position();
- }
-
- curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0;
- curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0;
-
- if ( jQuery.isFunction( options ) ) {
- options = options.call( elem, i, curOffset );
- }
-
- if (options.top != null) {
- props.top = (options.top - curOffset.top) + curTop;
- }
- if (options.left != null) {
- props.left = (options.left - curOffset.left) + curLeft;
- }
-
- if ( "using" in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- }
-};
-
-
-jQuery.fn.extend({
- position: function() {
- if ( !this[0] ) {
- return null;
- }
-
- var elem = this[0],
-
- // Get *real* offsetParent
- offsetParent = this.offsetParent(),
-
- // Get correct offsets
- offset = this.offset(),
- parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
-
- // Subtract element margins
- // note: when an element has margin: auto the offsetLeft and marginLeft
- // are the same in Safari causing offset.left to incorrectly be 0
- offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
- offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
-
- // Add offsetParent borders
- parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
- parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
-
- // Subtract the two offsets
- return {
- top: offset.top - parentOffset.top,
- left: offset.left - parentOffset.left
- };
- },
-
- offsetParent: function() {
- return this.map(function() {
- var offsetParent = this.offsetParent || document.body;
- while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
- offsetParent = offsetParent.offsetParent;
- }
- return offsetParent;
- });
- }
-});
-
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( ["Left", "Top"], function( i, name ) {
- var method = "scroll" + name;
-
- jQuery.fn[ method ] = function(val) {
- var elem = this[0], win;
-
- if ( !elem ) {
- return null;
- }
-
- if ( val !== undefined ) {
- // Set the scroll offset
- return this.each(function() {
- win = getWindow( this );
-
- if ( win ) {
- win.scrollTo(
- !i ? val : jQuery(win).scrollLeft(),
- i ? val : jQuery(win).scrollTop()
- );
-
- } else {
- this[ method ] = val;
- }
- });
- } else {
- win = getWindow( elem );
-
- // Return the scroll offset
- return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
- jQuery.support.boxModel && win.document.documentElement[ method ] ||
- win.document.body[ method ] :
- elem[ method ];
- }
- };
-});
-
-function getWindow( elem ) {
- return jQuery.isWindow( elem ) ?
- elem :
- elem.nodeType === 9 ?
- elem.defaultView || elem.parentWindow :
- false;
-}
-
-
-
-
-// Create innerHeight, innerWidth, outerHeight and outerWidth methods
-jQuery.each([ "Height", "Width" ], function( i, name ) {
-
- var type = name.toLowerCase();
-
- // innerHeight and innerWidth
- jQuery.fn["inner" + name] = function() {
- return this[0] ?
- parseFloat( jQuery.css( this[0], type, "padding" ) ) :
- null;
- };
-
- // outerHeight and outerWidth
- jQuery.fn["outer" + name] = function( margin ) {
- return this[0] ?
- parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) :
- null;
- };
-
- jQuery.fn[ type ] = function( size ) {
- // Get window width or height
- var elem = this[0];
- if ( !elem ) {
- return size == null ? null : this;
- }
-
- if ( jQuery.isFunction( size ) ) {
- return this.each(function( i ) {
- var self = jQuery( this );
- self[ type ]( size.call( this, i, self[ type ]() ) );
- });
- }
-
- if ( jQuery.isWindow( elem ) ) {
- // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
- // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
- var docElemProp = elem.document.documentElement[ "client" + name ];
- return elem.document.compatMode === "CSS1Compat" && docElemProp ||
- elem.document.body[ "client" + name ] || docElemProp;
-
- // Get document width or height
- } else if ( elem.nodeType === 9 ) {
- // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
- return Math.max(
- elem.documentElement["client" + name],
- elem.body["scroll" + name], elem.documentElement["scroll" + name],
- elem.body["offset" + name], elem.documentElement["offset" + name]
- );
-
- // Get or set width or height on the element
- } else if ( size === undefined ) {
- var orig = jQuery.css( elem, type ),
- ret = parseFloat( orig );
-
- return jQuery.isNaN( ret ) ? orig : ret;
-
- // Set the width or height on the element (default to pixels if value is unitless)
- } else {
- return this.css( type, typeof size === "string" ? size : size + "px" );
- }
- };
-
-});
-
-
-})(window);
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
deleted file mode 100644
index 4dcb3779a2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/**
- * Unobtrusive scripting adapter for jQuery
- *
- * Requires jQuery 1.4.3 or later.
- * https://github.com/rails/jquery-ujs
- */
-
-(function($) {
- // Triggers an event on an element and returns the event result
- function fire(obj, name, data) {
- var event = new $.Event(name);
- obj.trigger(event, data);
- return event.result !== false;
- }
-
- // Submits "remote" forms and links with ajax
- function handleRemote(element) {
- var method, url, data,
- dataType = element.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType);
-
- if (element.is('form')) {
- method = element.attr('method');
- url = element.attr('action');
- data = element.serializeArray();
- // memoized value from clicked submit button
- var button = element.data('ujs:submit-button');
- if (button) {
- data.push(button);
- element.data('ujs:submit-button', null);
- }
- } else {
- method = element.attr('data-method');
- url = element.attr('href');
- data = null;
- }
-
- $.ajax({
- url: url, type: method || 'GET', data: data, dataType: dataType,
- // stopping the "ajax:beforeSend" event will cancel the ajax request
- beforeSend: function(xhr, settings) {
- if (settings.dataType === undefined) {
- xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
- }
- return fire(element, 'ajax:beforeSend', [xhr, settings]);
- },
- success: function(data, status, xhr) {
- element.trigger('ajax:success', [data, status, xhr]);
- },
- complete: function(xhr, status) {
- element.trigger('ajax:complete', [xhr, status]);
- },
- error: function(xhr, status, error) {
- element.trigger('ajax:error', [xhr, status, error]);
- }
- });
- }
-
- // Handles "data-method" on links such as:
- // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
- function handleMethod(link) {
- var href = link.attr('href'),
- method = link.attr('data-method'),
- csrf_token = $('meta[name=csrf-token]').attr('content'),
- csrf_param = $('meta[name=csrf-param]').attr('content'),
- form = $('<form method="post" action="' + href + '"></form>'),
- metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
-
- if (csrf_param !== undefined && csrf_token !== undefined) {
- metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
- }
-
- form.hide().append(metadata_input).appendTo('body');
- form.submit();
- }
-
- function disableFormElements(form) {
- form.find('input[data-disable-with]').each(function() {
- var input = $(this);
- input.data('ujs:enable-with', input.val())
- .val(input.attr('data-disable-with'))
- .attr('disabled', 'disabled');
- });
- }
-
- function enableFormElements(form) {
- form.find('input[data-disable-with]').each(function() {
- var input = $(this);
- input.val(input.data('ujs:enable-with')).removeAttr('disabled');
- });
- }
-
- function allowAction(element) {
- var message = element.attr('data-confirm');
- return !message || (fire(element, 'confirm') && confirm(message));
- }
-
- function requiredValuesMissing(form) {
- var missing = false;
- form.find('input[name][required]').each(function() {
- if (!$(this).val()) missing = true;
- });
- return missing;
- }
-
- $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) {
- var link = $(this);
- if (!allowAction(link)) return false;
-
- if (link.attr('data-remote') != undefined) {
- handleRemote(link);
- return false;
- } else if (link.attr('data-method')) {
- handleMethod(link);
- return false;
- }
- });
-
- $('form').live('submit.rails', function(e) {
- var form = $(this), remote = form.attr('data-remote') != undefined;
- if (!allowAction(form)) return false;
-
- // skip other logic when required values are missing
- if (requiredValuesMissing(form)) return !remote;
-
- if (remote) {
- handleRemote(form);
- return false;
- } else {
- disableFormElements(form);
- }
- });
-
- $('form input[type=submit], form button[type=submit], form button:not([type])').live('click.rails', function() {
- var button = $(this);
- if (!allowAction(button)) return false;
- // register the pressed submit button
- var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null;
- button.closest('form').data('ujs:submit-button', data);
- });
-
- $('form').live('ajax:beforeSend.rails', function(event) {
- if (this == event.target) disableFormElements($(this));
- });
-
- $('form').live('ajax:complete.rails', function(event) {
- if (this == event.target) enableFormElements($(this));
- });
-})( jQuery );
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js
deleted file mode 100644
index 474b2231bb..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js
+++ /dev/null
@@ -1,6082 +0,0 @@
-/* Prototype JavaScript framework, version 1.7
- * (c) 2005-2010 Sam Stephenson
- *
- * Prototype is freely distributable under the terms of an MIT-style license.
- * For details, see the Prototype web site: http://www.prototypejs.org/
- *
- *--------------------------------------------------------------------------*/
-
-var Prototype = {
-
- Version: '1.7',
-
- Browser: (function(){
- var ua = navigator.userAgent;
- var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
- return {
- IE: !!window.attachEvent && !isOpera,
- Opera: isOpera,
- WebKit: ua.indexOf('AppleWebKit/') > -1,
- Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
- MobileSafari: /Apple.*Mobile/.test(ua)
- }
- })(),
-
- BrowserFeatures: {
- XPath: !!document.evaluate,
-
- SelectorsAPI: !!document.querySelector,
-
- ElementExtensions: (function() {
- var constructor = window.Element || window.HTMLElement;
- return !!(constructor && constructor.prototype);
- })(),
- SpecificElementExtensions: (function() {
- if (typeof window.HTMLDivElement !== 'undefined')
- return true;
-
- var div = document.createElement('div'),
- form = document.createElement('form'),
- isSupported = false;
-
- if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
- isSupported = true;
- }
-
- div = form = null;
-
- return isSupported;
- })()
- },
-
- ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
- JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
-
- emptyFunction: function() { },
-
- K: function(x) { return x }
-};
-
-if (Prototype.Browser.MobileSafari)
- Prototype.BrowserFeatures.SpecificElementExtensions = false;
-
-
-var Abstract = { };
-
-
-var Try = {
- these: function() {
- var returnValue;
-
- for (var i = 0, length = arguments.length; i < length; i++) {
- var lambda = arguments[i];
- try {
- returnValue = lambda();
- break;
- } catch (e) { }
- }
-
- return returnValue;
- }
-};
-
-/* Based on Alex Arnell's inheritance implementation. */
-
-var Class = (function() {
-
- var IS_DONTENUM_BUGGY = (function(){
- for (var p in { toString: 1 }) {
- if (p === 'toString') return false;
- }
- return true;
- })();
-
- function subclass() {};
- function create() {
- var parent = null, properties = $A(arguments);
- if (Object.isFunction(properties[0]))
- parent = properties.shift();
-
- function klass() {
- this.initialize.apply(this, arguments);
- }
-
- Object.extend(klass, Class.Methods);
- klass.superclass = parent;
- klass.subclasses = [];
-
- if (parent) {
- subclass.prototype = parent.prototype;
- klass.prototype = new subclass;
- parent.subclasses.push(klass);
- }
-
- for (var i = 0, length = properties.length; i < length; i++)
- klass.addMethods(properties[i]);
-
- if (!klass.prototype.initialize)
- klass.prototype.initialize = Prototype.emptyFunction;
-
- klass.prototype.constructor = klass;
- return klass;
- }
-
- function addMethods(source) {
- var ancestor = this.superclass && this.superclass.prototype,
- properties = Object.keys(source);
-
- if (IS_DONTENUM_BUGGY) {
- if (source.toString != Object.prototype.toString)
- properties.push("toString");
- if (source.valueOf != Object.prototype.valueOf)
- properties.push("valueOf");
- }
-
- for (var i = 0, length = properties.length; i < length; i++) {
- var property = properties[i], value = source[property];
- if (ancestor && Object.isFunction(value) &&
- value.argumentNames()[0] == "$super") {
- var method = value;
- value = (function(m) {
- return function() { return ancestor[m].apply(this, arguments); };
- })(property).wrap(method);
-
- value.valueOf = method.valueOf.bind(method);
- value.toString = method.toString.bind(method);
- }
- this.prototype[property] = value;
- }
-
- return this;
- }
-
- return {
- create: create,
- Methods: {
- addMethods: addMethods
- }
- };
-})();
-(function() {
-
- var _toString = Object.prototype.toString,
- NULL_TYPE = 'Null',
- UNDEFINED_TYPE = 'Undefined',
- BOOLEAN_TYPE = 'Boolean',
- NUMBER_TYPE = 'Number',
- STRING_TYPE = 'String',
- OBJECT_TYPE = 'Object',
- FUNCTION_CLASS = '[object Function]',
- BOOLEAN_CLASS = '[object Boolean]',
- NUMBER_CLASS = '[object Number]',
- STRING_CLASS = '[object String]',
- ARRAY_CLASS = '[object Array]',
- DATE_CLASS = '[object Date]',
- NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
- typeof JSON.stringify === 'function' &&
- JSON.stringify(0) === '0' &&
- typeof JSON.stringify(Prototype.K) === 'undefined';
-
- function Type(o) {
- switch(o) {
- case null: return NULL_TYPE;
- case (void 0): return UNDEFINED_TYPE;
- }
- var type = typeof o;
- switch(type) {
- case 'boolean': return BOOLEAN_TYPE;
- case 'number': return NUMBER_TYPE;
- case 'string': return STRING_TYPE;
- }
- return OBJECT_TYPE;
- }
-
- function extend(destination, source) {
- for (var property in source)
- destination[property] = source[property];
- return destination;
- }
-
- function inspect(object) {
- try {
- if (isUndefined(object)) return 'undefined';
- if (object === null) return 'null';
- return object.inspect ? object.inspect() : String(object);
- } catch (e) {
- if (e instanceof RangeError) return '...';
- throw e;
- }
- }
-
- function toJSON(value) {
- return Str('', { '': value }, []);
- }
-
- function Str(key, holder, stack) {
- var value = holder[key],
- type = typeof value;
-
- if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
-
- var _class = _toString.call(value);
-
- switch (_class) {
- case NUMBER_CLASS:
- case BOOLEAN_CLASS:
- case STRING_CLASS:
- value = value.valueOf();
- }
-
- switch (value) {
- case null: return 'null';
- case true: return 'true';
- case false: return 'false';
- }
-
- type = typeof value;
- switch (type) {
- case 'string':
- return value.inspect(true);
- case 'number':
- return isFinite(value) ? String(value) : 'null';
- case 'object':
-
- for (var i = 0, length = stack.length; i < length; i++) {
- if (stack[i] === value) { throw new TypeError(); }
- }
- stack.push(value);
-
- var partial = [];
- if (_class === ARRAY_CLASS) {
- for (var i = 0, length = value.length; i < length; i++) {
- var str = Str(i, value, stack);
- partial.push(typeof str === 'undefined' ? 'null' : str);
- }
- partial = '[' + partial.join(',') + ']';
- } else {
- var keys = Object.keys(value);
- for (var i = 0, length = keys.length; i < length; i++) {
- var key = keys[i], str = Str(key, value, stack);
- if (typeof str !== "undefined") {
- partial.push(key.inspect(true)+ ':' + str);
- }
- }
- partial = '{' + partial.join(',') + '}';
- }
- stack.pop();
- return partial;
- }
- }
-
- function stringify(object) {
- return JSON.stringify(object);
- }
-
- function toQueryString(object) {
- return $H(object).toQueryString();
- }
-
- function toHTML(object) {
- return object && object.toHTML ? object.toHTML() : String.interpret(object);
- }
-
- function keys(object) {
- if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
- var results = [];
- for (var property in object) {
- if (object.hasOwnProperty(property)) {
- results.push(property);
- }
- }
- return results;
- }
-
- function values(object) {
- var results = [];
- for (var property in object)
- results.push(object[property]);
- return results;
- }
-
- function clone(object) {
- return extend({ }, object);
- }
-
- function isElement(object) {
- return !!(object && object.nodeType == 1);
- }
-
- function isArray(object) {
- return _toString.call(object) === ARRAY_CLASS;
- }
-
- var hasNativeIsArray = (typeof Array.isArray == 'function')
- && Array.isArray([]) && !Array.isArray({});
-
- if (hasNativeIsArray) {
- isArray = Array.isArray;
- }
-
- function isHash(object) {
- return object instanceof Hash;
- }
-
- function isFunction(object) {
- return _toString.call(object) === FUNCTION_CLASS;
- }
-
- function isString(object) {
- return _toString.call(object) === STRING_CLASS;
- }
-
- function isNumber(object) {
- return _toString.call(object) === NUMBER_CLASS;
- }
-
- function isDate(object) {
- return _toString.call(object) === DATE_CLASS;
- }
-
- function isUndefined(object) {
- return typeof object === "undefined";
- }
-
- extend(Object, {
- extend: extend,
- inspect: inspect,
- toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
- toQueryString: toQueryString,
- toHTML: toHTML,
- keys: Object.keys || keys,
- values: values,
- clone: clone,
- isElement: isElement,
- isArray: isArray,
- isHash: isHash,
- isFunction: isFunction,
- isString: isString,
- isNumber: isNumber,
- isDate: isDate,
- isUndefined: isUndefined
- });
-})();
-Object.extend(Function.prototype, (function() {
- var slice = Array.prototype.slice;
-
- function update(array, args) {
- var arrayLength = array.length, length = args.length;
- while (length--) array[arrayLength + length] = args[length];
- return array;
- }
-
- function merge(array, args) {
- array = slice.call(array, 0);
- return update(array, args);
- }
-
- function argumentNames() {
- var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
- .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
- .replace(/\s+/g, '').split(',');
- return names.length == 1 && !names[0] ? [] : names;
- }
-
- function bind(context) {
- if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
- var __method = this, args = slice.call(arguments, 1);
- return function() {
- var a = merge(args, arguments);
- return __method.apply(context, a);
- }
- }
-
- function bindAsEventListener(context) {
- var __method = this, args = slice.call(arguments, 1);
- return function(event) {
- var a = update([event || window.event], args);
- return __method.apply(context, a);
- }
- }
-
- function curry() {
- if (!arguments.length) return this;
- var __method = this, args = slice.call(arguments, 0);
- return function() {
- var a = merge(args, arguments);
- return __method.apply(this, a);
- }
- }
-
- function delay(timeout) {
- var __method = this, args = slice.call(arguments, 1);
- timeout = timeout * 1000;
- return window.setTimeout(function() {
- return __method.apply(__method, args);
- }, timeout);
- }
-
- function defer() {
- var args = update([0.01], arguments);
- return this.delay.apply(this, args);
- }
-
- function wrap(wrapper) {
- var __method = this;
- return function() {
- var a = update([__method.bind(this)], arguments);
- return wrapper.apply(this, a);
- }
- }
-
- function methodize() {
- if (this._methodized) return this._methodized;
- var __method = this;
- return this._methodized = function() {
- var a = update([this], arguments);
- return __method.apply(null, a);
- };
- }
-
- return {
- argumentNames: argumentNames,
- bind: bind,
- bindAsEventListener: bindAsEventListener,
- curry: curry,
- delay: delay,
- defer: defer,
- wrap: wrap,
- methodize: methodize
- }
-})());
-
-
-
-(function(proto) {
-
-
- function toISOString() {
- return this.getUTCFullYear() + '-' +
- (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
- this.getUTCDate().toPaddedString(2) + 'T' +
- this.getUTCHours().toPaddedString(2) + ':' +
- this.getUTCMinutes().toPaddedString(2) + ':' +
- this.getUTCSeconds().toPaddedString(2) + 'Z';
- }
-
-
- function toJSON() {
- return this.toISOString();
- }
-
- if (!proto.toISOString) proto.toISOString = toISOString;
- if (!proto.toJSON) proto.toJSON = toJSON;
-
-})(Date.prototype);
-
-
-RegExp.prototype.match = RegExp.prototype.test;
-
-RegExp.escape = function(str) {
- return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
-};
-var PeriodicalExecuter = Class.create({
- initialize: function(callback, frequency) {
- this.callback = callback;
- this.frequency = frequency;
- this.currentlyExecuting = false;
-
- this.registerCallback();
- },
-
- registerCallback: function() {
- this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
- },
-
- execute: function() {
- this.callback(this);
- },
-
- stop: function() {
- if (!this.timer) return;
- clearInterval(this.timer);
- this.timer = null;
- },
-
- onTimerEvent: function() {
- if (!this.currentlyExecuting) {
- try {
- this.currentlyExecuting = true;
- this.execute();
- this.currentlyExecuting = false;
- } catch(e) {
- this.currentlyExecuting = false;
- throw e;
- }
- }
- }
-});
-Object.extend(String, {
- interpret: function(value) {
- return value == null ? '' : String(value);
- },
- specialChar: {
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '\\': '\\\\'
- }
-});
-
-Object.extend(String.prototype, (function() {
- var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
- typeof JSON.parse === 'function' &&
- JSON.parse('{"test": true}').test;
-
- function prepareReplacement(replacement) {
- if (Object.isFunction(replacement)) return replacement;
- var template = new Template(replacement);
- return function(match) { return template.evaluate(match) };
- }
-
- function gsub(pattern, replacement) {
- var result = '', source = this, match;
- replacement = prepareReplacement(replacement);
-
- if (Object.isString(pattern))
- pattern = RegExp.escape(pattern);
-
- if (!(pattern.length || pattern.source)) {
- replacement = replacement('');
- return replacement + source.split('').join(replacement) + replacement;
- }
-
- while (source.length > 0) {
- if (match = source.match(pattern)) {
- result += source.slice(0, match.index);
- result += String.interpret(replacement(match));
- source = source.slice(match.index + match[0].length);
- } else {
- result += source, source = '';
- }
- }
- return result;
- }
-
- function sub(pattern, replacement, count) {
- replacement = prepareReplacement(replacement);
- count = Object.isUndefined(count) ? 1 : count;
-
- return this.gsub(pattern, function(match) {
- if (--count < 0) return match[0];
- return replacement(match);
- });
- }
-
- function scan(pattern, iterator) {
- this.gsub(pattern, iterator);
- return String(this);
- }
-
- function truncate(length, truncation) {
- length = length || 30;
- truncation = Object.isUndefined(truncation) ? '...' : truncation;
- return this.length > length ?
- this.slice(0, length - truncation.length) + truncation : String(this);
- }
-
- function strip() {
- return this.replace(/^\s+/, '').replace(/\s+$/, '');
- }
-
- function stripTags() {
- return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
- }
-
- function stripScripts() {
- return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
- }
-
- function extractScripts() {
- var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
- matchOne = new RegExp(Prototype.ScriptFragment, 'im');
- return (this.match(matchAll) || []).map(function(scriptTag) {
- return (scriptTag.match(matchOne) || ['', ''])[1];
- });
- }
-
- function evalScripts() {
- return this.extractScripts().map(function(script) { return eval(script) });
- }
-
- function escapeHTML() {
- return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
- }
-
- function unescapeHTML() {
- return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
- }
-
-
- function toQueryParams(separator) {
- var match = this.strip().match(/([^?#]*)(#.*)?$/);
- if (!match) return { };
-
- return match[1].split(separator || '&').inject({ }, function(hash, pair) {
- if ((pair = pair.split('='))[0]) {
- var key = decodeURIComponent(pair.shift()),
- value = pair.length > 1 ? pair.join('=') : pair[0];
-
- if (value != undefined) value = decodeURIComponent(value);
-
- if (key in hash) {
- if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
- hash[key].push(value);
- }
- else hash[key] = value;
- }
- return hash;
- });
- }
-
- function toArray() {
- return this.split('');
- }
-
- function succ() {
- return this.slice(0, this.length - 1) +
- String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
- }
-
- function times(count) {
- return count < 1 ? '' : new Array(count + 1).join(this);
- }
-
- function camelize() {
- return this.replace(/-+(.)?/g, function(match, chr) {
- return chr ? chr.toUpperCase() : '';
- });
- }
-
- function capitalize() {
- return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
- }
-
- function underscore() {
- return this.replace(/::/g, '/')
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
- .replace(/([a-z\d])([A-Z])/g, '$1_$2')
- .replace(/-/g, '_')
- .toLowerCase();
- }
-
- function dasherize() {
- return this.replace(/_/g, '-');
- }
-
- function inspect(useDoubleQuotes) {
- var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
- if (character in String.specialChar) {
- return String.specialChar[character];
- }
- return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
- });
- if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
- return "'" + escapedString.replace(/'/g, '\\\'') + "'";
- }
-
- function unfilterJSON(filter) {
- return this.replace(filter || Prototype.JSONFilter, '$1');
- }
-
- function isJSON() {
- var str = this;
- if (str.blank()) return false;
- str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
- str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
- str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
- return (/^[\],:{}\s]*$/).test(str);
- }
-
- function evalJSON(sanitize) {
- var json = this.unfilterJSON(),
- cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
- if (cx.test(json)) {
- json = json.replace(cx, function (a) {
- return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- });
- }
- try {
- if (!sanitize || json.isJSON()) return eval('(' + json + ')');
- } catch (e) { }
- throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
- }
-
- function parseJSON() {
- var json = this.unfilterJSON();
- return JSON.parse(json);
- }
-
- function include(pattern) {
- return this.indexOf(pattern) > -1;
- }
-
- function startsWith(pattern) {
- return this.lastIndexOf(pattern, 0) === 0;
- }
-
- function endsWith(pattern) {
- var d = this.length - pattern.length;
- return d >= 0 && this.indexOf(pattern, d) === d;
- }
-
- function empty() {
- return this == '';
- }
-
- function blank() {
- return /^\s*$/.test(this);
- }
-
- function interpolate(object, pattern) {
- return new Template(this, pattern).evaluate(object);
- }
-
- return {
- gsub: gsub,
- sub: sub,
- scan: scan,
- truncate: truncate,
- strip: String.prototype.trim || strip,
- stripTags: stripTags,
- stripScripts: stripScripts,
- extractScripts: extractScripts,
- evalScripts: evalScripts,
- escapeHTML: escapeHTML,
- unescapeHTML: unescapeHTML,
- toQueryParams: toQueryParams,
- parseQuery: toQueryParams,
- toArray: toArray,
- succ: succ,
- times: times,
- camelize: camelize,
- capitalize: capitalize,
- underscore: underscore,
- dasherize: dasherize,
- inspect: inspect,
- unfilterJSON: unfilterJSON,
- isJSON: isJSON,
- evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
- include: include,
- startsWith: startsWith,
- endsWith: endsWith,
- empty: empty,
- blank: blank,
- interpolate: interpolate
- };
-})());
-
-var Template = Class.create({
- initialize: function(template, pattern) {
- this.template = template.toString();
- this.pattern = pattern || Template.Pattern;
- },
-
- evaluate: function(object) {
- if (object && Object.isFunction(object.toTemplateReplacements))
- object = object.toTemplateReplacements();
-
- return this.template.gsub(this.pattern, function(match) {
- if (object == null) return (match[1] + '');
-
- var before = match[1] || '';
- if (before == '\\') return match[2];
-
- var ctx = object, expr = match[3],
- pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
-
- match = pattern.exec(expr);
- if (match == null) return before;
-
- while (match != null) {
- var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
- ctx = ctx[comp];
- if (null == ctx || '' == match[3]) break;
- expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
- match = pattern.exec(expr);
- }
-
- return before + String.interpret(ctx);
- });
- }
-});
-Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
-
-var $break = { };
-
-var Enumerable = (function() {
- function each(iterator, context) {
- var index = 0;
- try {
- this._each(function(value) {
- iterator.call(context, value, index++);
- });
- } catch (e) {
- if (e != $break) throw e;
- }
- return this;
- }
-
- function eachSlice(number, iterator, context) {
- var index = -number, slices = [], array = this.toArray();
- if (number < 1) return array;
- while ((index += number) < array.length)
- slices.push(array.slice(index, index+number));
- return slices.collect(iterator, context);
- }
-
- function all(iterator, context) {
- iterator = iterator || Prototype.K;
- var result = true;
- this.each(function(value, index) {
- result = result && !!iterator.call(context, value, index);
- if (!result) throw $break;
- });
- return result;
- }
-
- function any(iterator, context) {
- iterator = iterator || Prototype.K;
- var result = false;
- this.each(function(value, index) {
- if (result = !!iterator.call(context, value, index))
- throw $break;
- });
- return result;
- }
-
- function collect(iterator, context) {
- iterator = iterator || Prototype.K;
- var results = [];
- this.each(function(value, index) {
- results.push(iterator.call(context, value, index));
- });
- return results;
- }
-
- function detect(iterator, context) {
- var result;
- this.each(function(value, index) {
- if (iterator.call(context, value, index)) {
- result = value;
- throw $break;
- }
- });
- return result;
- }
-
- function findAll(iterator, context) {
- var results = [];
- this.each(function(value, index) {
- if (iterator.call(context, value, index))
- results.push(value);
- });
- return results;
- }
-
- function grep(filter, iterator, context) {
- iterator = iterator || Prototype.K;
- var results = [];
-
- if (Object.isString(filter))
- filter = new RegExp(RegExp.escape(filter));
-
- this.each(function(value, index) {
- if (filter.match(value))
- results.push(iterator.call(context, value, index));
- });
- return results;
- }
-
- function include(object) {
- if (Object.isFunction(this.indexOf))
- if (this.indexOf(object) != -1) return true;
-
- var found = false;
- this.each(function(value) {
- if (value == object) {
- found = true;
- throw $break;
- }
- });
- return found;
- }
-
- function inGroupsOf(number, fillWith) {
- fillWith = Object.isUndefined(fillWith) ? null : fillWith;
- return this.eachSlice(number, function(slice) {
- while(slice.length < number) slice.push(fillWith);
- return slice;
- });
- }
-
- function inject(memo, iterator, context) {
- this.each(function(value, index) {
- memo = iterator.call(context, memo, value, index);
- });
- return memo;
- }
-
- function invoke(method) {
- var args = $A(arguments).slice(1);
- return this.map(function(value) {
- return value[method].apply(value, args);
- });
- }
-
- function max(iterator, context) {
- iterator = iterator || Prototype.K;
- var result;
- this.each(function(value, index) {
- value = iterator.call(context, value, index);
- if (result == null || value >= result)
- result = value;
- });
- return result;
- }
-
- function min(iterator, context) {
- iterator = iterator || Prototype.K;
- var result;
- this.each(function(value, index) {
- value = iterator.call(context, value, index);
- if (result == null || value < result)
- result = value;
- });
- return result;
- }
-
- function partition(iterator, context) {
- iterator = iterator || Prototype.K;
- var trues = [], falses = [];
- this.each(function(value, index) {
- (iterator.call(context, value, index) ?
- trues : falses).push(value);
- });
- return [trues, falses];
- }
-
- function pluck(property) {
- var results = [];
- this.each(function(value) {
- results.push(value[property]);
- });
- return results;
- }
-
- function reject(iterator, context) {
- var results = [];
- this.each(function(value, index) {
- if (!iterator.call(context, value, index))
- results.push(value);
- });
- return results;
- }
-
- function sortBy(iterator, context) {
- return this.map(function(value, index) {
- return {
- value: value,
- criteria: iterator.call(context, value, index)
- };
- }).sort(function(left, right) {
- var a = left.criteria, b = right.criteria;
- return a < b ? -1 : a > b ? 1 : 0;
- }).pluck('value');
- }
-
- function toArray() {
- return this.map();
- }
-
- function zip() {
- var iterator = Prototype.K, args = $A(arguments);
- if (Object.isFunction(args.last()))
- iterator = args.pop();
-
- var collections = [this].concat(args).map($A);
- return this.map(function(value, index) {
- return iterator(collections.pluck(index));
- });
- }
-
- function size() {
- return this.toArray().length;
- }
-
- function inspect() {
- return '#<Enumerable:' + this.toArray().inspect() + '>';
- }
-
-
-
-
-
-
-
-
-
- return {
- each: each,
- eachSlice: eachSlice,
- all: all,
- every: all,
- any: any,
- some: any,
- collect: collect,
- map: collect,
- detect: detect,
- findAll: findAll,
- select: findAll,
- filter: findAll,
- grep: grep,
- include: include,
- member: include,
- inGroupsOf: inGroupsOf,
- inject: inject,
- invoke: invoke,
- max: max,
- min: min,
- partition: partition,
- pluck: pluck,
- reject: reject,
- sortBy: sortBy,
- toArray: toArray,
- entries: toArray,
- zip: zip,
- size: size,
- inspect: inspect,
- find: detect
- };
-})();
-
-function $A(iterable) {
- if (!iterable) return [];
- if ('toArray' in Object(iterable)) return iterable.toArray();
- var length = iterable.length || 0, results = new Array(length);
- while (length--) results[length] = iterable[length];
- return results;
-}
-
-
-function $w(string) {
- if (!Object.isString(string)) return [];
- string = string.strip();
- return string ? string.split(/\s+/) : [];
-}
-
-Array.from = $A;
-
-
-(function() {
- var arrayProto = Array.prototype,
- slice = arrayProto.slice,
- _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
-
- function each(iterator, context) {
- for (var i = 0, length = this.length >>> 0; i < length; i++) {
- if (i in this) iterator.call(context, this[i], i, this);
- }
- }
- if (!_each) _each = each;
-
- function clear() {
- this.length = 0;
- return this;
- }
-
- function first() {
- return this[0];
- }
-
- function last() {
- return this[this.length - 1];
- }
-
- function compact() {
- return this.select(function(value) {
- return value != null;
- });
- }
-
- function flatten() {
- return this.inject([], function(array, value) {
- if (Object.isArray(value))
- return array.concat(value.flatten());
- array.push(value);
- return array;
- });
- }
-
- function without() {
- var values = slice.call(arguments, 0);
- return this.select(function(value) {
- return !values.include(value);
- });
- }
-
- function reverse(inline) {
- return (inline === false ? this.toArray() : this)._reverse();
- }
-
- function uniq(sorted) {
- return this.inject([], function(array, value, index) {
- if (0 == index || (sorted ? array.last() != value : !array.include(value)))
- array.push(value);
- return array;
- });
- }
-
- function intersect(array) {
- return this.uniq().findAll(function(item) {
- return array.detect(function(value) { return item === value });
- });
- }
-
-
- function clone() {
- return slice.call(this, 0);
- }
-
- function size() {
- return this.length;
- }
-
- function inspect() {
- return '[' + this.map(Object.inspect).join(', ') + ']';
- }
-
- function indexOf(item, i) {
- i || (i = 0);
- var length = this.length;
- if (i < 0) i = length + i;
- for (; i < length; i++)
- if (this[i] === item) return i;
- return -1;
- }
-
- function lastIndexOf(item, i) {
- i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
- var n = this.slice(0, i).reverse().indexOf(item);
- return (n < 0) ? n : i - n - 1;
- }
-
- function concat() {
- var array = slice.call(this, 0), item;
- for (var i = 0, length = arguments.length; i < length; i++) {
- item = arguments[i];
- if (Object.isArray(item) && !('callee' in item)) {
- for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
- array.push(item[j]);
- } else {
- array.push(item);
- }
- }
- return array;
- }
-
- Object.extend(arrayProto, Enumerable);
-
- if (!arrayProto._reverse)
- arrayProto._reverse = arrayProto.reverse;
-
- Object.extend(arrayProto, {
- _each: _each,
- clear: clear,
- first: first,
- last: last,
- compact: compact,
- flatten: flatten,
- without: without,
- reverse: reverse,
- uniq: uniq,
- intersect: intersect,
- clone: clone,
- toArray: clone,
- size: size,
- inspect: inspect
- });
-
- var CONCAT_ARGUMENTS_BUGGY = (function() {
- return [].concat(arguments)[0][0] !== 1;
- })(1,2)
-
- if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
-
- if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
- if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
-})();
-function $H(object) {
- return new Hash(object);
-};
-
-var Hash = Class.create(Enumerable, (function() {
- function initialize(object) {
- this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
- }
-
-
- function _each(iterator) {
- for (var key in this._object) {
- var value = this._object[key], pair = [key, value];
- pair.key = key;
- pair.value = value;
- iterator(pair);
- }
- }
-
- function set(key, value) {
- return this._object[key] = value;
- }
-
- function get(key) {
- if (this._object[key] !== Object.prototype[key])
- return this._object[key];
- }
-
- function unset(key) {
- var value = this._object[key];
- delete this._object[key];
- return value;
- }
-
- function toObject() {
- return Object.clone(this._object);
- }
-
-
-
- function keys() {
- return this.pluck('key');
- }
-
- function values() {
- return this.pluck('value');
- }
-
- function index(value) {
- var match = this.detect(function(pair) {
- return pair.value === value;
- });
- return match && match.key;
- }
-
- function merge(object) {
- return this.clone().update(object);
- }
-
- function update(object) {
- return new Hash(object).inject(this, function(result, pair) {
- result.set(pair.key, pair.value);
- return result;
- });
- }
-
- function toQueryPair(key, value) {
- if (Object.isUndefined(value)) return key;
- return key + '=' + encodeURIComponent(String.interpret(value));
- }
-
- function toQueryString() {
- return this.inject([], function(results, pair) {
- var key = encodeURIComponent(pair.key), values = pair.value;
-
- if (values && typeof values == 'object') {
- if (Object.isArray(values)) {
- var queryValues = [];
- for (var i = 0, len = values.length, value; i < len; i++) {
- value = values[i];
- queryValues.push(toQueryPair(key, value));
- }
- return results.concat(queryValues);
- }
- } else results.push(toQueryPair(key, values));
- return results;
- }).join('&');
- }
-
- function inspect() {
- return '#<Hash:{' + this.map(function(pair) {
- return pair.map(Object.inspect).join(': ');
- }).join(', ') + '}>';
- }
-
- function clone() {
- return new Hash(this);
- }
-
- return {
- initialize: initialize,
- _each: _each,
- set: set,
- get: get,
- unset: unset,
- toObject: toObject,
- toTemplateReplacements: toObject,
- keys: keys,
- values: values,
- index: index,
- merge: merge,
- update: update,
- toQueryString: toQueryString,
- inspect: inspect,
- toJSON: toObject,
- clone: clone
- };
-})());
-
-Hash.from = $H;
-Object.extend(Number.prototype, (function() {
- function toColorPart() {
- return this.toPaddedString(2, 16);
- }
-
- function succ() {
- return this + 1;
- }
-
- function times(iterator, context) {
- $R(0, this, true).each(iterator, context);
- return this;
- }
-
- function toPaddedString(length, radix) {
- var string = this.toString(radix || 10);
- return '0'.times(length - string.length) + string;
- }
-
- function abs() {
- return Math.abs(this);
- }
-
- function round() {
- return Math.round(this);
- }
-
- function ceil() {
- return Math.ceil(this);
- }
-
- function floor() {
- return Math.floor(this);
- }
-
- return {
- toColorPart: toColorPart,
- succ: succ,
- times: times,
- toPaddedString: toPaddedString,
- abs: abs,
- round: round,
- ceil: ceil,
- floor: floor
- };
-})());
-
-function $R(start, end, exclusive) {
- return new ObjectRange(start, end, exclusive);
-}
-
-var ObjectRange = Class.create(Enumerable, (function() {
- function initialize(start, end, exclusive) {
- this.start = start;
- this.end = end;
- this.exclusive = exclusive;
- }
-
- function _each(iterator) {
- var value = this.start;
- while (this.include(value)) {
- iterator(value);
- value = value.succ();
- }
- }
-
- function include(value) {
- if (value < this.start)
- return false;
- if (this.exclusive)
- return value < this.end;
- return value <= this.end;
- }
-
- return {
- initialize: initialize,
- _each: _each,
- include: include
- };
-})());
-
-
-
-var Ajax = {
- getTransport: function() {
- return Try.these(
- function() {return new XMLHttpRequest()},
- function() {return new ActiveXObject('Msxml2.XMLHTTP')},
- function() {return new ActiveXObject('Microsoft.XMLHTTP')}
- ) || false;
- },
-
- activeRequestCount: 0
-};
-
-Ajax.Responders = {
- responders: [],
-
- _each: function(iterator) {
- this.responders._each(iterator);
- },
-
- register: function(responder) {
- if (!this.include(responder))
- this.responders.push(responder);
- },
-
- unregister: function(responder) {
- this.responders = this.responders.without(responder);
- },
-
- dispatch: function(callback, request, transport, json) {
- this.each(function(responder) {
- if (Object.isFunction(responder[callback])) {
- try {
- responder[callback].apply(responder, [request, transport, json]);
- } catch (e) { }
- }
- });
- }
-};
-
-Object.extend(Ajax.Responders, Enumerable);
-
-Ajax.Responders.register({
- onCreate: function() { Ajax.activeRequestCount++ },
- onComplete: function() { Ajax.activeRequestCount-- }
-});
-Ajax.Base = Class.create({
- initialize: function(options) {
- this.options = {
- method: 'post',
- asynchronous: true,
- contentType: 'application/x-www-form-urlencoded',
- encoding: 'UTF-8',
- parameters: '',
- evalJSON: true,
- evalJS: true
- };
- Object.extend(this.options, options || { });
-
- this.options.method = this.options.method.toLowerCase();
-
- if (Object.isHash(this.options.parameters))
- this.options.parameters = this.options.parameters.toObject();
- }
-});
-Ajax.Request = Class.create(Ajax.Base, {
- _complete: false,
-
- initialize: function($super, url, options) {
- $super(options);
- this.transport = Ajax.getTransport();
- this.request(url);
- },
-
- request: function(url) {
- this.url = url;
- this.method = this.options.method;
- var params = Object.isString(this.options.parameters) ?
- this.options.parameters :
- Object.toQueryString(this.options.parameters);
-
- if (!['get', 'post'].include(this.method)) {
- params += (params ? '&' : '') + "_method=" + this.method;
- this.method = 'post';
- }
-
- if (params && this.method === 'get') {
- this.url += (this.url.include('?') ? '&' : '?') + params;
- }
-
- this.parameters = params.toQueryParams();
-
- try {
- var response = new Ajax.Response(this);
- if (this.options.onCreate) this.options.onCreate(response);
- Ajax.Responders.dispatch('onCreate', this, response);
-
- this.transport.open(this.method.toUpperCase(), this.url,
- this.options.asynchronous);
-
- if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
-
- this.transport.onreadystatechange = this.onStateChange.bind(this);
- this.setRequestHeaders();
-
- this.body = this.method == 'post' ? (this.options.postBody || params) : null;
- this.transport.send(this.body);
-
- /* Force Firefox to handle ready state 4 for synchronous requests */
- if (!this.options.asynchronous && this.transport.overrideMimeType)
- this.onStateChange();
-
- }
- catch (e) {
- this.dispatchException(e);
- }
- },
-
- onStateChange: function() {
- var readyState = this.transport.readyState;
- if (readyState > 1 && !((readyState == 4) && this._complete))
- this.respondToReadyState(this.transport.readyState);
- },
-
- setRequestHeaders: function() {
- var headers = {
- 'X-Requested-With': 'XMLHttpRequest',
- 'X-Prototype-Version': Prototype.Version,
- 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
- };
-
- if (this.method == 'post') {
- headers['Content-type'] = this.options.contentType +
- (this.options.encoding ? '; charset=' + this.options.encoding : '');
-
- /* Force "Connection: close" for older Mozilla browsers to work
- * around a bug where XMLHttpRequest sends an incorrect
- * Content-length header. See Mozilla Bugzilla #246651.
- */
- if (this.transport.overrideMimeType &&
- (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
- headers['Connection'] = 'close';
- }
-
- if (typeof this.options.requestHeaders == 'object') {
- var extras = this.options.requestHeaders;
-
- if (Object.isFunction(extras.push))
- for (var i = 0, length = extras.length; i < length; i += 2)
- headers[extras[i]] = extras[i+1];
- else
- $H(extras).each(function(pair) { headers[pair.key] = pair.value });
- }
-
- for (var name in headers)
- this.transport.setRequestHeader(name, headers[name]);
- },
-
- success: function() {
- var status = this.getStatus();
- return !status || (status >= 200 && status < 300) || status == 304;
- },
-
- getStatus: function() {
- try {
- if (this.transport.status === 1223) return 204;
- return this.transport.status || 0;
- } catch (e) { return 0 }
- },
-
- respondToReadyState: function(readyState) {
- var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
-
- if (state == 'Complete') {
- try {
- this._complete = true;
- (this.options['on' + response.status]
- || this.options['on' + (this.success() ? 'Success' : 'Failure')]
- || Prototype.emptyFunction)(response, response.headerJSON);
- } catch (e) {
- this.dispatchException(e);
- }
-
- var contentType = response.getHeader('Content-type');
- if (this.options.evalJS == 'force'
- || (this.options.evalJS && this.isSameOrigin() && contentType
- && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
- this.evalResponse();
- }
-
- try {
- (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
- Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
- } catch (e) {
- this.dispatchException(e);
- }
-
- if (state == 'Complete') {
- this.transport.onreadystatechange = Prototype.emptyFunction;
- }
- },
-
- isSameOrigin: function() {
- var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
- return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
- protocol: location.protocol,
- domain: document.domain,
- port: location.port ? ':' + location.port : ''
- }));
- },
-
- getHeader: function(name) {
- try {
- return this.transport.getResponseHeader(name) || null;
- } catch (e) { return null; }
- },
-
- evalResponse: function() {
- try {
- return eval((this.transport.responseText || '').unfilterJSON());
- } catch (e) {
- this.dispatchException(e);
- }
- },
-
- dispatchException: function(exception) {
- (this.options.onException || Prototype.emptyFunction)(this, exception);
- Ajax.Responders.dispatch('onException', this, exception);
- }
-});
-
-Ajax.Request.Events =
- ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
-
-
-
-
-
-
-
-
-Ajax.Response = Class.create({
- initialize: function(request){
- this.request = request;
- var transport = this.transport = request.transport,
- readyState = this.readyState = transport.readyState;
-
- if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
- this.status = this.getStatus();
- this.statusText = this.getStatusText();
- this.responseText = String.interpret(transport.responseText);
- this.headerJSON = this._getHeaderJSON();
- }
-
- if (readyState == 4) {
- var xml = transport.responseXML;
- this.responseXML = Object.isUndefined(xml) ? null : xml;
- this.responseJSON = this._getResponseJSON();
- }
- },
-
- status: 0,
-
- statusText: '',
-
- getStatus: Ajax.Request.prototype.getStatus,
-
- getStatusText: function() {
- try {
- return this.transport.statusText || '';
- } catch (e) { return '' }
- },
-
- getHeader: Ajax.Request.prototype.getHeader,
-
- getAllHeaders: function() {
- try {
- return this.getAllResponseHeaders();
- } catch (e) { return null }
- },
-
- getResponseHeader: function(name) {
- return this.transport.getResponseHeader(name);
- },
-
- getAllResponseHeaders: function() {
- return this.transport.getAllResponseHeaders();
- },
-
- _getHeaderJSON: function() {
- var json = this.getHeader('X-JSON');
- if (!json) return null;
- json = decodeURIComponent(escape(json));
- try {
- return json.evalJSON(this.request.options.sanitizeJSON ||
- !this.request.isSameOrigin());
- } catch (e) {
- this.request.dispatchException(e);
- }
- },
-
- _getResponseJSON: function() {
- var options = this.request.options;
- if (!options.evalJSON || (options.evalJSON != 'force' &&
- !(this.getHeader('Content-type') || '').include('application/json')) ||
- this.responseText.blank())
- return null;
- try {
- return this.responseText.evalJSON(options.sanitizeJSON ||
- !this.request.isSameOrigin());
- } catch (e) {
- this.request.dispatchException(e);
- }
- }
-});
-
-Ajax.Updater = Class.create(Ajax.Request, {
- initialize: function($super, container, url, options) {
- this.container = {
- success: (container.success || container),
- failure: (container.failure || (container.success ? null : container))
- };
-
- options = Object.clone(options);
- var onComplete = options.onComplete;
- options.onComplete = (function(response, json) {
- this.updateContent(response.responseText);
- if (Object.isFunction(onComplete)) onComplete(response, json);
- }).bind(this);
-
- $super(url, options);
- },
-
- updateContent: function(responseText) {
- var receiver = this.container[this.success() ? 'success' : 'failure'],
- options = this.options;
-
- if (!options.evalScripts) responseText = responseText.stripScripts();
-
- if (receiver = $(receiver)) {
- if (options.insertion) {
- if (Object.isString(options.insertion)) {
- var insertion = { }; insertion[options.insertion] = responseText;
- receiver.insert(insertion);
- }
- else options.insertion(receiver, responseText);
- }
- else receiver.update(responseText);
- }
- }
-});
-
-Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
- initialize: function($super, container, url, options) {
- $super(options);
- this.onComplete = this.options.onComplete;
-
- this.frequency = (this.options.frequency || 2);
- this.decay = (this.options.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.options.onComplete = undefined;
- clearTimeout(this.timer);
- (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
- },
-
- updateComplete: function(response) {
- if (this.options.decay) {
- this.decay = (response.responseText == this.lastText ?
- this.decay * this.options.decay : 1);
-
- this.lastText = response.responseText;
- }
- this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
- },
-
- onTimerEvent: function() {
- this.updater = new Ajax.Updater(this.container, this.url, this.options);
- }
-});
-
-
-function $(element) {
- if (arguments.length > 1) {
- for (var i = 0, elements = [], length = arguments.length; i < length; i++)
- elements.push($(arguments[i]));
- return elements;
- }
- if (Object.isString(element))
- element = document.getElementById(element);
- return Element.extend(element);
-}
-
-if (Prototype.BrowserFeatures.XPath) {
- document._getElementsByXPath = function(expression, parentElement) {
- var results = [];
- var query = document.evaluate(expression, $(parentElement) || document,
- null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
- for (var i = 0, length = query.snapshotLength; i < length; i++)
- results.push(Element.extend(query.snapshotItem(i)));
- return results;
- };
-}
-
-/*--------------------------------------------------------------------------*/
-
-if (!Node) var Node = { };
-
-if (!Node.ELEMENT_NODE) {
- Object.extend(Node, {
- ELEMENT_NODE: 1,
- ATTRIBUTE_NODE: 2,
- TEXT_NODE: 3,
- CDATA_SECTION_NODE: 4,
- ENTITY_REFERENCE_NODE: 5,
- ENTITY_NODE: 6,
- PROCESSING_INSTRUCTION_NODE: 7,
- COMMENT_NODE: 8,
- DOCUMENT_NODE: 9,
- DOCUMENT_TYPE_NODE: 10,
- DOCUMENT_FRAGMENT_NODE: 11,
- NOTATION_NODE: 12
- });
-}
-
-
-
-(function(global) {
- function shouldUseCache(tagName, attributes) {
- if (tagName === 'select') return false;
- if ('type' in attributes) return false;
- return true;
- }
-
- var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
- try {
- var el = document.createElement('<input name="x">');
- return el.tagName.toLowerCase() === 'input' && el.name === 'x';
- }
- catch(err) {
- return false;
- }
- })();
-
- var element = global.Element;
-
- global.Element = function(tagName, attributes) {
- attributes = attributes || { };
- tagName = tagName.toLowerCase();
- var cache = Element.cache;
-
- if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) {
- tagName = '<' + tagName + ' name="' + attributes.name + '">';
- delete attributes.name;
- return Element.writeAttribute(document.createElement(tagName), attributes);
- }
-
- if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
-
- var node = shouldUseCache(tagName, attributes) ?
- cache[tagName].cloneNode(false) : document.createElement(tagName);
-
- return Element.writeAttribute(node, attributes);
- };
-
- Object.extend(global.Element, element || { });
- if (element) global.Element.prototype = element.prototype;
-
-})(this);
-
-Element.idCounter = 1;
-Element.cache = { };
-
-Element._purgeElement = function(element) {
- var uid = element._prototypeUID;
- if (uid) {
- Element.stopObserving(element);
- element._prototypeUID = void 0;
- delete Element.Storage[uid];
- }
-}
-
-Element.Methods = {
- visible: function(element) {
- return $(element).style.display != 'none';
- },
-
- toggle: function(element) {
- element = $(element);
- Element[Element.visible(element) ? 'hide' : 'show'](element);
- return element;
- },
-
- hide: function(element) {
- element = $(element);
- element.style.display = 'none';
- return element;
- },
-
- show: function(element) {
- element = $(element);
- element.style.display = '';
- return element;
- },
-
- remove: function(element) {
- element = $(element);
- element.parentNode.removeChild(element);
- return element;
- },
-
- update: (function(){
-
- var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
- var el = document.createElement("select"),
- isBuggy = true;
- el.innerHTML = "<option value=\"test\">test</option>";
- if (el.options && el.options[0]) {
- isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
- }
- el = null;
- return isBuggy;
- })();
-
- var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
- try {
- var el = document.createElement("table");
- if (el && el.tBodies) {
- el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
- var isBuggy = typeof el.tBodies[0] == "undefined";
- el = null;
- return isBuggy;
- }
- } catch (e) {
- return true;
- }
- })();
-
- var LINK_ELEMENT_INNERHTML_BUGGY = (function() {
- try {
- var el = document.createElement('div');
- el.innerHTML = "<link>";
- var isBuggy = (el.childNodes.length === 0);
- el = null;
- return isBuggy;
- } catch(e) {
- return true;
- }
- })();
-
- var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY ||
- TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY;
-
- var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
- var s = document.createElement("script"),
- isBuggy = false;
- try {
- s.appendChild(document.createTextNode(""));
- isBuggy = !s.firstChild ||
- s.firstChild && s.firstChild.nodeType !== 3;
- } catch (e) {
- isBuggy = true;
- }
- s = null;
- return isBuggy;
- })();
-
-
- function update(element, content) {
- element = $(element);
- var purgeElement = Element._purgeElement;
-
- var descendants = element.getElementsByTagName('*'),
- i = descendants.length;
- while (i--) purgeElement(descendants[i]);
-
- if (content && content.toElement)
- content = content.toElement();
-
- if (Object.isElement(content))
- return element.update().insert(content);
-
- content = Object.toHTML(content);
-
- var tagName = element.tagName.toUpperCase();
-
- if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
- element.text = content;
- return element;
- }
-
- if (ANY_INNERHTML_BUGGY) {
- if (tagName in Element._insertionTranslations.tags) {
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
- Element._getContentFromAnonymousElement(tagName, content.stripScripts())
- .each(function(node) {
- element.appendChild(node)
- });
- } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf('<link') > -1) {
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
- var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true);
- nodes.each(function(node) { element.appendChild(node) });
- }
- else {
- element.innerHTML = content.stripScripts();
- }
- }
- else {
- element.innerHTML = content.stripScripts();
- }
-
- content.evalScripts.bind(content).defer();
- return element;
- }
-
- return update;
- })(),
-
- replace: function(element, content) {
- element = $(element);
- if (content && content.toElement) content = content.toElement();
- else if (!Object.isElement(content)) {
- content = Object.toHTML(content);
- var range = element.ownerDocument.createRange();
- range.selectNode(element);
- content.evalScripts.bind(content).defer();
- content = range.createContextualFragment(content.stripScripts());
- }
- element.parentNode.replaceChild(content, element);
- return element;
- },
-
- insert: function(element, insertions) {
- element = $(element);
-
- if (Object.isString(insertions) || Object.isNumber(insertions) ||
- Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
- insertions = {bottom:insertions};
-
- var content, insert, tagName, childNodes;
-
- for (var position in insertions) {
- content = insertions[position];
- position = position.toLowerCase();
- insert = Element._insertionTranslations[position];
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- insert(element, content);
- continue;
- }
-
- content = Object.toHTML(content);
-
- tagName = ((position == 'before' || position == 'after')
- ? element.parentNode : element).tagName.toUpperCase();
-
- childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
-
- if (position == 'top' || position == 'after') childNodes.reverse();
- childNodes.each(insert.curry(element));
-
- content.evalScripts.bind(content).defer();
- }
-
- return element;
- },
-
- wrap: function(element, wrapper, attributes) {
- element = $(element);
- if (Object.isElement(wrapper))
- $(wrapper).writeAttribute(attributes || { });
- else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
- else wrapper = new Element('div', wrapper);
- if (element.parentNode)
- element.parentNode.replaceChild(wrapper, element);
- wrapper.appendChild(element);
- return wrapper;
- },
-
- inspect: function(element) {
- element = $(element);
- var result = '<' + element.tagName.toLowerCase();
- $H({'id': 'id', 'className': 'class'}).each(function(pair) {
- var property = pair.first(),
- attribute = pair.last(),
- value = (element[property] || '').toString();
- if (value) result += ' ' + attribute + '=' + value.inspect(true);
- });
- return result + '>';
- },
-
- recursivelyCollect: function(element, property, maximumLength) {
- element = $(element);
- maximumLength = maximumLength || -1;
- var elements = [];
-
- while (element = element[property]) {
- if (element.nodeType == 1)
- elements.push(Element.extend(element));
- if (elements.length == maximumLength)
- break;
- }
-
- return elements;
- },
-
- ancestors: function(element) {
- return Element.recursivelyCollect(element, 'parentNode');
- },
-
- descendants: function(element) {
- return Element.select(element, "*");
- },
-
- firstDescendant: function(element) {
- element = $(element).firstChild;
- while (element && element.nodeType != 1) element = element.nextSibling;
- return $(element);
- },
-
- immediateDescendants: function(element) {
- var results = [], child = $(element).firstChild;
- while (child) {
- if (child.nodeType === 1) {
- results.push(Element.extend(child));
- }
- child = child.nextSibling;
- }
- return results;
- },
-
- previousSiblings: function(element, maximumLength) {
- return Element.recursivelyCollect(element, 'previousSibling');
- },
-
- nextSiblings: function(element) {
- return Element.recursivelyCollect(element, 'nextSibling');
- },
-
- siblings: function(element) {
- element = $(element);
- return Element.previousSiblings(element).reverse()
- .concat(Element.nextSiblings(element));
- },
-
- match: function(element, selector) {
- element = $(element);
- if (Object.isString(selector))
- return Prototype.Selector.match(element, selector);
- return selector.match(element);
- },
-
- up: function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return $(element.parentNode);
- var ancestors = Element.ancestors(element);
- return Object.isNumber(expression) ? ancestors[expression] :
- Prototype.Selector.find(ancestors, expression, index);
- },
-
- down: function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return Element.firstDescendant(element);
- return Object.isNumber(expression) ? Element.descendants(element)[expression] :
- Element.select(element, expression)[index || 0];
- },
-
- previous: function(element, expression, index) {
- element = $(element);
- if (Object.isNumber(expression)) index = expression, expression = false;
- if (!Object.isNumber(index)) index = 0;
-
- if (expression) {
- return Prototype.Selector.find(element.previousSiblings(), expression, index);
- } else {
- return element.recursivelyCollect("previousSibling", index + 1)[index];
- }
- },
-
- next: function(element, expression, index) {
- element = $(element);
- if (Object.isNumber(expression)) index = expression, expression = false;
- if (!Object.isNumber(index)) index = 0;
-
- if (expression) {
- return Prototype.Selector.find(element.nextSiblings(), expression, index);
- } else {
- var maximumLength = Object.isNumber(index) ? index + 1 : 1;
- return element.recursivelyCollect("nextSibling", index + 1)[index];
- }
- },
-
-
- select: function(element) {
- element = $(element);
- var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
- return Prototype.Selector.select(expressions, element);
- },
-
- adjacent: function(element) {
- element = $(element);
- var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
- return Prototype.Selector.select(expressions, element.parentNode).without(element);
- },
-
- identify: function(element) {
- element = $(element);
- var id = Element.readAttribute(element, 'id');
- if (id) return id;
- do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
- Element.writeAttribute(element, 'id', id);
- return id;
- },
-
- readAttribute: function(element, name) {
- element = $(element);
- if (Prototype.Browser.IE) {
- var t = Element._attributeTranslations.read;
- if (t.values[name]) return t.values[name](element, name);
- if (t.names[name]) name = t.names[name];
- if (name.include(':')) {
- return (!element.attributes || !element.attributes[name]) ? null :
- element.attributes[name].value;
- }
- }
- return element.getAttribute(name);
- },
-
- writeAttribute: function(element, name, value) {
- element = $(element);
- var attributes = { }, t = Element._attributeTranslations.write;
-
- if (typeof name == 'object') attributes = name;
- else attributes[name] = Object.isUndefined(value) ? true : value;
-
- for (var attr in attributes) {
- name = t.names[attr] || attr;
- value = attributes[attr];
- if (t.values[attr]) name = t.values[attr](element, value);
- if (value === false || value === null)
- element.removeAttribute(name);
- else if (value === true)
- element.setAttribute(name, name);
- else element.setAttribute(name, value);
- }
- return element;
- },
-
- getHeight: function(element) {
- return Element.getDimensions(element).height;
- },
-
- getWidth: function(element) {
- return Element.getDimensions(element).width;
- },
-
- classNames: function(element) {
- return new Element.ClassNames(element);
- },
-
- hasClassName: function(element, className) {
- if (!(element = $(element))) return;
- var elementClassName = element.className;
- return (elementClassName.length > 0 && (elementClassName == className ||
- new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
- },
-
- addClassName: function(element, className) {
- if (!(element = $(element))) return;
- if (!Element.hasClassName(element, className))
- element.className += (element.className ? ' ' : '') + className;
- return element;
- },
-
- removeClassName: function(element, className) {
- if (!(element = $(element))) return;
- element.className = element.className.replace(
- new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
- return element;
- },
-
- toggleClassName: function(element, className) {
- if (!(element = $(element))) return;
- return Element[Element.hasClassName(element, className) ?
- 'removeClassName' : 'addClassName'](element, className);
- },
-
- cleanWhitespace: function(element) {
- element = $(element);
- var node = element.firstChild;
- while (node) {
- var nextNode = node.nextSibling;
- if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
- element.removeChild(node);
- node = nextNode;
- }
- return element;
- },
-
- empty: function(element) {
- return $(element).innerHTML.blank();
- },
-
- descendantOf: function(element, ancestor) {
- element = $(element), ancestor = $(ancestor);
-
- if (element.compareDocumentPosition)
- return (element.compareDocumentPosition(ancestor) & 8) === 8;
-
- if (ancestor.contains)
- return ancestor.contains(element) && ancestor !== element;
-
- while (element = element.parentNode)
- if (element == ancestor) return true;
-
- return false;
- },
-
- scrollTo: function(element) {
- element = $(element);
- var pos = Element.cumulativeOffset(element);
- window.scrollTo(pos[0], pos[1]);
- return element;
- },
-
- getStyle: function(element, style) {
- element = $(element);
- style = style == 'float' ? 'cssFloat' : style.camelize();
- var value = element.style[style];
- if (!value || value == 'auto') {
- var css = document.defaultView.getComputedStyle(element, null);
- value = css ? css[style] : null;
- }
- if (style == 'opacity') return value ? parseFloat(value) : 1.0;
- return value == 'auto' ? null : value;
- },
-
- getOpacity: function(element) {
- return $(element).getStyle('opacity');
- },
-
- setStyle: function(element, styles) {
- element = $(element);
- var elementStyle = element.style, match;
- if (Object.isString(styles)) {
- element.style.cssText += ';' + styles;
- return styles.include('opacity') ?
- element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
- }
- for (var property in styles)
- if (property == 'opacity') element.setOpacity(styles[property]);
- else
- elementStyle[(property == 'float' || property == 'cssFloat') ?
- (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
- property] = styles[property];
-
- return element;
- },
-
- setOpacity: function(element, value) {
- element = $(element);
- element.style.opacity = (value == 1 || value === '') ? '' :
- (value < 0.00001) ? 0 : value;
- return element;
- },
-
- makePositioned: function(element) {
- element = $(element);
- var pos = Element.getStyle(element, 'position');
- if (pos == 'static' || !pos) {
- element._madePositioned = true;
- element.style.position = 'relative';
- if (Prototype.Browser.Opera) {
- element.style.top = 0;
- element.style.left = 0;
- }
- }
- return element;
- },
-
- undoPositioned: function(element) {
- element = $(element);
- if (element._madePositioned) {
- element._madePositioned = undefined;
- element.style.position =
- element.style.top =
- element.style.left =
- element.style.bottom =
- element.style.right = '';
- }
- return element;
- },
-
- makeClipping: function(element) {
- element = $(element);
- if (element._overflow) return element;
- element._overflow = Element.getStyle(element, 'overflow') || 'auto';
- if (element._overflow !== 'hidden')
- element.style.overflow = 'hidden';
- return element;
- },
-
- undoClipping: function(element) {
- element = $(element);
- if (!element._overflow) return element;
- element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
- element._overflow = null;
- return element;
- },
-
- clonePosition: function(element, source) {
- var options = Object.extend({
- setLeft: true,
- setTop: true,
- setWidth: true,
- setHeight: true,
- offsetTop: 0,
- offsetLeft: 0
- }, arguments[2] || { });
-
- source = $(source);
- var p = Element.viewportOffset(source), delta = [0, 0], parent = null;
-
- element = $(element);
-
- if (Element.getStyle(element, 'position') == 'absolute') {
- parent = Element.getOffsetParent(element);
- delta = Element.viewportOffset(parent);
- }
-
- if (parent == document.body) {
- delta[0] -= document.body.offsetLeft;
- delta[1] -= document.body.offsetTop;
- }
-
- if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
- if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
- if (options.setWidth) element.style.width = source.offsetWidth + 'px';
- if (options.setHeight) element.style.height = source.offsetHeight + 'px';
- return element;
- }
-};
-
-Object.extend(Element.Methods, {
- getElementsBySelector: Element.Methods.select,
-
- childElements: Element.Methods.immediateDescendants
-});
-
-Element._attributeTranslations = {
- write: {
- names: {
- className: 'class',
- htmlFor: 'for'
- },
- values: { }
- }
-};
-
-if (Prototype.Browser.Opera) {
- Element.Methods.getStyle = Element.Methods.getStyle.wrap(
- function(proceed, element, style) {
- switch (style) {
- case 'height': case 'width':
- if (!Element.visible(element)) return null;
-
- var dim = parseInt(proceed(element, style), 10);
-
- if (dim !== element['offset' + style.capitalize()])
- return dim + 'px';
-
- var properties;
- if (style === 'height') {
- properties = ['border-top-width', 'padding-top',
- 'padding-bottom', 'border-bottom-width'];
- }
- else {
- properties = ['border-left-width', 'padding-left',
- 'padding-right', 'border-right-width'];
- }
- return properties.inject(dim, function(memo, property) {
- var val = proceed(element, property);
- return val === null ? memo : memo - parseInt(val, 10);
- }) + 'px';
- default: return proceed(element, style);
- }
- }
- );
-
- Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
- function(proceed, element, attribute) {
- if (attribute === 'title') return element.title;
- return proceed(element, attribute);
- }
- );
-}
-
-else if (Prototype.Browser.IE) {
- Element.Methods.getStyle = function(element, style) {
- element = $(element);
- style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
- var value = element.style[style];
- if (!value && element.currentStyle) value = element.currentStyle[style];
-
- if (style == 'opacity') {
- if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
- if (value[1]) return parseFloat(value[1]) / 100;
- return 1.0;
- }
-
- if (value == 'auto') {
- if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
- return element['offset' + style.capitalize()] + 'px';
- return null;
- }
- return value;
- };
-
- Element.Methods.setOpacity = function(element, value) {
- function stripAlpha(filter){
- return filter.replace(/alpha\([^\)]*\)/gi,'');
- }
- element = $(element);
- var currentStyle = element.currentStyle;
- if ((currentStyle && !currentStyle.hasLayout) ||
- (!currentStyle && element.style.zoom == 'normal'))
- element.style.zoom = 1;
-
- var filter = element.getStyle('filter'), style = element.style;
- if (value == 1 || value === '') {
- (filter = stripAlpha(filter)) ?
- style.filter = filter : style.removeAttribute('filter');
- return element;
- } else if (value < 0.00001) value = 0;
- style.filter = stripAlpha(filter) +
- 'alpha(opacity=' + (value * 100) + ')';
- return element;
- };
-
- Element._attributeTranslations = (function(){
-
- var classProp = 'className',
- forProp = 'for',
- el = document.createElement('div');
-
- el.setAttribute(classProp, 'x');
-
- if (el.className !== 'x') {
- el.setAttribute('class', 'x');
- if (el.className === 'x') {
- classProp = 'class';
- }
- }
- el = null;
-
- el = document.createElement('label');
- el.setAttribute(forProp, 'x');
- if (el.htmlFor !== 'x') {
- el.setAttribute('htmlFor', 'x');
- if (el.htmlFor === 'x') {
- forProp = 'htmlFor';
- }
- }
- el = null;
-
- return {
- read: {
- names: {
- 'class': classProp,
- 'className': classProp,
- 'for': forProp,
- 'htmlFor': forProp
- },
- values: {
- _getAttr: function(element, attribute) {
- return element.getAttribute(attribute);
- },
- _getAttr2: function(element, attribute) {
- return element.getAttribute(attribute, 2);
- },
- _getAttrNode: function(element, attribute) {
- var node = element.getAttributeNode(attribute);
- return node ? node.value : "";
- },
- _getEv: (function(){
-
- var el = document.createElement('div'), f;
- el.onclick = Prototype.emptyFunction;
- var value = el.getAttribute('onclick');
-
- if (String(value).indexOf('{') > -1) {
- f = function(element, attribute) {
- attribute = element.getAttribute(attribute);
- if (!attribute) return null;
- attribute = attribute.toString();
- attribute = attribute.split('{')[1];
- attribute = attribute.split('}')[0];
- return attribute.strip();
- };
- }
- else if (value === '') {
- f = function(element, attribute) {
- attribute = element.getAttribute(attribute);
- if (!attribute) return null;
- return attribute.strip();
- };
- }
- el = null;
- return f;
- })(),
- _flag: function(element, attribute) {
- return $(element).hasAttribute(attribute) ? attribute : null;
- },
- style: function(element) {
- return element.style.cssText.toLowerCase();
- },
- title: function(element) {
- return element.title;
- }
- }
- }
- }
- })();
-
- Element._attributeTranslations.write = {
- names: Object.extend({
- cellpadding: 'cellPadding',
- cellspacing: 'cellSpacing'
- }, Element._attributeTranslations.read.names),
- values: {
- checked: function(element, value) {
- element.checked = !!value;
- },
-
- style: function(element, value) {
- element.style.cssText = value ? value : '';
- }
- }
- };
-
- Element._attributeTranslations.has = {};
-
- $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
- 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
- Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
- Element._attributeTranslations.has[attr.toLowerCase()] = attr;
- });
-
- (function(v) {
- Object.extend(v, {
- href: v._getAttr2,
- src: v._getAttr2,
- type: v._getAttr,
- action: v._getAttrNode,
- disabled: v._flag,
- checked: v._flag,
- readonly: v._flag,
- multiple: v._flag,
- onload: v._getEv,
- onunload: v._getEv,
- onclick: v._getEv,
- ondblclick: v._getEv,
- onmousedown: v._getEv,
- onmouseup: v._getEv,
- onmouseover: v._getEv,
- onmousemove: v._getEv,
- onmouseout: v._getEv,
- onfocus: v._getEv,
- onblur: v._getEv,
- onkeypress: v._getEv,
- onkeydown: v._getEv,
- onkeyup: v._getEv,
- onsubmit: v._getEv,
- onreset: v._getEv,
- onselect: v._getEv,
- onchange: v._getEv
- });
- })(Element._attributeTranslations.read.values);
-
- if (Prototype.BrowserFeatures.ElementExtensions) {
- (function() {
- function _descendants(element) {
- var nodes = element.getElementsByTagName('*'), results = [];
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName !== "!") // Filter out comment nodes.
- results.push(node);
- return results;
- }
-
- Element.Methods.down = function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return element.firstDescendant();
- return Object.isNumber(expression) ? _descendants(element)[expression] :
- Element.select(element, expression)[index || 0];
- }
- })();
- }
-
-}
-
-else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
- Element.Methods.setOpacity = function(element, value) {
- element = $(element);
- element.style.opacity = (value == 1) ? 0.999999 :
- (value === '') ? '' : (value < 0.00001) ? 0 : value;
- return element;
- };
-}
-
-else if (Prototype.Browser.WebKit) {
- Element.Methods.setOpacity = function(element, value) {
- element = $(element);
- element.style.opacity = (value == 1 || value === '') ? '' :
- (value < 0.00001) ? 0 : value;
-
- if (value == 1)
- if (element.tagName.toUpperCase() == 'IMG' && element.width) {
- element.width++; element.width--;
- } else try {
- var n = document.createTextNode(' ');
- element.appendChild(n);
- element.removeChild(n);
- } catch (e) { }
-
- return element;
- };
-}
-
-if ('outerHTML' in document.documentElement) {
- Element.Methods.replace = function(element, content) {
- element = $(element);
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- element.parentNode.replaceChild(content, element);
- return element;
- }
-
- content = Object.toHTML(content);
- var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
-
- if (Element._insertionTranslations.tags[tagName]) {
- var nextSibling = element.next(),
- fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
- parent.removeChild(element);
- if (nextSibling)
- fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
- else
- fragments.each(function(node) { parent.appendChild(node) });
- }
- else element.outerHTML = content.stripScripts();
-
- content.evalScripts.bind(content).defer();
- return element;
- };
-}
-
-Element._returnOffset = function(l, t) {
- var result = [l, t];
- result.left = l;
- result.top = t;
- return result;
-};
-
-Element._getContentFromAnonymousElement = function(tagName, html, force) {
- var div = new Element('div'),
- t = Element._insertionTranslations.tags[tagName];
-
- var workaround = false;
- if (t) workaround = true;
- else if (force) {
- workaround = true;
- t = ['', '', 0];
- }
-
- if (workaround) {
- div.innerHTML = '&nbsp;' + t[0] + html + t[1];
- div.removeChild(div.firstChild);
- for (var i = t[2]; i--; ) {
- div = div.firstChild;
- }
- }
- else {
- div.innerHTML = html;
- }
- return $A(div.childNodes);
-};
-
-Element._insertionTranslations = {
- before: function(element, node) {
- element.parentNode.insertBefore(node, element);
- },
- top: function(element, node) {
- element.insertBefore(node, element.firstChild);
- },
- bottom: function(element, node) {
- element.appendChild(node);
- },
- after: function(element, node) {
- element.parentNode.insertBefore(node, element.nextSibling);
- },
- tags: {
- TABLE: ['<table>', '</table>', 1],
- TBODY: ['<table><tbody>', '</tbody></table>', 2],
- TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
- TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
- SELECT: ['<select>', '</select>', 1]
- }
-};
-
-(function() {
- var tags = Element._insertionTranslations.tags;
- Object.extend(tags, {
- THEAD: tags.TBODY,
- TFOOT: tags.TBODY,
- TH: tags.TD
- });
-})();
-
-Element.Methods.Simulated = {
- hasAttribute: function(element, attribute) {
- attribute = Element._attributeTranslations.has[attribute] || attribute;
- var node = $(element).getAttributeNode(attribute);
- return !!(node && node.specified);
- }
-};
-
-Element.Methods.ByTag = { };
-
-Object.extend(Element, Element.Methods);
-
-(function(div) {
-
- if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
- window.HTMLElement = { };
- window.HTMLElement.prototype = div['__proto__'];
- Prototype.BrowserFeatures.ElementExtensions = true;
- }
-
- div = null;
-
-})(document.createElement('div'));
-
-Element.extend = (function() {
-
- function checkDeficiency(tagName) {
- if (typeof window.Element != 'undefined') {
- var proto = window.Element.prototype;
- if (proto) {
- var id = '_' + (Math.random()+'').slice(2),
- el = document.createElement(tagName);
- proto[id] = 'x';
- var isBuggy = (el[id] !== 'x');
- delete proto[id];
- el = null;
- return isBuggy;
- }
- }
- return false;
- }
-
- function extendElementWith(element, methods) {
- for (var property in methods) {
- var value = methods[property];
- if (Object.isFunction(value) && !(property in element))
- element[property] = value.methodize();
- }
- }
-
- var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
-
- if (Prototype.BrowserFeatures.SpecificElementExtensions) {
- if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
- return function(element) {
- if (element && typeof element._extendedByPrototype == 'undefined') {
- var t = element.tagName;
- if (t && (/^(?:object|applet|embed)$/i.test(t))) {
- extendElementWith(element, Element.Methods);
- extendElementWith(element, Element.Methods.Simulated);
- extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
- }
- }
- return element;
- }
- }
- return Prototype.K;
- }
-
- var Methods = { }, ByTag = Element.Methods.ByTag;
-
- var extend = Object.extend(function(element) {
- if (!element || typeof element._extendedByPrototype != 'undefined' ||
- element.nodeType != 1 || element == window) return element;
-
- var methods = Object.clone(Methods),
- tagName = element.tagName.toUpperCase();
-
- if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
-
- extendElementWith(element, methods);
-
- element._extendedByPrototype = Prototype.emptyFunction;
- return element;
-
- }, {
- refresh: function() {
- if (!Prototype.BrowserFeatures.ElementExtensions) {
- Object.extend(Methods, Element.Methods);
- Object.extend(Methods, Element.Methods.Simulated);
- }
- }
- });
-
- extend.refresh();
- return extend;
-})();
-
-if (document.documentElement.hasAttribute) {
- Element.hasAttribute = function(element, attribute) {
- return element.hasAttribute(attribute);
- };
-}
-else {
- Element.hasAttribute = Element.Methods.Simulated.hasAttribute;
-}
-
-Element.addMethods = function(methods) {
- var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
-
- if (!methods) {
- Object.extend(Form, Form.Methods);
- Object.extend(Form.Element, Form.Element.Methods);
- Object.extend(Element.Methods.ByTag, {
- "FORM": Object.clone(Form.Methods),
- "INPUT": Object.clone(Form.Element.Methods),
- "SELECT": Object.clone(Form.Element.Methods),
- "TEXTAREA": Object.clone(Form.Element.Methods),
- "BUTTON": Object.clone(Form.Element.Methods)
- });
- }
-
- if (arguments.length == 2) {
- var tagName = methods;
- methods = arguments[1];
- }
-
- if (!tagName) Object.extend(Element.Methods, methods || { });
- else {
- if (Object.isArray(tagName)) tagName.each(extend);
- else extend(tagName);
- }
-
- function extend(tagName) {
- tagName = tagName.toUpperCase();
- if (!Element.Methods.ByTag[tagName])
- Element.Methods.ByTag[tagName] = { };
- Object.extend(Element.Methods.ByTag[tagName], methods);
- }
-
- function copy(methods, destination, onlyIfAbsent) {
- onlyIfAbsent = onlyIfAbsent || false;
- for (var property in methods) {
- var value = methods[property];
- if (!Object.isFunction(value)) continue;
- if (!onlyIfAbsent || !(property in destination))
- destination[property] = value.methodize();
- }
- }
-
- function findDOMClass(tagName) {
- var klass;
- var trans = {
- "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
- "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
- "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
- "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
- "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
- "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
- "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
- "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
- "FrameSet", "IFRAME": "IFrame"
- };
- if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
- if (window[klass]) return window[klass];
- klass = 'HTML' + tagName + 'Element';
- if (window[klass]) return window[klass];
- klass = 'HTML' + tagName.capitalize() + 'Element';
- if (window[klass]) return window[klass];
-
- var element = document.createElement(tagName),
- proto = element['__proto__'] || element.constructor.prototype;
-
- element = null;
- return proto;
- }
-
- var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
- Element.prototype;
-
- if (F.ElementExtensions) {
- copy(Element.Methods, elementPrototype);
- copy(Element.Methods.Simulated, elementPrototype, true);
- }
-
- if (F.SpecificElementExtensions) {
- for (var tag in Element.Methods.ByTag) {
- var klass = findDOMClass(tag);
- if (Object.isUndefined(klass)) continue;
- copy(T[tag], klass.prototype);
- }
- }
-
- Object.extend(Element, Element.Methods);
- delete Element.ByTag;
-
- if (Element.extend.refresh) Element.extend.refresh();
- Element.cache = { };
-};
-
-
-document.viewport = {
-
- getDimensions: function() {
- return { width: this.getWidth(), height: this.getHeight() };
- },
-
- getScrollOffsets: function() {
- return Element._returnOffset(
- window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
- window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
- }
-};
-
-(function(viewport) {
- var B = Prototype.Browser, doc = document, element, property = {};
-
- function getRootElement() {
- if (B.WebKit && !doc.evaluate)
- return document;
-
- if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
- return document.body;
-
- return document.documentElement;
- }
-
- function define(D) {
- if (!element) element = getRootElement();
-
- property[D] = 'client' + D;
-
- viewport['get' + D] = function() { return element[property[D]] };
- return viewport['get' + D]();
- }
-
- viewport.getWidth = define.curry('Width');
-
- viewport.getHeight = define.curry('Height');
-})(document.viewport);
-
-
-Element.Storage = {
- UID: 1
-};
-
-Element.addMethods({
- getStorage: function(element) {
- if (!(element = $(element))) return;
-
- var uid;
- if (element === window) {
- uid = 0;
- } else {
- if (typeof element._prototypeUID === "undefined")
- element._prototypeUID = Element.Storage.UID++;
- uid = element._prototypeUID;
- }
-
- if (!Element.Storage[uid])
- Element.Storage[uid] = $H();
-
- return Element.Storage[uid];
- },
-
- store: function(element, key, value) {
- if (!(element = $(element))) return;
-
- if (arguments.length === 2) {
- Element.getStorage(element).update(key);
- } else {
- Element.getStorage(element).set(key, value);
- }
-
- return element;
- },
-
- retrieve: function(element, key, defaultValue) {
- if (!(element = $(element))) return;
- var hash = Element.getStorage(element), value = hash.get(key);
-
- if (Object.isUndefined(value)) {
- hash.set(key, defaultValue);
- value = defaultValue;
- }
-
- return value;
- },
-
- clone: function(element, deep) {
- if (!(element = $(element))) return;
- var clone = element.cloneNode(deep);
- clone._prototypeUID = void 0;
- if (deep) {
- var descendants = Element.select(clone, '*'),
- i = descendants.length;
- while (i--) {
- descendants[i]._prototypeUID = void 0;
- }
- }
- return Element.extend(clone);
- },
-
- purge: function(element) {
- if (!(element = $(element))) return;
- var purgeElement = Element._purgeElement;
-
- purgeElement(element);
-
- var descendants = element.getElementsByTagName('*'),
- i = descendants.length;
-
- while (i--) purgeElement(descendants[i]);
-
- return null;
- }
-});
-
-(function() {
-
- function toDecimal(pctString) {
- var match = pctString.match(/^(\d+)%?$/i);
- if (!match) return null;
- return (Number(match[1]) / 100);
- }
-
- function getPixelValue(value, property, context) {
- var element = null;
- if (Object.isElement(value)) {
- element = value;
- value = element.getStyle(property);
- }
-
- if (value === null) {
- return null;
- }
-
- if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
- return window.parseFloat(value);
- }
-
- var isPercentage = value.include('%'), isViewport = (context === document.viewport);
-
- if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) {
- var style = element.style.left, rStyle = element.runtimeStyle.left;
- element.runtimeStyle.left = element.currentStyle.left;
- element.style.left = value || 0;
- value = element.style.pixelLeft;
- element.style.left = style;
- element.runtimeStyle.left = rStyle;
-
- return value;
- }
-
- if (element && isPercentage) {
- context = context || element.parentNode;
- var decimal = toDecimal(value);
- var whole = null;
- var position = element.getStyle('position');
-
- var isHorizontal = property.include('left') || property.include('right') ||
- property.include('width');
-
- var isVertical = property.include('top') || property.include('bottom') ||
- property.include('height');
-
- if (context === document.viewport) {
- if (isHorizontal) {
- whole = document.viewport.getWidth();
- } else if (isVertical) {
- whole = document.viewport.getHeight();
- }
- } else {
- if (isHorizontal) {
- whole = $(context).measure('width');
- } else if (isVertical) {
- whole = $(context).measure('height');
- }
- }
-
- return (whole === null) ? 0 : whole * decimal;
- }
-
- return 0;
- }
-
- function toCSSPixels(number) {
- if (Object.isString(number) && number.endsWith('px')) {
- return number;
- }
- return number + 'px';
- }
-
- function isDisplayed(element) {
- var originalElement = element;
- while (element && element.parentNode) {
- var display = element.getStyle('display');
- if (display === 'none') {
- return false;
- }
- element = $(element.parentNode);
- }
- return true;
- }
-
- var hasLayout = Prototype.K;
- if ('currentStyle' in document.documentElement) {
- hasLayout = function(element) {
- if (!element.currentStyle.hasLayout) {
- element.style.zoom = 1;
- }
- return element;
- };
- }
-
- function cssNameFor(key) {
- if (key.include('border')) key = key + '-width';
- return key.camelize();
- }
-
- Element.Layout = Class.create(Hash, {
- initialize: function($super, element, preCompute) {
- $super();
- this.element = $(element);
-
- Element.Layout.PROPERTIES.each( function(property) {
- this._set(property, null);
- }, this);
-
- if (preCompute) {
- this._preComputing = true;
- this._begin();
- Element.Layout.PROPERTIES.each( this._compute, this );
- this._end();
- this._preComputing = false;
- }
- },
-
- _set: function(property, value) {
- return Hash.prototype.set.call(this, property, value);
- },
-
- set: function(property, value) {
- throw "Properties of Element.Layout are read-only.";
- },
-
- get: function($super, property) {
- var value = $super(property);
- return value === null ? this._compute(property) : value;
- },
-
- _begin: function() {
- if (this._prepared) return;
-
- var element = this.element;
- if (isDisplayed(element)) {
- this._prepared = true;
- return;
- }
-
- var originalStyles = {
- position: element.style.position || '',
- width: element.style.width || '',
- visibility: element.style.visibility || '',
- display: element.style.display || ''
- };
-
- element.store('prototype_original_styles', originalStyles);
-
- var position = element.getStyle('position'),
- width = element.getStyle('width');
-
- if (width === "0px" || width === null) {
- element.style.display = 'block';
- width = element.getStyle('width');
- }
-
- var context = (position === 'fixed') ? document.viewport :
- element.parentNode;
-
- element.setStyle({
- position: 'absolute',
- visibility: 'hidden',
- display: 'block'
- });
-
- var positionedWidth = element.getStyle('width');
-
- var newWidth;
- if (width && (positionedWidth === width)) {
- newWidth = getPixelValue(element, 'width', context);
- } else if (position === 'absolute' || position === 'fixed') {
- newWidth = getPixelValue(element, 'width', context);
- } else {
- var parent = element.parentNode, pLayout = $(parent).getLayout();
-
- newWidth = pLayout.get('width') -
- this.get('margin-left') -
- this.get('border-left') -
- this.get('padding-left') -
- this.get('padding-right') -
- this.get('border-right') -
- this.get('margin-right');
- }
-
- element.setStyle({ width: newWidth + 'px' });
-
- this._prepared = true;
- },
-
- _end: function() {
- var element = this.element;
- var originalStyles = element.retrieve('prototype_original_styles');
- element.store('prototype_original_styles', null);
- element.setStyle(originalStyles);
- this._prepared = false;
- },
-
- _compute: function(property) {
- var COMPUTATIONS = Element.Layout.COMPUTATIONS;
- if (!(property in COMPUTATIONS)) {
- throw "Property not found.";
- }
-
- return this._set(property, COMPUTATIONS[property].call(this, this.element));
- },
-
- toObject: function() {
- var args = $A(arguments);
- var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
- args.join(' ').split(' ');
- var obj = {};
- keys.each( function(key) {
- if (!Element.Layout.PROPERTIES.include(key)) return;
- var value = this.get(key);
- if (value != null) obj[key] = value;
- }, this);
- return obj;
- },
-
- toHash: function() {
- var obj = this.toObject.apply(this, arguments);
- return new Hash(obj);
- },
-
- toCSS: function() {
- var args = $A(arguments);
- var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
- args.join(' ').split(' ');
- var css = {};
-
- keys.each( function(key) {
- if (!Element.Layout.PROPERTIES.include(key)) return;
- if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
-
- var value = this.get(key);
- if (value != null) css[cssNameFor(key)] = value + 'px';
- }, this);
- return css;
- },
-
- inspect: function() {
- return "#<Element.Layout>";
- }
- });
-
- Object.extend(Element.Layout, {
- PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),
-
- COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),
-
- COMPUTATIONS: {
- 'height': function(element) {
- if (!this._preComputing) this._begin();
-
- var bHeight = this.get('border-box-height');
- if (bHeight <= 0) {
- if (!this._preComputing) this._end();
- return 0;
- }
-
- var bTop = this.get('border-top'),
- bBottom = this.get('border-bottom');
-
- var pTop = this.get('padding-top'),
- pBottom = this.get('padding-bottom');
-
- if (!this._preComputing) this._end();
-
- return bHeight - bTop - bBottom - pTop - pBottom;
- },
-
- 'width': function(element) {
- if (!this._preComputing) this._begin();
-
- var bWidth = this.get('border-box-width');
- if (bWidth <= 0) {
- if (!this._preComputing) this._end();
- return 0;
- }
-
- var bLeft = this.get('border-left'),
- bRight = this.get('border-right');
-
- var pLeft = this.get('padding-left'),
- pRight = this.get('padding-right');
-
- if (!this._preComputing) this._end();
-
- return bWidth - bLeft - bRight - pLeft - pRight;
- },
-
- 'padding-box-height': function(element) {
- var height = this.get('height'),
- pTop = this.get('padding-top'),
- pBottom = this.get('padding-bottom');
-
- return height + pTop + pBottom;
- },
-
- 'padding-box-width': function(element) {
- var width = this.get('width'),
- pLeft = this.get('padding-left'),
- pRight = this.get('padding-right');
-
- return width + pLeft + pRight;
- },
-
- 'border-box-height': function(element) {
- if (!this._preComputing) this._begin();
- var height = element.offsetHeight;
- if (!this._preComputing) this._end();
- return height;
- },
-
- 'border-box-width': function(element) {
- if (!this._preComputing) this._begin();
- var width = element.offsetWidth;
- if (!this._preComputing) this._end();
- return width;
- },
-
- 'margin-box-height': function(element) {
- var bHeight = this.get('border-box-height'),
- mTop = this.get('margin-top'),
- mBottom = this.get('margin-bottom');
-
- if (bHeight <= 0) return 0;
-
- return bHeight + mTop + mBottom;
- },
-
- 'margin-box-width': function(element) {
- var bWidth = this.get('border-box-width'),
- mLeft = this.get('margin-left'),
- mRight = this.get('margin-right');
-
- if (bWidth <= 0) return 0;
-
- return bWidth + mLeft + mRight;
- },
-
- 'top': function(element) {
- var offset = element.positionedOffset();
- return offset.top;
- },
-
- 'bottom': function(element) {
- var offset = element.positionedOffset(),
- parent = element.getOffsetParent(),
- pHeight = parent.measure('height');
-
- var mHeight = this.get('border-box-height');
-
- return pHeight - mHeight - offset.top;
- },
-
- 'left': function(element) {
- var offset = element.positionedOffset();
- return offset.left;
- },
-
- 'right': function(element) {
- var offset = element.positionedOffset(),
- parent = element.getOffsetParent(),
- pWidth = parent.measure('width');
-
- var mWidth = this.get('border-box-width');
-
- return pWidth - mWidth - offset.left;
- },
-
- 'padding-top': function(element) {
- return getPixelValue(element, 'paddingTop');
- },
-
- 'padding-bottom': function(element) {
- return getPixelValue(element, 'paddingBottom');
- },
-
- 'padding-left': function(element) {
- return getPixelValue(element, 'paddingLeft');
- },
-
- 'padding-right': function(element) {
- return getPixelValue(element, 'paddingRight');
- },
-
- 'border-top': function(element) {
- return getPixelValue(element, 'borderTopWidth');
- },
-
- 'border-bottom': function(element) {
- return getPixelValue(element, 'borderBottomWidth');
- },
-
- 'border-left': function(element) {
- return getPixelValue(element, 'borderLeftWidth');
- },
-
- 'border-right': function(element) {
- return getPixelValue(element, 'borderRightWidth');
- },
-
- 'margin-top': function(element) {
- return getPixelValue(element, 'marginTop');
- },
-
- 'margin-bottom': function(element) {
- return getPixelValue(element, 'marginBottom');
- },
-
- 'margin-left': function(element) {
- return getPixelValue(element, 'marginLeft');
- },
-
- 'margin-right': function(element) {
- return getPixelValue(element, 'marginRight');
- }
- }
- });
-
- if ('getBoundingClientRect' in document.documentElement) {
- Object.extend(Element.Layout.COMPUTATIONS, {
- 'right': function(element) {
- var parent = hasLayout(element.getOffsetParent());
- var rect = element.getBoundingClientRect(),
- pRect = parent.getBoundingClientRect();
-
- return (pRect.right - rect.right).round();
- },
-
- 'bottom': function(element) {
- var parent = hasLayout(element.getOffsetParent());
- var rect = element.getBoundingClientRect(),
- pRect = parent.getBoundingClientRect();
-
- return (pRect.bottom - rect.bottom).round();
- }
- });
- }
-
- Element.Offset = Class.create({
- initialize: function(left, top) {
- this.left = left.round();
- this.top = top.round();
-
- this[0] = this.left;
- this[1] = this.top;
- },
-
- relativeTo: function(offset) {
- return new Element.Offset(
- this.left - offset.left,
- this.top - offset.top
- );
- },
-
- inspect: function() {
- return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
- },
-
- toString: function() {
- return "[#{left}, #{top}]".interpolate(this);
- },
-
- toArray: function() {
- return [this.left, this.top];
- }
- });
-
- function getLayout(element, preCompute) {
- return new Element.Layout(element, preCompute);
- }
-
- function measure(element, property) {
- return $(element).getLayout().get(property);
- }
-
- function getDimensions(element) {
- element = $(element);
- var display = Element.getStyle(element, 'display');
-
- if (display && display !== 'none') {
- return { width: element.offsetWidth, height: element.offsetHeight };
- }
-
- var style = element.style;
- var originalStyles = {
- visibility: style.visibility,
- position: style.position,
- display: style.display
- };
-
- var newStyles = {
- visibility: 'hidden',
- display: 'block'
- };
-
- if (originalStyles.position !== 'fixed')
- newStyles.position = 'absolute';
-
- Element.setStyle(element, newStyles);
-
- var dimensions = {
- width: element.offsetWidth,
- height: element.offsetHeight
- };
-
- Element.setStyle(element, originalStyles);
-
- return dimensions;
- }
-
- function getOffsetParent(element) {
- element = $(element);
-
- if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
- return $(document.body);
-
- var isInline = (Element.getStyle(element, 'display') === 'inline');
- if (!isInline && element.offsetParent) return $(element.offsetParent);
-
- while ((element = element.parentNode) && element !== document.body) {
- if (Element.getStyle(element, 'position') !== 'static') {
- return isHtml(element) ? $(document.body) : $(element);
- }
- }
-
- return $(document.body);
- }
-
-
- function cumulativeOffset(element) {
- element = $(element);
- var valueT = 0, valueL = 0;
- if (element.parentNode) {
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- } while (element);
- }
- return new Element.Offset(valueL, valueT);
- }
-
- function positionedOffset(element) {
- element = $(element);
-
- var layout = element.getLayout();
-
- var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- if (element) {
- if (isBody(element)) break;
- var p = Element.getStyle(element, 'position');
- if (p !== 'static') break;
- }
- } while (element);
-
- valueL -= layout.get('margin-top');
- valueT -= layout.get('margin-left');
-
- return new Element.Offset(valueL, valueT);
- }
-
- function cumulativeScrollOffset(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.scrollTop || 0;
- valueL += element.scrollLeft || 0;
- element = element.parentNode;
- } while (element);
- return new Element.Offset(valueL, valueT);
- }
-
- function viewportOffset(forElement) {
- element = $(element);
- var valueT = 0, valueL = 0, docBody = document.body;
-
- var element = forElement;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- if (element.offsetParent == docBody &&
- Element.getStyle(element, 'position') == 'absolute') break;
- } while (element = element.offsetParent);
-
- element = forElement;
- do {
- if (element != docBody) {
- valueT -= element.scrollTop || 0;
- valueL -= element.scrollLeft || 0;
- }
- } while (element = element.parentNode);
- return new Element.Offset(valueL, valueT);
- }
-
- function absolutize(element) {
- element = $(element);
-
- if (Element.getStyle(element, 'position') === 'absolute') {
- return element;
- }
-
- var offsetParent = getOffsetParent(element);
- var eOffset = element.viewportOffset(),
- pOffset = offsetParent.viewportOffset();
-
- var offset = eOffset.relativeTo(pOffset);
- var layout = element.getLayout();
-
- element.store('prototype_absolutize_original_styles', {
- left: element.getStyle('left'),
- top: element.getStyle('top'),
- width: element.getStyle('width'),
- height: element.getStyle('height')
- });
-
- element.setStyle({
- position: 'absolute',
- top: offset.top + 'px',
- left: offset.left + 'px',
- width: layout.get('width') + 'px',
- height: layout.get('height') + 'px'
- });
-
- return element;
- }
-
- function relativize(element) {
- element = $(element);
- if (Element.getStyle(element, 'position') === 'relative') {
- return element;
- }
-
- var originalStyles =
- element.retrieve('prototype_absolutize_original_styles');
-
- if (originalStyles) element.setStyle(originalStyles);
- return element;
- }
-
- if (Prototype.Browser.IE) {
- getOffsetParent = getOffsetParent.wrap(
- function(proceed, element) {
- element = $(element);
-
- if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
- return $(document.body);
-
- var position = element.getStyle('position');
- if (position !== 'static') return proceed(element);
-
- element.setStyle({ position: 'relative' });
- var value = proceed(element);
- element.setStyle({ position: position });
- return value;
- }
- );
-
- positionedOffset = positionedOffset.wrap(function(proceed, element) {
- element = $(element);
- if (!element.parentNode) return new Element.Offset(0, 0);
- var position = element.getStyle('position');
- if (position !== 'static') return proceed(element);
-
- var offsetParent = element.getOffsetParent();
- if (offsetParent && offsetParent.getStyle('position') === 'fixed')
- hasLayout(offsetParent);
-
- element.setStyle({ position: 'relative' });
- var value = proceed(element);
- element.setStyle({ position: position });
- return value;
- });
- } else if (Prototype.Browser.Webkit) {
- cumulativeOffset = function(element) {
- element = $(element);
- var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- if (element.offsetParent == document.body)
- if (Element.getStyle(element, 'position') == 'absolute') break;
-
- element = element.offsetParent;
- } while (element);
-
- return new Element.Offset(valueL, valueT);
- };
- }
-
-
- Element.addMethods({
- getLayout: getLayout,
- measure: measure,
- getDimensions: getDimensions,
- getOffsetParent: getOffsetParent,
- cumulativeOffset: cumulativeOffset,
- positionedOffset: positionedOffset,
- cumulativeScrollOffset: cumulativeScrollOffset,
- viewportOffset: viewportOffset,
- absolutize: absolutize,
- relativize: relativize
- });
-
- function isBody(element) {
- return element.nodeName.toUpperCase() === 'BODY';
- }
-
- function isHtml(element) {
- return element.nodeName.toUpperCase() === 'HTML';
- }
-
- function isDocument(element) {
- return element.nodeType === Node.DOCUMENT_NODE;
- }
-
- function isDetached(element) {
- return element !== document.body &&
- !Element.descendantOf(element, document.body);
- }
-
- if ('getBoundingClientRect' in document.documentElement) {
- Element.addMethods({
- viewportOffset: function(element) {
- element = $(element);
- if (isDetached(element)) return new Element.Offset(0, 0);
-
- var rect = element.getBoundingClientRect(),
- docEl = document.documentElement;
- return new Element.Offset(rect.left - docEl.clientLeft,
- rect.top - docEl.clientTop);
- }
- });
- }
-})();
-window.$$ = function() {
- var expression = $A(arguments).join(', ');
- return Prototype.Selector.select(expression, document);
-};
-
-Prototype.Selector = (function() {
-
- function select() {
- throw new Error('Method "Prototype.Selector.select" must be defined.');
- }
-
- function match() {
- throw new Error('Method "Prototype.Selector.match" must be defined.');
- }
-
- function find(elements, expression, index) {
- index = index || 0;
- var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
-
- for (i = 0; i < length; i++) {
- if (match(elements[i], expression) && index == matchIndex++) {
- return Element.extend(elements[i]);
- }
- }
- }
-
- function extendElements(elements) {
- for (var i = 0, length = elements.length; i < length; i++) {
- Element.extend(elements[i]);
- }
- return elements;
- }
-
-
- var K = Prototype.K;
-
- return {
- select: select,
- match: match,
- find: find,
- extendElements: (Element.extend === K) ? K : extendElements,
- extendElement: Element.extend
- };
-})();
-Prototype._original_property = window.Sizzle;
-/*!
- * Sizzle CSS Selector Engine - v1.0
- * Copyright 2009, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- * More information: http://sizzlejs.com/
- */
-(function(){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
- done = 0,
- toString = Object.prototype.toString,
- hasDuplicate = false,
- baseHasDuplicate = true;
-
-[0, 0].sort(function(){
- baseHasDuplicate = false;
- return 0;
-});
-
-var Sizzle = function(selector, context, results, seed) {
- results = results || [];
- var origContext = context = context || document;
-
- if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
- return [];
- }
-
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
-
- var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
- soFar = selector;
-
- while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
- soFar = m[3];
-
- parts.push( m[1] );
-
- if ( m[2] ) {
- extra = m[3];
- break;
- }
- }
-
- if ( parts.length > 1 && origPOS.exec( selector ) ) {
- if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
- set = posProcess( parts[0] + parts[1], context );
- } else {
- set = Expr.relative[ parts[0] ] ?
- [ context ] :
- Sizzle( parts.shift(), context );
-
- while ( parts.length ) {
- selector = parts.shift();
-
- if ( Expr.relative[ selector ] )
- selector += parts.shift();
-
- set = posProcess( selector, set );
- }
- }
- } else {
- if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
- Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
- var ret = Sizzle.find( parts.shift(), context, contextXML );
- context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
- }
-
- if ( context ) {
- var ret = seed ?
- { expr: parts.pop(), set: makeArray(seed) } :
- Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
- set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
-
- if ( parts.length > 0 ) {
- checkSet = makeArray(set);
- } else {
- prune = false;
- }
-
- while ( parts.length ) {
- var cur = parts.pop(), pop = cur;
-
- if ( !Expr.relative[ cur ] ) {
- cur = "";
- } else {
- pop = parts.pop();
- }
-
- if ( pop == null ) {
- pop = context;
- }
-
- Expr.relative[ cur ]( checkSet, pop, contextXML );
- }
- } else {
- checkSet = parts = [];
- }
- }
-
- if ( !checkSet ) {
- checkSet = set;
- }
-
- if ( !checkSet ) {
- throw "Syntax error, unrecognized expression: " + (cur || selector);
- }
-
- if ( toString.call(checkSet) === "[object Array]" ) {
- if ( !prune ) {
- results.push.apply( results, checkSet );
- } else if ( context && context.nodeType === 1 ) {
- for ( var i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
- results.push( set[i] );
- }
- }
- } else {
- for ( var i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
- results.push( set[i] );
- }
- }
- }
- } else {
- makeArray( checkSet, results );
- }
-
- if ( extra ) {
- Sizzle( extra, origContext, results, seed );
- Sizzle.uniqueSort( results );
- }
-
- return results;
-};
-
-Sizzle.uniqueSort = function(results){
- if ( sortOrder ) {
- hasDuplicate = baseHasDuplicate;
- results.sort(sortOrder);
-
- if ( hasDuplicate ) {
- for ( var i = 1; i < results.length; i++ ) {
- if ( results[i] === results[i-1] ) {
- results.splice(i--, 1);
- }
- }
- }
- }
-
- return results;
-};
-
-Sizzle.matches = function(expr, set){
- return Sizzle(expr, null, null, set);
-};
-
-Sizzle.find = function(expr, context, isXML){
- var set, match;
-
- if ( !expr ) {
- return [];
- }
-
- for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
- var type = Expr.order[i], match;
-
- if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
- var left = match[1];
- match.splice(1,1);
-
- if ( left.substr( left.length - 1 ) !== "\\" ) {
- match[1] = (match[1] || "").replace(/\\/g, "");
- set = Expr.find[ type ]( match, context, isXML );
- if ( set != null ) {
- expr = expr.replace( Expr.match[ type ], "" );
- break;
- }
- }
- }
- }
-
- if ( !set ) {
- set = context.getElementsByTagName("*");
- }
-
- return {set: set, expr: expr};
-};
-
-Sizzle.filter = function(expr, set, inplace, not){
- var old = expr, result = [], curLoop = set, match, anyFound,
- isXMLFilter = set && set[0] && isXML(set[0]);
-
- while ( expr && set.length ) {
- for ( var type in Expr.filter ) {
- if ( (match = Expr.match[ type ].exec( expr )) != null ) {
- var filter = Expr.filter[ type ], found, item;
- anyFound = false;
-
- if ( curLoop == result ) {
- result = [];
- }
-
- if ( Expr.preFilter[ type ] ) {
- match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
- if ( !match ) {
- anyFound = found = true;
- } else if ( match === true ) {
- continue;
- }
- }
-
- if ( match ) {
- for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
- if ( item ) {
- found = filter( item, match, i, curLoop );
- var pass = not ^ !!found;
-
- if ( inplace && found != null ) {
- if ( pass ) {
- anyFound = true;
- } else {
- curLoop[i] = false;
- }
- } else if ( pass ) {
- result.push( item );
- anyFound = true;
- }
- }
- }
- }
-
- if ( found !== undefined ) {
- if ( !inplace ) {
- curLoop = result;
- }
-
- expr = expr.replace( Expr.match[ type ], "" );
-
- if ( !anyFound ) {
- return [];
- }
-
- break;
- }
- }
- }
-
- if ( expr == old ) {
- if ( anyFound == null ) {
- throw "Syntax error, unrecognized expression: " + expr;
- } else {
- break;
- }
- }
-
- old = expr;
- }
-
- return curLoop;
-};
-
-var Expr = Sizzle.selectors = {
- order: [ "ID", "NAME", "TAG" ],
- match: {
- ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
- CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
- NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
- ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
- TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
- CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
- POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
- PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
- },
- leftMatch: {},
- attrMap: {
- "class": "className",
- "for": "htmlFor"
- },
- attrHandle: {
- href: function(elem){
- return elem.getAttribute("href");
- }
- },
- relative: {
- "+": function(checkSet, part, isXML){
- var isPartStr = typeof part === "string",
- isTag = isPartStr && !/\W/.test(part),
- isPartStrNotTag = isPartStr && !isTag;
-
- if ( isTag && !isXML ) {
- part = part.toUpperCase();
- }
-
- for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
- if ( (elem = checkSet[i]) ) {
- while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
- checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
- elem || false :
- elem === part;
- }
- }
-
- if ( isPartStrNotTag ) {
- Sizzle.filter( part, checkSet, true );
- }
- },
- ">": function(checkSet, part, isXML){
- var isPartStr = typeof part === "string";
-
- if ( isPartStr && !/\W/.test(part) ) {
- part = isXML ? part : part.toUpperCase();
-
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- var parent = elem.parentNode;
- checkSet[i] = parent.nodeName === part ? parent : false;
- }
- }
- } else {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- checkSet[i] = isPartStr ?
- elem.parentNode :
- elem.parentNode === part;
- }
- }
-
- if ( isPartStr ) {
- Sizzle.filter( part, checkSet, true );
- }
- }
- },
- "": function(checkSet, part, isXML){
- var doneName = done++, checkFn = dirCheck;
-
- if ( !/\W/.test(part) ) {
- var nodeCheck = part = isXML ? part : part.toUpperCase();
- checkFn = dirNodeCheck;
- }
-
- checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
- },
- "~": function(checkSet, part, isXML){
- var doneName = done++, checkFn = dirCheck;
-
- if ( typeof part === "string" && !/\W/.test(part) ) {
- var nodeCheck = part = isXML ? part : part.toUpperCase();
- checkFn = dirNodeCheck;
- }
-
- checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
- }
- },
- find: {
- ID: function(match, context, isXML){
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- return m ? [m] : [];
- }
- },
- NAME: function(match, context, isXML){
- if ( typeof context.getElementsByName !== "undefined" ) {
- var ret = [], results = context.getElementsByName(match[1]);
-
- for ( var i = 0, l = results.length; i < l; i++ ) {
- if ( results[i].getAttribute("name") === match[1] ) {
- ret.push( results[i] );
- }
- }
-
- return ret.length === 0 ? null : ret;
- }
- },
- TAG: function(match, context){
- return context.getElementsByTagName(match[1]);
- }
- },
- preFilter: {
- CLASS: function(match, curLoop, inplace, result, not, isXML){
- match = " " + match[1].replace(/\\/g, "") + " ";
-
- if ( isXML ) {
- return match;
- }
-
- for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
- if ( elem ) {
- if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
- if ( !inplace )
- result.push( elem );
- } else if ( inplace ) {
- curLoop[i] = false;
- }
- }
- }
-
- return false;
- },
- ID: function(match){
- return match[1].replace(/\\/g, "");
- },
- TAG: function(match, curLoop){
- for ( var i = 0; curLoop[i] === false; i++ ){}
- return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
- },
- CHILD: function(match){
- if ( match[1] == "nth" ) {
- var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
- match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
- !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
- match[2] = (test[1] + (test[2] || 1)) - 0;
- match[3] = test[3] - 0;
- }
-
- match[0] = done++;
-
- return match;
- },
- ATTR: function(match, curLoop, inplace, result, not, isXML){
- var name = match[1].replace(/\\/g, "");
-
- if ( !isXML && Expr.attrMap[name] ) {
- match[1] = Expr.attrMap[name];
- }
-
- if ( match[2] === "~=" ) {
- match[4] = " " + match[4] + " ";
- }
-
- return match;
- },
- PSEUDO: function(match, curLoop, inplace, result, not){
- if ( match[1] === "not" ) {
- if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
- match[3] = Sizzle(match[3], null, null, curLoop);
- } else {
- var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
- if ( !inplace ) {
- result.push.apply( result, ret );
- }
- return false;
- }
- } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
- return true;
- }
-
- return match;
- },
- POS: function(match){
- match.unshift( true );
- return match;
- }
- },
- filters: {
- enabled: function(elem){
- return elem.disabled === false && elem.type !== "hidden";
- },
- disabled: function(elem){
- return elem.disabled === true;
- },
- checked: function(elem){
- return elem.checked === true;
- },
- selected: function(elem){
- elem.parentNode.selectedIndex;
- return elem.selected === true;
- },
- parent: function(elem){
- return !!elem.firstChild;
- },
- empty: function(elem){
- return !elem.firstChild;
- },
- has: function(elem, i, match){
- return !!Sizzle( match[3], elem ).length;
- },
- header: function(elem){
- return /h\d/i.test( elem.nodeName );
- },
- text: function(elem){
- return "text" === elem.type;
- },
- radio: function(elem){
- return "radio" === elem.type;
- },
- checkbox: function(elem){
- return "checkbox" === elem.type;
- },
- file: function(elem){
- return "file" === elem.type;
- },
- password: function(elem){
- return "password" === elem.type;
- },
- submit: function(elem){
- return "submit" === elem.type;
- },
- image: function(elem){
- return "image" === elem.type;
- },
- reset: function(elem){
- return "reset" === elem.type;
- },
- button: function(elem){
- return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
- },
- input: function(elem){
- return /input|select|textarea|button/i.test(elem.nodeName);
- }
- },
- setFilters: {
- first: function(elem, i){
- return i === 0;
- },
- last: function(elem, i, match, array){
- return i === array.length - 1;
- },
- even: function(elem, i){
- return i % 2 === 0;
- },
- odd: function(elem, i){
- return i % 2 === 1;
- },
- lt: function(elem, i, match){
- return i < match[3] - 0;
- },
- gt: function(elem, i, match){
- return i > match[3] - 0;
- },
- nth: function(elem, i, match){
- return match[3] - 0 == i;
- },
- eq: function(elem, i, match){
- return match[3] - 0 == i;
- }
- },
- filter: {
- PSEUDO: function(elem, match, i, array){
- var name = match[1], filter = Expr.filters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- } else if ( name === "contains" ) {
- return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
- } else if ( name === "not" ) {
- var not = match[3];
-
- for ( var i = 0, l = not.length; i < l; i++ ) {
- if ( not[i] === elem ) {
- return false;
- }
- }
-
- return true;
- }
- },
- CHILD: function(elem, match){
- var type = match[1], node = elem;
- switch (type) {
- case 'only':
- case 'first':
- while ( (node = node.previousSibling) ) {
- if ( node.nodeType === 1 ) return false;
- }
- if ( type == 'first') return true;
- node = elem;
- case 'last':
- while ( (node = node.nextSibling) ) {
- if ( node.nodeType === 1 ) return false;
- }
- return true;
- case 'nth':
- var first = match[2], last = match[3];
-
- if ( first == 1 && last == 0 ) {
- return true;
- }
-
- var doneName = match[0],
- parent = elem.parentNode;
-
- if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
- var count = 0;
- for ( node = parent.firstChild; node; node = node.nextSibling ) {
- if ( node.nodeType === 1 ) {
- node.nodeIndex = ++count;
- }
- }
- parent.sizcache = doneName;
- }
-
- var diff = elem.nodeIndex - last;
- if ( first == 0 ) {
- return diff == 0;
- } else {
- return ( diff % first == 0 && diff / first >= 0 );
- }
- }
- },
- ID: function(elem, match){
- return elem.nodeType === 1 && elem.getAttribute("id") === match;
- },
- TAG: function(elem, match){
- return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
- },
- CLASS: function(elem, match){
- return (" " + (elem.className || elem.getAttribute("class")) + " ")
- .indexOf( match ) > -1;
- },
- ATTR: function(elem, match){
- var name = match[1],
- result = Expr.attrHandle[ name ] ?
- Expr.attrHandle[ name ]( elem ) :
- elem[ name ] != null ?
- elem[ name ] :
- elem.getAttribute( name ),
- value = result + "",
- type = match[2],
- check = match[4];
-
- return result == null ?
- type === "!=" :
- type === "=" ?
- value === check :
- type === "*=" ?
- value.indexOf(check) >= 0 :
- type === "~=" ?
- (" " + value + " ").indexOf(check) >= 0 :
- !check ?
- value && result !== false :
- type === "!=" ?
- value != check :
- type === "^=" ?
- value.indexOf(check) === 0 :
- type === "$=" ?
- value.substr(value.length - check.length) === check :
- type === "|=" ?
- value === check || value.substr(0, check.length + 1) === check + "-" :
- false;
- },
- POS: function(elem, match, i, array){
- var name = match[2], filter = Expr.setFilters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- }
- }
- }
-};
-
-var origPOS = Expr.match.POS;
-
-for ( var type in Expr.match ) {
- Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
- Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
-}
-
-var makeArray = function(array, results) {
- array = Array.prototype.slice.call( array, 0 );
-
- if ( results ) {
- results.push.apply( results, array );
- return results;
- }
-
- return array;
-};
-
-try {
- Array.prototype.slice.call( document.documentElement.childNodes, 0 );
-
-} catch(e){
- makeArray = function(array, results) {
- var ret = results || [];
-
- if ( toString.call(array) === "[object Array]" ) {
- Array.prototype.push.apply( ret, array );
- } else {
- if ( typeof array.length === "number" ) {
- for ( var i = 0, l = array.length; i < l; i++ ) {
- ret.push( array[i] );
- }
- } else {
- for ( var i = 0; array[i]; i++ ) {
- ret.push( array[i] );
- }
- }
- }
-
- return ret;
- };
-}
-
-var sortOrder;
-
-if ( document.documentElement.compareDocumentPosition ) {
- sortOrder = function( a, b ) {
- if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
- if ( a == b ) {
- hasDuplicate = true;
- }
- return 0;
- }
-
- var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
- if ( ret === 0 ) {
- hasDuplicate = true;
- }
- return ret;
- };
-} else if ( "sourceIndex" in document.documentElement ) {
- sortOrder = function( a, b ) {
- if ( !a.sourceIndex || !b.sourceIndex ) {
- if ( a == b ) {
- hasDuplicate = true;
- }
- return 0;
- }
-
- var ret = a.sourceIndex - b.sourceIndex;
- if ( ret === 0 ) {
- hasDuplicate = true;
- }
- return ret;
- };
-} else if ( document.createRange ) {
- sortOrder = function( a, b ) {
- if ( !a.ownerDocument || !b.ownerDocument ) {
- if ( a == b ) {
- hasDuplicate = true;
- }
- return 0;
- }
-
- var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
- aRange.setStart(a, 0);
- aRange.setEnd(a, 0);
- bRange.setStart(b, 0);
- bRange.setEnd(b, 0);
- var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
- if ( ret === 0 ) {
- hasDuplicate = true;
- }
- return ret;
- };
-}
-
-(function(){
- var form = document.createElement("div"),
- id = "script" + (new Date).getTime();
- form.innerHTML = "<a name='" + id + "'/>";
-
- var root = document.documentElement;
- root.insertBefore( form, root.firstChild );
-
- if ( !!document.getElementById( id ) ) {
- Expr.find.ID = function(match, context, isXML){
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
- }
- };
-
- Expr.filter.ID = function(elem, match){
- var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
- return elem.nodeType === 1 && node && node.nodeValue === match;
- };
- }
-
- root.removeChild( form );
- root = form = null; // release memory in IE
-})();
-
-(function(){
-
- var div = document.createElement("div");
- div.appendChild( document.createComment("") );
-
- if ( div.getElementsByTagName("*").length > 0 ) {
- Expr.find.TAG = function(match, context){
- var results = context.getElementsByTagName(match[1]);
-
- if ( match[1] === "*" ) {
- var tmp = [];
-
- for ( var i = 0; results[i]; i++ ) {
- if ( results[i].nodeType === 1 ) {
- tmp.push( results[i] );
- }
- }
-
- results = tmp;
- }
-
- return results;
- };
- }
-
- div.innerHTML = "<a href='#'></a>";
- if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
- div.firstChild.getAttribute("href") !== "#" ) {
- Expr.attrHandle.href = function(elem){
- return elem.getAttribute("href", 2);
- };
- }
-
- div = null; // release memory in IE
-})();
-
-if ( document.querySelectorAll ) (function(){
- var oldSizzle = Sizzle, div = document.createElement("div");
- div.innerHTML = "<p class='TEST'></p>";
-
- if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
- return;
- }
-
- Sizzle = function(query, context, extra, seed){
- context = context || document;
-
- if ( !seed && context.nodeType === 9 && !isXML(context) ) {
- try {
- return makeArray( context.querySelectorAll(query), extra );
- } catch(e){}
- }
-
- return oldSizzle(query, context, extra, seed);
- };
-
- for ( var prop in oldSizzle ) {
- Sizzle[ prop ] = oldSizzle[ prop ];
- }
-
- div = null; // release memory in IE
-})();
-
-if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
- var div = document.createElement("div");
- div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
- if ( div.getElementsByClassName("e").length === 0 )
- return;
-
- div.lastChild.className = "e";
-
- if ( div.getElementsByClassName("e").length === 1 )
- return;
-
- Expr.order.splice(1, 0, "CLASS");
- Expr.find.CLASS = function(match, context, isXML) {
- if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
- return context.getElementsByClassName(match[1]);
- }
- };
-
- div = null; // release memory in IE
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- var sibDir = dir == "previousSibling" && !isXML;
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- if ( sibDir && elem.nodeType === 1 ){
- elem.sizcache = doneName;
- elem.sizset = i;
- }
- elem = elem[dir];
- var match = false;
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 && !isXML ){
- elem.sizcache = doneName;
- elem.sizset = i;
- }
-
- if ( elem.nodeName === cur ) {
- match = elem;
- break;
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- var sibDir = dir == "previousSibling" && !isXML;
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- if ( sibDir && elem.nodeType === 1 ) {
- elem.sizcache = doneName;
- elem.sizset = i;
- }
- elem = elem[dir];
- var match = false;
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 ) {
- if ( !isXML ) {
- elem.sizcache = doneName;
- elem.sizset = i;
- }
- if ( typeof cur !== "string" ) {
- if ( elem === cur ) {
- match = true;
- break;
- }
-
- } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
- match = elem;
- break;
- }
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-var contains = document.compareDocumentPosition ? function(a, b){
- return a.compareDocumentPosition(b) & 16;
-} : function(a, b){
- return a !== b && (a.contains ? a.contains(b) : true);
-};
-
-var isXML = function(elem){
- return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
- !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
-};
-
-var posProcess = function(selector, context){
- var tmpSet = [], later = "", match,
- root = context.nodeType ? [context] : context;
-
- while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
- later += match[0];
- selector = selector.replace( Expr.match.PSEUDO, "" );
- }
-
- selector = Expr.relative[selector] ? selector + "*" : selector;
-
- for ( var i = 0, l = root.length; i < l; i++ ) {
- Sizzle( selector, root[i], tmpSet );
- }
-
- return Sizzle.filter( later, tmpSet );
-};
-
-
-window.Sizzle = Sizzle;
-
-})();
-
-;(function(engine) {
- var extendElements = Prototype.Selector.extendElements;
-
- function select(selector, scope) {
- return extendElements(engine(selector, scope || document));
- }
-
- function match(element, selector) {
- return engine.matches(selector, [element]).length == 1;
- }
-
- Prototype.Selector.engine = engine;
- Prototype.Selector.select = select;
- Prototype.Selector.match = match;
-})(Sizzle);
-
-window.Sizzle = Prototype._original_property;
-delete Prototype._original_property;
-
-var Form = {
- reset: function(form) {
- form = $(form);
- form.reset();
- return form;
- },
-
- serializeElements: function(elements, options) {
- if (typeof options != 'object') options = { hash: !!options };
- else if (Object.isUndefined(options.hash)) options.hash = true;
- var key, value, submitted = false, submit = options.submit, accumulator, initial;
-
- if (options.hash) {
- initial = {};
- accumulator = function(result, key, value) {
- if (key in result) {
- if (!Object.isArray(result[key])) result[key] = [result[key]];
- result[key].push(value);
- } else result[key] = value;
- return result;
- };
- } else {
- initial = '';
- accumulator = function(result, key, value) {
- return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value);
- }
- }
-
- return elements.inject(initial, function(result, element) {
- if (!element.disabled && element.name) {
- key = element.name; value = $(element).getValue();
- if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
- submit !== false && (!submit || key == submit) && (submitted = true)))) {
- result = accumulator(result, key, value);
- }
- }
- return result;
- });
- }
-};
-
-Form.Methods = {
- serialize: function(form, options) {
- return Form.serializeElements(Form.getElements(form), options);
- },
-
- getElements: function(form) {
- var elements = $(form).getElementsByTagName('*'),
- element,
- arr = [ ],
- serializers = Form.Element.Serializers;
- for (var i = 0; element = elements[i]; i++) {
- arr.push(element);
- }
- return arr.inject([], function(elements, child) {
- if (serializers[child.tagName.toLowerCase()])
- elements.push(Element.extend(child));
- return elements;
- })
- },
-
- getInputs: function(form, typeName, name) {
- form = $(form);
- var inputs = form.getElementsByTagName('input');
-
- if (!typeName && !name) return $A(inputs).map(Element.extend);
-
- for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
- var input = inputs[i];
- if ((typeName && input.type != typeName) || (name && input.name != name))
- continue;
- matchingInputs.push(Element.extend(input));
- }
-
- return matchingInputs;
- },
-
- disable: function(form) {
- form = $(form);
- Form.getElements(form).invoke('disable');
- return form;
- },
-
- enable: function(form) {
- form = $(form);
- Form.getElements(form).invoke('enable');
- return form;
- },
-
- findFirstElement: function(form) {
- var elements = $(form).getElements().findAll(function(element) {
- return 'hidden' != element.type && !element.disabled;
- });
- var firstByIndex = elements.findAll(function(element) {
- return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
- }).sortBy(function(element) { return element.tabIndex }).first();
-
- return firstByIndex ? firstByIndex : elements.find(function(element) {
- return /^(?:input|select|textarea)$/i.test(element.tagName);
- });
- },
-
- focusFirstElement: function(form) {
- form = $(form);
- var element = form.findFirstElement();
- if (element) element.activate();
- return form;
- },
-
- request: function(form, options) {
- form = $(form), options = Object.clone(options || { });
-
- var params = options.parameters, action = form.readAttribute('action') || '';
- if (action.blank()) action = window.location.href;
- options.parameters = form.serialize(true);
-
- if (params) {
- if (Object.isString(params)) params = params.toQueryParams();
- Object.extend(options.parameters, params);
- }
-
- if (form.hasAttribute('method') && !options.method)
- options.method = form.method;
-
- return new Ajax.Request(action, options);
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-
-Form.Element = {
- focus: function(element) {
- $(element).focus();
- return element;
- },
-
- select: function(element) {
- $(element).select();
- return element;
- }
-};
-
-Form.Element.Methods = {
-
- serialize: function(element) {
- element = $(element);
- if (!element.disabled && element.name) {
- var value = element.getValue();
- if (value != undefined) {
- var pair = { };
- pair[element.name] = value;
- return Object.toQueryString(pair);
- }
- }
- return '';
- },
-
- getValue: function(element) {
- element = $(element);
- var method = element.tagName.toLowerCase();
- return Form.Element.Serializers[method](element);
- },
-
- setValue: function(element, value) {
- element = $(element);
- var method = element.tagName.toLowerCase();
- Form.Element.Serializers[method](element, value);
- return element;
- },
-
- clear: function(element) {
- $(element).value = '';
- return element;
- },
-
- present: function(element) {
- return $(element).value != '';
- },
-
- activate: function(element) {
- element = $(element);
- try {
- element.focus();
- if (element.select && (element.tagName.toLowerCase() != 'input' ||
- !(/^(?:button|reset|submit)$/i.test(element.type))))
- element.select();
- } catch (e) { }
- return element;
- },
-
- disable: function(element) {
- element = $(element);
- element.disabled = true;
- return element;
- },
-
- enable: function(element) {
- element = $(element);
- element.disabled = false;
- return element;
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-var Field = Form.Element;
-
-var $F = Form.Element.Methods.getValue;
-
-/*--------------------------------------------------------------------------*/
-
-Form.Element.Serializers = (function() {
- function input(element, value) {
- switch (element.type.toLowerCase()) {
- case 'checkbox':
- case 'radio':
- return inputSelector(element, value);
- default:
- return valueSelector(element, value);
- }
- }
-
- function inputSelector(element, value) {
- if (Object.isUndefined(value))
- return element.checked ? element.value : null;
- else element.checked = !!value;
- }
-
- function valueSelector(element, value) {
- if (Object.isUndefined(value)) return element.value;
- else element.value = value;
- }
-
- function select(element, value) {
- if (Object.isUndefined(value))
- return (element.type === 'select-one' ? selectOne : selectMany)(element);
-
- var opt, currentValue, single = !Object.isArray(value);
- for (var i = 0, length = element.length; i < length; i++) {
- opt = element.options[i];
- currentValue = this.optionValue(opt);
- if (single) {
- if (currentValue == value) {
- opt.selected = true;
- return;
- }
- }
- else opt.selected = value.include(currentValue);
- }
- }
-
- function selectOne(element) {
- var index = element.selectedIndex;
- return index >= 0 ? optionValue(element.options[index]) : null;
- }
-
- function selectMany(element) {
- var values, length = element.length;
- if (!length) return null;
-
- for (var i = 0, values = []; i < length; i++) {
- var opt = element.options[i];
- if (opt.selected) values.push(optionValue(opt));
- }
- return values;
- }
-
- function optionValue(opt) {
- return Element.hasAttribute(opt, 'value') ? opt.value : opt.text;
- }
-
- return {
- input: input,
- inputSelector: inputSelector,
- textarea: valueSelector,
- select: select,
- selectOne: selectOne,
- selectMany: selectMany,
- optionValue: optionValue,
- button: valueSelector
- };
-})();
-
-/*--------------------------------------------------------------------------*/
-
-
-Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
- initialize: function($super, element, frequency, callback) {
- $super(callback, frequency);
- this.element = $(element);
- this.lastValue = this.getValue();
- },
-
- execute: function() {
- var value = this.getValue();
- if (Object.isString(this.lastValue) && Object.isString(value) ?
- this.lastValue != value : String(this.lastValue) != String(value)) {
- this.callback(this.element, value);
- this.lastValue = value;
- }
- }
-});
-
-Form.Element.Observer = Class.create(Abstract.TimedObserver, {
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
-});
-
-Form.Observer = Class.create(Abstract.TimedObserver, {
- getValue: function() {
- return Form.serialize(this.element);
- }
-});
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.EventObserver = Class.create({
- 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() {
- Form.getElements(this.element).each(this.registerCallback, this);
- },
-
- registerCallback: function(element) {
- if (element.type) {
- switch (element.type.toLowerCase()) {
- case 'checkbox':
- case 'radio':
- Event.observe(element, 'click', this.onElementEvent.bind(this));
- break;
- default:
- Event.observe(element, 'change', this.onElementEvent.bind(this));
- break;
- }
- }
- }
-});
-
-Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
-});
-
-Form.EventObserver = Class.create(Abstract.EventObserver, {
- getValue: function() {
- return Form.serialize(this.element);
- }
-});
-(function() {
-
- var 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,
- KEY_HOME: 36,
- KEY_END: 35,
- KEY_PAGEUP: 33,
- KEY_PAGEDOWN: 34,
- KEY_INSERT: 45,
-
- cache: {}
- };
-
- var docEl = document.documentElement;
- var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
- && 'onmouseleave' in docEl;
-
-
-
- var isIELegacyEvent = function(event) { return false; };
-
- if (window.attachEvent) {
- if (window.addEventListener) {
- isIELegacyEvent = function(event) {
- return !(event instanceof window.Event);
- };
- } else {
- isIELegacyEvent = function(event) { return true; };
- }
- }
-
- var _isButton;
-
- function _isButtonForDOMEvents(event, code) {
- return event.which ? (event.which === code + 1) : (event.button === code);
- }
-
- var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
- function _isButtonForLegacyEvents(event, code) {
- return event.button === legacyButtonMap[code];
- }
-
- function _isButtonForWebKit(event, code) {
- switch (code) {
- case 0: return event.which == 1 && !event.metaKey;
- case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
- case 2: return event.which == 3;
- default: return false;
- }
- }
-
- if (window.attachEvent) {
- if (!window.addEventListener) {
- _isButton = _isButtonForLegacyEvents;
- } else {
- _isButton = function(event, code) {
- return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
- _isButtonForDOMEvents(event, code);
- }
- }
- } else if (Prototype.Browser.WebKit) {
- _isButton = _isButtonForWebKit;
- } else {
- _isButton = _isButtonForDOMEvents;
- }
-
- function isLeftClick(event) { return _isButton(event, 0) }
-
- function isMiddleClick(event) { return _isButton(event, 1) }
-
- function isRightClick(event) { return _isButton(event, 2) }
-
- function element(event) {
- event = Event.extend(event);
-
- var node = event.target, type = event.type,
- currentTarget = event.currentTarget;
-
- if (currentTarget && currentTarget.tagName) {
- if (type === 'load' || type === 'error' ||
- (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
- && currentTarget.type === 'radio'))
- node = currentTarget;
- }
-
- if (node.nodeType == Node.TEXT_NODE)
- node = node.parentNode;
-
- return Element.extend(node);
- }
-
- function findElement(event, expression) {
- var element = Event.element(event);
-
- if (!expression) return element;
- while (element) {
- if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
- return Element.extend(element);
- }
- element = element.parentNode;
- }
- }
-
- function pointer(event) {
- return { x: pointerX(event), y: pointerY(event) };
- }
-
- function pointerX(event) {
- var docElement = document.documentElement,
- body = document.body || { scrollLeft: 0 };
-
- return event.pageX || (event.clientX +
- (docElement.scrollLeft || body.scrollLeft) -
- (docElement.clientLeft || 0));
- }
-
- function pointerY(event) {
- var docElement = document.documentElement,
- body = document.body || { scrollTop: 0 };
-
- return event.pageY || (event.clientY +
- (docElement.scrollTop || body.scrollTop) -
- (docElement.clientTop || 0));
- }
-
-
- function stop(event) {
- Event.extend(event);
- event.preventDefault();
- event.stopPropagation();
-
- event.stopped = true;
- }
-
-
- Event.Methods = {
- isLeftClick: isLeftClick,
- isMiddleClick: isMiddleClick,
- isRightClick: isRightClick,
-
- element: element,
- findElement: findElement,
-
- pointer: pointer,
- pointerX: pointerX,
- pointerY: pointerY,
-
- stop: stop
- };
-
- var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
- m[name] = Event.Methods[name].methodize();
- return m;
- });
-
- if (window.attachEvent) {
- function _relatedTarget(event) {
- var element;
- switch (event.type) {
- case 'mouseover':
- case 'mouseenter':
- element = event.fromElement;
- break;
- case 'mouseout':
- case 'mouseleave':
- element = event.toElement;
- break;
- default:
- return null;
- }
- return Element.extend(element);
- }
-
- var additionalMethods = {
- stopPropagation: function() { this.cancelBubble = true },
- preventDefault: function() { this.returnValue = false },
- inspect: function() { return '[object Event]' }
- };
-
- Event.extend = function(event, element) {
- if (!event) return false;
-
- if (!isIELegacyEvent(event)) return event;
-
- if (event._extendedByPrototype) return event;
- event._extendedByPrototype = Prototype.emptyFunction;
-
- var pointer = Event.pointer(event);
-
- Object.extend(event, {
- target: event.srcElement || element,
- relatedTarget: _relatedTarget(event),
- pageX: pointer.x,
- pageY: pointer.y
- });
-
- Object.extend(event, methods);
- Object.extend(event, additionalMethods);
-
- return event;
- };
- } else {
- Event.extend = Prototype.K;
- }
-
- if (window.addEventListener) {
- Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
- Object.extend(Event.prototype, methods);
- }
-
- function _createResponder(element, eventName, handler) {
- var registry = Element.retrieve(element, 'prototype_event_registry');
-
- if (Object.isUndefined(registry)) {
- CACHE.push(element);
- registry = Element.retrieve(element, 'prototype_event_registry', $H());
- }
-
- var respondersForEvent = registry.get(eventName);
- if (Object.isUndefined(respondersForEvent)) {
- respondersForEvent = [];
- registry.set(eventName, respondersForEvent);
- }
-
- if (respondersForEvent.pluck('handler').include(handler)) return false;
-
- var responder;
- if (eventName.include(":")) {
- responder = function(event) {
- if (Object.isUndefined(event.eventName))
- return false;
-
- if (event.eventName !== eventName)
- return false;
-
- Event.extend(event, element);
- handler.call(element, event);
- };
- } else {
- if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
- (eventName === "mouseenter" || eventName === "mouseleave")) {
- if (eventName === "mouseenter" || eventName === "mouseleave") {
- responder = function(event) {
- Event.extend(event, element);
-
- var parent = event.relatedTarget;
- while (parent && parent !== element) {
- try { parent = parent.parentNode; }
- catch(e) { parent = element; }
- }
-
- if (parent === element) return;
-
- handler.call(element, event);
- };
- }
- } else {
- responder = function(event) {
- Event.extend(event, element);
- handler.call(element, event);
- };
- }
- }
-
- responder.handler = handler;
- respondersForEvent.push(responder);
- return responder;
- }
-
- function _destroyCache() {
- for (var i = 0, length = CACHE.length; i < length; i++) {
- Event.stopObserving(CACHE[i]);
- CACHE[i] = null;
- }
- }
-
- var CACHE = [];
-
- if (Prototype.Browser.IE)
- window.attachEvent('onunload', _destroyCache);
-
- if (Prototype.Browser.WebKit)
- window.addEventListener('unload', Prototype.emptyFunction, false);
-
-
- var _getDOMEventName = Prototype.K,
- translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
-
- if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
- _getDOMEventName = function(eventName) {
- return (translations[eventName] || eventName);
- };
- }
-
- function observe(element, eventName, handler) {
- element = $(element);
-
- var responder = _createResponder(element, eventName, handler);
-
- if (!responder) return element;
-
- if (eventName.include(':')) {
- if (element.addEventListener)
- element.addEventListener("dataavailable", responder, false);
- else {
- element.attachEvent("ondataavailable", responder);
- element.attachEvent("onlosecapture", responder);
- }
- } else {
- var actualEventName = _getDOMEventName(eventName);
-
- if (element.addEventListener)
- element.addEventListener(actualEventName, responder, false);
- else
- element.attachEvent("on" + actualEventName, responder);
- }
-
- return element;
- }
-
- function stopObserving(element, eventName, handler) {
- element = $(element);
-
- var registry = Element.retrieve(element, 'prototype_event_registry');
- if (!registry) return element;
-
- if (!eventName) {
- registry.each( function(pair) {
- var eventName = pair.key;
- stopObserving(element, eventName);
- });
- return element;
- }
-
- var responders = registry.get(eventName);
- if (!responders) return element;
-
- if (!handler) {
- responders.each(function(r) {
- stopObserving(element, eventName, r.handler);
- });
- return element;
- }
-
- var i = responders.length, responder;
- while (i--) {
- if (responders[i].handler === handler) {
- responder = responders[i];
- break;
- }
- }
- if (!responder) return element;
-
- if (eventName.include(':')) {
- if (element.removeEventListener)
- element.removeEventListener("dataavailable", responder, false);
- else {
- element.detachEvent("ondataavailable", responder);
- element.detachEvent("onlosecapture", responder);
- }
- } else {
- var actualEventName = _getDOMEventName(eventName);
- if (element.removeEventListener)
- element.removeEventListener(actualEventName, responder, false);
- else
- element.detachEvent('on' + actualEventName, responder);
- }
-
- registry.set(eventName, responders.without(responder));
-
- return element;
- }
-
- function fire(element, eventName, memo, bubble) {
- element = $(element);
-
- if (Object.isUndefined(bubble))
- bubble = true;
-
- if (element == document && document.createEvent && !element.dispatchEvent)
- element = document.documentElement;
-
- var event;
- if (document.createEvent) {
- event = document.createEvent('HTMLEvents');
- event.initEvent('dataavailable', bubble, true);
- } else {
- event = document.createEventObject();
- event.eventType = bubble ? 'ondataavailable' : 'onlosecapture';
- }
-
- event.eventName = eventName;
- event.memo = memo || { };
-
- if (document.createEvent)
- element.dispatchEvent(event);
- else
- element.fireEvent(event.eventType, event);
-
- return Event.extend(event);
- }
-
- Event.Handler = Class.create({
- initialize: function(element, eventName, selector, callback) {
- this.element = $(element);
- this.eventName = eventName;
- this.selector = selector;
- this.callback = callback;
- this.handler = this.handleEvent.bind(this);
- },
-
- start: function() {
- Event.observe(this.element, this.eventName, this.handler);
- return this;
- },
-
- stop: function() {
- Event.stopObserving(this.element, this.eventName, this.handler);
- return this;
- },
-
- handleEvent: function(event) {
- var element = Event.findElement(event, this.selector);
- if (element) this.callback.call(this.element, event, element);
- }
- });
-
- function on(element, eventName, selector, callback) {
- element = $(element);
- if (Object.isFunction(selector) && Object.isUndefined(callback)) {
- callback = selector, selector = null;
- }
-
- return new Event.Handler(element, eventName, selector, callback).start();
- }
-
- Object.extend(Event, Event.Methods);
-
- Object.extend(Event, {
- fire: fire,
- observe: observe,
- stopObserving: stopObserving,
- on: on
- });
-
- Element.addMethods({
- fire: fire,
-
- observe: observe,
-
- stopObserving: stopObserving,
-
- on: on
- });
-
- Object.extend(document, {
- fire: fire.methodize(),
-
- observe: observe.methodize(),
-
- stopObserving: stopObserving.methodize(),
-
- on: on.methodize(),
-
- loaded: false
- });
-
- if (window.Event) Object.extend(window.Event, Event);
- else window.Event = Event;
-})();
-
-(function() {
- /* Support for the DOMContentLoaded event is based on work by Dan Webb,
- Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
-
- var timer;
-
- function fireContentLoadedEvent() {
- if (document.loaded) return;
- if (timer) window.clearTimeout(timer);
- document.loaded = true;
- document.fire('dom:loaded');
- }
-
- function checkReadyState() {
- if (document.readyState === 'complete') {
- document.stopObserving('readystatechange', checkReadyState);
- fireContentLoadedEvent();
- }
- }
-
- function pollDoScroll() {
- try { document.documentElement.doScroll('left'); }
- catch(e) {
- timer = pollDoScroll.defer();
- return;
- }
- fireContentLoadedEvent();
- }
-
- if (document.addEventListener) {
- document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
- } else {
- document.observe('readystatechange', checkReadyState);
- if (window == top)
- timer = pollDoScroll.defer();
- }
-
- Event.observe(window, 'load', fireContentLoadedEvent);
-})();
-
-Element.addMethods();
-
-/*------------------------------- DEPRECATED -------------------------------*/
-
-Hash.toQueryString = Object.toQueryString;
-
-var Toggle = { display: Element.toggle };
-
-Element.Methods.childOf = Element.Methods.descendantOf;
-
-var Insertion = {
- Before: function(element, content) {
- return Element.insert(element, {before:content});
- },
-
- Top: function(element, content) {
- return Element.insert(element, {top:content});
- },
-
- Bottom: function(element, content) {
- return Element.insert(element, {bottom:content});
- },
-
- After: function(element, content) {
- return Element.insert(element, {after:content});
- }
-};
-
-var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
-
-var Position = {
- includeScrollOffsets: false,
-
- prepare: function() {
- this.deltaX = window.pageXOffset
- || document.documentElement.scrollLeft
- || document.body.scrollLeft
- || 0;
- this.deltaY = window.pageYOffset
- || document.documentElement.scrollTop
- || document.body.scrollTop
- || 0;
- },
-
- within: function(element, x, y) {
- if (this.includeScrollOffsets)
- return this.withinIncludingScrolloffsets(element, x, y);
- this.xcomp = x;
- this.ycomp = y;
- this.offset = Element.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 = Element.cumulativeScrollOffset(element);
-
- this.xcomp = x + offsetcache[0] - this.deltaX;
- this.ycomp = y + offsetcache[1] - this.deltaY;
- this.offset = Element.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);
- },
-
- 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;
- },
-
-
- cumulativeOffset: Element.Methods.cumulativeOffset,
-
- positionedOffset: Element.Methods.positionedOffset,
-
- absolutize: function(element) {
- Position.prepare();
- return Element.absolutize(element);
- },
-
- relativize: function(element) {
- Position.prepare();
- return Element.relativize(element);
- },
-
- realOffset: Element.Methods.cumulativeScrollOffset,
-
- offsetParent: Element.Methods.getOffsetParent,
-
- page: Element.Methods.viewportOffset,
-
- clone: function(source, target, options) {
- options = options || { };
- return Element.clonePosition(target, source, options);
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
- function iter(name) {
- return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
- }
-
- instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
- function(element, className) {
- className = className.toString().strip();
- var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
- return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
- } : function(element, className) {
- className = className.toString().strip();
- var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
- if (!classNames && !className) return elements;
-
- var nodes = $(element).getElementsByTagName('*');
- className = ' ' + className + ' ';
-
- for (var i = 0, child, cn; child = nodes[i]; i++) {
- if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
- (classNames && classNames.all(function(name) {
- return !name.toString().blank() && cn.include(' ' + name + ' ');
- }))))
- elements.push(Element.extend(child));
- }
- return elements;
- };
-
- return function(className, parentElement) {
- return $(parentElement || document.body).getElementsByClassName(className);
- };
-}(Element.Methods);
-
-/*--------------------------------------------------------------------------*/
-
-Element.ClassNames = Class.create();
-Element.ClassNames.prototype = {
- initialize: function(element) {
- this.element = $(element);
- },
-
- _each: function(iterator) {
- this.element.className.split(/\s+/).select(function(name) {
- return name.length > 0;
- })._each(iterator);
- },
-
- set: function(className) {
- this.element.className = className;
- },
-
- add: function(classNameToAdd) {
- if (this.include(classNameToAdd)) return;
- this.set($A(this).concat(classNameToAdd).join(' '));
- },
-
- remove: function(classNameToRemove) {
- if (!this.include(classNameToRemove)) return;
- this.set($A(this).without(classNameToRemove).join(' '));
- },
-
- toString: function() {
- return $A(this).join(' ');
- }
-};
-
-Object.extend(Element.ClassNames.prototype, Enumerable);
-
-/*--------------------------------------------------------------------------*/
-
-(function() {
- window.Selector = Class.create({
- initialize: function(expression) {
- this.expression = expression.strip();
- },
-
- findElements: function(rootElement) {
- return Prototype.Selector.select(this.expression, rootElement);
- },
-
- match: function(element) {
- return Prototype.Selector.match(element, this.expression);
- },
-
- toString: function() {
- return this.expression;
- },
-
- inspect: function() {
- return "#<Selector: " + this.expression + ">";
- }
- });
-
- Object.extend(Selector, {
- matchElements: function(elements, expression) {
- var match = Prototype.Selector.match,
- results = [];
-
- for (var i = 0, length = elements.length; i < length; i++) {
- var element = elements[i];
- if (match(element, expression)) {
- results.push(Element.extend(element));
- }
- }
- return results;
- },
-
- findElement: function(elements, expression, index) {
- index = index || 0;
- var matchIndex = 0, element;
- for (var i = 0, length = elements.length; i < length; i++) {
- element = elements[i];
- if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
- return Element.extend(element);
- }
- }
- },
-
- findChildElements: function(element, expressions) {
- var selector = expressions.toArray().join(', ');
- return Prototype.Selector.select(selector, element || document);
- }
- });
-})();
diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js
deleted file mode 100644
index 2cd1220786..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js
+++ /dev/null
@@ -1,208 +0,0 @@
-(function() {
- // Technique from Juriy Zaytsev
- // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
- function isEventSupported(eventName) {
- var el = document.createElement('div');
- eventName = 'on' + eventName;
- var isSupported = (eventName in el);
- if (!isSupported) {
- el.setAttribute(eventName, 'return;');
- isSupported = typeof el[eventName] == 'function';
- }
- el = null;
- return isSupported;
- }
-
- function isForm(element) {
- return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM';
- }
-
- function isInput(element) {
- if (Object.isElement(element)) {
- var name = element.nodeName.toUpperCase();
- return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA';
- }
- else return false;
- }
-
- var submitBubbles = isEventSupported('submit'),
- changeBubbles = isEventSupported('change');
-
- if (!submitBubbles || !changeBubbles) {
- // augment the Event.Handler class to observe custom events when needed
- Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
- function(init, element, eventName, selector, callback) {
- init(element, eventName, selector, callback);
- // is the handler being attached to an element that doesn't support this event?
- if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
- (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
- // "submit" => "emulated:submit"
- this.eventName = 'emulated:' + this.eventName;
- }
- }
- );
- }
-
- if (!submitBubbles) {
- // discover forms on the page by observing focus events which always bubble
- document.on('focusin', 'form', function(focusEvent, form) {
- // special handler for the real "submit" event (one-time operation)
- if (!form.retrieve('emulated:submit')) {
- form.on('submit', function(submitEvent) {
- var emulated = form.fire('emulated:submit', submitEvent, true);
- // if custom event received preventDefault, cancel the real one too
- if (emulated.returnValue === false) submitEvent.preventDefault();
- });
- form.store('emulated:submit', true);
- }
- });
- }
-
- if (!changeBubbles) {
- // discover form inputs on the page
- document.on('focusin', 'input, select, textarea', function(focusEvent, input) {
- // special handler for real "change" events
- if (!input.retrieve('emulated:change')) {
- input.on('change', function(changeEvent) {
- input.fire('emulated:change', changeEvent, true);
- });
- input.store('emulated:change', true);
- }
- });
- }
-
- function handleRemote(element) {
- var method, url, params;
-
- var event = element.fire("ajax:before");
- if (event.stopped) return false;
-
- if (element.tagName.toLowerCase() === 'form') {
- method = element.readAttribute('method') || 'post';
- url = element.readAttribute('action');
- // serialize the form with respect to the submit button that was pressed
- params = element.serialize({ submit: element.retrieve('rails:submit-button') });
- // clear the pressed submit button information
- element.store('rails:submit-button', null);
- } else {
- method = element.readAttribute('data-method') || 'get';
- url = element.readAttribute('href');
- params = {};
- }
-
- new Ajax.Request(url, {
- method: method,
- parameters: params,
- evalScripts: true,
-
- onCreate: function(response) { element.fire("ajax:create", response); },
- onComplete: function(response) { element.fire("ajax:complete", response); },
- onSuccess: function(response) { element.fire("ajax:success", response); },
- onFailure: function(response) { element.fire("ajax:failure", response); }
- });
-
- element.fire("ajax:after");
- }
-
- function insertHiddenField(form, name, value) {
- form.insert(new Element('input', { type: 'hidden', name: name, value: value }));
- }
-
- function handleMethod(element) {
- var method = element.readAttribute('data-method'),
- url = element.readAttribute('href'),
- csrf_param = $$('meta[name=csrf-param]')[0],
- csrf_token = $$('meta[name=csrf-token]')[0];
-
- var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
- $(element.parentNode).insert(form);
-
- if (method !== 'post') {
- insertHiddenField(form, '_method', method);
- }
-
- if (csrf_param) {
- insertHiddenField(form, csrf_param.readAttribute('content'), csrf_token.readAttribute('content'));
- }
-
- form.submit();
- }
-
- function disableFormElements(form) {
- form.select('input[type=submit][data-disable-with]').each(function(input) {
- input.store('rails:original-value', input.getValue());
- input.setValue(input.readAttribute('data-disable-with')).disable();
- });
- }
-
- function enableFormElements(form) {
- form.select('input[type=submit][data-disable-with]').each(function(input) {
- input.setValue(input.retrieve('rails:original-value')).enable();
- });
- }
-
- function allowAction(element) {
- var message = element.readAttribute('data-confirm');
- return !message || confirm(message);
- }
-
- document.on('click', 'a[data-confirm], a[data-remote], a[data-method]', function(event, link) {
- if (!allowAction(link)) {
- event.stop();
- return false;
- }
-
- if (link.readAttribute('data-remote')) {
- handleRemote(link);
- event.stop();
- } else if (link.readAttribute('data-method')) {
- handleMethod(link);
- event.stop();
- }
- });
-
- document.on("click", "form input[type=submit], form button[type=submit], form button:not([type])", function(event, button) {
- // register the pressed submit button
- event.findElement('form').store('rails:submit-button', button.name || false);
- });
-
- document.on("submit", function(event) {
- var form = event.findElement();
-
- if (!allowAction(form)) {
- event.stop();
- return false;
- }
-
- if (form.readAttribute('data-remote')) {
- handleRemote(form);
- event.stop();
- } else {
- disableFormElements(form);
- }
- });
-
- document.on('ajax:create', 'form', function(event, form) {
- if (form == event.findElement()) disableFormElements(form);
- });
-
- document.on('ajax:complete', 'form', function(event, form) {
- if (form == event.findElement()) enableFormElements(form);
- });
-
- Ajax.Responders.register({
- onCreate: function(request) {
- var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
-
- if (csrf_meta_tag) {
- var header = 'X-CSRF-Token',
- token = csrf_meta_tag.readAttribute('content');
-
- if (!request.options.requestHeaders) {
- request.options.requestHeaders = {};
- }
- request.options.requestHeaders[header] = token;
- }
- }
- });
-})();
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 126aadb88d..6201595308 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -119,19 +119,6 @@ task :default => :test
if mountable?
copy_file "#{app_templates_dir}/app/assets/javascripts/application.js.tt",
"app/assets/javascripts/application.js"
- copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}.js",
- "vendor/assets/javascripts/#{options[:javascript]}.js"
- copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}_ujs.js",
- "vendor/assets/javascripts/#{options[:javascript]}_ujs.js"
-
- if options[:javascript] == "prototype"
- copy_file "#{app_templates_dir}/vendor/assets/javascripts/controls.js",
- "vendor/assets/javascripts/controls.js"
- copy_file "#{app_templates_dir}/vendor/assets/javascripts/dragdrop.js",
- "vendor/assets/javascripts/dragdrop.js"
- copy_file "#{app_templates_dir}/vendor/assets/javascripts/effects.js",
- "vendor/assets/javascripts/effects.js"
- end
elsif full?
empty_directory_with_gitkeep "app/assets/javascripts"
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index d15887f561..5d217dcb10 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -31,23 +31,23 @@ module Rails
# This means we can get a Path object back like below:
#
# path = root["app/controllers"]
- # path.eager_load? #=> true
- # path.is_a?(Rails::Paths::Path) #=> true
+ # path.eager_load? # => true
+ # path.is_a?(Rails::Paths::Path) # => true
#
# The Path object is simply an array and allows you to easily add extra paths:
#
- # path.is_a?(Array) #=> true
- # path.inspect #=> ["app/controllers"]
+ # path.is_a?(Array) # => true
+ # path.inspect # => ["app/controllers"]
#
# path << "lib/controllers"
- # path.inspect #=> ["app/controllers", "lib/controllers"]
+ # path.inspect # => ["app/controllers", "lib/controllers"]
#
# Notice that when you add a path using #add, the path object created already
# contains the path with the same path value given to #add. In some situations,
# you may not want this behavior, so you can give :with as option.
#
# root.add "config/routes", :with => "config/routes.rb"
- # root["config/routes"].inspect #=> ["config/routes.rb"]
+ # root["config/routes"].inspect # => ["config/routes.rb"]
#
# #add also accepts the following options as argument: eager_load, autoload,
# autoload_once and glob.
@@ -58,8 +58,8 @@ module Rails
# root.path = "/rails"
# root.add "app/controllers"
#
- # root["app/controllers"].expanded #=> ["/rails/app/controllers"]
- # root["app/controllers"].existent #=> ["/rails/app/controllers"]
+ # root["app/controllers"].expanded # => ["/rails/app/controllers"]
+ # root["app/controllers"].existent # => ["/rails/app/controllers"]
#
# Check the Path documentation for more information.
class Root < ::Hash
@@ -156,17 +156,17 @@ module Rails
%w(autoload_once eager_load autoload load_path).each do |m|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{m}!
- @#{m} = true
- end
-
- def skip_#{m}!
- @#{m} = false
- end
-
- def #{m}?
- @#{m}
- end
+ def #{m}! # def eager_load!
+ @#{m} = true # @eager_load = true
+ end # end
+ #
+ def skip_#{m}! # def skip_eager_load!
+ @#{m} = false # @eager_load = false
+ end # end
+ #
+ def #{m}? # def eager_load?
+ @#{m} # @eager_load
+ end # end
RUBY
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 7fca565124..b183eb8ddd 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -173,8 +173,8 @@ module Rails
def eager_load!
end
- def load_console
- self.class.console.each(&:call)
+ def load_console(sandbox=false)
+ self.class.console.each { |block| block.call(sandbox) }
end
def load_tasks
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 02e22361e0..a0c953967c 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -16,7 +16,8 @@ task :routes => :environment do
{:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs}
end
- routes.reject! { |r| r[:path] =~ %r{/rails/info/properties} } # Skip the route if it's internal info route
+ # Skip the route if it's internal info route
+ routes.reject! { |r| r[:path] =~ %r{/rails/info/properties|^/assets} }
name_width = routes.map{ |r| r[:name].length }.max
verb_width = routes.map{ |r| r[:verb].length }.max
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index b076881c21..fc6c0a0204 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index cd0646b8ed..4404838670 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '~> 0.14.4')
+ s.add_dependency('thor', '~> 0.14.6')
s.add_dependency('rack-ssl', '~> 1.3.2')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index ab3eb4c9e7..6193e72625 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -12,7 +12,6 @@ end
class ::MyOtherMailObserver < ::MyMailObserver; end
-
module ApplicationTests
class ConfigurationTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -432,5 +431,61 @@ module ApplicationTests
get "/"
assert_equal 'true', last_response.body
end
+
+ test "config.action_controller.wrap_parameters is set in ActionController::Base" do
+ app_file 'config/initializers/wrap_parameters.rb', <<-RUBY
+ ActionController::Base.wrap_parameters :format => [:json]
+ RUBY
+
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ def self.column_names
+ %w(title)
+ end
+ end
+ RUBY
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render :text => params[:post].inspect
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.append do
+ resources :posts
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "rack/test"
+ extend Rack::Test::Methods
+
+ post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json"
+ assert_equal '{"title"=>"foo"}', last_response.body
+ end
+
+ test "config.action_dispatch.ignore_accept_header" do
+ make_basic_app do |app|
+ app.config.action_dispatch.ignore_accept_header = true
+ end
+
+ class ::OmgController < ActionController::Base
+ def index
+ respond_to do |format|
+ format.html { render :text => "HTML" }
+ format.xml { render :text => "XML" }
+ end
+ end
+ end
+
+ get "/", {}, "HTTP_ACCEPT" => "application/xml"
+ assert_equal 'HTML', last_response.body
+
+ get "/", { :format => :xml }, "HTTP_ACCEPT" => "application/xml"
+ assert_equal 'XML', last_response.body
+ end
end
end
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 793e73556c..5ae6323345 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -8,9 +8,9 @@ class ConsoleTest < Test::Unit::TestCase
boot_rails
end
- def load_environment
+ def load_environment(sandbox = false)
require "#{rails_root}/config/environment"
- Rails.application.load_console
+ Rails.application.load_console(sandbox)
end
def test_app_method_should_return_integration_session
@@ -73,6 +73,20 @@ class ConsoleTest < Test::Unit::TestCase
helper.truncate('Once upon a time in a world far far away')
end
+ def test_with_sandbox
+ require 'rails/all'
+ value = false
+
+ Class.new(Rails::Railtie) do
+ console do |sandbox|
+ value = sandbox
+ end
+ end
+
+ load_environment(true)
+ assert value
+ end
+
def test_active_record_does_not_panic_when_referencing_an_observed_constant
add_to_config "config.active_record.observers = :user_observer"
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 59e5ef4dee..d77c2d14ab 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -64,6 +64,28 @@ module ApplicationTests
assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` }
end
+ def test_rake_routes_shows_custom_assets
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/custom/assets', :to => 'custom_assets#show'
+ end
+ RUBY
+ assert_match 'custom_assets GET /custom/assets(.:format)', Dir.chdir(app_path){ `rake routes` }
+ end
+
+ def test_logger_is_flushed_when_exiting_production_rake_tasks
+ add_to_config <<-RUBY
+ rake_tasks do
+ task :log_something => :environment do
+ Rails.logger.error("Sample log message")
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `rake log_something RAILS_ENV=production && cat log/production.log` }
+ assert_match "Sample log message", output
+ end
+
def test_model_and_migration_generator_with_change_syntax
Dir.chdir(app_path) do
`rails generate model user username:string password:string`
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 1fbbb40132..f96319f472 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -65,6 +65,31 @@ module ApplicationTests
run_test 'integration/posts_test.rb'
end
+ test "performance test" do
+ controller 'posts', <<-RUBY
+ class PostsController < ActionController::Base
+ end
+ RUBY
+
+ app_file 'app/views/posts/index.html.erb', <<-HTML
+ Posts#index
+ HTML
+
+ app_file 'test/performance/posts_test.rb', <<-RUBY
+ require 'test_helper'
+ require 'rails/performance_test_help'
+
+ class PostsTest < ActionDispatch::PerformanceTest
+ def test_index
+ get '/posts'
+ assert_response :success
+ end
+ end
+ RUBY
+
+ run_test 'performance/posts_test.rb'
+ end
+
private
def run_test(name)
result = ruby '-Itest', "#{app_path}/test/#{name}"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 1902484301..cc0bd53639 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -39,6 +39,8 @@ DEFAULT_APP_FILES = %w(
class AppGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments [destination_root]
+
+ # brings setup, teardown, and some tests
include SharedGeneratorTests
def default_files
@@ -135,6 +137,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator([destination_root, "-d", "jdbcmysql"])
assert_file "config/database.yml", /jdbcmysql/
assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ assert_file "Gemfile", /^gem\s+["']jruby-openssl["']$/ if defined?(JRUBY_VERSION) && JRUBY_VERSION < "1.6"
end
def test_config_jdbcsqlite3_database
@@ -163,40 +166,41 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
- def test_jquery_and_test_unit_are_added_by_default
+ def test_creation_of_a_test_directory
run_generator
- assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
- assert_file "app/assets/javascripts/application.js"
- assert_file "vendor/assets/javascripts/jquery.js"
- assert_file "vendor/assets/javascripts/jquery_ujs.js"
- assert_file "test"
+ assert_file 'test'
end
- def test_javascript_is_skipped_if_required
- run_generator [destination_root, "--skip-javascript"]
- assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/
- assert_file "app/assets/javascripts/application.js"
- assert_no_file "vendor/assets/javascripts/jquery.js"
- assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
+ def test_jquery_is_the_default_javascript_library
+ run_generator
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype prototype_ujs\)/
+ assert_file "app/assets/javascripts/application.js" do |contents|
+ assert_match %r{^//= require jquery}, contents
+ assert_match %r{^//= require jquery_ujs}, contents
+ end
+ assert_file 'Gemfile' do |contents|
+ assert_match /^gem 'jquery-rails'/, contents
+ end
end
- def test_config_prototype_javascript_library
- run_generator [destination_root, "-j", "prototype"]
- assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
- assert_file "app/assets/javascripts/application.js"
- assert_file "vendor/assets/javascripts/prototype.js"
- assert_file "vendor/assets/javascripts/effects.js"
- assert_file "vendor/assets/javascripts/dragdrop.js"
- assert_file "vendor/assets/javascripts/controls.js"
- assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/
+ def test_other_javascript_libraries
+ run_generator [destination_root, '-j', 'prototype']
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype prototype_ujs\)/
+ assert_file "app/assets/javascripts/application.js" do |contents|
+ assert_match %r{^//= require prototype}, contents
+ assert_match %r{^//= require prototype_ujs}, contents
+ end
+ assert_file 'Gemfile' do |contents|
+ assert_match /^gem 'prototype-rails'/, contents
+ end
end
- def test_config_jquery_javascript_library
- run_generator [destination_root, "-j", "jquery"]
- assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/
- assert_file "app/assets/javascripts/application.js"
- assert_file "vendor/assets/javascripts/jquery.js"
- assert_file "vendor/assets/javascripts/jquery_ujs.js", /jQuery/
+ def test_javascript_is_skipped_if_required
+ run_generator [destination_root, "--skip-javascript"]
+ assert_file "config/application.rb", /^\s+# config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/
+ assert_file "app/assets/javascripts/application.js" do |contents|
+ assert_no_match %r{^//=\s+require\s}, contents
+ end
end
def test_template_from_dir_pwd
@@ -225,9 +229,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given
run_generator [destination_root, "--skip-test-unit"]
- assert_file "config/application.rb" do |file|
- assert_match /config.generators.test_framework = false/, file
- end
+ assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
+ end
+
+ def test_no_active_record_or_test_unit_if_skips_given
+ run_generator [destination_root, "--skip-test-unit", "--skip-active-record"]
+ assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
+ assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
def test_new_hash_style
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index fb956a8335..f637a6a17e 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -22,6 +22,8 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
destination File.join(Rails.root, "tmp/bukkits")
arguments [destination_root]
+
+ # brings setup, teardown, and some tests
include SharedGeneratorTests
def default_files
@@ -103,8 +105,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_javascripts_generation
run_generator [destination_root, "--mountable"]
assert_file "app/assets/javascripts/application.js"
- assert_file "vendor/assets/javascripts/jquery.js"
- assert_file "vendor/assets/javascripts/jquery_ujs.js"
end
def test_skip_javascripts
@@ -114,16 +114,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "vendor/assets/javascripts/jquery_ujs.js"
end
- def test_config_prototype_javascript_library
- run_generator [destination_root, "-j", "prototype", "--mountable"]
- assert_file "app/assets/javascripts/application.js"
- assert_file "vendor/assets/javascripts/prototype.js"
- assert_file "vendor/assets/javascripts/effects.js"
- assert_file "vendor/assets/javascripts/dragdrop.js"
- assert_file "vendor/assets/javascripts/controls.js"
- assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/
- end
-
def test_template_from_dir_pwd
FileUtils.cd(Rails.root)
assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index c9c5d2fad2..03fd64ca38 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -1,3 +1,6 @@
+#
+# Tests, setup, and teardown common to the application and plugin generator suites.
+#
module SharedGeneratorTests
def setup
Rails.application = TestApp::Application
diff --git a/version.rb b/version.rb
index b076881c21..fc6c0a0204 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end