aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller/assertions/response_assertions.rb6
-rwxr-xr-xactionpack/lib/action_controller/base.rb28
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb4
-rw-r--r--actionpack/lib/action_controller/cookies.rb2
-rw-r--r--actionpack/lib/action_controller/filters.rb12
-rw-r--r--actionpack/lib/action_controller/rack_process.rb8
-rwxr-xr-xactionpack/lib/action_controller/request.rb41
-rw-r--r--actionpack/lib/action_controller/rescue.rb28
-rw-r--r--actionpack/lib/action_controller/resources.rb9
-rwxr-xr-xactionpack/lib/action_controller/response.rb38
-rw-r--r--actionpack/lib/action_controller/routing/builder.rb15
-rw-r--r--actionpack/lib/action_controller/routing/optimisations.rb2
-rw-r--r--actionpack/lib/action_controller/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_controller/test_process.rb17
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb6
-rw-r--r--actionpack/lib/action_view.rb4
-rw-r--r--actionpack/lib/action_view/base.rb40
-rw-r--r--actionpack/lib/action_view/helpers/active_record_helper.rb69
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb85
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb17
-rwxr-xr-xactionpack/lib/action_view/helpers/date_helper.rb336
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb12
-rw-r--r--actionpack/lib/action_view/helpers/form_country_helper.rb92
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb38
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb83
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/javascripts/prototype.js304
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb109
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb378
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb18
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb204
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb93
-rw-r--r--actionpack/lib/action_view/locale/en-US.rb32
-rw-r--r--actionpack/lib/action_view/partials.rb38
-rw-r--r--actionpack/lib/action_view/paths.rb61
-rw-r--r--actionpack/lib/action_view/renderable.rb85
-rw-r--r--actionpack/lib/action_view/renderable_partial.rb25
-rw-r--r--actionpack/lib/action_view/template.rb51
-rw-r--r--actionpack/lib/action_view/template_handler.rb27
-rw-r--r--actionpack/lib/action_view/template_handlers.rb1
-rw-r--r--actionpack/lib/action_view/template_handlers/builder.rb10
-rw-r--r--actionpack/lib/action_view/template_handlers/compilable.rb20
-rw-r--r--actionpack/lib/action_view/template_handlers/erb.rb11
-rw-r--r--actionpack/lib/action_view/template_handlers/rjs.rb11
47 files changed, 1373 insertions, 1130 deletions
diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb
index cb36405700..765225ae24 100644
--- a/actionpack/lib/action_controller/assertions/response_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/response_assertions.rb
@@ -87,13 +87,13 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
- rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
+ rendered = @response.rendered_template
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
- !@response.rendered_with_file?
+ @response.rendered_template.nil?
else
- rendered.match(expected)
+ rendered.to_s.match(expected)
end
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index df94f78f18..5f4a38dac0 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -354,6 +354,15 @@ module ActionController #:nodoc:
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
+ # If you are deploying to a subdirectory, you will need to set
+ # <tt>config.action_controller.relative_url_root</tt>
+ # This defaults to ENV['RAILS_RELATIVE_URL_ROOT']
+ cattr_writer :relative_url_root
+
+ def self.relative_url_root
+ @@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT']
+ end
+
# Holds the request object that's primarily used to get environment variables through access like
# <tt>request.env["REQUEST_URI"]</tt>.
attr_internal :request
@@ -519,6 +528,8 @@ module ActionController #:nodoc:
public
# Extracts the action_name from the request parameters and performs that action.
def process(request, response, method = :perform_action, *arguments) #:nodoc:
+ response.request = request
+
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
@@ -529,8 +540,6 @@ module ActionController #:nodoc:
send(method, *arguments)
assign_default_content_type_and_charset
-
- response.request = request
response.prepare! unless component_request?
response
ensure
@@ -968,6 +977,17 @@ module ActionController #:nodoc:
render :nothing => true, :status => status
end
+ # Sets the Last-Modified response header. Returns 304 Not Modified if the
+ # If-Modified-Since request header is <= last modified.
+ def last_modified!(utc_time)
+ head(:not_modified) if response.last_modified!(utc_time)
+ end
+
+ # Sets the ETag response header. Returns 304 Not Modified if the
+ # If-None-Match request header matches.
+ def etag!(etag)
+ head(:not_modified) if response.etag!(etag)
+ end
# Clears the rendered results, allowing for another render to be performed.
def erase_render_results #:nodoc:
@@ -1155,7 +1175,7 @@ module ActionController #:nodoc:
def log_processing
if logger && logger.info?
- logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
+ logger.info "\n\nProcessing #{self.class.name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id)
logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}"
end
@@ -1250,6 +1270,8 @@ module ActionController #:nodoc:
def template_exempt_from_layout?(template_name = default_template_name)
template_name = @template.pick_template(template_name).to_s if @template
@@exempt_from_layout.any? { |ext| template_name =~ ext }
+ rescue ActionView::MissingTemplate
+ false
end
def default_template_name(action_name = self.action_name)
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 45946421fc..e9b434dd25 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -60,10 +60,8 @@ module ActionController #:nodoc:
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
- def fragment_for(block, name = {}, options = nil) #:nodoc:
+ def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
if perform_caching
- buffer = yield
-
if cache = read_fragment(name, options)
buffer.concat(cache)
else
diff --git a/actionpack/lib/action_controller/cookies.rb b/actionpack/lib/action_controller/cookies.rb
index 51bc4ad23d..0428f2a23d 100644
--- a/actionpack/lib/action_controller/cookies.rb
+++ b/actionpack/lib/action_controller/cookies.rb
@@ -23,7 +23,7 @@ module ActionController #:nodoc:
# cookies.delete :user_name
#
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
- #
+ #
# cookies[:key] = {
# :value => 'a yummy cookie',
# :expires => 1.year.from_now,
diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb
index fc63890d13..10dc0cc45b 100644
--- a/actionpack/lib/action_controller/filters.rb
+++ b/actionpack/lib/action_controller/filters.rb
@@ -569,21 +569,13 @@ module ActionController #:nodoc:
# Returns all the before filters for this class and all its ancestors.
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
def before_filters #:nodoc:
- filters = []
- filter_chain.each do |filter|
- filters << filter.method if filter.before?
- end
- filters
+ filter_chain.select(&:before?).map(&:method)
end
# Returns all the after filters for this class and all its ancestors.
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
def after_filters #:nodoc:
- filters = []
- filter_chain.each do |filter|
- filters << filter.method if filter.after?
- end
- filters
+ filter_chain.select(&:after?).map(&:method)
end
end
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 3117fe2da5..7e0a6b091e 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -24,7 +24,7 @@ module ActionController #:nodoc:
super()
end
- %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
+ %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
PATH_TRANSLATED QUERY_STRING REMOTE_HOST
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
SERVER_NAME SERVER_PROTOCOL
@@ -98,10 +98,6 @@ module ActionController #:nodoc:
@env['REMOTE_ADDR']
end
- def request_method
- @env['REQUEST_METHOD'].downcase.to_sym
- end
-
def server_port
@env['SERVER_PORT'].to_i
end
@@ -250,7 +246,7 @@ end_msg
headers['Content-Language'] = options.delete('language') if options['language']
headers['Expires'] = options.delete('expires') if options['expires']
- @status = options['Status'] || "200 OK"
+ @status = options.delete('Status') || "200 OK"
# Convert 'cookie' header to 'Set-Cookie' headers.
# Because Set-Cookie header can appear more the once in the response body,
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index c42f113d2c..c55788a531 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -3,13 +3,16 @@ require 'stringio'
require 'strscan'
module ActionController
- # HTTP methods which are accepted by default.
+ # HTTP methods which are accepted by default.
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
# CgiRequest and TestRequest provide concrete implementations.
class AbstractRequest
- cattr_accessor :relative_url_root
- remove_method :relative_url_root
+ def self.relative_url_root=(*args)
+ ActiveSupport::Deprecation.warn(
+ "ActionController::AbstractRequest.relative_url_root= has been renamed." +
+ "You can now set it with config.action_controller.relative_url_root=", caller)
+ end
# The hash of environment variables for this request,
# such as { 'RAILS_ENV' => 'production' }.
@@ -111,14 +114,14 @@ module ActionController
end
end
end
-
-
+
+
# Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
# Example:
#
# class ApplicationController < ActionController::Base
# before_filter :adjust_format_for_iphone
- #
+ #
# private
# def adjust_format_for_iphone
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
@@ -303,26 +306,10 @@ EOM
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
# Cut off the path to the installation directory if given
- path.sub!(%r/^#{relative_url_root}/, '')
- path || ''
- end
-
- # Returns the path minus the web server relative installation directory.
- # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
- # It can be automatically extracted for Apache setups. If the server is not
- # Apache, this method returns an empty string.
- def relative_url_root
- @@relative_url_root ||= case
- when @env["RAILS_RELATIVE_URL_ROOT"]
- @env["RAILS_RELATIVE_URL_ROOT"]
- when server_software == 'apache'
- @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
- else
- ''
- end
+ path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
+ path || ''
end
-
# Read the request body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
@@ -343,15 +330,15 @@ EOM
@symbolized_path_parameters = @parameters = nil
end
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys
- def symbolized_path_parameters
+ # The same as <tt>path_parameters</tt> with explicitly symbolized keys
+ def symbolized_path_parameters
@symbolized_path_parameters ||= path_parameters.symbolize_keys
end
# Returns a hash with the parameters used to form the path of the request.
# Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
#
- # Example:
+ # Example:
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
def path_parameters
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 163ed87fbb..482ac7d7a4 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -112,19 +112,23 @@ module ActionController #:nodoc:
protected
# Exception handler called when the performance of an action raises an exception.
def rescue_action(exception)
- log_error(exception) if logger
- erase_results if performed?
+ if handler_for_rescue(exception)
+ rescue_action_with_handler(exception)
+ else
+ log_error(exception) if logger
+ erase_results if performed?
- # Let the exception alter the response if it wants.
- # For example, MethodNotAllowed sets the Allow header.
- if exception.respond_to?(:handle_response!)
- exception.handle_response!(response)
- end
+ # Let the exception alter the response if it wants.
+ # For example, MethodNotAllowed sets the Allow header.
+ if exception.respond_to?(:handle_response!)
+ exception.handle_response!(response)
+ end
- if consider_all_requests_local || local_request?
- rescue_action_locally(exception)
- else
- rescue_action_in_public(exception)
+ if consider_all_requests_local || local_request?
+ rescue_action_locally(exception)
+ else
+ rescue_action_in_public(exception)
+ end
end
end
@@ -200,7 +204,7 @@ module ActionController #:nodoc:
def perform_action_with_rescue #:nodoc:
perform_action_without_rescue
rescue Exception => exception
- rescue_action_with_handler(exception) || rescue_action(exception)
+ rescue_action(exception)
end
def rescues_path(template_name)
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index b11aa5625b..0614b9a4d9 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -307,13 +307,13 @@ module ActionController
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
#
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
- #
+ #
# map.resources :articles do |article|
# article.resources :comments, :name_prefix => nil
- # end
- #
+ # end
+ #
# This will yield named resources like so:
- #
+ #
# comments_url(@article)
# comment_url(@article, @comment)
#
@@ -559,6 +559,7 @@ module ActionController
def action_options_for(action, resource, method = nil)
default_options = { :action => action.to_s }
require_id = !resource.kind_of?(SingletonResource)
+
case default_options[:action]
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 8f2672425f..de7425230c 100755
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -83,20 +83,48 @@ module ActionController # :nodoc:
set_content_length!
end
+ # Sets the Last-Modified response header. Returns whether it's older than
+ # the If-Modified-Since request header.
+ def last_modified!(utc_time)
+ headers['Last-Modified'] ||= utc_time.httpdate
+ if request && since = request.headers['HTTP_IF_MODIFIED_SINCE']
+ utc_time <= Time.rfc2822(since)
+ end
+ end
+
+ # Sets the ETag response header. Returns whether it matches the
+ # If-None-Match request header.
+ def etag!(tag)
+ headers['ETag'] ||= %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(tag))}")
+ if request && request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
+ true
+ end
+ end
private
def handle_conditional_get!
- if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
- self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
- self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
+ if nonempty_ok_response?
+ set_conditional_cache_control!
- if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
- self.headers['Status'] = '304 Not Modified'
+ if etag!(body)
+ headers['Status'] = '304 Not Modified'
self.body = ''
end
end
end
+ def nonempty_ok_response?
+ status = headers['Status']
+ ok = !status || status[0..2] == '200'
+ ok && body.is_a?(String) && !body.empty?
+ end
+
+ def set_conditional_cache_control!
+ if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
+ headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
+ end
+ end
+
def convert_content_type!
if content_type = headers.delete("Content-Type")
self.headers["type"] = content_type
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
index b8323847fd..912999d845 100644
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ b/actionpack/lib/action_controller/routing/builder.rb
@@ -76,6 +76,8 @@ module ActionController
defaults = (options.delete(:defaults) || {}).dup
conditions = (options.delete(:conditions) || {}).dup
+ validate_route_conditions(conditions)
+
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
options.each do |key, value|
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
@@ -198,6 +200,19 @@ module ActionController
route
end
+
+ private
+ def validate_route_conditions(conditions)
+ if method = conditions[:method]
+ if method == :head
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(method.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
+ end
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
index cd4a423e6b..4b70ea13f2 100644
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ b/actionpack/lib/action_controller/routing/optimisations.rb
@@ -76,7 +76,7 @@ module ActionController
elements << '#{request.host_with_port}'
end
- elements << '#{request.relative_url_root if request.relative_url_root}'
+ elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
# The last entry in <tt>route.segments</tt> appears to *always* be a
# 'divider segment' for '/' but we have assertions to ensure that
diff --git a/actionpack/lib/action_controller/streaming.rb b/actionpack/lib/action_controller/streaming.rb
index b944b52b98..333fb61b45 100644
--- a/actionpack/lib/action_controller/streaming.rb
+++ b/actionpack/lib/action_controller/streaming.rb
@@ -41,7 +41,7 @@ module ActionController #:nodoc:
# only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
# uses the web server to send the file, this may lower memory consumption on your server and
# it will not block your application for further requests.
- # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
+ # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
# http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
#
# The default Content-Type and Content-Disposition headers are
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index d50416272a..d0f4f3c71b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -100,7 +100,7 @@ module ActionController
@@controller_class = nil
class << self
- # Sets the controller class name. Useful if the name can't be inferred from test class.
+ # Sets the controller class name. Useful if the name can't be inferred from test class.
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
def tests(controller_class)
self.controller_class = controller_class
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index b9cf1e2bb0..66675aaa13 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -171,7 +171,7 @@ module ActionController #:nodoc:
# Was the response successful?
def success?
- response_code == 200
+ (200..299).include?(response_code)
end
# Was the URL not found?
@@ -205,17 +205,10 @@ module ActionController #:nodoc:
p.match(redirect_url) != nil
end
- # Returns the template path of the file which was used to
- # render this response (or nil)
- def rendered_file(with_controller = false)
- if template.first_render
- template.first_render.to_s
- end
- end
-
- # Was this template rendered by a file?
- def rendered_with_file?
- !rendered_file.nil?
+ # Returns the template of the file which was used to
+ # render this response (or nil)
+ def rendered_template
+ template._first_render
end
# A shortcut to the flash. Returns an empty hash if no session flash exists.
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index 457318472c..c2def7a84b 100644
--- a/actionpack/lib/action_controller/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -114,7 +114,7 @@ module ActionController
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
- # +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root.
+ # +relative_url_root+ set in ActionController::Base.relative_url_root.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
@@ -144,7 +144,7 @@ module ActionController
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
end
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
- url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root]
+ url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
generated = Routing::Routes.generate(options, {})
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
@@ -185,7 +185,7 @@ module ActionController
end
path = rewrite_path(options)
- rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
+ rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{options[:anchor]}" if options[:anchor]
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 9ab615c7a5..dd555b3792 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -34,6 +34,10 @@ require 'action_view/base'
require 'action_view/partials'
require 'action_view/template_error'
+I18n.backend.populate do
+ require 'action_view/locale/en-US.rb'
+end
+
ActionView::Base.class_eval do
include ActionView::Partials
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index fb82443060..bdcb1dc246 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -159,11 +159,11 @@ module ActionView #:nodoc:
class Base
include ERB::Util
- attr_accessor :base_path, :assigns, :template_extension, :first_render
+ attr_accessor :base_path, :assigns, :template_extension
attr_accessor :controller
+ attr_accessor :_first_render, :_last_render
attr_writer :template_format
- attr_accessor :current_render_extension
attr_accessor :output_buffer
@@ -171,13 +171,16 @@ module ActionView #:nodoc:
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
end
- # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
- @@cache_template_loading = false
- cattr_accessor :cache_template_loading
+ def self.cache_template_loading=(*args)
+ ActiveSupport::Deprecation.warn(
+ "config.action_view.cache_template_loading option has been deprecated" +
+ "and has no effect. Please remove it from your config files.", caller)
+ end
def self.cache_template_extensions=(*args)
- ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no effect. " <<
- "Please remove it from your config files.", caller)
+ ActiveSupport::Deprecation.warn(
+ "config.action_view.cache_template_extensions option has been" +
+ "deprecated and has no effect. Please remove it from your config files.", caller)
end
# Specify whether RJS responses should be wrapped in a try/catch block
@@ -199,10 +202,6 @@ module ActionView #:nodoc:
end
include CompiledTemplates
- # Cache public asset paths
- cattr_reader :computed_public_paths
- @@computed_public_paths = {}
-
def self.helper_modules #:nodoc:
helpers = []
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
@@ -313,7 +312,7 @@ module ActionView #:nodoc:
template
elsif template = self.view_paths[template_file_name]
template
- elsif first_render && template = self.view_paths["#{template_file_name}.#{first_render.extension}"]
+ elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"]
template
elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
@template_format = :html
@@ -324,8 +323,8 @@ module ActionView #:nodoc:
if self.class.warn_cache_misses && logger = ActionController::Base.logger
logger.debug "[PERFORMANCE] Rendering a template that was " +
"not found in view path. Templates outside the view path are " +
- "not cached and result in expensive disk operations. Move this " +
- "file into #{view_paths.join(':')} or add the folder to your " +
+ "not cached and result in expensive disk operations. Move this " +
+ "file into #{view_paths.join(':')} or add the folder to your " +
"view path list"
end
@@ -333,6 +332,9 @@ module ActionView #:nodoc:
end
end
+ extend ActiveSupport::Memoizable
+ memoize :pick_template
+
private
# Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt>
# is made available as local variables.
@@ -382,8 +384,14 @@ module ActionView #:nodoc:
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
- def execute(template, local_assigns = {})
- send(template.method(local_assigns), local_assigns) do |*names|
+ def set_controller_content_type(content_type)
+ if controller.respond_to?(:response)
+ controller.response.content_type ||= content_type
+ end
+ end
+
+ def execute(method, local_assigns = {})
+ send(method, local_assigns) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
end
end
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index e788ebf359..fce03ff605 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -25,7 +25,7 @@ module ActionView
# Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
# has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
#
- # form("post")
+ # form("post")
#
# would yield a form like the following (modulus formatting):
#
@@ -90,23 +90,41 @@ module ActionView
end
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
- # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+
- # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or
- # the actual object. As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
+ # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt>
+ # and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it
+ # accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
+ # passed in either as a string or a symbol.
+ # As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
#
# <%= error_message_on "post", "title" %>
# # => <div class="formError">can't be empty</div>
#
- # <%= error_message_on @post, "title" %>
+ # <%= error_message_on @post, :title %>
# # => <div class="formError">can't be empty</div>
#
- # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %>
- # # => <div class="inputError">Title simply can't be empty (or it won't work).</div>
- def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
+ # <%= error_message_on "post", "title",
+ # :prepend_text => "Title simply ",
+ # :append_text => " (or it won't work).",
+ # :css_class => "inputError" %>
+ def error_message_on(object, method, *args)
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
+ 'prepend_text, append_text, and css_class arguments', caller)
+
+ options[:prepend_text] = args[0] || ''
+ options[:append_text] = args[1] || ''
+ options[:css_class] = args[2] || 'formError'
+ end
+ options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
+
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
(errors = obj.errors.on(method))
- content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
- else
+ content_tag("div",
+ "#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}",
+ :class => options[:css_class]
+ )
+ else
''
end
end
@@ -133,7 +151,7 @@ module ActionView
#
# To specify the display for one object, you simply provide its name as a parameter.
# For example, for the <tt>@user</tt> model:
- #
+ #
# error_messages_for 'user'
#
# To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
@@ -151,12 +169,14 @@ module ActionView
# instance yourself and set it up. View the source of this method to see how easy it is.
def error_messages_for(*params)
options = params.extract_options!.symbolize_keys
+
if object = options.delete(:object)
objects = [object].flatten
else
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
end
- count = objects.inject(0) {|sum, object| sum + object.errors.count }
+
+ count = objects.inject(0) {|sum, object| sum + object.errors.count }
unless count.zero?
html = {}
[:id, :class].each do |key|
@@ -168,16 +188,25 @@ module ActionView
end
end
options[:object_name] ||= params.first
- options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
- options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
- error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
- contents = ''
- contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
- contents << content_tag(:p, options[:message]) unless options[:message].blank?
- contents << content_tag(:ul, error_messages)
+ I18n.with_options :locale => options[:locale], :scope => [:active_record, :error] do |locale|
+ header_message = if options.include?(:header_message)
+ options[:header_message]
+ else
+ object_name = options[:object_name].to_s.gsub('_', ' ')
+ object_name = I18n.t(object_name, :default => object_name)
+ locale.t :header_message, :count => count, :object_name => object_name
+ end
+ message = options.include?(:message) ? options[:message] : locale.t(:message)
+ error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
- content_tag(:div, contents, html)
+ contents = ''
+ contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
+ contents << content_tag(:p, message) unless message.blank?
+ contents << content_tag(:ul, error_messages)
+
+ content_tag(:div, contents, html)
+ end
else
''
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index bf13945844..769eada120 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -5,12 +5,12 @@ require 'action_view/helpers/tag_helper'
module ActionView
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
- # as images, javascripts, stylesheets, and feeds. These methods do not verify
- # the assets exist before linking to them.
+ # as images, javascripts, stylesheets, and feeds. These methods do not verify
+ # the assets exist before linking to them.
#
# === Using asset hosts
# By default, Rails links to these assets on the current host in the public
- # folder, but you can direct Rails to link to assets from a dedicated assets server by
+ # folder, but you can direct Rails to link to assets from a dedicated assets server by
# setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
# let's say your asset host is <tt>assets.example.com</tt>.
#
@@ -22,16 +22,16 @@ module ActionView
#
# This is useful since browsers typically open at most two connections to a single host,
# which means your assets often wait in single file for their turn to load. You can
- # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
+ # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
# to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
- # so browsers will open eight connections rather than two.
+ # so browsers will open eight connections rather than two.
#
# image_tag("rails.png")
# => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
# stylesheet_link_tag("application")
# => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
#
- # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
+ # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
# the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
# your ISP.
#
@@ -86,7 +86,7 @@ module ActionView
# asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
# which then updates the URL as the timestamp is part of that, which in turn busts the cache).
#
- # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
+ # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
# advantage of this feature. Here's an example for Apache:
#
# # Asset Expiration
@@ -95,16 +95,17 @@ module ActionView
# ExpiresDefault "access plus 1 year"
# </FilesMatch>
#
- # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
+ # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
# have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
# work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
- # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
+ # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
# requested over and over).
module AssetTagHelper
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
-
+ JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].map(&:to_s).freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
+
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
@@ -154,10 +155,6 @@ module ActionView
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
- JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
- @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
- @@stylesheet_expansions = {}
-
# Returns an html script tag for each of the +sources+ provided. You
# can pass in the filename (.js extension is optional) of javascript files
# that exist in your public/javascripts directory for inclusion into the
@@ -193,7 +190,7 @@ module ActionView
#
# * = The application.js file is only referenced if it exists
#
- # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
+ # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
#
# You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
@@ -218,7 +215,7 @@ module ActionView
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
- # environment).
+ # environment).
#
# ==== Examples
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
@@ -259,6 +256,8 @@ module ActionView
end
end
+ @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
+
# Register one or more javascript files to be included when <tt>symbol</tt>
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
# to be called from plugin initialization to register javascript files
@@ -274,6 +273,8 @@ module ActionView
@@javascript_expansions.merge!(expansions)
end
+ @@stylesheet_expansions = {}
+
# Register one or more stylesheet files to be included when <tt>symbol</tt>
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
# to be called from plugin initialization to register stylesheet files
@@ -439,9 +440,9 @@ module ActionView
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
- # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
+ # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
+ # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
def image_tag(source, options = {})
options.symbolize_keys!
@@ -454,23 +455,15 @@ module ActionView
end
if mouseover = options.delete(:mouseover)
- options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
- options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
+ options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
+ options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
end
tag("img", options)
end
private
- def file_exist?(path)
- @@file_exist_cache ||= {}
- if !(@@file_exist_cache[path] ||= File.exist?(path))
- @@file_exist_cache[path] = true
- false
- else
- true
- end
- end
+ COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!.threadsafe!
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
@@ -483,14 +476,14 @@ module ActionView
if has_request
[ @controller.request.protocol,
ActionController::Base.asset_host.to_s,
- @controller.request.relative_url_root,
+ ActionController::Base.relative_url_root,
dir, source, ext, include_host ].join
else
[ ActionController::Base.asset_host.to_s,
dir, source, ext, include_host ].join
end
- ActionView::Base.computed_public_paths[cache_key] ||=
+ source = COMPUTED_PUBLIC_PATHS.fetch(cache_key) do
begin
source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))
@@ -499,25 +492,27 @@ module ActionView
else
source = "/#{dir}/#{source}" unless source[0] == ?/
if has_request
- unless source =~ %r{^#{@controller.request.relative_url_root}/}
- source = "#{@controller.request.relative_url_root}#{source}"
+ unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
+ source = "#{ActionController::Base.relative_url_root}#{source}"
end
end
- source = rewrite_asset_path(source)
- if include_host
- host = compute_asset_host(source)
+ rewrite_asset_path(source)
+ end
+ end
+ end
- if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
- host = "#{@controller.request.protocol}#{host}"
- end
+ if include_host && source !~ %r{^[-a-z]+://}
+ host = compute_asset_host(source)
- "#{host}#{source}"
- else
- source
- end
- end
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
+ host = "#{@controller.request.protocol}#{host}"
end
+
+ "#{host}#{source}"
+ else
+ source
+ end
end
# Pick an asset host for this source. Returns +nil+ if no host is set,
@@ -591,7 +586,7 @@ module ActionView
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
end.flatten
- expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
+ expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
expanded_sources
end
end
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 930c397785..64d1ad2715 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,8 +32,7 @@ module ActionView
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
- handler = Template.handler_class_for_extension(current_render_extension.to_sym)
- handler.new(@controller).cache_fragment(block, name, options)
+ @controller.fragment_for(output_buffer, name, options, &block)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 720e2da8cc..e86ca27f31 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -122,14 +122,15 @@ module ActionView
nil
end
- private
- def with_output_buffer(buf = '')
- self.output_buffer, old_buffer = buf, output_buffer
- yield
- output_buffer
- ensure
- self.output_buffer = old_buffer
- end
+ # Use an alternate output buffer for the duration of the block.
+ # Defaults to a new empty string.
+ def with_output_buffer(buf = '') #:nodoc:
+ self.output_buffer, old_buffer = buf, output_buffer
+ yield
+ output_buffer
+ ensure
+ self.output_buffer = old_buffer
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 0735ed07ee..c7a1d40ff2 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -3,14 +3,15 @@ require 'action_view/helpers/tag_helper'
module ActionView
module Helpers
- # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
- # share a number of common options that are as follows:
+ # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the
+ # select-type methods share a number of common options that are as follows:
#
- # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
- # birthday[month] instead of date[month] if passed to the select_month method.
+ # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
+ # would give birthday[month] instead of date[month] if passed to the select_month method.
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
- # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month
- # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]".
+ # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
+ # the select_month method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of
+ # "date[month]".
module DateHelper
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
@@ -58,33 +59,38 @@ module ActionView
# distance_of_time_in_words(to_time, from_time, true) # => over 6 years
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
distance_in_seconds = ((to_time - from_time).abs).round
- case distance_in_minutes
- when 0..1
- return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
- case distance_in_seconds
- when 0..4 then 'less than 5 seconds'
- when 5..9 then 'less than 10 seconds'
- when 10..19 then 'less than 20 seconds'
- when 20..39 then 'half a minute'
- when 40..59 then 'less than a minute'
- else '1 minute'
- end
+ I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
+ case distance_in_minutes
+ when 0..1
+ return distance_in_minutes == 0 ?
+ locale.t(:less_than_x_minutes, :count => 1) :
+ locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
+
+ case distance_in_seconds
+ when 0..4 then locale.t :less_than_x_seconds, :count => 5
+ when 5..9 then locale.t :less_than_x_seconds, :count => 10
+ when 10..19 then locale.t :less_than_x_seconds, :count => 20
+ when 20..39 then locale.t :half_a_minute
+ when 40..59 then locale.t :less_than_x_minutes, :count => 1
+ else locale.t :x_minutes, :count => 1
+ end
- when 2..44 then "#{distance_in_minutes} minutes"
- when 45..89 then 'about 1 hour'
- when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
- when 1440..2879 then '1 day'
- when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
- when 43200..86399 then 'about 1 month'
- when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
- when 525600..1051199 then 'about 1 year'
- else "over #{(distance_in_minutes / 525600).round} years"
+ when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
+ when 45..89 then locale.t :about_x_hours, :count => 1
+ when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ when 1440..2879 then locale.t :x_days, :count => 1
+ when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round
+ when 43200..86399 then locale.t :about_x_months, :count => 1
+ when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round
+ when 525600..1051199 then locale.t :about_x_years, :count => 1
+ else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round
+ end
end
end
@@ -102,15 +108,18 @@ module ActionView
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
- # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
- # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
- # which accepts all the keys that each of the individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of
- # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll
- # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
- # set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in
- # the desired order. Symbols may be omitted and the respective select is not included.
+ # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). It's
+ # possible to tailor the selects through the +options+ hash, which accepts all the keys that each of the
+ # individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of discard
+ # options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set
+ # to true, they'll drop the respective select. Discarding the month select will also automatically discard the
+ # day select. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an
+ # array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. Symbols may be omitted
+ # and the respective select is not included.
#
- # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>.
+ # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>,
+ # <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>.
#
# Passing <tt>:disabled => true</tt> as part of the +options+ will make elements inaccessible for change.
#
@@ -128,7 +137,7 @@ module ActionView
#
# # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
- # # and without a day select box.
+ # # and without a day select box.
# date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
@@ -150,8 +159,8 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
- # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
- # choices are valid.
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
+ # all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
@@ -175,12 +184,12 @@ module ActionView
# # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
# time_select("mail", "sent_at")
#
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
- # # the sunrise attribute.
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
+ # # the sunrise attribute.
# time_select("post", "start_time", :include_seconds => true)
#
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
- # # the submission_time attribute.
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
+ # # the submission_time attribute.
# time_select("entry", "submission_time", :include_seconds => true)
#
# # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
@@ -188,14 +197,15 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
- # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
- # choices are valid.
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
+ # all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
end
- # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
- # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
+ # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
+ # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
+ # by +object+). Examples:
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -203,16 +213,16 @@ module ActionView
# # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
# datetime_select("post", "written_on")
#
- # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
+ # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
# # post variable in the written_on attribute.
# datetime_select("post", "written_on", :start_year => 1995)
#
- # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the
- # # trip variable in the departing attribute.
+ # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
+ # # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
- # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on
- # # attribute.
+ # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
+ # # as the written_on attribute.
# datetime_select("post", "written_on", :discard_type => true)
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -222,9 +232,10 @@ module ActionView
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
# It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
- # will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
- # keys to the +options+ to control visual display of the elements.
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
+ # it will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt>,
+ # <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to control visual display of
+ # the elements.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -245,7 +256,12 @@ module ActionView
# # with a '/' between each date field.
# select_datetime(my_date_time, :date_separator => '/')
#
- # # Generates a datetime select that discards the type of the field and defaults to the datetime in
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
+ # # with a date fields separated by '/', time fields separated by '' and the date and time fields
+ # # separated by a comma (',').
+ # select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',')
+ #
+ # # Generates a datetime select that discards the type of the field and defaults to the datetime in
# # my_date_time (four days after today)
# select_datetime(my_date_time, :discard_type => true)
#
@@ -256,7 +272,7 @@ module ActionView
def select_datetime(datetime = Time.current, options = {}, html_options = {})
separator = options[:datetime_separator] || ''
select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options)
- end
+ end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
@@ -278,27 +294,29 @@ module ActionView
# # with the fields ordered year, month, day rather than month, day, year.
# select_date(my_date, :order => [:year, :month, :day])
#
- # # Generates a date select that discards the type of the field and defaults to the date in
+ # # Generates a date select that discards the type of the field and defaults to the date in
# # my_date (six days after today)
# select_date(my_date, :discard_type => true)
#
+ # # Generates a date select that defaults to the date in my_date,
+ # # which has fields separated by '/'
+ # select_date(my_date, :date_separator => '/')
+ #
# # Generates a date select that defaults to the datetime in my_date (six days after today)
# # prefixed with 'payday' rather than 'date'
# select_date(my_date, :prefix => 'payday')
#
def select_date(date = Date.current, options = {}, html_options = {})
- options[:order] ||= []
+ options.reverse_merge!(:order => [], :date_separator => '')
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
- select_date = ''
- options[:order].each do |o|
- select_date << self.send("select_#{o}", date, options, html_options)
- end
- select_date
+ options[:order].inject([]) { |s, o|
+ s << self.send("select_#{o}", date, options, html_options)
+ }.join(options[:date_separator])
end
# Returns a set of html select-tags (one for hour and minute)
- # You can set <tt>:time_separator</tt> key to format the output, and
+ # You can set <tt>:time_separator</tt> key to format the output, and
# the <tt>:include_seconds</tt> option to include an input for seconds.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
@@ -313,7 +331,7 @@ module ActionView
# select_time()
#
# # Generates a time select that defaults to the time in my_time,
- # # which has fields separated by ':'
+ # # which has fields separated by ':'
# select_time(my_time, :time_separator => ':')
#
# # Generates a time select that defaults to the time in my_time,
@@ -326,7 +344,8 @@ module ActionView
#
def select_time(datetime = Time.current, options = {}, html_options = {})
separator = options[:time_separator] || ''
- select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
+ select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) +
+ (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
@@ -341,26 +360,16 @@ module ActionView
#
# # Generates a select field for seconds that defaults to the number given
# select_second(33)
- #
+ #
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
# # that is named 'interval' rather than 'second'
# select_second(my_time, :field_name => 'interval')
#
def select_second(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
- if options[:use_hidden]
- options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
- else
- second_options = []
- 0.upto(59) do |second|
- second_options << ((val == second) ?
- content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second), :selected => "selected") :
- content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second))
- )
- second_options << "\n"
- end
- select_html(options[:field_name] || 'second', second_options.join, options, html_options)
- end
+ options[:use_hidden] ?
+ (options[:include_seconds] ? _date_hidden_html(options[:field_name] || 'second', val, options) : '') :
+ _date_select_html(options[:field_name] || 'second', _date_build_options(val), options, html_options)
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
@@ -376,26 +385,17 @@ module ActionView
#
# # Generates a select field for minutes that defaults to the number given
# select_minute(14)
- #
+ #
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
# # that is named 'stride' rather than 'second'
# select_minute(my_time, :field_name => 'stride')
#
def select_minute(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
- if options[:use_hidden]
- hidden_html(options[:field_name] || 'minute', val, options)
- else
- minute_options = []
- 0.step(59, options[:minute_step] || 1) do |minute|
- minute_options << ((val == minute) ?
- content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute), :selected => "selected") :
- content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute))
- )
- minute_options << "\n"
- end
- select_html(options[:field_name] || 'minute', minute_options.join, options, html_options)
- end
+ options[:use_hidden] ?
+ _date_hidden_html(options[:field_name] || 'minute', val, options) :
+ _date_select_html(options[:field_name] || 'minute',
+ _date_build_options(val, :step => options[:minute_step]), options, html_options)
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
@@ -410,26 +410,15 @@ module ActionView
#
# # Generates a select field for minutes that defaults to the number given
# select_minute(14)
- #
+ #
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
# # that is named 'stride' rather than 'second'
# select_minute(my_time, :field_name => 'stride')
#
def select_hour(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
- if options[:use_hidden]
- hidden_html(options[:field_name] || 'hour', val, options)
- else
- hour_options = []
- 0.upto(23) do |hour|
- hour_options << ((val == hour) ?
- content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour), :selected => "selected") :
- content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour))
- )
- hour_options << "\n"
- end
- select_html(options[:field_name] || 'hour', hour_options.join, options, html_options)
- end
+ options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'hour', val, options) :
+ _date_select_html(options[:field_name] || 'hour', _date_build_options(val, :end => 23), options, html_options)
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
@@ -444,36 +433,27 @@ module ActionView
#
# # Generates a select field for days that defaults to the number given
# select_day(5)
- #
+ #
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'
# select_day(my_time, :field_name => 'due')
#
def select_day(date, options = {}, html_options = {})
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
- if options[:use_hidden]
- hidden_html(options[:field_name] || 'day', val, options)
- else
- day_options = []
- 1.upto(31) do |day|
- day_options << ((val == day) ?
- content_tag(:option, day, :value => day, :selected => "selected") :
- content_tag(:option, day, :value => day)
- )
- day_options << "\n"
- end
- select_html(options[:field_name] || 'day', day_options.join, options, html_options)
- end
+ options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'day', val, options) :
+ _date_select_html(options[:field_name] || 'day',
+ _date_build_options(val, :start => 1, :end => 31, :leading_zeros => false),
+ options, html_options)
end
- # Returns a select tag with options for each of the months January through December with the current month selected.
- # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
- # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
- # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
- # set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
- # set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
- # <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. Override the field name using the
- # <tt>:field_name</tt> option, 'month' by default.
+ # Returns a select tag with options for each of the months January through December with the current month
+ # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
+ # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
+ # instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you
+ # want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
+ # to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
+ # to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
+ # Override the field name using the <tt>:field_name</tt> option, 'month' by default.
#
# ==== Examples
# # Generates a select field for months that defaults to the current month that
@@ -485,7 +465,7 @@ module ActionView
# select_month(Date.today, :field_name => 'start')
#
# # Generates a select field for months that defaults to the current month that
- # # will use keys like "1", "3".
+ # # will use keys like "1", "3".
# select_month(Date.today, :use_month_numbers => true)
#
# # Generates a select field for months that defaults to the current month that
@@ -501,13 +481,19 @@ module ActionView
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
def select_month(date, options = {}, html_options = {})
+ locale = options[:locale]
+
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
- hidden_html(options[:field_name] || 'month', val, options)
+ _date_hidden_html(options[:field_name] || 'month', val, options)
else
month_options = []
- month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
+ month_names = options[:use_month_names] || begin
+ key = options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
+ I18n.translate key, :locale => locale
+ end
month_names.unshift(nil) if month_names.size < 13
+
1.upto(12) do |month_number|
month_name = if options[:use_month_numbers]
month_number
@@ -523,14 +509,15 @@ module ActionView
)
month_options << "\n"
end
- select_html(options[:field_name] || 'month', month_options.join, options, html_options)
+ _date_select_html(options[:field_name] || 'month', month_options.join, options, html_options)
end
end
- # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
- # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
- # lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be
- # substituted for a year given as a number. Override the field name using the <tt>:field_name</tt> option, 'year' by default.
+ # Returns a select tag with options for each of the five years on each side of the current, which is selected.
+ # The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the
+ # +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or
+ # greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
+ # Override the field name using the <tt>:field_name</tt> option, 'year' by default.
#
# ==== Examples
# # Generates a select field for years that defaults to the current year that
@@ -551,38 +538,48 @@ module ActionView
#
def select_year(date, options = {}, html_options = {})
if !date || date == 0
- value = ''
+ val = ''
middle_year = Date.today.year
elsif date.kind_of?(Fixnum)
- value = middle_year = date
+ val = middle_year = date
else
- value = middle_year = date.year
+ val = middle_year = date.year
end
if options[:use_hidden]
- hidden_html(options[:field_name] || 'year', value, options)
+ _date_hidden_html(options[:field_name] || 'year', val, options)
else
- year_options = ''
- start_year = options[:start_year] || middle_year - 5
- end_year = options[:end_year] || middle_year + 5
- step_val = start_year < end_year ? 1 : -1
-
- start_year.step(end_year, step_val) do |year|
- if value == year
- year_options << content_tag(:option, year, :value => year, :selected => "selected")
- else
- year_options << content_tag(:option, year, :value => year)
- end
- year_options << "\n"
- end
- select_html(options[:field_name] || 'year', year_options, options, html_options)
+ options[:start_year] ||= middle_year - 5
+ options[:end_year] ||= middle_year + 5
+ step = options[:start_year] < options[:end_year] ? 1 : -1
+
+ _date_select_html(options[:field_name] || 'year',
+ _date_build_options(val,
+ :start => options[:start_year],
+ :end => options[:end_year],
+ :step => step,
+ :leading_zeros => false
+ ), options, html_options)
end
end
private
+ def _date_build_options(selected, options={})
+ options.reverse_merge!(:start => 0, :end => 59, :step => 1, :leading_zeros => true)
+
+ select_options = []
+ (options[:start] || 0).step((options[:end] || 59), options[:step] || 1) do |i|
+ value = options[:leading_zeros] ? sprintf("%02d", i) : i
+ tag_options = { :value => value }
+ tag_options[:selected] = "selected" if selected == i
+
+ select_options << content_tag(:option, value, tag_options)
+ end
+ select_options.join("\n") + "\n"
+ end
- def select_html(type, html_options, options, select_tag_options = {})
- name_and_id_from_options(options, type)
+ def _date_select_html(type, html_options, options, select_tag_options = {})
+ _date_name_and_id_from_options(options, type)
select_options = {:id => options[:id], :name => options[:name]}
select_options.merge!(:disabled => 'disabled') if options[:disabled]
select_options.merge!(select_tag_options) unless select_tag_options.empty?
@@ -592,19 +589,15 @@ module ActionView
content_tag(:select, select_html, select_options) + "\n"
end
- def hidden_html(type, value, options)
- name_and_id_from_options(options, type)
+ def _date_hidden_html(type, value, options)
+ _date_name_and_id_from_options(options, type)
hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n"
end
- def name_and_id_from_options(options, type)
+ def _date_name_and_id_from_options(options, type)
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
end
-
- def leading_zero_on_single_digits(number)
- number > 9 ? number : "0#{number}"
- end
end
class InstanceTag #:nodoc:
@@ -624,6 +617,8 @@ module ActionView
private
def date_or_time_select(options, html_options = {})
+ locale = options[:locale]
+
defaults = { :discard_type => true }
options = defaults.merge(options)
datetime = value(object)
@@ -631,7 +626,7 @@ module ActionView
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
- order = (options[:order] ||= [:year, :month, :day])
+ order = options[:order] ||= I18n.translate(:'date.order', :locale => locale)
# Discard explicit and implicit by not being included in the :order
discard = {}
@@ -660,7 +655,11 @@ module ActionView
# This ensures AR can reconstruct valid dates using ParseDate
next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
- date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options))
+ date_or_time_select.insert(0,
+ self.send("select_#{param}",
+ datetime,
+ options_with_prefix(position[param], options.merge(:use_hidden => discard[param])),
+ html_options))
date_or_time_select.insert(0,
case param
when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
@@ -668,7 +667,6 @@ module ActionView
when :second then options[:include_seconds] ? " : " : ""
else ""
end)
-
end
date_or_time_select
@@ -696,7 +694,7 @@ module ActionView
default[:sec] ||= default[:second]
time = Time.current
-
+
[:year, :month, :day, :hour, :min, :sec].each do |key|
default[key] ||= time.send(key)
end
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index ea70a697de..90863fca08 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -11,16 +11,16 @@ module ActionView
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
# debug(@user)
# # =>
- # <pre class='debug_dump'>--- !ruby/object:User
- # attributes:
- # &nbsp; updated_at:
+ # <pre class='debug_dump'>--- !ruby/object:User
+ # attributes:
+ # &nbsp; updated_at:
# &nbsp; username: testing
- #
+ #
# &nbsp; age: 42
# &nbsp; password: xyz
- # &nbsp; created_at:
+ # &nbsp; created_at:
# attributes_cache: {}
- #
+ #
# new_record: true
# </pre>
diff --git a/actionpack/lib/action_view/helpers/form_country_helper.rb b/actionpack/lib/action_view/helpers/form_country_helper.rb
new file mode 100644
index 0000000000..84e811f61d
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/form_country_helper.rb
@@ -0,0 +1,92 @@
+require 'action_view/helpers/form_options_helper'
+
+module ActionView
+ module Helpers
+ module FormCountryHelper
+
+ # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
+ def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
+ end
+
+ # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
+ # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
+ # that they will be listed above the rest of the (long) list.
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def country_options_for_select(selected = nil, priority_countries = nil)
+ country_options = ""
+
+ if priority_countries
+ country_options += options_for_select(priority_countries, selected)
+ country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
+ end
+
+ return country_options + options_for_select(COUNTRIES, selected)
+ end
+
+ private
+
+ # All the countries included in the country_options output.
+ COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
+ "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
+ "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
+ "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
+ "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
+ "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
+ "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
+ "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
+ "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
+ "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
+ "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
+ "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
+ "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
+ "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
+ "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
+ "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
+ "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
+ "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
+ "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
+ "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
+ "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
+ "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
+ "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
+ "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
+ "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
+ "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
+ "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
+ "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
+ "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
+ "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
+ "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
+ end
+
+ class InstanceTag #:nodoc:
+ include FormCountryHelper
+
+ def to_country_select_tag(priority_countries, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ value = value(object)
+ content_tag("select",
+ add_options(
+ country_options_for_select(value, priority_countries),
+ options, value
+ ), html_options
+ )
+ end
+ end
+
+ class FormBuilder
+ def country_select(method, priority_countries = nil, options = {}, html_options = {})
+ @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index fa26aa4640..7bb82ba5bb 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -304,10 +304,6 @@ module ActionView
when String, Symbol
object_name = record_or_name_or_array
object = args.first
- when Array
- object = record_or_name_or_array.last
- object_name = ActionController::RecordIdentifier.singular_class_name(object)
- apply_form_for_options!(record_or_name_or_array, options)
else
object = record_or_name_or_array
object_name = ActionController::RecordIdentifier.singular_class_name(object)
@@ -532,10 +528,10 @@ module ActionView
def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object= template_object
+ @template_object = template_object
@object = object
- if @object_name.sub!(/\[\]$/,"")
- if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
@auto_index = object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
@@ -712,7 +708,7 @@ module ActionView
end
def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
end
def sanitized_method_name
@@ -730,6 +726,13 @@ module ActionView
def initialize(object_name, object, template, options, proc)
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
@default_options = @options ? @options.slice(:index) : {}
+ if @object_name.to_s.match(/\[\]$/)
+ if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ @auto_index = object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
end
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
@@ -742,16 +745,25 @@ module ActionView
end
def fields_for(record_or_name_or_array, *args, &block)
+ if options.has_key?(:index)
+ index = "[#{options[:index]}]"
+ elsif defined?(@auto_index)
+ self.object_name = @object_name.to_s.sub(/\[\]$/,"")
+ index = "[#{@auto_index}]"
+ else
+ index = ""
+ end
+
case record_or_name_or_array
when String, Symbol
- name = "#{object_name}[#{record_or_name_or_array}]"
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
when Array
object = record_or_name_or_array.last
- name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
args.unshift(object)
else
object = record_or_name_or_array
- name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
args.unshift(object)
end
@@ -770,8 +782,8 @@ module ActionView
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
- def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
- @template.error_message_on(@object, method, prepend_text, append_text, css_class)
+ def error_message_on(method, *args)
+ @template.error_message_on(@object, method, *args)
end
def error_messages(options = {})
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 0bd44c5aca..9aae945408 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -133,11 +133,6 @@ module ActionView
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
- # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
- def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
- end
-
# Return select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
@@ -274,24 +269,6 @@ module ActionView
end
end
- # Returns a string of option tags for most countries in the
- # world (as defined in COUNTRIES). Supply a country name as
- # +selected+ to have it marked as the selected option tag. You
- # can also supply an array of countries as +priority_countries+,
- # so that they will be listed above the rest of the (long) list.
- #
- # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
- def country_options_for_select(selected = nil, priority_countries = nil)
- country_options = ""
-
- if priority_countries
- country_options += options_for_select(priority_countries, selected)
- country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
- end
-
- return country_options + options_for_select(COUNTRIES, selected)
- end
-
# Returns a string of option tags for pretty much any time zone in the
# world. Supply a TimeZone name as +selected+ to have it marked as the
# selected option tag. You can also supply an array of TimeZone objects
@@ -349,43 +326,7 @@ module ActionView
end
# All the countries included in the country_options output.
- COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
- "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
- "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
- "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
- "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
- "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
- "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
- "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
- "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
- "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
- "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
- "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
- "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
- "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
- "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
- "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
- "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
- "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
- "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
- "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
- "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
- "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
- "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
- "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
- "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
- "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
- "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
- "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
- "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
- "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
- "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
- "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
- "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
- "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
- "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
- "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
- "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
+ COUNTRIES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new 'COUNTRIES', 'ActionView::Helpers::FormCountryHelper::COUNTRIES'
end
class InstanceTag #:nodoc:
@@ -408,18 +349,6 @@ module ActionView
)
end
- def to_country_select_tag(priority_countries, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- content_tag("select",
- add_options(
- country_options_for_select(value, priority_countries),
- options, value
- ), html_options
- )
- end
-
def to_time_zone_select_tag(priority_zones, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
@@ -447,19 +376,15 @@ module ActionView
class FormBuilder
def select(method, choices, options = {}, html_options = {})
- @template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
- end
-
- def country_select(method, priority_countries = nil, options = {}, html_options = {})
- @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
+ @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
end
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
- @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
end
end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 6c2d76c85f..32089442b7 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -44,7 +44,7 @@ module ActionView
include PrototypeHelper
- # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
+ # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
# onclick handler and return false after the fact.
#
# The first argument +name+ is used as the link text.
@@ -97,8 +97,8 @@ module ActionView
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
-
- # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
+
+ # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
# onclick handler.
#
# The first argument +name+ is used as the button's value or display text.
diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js
index 546f9fe449..2c70b8a7e8 100644
--- a/actionpack/lib/action_view/helpers/javascripts/prototype.js
+++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js
@@ -1,5 +1,5 @@
-/* Prototype JavaScript framework, version 1.6.0.1
- * (c) 2005-2007 Sam Stephenson
+/* Prototype JavaScript framework, version 1.6.0.2
+ * (c) 2005-2008 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/
@@ -7,7 +7,7 @@
*--------------------------------------------------------------------------*/
var Prototype = {
- Version: '1.6.0.1',
+ Version: '1.6.0.2',
Browser: {
IE: !!(window.attachEvent && !window.opera),
@@ -110,7 +110,7 @@ Object.extend(Object, {
try {
if (Object.isUndefined(object)) return 'undefined';
if (object === null) return 'null';
- return object.inspect ? object.inspect() : object.toString();
+ return object.inspect ? object.inspect() : String(object);
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
@@ -171,7 +171,8 @@ Object.extend(Object, {
},
isArray: function(object) {
- return object && object.constructor === Array;
+ return object != null && typeof object == "object" &&
+ 'splice' in object && 'join' in object;
},
isHash: function(object) {
@@ -578,7 +579,7 @@ var Template = Class.create({
}
return before + String.interpret(ctx);
- }.bind(this));
+ });
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
@@ -806,20 +807,20 @@ Object.extend(Enumerable, {
function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
- var length = iterable.length, results = new Array(length);
+ var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
if (Prototype.Browser.WebKit) {
- function $A(iterable) {
+ $A = function(iterable) {
if (!iterable) return [];
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
iterable.toArray) return iterable.toArray();
- var length = iterable.length, results = new Array(length);
+ var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
- }
+ };
}
Array.from = $A;
@@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, {
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
- || (this.options.evalJS && contentType
+ || (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
@@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, {
}
},
+ 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);
+ return this.transport.getResponseHeader(name) || null;
} catch (e) { return null }
},
@@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({
if (!json) return null;
json = decodeURIComponent(escape(json));
try {
- return json.evalJSON(this.request.options.sanitizeJSON);
+ return json.evalJSON(this.request.options.sanitizeJSON ||
+ !this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
@@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({
this.responseText.blank())
return null;
try {
- return this.responseText.evalJSON(options.sanitizeJSON);
+ return this.responseText.evalJSON(options.sanitizeJSON ||
+ !this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
@@ -1608,24 +1620,28 @@ Element.Methods = {
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
insertions = {bottom:insertions};
- var content, t, range;
+ var content, insert, tagName, childNodes;
- for (position in insertions) {
+ for (var position in insertions) {
content = insertions[position];
position = position.toLowerCase();
- t = Element._insertionTranslations[position];
+ insert = Element._insertionTranslations[position];
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
- t.insert(element, content);
+ insert(element, content);
continue;
}
content = Object.toHTML(content);
- range = element.ownerDocument.createRange();
- t.initializeRange(element, range);
- t.insert(element, range.createContextualFragment(content.stripScripts()));
+ 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();
}
@@ -1670,7 +1686,7 @@ Element.Methods = {
},
descendants: function(element) {
- return $(element).getElementsBySelector("*");
+ return $(element).select("*");
},
firstDescendant: function(element) {
@@ -1709,32 +1725,31 @@ Element.Methods = {
element = $(element);
if (arguments.length == 1) return $(element.parentNode);
var ancestors = element.ancestors();
- return expression ? Selector.findElement(ancestors, expression, index) :
- ancestors[index || 0];
+ return Object.isNumber(expression) ? ancestors[expression] :
+ Selector.findElement(ancestors, expression, index);
},
down: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return element.firstDescendant();
- var descendants = element.descendants();
- return expression ? Selector.findElement(descendants, expression, index) :
- descendants[index || 0];
+ return Object.isNumber(expression) ? element.descendants()[expression] :
+ element.select(expression)[index || 0];
},
previous: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
var previousSiblings = element.previousSiblings();
- return expression ? Selector.findElement(previousSiblings, expression, index) :
- previousSiblings[index || 0];
+ return Object.isNumber(expression) ? previousSiblings[expression] :
+ Selector.findElement(previousSiblings, expression, index);
},
next: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
var nextSiblings = element.nextSiblings();
- return expression ? Selector.findElement(nextSiblings, expression, index) :
- nextSiblings[index || 0];
+ return Object.isNumber(expression) ? nextSiblings[expression] :
+ Selector.findElement(nextSiblings, expression, index);
},
select: function() {
@@ -1860,7 +1875,8 @@ Element.Methods = {
do { ancestor = ancestor.parentNode; }
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
}
- if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+ if (nextAncestor && nextAncestor.sourceIndex)
+ return (e > a && e < nextAncestor.sourceIndex);
}
while (element = element.parentNode)
@@ -2004,7 +2020,7 @@ Element.Methods = {
if (element) {
if (element.tagName == 'BODY') break;
var p = Element.getStyle(element, 'position');
- if (p == 'relative' || p == 'absolute') break;
+ if (p !== 'static') break;
}
} while (element);
return Element._returnOffset(valueL, valueT);
@@ -2153,46 +2169,6 @@ Element._attributeTranslations = {
}
};
-
-if (!document.createRange || Prototype.Browser.Opera) {
- Element.Methods.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 t = Element._insertionTranslations, content, position, pos, tagName;
-
- for (position in insertions) {
- content = insertions[position];
- position = position.toLowerCase();
- pos = t[position];
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- pos.insert(element, content);
- continue;
- }
-
- content = Object.toHTML(content);
- tagName = ((position == 'before' || position == 'after')
- ? element.parentNode : element).tagName.toUpperCase();
-
- if (t.tags[tagName]) {
- var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
- if (position == 'top' || position == 'after') fragments.reverse();
- fragments.each(pos.insert.curry(element));
- }
- else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
-
- content.evalScripts.bind(content).defer();
- }
-
- return element;
- };
-}
-
if (Prototype.Browser.Opera) {
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
function(proceed, element, style) {
@@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) {
}
else if (Prototype.Browser.IE) {
- $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+ // IE doesn't report offsets correctly for static elements, so we change them
+ // to "relative" to get the values, then change them back.
+ Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+ function(proceed, element) {
+ element = $(element);
+ 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;
+ }
+ );
+
+ $w('positionedOffset viewportOffset').each(function(method) {
Element.Methods[method] = Element.Methods[method].wrap(
function(proceed, element) {
element = $(element);
var position = element.getStyle('position');
- if (position != 'static') return proceed(element);
+ if (position !== 'static') return proceed(element);
+ // Trigger hasLayout on the offset parent so that IE6 reports
+ // accurate offsetTop and offsetLeft values for position: fixed.
+ var offsetParent = element.getOffsetParent();
+ if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+ offsetParent.setStyle({ zoom: 1 });
element.setStyle({ position: 'relative' });
var value = proceed(element);
element.setStyle({ position: position });
@@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) {
};
Element._attributeTranslations.write = {
- names: Object.clone(Element._attributeTranslations.read.names),
+ names: Object.extend({
+ cellpadding: 'cellPadding',
+ cellspacing: 'cellSpacing'
+ }, Element._attributeTranslations.read.names),
values: {
checked: function(element, value) {
element.checked = !!value;
@@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
};
}
-if (document.createElement('div').outerHTML) {
+if ('outerHTML' in document.createElement('div')) {
Element.Methods.replace = function(element, content) {
element = $(element);
@@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) {
Element._getContentFromAnonymousElement = function(tagName, html) {
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
- div.innerHTML = t[0] + html + t[1];
- t[2].times(function() { div = div.firstChild });
+ if (t) {
+ div.innerHTML = t[0] + html + t[1];
+ t[2].times(function() { div = div.firstChild });
+ } else div.innerHTML = html;
return $A(div.childNodes);
};
Element._insertionTranslations = {
- before: {
- adjacency: 'beforeBegin',
- insert: function(element, node) {
- element.parentNode.insertBefore(node, element);
- },
- initializeRange: function(element, range) {
- range.setStartBefore(element);
- }
+ before: function(element, node) {
+ element.parentNode.insertBefore(node, element);
},
- top: {
- adjacency: 'afterBegin',
- insert: function(element, node) {
- element.insertBefore(node, element.firstChild);
- },
- initializeRange: function(element, range) {
- range.selectNodeContents(element);
- range.collapse(true);
- }
+ top: function(element, node) {
+ element.insertBefore(node, element.firstChild);
},
- bottom: {
- adjacency: 'beforeEnd',
- insert: function(element, node) {
- element.appendChild(node);
- }
+ bottom: function(element, node) {
+ element.appendChild(node);
},
- after: {
- adjacency: 'afterEnd',
- insert: function(element, node) {
- element.parentNode.insertBefore(node, element.nextSibling);
- },
- initializeRange: function(element, range) {
- range.setStartAfter(element);
- }
+ after: function(element, node) {
+ element.parentNode.insertBefore(node, element.nextSibling);
},
tags: {
TABLE: ['<table>', '</table>', 1],
@@ -2532,7 +2510,6 @@ Element._insertionTranslations = {
};
(function() {
- this.bottom.initializeRange = this.top.initializeRange;
Object.extend(this.tags, {
THEAD: this.tags.TBODY,
TFOOT: this.tags.TBODY,
@@ -2716,7 +2693,7 @@ document.viewport = {
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
}
};
-/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
* license. Please see http://www.yui-ext.com/ for more information. */
@@ -2959,13 +2936,13 @@ Object.extend(Selector, {
},
criteria: {
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
- attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+ tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
+ className: 'n = h.className(n, r, "#{1}", c); c = false;',
+ id: 'n = h.id(n, r, "#{1}", c); c = false;',
+ attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
attr: function(m) {
m[3] = (m[5] || m[6]);
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+ return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
},
pseudo: function(m) {
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
@@ -2989,7 +2966,8 @@ Object.extend(Selector, {
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
id: /^#([\w\-\*]+)(\b|$)/,
className: /^\.([\w\-\*]+)(\b|$)/,
- pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
+ pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
attrPresence: /^\[([\w]+)\]/,
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
},
@@ -3014,7 +2992,7 @@ Object.extend(Selector, {
attr: function(element, matches) {
var nodeValue = Element.readAttribute(element, matches[1]);
- return Selector.operators[matches[2]](nodeValue, matches[3]);
+ return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
}
},
@@ -3029,14 +3007,15 @@ Object.extend(Selector, {
// marks an array of nodes for counting
mark: function(nodes) {
+ var _true = Prototype.emptyFunction;
for (var i = 0, node; node = nodes[i]; i++)
- node._counted = true;
+ node._countedByPrototype = _true;
return nodes;
},
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
- node._counted = undefined;
+ node._countedByPrototype = undefined;
return nodes;
},
@@ -3044,15 +3023,15 @@ Object.extend(Selector, {
// "ofType" flag indicates whether we're indexing for nth-of-type
// rather than nth-child
index: function(parentNode, reverse, ofType) {
- parentNode._counted = true;
+ parentNode._countedByPrototype = Prototype.emptyFunction;
if (reverse) {
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
var node = nodes[i];
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
} else {
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
},
@@ -3061,8 +3040,8 @@ Object.extend(Selector, {
if (nodes.length == 0) return nodes;
var results = [], n;
for (var i = 0, l = nodes.length; i < l; i++)
- if (!(n = nodes[i])._counted) {
- n._counted = true;
+ if (!(n = nodes[i])._countedByPrototype) {
+ n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
@@ -3114,7 +3093,7 @@ Object.extend(Selector, {
// TOKEN FUNCTIONS
tagName: function(nodes, root, tagName, combinator) {
- tagName = tagName.toUpperCase();
+ var uTagName = tagName.toUpperCase();
var results = [], h = Selector.handlers;
if (nodes) {
if (combinator) {
@@ -3127,7 +3106,7 @@ Object.extend(Selector, {
if (tagName == "*") return nodes;
}
for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName.toUpperCase() == tagName) results.push(node);
+ if (node.tagName.toUpperCase() === uTagName) results.push(node);
return results;
} else return root.getElementsByTagName(tagName);
},
@@ -3174,16 +3153,18 @@ Object.extend(Selector, {
return results;
},
- attrPresence: function(nodes, root, attr) {
+ attrPresence: function(nodes, root, attr, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
+ if (nodes && combinator) nodes = this[combinator](nodes);
var results = [];
for (var i = 0, node; node = nodes[i]; i++)
if (Element.hasAttribute(node, attr)) results.push(node);
return results;
},
- attr: function(nodes, root, attr, value, operator) {
+ attr: function(nodes, root, attr, value, operator, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
+ if (nodes && combinator) nodes = this[combinator](nodes);
var handler = Selector.operators[operator], results = [];
for (var i = 0, node; node = nodes[i]; i++) {
var nodeValue = Element.readAttribute(node, attr);
@@ -3262,7 +3243,7 @@ Object.extend(Selector, {
var h = Selector.handlers, results = [], indexed = [], m;
h.mark(nodes);
for (var i = 0, node; node = nodes[i]; i++) {
- if (!node.parentNode._counted) {
+ if (!node.parentNode._countedByPrototype) {
h.index(node.parentNode, reverse, ofType);
indexed.push(node.parentNode);
}
@@ -3300,7 +3281,7 @@ Object.extend(Selector, {
var exclusions = new Selector(selector).findElements(root);
h.mark(exclusions);
for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node._counted) results.push(node);
+ if (!node._countedByPrototype) results.push(node);
h.unmark(exclusions);
return results;
},
@@ -3334,11 +3315,19 @@ Object.extend(Selector, {
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
},
+ split: function(expression) {
+ var expressions = [];
+ expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+ expressions.push(m[1].strip());
+ });
+ return expressions;
+ },
+
matchElements: function(elements, expression) {
- var matches = new Selector(expression).findElements(), h = Selector.handlers;
+ var matches = $$(expression), h = Selector.handlers;
h.mark(matches);
for (var i = 0, results = [], element; element = elements[i]; i++)
- if (element._counted) results.push(element);
+ if (element._countedByPrototype) results.push(element);
h.unmark(matches);
return results;
},
@@ -3351,11 +3340,7 @@ Object.extend(Selector, {
},
findChildElements: function(element, expressions) {
- var exprs = expressions.join(',');
- expressions = [];
- exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
- expressions.push(m[1].strip());
- });
+ expressions = Selector.split(expressions.join(','));
var results = [], h = Selector.handlers;
for (var i = 0, l = expressions.length, selector; i < l; i++) {
selector = new Selector(expressions[i].strip());
@@ -3366,13 +3351,22 @@ Object.extend(Selector, {
});
if (Prototype.Browser.IE) {
- // IE returns comment nodes on getElementsByTagName("*").
- // Filter them out.
- Selector.handlers.concat = function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- if (node.tagName !== "!") a.push(node);
- return a;
- };
+ Object.extend(Selector.handlers, {
+ // IE returns comment nodes on getElementsByTagName("*").
+ // Filter them out.
+ concat: function(a, b) {
+ for (var i = 0, node; node = b[i]; i++)
+ if (node.tagName !== "!") a.push(node);
+ return a;
+ },
+
+ // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+ unmark: function(nodes) {
+ for (var i = 0, node; node = nodes[i]; i++)
+ node.removeAttribute('_countedByPrototype');
+ return nodes;
+ }
+ });
}
function $$() {
@@ -3850,9 +3844,9 @@ Object.extend(Event, (function() {
var cache = Event.cache;
function getEventID(element) {
- if (element._eventID) return element._eventID;
+ if (element._prototypeEventID) return element._prototypeEventID[0];
arguments.callee.id = arguments.callee.id || 1;
- return element._eventID = ++arguments.callee.id;
+ return element._prototypeEventID = [++arguments.callee.id];
}
function getDOMEventName(eventName) {
@@ -3880,7 +3874,7 @@ Object.extend(Event, (function() {
return false;
Event.extend(event);
- handler.call(element, event)
+ handler.call(element, event);
};
wrapper.handler = handler;
@@ -3962,11 +3956,12 @@ Object.extend(Event, (function() {
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
+ var event;
if (document.createEvent) {
- var event = document.createEvent("HTMLEvents");
+ event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
- var event = document.createEventObject();
+ event = document.createEventObject();
event.eventType = "ondataavailable";
}
@@ -3995,20 +3990,21 @@ Element.addMethods({
Object.extend(document, {
fire: Element.Methods.fire.methodize(),
observe: Element.Methods.observe.methodize(),
- stopObserving: Element.Methods.stopObserving.methodize()
+ stopObserving: Element.Methods.stopObserving.methodize(),
+ loaded: false
});
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
- var timer, fired = false;
+ var timer;
function fireContentLoadedEvent() {
- if (fired) return;
+ if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.fire("dom:loaded");
- fired = true;
+ document.loaded = true;
}
if (document.addEventListener) {
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 120bb4cc1f..c4ba7ccc8e 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -22,7 +22,7 @@ module ActionView
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
#
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
- # => +1.123.555.1234 x 1343
+ # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
number = number.to_s.strip unless number.nil?
options = options.stringify_keys
@@ -69,12 +69,15 @@ module ActionView
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
- options = options.stringify_keys
- precision = options["precision"] || 2
- unit = options["unit"] || "$"
- separator = precision > 0 ? options["separator"] || "." : ""
- delimiter = options["delimiter"] || ","
- format = options["format"] || "%u%n"
+ options = options.symbolize_keys
+ defaults = I18n.translate(:'currency.format', :locale => options[:locale]) || {}
+
+ precision = options[:precision] || defaults[:precision]
+ unit = options[:unit] || defaults[:unit]
+ separator = options[:separator] || defaults[:separator]
+ delimiter = options[:delimiter] || defaults[:delimiter]
+ format = options[:format] || defaults[:format]
+ separator = '' if precision == 0
begin
parts = number_with_precision(number, precision).split('.')
@@ -115,49 +118,72 @@ module ActionView
end
end
- # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
- # can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
+ # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
+ # customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
#
# ==== Examples
- # number_with_delimiter(12345678) # => 12,345,678
- # number_with_delimiter(12345678.05) # => 12,345,678.05
- # number_with_delimiter(12345678, ".") # => 12.345.678
- #
- # number_with_delimiter(98765432.98, " ", ",")
+ # number_with_delimiter(12345678) # => 12,345,678
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
+ # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
+ # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678
+ # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
# # => 98 765 432,98
- def number_with_delimiter(number, delimiter=",", separator=".")
+ #
+ # You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
+ # +delimiter+ as its optional second and the +separator+ as its
+ # optional third parameter:
+ # number_with_delimiter(12345678, " ") # => 12 345.678
+ # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
+ def number_with_delimiter(number, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:delimiter] = args[0] || ","
+ options[:separator] = args[1] || "."
+ end
+ options.reverse_merge!(:delimiter => ",", :separator => ".")
+
begin
parts = number.to_s.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
- parts.join separator
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
+ parts.join options[:separator]
rescue
number
end
end
- # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
- # level of precision is 3.
+ # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2).
+ # The default level of precision is 3.
#
# ==== Examples
- # number_with_precision(111.2345) # => 111.235
- # number_with_precision(111.2345, 2) # => 111.23
- # number_with_precision(13, 5) # => 13.00000
- # number_with_precision(389.32314, 0) # => 389
- def number_with_precision(number, precision=3)
- "%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision)
+ # number_with_precision(111.2345) # => 111.235
+ # number_with_precision(111.2345, :precision => 2) # => 111.23
+ # number_with_precision(13, :precision => 5) # => 13.00000
+ # number_with_precision(389.32314, :precision => 0) # => 389
+ #
+ # You can still use <tt>number_with_precision</tt> with the old API that accepts the
+ # +precision+ as its optional second parameter:
+ # number_with_precision(number_with_precision(111.2345, 2) # => 111.23
+ def number_with_precision(number, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:precision] = args[0] || 3
+ end
+ options.reverse_merge!(:precision => 3)
+ "%01.#{options[:precision]}f" %
+ ((Float(number) * (10 ** options[:precision])).round.to_f / 10 ** options[:precision])
rescue
number
end
# Formats the bytes in +size+ into a more understandable representation
- # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
+ # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
# reporting file sizes to users. This method returns nil if
# +size+ cannot be converted into a number. You can change the default
- # precision of 1 using the precision parameter +precision+.
+ # precision of 1 using the precision parameter <tt>:precision</tt>.
#
# ==== Examples
# number_to_human_size(123) # => 123 Bytes
@@ -166,17 +192,28 @@ module ActionView
# number_to_human_size(1234567) # => 1.2 MB
# number_to_human_size(1234567890) # => 1.1 GB
# number_to_human_size(1234567890123) # => 1.1 TB
+ # number_to_human_size(1234567, :precision => 2) # => 1.18 MB
+ # number_to_human_size(483989, :precision => 0) # => 473 KB
+ #
+ # You can still use <tt>number_to_human_size</tt> with the old API that accepts the
+ # +precision+ as its optional second parameter:
# number_to_human_size(1234567, 2) # => 1.18 MB
- # number_to_human_size(483989, 0) # => 4 MB
- def number_to_human_size(size, precision=1)
- size = Kernel.Float(size)
+ # number_to_human_size(483989, 0) # => 473 KB
+ def number_to_human_size(size, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:precision] = args[0] || 1
+ end
+ options.reverse_merge!(:precision => 1)
+
+ size = Float(size)
case
when size.to_i == 1; "1 Byte"
when size < 1.kilobyte; "%d Bytes" % size
- when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
- when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
- when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
- else "%.#{precision}f TB" % (size / 1.0.terabyte)
+ when size < 1.megabyte; "%.#{options[:precision]}f KB" % (size / 1.0.kilobyte)
+ when size < 1.gigabyte; "%.#{options[:precision]}f MB" % (size / 1.0.megabyte)
+ when size < 1.terabyte; "%.#{options[:precision]}f GB" % (size / 1.0.gigabyte)
+ else "%.#{options[:precision]}f TB" % (size / 1.0.terabyte)
end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ')
rescue
nil
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 576ca84bcc..cb4b53a9f7 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -3,25 +3,25 @@ require 'set'
module ActionView
module Helpers
# Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
# Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
+ # 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.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
+ # functions from Prototype using Rails, including functionality to call remote
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
+ # This means that you can call actions in your controllers without
+ # reloading the page, but still update certain parts of it using
# injections into the DOM. A common use case is having a form that adds
# a new element to a list without reloading the page or updating a shopping
# cart total when a new item is added.
#
# == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
+ # To be able to use these helpers, you must first include the Prototype
+ # JavaScript framework in your pages.
#
# javascript_include_tag 'prototype'
#
- # (See the documentation for
+ # (See the documentation for
# ActionView::Helpers::JavaScriptHelper for more information on including
# this and other JavaScript files in your Rails templates.)
#
@@ -29,7 +29,7 @@ module ActionView
#
# link_to_remote "Add to cart",
# :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
+ # :update => { :success => "cart", :failure => "error" }
#
# ...through a form...
#
@@ -50,8 +50,8 @@ module ActionView
# :update => :hits,
# :with => 'query'
# %>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
+ #
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
# are listed here); check out the documentation for each method to find out more about its usage and options.
#
# === Common Options
@@ -63,7 +63,7 @@ module ActionView
# When building your action handlers (that is, the Rails actions that receive your background requests), it's
# important to remember a few things. First, whatever your action would normally return to the browser, it will
# return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
# You can turn the layout off on particular actions by doing the following:
#
# class SiteController < ActionController::Base
@@ -74,8 +74,8 @@ module ActionView
#
# render :layout => false
#
- # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
+ # method that Ajax uses to make background requests) method.
# def name
# # Is this an XmlHttpRequest request?
# if (request.xhr?)
@@ -93,7 +93,7 @@ module ActionView
#
# Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
#
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
# render text output, like this:
#
# render :text => 'Return this from my method!'
@@ -103,7 +103,7 @@ module ActionView
#
# == Updating multiple elements
# See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
+ # on the page in an Ajax response.
module PrototypeHelper
unless const_defined? :CALLBACKS
CALLBACKS = Set.new([ :uninitialized, :loading, :loaded,
@@ -114,64 +114,64 @@ module ActionView
:form, :with, :update, :script ]).merge(CALLBACKS)
end
- # Returns a link to a remote action defined by <tt>options[:url]</tt>
- # (using the url_for format) that's called in the background using
+ # Returns a link to a remote action defined by <tt>options[:url]</tt>
+ # (using the url_for format) that's called in the background using
# XMLHttpRequest. The result of that request can then be inserted into a
- # DOM object whose id can be specified with <tt>options[:update]</tt>.
+ # DOM object whose id can be specified with <tt>options[:update]</tt>.
# Usually, the result would be a partial prepared by the controller with
- # render :partial.
+ # render :partial.
#
# Examples:
- # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
+ # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
# # return false;">Delete this post</a>
- # link_to_remote "Delete this post", :update => "posts",
+ # link_to_remote "Delete this post", :update => "posts",
# :url => { :action => "destroy", :id => post.id }
#
- # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
+ # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
# # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
- # link_to_remote(image_tag("refresh"), :update => "emails",
+ # link_to_remote(image_tag("refresh"), :update => "emails",
# :url => { :action => "list_emails" })
- #
+ #
# You can override the generated HTML options by specifying a hash in
# <tt>options[:html]</tt>.
- #
+ #
# link_to_remote "Delete this post", :update => "posts",
- # :url => post_url(@post), :method => :delete,
- # :html => { :class => "destructive" }
+ # :url => post_url(@post), :method => :delete,
+ # :html => { :class => "destructive" }
#
# You can also specify a hash for <tt>options[:update]</tt> to allow for
- # easy redirection of output to an other DOM element if a server-side
+ # easy redirection of output to an other DOM element if a server-side
# error occurs:
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
+ # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
# # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
# link_to_remote "Delete this post",
# :url => { :action => "destroy", :id => post.id },
# :update => { :success => "posts", :failure => "error" }
#
- # Optionally, you can use the <tt>options[:position]</tt> parameter to
- # influence how the target DOM element is updated. It must be one of
+ # Optionally, you can use the <tt>options[:position]</tt> parameter to
+ # influence how the target DOM element is updated. It must be one of
# <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
#
# The method used is by default POST. You can also specify GET or you
# can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
+ # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
# # return false;">Destroy</a>
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
#
- # By default, these remote requests are processed asynchronous during
- # which various JavaScript callbacks can be triggered (for progress
- # indicators and the likes). All callbacks get access to the
- # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
+ # By default, these remote requests are processed asynchronous during
+ # which various JavaScript callbacks can be triggered (for progress
+ # indicators and the likes). All callbacks get access to the
+ # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
#
# To access the server response, use <tt>request.responseText</tt>, to
# find out the HTTP status, use <tt>request.status</tt>.
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
+ # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
# # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
# word = 'hello'
# link_to_remote word,
@@ -180,43 +180,43 @@ module ActionView
#
# The callbacks that may be specified are (in order):
#
- # <tt>:loading</tt>:: Called when the remote document is being
+ # <tt>:loading</tt>:: Called when the remote document is being
# loaded with data by the browser.
# <tt>:loaded</tt>:: Called when the browser has finished loading
# the remote document.
- # <tt>:interactive</tt>:: Called when the user can interact with the
- # remote document, even though it has not
+ # <tt>:interactive</tt>:: Called when the user can interact with the
+ # remote document, even though it has not
# finished loading.
# <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is in the 2XX range.
# <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is not in the 2XX
# range.
- # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
- # (fires after success/failure if they are
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
+ # (fires after success/failure if they are
# present).
- #
- # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
+ #
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
# adding additional callbacks for specific status codes.
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
- # # on404:function(request){alert('Not found...? Wrong URL...?')},
+ # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
+ # # on404:function(request){alert('Not found...? Wrong URL...?')},
# # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
# link_to_remote word,
# :url => { :action => "action" },
# 404 => "alert('Not found...? Wrong URL...?')",
# :failure => "alert('HTTP Error ' + request.status + '!')"
#
- # A status code callback overrides the success/failure handlers if
+ # A status code callback overrides the success/failure handlers if
# present.
#
# If you for some reason or another need synchronous processing (that'll
- # block the browser while the request is happening), you can specify
+ # block the browser while the request is happening), you can specify
# <tt>options[:type] = :synchronous</tt>.
#
# You can customize further browser side call logic by passing in
- # JavaScript code snippets via some optional parameters. In their order
+ # JavaScript code snippets via some optional parameters. In their order
# of use these are:
#
# <tt>:confirm</tt>:: Adds confirmation dialog.
@@ -228,7 +228,7 @@ module ActionView
# <tt>:after</tt>:: Called immediately after request was
# initiated and before <tt>:loading</tt>.
# <tt>:submit</tt>:: Specifies the DOM element ID that's used
- # as the parent of the form elements. By
+ # as the parent of the form elements. By
# default this is the current form, but
# it could just as well be the ID of a
# table row or any other DOM element.
@@ -238,10 +238,10 @@ module ActionView
# URL query string.
#
# Example:
- #
+ #
# :with => "'name=' + $('name').value"
#
- # You can generate a link that uses AJAX in the general case, while
+ # You can generate a link that uses AJAX in the general case, while
# degrading gracefully to plain link behavior in the absence of
# JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
# Note the extra curly braces around the <tt>options</tt> hash separate
@@ -251,7 +251,7 @@ module ActionView
# link_to_remote "Delete this post",
# { :update => "posts", :url => { :action => "destroy", :id => post.id } },
# :href => url_for(:action => "destroy", :id => post.id)
- def link_to_remote(name, options = {}, html_options = nil)
+ def link_to_remote(name, options = {}, html_options = nil)
link_to_function(name, remote_function(options), html_options || options.delete(:html))
end
@@ -262,15 +262,15 @@ module ActionView
# and defining callbacks is the same as link_to_remote.
# Examples:
# # Call get_averages and put its results in 'avg' every 10 seconds
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
# # {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
#
# # Call invoice every 10 seconds with the id of the customer
# # If it succeeds, update the invoice DIV; if it fails, update the error DIV
# # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
# # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
# :update => { :success => "invoice", :failure => "error" }
@@ -286,11 +286,11 @@ module ActionView
javascript_tag(code)
end
- # Returns a form tag that will submit using XMLHttpRequest in the
- # background instead of the regular reloading POST arrangement. Even
+ # Returns a form tag that will submit using XMLHttpRequest in the
+ # background instead of the regular reloading POST arrangement. Even
# though it's using JavaScript to serialize the form elements, the form
# submission will work just like a regular submission as viewed by the
- # receiving side (all elements available in <tt>params</tt>). The options for
+ # receiving side (all elements available in <tt>params</tt>). The options for
# specifying the target with <tt>:url</tt> and defining callbacks is the same as
# +link_to_remote+.
#
@@ -299,21 +299,21 @@ module ActionView
#
# Example:
# # Generates:
- # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
+ # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
- # form_remote_tag :html => { :action =>
+ # form_remote_tag :html => { :action =>
# url_for(:controller => "some", :action => "place") }
#
# The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
# argument in the FormTagHelper.form_tag method.
#
- # By default the fall-through action is the same as the one specified in
+ # By default the fall-through action is the same as the one specified in
# the <tt>:url</tt> (and the default method is <tt>:post</tt>).
#
# form_remote_tag also takes a block, like form_tag:
# # Generates:
- # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
+ # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
# # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
# # </form>
# <% form_remote_tag :url => '/posts' do -%>
@@ -323,19 +323,19 @@ module ActionView
options[:form] = true
options[:html] ||= {}
- options[:html][:onsubmit] =
- (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
+ options[:html][:onsubmit] =
+ (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
"#{remote_function(options)}; return false;"
form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
end
- # Creates a form that will submit using XMLHttpRequest in the background
- # instead of the regular reloading POST arrangement and a scope around a
+ # Creates a form that will submit using XMLHttpRequest in the background
+ # instead of the regular reloading POST arrangement and a scope around a
# specific resource that is used as a base for questioning about
- # values for the fields.
+ # values for the fields.
#
- # === Resource
+ # === Resource
#
# Example:
# <% remote_form_for(@post) do |f| %>
@@ -348,7 +348,7 @@ module ActionView
# ...
# <% end %>
#
- # === Nested Resource
+ # === Nested Resource
#
# Example:
# <% remote_form_for([@post, @comment]) do |f| %>
@@ -387,23 +387,23 @@ module ActionView
concat('</form>')
end
alias_method :form_remote_for, :remote_form_for
-
+
# Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
# that will submit form using XMLHttpRequest in the background instead of a regular POST request that
- # reloads the page.
+ # reloads the page.
#
# # Create a button that submits to the create action
- # #
- # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # #
+ # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
# # return false;" type="button" value="Create" />
# <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
#
# # Submit to the remote action update and update the DIV succeed or fail based
# # on the success or failure of the request
# #
- # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
- # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
+ # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
# # return false;" type="button" value="Update" />
# <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
# :update => { :success => "succeed", :failure => "fail" }
@@ -423,7 +423,7 @@ module ActionView
tag("input", options[:html], false)
end
alias_method :submit_to_remote, :button_to_remote
-
+
# Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
# that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple
# update return document using +update_element_function+ calls.
@@ -433,11 +433,11 @@ module ActionView
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
- #
+ #
# Example:
- # # Generates: <select id="options" onchange="new Ajax.Updater('options',
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
# # '/testing/update_options', {asynchronous:true, evalScripts:true})">
- # <select id="options" onchange="<%= remote_function(:update => "options",
+ # <select id="options" onchange="<%= remote_function(:update => "options",
# :url => { :action => :update_options }) %>">
# <option value="0">Hello</option>
# <option value="1">World</option>
@@ -455,7 +455,7 @@ module ActionView
update << "'#{options[:update]}'"
end
- function = update.empty? ?
+ function = update.empty? ?
"new Ajax.Request(" :
"new Ajax.Updater(#{update}, "
@@ -476,9 +476,9 @@ module ActionView
# callback when its contents have changed. The default callback is an
# Ajax call. By default the value of the observed field is sent as a
# parameter with the Ajax call.
- #
+ #
# Example:
- # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
# # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
# <%= observe_field :suggest, :url => { :action => :find_suggestion },
# :frequency => 0.25,
@@ -500,14 +500,14 @@ module ActionView
# new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
# The element parameter is the DOM element being observed, and the value is its value at the
# time the observer is triggered.
- #
+ #
# Additional options are:
# <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
# this field will be detected. Not setting this
# option at all or to a value equal to or less than
# zero will use event based observation instead of
# time based observation.
- # <tt>:update</tt>:: Specifies the DOM ID of the element whose
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the
# XMLHttpRequest response text.
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
@@ -518,7 +518,7 @@ module ActionView
# variable +value+.
#
# Examples
- #
+ #
# :with => "'my_custom_key=' + value"
# :with => "'person[name]=' + prompt('New name')"
# :with => "Form.Element.serialize('other-field')"
@@ -544,7 +544,7 @@ module ActionView
# observe_field 'book_title',
# :url => 'http://example.com/books/edit/1',
# :with => 'title'
- #
+ #
# # Sends params: {:book_title => 'Title of the book'} when the focus leaves
# # the input field.
# observe_field 'book_title',
@@ -558,7 +558,7 @@ module ActionView
build_observer('Form.Element.EventObserver', field_id, options)
end
end
-
+
# Observes the form with the DOM ID specified by +form_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default all fields of the observed field are sent as
@@ -574,16 +574,18 @@ module ActionView
build_observer('Form.EventObserver', form_id, options)
end
end
-
- # All the methods were moved to GeneratorMethods so that
+
+ # All the methods were moved to GeneratorMethods so that
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
def initialize(context, &block) #:nodoc:
@context, @lines = context, []
include_helpers_from_context
- @context.instance_exec(self, &block)
+ @context.with_output_buffer(@lines) do
+ @context.instance_exec(self, &block)
+ end
end
-
+
private
def include_helpers_from_context
@context.extended_by.each do |mod|
@@ -591,17 +593,17 @@ module ActionView
end
extend GeneratorMethods
end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
+
+ # 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".
#
- # Create new instances with PrototypeHelper#update_page or with
- # ActionController::Base#render, then call +insert_html+, +replace_html+,
- # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
- # methods on the yielded generator in any order you like to modify the
- # content and appearance of the current page.
+ # Create new instances with PrototypeHelper#update_page or with
+ # ActionController::Base#render, then call +insert_html+, +replace_html+,
+ # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
+ # methods on the yielded generator in any order you like to modify the
+ # content and appearance of the current page.
#
# Example:
#
@@ -614,12 +616,12 @@ module ActionView
# page.visual_effect :highlight, 'list'
# page.hide 'status-indicator', 'cancel-link'
# end
- #
+ #
#
# Helper methods can be used in conjunction with JavaScriptGenerator.
- # When a helper method is called inside an update block on the +page+
+ # When a helper method is called inside an update block on the +page+
# object, that method will also have access to a +page+ object.
- #
+ #
# Example:
#
# module ApplicationHelper
@@ -655,7 +657,7 @@ module ActionView
# end
# end
#
- # You can also use PrototypeHelper#update_page_tag instead of
+ # You can also use PrototypeHelper#update_page_tag instead of
# PrototypeHelper#update_page to wrap the generated JavaScript in a
# <script> tag.
module GeneratorMethods
@@ -668,7 +670,7 @@ module ActionView
end
end
end
-
+
# Returns a element reference by finding it through +id+ in the DOM. This element can then be
# used for further method calls. Examples:
#
@@ -689,31 +691,31 @@ module ActionView
JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
end
end
-
- # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
+
+ # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method.
def literal(code)
ActiveSupport::JSON::Variable.new(code.to_s)
end
-
+
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
# used for further method calls. Examples:
#
# page.select('p') # => $$('p');
# page.select('p.welcome b').first # => $$('p.welcome b').first();
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
- #
+ #
# You can also use prototype enumerations with the collection. Observe:
- #
+ #
# # Generates: $$('#items li').each(function(value) { value.hide(); });
# page.select('#items li').each do |value|
# value.hide
- # end
+ # end
#
- # Though you can call the block param anything you want, they are always rendered in the
+ # Though you can call the block param anything you want, they are always rendered in the
# javascript as 'value, index.' Other enumerations, like collect() return the last statement:
#
- # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
# page.select('#items li').collect('hidden') do |item|
# item.hide
# end
@@ -721,13 +723,13 @@ module ActionView
def select(pattern)
JavaScriptElementCollectionProxy.new(self, pattern)
end
-
+
# Inserts HTML at the specified +position+ relative to the DOM element
# identified by the given +id+.
- #
+ #
# +position+ may be one of:
- #
- # <tt>:top</tt>:: HTML is inserted inside the element, before the
+ #
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
# element's existing content.
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
# element's existing content.
@@ -750,7 +752,7 @@ module ActionView
insertion = position.to_s.camelize
call "new Insertion.#{insertion}", id, render(*options_for_render)
end
-
+
# Replaces the inner HTML of the DOM element with the given +id+.
#
# +options_for_render+ may be either a string of HTML to insert, or a hash
@@ -764,7 +766,7 @@ module ActionView
def replace_html(id, *options_for_render)
call 'Element.update', id, render(*options_for_render)
end
-
+
# Replaces the "outer HTML" (i.e., the entire element, not just its
# contents) of the DOM element with the given +id+.
#
@@ -786,7 +788,7 @@ module ActionView
# </div>
#
# # Insert a new person
- # #
+ # #
# # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
# page.insert_html :bottom, :partial => 'person', :object => @person
#
@@ -798,7 +800,7 @@ module ActionView
def replace(id, *options_for_render)
call 'Element.replace', id, render(*options_for_render)
end
-
+
# Removes the DOM elements with the given +ids+ from the page.
#
# Example:
@@ -810,9 +812,9 @@ module ActionView
def remove(*ids)
loop_on_multiple_args 'Element.remove', ids
end
-
+
# Shows hidden DOM elements with the given +ids+.
- #
+ #
# Example:
#
# # Show a few people
@@ -822,7 +824,7 @@ module ActionView
def show(*ids)
loop_on_multiple_args 'Element.show', ids
end
-
+
# Hides the visible DOM elements with the given +ids+.
#
# Example:
@@ -832,9 +834,9 @@ module ActionView
# page.hide 'person_29', 'person_9', 'person_0'
#
def hide(*ids)
- loop_on_multiple_args 'Element.hide', ids
+ loop_on_multiple_args 'Element.hide', ids
end
-
+
# Toggles the visibility of the DOM elements with the given +ids+.
# Example:
#
@@ -844,9 +846,9 @@ module ActionView
# page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
#
def toggle(*ids)
- loop_on_multiple_args 'Element.toggle', ids
+ loop_on_multiple_args 'Element.toggle', ids
end
-
+
# Displays an alert dialog with the given +message+.
#
# Example:
@@ -856,21 +858,21 @@ module ActionView
def alert(message)
call 'alert', message
end
-
+
# Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
#
# Examples:
#
# # Generates: window.location.href = "/mycontroller";
# page.redirect_to(:action => 'index')
- #
+ #
# # Generates: window.location.href = "/account/signup";
# page.redirect_to(:controller => 'account', :action => 'signup')
def redirect_to(location)
url = location.is_a?(String) ? location : @context.url_for(location)
record "window.location.href = #{url.inspect}"
end
-
+
# Reloads the browser's current +location+ using JavaScript
#
# Examples:
@@ -884,17 +886,17 @@ module ActionView
# Calls the JavaScript +function+, optionally with the given +arguments+.
#
# If a block is given, the block will be passed to a new JavaScriptGenerator;
- # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
+ # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
# and passed as the called function's final argument.
- #
+ #
# Examples:
#
# # Generates: Element.replace(my_element, "My content to replace with.")
# page.call 'Element.replace', 'my_element', "My content to replace with."
- #
+ #
# # Generates: alert('My message!')
# page.call 'alert', 'My message!'
- #
+ #
# # Generates:
# # my_method(function() {
# # $("one").show();
@@ -907,7 +909,7 @@ module ActionView
def call(function, *arguments, &block)
record "#{function}(#{arguments_for_call(arguments, block)})"
end
-
+
# Assigns the JavaScript +variable+ the given +value+.
#
# Examples:
@@ -918,13 +920,13 @@ module ActionView
# # Generates: record_count = 33;
# page.assign 'record_count', 33
#
- # # Generates: tabulated_total = 47
+ # # Generates: tabulated_total = 47
# page.assign 'tabulated_total', @total_from_cart
#
def assign(variable, value)
record "#{variable} = #{javascript_object_for(value)}"
end
-
+
# Writes raw JavaScript to the page.
#
# Example:
@@ -933,10 +935,10 @@ module ActionView
def <<(javascript)
@lines << javascript
end
-
+
# Executes the content of the block after a delay of +seconds+. Example:
#
- # # Generates:
+ # # Generates:
# # setTimeout(function() {
# # ;
# # new Effect.Fade("notice",{});
@@ -949,13 +951,13 @@ module ActionView
yield
record "}, #{(seconds * 1000).to_i})"
end
-
- # Starts a script.aculo.us visual effect. See
+
+ # Starts a script.aculo.us visual effect. See
# ActionView::Helpers::ScriptaculousHelper for more information.
def visual_effect(name, id = nil, options = {})
record @context.send(:visual_effect, name, id, options)
end
-
+
# Creates a script.aculo.us sortable element. Useful
# to recreate sortable elements after items get added
# or deleted.
@@ -963,66 +965,66 @@ module ActionView
def sortable(id, options = {})
record @context.send(:sortable_element_js, id, options)
end
-
+
# Creates a script.aculo.us draggable element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def draggable(id, options = {})
record @context.send(:draggable_element_js, id, options)
end
-
+
# Creates a script.aculo.us drop receiving element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def drop_receiving(id, options = {})
record @context.send(:drop_receiving_element_js, id, options)
end
-
+
private
def loop_on_multiple_args(method, ids)
- record(ids.size>1 ?
- "#{javascript_object_for(ids)}.each(#{method})" :
+ record(ids.size>1 ?
+ "#{javascript_object_for(ids)}.each(#{method})" :
"#{method}(#{ids.first.to_json})")
end
-
+
def page
self
end
-
+
def record(line)
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
self << line
end
end
-
+
def render(*options_for_render)
old_format = @context && @context.template_format
@context.template_format = :html if @context
- Hash === options_for_render.first ?
- @context.render(*options_for_render) :
+ Hash === options_for_render.first ?
+ @context.render(*options_for_render) :
options_for_render.first.to_s
ensure
@context.template_format = old_format if @context
end
-
+
def javascript_object_for(object)
object.respond_to?(:to_json) ? object.to_json : object.inspect
end
-
+
def arguments_for_call(arguments, block = nil)
arguments << block_to_function(block) if block
arguments.map { |argument| javascript_object_for(argument) }.join ', '
end
-
+
def block_to_function(block)
generator = self.class.new(@context, &block)
literal("function() { #{generator.to_s} }")
- end
+ end
def method_missing(method, *arguments)
JavaScriptProxy.new(self, method.to_s.camelize)
end
end
end
-
+
# Yields a JavaScriptGenerator and returns the generated JavaScript code.
# Use this to update multiple elements on a page in an Ajax response.
# See JavaScriptGenerator for more information.
@@ -1035,13 +1037,13 @@ module ActionView
def update_page(&block)
JavaScriptGenerator.new(@template, &block).to_s
end
-
+
# Works like update_page but wraps the generated JavaScript in a <script>
# tag. Use this to include generated JavaScript in an ERb template.
# See JavaScriptGenerator for more information.
#
# +html_options+ may be a hash of <script> attributes to be passed
- # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
+ # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
def update_page_tag(html_options = {}, &block)
javascript_tag update_page(&block), html_options
end
@@ -1049,7 +1051,7 @@ module ActionView
protected
def options_for_ajax(options)
js_options = build_callbacks(options)
-
+
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
@@ -1062,7 +1064,7 @@ module ActionView
elsif options[:with]
js_options['parameters'] = options[:with]
end
-
+
if protect_against_forgery? && !options[:form]
if js_options['parameters']
js_options['parameters'] << " + '&"
@@ -1071,14 +1073,14 @@ module ActionView
end
js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
end
-
+
options_for_javascript(js_options)
end
- def method_option_to_s(method)
+ def method_option_to_s(method)
(method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
end
-
+
def build_observer(klass, name, options = {})
if options[:with] && (options[:with] !~ /[\{=(.]/)
options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
@@ -1095,7 +1097,7 @@ module ActionView
javascript << ")"
javascript_tag(javascript)
end
-
+
def build_callbacks(options)
callbacks = {}
options.each do |callback, code|
@@ -1108,7 +1110,7 @@ module ActionView
end
end
- # Converts chained method calls on DOM proxy elements into JavaScript chains
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
def initialize(generator, root = nil)
@@ -1124,7 +1126,7 @@ module ActionView
call("#{method.to_s.camelize(:lower)}", *arguments, &block)
end
end
-
+
def call(function, *arguments, &block)
append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
self
@@ -1133,23 +1135,23 @@ module ActionView
def assign(variable, value)
append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
end
-
+
def function_chain
@function_chain ||= @generator.instance_variable_get(:@lines)
end
-
+
def append_to_function_chain!(call)
function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call};"
end
end
-
+
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
def initialize(generator, id)
@id = id
super(generator, "$(#{id.to_json})")
end
-
+
# Allows access of element attributes through +attribute+. Examples:
#
# page['foo']['style'] # => $('foo').style;
@@ -1160,11 +1162,11 @@ module ActionView
append_to_function_chain!(attribute)
self
end
-
+
def []=(variable, value)
assign(variable, value)
end
-
+
def replace_html(*options_for_render)
call 'update', @generator.send(:render, *options_for_render)
end
@@ -1172,11 +1174,11 @@ module ActionView
def replace(*options_for_render)
call 'replace', @generator.send(:render, *options_for_render)
end
-
+
def reload(options_for_replace = {})
replace(options_for_replace.merge({ :partial => @id.to_s }))
end
-
+
end
class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
@@ -1195,7 +1197,7 @@ module ActionView
def to_json(options = nil)
@variable
end
-
+
private
def append_to_function_chain!(call)
@generator << @variable if @empty
@@ -1213,7 +1215,7 @@ module ActionView
def initialize(generator, pattern)
super(generator, @pattern = pattern)
end
-
+
def each_slice(variable, number, &block)
if block
enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
@@ -1222,18 +1224,18 @@ module ActionView
append_enumerable_function!("eachSlice(#{number.to_json});")
end
end
-
+
def grep(variable, pattern, &block)
enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
end
-
+
def in_groups_of(variable, number, fill_with = nil)
arguments = [number]
arguments << fill_with unless fill_with.nil?
add_variable_assignment!(variable)
append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
- end
-
+ end
+
def inject(variable, memo, &block)
enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
end
@@ -1295,13 +1297,13 @@ module ActionView
function_chain.push("return #{function_chain.pop.chomp(';')};")
end
end
-
+
def append_enumerable_function!(call)
function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call}"
end
end
-
+
class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
def initialize(generator, pattern)
super(generator, "$$(#{pattern.to_json})")
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 14e4f01a13..de08672d2d 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -110,12 +110,18 @@ module ActionView
private
BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
- # Check whether we're called from an erb template.
- # We'd return a string in any other case, but erb <%= ... %>
- # can't take an <% end %> later on, so we have to use <% ... %>
- # and implicitly concat.
- def block_called_from_erb?(block)
- block && eval(BLOCK_CALLED_FROM_ERB, block)
+ if RUBY_VERSION < '1.9.0'
+ # Check whether we're called from an erb template.
+ # We'd return a string in any other case, but erb <%= ... %>
+ # can't take an <% end %> later on, so we have to use <% ... %>
+ # and implicitly concat.
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block)
+ end
+ else
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
+ end
end
def content_tag_string(name, content, options, escape = true)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 3e3452b615..3c9f7230c3 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -34,40 +34,69 @@ module ActionView
end
if RUBY_VERSION < '1.9'
- # If +text+ is longer than +length+, +text+ will be truncated to the length of
- # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+
- # (defaults to "...").
+ # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
+ # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...").
#
# ==== Examples
- # truncate("Once upon a time in a world far far away", 14)
- # # => Once upon a...
#
# truncate("Once upon a time in a world far far away")
# # => Once upon a time in a world f...
#
- # truncate("And they found that many people were sleeping better.", 25, "(clipped)")
+ # truncate("Once upon a time in a world far far away", :length => 14)
+ # # => Once upon a...
+ #
+ # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
# # => And they found that many (clipped)
#
+ # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15)
+ # # => And they found... (continued)
+ #
+ # You can still use <tt>truncate</tt> with the old API that accepts the
+ # +length+ as its optional second and the +ellipsis+ as its
+ # optional third parameter:
+ # truncate("Once upon a time in a world far far away", 14)
+ # # => Once upon a time in a world f...
+ #
# truncate("And they found that many people were sleeping better.", 15, "... (continued)")
# # => And they found... (continued)
- def truncate(text, length = 30, truncate_string = "...")
+ def truncate(text, *args)
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
+ 'length and omission arguments', caller)
+
+ options[:length] = args[0] || 30
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:length => 30, :omission => "...")
+
if text
- l = length - truncate_string.chars.length
+ l = options[:length] - options[:omission].chars.length
chars = text.chars
- (chars.length > length ? chars[0...l] + truncate_string : text).to_s
+ (chars.length > options[:length] ? chars[0...l] + options[:omission] : text).to_s
end
end
else
- def truncate(text, length = 30, truncate_string = "...") #:nodoc:
+ def truncate(text, *args) #:nodoc:
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
+ 'length and omission arguments', caller)
+
+ options[:length] = args[0] || 30
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:length => 30, :omission => "...")
+
if text
- l = length - truncate_string.length
- (text.length > length ? text[0...l] + truncate_string : text).to_s
+ l = options[:length].to_i - options[:omission].length
+ (text.length > options[:length].to_i ? text[0...l] + options[:omission] : text).to_s
end
end
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
- # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
+ # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to
# '<strong class="highlight">\1</strong>')
#
@@ -78,52 +107,75 @@ module ActionView
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
#
- # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>')
+ # highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>')
# # => You searched <em>for</em>: <em>rails</em>
#
- # highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>")
- # # => You searched for: <a href='search?q=rails>rails</a>
- def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>')
+ # highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
+ # # => You searched for: <a href="search?q=rails">rails</a>
+ #
+ # You can still use <tt>highlight</tt> with the old API that accepts the
+ # +highlighter+ as its optional third parameter:
+ # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a>
+ def highlight(text, phrases, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>'
+ end
+ options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
+
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})/i, highlighter)
+ text.gsub(/(#{match})/i, options[:highlighter])
end
end
if RUBY_VERSION < '1.9'
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
- # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
- # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
- # then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case.
- # If the +phrase+ isn't found, nil is returned.
+ # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
+ # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
+ # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string
+ # will be stripped in any case. If the +phrase+ isn't found, nil is returned.
#
# ==== Examples
- # excerpt('This is an example', 'an', 5)
- # # => "...s is an exam..."
+ # excerpt('This is an example', 'an', :radius => 5)
+ # # => ...s is an exam...
#
- # excerpt('This is an example', 'is', 5)
- # # => "This is a..."
+ # excerpt('This is an example', 'is', :radius => 5)
+ # # => This is a...
#
# excerpt('This is an example', 'is')
- # # => "This is an example"
+ # # => This is an example
#
- # excerpt('This next thing is an example', 'ex', 2)
- # # => "...next..."
+ # excerpt('This next thing is an example', 'ex', :radius => 2)
+ # # => ...next...
#
- # excerpt('This is also an example', 'an', 8, '<chop> ')
- # # => "<chop> is also an example"
- def excerpt(text, phrase, radius = 100, excerpt_string = "...")
+ # excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
+ # # => <chop> is also an example
+ #
+ # You can still use <tt>excerpt</tt> with the old API that accepts the
+ # +radius+ as its optional third and the +ellipsis+ as its
+ # optional forth parameter:
+ # excerpt('This is an example', 'an', 5) # => ...s is an exam...
+ # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example
+ def excerpt(text, phrase, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:radius] = args[0] || 100
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:radius => 100, :omission => "...")
+
if text && phrase
phrase = Regexp.escape(phrase)
if found_pos = text.chars =~ /(#{phrase})/i
- start_pos = [ found_pos - radius, 0 ].max
- end_pos = [ [ found_pos + phrase.chars.length + radius - 1, 0].max, text.chars.length ].min
+ start_pos = [ found_pos - options[:radius], 0 ].max
+ end_pos = [ [ found_pos + phrase.chars.length + options[:radius] - 1, 0].max, text.chars.length ].min
- prefix = start_pos > 0 ? excerpt_string : ""
- postfix = end_pos < text.chars.length - 1 ? excerpt_string : ""
+ prefix = start_pos > 0 ? options[:omission] : ""
+ postfix = end_pos < text.chars.length - 1 ? options[:omission] : ""
prefix + text.chars[start_pos..end_pos].strip + postfix
else
@@ -132,16 +184,23 @@ module ActionView
end
end
else
- def excerpt(text, phrase, radius = 100, excerpt_string = "...") #:nodoc:
+ def excerpt(text, phrase, *args) #:nodoc:
+ options = args.extract_options!
+ unless args.empty?
+ options[:radius] = args[0] || 100
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:radius => 100, :omission => "...")
+
if text && phrase
phrase = Regexp.escape(phrase)
if found_pos = text =~ /(#{phrase})/i
- start_pos = [ found_pos - radius, 0 ].max
- end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min
+ start_pos = [ found_pos - options[:radius], 0 ].max
+ end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min
- prefix = start_pos > 0 ? excerpt_string : ""
- postfix = end_pos < text.length - 1 ? excerpt_string : ""
+ prefix = start_pos > 0 ? options[:omission] : ""
+ postfix = end_pos < text.length - 1 ? options[:omission] : ""
prefix + text[start_pos..end_pos].strip + postfix
else
@@ -176,20 +235,31 @@ module ActionView
# (which is 80 by default).
#
# ==== Examples
- # word_wrap('Once upon a time', 4)
- # # => Once\nupon\na\ntime
- #
- # word_wrap('Once upon a time', 8)
- # # => Once upon\na time
#
# word_wrap('Once upon a time')
# # => Once upon a time
#
- # word_wrap('Once upon a time', 1)
+ # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
+ # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined...
+ #
+ # word_wrap('Once upon a time', :line_width => 8)
+ # # => Once upon\na time
+ #
+ # word_wrap('Once upon a time', :line_width => 1)
# # => Once\nupon\na\ntime
- def word_wrap(text, line_width = 80)
+ #
+ # You can still use <tt>word_wrap</tt> with the old API that accepts the
+ # +line_width+ as its optional second parameter:
+ # word_wrap('Once upon a time', 8) # => Once upon\na time
+ def word_wrap(text, *args)
+ options = args.extract_options!
+ unless args.blank?
+ options[:line_width] = args[0] || 80
+ end
+ options.reverse_merge!(:line_width => 80)
+
text.split("\n").collect do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
+ line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
@@ -336,12 +406,32 @@ module ActionView
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>.
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
#
- def auto_link(text, link = :all, href_options = {}, &block)
+ #
+ # You can still use <tt>auto_link</tt> with the old API that accepts the
+ # +link+ as its optional second parameter and the +html_options+ hash
+ # as its optional third parameter:
+ # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
+ # auto_link(post_body, :urls) # => Once upon\na time
+ # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>.
+ # Please e-mail me at me@email.com."
+ #
+ # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time
+ # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
+ # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
+ def auto_link(text, *args, &block)#link = :all, href_options = {}, &block)
return '' if text.blank?
- case link
- when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block)
- when :email_addresses then auto_link_email_addresses(text, &block)
- when :urls then auto_link_urls(text, href_options, &block)
+
+ options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
+ unless args.empty?
+ options[:link] = args[0] || :all
+ options[:html] = args[1] || {}
+ end
+ options.reverse_merge!(:link => :all, :html => {})
+
+ case options[:link].to_sym
+ when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), &block)
+ when :email_addresses then auto_link_email_addresses(text, &block)
+ when :urls then auto_link_urls(text, options[:html], &block)
end
end
@@ -477,8 +567,8 @@ module ActionView
# Turns all urls into clickable links. If a block is given, each url
# is yielded and the result is used as the link text.
- def auto_link_urls(text, href_options = {})
- extra_options = tag_options(href_options.stringify_keys) || ""
+ def auto_link_urls(text, html_options = {})
+ extra_options = tag_options(html_options.stringify_keys) || ""
text.gsub(AUTO_LINK_RE) do
all, a, b, c, d = $&, $1, $2, $3, $4
if a =~ /<a\s/i # don't replace URL's that are already linked
@@ -508,4 +598,4 @@ module ActionView
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
new file mode 100644
index 0000000000..60ac5c8790
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -0,0 +1,20 @@
+require 'action_view/helpers/tag_helper'
+
+module ActionView
+ module Helpers
+ module TranslationHelper
+ def translate(*args)
+ args << args.extract_options!.merge(:raise => true)
+ I18n.translate *args
+
+ rescue I18n::MissingTranslationData => e
+ keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope]
+ content_tag('span', keys.join(', '), :class => 'translation_missing')
+ end
+
+ def localize(*args)
+ I18n.localize *args
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 94e1f1d33a..f31502d99d 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -3,8 +3,8 @@ require 'action_view/helpers/javascript_helper'
module ActionView
module Helpers #:nodoc:
# Provides a set of methods for making links and getting URLs that
- # depend on the routing subsystem (see ActionController::Routing).
- # This allows you to use the same format for links in views
+ # depend on the routing subsystem (see ActionController::Routing).
+ # This allows you to use the same format for links in views
# and controllers.
module UrlHelper
include JavaScriptHelper
@@ -33,8 +33,8 @@ module ActionView
#
# If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter,
# you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing
- # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
- # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
+ # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
+ # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
#
# ==== Examples
# <%= url_for(:action => 'index') %>
@@ -62,19 +62,33 @@ module ActionView
# <%= url_for(@workshop) %>
# # calls @workshop.to_s
# # => /workshops/5
+ #
+ # <%= url_for("http://www.example.com") %>
+ # # => http://www.example.com
+ #
+ # <%= url_for(:back) %>
+ # # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
+ # # => http://www.example.com
+ #
+ # <%= url_for(:back) %>
+ # # if request.env["HTTP_REFERER"] is not set or is blank
+ # # => javascript:history.back()
def url_for(options = {})
options ||= {}
- case options
+ url = case options
+ when String
+ escape = true
+ options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
- url = @controller.send(:url_for, options)
- when String
- escape = true
- url = options
+ @controller.send(:url_for, options)
+ when :back
+ escape = false
+ @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
escape = false
- url = polymorphic_path(options)
+ polymorphic_path(options)
end
escape ? escape_once(url) : url
@@ -116,8 +130,8 @@ module ActionView
#
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript disabled
- # clicking the link will have no effect. If you are relying on the POST
- # behavior, your should check for it in your controller's action by using the
+ # clicking the link will have no effect. If you are relying on the POST
+ # behavior, your should check for it in your controller's action by using the
# request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
#
# You can mix and match the +html_options+ with the exception of
@@ -141,8 +155,8 @@ module ActionView
#
# link_to "Profile", :controller => "profiles", :action => "show", :id => @profile
# # => <a href="/profiles/show/1">Profile</a>
- #
- # Similarly,
+ #
+ # Similarly,
#
# link_to "Profiles", profiles_path
# # => <a href="/profiles">Profiles</a>
@@ -197,9 +211,9 @@ module ActionView
# # => <a href="/images/9" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a>
#
# link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete
- # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
+ # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
# f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;
- # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
+ # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
# m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a>
def link_to(*args, &block)
if block_given?
@@ -211,14 +225,7 @@ module ActionView
options = args.second || {}
html_options = args.third
- url = case options
- when String
- options
- when :back
- @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
- else
- self.url_for(options)
- end
+ url = url_for(options)
if html_options
html_options = html_options.stringify_keys
@@ -228,7 +235,7 @@ module ActionView
else
tag_options = nil
end
-
+
href_attr = "href=\"#{url}\"" unless href
"<a #{href_attr}#{tag_options}>#{name || url}</a>"
end
@@ -260,7 +267,7 @@ module ActionView
# * <tt>:confirm</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
- #
+ #
# ==== Examples
# <%= button_to "New", :action => "new" %>
# # => "<form method="post" action="/controller/new" class="button-to">
@@ -286,12 +293,12 @@ module ActionView
end
form_method = method.to_s == 'get' ? 'get' : 'post'
-
+
request_token_tag = ''
if form_method == 'post' && protect_against_forgery?
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
end
-
+
if confirm = html_options.delete("confirm")
html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
end
@@ -309,7 +316,7 @@ module ActionView
# Creates a link tag of the given +name+ using a URL created by the set of
# +options+ unless the current request URI is the same as the links, in
# which case only the name is returned (or the given block is yielded, if
- # one exists). You can give link_to_unless_current a block which will
+ # one exists). You can give link_to_unless_current a block which will
# specialize the default behavior (e.g., show a "Start Here" link rather
# than the link's text).
#
@@ -336,13 +343,13 @@ module ActionView
# </ul>
#
# The implicit block given to link_to_unless_current is evaluated if the current
- # action is the action given. So, if we had a comments page and wanted to render a
+ # action is the action given. So, if we had a comments page and wanted to render a
# "Go Back" link instead of a link to the comments page, we could do something like this...
- #
- # <%=
+ #
+ # <%=
# link_to_unless_current("Comment", { :controller => 'comments', :action => 'new}) do
- # link_to("Go back", { :controller => 'posts', :action => 'index' })
- # end
+ # link_to("Go back", { :controller => 'posts', :action => 'index' })
+ # end
# %>
def link_to_unless_current(name, options = {}, html_options = {}, &block)
link_to_unless current_page?(options), name, options, html_options, &block
@@ -359,10 +366,10 @@ module ActionView
# # If the user is logged in...
# # => <a href="/controller/reply/">Reply</a>
#
- # <%=
+ # <%=
# link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name|
# link_to(name, { :controller => "accounts", :action => "signup" })
- # end
+ # end
# %>
# # If the user is logged in...
# # => <a href="/controller/reply/">Reply</a>
@@ -391,10 +398,10 @@ module ActionView
# # If the user isn't logged in...
# # => <a href="/sessions/new/">Login</a>
#
- # <%=
+ # <%=
# link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do
# link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user })
- # end
+ # end
# %>
# # If the user isn't logged in...
# # => <a href="/sessions/new/">Login</a>
@@ -431,20 +438,20 @@ module ActionView
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Examples
- # mail_to "me@domain.com"
+ # mail_to "me@domain.com"
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
- # mail_to "me@domain.com", "My email", :encode => "javascript"
+ # mail_to "me@domain.com", "My email", :encode => "javascript"
# # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
#
- # mail_to "me@domain.com", "My email", :encode => "hex"
+ # mail_to "me@domain.com", "My email", :encode => "hex"
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
#
- # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
+ # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
# # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
#
# mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com",
- # :subject => "This is an example email"
+ # :subject => "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
def mail_to(email_address, name = nil, html_options = {})
html_options = html_options.stringify_keys
diff --git a/actionpack/lib/action_view/locale/en-US.rb b/actionpack/lib/action_view/locale/en-US.rb
new file mode 100644
index 0000000000..3adb199681
--- /dev/null
+++ b/actionpack/lib/action_view/locale/en-US.rb
@@ -0,0 +1,32 @@
+I18n.backend.store_translations :'en-US', {
+ :datetime => {
+ :distance_in_words => {
+ :half_a_minute => 'half a minute',
+ :less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'],
+ :x_seconds => ['1 second', '{{count}} seconds'],
+ :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'],
+ :x_minutes => ['1 minute', '{{count}} minutes'],
+ :about_x_hours => ['about 1 hour', 'about {{count}} hours'],
+ :x_days => ['1 day', '{{count}} days'],
+ :about_x_months => ['about 1 month', 'about {{count}} months'],
+ :x_months => ['1 month', '{{count}} months'],
+ :about_x_years => ['about 1 year', 'about {{count}} year'],
+ :over_x_years => ['over 1 year', 'over {{count}} years']
+ }
+ },
+ :currency => {
+ :format => {
+ :unit => '$',
+ :precision => 2,
+ :separator => '.',
+ :delimiter => ',',
+ :format => '%u%n',
+ }
+ },
+ :active_record => {
+ :error => {
+ :header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"],
+ :message => "There were problems with the following fields:"
+ }
+ }
+}
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index 116d61e13b..eb74d4a4c7 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -102,14 +102,15 @@ module ActionView
#
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
module Partials
+ extend ActiveSupport::Memoizable
+
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
local_assigns ||= {}
case partial_path
when String, Symbol, NilClass
- variable_name, path = partial_pieces(partial_path)
- pick_template(path).render_partial(self, variable_name, object_assigns, local_assigns)
+ pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns)
when ActionView::Helpers::FormBuilder
builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '')
render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path))
@@ -130,43 +131,28 @@ module ActionView
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
- _partial_pieces = {}
- _templates = {}
index = 0
collection.map do |object|
_partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
- variable_name, path = _partial_pieces[_partial_path] ||= partial_pieces(_partial_path)
- template = _templates[path] ||= pick_template(path)
-
- local_assigns["#{variable_name}_counter".to_sym] = index
- local_assigns[:object] = local_assigns[variable_name] = object
- local_assigns[as] = object if as
-
- result = template.render_partial(self, variable_name, object, local_assigns)
-
- local_assigns.delete(as)
- local_assigns.delete(variable_name)
- local_assigns.delete(:object)
+ path = find_partial_path(_partial_path)
+ template = pick_template(path)
+ local_assigns[template.counter_name] = index
+ result = template.render_partial(self, object, local_assigns, as)
index += 1
-
result
end.join(spacer)
end
- def partial_pieces(partial_path)
+ def find_partial_path(partial_path)
if partial_path.include?('/')
- variable_name = File.basename(partial_path)
- path = "#{File.dirname(partial_path)}/_#{variable_name}"
+ "#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
elsif respond_to?(:controller)
- variable_name = partial_path
- path = "#{controller.class.controller_path}/_#{variable_name}"
+ "#{controller.class.controller_path}/_#{partial_path}"
else
- variable_name = partial_path
- path = "_#{variable_name}"
+ "_#{partial_path}"
end
- variable_name = variable_name.sub(/\..*$/, '').to_sym
- return variable_name, path
end
+ memoize :find_partial_path
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index b0ab7d0c67..a37706faee 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -1,5 +1,5 @@
module ActionView #:nodoc:
- class PathSet < Array #:nodoc:
+ class PathSet < ActiveSupport::TypedArray #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
@@ -16,69 +16,77 @@ module ActionView #:nodoc:
end
class Path #:nodoc:
+ def self.eager_load_templates!
+ @eager_load_templates = true
+ end
+
+ def self.eager_load_templates?
+ @eager_load_templates || false
+ end
+
attr_reader :path, :paths
- delegate :to_s, :to_str, :inspect, :to => :path
+ delegate :to_s, :to_str, :hash, :inspect, :to => :path
- def initialize(path)
+ def initialize(path, load = true)
+ raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
@path = path.freeze
- reload!
+ reload! if load
end
def ==(path)
to_str == path.to_str
end
+ def eql?(path)
+ to_str == path.to_str
+ end
+
def [](path)
@paths[path]
end
+ def loaded?
+ @loaded ? true : false
+ end
+
+ def load
+ reload! unless loaded?
+ end
+
# Rebuild load path directory cache
def reload!
@paths = {}
templates_in_path do |template|
+ # Eager load memoized methods and freeze cached template
+ template.freeze if self.class.eager_load_templates?
+
@paths[template.path] = template
@paths[template.path_without_extension] ||= template
end
@paths.freeze
+ @loaded = true
end
private
def templates_in_path
(Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
unless File.directory?(file)
- template = Template.new(file.split("#{self}/").last, self)
- # Eager load memoized methods and freeze cached template
- template.freeze if Base.cache_template_loading
- yield template
+ yield Template.new(file.split("#{self}/").last, self)
end
end
end
end
- def initialize(*args)
- super(*args).map! { |obj| self.class.type_cast(obj) }
+ def load
+ each { |path| path.load }
end
def reload!
each { |path| path.reload! }
end
- def <<(obj)
- super(self.class.type_cast(obj))
- end
-
- def push(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
- def unshift(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
def [](template_path)
each do |path|
if template = path[template_path]
@@ -87,10 +95,5 @@ module ActionView #:nodoc:
end
nil
end
-
- private
- def delete_paths!(paths)
- paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
- end
end
end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 2c4302146f..5fe1ca86f3 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -3,26 +3,35 @@ module ActionView
# NOTE: The template that this mixin is beening include into is frozen
# So you can not set or modify any instance variables
+ extend ActiveSupport::Memoizable
+
def self.included(base)
@@mutex = Mutex.new
end
- # NOTE: Exception to earlier notice. Ensure this is called before freeze
+ def filename
+ 'compiled-template'
+ end
+
def handler
- @handler ||= Template.handler_class_for_extension(extension)
+ Template.handler_class_for_extension(extension)
end
+ memoize :handler
- # NOTE: Exception to earlier notice. Ensure this is called before freeze
def compiled_source
- @compiled_source ||= handler.new(nil).compile(self) if handler.compilable?
+ handler.call(self)
end
+ memoize :compiled_source
def render(view, local_assigns = {})
- view.first_render ||= self
+ compile(local_assigns)
+
+ view._first_render ||= self
+ view._last_render = self
+
view.send(:evaluate_assigns)
- view.current_render_extension = extension
- compile(local_assigns) if handler.compilable?
- handler.new(view).render(self, local_assigns)
+ view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
+ view.send(:execute, method(local_assigns), local_assigns)
end
def method(local_assigns)
@@ -33,47 +42,49 @@ module ActionView
end
private
- # Compile and evaluate the template's code
+ # Compile and evaluate the template's code (if necessary)
def compile(local_assigns)
render_symbol = method(local_assigns)
@@mutex.synchronize do
- return false unless recompile?(render_symbol)
-
- locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
-
- source = <<-end_src
- def #{render_symbol}(local_assigns)
- old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
- ensure
- self.output_buffer = old_output_buffer
- end
- end_src
-
- begin
- file_name = respond_to?(:filename) ? filename : 'compiled-template'
- ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0)
- rescue Exception => e # errors from template code
- if logger = ActionController::Base.logger
- logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
- logger.debug "Function body: #{source}"
- logger.debug "Backtrace: #{e.backtrace.join("\n")}"
- end
-
- raise ActionView::TemplateError.new(self, {}, e)
+ if recompile?(render_symbol)
+ compile!(render_symbol, local_assigns)
end
end
end
+ def compile!(render_symbol, local_assigns)
+ locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
+
+ source = <<-end_src
+ def #{render_symbol}(local_assigns)
+ old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
+ ensure
+ self.output_buffer = old_output_buffer
+ end
+ end_src
+
+ begin
+ logger = ActionController::Base.logger
+ logger.debug "Compiling template #{render_symbol}" if logger
+
+ ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
+ rescue Exception => e # errors from template code
+ if logger
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
+ logger.debug "Function body: #{source}"
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
+ end
+
+ raise ActionView::TemplateError.new(self, {}, e)
+ end
+ end
+
# Method to check whether template compilation is necessary.
# The template will be compiled if the file has not been compiled yet, or
# if local_assigns has a new key, which isn't supported by the compiled code yet.
def recompile?(symbol)
- unless Base::CompiledTemplates.instance_methods.include?(symbol) && Base.cache_template_loading
- true
- else
- false
- end
+ !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol))
end
end
end
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
index 6a17b50a14..342850f0f0 100644
--- a/actionpack/lib/action_view/renderable_partial.rb
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -3,16 +3,33 @@ module ActionView
# NOTE: The template that this mixin is beening include into is frozen
# So you can not set or modify any instance variables
+ extend ActiveSupport::Memoizable
+
+ def variable_name
+ name.sub(/\A_/, '').to_sym
+ end
+ memoize :variable_name
+
+ def counter_name
+ "#{variable_name}_counter".to_sym
+ end
+ memoize :counter_name
+
def render(view, local_assigns = {})
ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do
super
end
end
- def render_partial(view, variable_name, object = nil, local_assigns = {}, as = nil)
- object ||= view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller)
- local_assigns[:object] ||= local_assigns[variable_name] ||= object
- local_assigns[as] ||= local_assigns[:object] if as
+ def render_partial(view, object = nil, local_assigns = {}, as = nil)
+ object ||= local_assigns[:object] ||
+ local_assigns[variable_name] ||
+ view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller)
+
+ # Ensure correct object is reassigned to other accessors
+ local_assigns[:object] = local_assigns[variable_name] = object
+ local_assigns[as] = object if as
+
render_template(view, local_assigns)
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 03f9234289..b281ff61f2 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,6 +1,7 @@
module ActionView #:nodoc:
class Template
extend TemplateHandlers
+ extend ActiveSupport::Memoizable
include Renderable
attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
@@ -16,54 +17,42 @@ module ActionView #:nodoc:
extend RenderablePartial if @name =~ /^_/
end
- def freeze
- # Eager load memoized methods
- format_and_extension
- path
- path_without_extension
- path_without_format_and_extension
- source
- method_segment
-
- # Eager load memoized methods from Renderable
- handler
- compiled_source
-
- instance_variables.each { |ivar| ivar.freeze }
-
- super
+ def format_and_extension
+ (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
end
+ memoize :format_and_extension
- def format_and_extension
- @format_and_extension ||= (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
+ def mime_type
+ Mime::Type.lookup_by_extension(format) if format
end
+ memoize :mime_type
def path
- @path ||= [base_path, [name, format, extension].compact.join('.')].compact.join('/')
+ [base_path, [name, format, extension].compact.join('.')].compact.join('/')
end
+ memoize :path
def path_without_extension
- @path_without_extension ||= [base_path, [name, format].compact.join('.')].compact.join('/')
+ [base_path, [name, format].compact.join('.')].compact.join('/')
end
+ memoize :path_without_extension
def path_without_format_and_extension
- @path_without_format_and_extension ||= [base_path, name].compact.join('/')
+ [base_path, name].compact.join('/')
end
+ memoize :path_without_format_and_extension
def source
- @source ||= File.read(@filename)
+ File.read(filename)
end
+ memoize :source
def method_segment
- unless @method_segment
- segment = File.expand_path(@filename)
- segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
- segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
- @method_segment = segment
- end
-
- @method_segment
+ segment = File.expand_path(filename)
+ segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
+ segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
end
+ memoize :method_segment
def render_template(view, local_assigns = {})
render(view, local_assigns)
@@ -86,7 +75,7 @@ module ActionView #:nodoc:
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
file = [load_path, path].compact.join('/')
- return load_path, file if File.exist?(file)
+ return load_path, file if File.file?(file)
end
raise MissingTemplate.new(load_paths, path)
end
diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb
index 1afea21f67..d7e7c9b199 100644
--- a/actionpack/lib/action_view/template_handler.rb
+++ b/actionpack/lib/action_view/template_handler.rb
@@ -1,25 +1,14 @@
-module ActionView
- class TemplateHandler
- def self.compilable?
- false
- end
-
- def initialize(view)
- @view = view
- end
-
- def render(template, local_assigns = {})
- end
-
- def compile(template)
- end
+# Legacy TemplateHandler stub
- def compilable?
- self.class.compilable?
+module ActionView
+ module TemplateHandlers
+ module Compilable
end
+ end
- # Called by CacheHelper#cache
- def cache_fragment(block, name = {}, options = nil)
+ class TemplateHandler
+ def self.call(template)
+ new.compile(template)
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb
index 1471e99e01..6c8aa4c2a7 100644
--- a/actionpack/lib/action_view/template_handlers.rb
+++ b/actionpack/lib/action_view/template_handlers.rb
@@ -1,5 +1,4 @@
require 'action_view/template_handler'
-require 'action_view/template_handlers/compilable'
require 'action_view/template_handlers/builder'
require 'action_view/template_handlers/erb'
require 'action_view/template_handlers/rjs'
diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb
index cbe53e11d8..7d24a5c423 100644
--- a/actionpack/lib/action_view/template_handlers/builder.rb
+++ b/actionpack/lib/action_view/template_handlers/builder.rb
@@ -6,18 +6,12 @@ module ActionView
include Compilable
def compile(template)
- # ActionMailer does not have a response
- "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" +
+ "set_controller_content_type(Mime::XML);" +
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
+ "self.output_buffer = xml.target!;" +
template.source +
";xml.target!;"
end
-
- def cache_fragment(block, name = {}, options = nil)
- @view.fragment_for(block, name, options) do
- eval('xml.target!', block.binding)
- end
- end
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb
deleted file mode 100644
index a0ebaefeef..0000000000
--- a/actionpack/lib/action_view/template_handlers/compilable.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module ActionView
- module TemplateHandlers
- module Compilable
- def self.included(base)
- base.extend ClassMethod
- end
-
- module ClassMethod
- # If a handler is mixin this module, set compilable to true
- def compilable?
- true
- end
- end
-
- def render(template, local_assigns = {})
- @view.send(:execute, template, local_assigns)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb
index ac715e30c2..3def949f1e 100644
--- a/actionpack/lib/action_view/template_handlers/erb.rb
+++ b/actionpack/lib/action_view/template_handlers/erb.rb
@@ -48,14 +48,11 @@ module ActionView
self.erb_trim_mode = '-'
def compile(template)
- src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src
- "__in_erb_template=true;#{src}"
- end
+ src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
- def cache_fragment(block, name = {}, options = nil) #:nodoc:
- @view.fragment_for(block, name, options) do
- @view.response.template.output_buffer
- end
+ # Ruby 1.9 prepends an encoding to the source. However this is
+ # useless because you can only set an encoding on the first line
+ RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/rjs.rb b/actionpack/lib/action_view/template_handlers/rjs.rb
index 3892bf1d6e..a700655c9a 100644
--- a/actionpack/lib/action_view/template_handlers/rjs.rb
+++ b/actionpack/lib/action_view/template_handlers/rjs.rb
@@ -7,17 +7,6 @@ module ActionView
"controller.response.content_type ||= Mime::JS;" +
"update_page do |page|;#{template.source}\nend"
end
-
- def cache_fragment(block, name = {}, options = nil) #:nodoc:
- @view.fragment_for(block, name, options) do
- begin
- debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false
- eval('page.to_s', block.binding)
- ensure
- ActionView::Base.debug_rjs = debug_mode
- end
- end
- end
end
end
end