aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Gemfile4
-rw-r--r--README.rdoc16
-rwxr-xr-xRakefile16
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb13
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb204
-rw-r--r--actionpack/lib/action_controller/test_case.rb4
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb2
-rw-r--r--actionpack/lib/action_view.rb3
-rw-r--r--actionpack/lib/action_view/base.rb34
-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.rb2
-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/form_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/rendering_helper.rb92
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb4
-rw-r--r--actionpack/lib/action_view/partials.rb226
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb6
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb294
-rw-r--r--actionpack/lib/action_view/renderer/renderer.rb46
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb62
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb6
-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.rb16
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb54
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb13
-rw-r--r--actionpack/test/controller/test_test.rb12
-rw-r--r--actionpack/test/template/capture_helper_test.rb10
-rw-r--r--actionpack/test/template/form_helper_test.rb12
-rw-r--r--actionpack/test/template/log_subscriber_test.rb7
-rw-r--r--actionpack/test/template/number_helper_test.rb11
-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/url_helper_test.rb2
-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.rb104
-rw-r--r--activemodel/lib/active_model/observing.rb34
-rw-r--r--activemodel/lib/active_model/translation.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb4
-rw-r--r--activemodel/test/cases/observer_array_test.rb220
-rw-r--r--activemodel/test/cases/observing_test.rb5
-rw-r--r--activemodel/test/cases/validations_test.rb14
-rw-r--r--activemodel/test/models/observers.rb27
-rw-r--r--activerecord/activerecord.gemspec2
-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/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb101
-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/persistence.rb10
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-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/test/cases/associations/belongs_to_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb2
-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_test.rb8
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb18
-rw-r--r--activerecord/test/cases/persistence_test.rb34
-rw-r--r--activeresource/test/connection_test.rb2
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb4
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb2
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb6
-rw-r--r--activesupport/lib/active_support/whiny_nil.rb2
-rw-r--r--activesupport/test/caching_test.rb38
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb2
-rw-r--r--activesupport/test/inflector_test.rb42
-rw-r--r--railties/CHANGELOG5
-rwxr-xr-xrailties/Rakefile3
-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.textile77
-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/lib/rails/generators/app_base.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt8
-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_ujs.js419
-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.rb17
-rw-r--r--railties/test/generators/app_generator_test.rb21
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb10
111 files changed, 2114 insertions, 10663 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 98272c7261..6cbc1bbebc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,7 +5,7 @@ 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"
@@ -54,7 +54,7 @@ platforms :ruby do
group :db do
gem "pg", ">= 0.11.0"
gem "mysql", ">= 2.8.1"
- gem "mysql2", :git => "git://github.com/brianmario/mysql2.git"
+ gem "mysql2", ">= 0.3.0"
end
end
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/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index f771737779..0d667a76a7 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
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-mount', '~> 0.7.1')
+ s.add_dependency('rack-mount', '~> 0.7.2')
s.add_dependency('tzinfo', '~> 0.3.23')
s.add_dependency('erubis', '~> 2.7.0')
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 306bd41e2d..db6ff41f55 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -99,7 +99,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, self)
end
# Normalize arguments, options and then delegates render_to_body and
@@ -127,7 +132,11 @@ module AbstractController
# Find and renders a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
- view_context.render(options)
+ if options.key?(:partial)
+ view_renderer.render_partial(view_context, options)
+ else
+ view_renderer.render_template(view_context, options)
+ end
end
# The prefixes used in render "foo" shortcuts.
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index b9bd49f670..0dd847f967 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -2,7 +2,207 @@ require 'active_support/core_ext/file/path'
require 'rack/chunked'
module ActionController #:nodoc:
- # Methods for sending streaming templates back to the client.
+ # Allow 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 first chunk of response is sent to the client
+ # just 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 at 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
+ # @post = Post.scoped
+ # render :stream => true
+ # end
+ # end
+ #
+ # == When to use streaming
+ #
+ # Streaming may be considering an overkill for common actions like
+ # new or edit. The real benefit of streaming is on expensive actions
+ # that, for example, does 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
+ # most of streaming, you would want to rewrite it as:
+ #
+ # def dashboard
+ # # Allow lazily execution of the query
+ # @posts = Post.scoped
+ # @pages = Page.scoped
+ # @articles = Article.scoped
+ # render :stream => true
+ # end
+ #
+ # == Communication between layout and template
+ #
+ # When streaming, the layout is rendered first than the template.
+ # This means that, if your application currently rely on 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 if 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 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 below, using +provide+:
+ #
+ # <%= provide :title, "Main" %>
+ # Hello
+ # <%= content_for :title, " page" %>
+ #
+ # Has as final result:
+ #
+ # <html>
+ # <head><title>Main</title></head>
+ # <body>Hello</body>
+ # </html>
+ #
+ # That said, when streaming, you need to properly check your templates
+ # and chose 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 start rendering will not propagate
+ # to the client.
+ #
+ # If you try to modify cookies, session or flash, a 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. So, 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 :tcp_nodelay. 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 +251,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/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index bc4f8bb9ce..0085f542aa 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -147,7 +147,9 @@ module ActionController
if value.is_a? Fixnum
value = value.to_s
elsif value.is_a? Array
- value = Result.new(value)
+ value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v })
+ elsif value.is_a? String
+ value = value.dup
end
if extra_keys.include?(key.to_sym)
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_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..ca0a89c8a5 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>)
@@ -159,9 +158,10 @@ module ActionView #:nodoc:
end
end
- attr_accessor :_template, :_view_flow
- attr_internal :request, :controller, :config, :assigns, :lookup_context
+ attr_accessor :view_renderer
+ attr_internal :request, :controller, :config, :assigns
+ delegate :lookup_context, :to => :view_renderer
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
@@ -182,33 +182,35 @@ module ActionView #: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:
+ def initialize(context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
assign(assigns_for_first_render)
self.helpers = Module.new unless self.class.helpers
@_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)
end
- @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ?
- lookup_context : ActionView::LookupContext.new(lookup_context)
- @_lookup_context.formats = formats if formats
+ # 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
+ @view_renderer = ActionView::Renderer.new(lookup_context, controller)
+ end
+
+ _prepare_context
end
+ # TODO Is this needed anywhere? Maybe deprecate it?
def controller_path
@controller_path ||= controller && controller.controller_path
end
- def controller_prefixes
- @controller_prefixes ||= controller && controller._prefixes
- end
-
ActiveSupport.run_load_hooks(:action_view, self)
end
end
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..3ef9826d30 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -19,6 +19,7 @@ module ActionView #:nodoc:
autoload :NumberHelper
autoload :OutputSafetyHelper
autoload :RecordTagHelper
+ autoload :RenderingHelper
autoload :SanitizeHelper
autoload :SprocketsHelper
autoload :TagHelper
@@ -48,6 +49,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_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/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 440acafa88..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.
#
@@ -947,7 +952,8 @@ module ActionView
label_tag(name_and_id["id"], options, &block)
else
content = if text.blank?
- I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence
+ method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
+ I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
else
text.to_s
end
@@ -1166,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/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index b545031fcc..63d13a0f0b 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -304,6 +304,7 @@ module ActionView
# * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary)
# ==== Examples
# number_to_human_size(123) # => 123 Bytes
# number_to_human_size(1234) # => 1.21 KB
@@ -341,15 +342,17 @@ module ActionView
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+
+ base = options[:prefix] == :si ? 1000 : 1024
- if number.to_i < 1024
+ if number.to_i < base
unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
else
max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= 1024 ** exponent
+ number /= base ** exponent
unit_key = STORAGE_UNITS[exponent]
unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
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..c91e03b7e8
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/rendering_helper.rb
@@ -0,0 +1,92 @@
+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)
+ elsif options.key?(:partial)
+ view_renderer.render_partial(self, options)
+ else
+ view_renderer.render_template(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/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/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/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index 4a52b3172e..d389105a7a 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -3,9 +3,9 @@ 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, controller)
+ @lookup_context = lookup_context
+ @controller = controller
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..70327b16c4 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[@controller.class.name]
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
+ def render(context, options, block)
+ setup(context, options, block)
- 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
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,42 @@ 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 controller_prefixes
+ @controller_prefixes ||= @controller && @controller._prefixes
+ end
+
def collection
if @options.key?(:collection)
collection = @options[:collection]
@@ -109,7 +331,7 @@ module ActionView
end
def find_template(path=@path, locals=@locals.keys)
- prefixes = path.include?(?/) ? [] : @view.controller_prefixes
+ prefixes = path.include?(?/) ? [] : controller_prefixes
@lookup_context.find_template(path, prefixes, true, locals)
end
@@ -150,7 +372,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 = controller_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..3c0126f6bb
--- /dev/null
+++ b/actionpack/lib/action_view/renderer/renderer.rb
@@ -0,0 +1,46 @@
+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, :controller
+
+ def initialize(lookup_context, controller)
+ @lookup_context = lookup_context
+ @controller = controller
+ 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, @controller).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, @controller)
+ end
+
+ def _partial_renderer #:nodoc:
+ @_partial_renderer ||= PartialRenderer.new(@lookup_context, @controller)
+ end
+ end
+end \ No newline at end of file
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..6c55a865a1 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)
@@ -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..14e032790d 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -147,9 +147,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/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_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/test_test.rb b/actionpack/test/controller/test_test.rb
index edda0d0a30..5896222a0a 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -493,6 +493,18 @@ XML
)
end
+ def test_params_passing_with_frozen_values
+ assert_nothing_raised do
+ get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze
+ end
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_test/test', 'action' => 'test_params',
+ 'frozen' => 'icy', 'frozens' => ['icy']},
+ parsed_params
+ )
+ end
+
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
assert_kind_of String, @request.path_parameters['id']
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/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 7afab3179c..c25c850eb3 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -24,7 +24,10 @@ class FormHelperTest < ActionView::TestCase
:helpers => {
:label => {
:post => {
- :body => "Write entire text here"
+ :body => "Write entire text here",
+ :color => {
+ :red => "Rojo"
+ }
}
}
}
@@ -141,6 +144,13 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
+ def test_label_with_locales_and_value
+ old_locale, I18n.locale = I18n.locale, :label
+ assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, :value => "red"))
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
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/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 23a7e17e65..63b92aadf4 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -172,6 +172,17 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '10 Bytes', number_to_human_size(10)
end
+ def test_number_to_human_size_with_si_prefix
+ assert_equal '3 Bytes', number_to_human_size(3.14159265, :prefix => :si)
+ assert_equal '123 Bytes', number_to_human_size(123.0, :prefix => :si)
+ assert_equal '123 Bytes', number_to_human_size(123, :prefix => :si)
+ assert_equal '1.23 KB', number_to_human_size(1234, :prefix => :si)
+ assert_equal '12.3 KB', number_to_human_size(12345, :prefix => :si)
+ assert_equal '1.23 MB', number_to_human_size(1234567, :prefix => :si)
+ assert_equal '1.23 GB', number_to_human_size(1234567890, :prefix => :si)
+ assert_equal '1.23 TB', number_to_human_size(1234567890123, :prefix => :si)
+ end
+
def test_number_to_human_size_with_options_hash
assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2)
assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4)
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/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/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
new file mode 100644
index 0000000000..ab7f86007f
--- /dev/null
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -0,0 +1,104 @@
+require 'set'
+
+module ActiveModel
+ # Stores the enabled/disabled state of individual observers for
+ # a particular model classes.
+ class ObserverArray < Array
+ attr_reader :model_class
+ def initialize(model_class, *args)
+ @model_class = model_class
+ super(*args)
+ end
+
+ def disabled_for?(observer)
+ disabled_observers.include?(observer.class)
+ end
+
+ def disable(*observers, &block)
+ set_enablement(false, observers, &block)
+ end
+
+ def enable(*observers, &block)
+ set_enablement(true, observers, &block)
+ end
+
+ protected
+
+ def disabled_observers
+ @disabled_observers ||= Set.new
+ end
+
+ def observer_class_for(observer)
+ return observer if observer.is_a?(Class)
+
+ if observer.respond_to?(:to_sym) # string/symbol
+ observer.to_s.camelize.constantize
+ else
+ raise ArgumentError, "#{observer} was not a class or a " +
+ "lowercase, underscored class name as expected."
+ end
+ end
+
+ def start_transaction
+ disabled_observer_stack.push(disabled_observers.dup)
+ each_subclass_array do |array|
+ array.start_transaction
+ end
+ end
+
+ def disabled_observer_stack
+ @disabled_observer_stack ||= []
+ end
+
+ def end_transaction
+ @disabled_observers = disabled_observer_stack.pop
+ each_subclass_array do |array|
+ array.end_transaction
+ end
+ end
+
+ def transaction
+ start_transaction
+
+ begin
+ yield
+ ensure
+ end_transaction
+ end
+ end
+
+ def each_subclass_array
+ model_class.descendants.each do |subclass|
+ yield subclass.observers
+ end
+ end
+
+ def set_enablement(enabled, observers)
+ if block_given?
+ transaction do
+ set_enablement(enabled, observers)
+ yield
+ end
+ else
+ observers = ActiveModel::Observer.descendants if observers == [:all]
+ observers.each do |obs|
+ klass = observer_class_for(obs)
+
+ unless klass < ActiveModel::Observer
+ raise ArgumentError.new("#{obs} does not refer to a valid observer")
+ end
+
+ if enabled
+ disabled_observers.delete(klass)
+ else
+ disabled_observers << klass
+ end
+ end
+
+ each_subclass_array do |array|
+ array.set_enablement(enabled, observers)
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 3c80d584fe..cfe71074a5 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,13 +1,20 @@
require 'singleton'
+require 'active_model/observer_array'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/enumerable'
+require 'active_support/descendants_tracker'
module ActiveModel
module Observing
extend ActiveSupport::Concern
+ included do
+ extend ActiveSupport::DescendantsTracker
+ end
+
module ClassMethods
# == Active Model Observers Activation
#
@@ -30,12 +37,12 @@ module ActiveModel
# +instantiate_observers+ is called during startup, and before
# each development request.
def observers=(*values)
- @observers = values.flatten
+ observers.replace(values.flatten)
end
# Gets the current observers.
def observers
- @observers ||= []
+ @observers ||= ObserverArray.new(self)
end
# Gets the current observer instances.
@@ -49,6 +56,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'"
@@ -76,7 +85,11 @@ module ActiveModel
elsif observer.respond_to?(:instance)
observer.instance
else
- raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
+ raise ArgumentError,
+ "#{observer} must be a lowercase, underscored class name (or an " +
+ "instance of the class itself) responding to the instance " +
+ "method. Example: Person.observers = :big_brother # calls " +
+ "BigBrother.instance"
end
end
@@ -133,8 +146,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
@@ -165,6 +178,7 @@ module ActiveModel
#
class Observer
include Singleton
+ extend ActiveSupport::DescendantsTracker
class << self
# Attaches the observer to the supplied model classes.
@@ -210,7 +224,9 @@ module ActiveModel
# Send observed_method(object) if the method exists.
def update(observed_method, object) #:nodoc:
- send(observed_method, object) if respond_to?(observed_method)
+ return unless respond_to?(observed_method)
+ return if disabled_for?(object)
+ send(observed_method, object)
end
# Special method sent by the observed class when it is inherited.
@@ -224,5 +240,11 @@ module ActiveModel
def add_observer!(klass) #:nodoc:
klass.add_observer(self)
end
+
+ def disabled_for?(object)
+ klass = object.class
+ return false unless klass.respond_to?(:observers)
+ klass.observers.disabled_for?(self)
+ end
end
end
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/validations.rb b/activemodel/lib/active_model/validations.rb
index d968609e67..5e567307f3 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -133,7 +133,7 @@ module ActiveModel
if options.key?(:on)
options = options.dup
options[:if] = Array.wrap(options[:if])
- options[:if] << "validation_context == :#{options[:on]}"
+ options[:if].unshift("validation_context == :#{options[:on]}")
end
args << options
set_callback(:validate, *args, &block)
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index adc2867ad0..22a77320dc 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -31,7 +31,7 @@ module ActiveModel
options = args.last
if options.is_a?(Hash) && options[:on]
options[:if] = Array.wrap(options[:if])
- options[:if] << "self.validation_context == :#{options[:on]}"
+ options[:if].unshift("self.validation_context == :#{options[:on]}")
end
set_callback(:validation, :before, *args, &block)
end
@@ -41,7 +41,7 @@ module ActiveModel
options[:prepend] = true
options[:if] = Array.wrap(options[:if])
options[:if] << "!halted"
- options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
+ options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on]
set_callback(:validation, :after, *(args << options), &block)
end
end
diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb
new file mode 100644
index 0000000000..fc5f18008b
--- /dev/null
+++ b/activemodel/test/cases/observer_array_test.rb
@@ -0,0 +1,220 @@
+require 'cases/helper'
+require 'models/observers'
+
+class ObserverArrayTest < ActiveModel::TestCase
+ def teardown
+ ORM.observers.enable :all
+ Budget.observers.enable :all
+ Widget.observers.enable :all
+ end
+
+ def assert_observer_notified(model_class, observer_class)
+ observer_class.instance.before_save_invocations.clear
+ model_instance = model_class.new
+ model_instance.save
+ assert_equal [model_instance], observer_class.instance.before_save_invocations
+ end
+
+ def assert_observer_not_notified(model_class, observer_class)
+ observer_class.instance.before_save_invocations.clear
+ model_instance = model_class.new
+ model_instance.save
+ assert_equal [], observer_class.instance.before_save_invocations
+ end
+
+ test "all observers are enabled by default" do
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable individual observers using a class constant" do
+ ORM.observers.disable WidgetObserver
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can enable individual observers using a class constant" do
+ ORM.observers.disable :all
+ ORM.observers.enable AuditTrail
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable individual observers using a symbol" do
+ ORM.observers.disable :budget_observer
+
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can enable individual observers using a symbol" do
+ ORM.observers.disable :all
+ ORM.observers.enable :audit_trail
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable multiple observers at a time" do
+ ORM.observers.disable :widget_observer, :budget_observer
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can enable multiple observers at a time" do
+ ORM.observers.disable :all
+ ORM.observers.enable :widget_observer, :budget_observer
+
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_not_notified Budget, AuditTrail
+ end
+
+ test "can disable all observers using :all" do
+ ORM.observers.disable :all
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_not_notified Budget, AuditTrail
+ end
+
+ test "can enable all observers using :all" do
+ ORM.observers.disable :all
+ ORM.observers.enable :all
+
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable observers on individual models without affecting those observers on other models" do
+ Widget.observers.disable :all
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can enable observers on individual models without affecting those observers on other models" do
+ ORM.observers.disable :all
+ Budget.observers.enable AuditTrail
+
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can disable observers for the duration of a block" do
+ yielded = false
+ ORM.observers.disable :budget_observer do
+ yielded = true
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_not_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ assert yielded
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "can enable observers for the duration of a block" do
+ yielded = false
+ Widget.observers.disable :all
+
+ Widget.observers.enable :all do
+ yielded = true
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ assert yielded
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "raises an appropriate error when a developer accidentally enables or disables the wrong class (i.e. Widget instead of WidgetObserver)" do
+ assert_raise ArgumentError do
+ ORM.observers.enable :widget
+ end
+
+ assert_raise ArgumentError do
+ ORM.observers.enable Widget
+ end
+
+ assert_raise ArgumentError do
+ ORM.observers.disable :widget
+ end
+
+ assert_raise ArgumentError do
+ ORM.observers.disable Widget
+ end
+ end
+
+ test "allows #enable at the superclass level to override #disable at the subclass level when called last" do
+ Widget.observers.disable :all
+ ORM.observers.enable :all
+
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ test "allows #disable at the superclass level to override #enable at the subclass level when called last" do
+ Budget.observers.enable :audit_trail
+ ORM.observers.disable :audit_trail
+
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_not_notified Budget, AuditTrail
+ end
+
+ test "can use the block form at different levels of the hierarchy" do
+ yielded = false
+ Widget.observers.disable :all
+
+ ORM.observers.enable :all do
+ yielded = true
+ assert_observer_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+
+ assert yielded
+ assert_observer_not_notified Widget, WidgetObserver
+ assert_observer_notified Budget, BudgetObserver
+ assert_observer_not_notified Widget, AuditTrail
+ assert_observer_notified Budget, AuditTrail
+ end
+end
+
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 63686843b6..99b1f407ae 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -43,6 +43,11 @@ class ObservingTest < ActiveModel::TestCase
assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
end
+ test "uses an ObserverArray so observers can be disabled" do
+ ObservedModel.observers = [:foo, :bar]
+ assert ObservedModel.observers.is_a?(ActiveModel::ObserverArray)
+ end
+
test "instantiates observer names passed as strings" do
ObservedModel.observers << 'foo_observer'
FooObserver.expects(:instance)
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 2f36195627..0b50acf913 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -212,6 +212,20 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0]
end
+ def test_validaton_with_if_and_on
+ Topic.validates_presence_of :title, :if => Proc.new{|x| x.author_name = "bad"; true }, :on => :update
+
+ t = Topic.new(:title => "")
+
+ # If block should not fire
+ assert t.valid?
+ assert t.author_name.nil?
+
+ # If block should fire
+ assert t.invalid?(:update)
+ assert t.author_name == "bad"
+ end
+
def test_invalid_should_be_the_opposite_of_valid
Topic.validates_presence_of :title
diff --git a/activemodel/test/models/observers.rb b/activemodel/test/models/observers.rb
new file mode 100644
index 0000000000..3729b3435e
--- /dev/null
+++ b/activemodel/test/models/observers.rb
@@ -0,0 +1,27 @@
+class ORM
+ include ActiveModel::Observing
+
+ def save
+ notify_observers :before_save
+ end
+
+ class Observer < ActiveModel::Observer
+ def before_save_invocations
+ @before_save_invocations ||= []
+ end
+
+ def before_save(record)
+ before_save_invocations << record
+ end
+ end
+end
+
+class Widget < ORM; end
+class Budget < ORM; end
+class WidgetObserver < ORM::Observer; end
+class BudgetObserver < ORM::Observer; end
+class AuditTrail < ORM::Observer
+ observe :widget, :budget
+end
+
+ORM.instantiate_observers
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index c3cd76a714..9d4cbbc150 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('arel', '~> 2.1.0')
s.add_dependency('tzinfo', '~> 0.3.23')
end
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/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/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 7ac72acd58..d3a054c29b 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
+gem 'mysql2', '~> 0.3.0'
require 'mysql2'
module ActiveRecord
@@ -131,6 +132,7 @@ module ActiveRecord
ADAPTER_NAME
end
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
end
@@ -139,6 +141,7 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints?
true
end
@@ -289,6 +292,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
@@ -376,6 +388,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
@@ -406,6 +422,7 @@ module ActiveRecord
super(table_name, options)
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
indexes = []
current_index = nil
@@ -423,6 +440,7 @@ 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 = []
@@ -437,6 +455,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
@@ -521,6 +543,7 @@ module ActiveRecord
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)}")
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 2c05ff21f9..862ce852e6 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,34 +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
-
- stmt.execute(*binds.map { |col, val|
- type_cast(val, col)
- })
- 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)
+ exec_stmt(sql, name, binds) do |cols, stmt|
+ ActiveRecord::Result.new(cols, stmt.to_a) if cols
end
-
- stmt.free_result
- stmt.close if binds.empty?
-
- result
end
end
@@ -480,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
@@ -607,6 +585,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
@@ -625,6 +604,7 @@ 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 = []
@@ -638,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
@@ -833,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
@@ -875,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/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/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 0502f51170..5703fac033 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -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)
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/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 9006914508..7518bc19f9 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -576,9 +576,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_polymorphic_counter_cache
- tagging = taggings(:welcome_general)
- post = posts(:welcome)
- comment = comments(:greetings)
+ tagging = taggings(:welcome_general)
+ post = post = posts(:welcome)
+ comment = comments(:greetings)
assert_difference 'post.reload.taggings_count', -1 do
assert_difference 'comment.reload.taggings_count', +1 do
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_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/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_test.rb b/activerecord/test/cases/identity_map_test.rb
index 199e59657d..2238529f0f 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -137,8 +137,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 +147,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 +159,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 +169,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
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 43016df479..2c051bff84 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -35,10 +35,10 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
p = LoosePerson.new
p.assign_attributes(attributes_hash)
- assert_equal nil, p.id
+ assert_equal nil, p.id
assert_equal 'Josh', p.first_name
- assert_equal 'male', p.gender
- assert_equal nil, p.comments
+ assert_equal 'm', p.gender
+ assert_equal nil, p.comments
end
def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used
@@ -47,7 +47,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_equal 5, p.id
assert_equal 'Josh', p.first_name
- assert_equal 'male', p.gender
+ assert_equal 'm', p.gender
assert_equal 'rides a sweet bike', p.comments
end
@@ -57,7 +57,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_equal nil, p.id
assert_equal 'Josh', p.first_name
- assert_equal 'male', p.gender
+ assert_equal 'm', p.gender
assert_equal nil, p.comments
end
@@ -67,7 +67,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_equal nil, p.id
assert_equal 'Josh', p.first_name
- assert_equal 'male', p.gender
+ assert_equal 'm', p.gender
assert_equal 'rides a sweet bike', p.comments
end
@@ -77,7 +77,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_equal nil, p.id
assert_equal 'Josh', p.first_name
- assert_equal 'male', p.gender
+ assert_equal 'm', p.gender
assert_equal nil, p.comments
end
@@ -87,7 +87,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_equal nil, p.id
assert_equal 'Josh', p.first_name
- assert_equal 'male', p.gender
+ assert_equal 'm', p.gender
assert_equal 'rides a sweet bike', p.comments
end
@@ -107,7 +107,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
{
:id => 5,
:first_name => 'Josh',
- :gender => 'male',
+ :gender => 'm',
:comments => 'rides a sweet bike'
}
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 2044bc6e3f..b066575af8 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -492,22 +492,22 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_update_attributes_as_admin
- person = TightPerson.create
- person.update_attributes({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :as => :admin)
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin)
person.reload
- assert_equal 'Josh', person.first_name
- assert_equal 'male', person.gender
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
assert_equal 'from NZ', person.comments
end
- def test_update_attributes_as_without_protection
- person = TightPerson.create
- person.update_attributes({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :without_protection => true)
+ def test_update_attributes_without_protection
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true)
person.reload
- assert_equal 'Josh', person.first_name
- assert_equal 'male', person.gender
+ assert_equal 'Josh', person.first_name
+ assert_equal 'm', person.gender
assert_equal 'from NZ', person.comments
end
@@ -532,23 +532,23 @@ class PersistencesTest < ActiveRecord::TestCase
Reply.reset_callbacks(:validate)
end
- def test_update_attributes_as_admin
- person = TightPerson.create
- person.update_attributes!({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :as => :admin)
+ def test_update_attributes_with_bang_as_admin
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin)
person.reload
assert_equal 'Josh', person.first_name
- assert_equal 'male', person.gender
+ assert_equal 'm', person.gender
assert_equal 'from NZ', person.comments
end
- def test_update_attributes_as_without_protection
- person = TightPerson.create
- person.update_attributes!({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :without_protection => true)
+ def test_update_attributestes_with_bang_without_protection
+ person = TightPerson.create({ "first_name" => 'Joshua' })
+ person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true)
person.reload
assert_equal 'Josh', person.first_name
- assert_equal 'male', person.gender
+ assert_equal 'm', person.gender
assert_equal 'from NZ', person.comments
end
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 6e79845aa0..7c36393cf2 100644
--- a/activeresource/test/connection_test.rb
+++ b/activeresource/test/connection_test.rb
@@ -50,7 +50,7 @@ class ConnectionTest < Test::Unit::TestCase
# 404 is a missing resource.
assert_response_raises ActiveResource::ResourceNotFound, 404
- # 405 is a missing not allowed error
+ # 405 is a method not allowed error
assert_response_raises ActiveResource::MethodNotAllowed, 405
# 409 is an optimistic locking error
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 45263d482f..7ef1497ac2 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -4,7 +4,9 @@ rescue LoadError => e
$stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
+
require 'digest/md5'
+require 'active_support/core_ext/string/encoding'
module ActiveSupport
module Cache
@@ -157,8 +159,14 @@ module ActiveSupport
end
private
+
+ # Memcache keys are binaries. So we need to force their encoding to binary
+ # before applying the regular expression to ensure we are escaping all
+ # characters properly.
def escape_key(key)
- key = key.to_s.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"}
+ key = key.to_s.dup
+ key = key.force_encoding("BINARY") if key.encoding_aware?
+ key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index c69a015f12..2df4fd1da1 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -3,10 +3,10 @@ class Array
#
# %w( a b c d ).from(0) # => %w( a b c d )
# %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) # => %w()
# %w().from(0) # => %w()
def from(position)
- self[position..-1]
+ self[position, length] || []
end
# Returns the beginning of the array up to +position+.
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/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index e136e4c5b3..d5d55b7207 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -96,7 +96,7 @@ module ActiveSupport
def clear(scope = :all)
case scope
when :all
- @plurals, @singulars, @uncountables = [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
else
instance_variable_set "@#{scope}", []
end
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/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/caching_test.rb b/activesupport/test/caching_test.rb
index e5668e29d7..476d55fffd 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -352,6 +352,43 @@ module CacheStoreBehavior
end
end
+# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
+# The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special
+# characters like the umlaut in UTF-8.
+module EncodedKeyCacheBehavior
+ if defined?(Encoding)
+ Encoding.list.each do |encoding|
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
+ key = "foo".force_encoding(encoding)
+ assert_equal true, @cache.write(key, "1", :raw => true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert_equal true, @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+ end
+
+ def test_common_utf8_values
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
+ assert_equal true, @cache.write(key, "1", :raw => true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert_equal true, @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+
+ def test_retains_encoding
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
+ assert_equal true, @cache.write(key, "1", :raw => true)
+ assert_equal Encoding::UTF_8, key.encoding
+ end
+ end
+end
+
module CacheDeleteMatchedBehavior
def test_delete_matched
@cache.write("foo", "bar")
@@ -617,6 +654,7 @@ uses_memcached 'memcached backed store' do
include CacheStoreBehavior
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
+ include EncodedKeyCacheBehavior
def test_raw_values
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index d7ab3ce605..0e5407bc35 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -10,7 +10,7 @@ class ArrayExtAccessTests < Test::Unit::TestCase
def test_from
assert_equal %w( a b c d ), %w( a b c d ).from(0)
assert_equal %w( c d ), %w( a b c d ).from(2)
- assert_nil %w( a b c d ).from(10)
+ assert_equal %w(), %w( a b c d ).from(10)
end
def test_to
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 1670d9ee7d..95f18126d4 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -255,12 +255,21 @@ class InflectorTest < Test::Unit::TestCase
end
def test_clear_all
- cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables, ActiveSupport::Inflector.inflections.humans
- ActiveSupport::Inflector.inflections.clear :all
- assert ActiveSupport::Inflector.inflections.plurals.empty?
- assert ActiveSupport::Inflector.inflections.singulars.empty?
- assert ActiveSupport::Inflector.inflections.uncountables.empty?
- assert ActiveSupport::Inflector.inflections.humans.empty?
+ cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear :all
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
+ end
ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
@@ -268,12 +277,21 @@ class InflectorTest < Test::Unit::TestCase
end
def test_clear_with_default
- cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables, ActiveSupport::Inflector.inflections.humans
- ActiveSupport::Inflector.inflections.clear
- assert ActiveSupport::Inflector.inflections.plurals.empty?
- assert ActiveSupport::Inflector.inflections.singulars.empty?
- assert ActiveSupport::Inflector.inflections.uncountables.empty?
- assert ActiveSupport::Inflector.inflections.humans.empty?
+ cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
+ end
ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index a4f0d31971..83c501c656 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,10 @@
*Rails 3.1.0 (unreleased)*
+* Application generation no longer supports the -j option. [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..1affa6dd39 100755
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -46,8 +46,7 @@ task :generate_guides do
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"
+ system "curl https://github.com/rails/jquery-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js"
end
# Validate guides -------------------------------------------------------------------------
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..9e59383315 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,7 +1301,7 @@ 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.
+Rails includes by default the jQuery JavaScript library. If you wish to use these libraries and they are in your asset pipeline, or otherwise 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
@@ -1402,16 +1402,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 +1498,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..d7090ef675 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
@@ -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,26 +226,27 @@ 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 http://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.
@@ -261,7 +258,7 @@ 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/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 3cc3762cee..324199e71c 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -38,9 +38,6 @@ 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 :skip_javascript, :type => :boolean, :aliases => "-J", :default => false,
:desc => "Skip JavaScript files"
@@ -135,14 +132,14 @@ module Rails
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"
+ 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"
+ gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
else
<<-GEMFILE.strip_heredoc
@@ -152,7 +149,7 @@ module Rails
# 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"
+ # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git'
GEMFILE
end
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..559f61e265 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -120,14 +120,8 @@ module Rails
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
+ copy_file "vendor/assets/javascripts/jquery.js"
+ copy_file "vendor/assets/javascripts/jquery_ujs.js"
end
end
@@ -162,10 +156,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 9f2346028a..de7d51d030 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -8,7 +8,10 @@ source 'http://rubygems.org'
<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
gem 'sass'
gem 'coffee-script'
-# gem 'uglifier'
+gem 'uglifier'
+
+# Prototype, Scriptaculous, and RJS.
+# gem 'prototype-rails'
# 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..c04c33e321 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
//
-//= require <%= options[:javascript] %>
-//= require <%= options[:javascript] %>_ujs
-<% if options[:javascript] == "prototype" %>
-//= require controls
-//= require dragdrop
-//= require effects
+<% unless options[:skip_javascript] -%>
+//= require jquery
+//= require jquery_ujs
<% end -%>
//= require_tree .
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..7bf4c779a0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -39,11 +39,13 @@ 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 -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index ce28e41b91..9553f3bdde 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -11,14 +11,14 @@
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
+ # Compress both stylesheets and JavaScripts
+ config.assets.js_compressor = :uglifier
+ config.assets.css_compressor = :scss
+
# Specifies the header that your server uses for sending files
# (comment out if your front-end server doesn't support this)
config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx
- # Compress both stylesheets and JavaScripts
- # config.assets.js_compressor = :uglifier
- config.assets.css_compressor = :scss
-
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
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_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js
index 4dcb3779a2..46b2e40e69 100644
--- 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
@@ -3,146 +3,287 @@
*
* Requires jQuery 1.4.3 or later.
* https://github.com/rails/jquery-ujs
+
+ * Uploading file using rails.js
+ * =============================
+ *
+ * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
+ * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
+ *
+ * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
+ *
+ * Ex:
+ * $('form').live('ajax:aborted:file', function(event, elements){
+ * // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
+ * // Returning false in this handler tells rails.js to disallow standard form submission
+ * return false;
+ * });
+ *
+ * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
+ *
+ * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
+ * techniques like the iframe method to upload the file instead.
+ *
+ * Required fields in rails.js
+ * ===========================
+ *
+ * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
+ * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
+ *
+ * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
+ *
+ * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
+ * get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
+ *
+ * Ex:
+ * $('form').live('ajax:aborted:required', function(event, elements){
+ * // Returning false in this handler tells rails.js to submit the form anyway.
+ * // The blank required inputs are passed to this function in `elements`.
+ * return ! confirm("Would you like to submit the form with missing info?");
+ * });
*/
(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));
- });
+ // Shorthand to make it a little easier to call public rails functions from within rails.js
+ var rails;
+
+ $.rails = rails = {
+ // Link elements bound by jquery-ujs
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]',
+
+ // Form elements bound by jquery-ujs
+ formSubmitSelector: 'form',
+
+ // Form input elements bound by jquery-ujs
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
+
+ // Form input elements disabled during form submission
+ disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
+
+ // Form input elements re-enabled after form submission
+ enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
+
+ // Form required input elements
+ requiredInputSelector: 'input[name][required],textarea[name][required]',
+
+ // Form file input elements
+ fileInputSelector: 'input:file',
+
+ // Make sure that every Ajax request sends the CSRF token
+ CSRFProtection: function(xhr) {
+ var token = $('meta[name="csrf-token"]').attr('content');
+ if (token) xhr.setRequestHeader('X-CSRF-Token', token);
+ },
+
+ // Triggers an event on an element and returns false if the event result is false
+ fire: function(obj, name, data) {
+ var event = $.Event(name);
+ obj.trigger(event, data);
+ return event.result !== false;
+ },
+
+ // Submits "remote" forms and links with ajax
+ handleRemote: function(element) {
+ var method, url, data,
+ dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
+
+ if (rails.fire(element, 'ajax:before')) {
+
+ 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.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 rails.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>
+ handleMethod: function(link) {
+ var href = link.attr('href'),
+ method = link.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();
+ },
+
+ /* Disables form elements:
+ - Caches element value in 'ujs:enable-with' data store
+ - Replaces element text with value of 'data-disable-with' attribute
+ - Adds disabled=disabled attribute
+ */
+ disableFormElements: function(form) {
+ form.find(rails.disableSelector).each(function() {
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
+ element.data('ujs:enable-with', element[method]());
+ element[method](element.data('disable-with'));
+ element.attr('disabled', 'disabled');
+ });
+ },
+
+ /* Re-enables disabled form elements:
+ - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
+ - Removes disabled attribute
+ */
+ enableFormElements: function(form) {
+ form.find(rails.enableSelector).each(function() {
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
+ if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
+ element.removeAttr('disabled');
+ });
+ },
+
+ // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog.
+ // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog.
+ allowAction: function(element) {
+ var message = element.data('confirm');
+ return !message || (rails.fire(element, 'confirm') && confirm(message));
+ },
+
+ // Helper function which checks for blank inputs in a form that match the specified CSS selector
+ blankInputs: function(form, specifiedSelector, nonBlank) {
+ var inputs = $(), input,
+ selector = specifiedSelector || 'input,textarea';
+ form.find(selector).each(function() {
+ input = $(this);
+ // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
+ if (nonBlank ? input.val() : !input.val()) {
+ inputs = inputs.add(input);
+ }
+ });
+ return inputs.length ? inputs : false;
+ },
+
+ // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
+ nonBlankInputs: function(form, specifiedSelector) {
+ return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
+ },
+
+ // Helper function, needed to provide consistent behavior in IE
+ stopEverything: function(e) {
+ e.stopImmediatePropagation();
+ return false;
+ },
+
+ // find all the submit events directly bound to the form and
+ // manually invoke them. If anyone returns false then stop the loop
+ callFormSubmitBindings: function(form) {
+ var events = form.data('events'), continuePropagation = true;
+ if (events !== undefined && events['submit'] !== undefined) {
+ $.each(events['submit'], function(i, obj){
+ if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data);
+ });
+ }
+ return continuePropagation;
+ }
+ };
+
+ // ajaxPrefilter is a jQuery 1.5 feature
+ if ('ajaxPrefilter' in $) {
+ $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); });
+ } else {
+ $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); });
+ }
+
+ $(rails.linkClickSelector).live('click.rails', function(e) {
+ var link = $(this);
+ if (!rails.allowAction(link)) return rails.stopEverything(e);
+
+ if (link.data('remote') !== undefined) {
+ rails.handleRemote(link);
+ return false;
+ } else if (link.data('method')) {
+ rails.handleMethod(link);
+ return false;
+ }
+ });
+
+ $(rails.formSubmitSelector).live('submit.rails', function(e) {
+ var form = $(this),
+ remote = form.data('remote') !== undefined,
+ blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
+ nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
+
+ if (!rails.allowAction(form)) return rails.stopEverything(e);
+
+ // skip other logic when required values are missing or file upload is present
+ if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
+ return !remote;
+ }
+
+ if (remote) {
+ if (nonBlankFileInputs) {
+ return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
+ }
+
+ // If browser does not support submit bubbling, then this live-binding will be called before direct
+ // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
+ if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e);
+
+ rails.handleRemote(form);
+ return false;
+ } else {
+ // slight timeout so that the submit button gets properly serialized
+ setTimeout(function(){ rails.disableFormElements(form); }, 13);
+ }
+ });
+
+ $(rails.formInputClickSelector).live('click.rails', function(event) {
+ var button = $(this);
+
+ if (!rails.allowAction(button)) return rails.stopEverything(event);
+
+ // 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);
+ });
+
+ $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) {
+ if (this == event.target) rails.disableFormElements($(this));
+ });
+
+ $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) {
+ if (this == event.target) rails.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..2bfe8b09f3 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,10 @@ 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
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/jquery.js",
+ "vendor/assets/javascripts/jquery.js"
+ copy_file "#{app_templates_dir}/vendor/assets/javascripts/jquery_ujs.js",
+ "vendor/assets/javascripts/jquery_ujs.js"
elsif full?
empty_directory_with_gitkeep "app/assets/javascripts"
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 1902484301..3cf92aed07 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -174,31 +174,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
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 "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"
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/
- 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/
- 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/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index fb956a8335..ae4fa6e596 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -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"]))