aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib/action_view/helpers')
-rw-r--r--actionview/lib/action_view/helpers/active_model_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb100
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb55
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/csrf_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb89
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb18
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb90
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb199
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb169
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb87
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/record_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb163
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb34
-rw-r--r--actionview/lib/action_view/helpers/tags.rb64
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb20
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb5
-rw-r--r--actionview/lib/action_view/helpers/tags/datetime_field.rb12
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb68
-rw-r--r--actionview/lib/action_view/helpers/tags/placeholderable.rb34
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb5
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb45
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb51
32 files changed, 863 insertions, 552 deletions
diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb
index 901f433c70..d5222e3616 100644
--- a/actionview/lib/action_view/helpers/active_model_helper.rb
+++ b/actionview/lib/action_view/helpers/active_model_helper.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/enumerable'
module ActionView
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index a13d0021ea..b7fdc16a9d 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -7,7 +7,7 @@ module ActionView
# = Action View Asset Tag Helpers
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
+ # as images, JavaScripts, stylesheets, and feeds. These methods do not verify
# the assets exist before linking to them:
#
# image_tag("rails.png")
@@ -103,7 +103,7 @@ module ActionView
}.join("\n").html_safe
end
- # Returns a link tag that browsers and news readers can use to auto-detect
+ # Returns a link tag that browsers and feed 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
# +url_options+. You can modify the LINK tag itself in +tag_options+.
@@ -142,30 +142,37 @@ module ActionView
)
end
- # Returns a link loading a favicon file. You may specify a different file
- # in the first argument. The helper accepts an additional options hash where
- # you can override "rel" and "type".
+ # Returns a link tag for a favicon managed by the asset pipeline.
#
- # ==== Options
+ # If a page has no link like the one generated by this helper, browsers
+ # ask for <tt>/favicon.ico</tt> automatically, and cache the file if the
+ # request succeeds. If the favicon changes it is hard to get it updated.
#
- # * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon'
- # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon'
+ # To have better control applications may let the asset pipeline manage
+ # their favicon storing the file under <tt>app/assets/images</tt>, and
+ # using this helper to generate its corresponding link tag.
#
- # ==== Examples
+ # The helper gets the name of the favicon file as first argument, which
+ # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
+ # to override their defaults, "shortcut icon" and "image/x-icon"
+ # respectively:
#
- # favicon_link_tag '/myicon.ico'
- # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # favicon_link_tag
+ # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
#
- # Mobile Safari looks for a different <link> tag, pointing to an image that
- # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
+ # favicon_link_tag 'myicon.ico'
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
+ #
+ # Mobile Safari looks for a different link tag, pointing to an image that
+ # will be used if you add the page to the home screen of an iOS device.
# The following call would generate such a tag:
#
- # favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
+ # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
- :type => 'image/vnd.microsoft.icon',
+ :type => 'image/x-icon',
:href => path_to_image(source)
}.merge!(options.symbolize_keys))
end
@@ -176,7 +183,7 @@ module ActionView
# ==== Options
#
# You can add HTML attributes using the +options+. The +options+ supports
- # three additional keys for convenience and conformance:
+ # two additional keys for convenience and conformance:
#
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
# +source+ is used (capitalized and without the extension)
@@ -207,15 +214,11 @@ module ActionView
options[:alt] = options.fetch(:alt){ image_alt(src) }
end
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z}
- options[:width] = options[:height] = size if size =~ %r{\A\d+\z}
- end
-
+ options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
tag("img", options)
end
- # Returns a string suitable for an html image tag alt attribute.
+ # Returns a string suitable for an HTML image tag alt attribute.
# The +src+ argument is meant to be an image file path.
# The method removes the basename of the file path and the digest,
# if any. It also removes hyphens and underscores from file names and
@@ -224,19 +227,19 @@ module ActionView
#
# ==== Examples
#
- # image_tag('rails.png')
- # # => <img alt="Rails" src="/assets/rails.png" />
+ # image_alt('rails.png')
+ # # => Rails
#
- # image_tag('hyphenated-file-name.png')
- # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" />
+ # image_alt('hyphenated-file-name.png')
+ # # => Hyphenated file name
#
- # image_tag('underscored_file_name.png')
- # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" />
+ # image_alt('underscored_file_name.png')
+ # # => Underscored file name
def image_alt(src)
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
end
- # Returns an html video tag for the +sources+. If +sources+ is a string,
+ # Returns an HTML video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
# +sources+ can be full paths or files that exists in your public videos
@@ -248,24 +251,26 @@ module ActionView
#
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
# before the video loads. The path is calculated like the +src+ of +image_tag+.
- # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
- # width="30" and height="45". <tt>:size</tt> will be ignored if the
- # value is not in the correct format.
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
+ # width="30" and height="45", and "50" becomes width="50" and height="50".
+ # <tt>:size</tt> will be ignored if the value is not in the correct format.
#
# ==== Examples
#
# video_tag("trailer")
- # # => <video src="/videos/trailer" />
+ # # => <video src="/videos/trailer"></video>
# video_tag("trailer.ogg")
- # # => <video src="/videos/trailer.ogg" />
+ # # => <video src="/videos/trailer.ogg"></video>
# video_tag("trailer.ogg", controls: true, autobuffer: true)
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
+ # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
- # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
# video_tag("/trailers/hd.avi", size: "16x16")
- # # => <video src="/trailers/hd.avi" width="16" height="16" />
+ # # => <video src="/trailers/hd.avi" width="16" height="16"></video>
+ # video_tag("/trailers/hd.avi", size: "16")
+ # # => <video height="16" src="/trailers/hd.avi" width="16"></video>
# video_tag("/trailers/hd.avi", height: '32', width: '32')
- # # => <video height="32" src="/trailers/hd.avi" width="32" />
+ # # => <video height="32" src="/trailers/hd.avi" width="32"></video>
# video_tag("trailer.ogg", "trailer.flv")
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"])
@@ -275,10 +280,7 @@ module ActionView
def video_tag(*sources)
multiple_sources_tag('video', sources) do |options|
options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
+ options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
end
end
@@ -287,11 +289,11 @@ module ActionView
# your public audios directory.
#
# audio_tag("sound")
- # # => <audio src="/audios/sound" />
+ # # => <audio src="/audios/sound"></audio>
# audio_tag("sound.wav")
- # # => <audio src="/audios/sound.wav" />
+ # # => <audio src="/audios/sound.wav"></audio>
# audio_tag("sound.wav", autoplay: true, controls: true)
- # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
# audio_tag("sound.wav", "sound.mid")
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
@@ -314,6 +316,14 @@ module ActionView
content_tag(type, nil, options)
end
end
+
+ def extract_dimensions(size)
+ if size =~ %r{\A\d+x\d+\z}
+ size.split('x')
+ elsif size =~ %r{\A\d+\z}
+ [size, size]
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 0b957adb91..29733442c1 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -88,9 +88,12 @@ module ActionView
# still sending assets for plain HTTP requests from asset hosts. If you don't
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
+ # Note that the request parameter might not be supplied, e.g. when the assets
+ # are precompiled via a Rake task. Make sure to use a Proc instead of a lambda,
+ # since a Proc allows missing parameters and sets them to nil.
#
# config.action_controller.asset_host = Proc.new { |source, request|
- # if request.ssl?
+ # if request && request.ssl?
# "#{request.protocol}#{request.host_with_port}"
# else
# "#{request.protocol}assets.example.com"
@@ -113,9 +116,9 @@ module ActionView
#
# All other asset *_path helpers delegate through this method.
#
- # asset_path "application.js" # => /application.js
- # asset_path "application", type: :javascript # => /javascripts/application.js
- # asset_path "application", type: :stylesheet # => /stylesheets/application.css
+ # asset_path "application.js" # => /assets/application.js
+ # asset_path "application", type: :javascript # => /assets/application.js
+ # asset_path "application", type: :stylesheet # => /assets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
source = source.to_s
@@ -134,20 +137,27 @@ module ActionView
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
if relative_url_root
- source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/")
+ source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/")
end
if host = compute_asset_host(source, options)
- source = "#{host}#{source}"
+ source = File.join(host, source)
end
"#{source}#{tail}"
end
- alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route
+ alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
- # Computes the full URL to a asset in the public directory. This
+ # Computes the full URL to an asset in the public directory. This
# will use +asset_path+ internally, so most of their behaviors
- # will be the same.
+ # will be the same. If :host options is set, it overwrites global
+ # +config.action_controller.asset_host+ setting.
+ #
+ # All other options provided are forwarded to +asset_path+ call.
+ #
+ # asset_url "application.js" # => http://example.com/assets/application.js
+ # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
+ #
def asset_url(source, options = {})
path_to_asset(source, options.merge(:protocol => :request))
end
@@ -191,8 +201,8 @@ module ActionView
# (proc or otherwise).
def compute_asset_host(source = "", options = {})
request = self.request if respond_to?(:request)
- host = config.asset_host if defined? config.asset_host
- host ||= request.base_url if request && options[:protocol] == :request
+ host = options[:host]
+ host ||= config.asset_host if defined? config.asset_host
if host.respond_to?(:call)
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
@@ -203,6 +213,7 @@ module ActionView
host = host % (Zlib.crc32(source) % 4)
end
+ host ||= request.base_url if request && options[:protocol] == :request
return unless host
if host =~ URI_REGEXP
@@ -220,13 +231,13 @@ module ActionView
end
end
- # Computes the path to a javascript asset in the public javascripts directory.
+ # Computes the path to a JavaScript asset in the public javascripts directory.
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
- # Used internally by javascript_include_tag to build the script path.
+ # Used internally by +javascript_include_tag+ to build the script path.
#
- # javascript_path "xmlhr" # => /javascripts/xmlhr.js
- # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
+ # javascript_path "xmlhr" # => /assets/xmlhr.js
+ # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
@@ -235,7 +246,7 @@ module ActionView
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
- # Computes the full URL to a javascript asset in the public javascripts directory.
+ # Computes the full URL to a JavaScript asset in the public javascripts directory.
# This will use +javascript_path+ internally, so most of their behaviors will be the same.
def javascript_url(source, options = {})
url_to_asset(source, {type: :javascript}.merge!(options))
@@ -243,12 +254,12 @@ module ActionView
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
# Computes the path to a stylesheet asset in the public stylesheets directory.
- # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
+ # If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
- # stylesheet_path "style" # => /stylesheets/style.css
- # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
+ # stylesheet_path "style" # => /assets/style.css
+ # stylesheet_path "dir/style.css" # => /assets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
@@ -334,9 +345,9 @@ module ActionView
# Computes the path to a font asset.
# Full paths from the document root will be passed through.
#
- # font_path("font") # => /assets/font
- # font_path("font.ttf") # => /assets/font.ttf
- # font_path("dir/font.ttf") # => /assets/dir/font.ttf
+ # font_path("font") # => /fonts/font
+ # font_path("font.ttf") # => /fonts/font.ttf
+ # font_path("dir/font.ttf") # => /fonts/dir/font.ttf
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source, options = {})
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index 42b1dd8933..227ad4cdfa 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -10,7 +10,7 @@ module ActionView
# Full usage example:
#
# config/routes.rb:
- # Basecamp::Application.routes.draw do
+ # Rails.application.routes.draw do
# resources :posts
# root to: "posts#index"
# end
@@ -64,7 +64,7 @@ module ActionView
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
# feed.title("My great blog!")
# feed.updated((@posts.first.created_at))
- # feed.tag!(openSearch:totalResults, 10)
+ # feed.tag!('openSearch:totalResults', 10)
#
# @posts.each do |post|
# feed.entry(post) do |entry|
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 2a38e5c446..4db8930a26 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -4,14 +4,14 @@ module ActionView
module CacheHelper
# This helper exposes a method for caching fragments of a view
# rather than an entire action or page. This technique is useful
- # caching pieces like menus, lists of newstopics, static HTML
+ # caching pieces like menus, lists of new topics, static HTML
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
# The best way to use this is by doing key-based cache expiration
# on top of a cache store like Memcached that'll automatically
# kick out old entries. For more on key-based expiration, see:
- # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
+ # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works
#
# When using this method, you list the cache dependency as the name of the cache, like so:
#
@@ -165,10 +165,10 @@ module ActionView
def fragment_name_with_digest(name) #:nodoc:
if @virtual_path
- [
- *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name),
- Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies)
- ]
+ names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name)
+ digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
+
+ [ *names, digest ]
else
name
end
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 5afe435459..75d1634b2e 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -202,15 +202,6 @@ module ActionView
ensure
self.output_buffer = old_buffer
end
-
- # Add the output buffer to the response body and start a new one.
- def flush_output_buffer #:nodoc:
- if output_buffer && !output_buffer.empty?
- response.stream.write output_buffer
- self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
- nil
- end
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb
index eeb0ed94b9..5af92c4ff2 100644
--- a/actionview/lib/action_view/helpers/csrf_helper.rb
+++ b/actionview/lib/action_view/helpers/csrf_helper.rb
@@ -12,8 +12,11 @@ module ActionView
# These are used to generate the dynamic forms that implement non-remote links with
# <tt>:method</tt>.
#
- # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted,
- # so they do not use these tags.
+ # You don't need to use these tags for regular forms as they generate their own hidden fields.
+ #
+ # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
+ # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically.
+ #
def csrf_meta_tags
if protect_against_forgery?
[
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 8e1aea50a9..9272bb5c10 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -19,6 +19,10 @@ module ActionView
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
# of \date[month].
module DateHelper
+ MINUTES_IN_YEAR = 525600
+ MINUTES_IN_QUARTER_YEAR = 131400
+ MINUTES_IN_THREE_QUARTERS_YEAR = 394200
+
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
# Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
@@ -50,19 +54,19 @@ module ActionView
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
- # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
# distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
- # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute
- # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
# distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
#
# to_time = Time.now + 6.years + 19.days
- # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
- # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
+ # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
+ # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 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, options = {})
options = {
@@ -115,16 +119,16 @@ module ActionView
# e.g. if there are 20 leap year days between 2 dates having the same day
# and month then the based on 365 days calculation
# the distance in years will come out to over 80 years when in written
- # english it would read better as about 80 years.
+ # English it would read better as about 80 years.
minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
else
minutes_with_offset = distance_in_minutes
end
- remainder = (minutes_with_offset % 525600)
- distance_in_years = (minutes_with_offset.div 525600)
- if remainder < 131400
+ remainder = (minutes_with_offset % MINUTES_IN_YEAR)
+ distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
+ if remainder < MINUTES_IN_QUARTER_YEAR
locale.t(:about_x_years, :count => distance_in_years)
- elsif remainder < 394200
+ elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
locale.t(:over_x_years, :count => distance_in_years)
else
locale.t(:almost_x_years, :count => distance_in_years + 1)
@@ -149,8 +153,8 @@ module ActionView
#
# Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>.
#
- def time_ago_in_words(from_time, include_seconds_or_options = {})
- distance_of_time_in_words(from_time, Time.now, include_seconds_or_options)
+ def time_ago_in_words(from_time, options = {})
+ distance_of_time_in_words(from_time, Time.now, options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
@@ -169,9 +173,16 @@ module ActionView
# "2 - February" instead of "February").
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
# Note: You can also use Rails' i18n functionality for this.
+ # * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer)
+ # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
+ # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
- # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>.
- # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
+ # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if
+ # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
+ # the current selected year minus 5.
+ # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
+ # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
+ # the current selected year plus 5.
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
# first of the given month in order to not create invalid dates like 31 February.
@@ -319,7 +330,7 @@ module ActionView
Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
end
- # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
+ # Returns a set of HTML select-tags (one for year, month, day, hour, minute, and second) 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
@@ -368,7 +379,7 @@ module ActionView
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
- # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
+ # 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
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
# If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
@@ -407,7 +418,7 @@ module ActionView
DateTimeSelector.new(date, options, html_options).select_date
end
- # Returns a set of html select-tags (one for hour and minute).
+ # 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
# the <tt>:include_seconds</tt> option to include an input for seconds.
#
@@ -531,7 +542,7 @@ module ActionView
# my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
- # select_day(my_time)
+ # select_day(my_date)
#
# # Generates a select field for days that defaults to the number given.
# select_day(5)
@@ -541,7 +552,7 @@ module ActionView
#
# # 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')
+ # select_day(my_date, field_name: 'due')
#
# # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a
# # generic prompt.
@@ -624,7 +635,7 @@ module ActionView
DateTimeSelector.new(date, options, html_options).select_year
end
- # Returns an html time tag for the given date or time.
+ # Returns an HTML time tag for the given date or time.
#
# time_tag Date.today # =>
# <time datetime="2010-11-04">November 04, 2010</time>
@@ -846,24 +857,36 @@ module ActionView
I18n.translate(key, :locale => @options[:locale])
end
- # Lookup month name for number.
- # month_name(1) => "January"
+ # Looks up month names by number (1-based):
+ #
+ # month_name(1) # => "January"
+ #
+ # If the <tt>:use_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => 1
+ #
+ # If the <tt>:use_two_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => '01'
+ #
+ # If the <tt>:add_month_numbers</tt> option is passed:
+ #
+ # month_name(1) # => "1 - January"
#
- # If <tt>:use_month_numbers</tt> option is passed
- # month_name(1) => 1
+ # If the <tt>:month_format_string</tt> option is passed:
#
- # If <tt>:use_two_month_numbers</tt> option is passed
- # month_name(1) => '01'
+ # month_name(1) # => "January (01)"
#
- # If <tt>:add_month_numbers</tt> option is passed
- # month_name(1) => "1 - January"
+ # depending on the format string.
def month_name(number)
if @options[:use_month_numbers]
number
elsif @options[:use_two_digit_numbers]
- sprintf "%02d", number
+ '%02d' % number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
+ elsif format_string = @options[:month_format_string]
+ format_string % {number: number, name: month_names[number]}
else
month_names[number]
end
@@ -891,7 +914,7 @@ module ActionView
build_select(type, build_options(selected, options))
end
- # Build select option html from date value and options.
+ # Build select option HTML from date value and options.
# build_options(15, start: 1, end: 31)
# => "<option value="1">1</option>
# <option value="2">2</option>
@@ -931,7 +954,7 @@ module ActionView
(select_options.join("\n") + "\n").html_safe
end
- # Builds select tag from date type and html select options.
+ # Builds select tag from date type and HTML select options.
# build_select(:month, "<option value="1">January</option>...")
# => "<select id="post_written_on_2i" name="post[written_on(2i)]">
# <option value="1">January</option>...
@@ -942,7 +965,7 @@ module ActionView
:name => input_name_from_type(type)
}.merge!(@html_options)
select_options[:disabled] = 'disabled' if @options[:disabled]
- select_options[:class] = type if @options[:with_css_classes]
+ select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
@@ -1062,7 +1085,7 @@ module ActionView
# Wraps ActionView::Helpers::DateHelper#datetime_select for form builders:
#
# <%= form_for @person do |f| %>
- # <%= f.time_select :last_request_at %>
+ # <%= f.datetime_select :last_request_at %>
# <%= f.submit %>
# <% end %>
#
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index c29c1b1eea..ba47eee9ba 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -11,24 +11,20 @@ module ActionView
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
- # @user = User.new({ username: 'testing', password: 'xyz', age: 42}) %>
+ # @user = User.new({ username: 'testing', password: 'xyz', age: 42})
# debug(@user)
# # =>
# <pre class='debug_dump'>--- !ruby/object:User
# attributes:
- # &nbsp; updated_at:
- # &nbsp; username: testing
- #
- # &nbsp; age: 42
- # &nbsp; password: xyz
- # &nbsp; created_at:
- # attributes_cache: {}
- #
- # new_record: true
+ # updated_at:
+ # username: testing
+ # age: 42
+ # password: xyz
+ # created_at:
# </pre>
def debug(object)
Marshal::dump(object)
- object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
+ object = ERB::Util.html_escape(object.to_yaml)
content_tag(:pre, object, :class => "debug_dump")
rescue Exception # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 8a4830d887..09843ca70d 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -3,9 +3,8 @@ require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
-require 'action_view/helpers/tags'
require 'action_view/model_naming'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/inflections'
@@ -52,7 +51,7 @@ module ActionView
# The HTML generated for this would be (modulus formatting):
#
# <form action="/people" class="new_person" id="new_person" method="post">
- # <div style="margin:0;padding:0;display:inline">
+ # <div style="display:none">
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
# <label for="person_first_name">First name</label>:
@@ -82,7 +81,7 @@ module ActionView
# the code above as is would yield instead:
#
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
- # <div style="margin:0;padding:0;display:inline">
+ # <div style="display:none">
# <input name="_method" type="hidden" value="patch" />
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
@@ -143,7 +142,7 @@ module ActionView
# will get expanded to
#
# <%= text_field :person, :first_name %>
- # which results in an html <tt><input></tt> tag whose +name+ attribute is
+ # which results in an HTML <tt><input></tt> tag whose +name+ attribute is
# <tt>person[first_name]</tt>. This means that when the form is submitted,
# the value entered by the user will be available in the controller as
# <tt>params[:person][:first_name]</tt>.
@@ -316,7 +315,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-remote='true'>
- # <div style='margin:0;padding:0;display:inline'>
+ # <div style='display:none'>
# <input name='_method' type='hidden' value='patch' />
# </div>
# ...
@@ -334,7 +333,7 @@ module ActionView
# The HTML generated for this would be:
#
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
- # <div style='margin:0;padding:0;display:inline'>
+ # <div style='display:none'>
# <input name='_method' type='hidden' value='patch' />
# </div>
# ...
@@ -435,21 +434,27 @@ module ActionView
output = capture(builder, &block)
html_options[:multipart] ||= builder.multipart?
- form_tag(options[:url] || {}, html_options) { output }
+ html_options = html_options_for_form(options[:url] || {}, html_options)
+ form_tag_with_body(html_options, output)
end
def apply_form_for_options!(record, object, options) #:nodoc:
object = convert_to_model(object)
as = options[:as]
+ namespace = options[:namespace]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
options[:html].reverse_merge!(
class: as ? "#{action}_#{as}" : dom_class(object, action),
- id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
+ id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
method: method
)
- options[:url] ||= polymorphic_path(record, format: options.delete(:format))
+ options[:url] ||= if options.key?(:format)
+ polymorphic_path(record, format: options.delete(:format))
+ else
+ polymorphic_path(record, {})
+ end
end
private :apply_form_for_options!
@@ -457,7 +462,7 @@ module ActionView
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
#
- # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
# its method signature is slightly different. Like +form_for+, it yields
# a FormBuilder object associated with a particular model object to a block,
# and within the block allows methods to be called on the builder to
@@ -477,7 +482,7 @@ module ActionView
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
- # <%= f.submit %>
+ # <%= person_form.submit %>
# <% end %>
#
# In this case, the checkbox field will be represented by an HTML +input+
@@ -746,6 +751,7 @@ module ActionView
# label(:post, :terms) do
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(object_name, method, content_or_options = nil, options = nil, &block)
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
@@ -762,8 +768,8 @@ module ActionView
# text_field(:post, :title, class: "create_input")
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
#
- # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }")
- # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/>
+ # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
#
# text_field(:snippet, :code, size: 20, class: 'code_input')
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
@@ -1007,6 +1013,18 @@ module ActionView
# date_field("user", "born_on", value: "1984-05-12")
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # date_field("user", "born_on", min: Date.today)
+ # # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 date as the
+ # values for "min" and "max."
+ #
+ # date_field("user", "born_on", min: "2014-05-20")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
+ #
def date_field(object_name, method, options = {})
Tags::DateField.new(object_name, method, self, options).render
end
@@ -1024,6 +1042,18 @@ module ActionView
# time_field("task", "started_at")
# # => <input id="task_started_at" name="task[started_at]" type="time" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # time_field("task", "started_at", min: Time.now)
+ # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 time as the
+ # values for "min" and "max."
+ #
+ # time_field("task", "started_at", min: "01:00:00")
+ # # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
+ #
def time_field(object_name, method, options = {})
Tags::TimeField.new(object_name, method, self, options).render
end
@@ -1041,6 +1071,18 @@ module ActionView
# datetime_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # datetime_field("user", "born_on", min: Date.today)
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 datetime
+ # with UTC offset as the values for "min" and "max."
+ #
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
+ #
def datetime_field(object_name, method, options = {})
Tags::DatetimeField.new(object_name, method, self, options).render
end
@@ -1058,6 +1100,18 @@ module ActionView
# datetime_local_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
#
+ # You can create values for the "min" and "max" attributes by passing
+ # instances of Date or Time to the options hash.
+ #
+ # datetime_local_field("user", "born_on", min: Date.today)
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
+ #
+ # Alternatively, you can pass a String formatted as an ISO8601 datetime as
+ # the values for "min" and "max."
+ #
+ # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
+ #
def datetime_local_field(object_name, method, options = {})
Tags::DatetimeLocalField.new(object_name, method, self, options).render
end
@@ -1172,7 +1226,7 @@ module ActionView
# methods in the +FormHelper+ module. This class, however, allows you to
# call methods with the model object you are building the form for.
#
- # You can create your own custom FormBuilder templates by subclasses this
+ # You can create your own custom FormBuilder templates by subclassing this
# class. For example:
#
# class MyFormBuilder < ActionView::Helpers::FormBuilder
@@ -1268,7 +1322,7 @@ module ActionView
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
#
- # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
# its method signature is slightly different. Like +form_for+, it yields
# a FormBuilder object associated with a particular model object to a block,
# and within the block allows methods to be called on the builder to
@@ -1809,8 +1863,8 @@ module ActionView
object = convert_to_model(@object)
key = object ? (object.persisted? ? :update : :create) : :submit
- model = if object.class.respond_to?(:model_name)
- object.class.model_name.human
+ model = if object.respond_to?(:model_name)
+ object.model_name.human
else
@object_name.to_s.humanize
end
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index fcd151ac32..83b07a00d4 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -14,81 +14,81 @@ module ActionView
#
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
+ # select("post", "category", Post::CATEGORIES, {include_blank: true})
#
- # could become:
+ # could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # </select>
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # </select>
#
- # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
+ # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
#
- # Example with @post.person_id => 2:
+ # Example with <tt>@post.person_id => 2</tt>:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
#
- # could become:
+ # could become:
#
- # <select name="post[person_id]">
- # <option value="">None</option>
- # <option value="1">David</option>
- # <option value="2" selected="selected">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
+ # <select name="post[person_id]">
+ # <option value="">None</option>
+ # <option value="1">David</option>
+ # <option value="2" selected="selected">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
#
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
#
- # could become:
+ # could become:
#
- # <select name="post[person_id]">
- # <option value="">Select Person</option>
- # <option value="1">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
+ # <select name="post[person_id]">
+ # <option value="">Select Person</option>
+ # <option value="1">David</option>
+ # <option value="2">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
#
- # Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
- # option to be in the +html_options+ parameter.
+ # * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
+ # option to be in the +html_options+ parameter.
#
- # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
+ # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
#
- # becomes:
+ # becomes:
#
- # <select name="album[][genre]" id="album__genre">
- # <option value="rap">rap</option>
- # <option value="rock">rock</option>
- # <option value="country">country</option>
- # </select>
+ # <select name="album[][genre]" id="album__genre">
+ # <option value="rap">rap</option>
+ # <option value="rock">rock</option>
+ # <option value="country">country</option>
+ # </select>
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
+ # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
#
- # could become:
+ # could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # <option disabled="disabled">restricted</option>
- # </select>
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # <option disabled="disabled">restricted</option>
+ # </select>
#
- # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
+ # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
#
- # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
- # <select name="post[category_id]">
- # <option value="1" disabled="disabled">2008 stuff</option>
- # <option value="2" disabled="disabled">Christmas</option>
- # <option value="3">Jokes</option>
- # <option value="4">Poems</option>
- # </select>
+ # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
+ # <select name="post[category_id]">
+ # <option value="1" disabled="disabled">2008 stuff</option>
+ # <option value="2" disabled="disabled">Christmas</option>
+ # <option value="3">Jokes</option>
+ # <option value="4">Poems</option>
+ # </select>
#
module FormOptionsHelper
# ERB::Util can mask some helpers like textilize. Make sure to include them.
@@ -128,6 +128,15 @@ module ActionView
# or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
#
+ # A block can be passed to +select+ to customize how the options tags will be rendered. This
+ # is useful when the options tag has complex attributes.
+ #
+ # select(report, "campaign_ids") do
+ # available_campaigns.each do |c|
+ # content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json })
+ # end
+ # end
+ #
# ==== Gotcha
#
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
@@ -143,17 +152,15 @@ module ActionView
# To prevent this the helper generates an auxiliary hidden field before
# every multiple select. The hidden field has the same name as multiple select and blank value.
#
- # This way, the client either sends only the hidden field (representing
- # the deselected multiple select box), or both fields. Since the HTML specification
- # says key/value pairs have to be sent in the same order they appear in the
- # form, and parameters extraction gets the last occurrence of any repeated
- # key in the query string, that works for ordinary forms.
+ # <b>Note:</b> The client either sends only the hidden field (representing
+ # the deselected multiple select box), or both fields. This means that the resulting array
+ # always contains a blank string.
#
# In case if you don't want the helper to generate this hidden field you can specify
# <tt>include_hidden: false</tt> option.
#
- def select(object, method, choices, options = {}, html_options = {})
- Tags::Select.new(object, method, self, choices, options, html_options).render
+ def select(object, method, choices = nil, options = {}, html_options = {}, &block)
+ Tags::Select.new(object, method, self, choices, options, html_options, &block).render
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -251,7 +258,7 @@ module ActionView
Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
- # Return select and option tags for the given object and method, using
+ # Returns select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
# In addition to the <tt>:include_blank</tt> option documented above,
@@ -307,7 +314,7 @@ module ActionView
# # => <option>MasterCard</option>
# # => <option selected="selected">Discover</option>
#
- # You can optionally provide html attributes as the last element of the array.
+ # You can optionally provide HTML attributes as the last element of the array.
#
# options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
# # => <option value="Denmark">Denmark</option>
@@ -351,8 +358,8 @@ module ActionView
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- html_attributes[:selected] = option_value_selected?(value, selected)
- html_attributes[:disabled] = disabled && option_value_selected?(value, disabled)
+ html_attributes[:selected] ||= option_value_selected?(value, selected)
+ html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
html_attributes[:value] = value
content_tag_string(:option, text, html_attributes)
@@ -454,21 +461,7 @@ module ActionView
end
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
- # wraps them with <tt><optgroup></tt> tags.
- #
- # Parameters:
- # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
- # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
- # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
- # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
- # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
- # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
- # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- #
- # Options:
- # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
- # prepends an option with a generic prompt - "Please select" - or the given prompt string.
- # * <tt>:divider</tt> - the divider for the options groups.
+ # wraps them with <tt><optgroup></tt> tags:
#
# grouped_options = [
# ['North America',
@@ -495,22 +488,36 @@ module ActionView
# <option value="France">France</option>
# </optgroup>
#
- # grouped_options = [
- # [['United States','US'], 'Canada'],
- # ['Denmark','Germany','France']
- # ]
- # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ # Parameters:
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
+ # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
+ # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
+ # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
#
- # Possible output:
- # <optgroup label="---------">
- # <option value="US">United States</option>
- # <option value="Canada">Canada</option>
- # </optgroup>
- # <optgroup label="---------">
- # <option value="Denmark">Denmark</option>
- # <option value="Germany">Germany</option>
- # <option value="France">France</option>
- # </optgroup>
+ # Options:
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
+ # prepends an option with a generic prompt - "Please select" - or the given prompt string.
+ # * <tt>:divider</tt> - the divider for the options groups.
+ #
+ # grouped_options = [
+ # [['United States','US'], 'Canada'],
+ # ['Denmark','Germany','France']
+ # ]
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ #
+ # Possible output:
+ # <optgroup label="---------">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ # <optgroup label="---------">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
#
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
@@ -626,7 +633,7 @@ module ActionView
# even use the label as wrapper, as in the example above.
#
# The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
- # extra html options:
+ # extra HTML options:
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
# b.label(class: "radio_button") { b.radio_button(class: "radio_button") }
# end
@@ -689,7 +696,7 @@ module ActionView
# use the label as wrapper, as in the example above.
#
# The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
- # extra html options:
+ # extra HTML options:
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
# b.label(class: "check_box") { b.check_box(class: "check_box") }
# end
@@ -766,8 +773,8 @@ module ActionView
# <% end %>
#
# Please refer to the documentation of the base helper for details.
- def select(method, choices, options = {}, html_options = {})
- @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 142c27ace0..7d1cdc5a68 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -35,10 +35,10 @@ module ActionView
# This is helpful when you're fragment-caching the form. Remote forms get the
# authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you
# support browsers without JavaScript.
- # * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behavior. By default this behavior is an ajax submit.
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name utf8 is not output.
+ # * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# form_tag('/posts')
@@ -67,7 +67,7 @@ module ActionView
def form_tag(url_for_options = {}, options = {}, &block)
html_options = html_options_for_form(url_for_options, options)
if block_given?
- form_tag_in_block(html_options, &block)
+ form_tag_with_body(html_options, capture(&block))
else
form_tag_html(html_options)
end
@@ -82,14 +82,18 @@ module ActionView
# ==== Options
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
- # * <tt>:include_blank</tt> - If set to true, an empty option will be created.
- # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
+ # * <tt>:include_blank</tt> - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty.
+ # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something.
+ # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
# # <select id="people" name="people"><option value="1">David</option></select>
#
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"]
+ # # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
+ #
# select_tag "people", "<option>David</option>".html_safe
# # => <select id="people" name="people"><option>David</option></select>
#
@@ -105,13 +109,16 @@ module ActionView
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
- # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input'
- # # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
+ # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input', id: 'unique_id'
+ # # => <select class="form_input" id="unique_id" multiple="multiple" name="access[]"><option>Read</option>
# # <option>Write</option></select>
#
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true
# # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select>
#
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: "All"
+ # # => <select id="people" name="people"><option value="">All</option><option value="1">David</option></select>
+ #
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something"
# # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
#
@@ -465,17 +472,23 @@ module ActionView
# # <strong>Ask me!</strong>
# # </button>
#
- # button_tag "Checkout", data: { disable_with => "Please wait..." }
+ # button_tag "Checkout", data: { disable_with: "Please wait..." }
# # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
#
def button_tag(content_or_options = nil, options = nil, &block)
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
- options ||= {}
- options = options.stringify_keys
+ if content_or_options.is_a? Hash
+ options = content_or_options
+ else
+ options ||= {}
+ end
- options.reverse_merge! 'name' => 'button', 'type' => 'submit'
+ options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys)
- content_tag :button, content_or_options || 'Button', options, &block
+ if block_given?
+ content_tag :button, options, &block
+ else
+ content_tag :button, content_or_options || 'Button', options
+ end
end
# Displays an image which when clicked will submit the form.
@@ -495,19 +508,19 @@ module ActionView
#
# ==== Examples
# image_submit_tag("login.png")
- # # => <input alt="Login" src="/images/login.png" type="image" />
+ # # => <input alt="Login" src="/assets/login.png" type="image" />
#
# image_submit_tag("purchase.png", disabled: true)
- # # => <input alt="Purchase" disabled="disabled" src="/images/purchase.png" type="image" />
+ # # => <input alt="Purchase" disabled="disabled" src="/assets/purchase.png" type="image" />
#
# image_submit_tag("search.png", class: 'search_button', alt: 'Find')
- # # => <input alt="Find" class="search_button" src="/images/search.png" type="image" />
+ # # => <input alt="Find" class="search_button" src="/assets/search.png" type="image" />
#
# image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button")
- # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
+ # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/assets/agree.png" type="image" />
#
# image_submit_tag("save.png", data: { confirm: "Are you sure?" })
- # # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" />
+ # # => <input alt="Save" src="/assets/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options)
@@ -544,6 +557,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # color_field_tag 'name'
+ # # => <input id="name" name="name" type="color" />
+ #
+ # color_field_tag 'color', '#DEF726'
+ # # => <input id="color" name="color" type="color" value="#DEF726" />
+ #
+ # color_field_tag 'color', nil, class: 'special_input'
+ # # => <input class="special_input" id="color" name="color" type="color" />
+ #
+ # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" />
def color_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
end
@@ -552,6 +578,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # search_field_tag 'name'
+ # # => <input id="name" name="name" type="search" />
+ #
+ # search_field_tag 'search', 'Enter your search query here'
+ # # => <input id="search" name="search" type="search" value="Enter your search query here" />
+ #
+ # search_field_tag 'search', nil, class: 'special_input'
+ # # => <input class="special_input" id="search" name="search" type="search" />
+ #
+ # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" />
def search_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
end
@@ -560,6 +599,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # telephone_field_tag 'name'
+ # # => <input id="name" name="name" type="tel" />
+ #
+ # telephone_field_tag 'tel', '0123456789'
+ # # => <input id="tel" name="tel" type="tel" value="0123456789" />
+ #
+ # telephone_field_tag 'tel', nil, class: 'special_input'
+ # # => <input class="special_input" id="tel" name="tel" type="tel" />
+ #
+ # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" />
def telephone_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
end
@@ -569,6 +621,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # date_field_tag 'name'
+ # # => <input id="name" name="name" type="date" />
+ #
+ # date_field_tag 'date', '01/01/2014'
+ # # => <input id="date" name="date" type="date" value="01/01/2014" />
+ #
+ # date_field_tag 'date', nil, class: 'special_input'
+ # # => <input class="special_input" id="date" name="date" type="date" />
+ #
+ # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
def date_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
end
@@ -632,6 +697,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # url_field_tag 'name'
+ # # => <input id="name" name="name" type="url" />
+ #
+ # url_field_tag 'url', 'http://rubyonrails.org'
+ # # => <input id="url" name="url" type="url" value="http://rubyonrails.org" />
+ #
+ # url_field_tag 'url', nil, class: 'special_input'
+ # # => <input class="special_input" id="url" name="url" type="url" />
+ #
+ # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" />
def url_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
end
@@ -640,6 +718,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # email_field_tag 'name'
+ # # => <input id="name" name="name" type="email" />
+ #
+ # email_field_tag 'email', 'email@example.com'
+ # # => <input id="email" name="email" type="email" value="email@example.com" />
+ #
+ # email_field_tag 'email', nil, class: 'special_input'
+ # # => <input class="special_input" id="email" name="email" type="email" />
+ #
+ # email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" />
def email_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
end
@@ -651,12 +742,40 @@ module ActionView
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
# <tt>:max</tt> values.
+ # * <tt>:within</tt> - Same as <tt>:in</tt>.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
#
# ==== Examples
+ # number_field_tag 'quantity'
+ # # => <input id="quantity" name="quantity" type="number" />
+ #
+ # number_field_tag 'quantity', '1'
+ # # => <input id="quantity" name="quantity" type="number" value="1" />
+ #
+ # number_field_tag 'quantity', nil, class: 'special_input'
+ # # => <input class="special_input" id="quantity" name="quantity" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1
+ # # => <input id="quantity" name="quantity" min="1" type="number" />
+ #
+ # number_field_tag 'quantity', nil, max: 9
+ # # => <input id="quantity" name="quantity" max="9" type="number" />
+ #
# number_field_tag 'quantity', nil, in: 1...10
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, within: 1...10
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1, max: 10
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1, max: 10, step: 2
+ # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" />
+ #
+ # number_field_tag 'quantity', '1', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
@@ -677,7 +796,10 @@ module ActionView
# Creates the hidden UTF8 enforcer tag. Override this method in a helper
# to customize the tag.
def utf8_enforcer_tag
- tag(:input, :type => "hidden", :name => "utf8", :value => "&#x2713;".html_safe)
+ # Use raw HTML to ensure the value is written as an HTML entity; it
+ # needs to be the right character regardless of which encoding the
+ # browser infers.
+ '<input name="utf8" type="hidden" value="&#x2713;" />'.html_safe
end
private
@@ -720,9 +842,11 @@ module ActionView
method_tag(method) + token_tag(authenticity_token)
end
- enforce_utf8 = html_options.delete("enforce_utf8") { true }
- tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag
- content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
+ if html_options.delete("enforce_utf8") { true }
+ utf8_enforcer_tag + method_tag
+ else
+ method_tag
+ end
end
def form_tag_html(html_options)
@@ -730,8 +854,7 @@ module ActionView
tag(:form, html_options, true) + extra_tags
end
- def form_tag_in_block(html_options, &block)
- content = capture(&block)
+ def form_tag_with_body(html_options, content)
output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index e475d5b018..629c447f3f 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -47,7 +47,13 @@ module ActionView
# tag.
#
# javascript_tag "alert('All is good')", defer: 'defer'
- # # => <script defer="defer">alert('All is good')</script>
+ #
+ # Returns:
+ # <script defer="defer">
+ # //<![CDATA[
+ # alert('All is good')
+ # //]]>
+ # </script>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index fda7038a5d..7220bded3c 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -100,17 +100,12 @@ module ActionView
#
# number_to_currency(-1234567890.50, negative_format: "(%u%n)")
# # => ($1,234,567,890.50)
- # number_to_currency(1234567890.50, unit: "&pound;", separator: ",", delimiter: "")
- # # => &pound;1234567890,50
- # number_to_currency(1234567890.50, unit: "&pound;", separator: ",", delimiter: "", format: "%n %u")
- # # => 1234567890,50 &pound;
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
+ # # => R$1234567890,50
+ # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
+ # # => 1234567890,50 R$
def number_to_currency(number, options = {})
- return unless number
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_currency(number, options)
- }
+ delegate_number_helper_method(:number_to_currency, number, options)
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -150,12 +145,7 @@ module ActionView
#
# number_to_percentage("98a", raise: true) # => InvalidNumberError
def number_to_percentage(number, options = {})
- return unless number
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_percentage(number, options)
- }
+ delegate_number_helper_method(:number_to_percentage, number, options)
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -188,11 +178,7 @@ module ActionView
#
# number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_delimited(number, options)
- }
+ delegate_number_helper_method(:number_to_delimited, number, options)
end
# Formats a +number+ with the specified level of
@@ -237,11 +223,7 @@ module ActionView
# number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_rounded(number, options)
- }
+ delegate_number_helper_method(:number_to_rounded, number, options)
end
# Formats the bytes in +number+ into a more understandable
@@ -284,20 +266,10 @@ module ActionView
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
- #
- # Non-significant zeros after the fractional separator are
- # stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change
- # that):
- #
- # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
- # number_to_human_size(524288000, precision: 5) # => "500 MB"
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
-
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_human_size(number, options)
- }
+ delegate_number_helper_method(:number_to_human_size, number, options)
end
# Pretty prints (formats and approximates) a number in a way it
@@ -365,11 +337,15 @@ module ActionView
# separator: ',',
# significant: false) # => "1,2 Million"
#
+ # number_to_human(500000000, precision: 5) # => "500 Million"
+ # number_to_human(12345012345, significant: false) # => "12.345 Billion"
+ #
# Non-significant zeros after the decimal separator are stripped
# out by default (set <tt>:strip_insignificant_zeros</tt> to
# +false+ to change that):
- # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
- # number_to_human(500000000, precision: 5) # => "500 Million"
+ #
+ # number_to_human(12.00001) # => "12"
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
#
# ==== Custom Unit Quantifiers
#
@@ -399,21 +375,36 @@ module ActionView
# number_to_human(0.34, units: :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
+ delegate_number_helper_method(:number_to_human, number, options)
+ end
+
+ private
+
+ def delegate_number_helper_method(method, number, options)
+ return unless number
+ options = escape_unsafe_options(options.symbolize_keys)
wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.number_to_human(number, options)
+ ActiveSupport::NumberHelper.public_send(method, number, options)
}
end
- private
-
- def escape_unsafe_delimiters_and_separators(options)
- options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
- options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
+ def escape_unsafe_options(options)
+ options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
+ options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
+ options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
options
end
+ def escape_units(units)
+ Hash[units.map do |k, v|
+ [k, ERB::Util.html_escape(v)]
+ end]
+ end
+
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
valid_float = valid_float?(number)
raise InvalidNumberError, number if raise_on_invalid && !valid_float
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 60a4478c26..1c2a400245 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -17,10 +17,10 @@ module ActionView #:nodoc:
stringish.to_s.html_safe
end
- # This method returns a html safe string similar to what <tt>Array#join</tt>
- # would return. All items in the array, including the supplied separator, are
- # html escaped unless they are html safe, and the returned string is marked
- # as html safe.
+ # This method returns an HTML safe string similar to what <tt>Array#join</tt>
+ # would return. The array is flattened, and all items, including
+ # the supplied separator, are HTML escaped unless they are HTML
+ # safe, and the returned string is marked as HTML safe.
#
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
@@ -29,9 +29,9 @@ module ActionView #:nodoc:
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
- sep = ERB::Util.html_escape(sep)
+ sep = ERB::Util.unwrapped_html_escape(sep)
- array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
+ array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
end
end
end
diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb
index f767957fa9..77c3e6d394 100644
--- a/actionview/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/record_tag_helper.rb
@@ -1,3 +1,5 @@
+require 'action_view/record_identifier'
+
module ActionView
# = Action View Record Tag Helpers
module Helpers
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 458086de96..e11670e00d 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -12,6 +12,14 @@ module ActionView
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
+ # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
+ # type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
+ # performs HTML escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
+ # * <tt>:body</tt> - Renders the text passed in, and inherits the content
+ # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index e5cb843670..4f2db0a0c4 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/try'
-require 'action_view/vendor/html-scanner'
+require 'active_support/deprecation'
+require 'rails-html-sanitizer'
module ActionView
# = Action View Sanitize Helpers
@@ -8,7 +9,7 @@ module ActionView
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
- # This +sanitize+ helper will html encode all tags and strip all attributes that
+ # This +sanitize+ helper will HTML encode all tags and strip all attributes that
# aren't specifically allowed.
#
# It also strips href/src tags with invalid protocols, like javascript: especially.
@@ -27,7 +28,29 @@ module ActionView
#
# <%= sanitize @article.body %>
#
- # Custom Use (only the mentioned tags and attributes are allowed, nothing else)
+ # Custom Use - Custom Scrubber
+ # (supply a Loofah::Scrubber that does the sanitization)
+ #
+ # scrubber can either wrap a block:
+ # scrubber = Loofah::Scrubber.new do |node|
+ # node.text = "dawn of cats"
+ # end
+ #
+ # or be a subclass of Loofah::Scrubber which responds to scrub:
+ # class KittyApocalypse < Loofah::Scrubber
+ # def scrub(node)
+ # node.text = "dawn of cats"
+ # end
+ # end
+ # scrubber = KittyApocalypse.new
+ #
+ # <%= sanitize @article.body, scrubber: scrubber %>
+ #
+ # A custom scrubber takes precedence over custom tags and attributes
+ # Learn more about scrubbers here: https://github.com/flavorjones/loofah
+ #
+ # Custom Use - tags and attributes
+ # (only the mentioned tags and attributes are allowed, nothing else)
#
# <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
#
@@ -48,7 +71,7 @@ module ActionView
# Change allowed default attributes
#
# class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
+ # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']
# end
#
# Please note that sanitizing user-provided text does not guarantee that the
@@ -65,9 +88,9 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses the
- # html-scanner tokenizer and so its HTML parsing ability is limited by
- # that of html-scanner.
+ # Strips all HTML tags from the +html+, including comments. This uses
+ # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability
+ # is limited by that of Nokogiri.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -98,47 +121,21 @@ module ActionView
module ClassMethods #:nodoc:
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
- def sanitized_protocol_separator
- white_list_sanitizer.protocol_separator
- end
-
- def sanitized_uri_attributes
- white_list_sanitizer.uri_attributes
- end
-
- def sanitized_bad_tags
- white_list_sanitizer.bad_tags
+ # Vendors the full, link and white list sanitizers.
+ # Provided strictly for compabitility and can be removed in Rails 5.
+ def sanitizer_vendor
+ Rails::Html::Sanitizer
end
def sanitized_allowed_tags
- white_list_sanitizer.allowed_tags
+ sanitizer_vendor.white_list_sanitizer.allowed_tags
end
def sanitized_allowed_attributes
- white_list_sanitizer.allowed_attributes
- end
-
- def sanitized_allowed_css_properties
- white_list_sanitizer.allowed_css_properties
- end
-
- def sanitized_allowed_css_keywords
- white_list_sanitizer.allowed_css_keywords
+ sanitizer_vendor.white_list_sanitizer.allowed_attributes
end
- def sanitized_shorthand_css_properties
- white_list_sanitizer.shorthand_css_properties
- end
-
- def sanitized_allowed_protocols
- white_list_sanitizer.allowed_protocols
- end
-
- def sanitized_protocol_separator=(value)
- white_list_sanitizer.protocol_separator = value
- end
-
- # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
+ # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
# any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -146,21 +143,21 @@ module ActionView
# end
#
def full_sanitizer
- @full_sanitizer ||= HTML::FullSanitizer.new
+ @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
end
- # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
- # any object that responds to +sanitize+.
+ # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
+ # Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
# config.action_view.link_sanitizer = MySpecialSanitizer.new
# end
#
def link_sanitizer
- @link_sanitizer ||= HTML::LinkSanitizer.new
+ @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
end
- # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
+ # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
# Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -168,88 +165,32 @@ module ActionView
# end
#
def white_list_sanitizer
- @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
- end
-
- # Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
- # end
- #
- def sanitized_uri_attributes=(attributes)
- HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
+ @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
end
- # Adds to the Set of 'bad' tags for the +sanitize+ helper.
+ ##
+ # :method: sanitized_allowed_tags=
#
- # class Application < Rails::Application
- # config.action_view.sanitized_bad_tags = 'embed', 'object'
- # end
+ # :call-seq: sanitized_allowed_tags=(tags)
#
- def sanitized_bad_tags=(attributes)
- HTML::WhiteListSanitizer.bad_tags.merge(attributes)
- end
-
- # Adds to the Set of allowed tags for the +sanitize+ helper.
+ # Replaces the allowed tags for the +sanitize+ helper.
#
# class Application < Rails::Application
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
# end
#
- def sanitized_allowed_tags=(attributes)
- HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
- end
-
- # Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
- # end
- #
- def sanitized_allowed_attributes=(attributes)
- HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
- end
-
- # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_css_properties = 'expression'
- # end
- #
- def sanitized_allowed_css_properties=(attributes)
- HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
- end
-
- # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_css_keywords = 'expression'
- # end
- #
- def sanitized_allowed_css_keywords=(attributes)
- HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
- end
- # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
+ ##
+ # :method: sanitized_allowed_attributes=
#
- # class Application < Rails::Application
- # config.action_view.sanitized_shorthand_css_properties = 'expression'
- # end
+ # :call-seq: sanitized_allowed_attributes=(attributes)
#
- def sanitized_shorthand_css_properties=(attributes)
- HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
- end
-
- # Adds to the Set of allowed protocols for the +sanitize+ helper.
+ # Replaces the allowed HTML attributes for the +sanitize+ helper.
#
# class Application < Rails::Application
- # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
+ # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
# end
#
- def sanitized_allowed_protocols=(attributes)
- HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 732f35643a..c20800598f 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -9,6 +9,7 @@ module ActionView
module TagHelper
extend ActiveSupport::Concern
include CaptureHelper
+ include OutputSafetyHelper
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
@@ -19,6 +20,8 @@ module ActionView
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
+
PRE_CONTENT_STRINGS = {
:textarea => "\n"
}
@@ -42,7 +45,8 @@ module ActionView
# For example, a key +user_id+ would render as <tt>data-user-id</tt> and
# thus accessed as <tt>dataset.userId</tt>.
#
- # Values are encoded to JSON, with the exception of strings and symbols.
+ # Values are encoded to JSON, with the exception of strings, symbols and
+ # BigDecimals.
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
# from 1.4.3.
#
@@ -56,6 +60,9 @@ module ActionView
# tag("input", type: 'text', disabled: true)
# # => <input type="text" disabled="disabled" />
#
+ # tag("input", type: 'text', class: ["strong", "highlight"])
+ # # => <input class="strong highlight" type="text" />
+ #
# tag("img", src: "open & shut.png")
# # => <img src="open &amp; shut.png" />
#
@@ -75,7 +82,7 @@ module ActionView
# Set escape to false to disable attribute value escaping.
#
# ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
@@ -84,6 +91,8 @@ module ActionView
# # => <p>Hello world!</p>
# content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
# # => <div class="strong"><p>Hello world!</p></div>
+ # content_tag(:div, "Hello world!", class: ["strong", "highlight"])
+ # # => <div class="strong highlight">Hello world!</div>
# content_tag("select", options, multiple: true)
# # => <select multiple="multiple">...options...</select>
#
@@ -114,7 +123,7 @@ module ActionView
# cdata_section("hello]]>world")
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- splitted = content.gsub(']]>', ']]]]><![CDATA[>')
+ splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>')
"<![CDATA[#{splitted}]]>".html_safe
end
@@ -133,7 +142,7 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- content = ERB::Util.h(content) if escape
+ content = ERB::Util.unwrapped_html_escape(content) if escape
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
end
@@ -141,9 +150,9 @@ module ActionView
return if options.blank?
attrs = []
options.each_pair do |key, value|
- if key.to_s == 'data' && value.is_a?(Hash)
+ if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
value.each_pair do |k, v|
- attrs << data_tag_option(k, v, escape)
+ attrs << prefix_tag_option(key, k, v, escape)
end
elsif BOOLEAN_ATTRIBUTES.include?(key)
attrs << boolean_tag_option(key) if value
@@ -151,11 +160,11 @@ module ActionView
attrs << tag_option(key, value, escape)
end
end
- " #{attrs.sort! * ' '}".html_safe unless attrs.empty?
+ " #{attrs.sort! * ' '}" unless attrs.empty?
end
- def data_tag_option(key, value, escape)
- key = "data-#{key.to_s.dasherize}"
+ def prefix_tag_option(prefix, key, value, escape)
+ key = "#{prefix}-#{key.to_s.dasherize}"
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
value = value.to_json
end
@@ -167,8 +176,11 @@ module ActionView
end
def tag_option(key, value, escape)
- value = value.join(" ") if value.is_a?(Array)
- value = ERB::Util.h(value) if escape
+ if value.is_a?(Array)
+ value = escape ? safe_join(value, " ") : value.join(" ")
+ else
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value
+ end
%(#{key}="#{value}")
end
end
diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb
index a05e16979a..45c75d10c0 100644
--- a/actionview/lib/action_view/helpers/tags.rb
+++ b/actionview/lib/action_view/helpers/tags.rb
@@ -3,37 +3,39 @@ module ActionView
module Tags #:nodoc:
extend ActiveSupport::Autoload
- autoload :Base
- autoload :CheckBox
- autoload :CollectionCheckBoxes
- autoload :CollectionRadioButtons
- autoload :CollectionSelect
- autoload :ColorField
- autoload :DateField
- autoload :DateSelect
- autoload :DatetimeField
- autoload :DatetimeLocalField
- autoload :DatetimeSelect
- autoload :EmailField
- autoload :FileField
- autoload :GroupedCollectionSelect
- autoload :HiddenField
- autoload :Label
- autoload :MonthField
- autoload :NumberField
- autoload :PasswordField
- autoload :RadioButton
- autoload :RangeField
- autoload :SearchField
- autoload :Select
- autoload :TelField
- autoload :TextArea
- autoload :TextField
- autoload :TimeField
- autoload :TimeSelect
- autoload :TimeZoneSelect
- autoload :UrlField
- autoload :WeekField
+ eager_autoload do
+ autoload :Base
+ autoload :CheckBox
+ autoload :CollectionCheckBoxes
+ autoload :CollectionRadioButtons
+ autoload :CollectionSelect
+ autoload :ColorField
+ autoload :DateField
+ autoload :DateSelect
+ autoload :DatetimeField
+ autoload :DatetimeLocalField
+ autoload :DatetimeSelect
+ autoload :EmailField
+ autoload :FileField
+ autoload :GroupedCollectionSelect
+ autoload :HiddenField
+ autoload :Label
+ autoload :MonthField
+ autoload :NumberField
+ autoload :PasswordField
+ autoload :RadioButton
+ autoload :RangeField
+ autoload :SearchField
+ autoload :Select
+ autoload :TelField
+ autoload :TextArea
+ autoload :TextField
+ autoload :TimeField
+ autoload :TimeSelect
+ autoload :TimeZoneSelect
+ autoload :UrlField
+ autoload :WeekField
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 3fe3f4e9df..8607da301c 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -119,7 +119,8 @@ module ActionView
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+ value = options.fetch(:selected) { value(object) }
+ select = content_tag("select", add_options(option_tags, options, value), html_options)
if html_options["multiple"] && options.fetch(:include_hidden, true)
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 52006d856b..6242a2a085 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -27,9 +27,11 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all check boxes are unchecked.
- hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil)
-
- rendered_collection + hidden
+ if @options.fetch(:include_hidden, true)
+ rendered_collection + hidden_field
+ else
+ rendered_collection
+ end
end
private
@@ -37,6 +39,18 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
+
+ def hidden_field
+ hidden_name = @html_options[:name]
+
+ hidden_name ||= if @options.has_key?(:index)
+ "#{tag_name_with_index(@options[:index])}[]"
+ else
+ "#{tag_name}[]"
+ end
+
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 388dcf1f13..8050638363 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -18,7 +18,8 @@ module ActionView
end
def label(label_html_options={}, &block)
- @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
+ html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
+ @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
end
end
@@ -43,7 +44,7 @@ module ActionView
def default_html_options_for_collection(item, value) #:nodoc:
html_options = @html_options.dup
- [:checked, :selected, :disabled].each do |option|
+ [:checked, :selected, :disabled, :readonly].each do |option|
current_value = @options[option]
next if current_value.nil?
diff --git a/actionview/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb
index 25e7e05ec6..b2cee9d198 100644
--- a/actionview/lib/action_view/helpers/tags/datetime_field.rb
+++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb
@@ -5,8 +5,8 @@ module ActionView
def render
options = @options.stringify_keys
options["value"] ||= format_date(value(object))
- options["min"] = format_date(options["min"])
- options["max"] = format_date(options["max"])
+ options["min"] = format_date(datetime_value(options["min"]))
+ options["max"] = format_date(datetime_value(options["max"]))
@options = options
super
end
@@ -16,6 +16,14 @@ module ActionView
def format_date(value)
value.try(:strftime, "%Y-%m-%dT%T.%L%z")
end
+
+ def datetime_value(value)
+ if value.is_a? String
+ DateTime.parse(value) rescue nil
+ else
+ value
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 35d3ba8434..08a23e497e 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -2,6 +2,39 @@ module ActionView
module Helpers
module Tags # :nodoc:
class Label < Base # :nodoc:
+ class LabelBuilder # :nodoc:
+ attr_reader :object
+
+ def initialize(template_object, object_name, method_name, object, tag_value)
+ @template_object = template_object
+ @object_name = object_name
+ @method_name = method_name
+ @object = object
+ @tag_value = tag_value
+ end
+
+ def translation
+ method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+
+ if object.respond_to?(:to_model)
+ key = object.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(method_and_value)
+ end
+
+ content ||= @method_name.humanize
+
+ content
+ end
+ end
+
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
options ||= {}
@@ -32,33 +65,24 @@ module ActionView
options.delete("namespace")
options["for"] = name_and_id["id"] unless options.key?("for")
- if block_given?
- content = @template_object.capture(&block)
- else
- content = if @content.blank?
- @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
- method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
-
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
+ builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
- i18n_default ||= ""
- I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
- else
- @content.to_s
- end
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(@method_name)
- end
-
- content ||= @method_name.humanize
+ content = if block_given?
+ @template_object.capture(builder, &block)
+ elsif @content.present?
+ @content.to_s
+ else
+ render_component(builder)
end
label_tag(name_and_id["id"], content, options)
end
+
+ private
+
+ def render_component(builder)
+ builder.translation
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb
new file mode 100644
index 0000000000..ae67bc13af
--- /dev/null
+++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb
@@ -0,0 +1,34 @@
+module ActionView
+ module Helpers
+ module Tags # :nodoc:
+ module Placeholderable # :nodoc:
+ def initialize(*)
+ super
+
+ if tag_value = @options[:placeholder]
+ placeholder = tag_value if tag_value.is_a?(String)
+
+ object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+ method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence
+
+ placeholder ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(method_and_value)
+ end
+
+ placeholder ||= @method_name.humanize
+
+ @options[:placeholder] = placeholder
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index d64e2f68ef..180900cc8d 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -3,8 +3,9 @@ module ActionView
module Tags # :nodoc:
class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
- @choices = choices
+ @choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
+
@html_options = html_options
super(object_name, method_name, template_object, options)
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index 9ee83ee7c2..69038c1498 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,7 +1,11 @@
+require 'action_view/helpers/tags/placeholderable'
+
module ActionView
module Helpers
module Tags # :nodoc:
class TextArea < Base # :nodoc:
+ include Placeholderable
+
def render
options = @options.stringify_keys
add_default_name_and_id(options)
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index e910879ebf..5c576a20ca 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,13 +1,16 @@
+require 'action_view/helpers/tags/placeholderable'
+
module ActionView
module Helpers
module Tags # :nodoc:
class TextField < Base # :nodoc:
+ include Placeholderable
+
def render
options = @options.stringify_keys
options["size"] = options["maxlength"] unless options.key?("size")
options["type"] ||= field_type
options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
- options["value"] &&= ERB::Util.html_escape(options["value"])
add_default_name_and_id(options)
tag("input", options)
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 3fc64fa8a5..b859653bc9 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -31,6 +31,8 @@ module ActionView
include SanitizeHelper
include TagHelper
+ include OutputSafetyHelper
+
# The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must
@@ -80,6 +82,9 @@ module ActionView
# # => "And they f... (continued)"
#
# truncate("<p>Once upon a time in a world far far away</p>")
+ # # => "&lt;p&gt;Once upon a time in a wo..."
+ #
+ # truncate("<p>Once upon a time in a world far far away</p>", escape: false)
# # => "<p>Once upon a time in a wo..."
#
# truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
@@ -98,11 +103,14 @@ module ActionView
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
- # '<mark>\1</mark>')
+ # '<mark>\1</mark>') or passing a block that receives each matched term.
#
# highlight('You searched for: rails', 'rails')
# # => You searched for: <mark>rails</mark>
#
+ # highlight('You searched for: rails', /for|rails/)
+ # # => You searched <mark>for</mark>: <mark>rails</mark>
+ #
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
#
@@ -111,15 +119,25 @@ module ActionView
#
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
# # => You searched for: <a href="search?q=rails">rails</a>
+ #
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
+ # # => You searched for: <a href="search?q=rails">rails</a>
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
else
- highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
- match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
+ match = Array(phrases).map do |p|
+ Regexp === p ? p.to_s : Regexp.escape(p)
+ end.join('|')
+
+ if block_given?
+ text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
+ else
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
+ end
end.html_safe
end
@@ -150,9 +168,13 @@ module ActionView
def excerpt(text, phrase, options = {})
return unless text && phrase
- separator = options.fetch(:separator, "")
- phrase = Regexp.escape(phrase)
- regex = /#{phrase}/i
+ separator = options.fetch(:separator, nil) || ""
+ case phrase
+ when Regexp
+ regex = phrase
+ else
+ regex = /#{Regexp.escape(phrase)}/i
+ end
return unless matches = text.match(regex)
phrase = matches[0]
@@ -166,12 +188,13 @@ module ActionView
end
end
- first_part, second_part = text.split(regex, 2)
+ first_part, second_part = text.split(phrase, 2)
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
- prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
+ affix = [first_part, separator, phrase, separator, second_part].join.strip
+ [prefix, affix, postfix].join
end
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
@@ -267,7 +290,7 @@ module ActionView
content_tag(wrapper_tag, nil, html_options)
else
paragraphs.map! { |paragraph|
- content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
+ content_tag(wrapper_tag, raw(paragraph), html_options)
}.join("\n\n").html_safe
end
end
@@ -313,7 +336,7 @@ module ActionView
options = values.extract_options!
name = options.fetch(:name, 'default')
- values.unshift(first_value)
+ values.unshift(*first_value)
cycle = get_cycle(name)
unless cycle && cycle.values == values
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index ad8eb47f1f..c2fda42396 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,24 +1,16 @@
require 'action_view/helpers/tag_helper'
+require 'active_support/core_ext/string/access'
require 'i18n/exceptions'
-module I18n
- class ExceptionHandler
- include Module.new {
- def call(exception, locale, key, options)
- exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
- end
- }
- end
-end
-
module ActionView
# = Action View Translation Helpers
module Helpers
module TranslationHelper
+ include TagHelper
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
#
- # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any
- # thrown +MissingTranslation+ messages will be turned into inline spans that
+ # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
+ # into inline spans that:
#
# * have a "translation-missing" class set,
# * contain the missing key as a title attribute and
@@ -44,8 +36,18 @@ module ActionView
# naming convention helps to identify translations that include HTML tags so that
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
- options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
+ options = options.dup
options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
+
+ # If the user has specified rescue_format then pass it all through, otherwise use
+ # raise and do the work ourselves
+ options[:raise] ||= ActionView::Base.raise_on_missing_translations
+
+ raise_error = options[:raise] || options.key?(:rescue_format)
+ unless raise_error
+ options[:raise] = true
+ end
+
if html_safe_translation_key?(key)
html_safe_options = options.dup
options.except(*I18n::RESERVED_KEYS).each do |name, value|
@@ -59,6 +61,11 @@ module ActionView
else
I18n.translate(scope_key_by_partial(key), options)
end
+ rescue I18n::MissingTranslationData => e
+ raise e if raise_error
+
+ keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
+ content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
end
alias :t :translate
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 1920a94567..c3be47133c 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -82,7 +82,7 @@ module ActionView
# 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, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
# * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -92,8 +92,9 @@ module ActionView
# ==== Data attributes
#
# * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript
- # driver to prompt with the question specified. If the user accepts, the link is
- # processed normally, otherwise no action is taken.
+ # driver to prompt with the question specified (in this case, the
+ # resulting text would be <tt>question?</tt>. If the user accepts, the
+ # link is processed normally, otherwise no action is taken.
# * <tt>:disable_with</tt> - Value of this parameter will be
# used as the value for a disabled version of the submit
# button when the form is submitted. This feature is provided
@@ -213,6 +214,7 @@ module ActionView
# * <tt>:form</tt> - This hash will be form attributes
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
# be placed
+ # * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
#
# ==== Data attributes
#
@@ -230,6 +232,11 @@ module ActionView
# # <div><input value="New" type="submit" /></div>
# # </form>"
#
+ # <%= button_to "New", new_articles_path %>
+ # # => "<form method="post" action="/articles/new" class="button_to">
+ # # <div><input value="New" type="submit" /></div>
+ # # </form>"
+ #
# <%= button_to [:make_happy, @user] do %>
# Make happy <strong><%= @user.name %></strong>
# <% end %>
@@ -287,6 +294,7 @@ module ActionView
url = options.is_a?(String) ? options : url_for(options)
remote = html_options.delete('remote')
+ params = html_options.delete('params')
method = html_options.delete('method').to_s
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe
@@ -310,7 +318,12 @@ module ActionView
end
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
- content_tag('form', content_tag('div', inner_tags), form_options)
+ if params
+ params.each do |param_name, value|
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
+ end
+ end
+ content_tag('form', inner_tags, form_options)
end
# Creates a link tag of the given +name+ using a URL created by the set of
@@ -376,15 +389,7 @@ module ActionView
# # If not...
# # => <a href="/accounts/signup">Reply</a>
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
- if condition
- if block_given?
- block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
- else
- ERB::Util.html_escape(name)
- end
- else
- link_to(name, options, html_options)
- end
+ link_to_if !condition, name, options, html_options, &block
end
# Creates a link tag of the given +name+ using a URL created by the set of
@@ -408,7 +413,15 @@ module ActionView
# # If they are logged in...
# # => <a href="/accounts/show/3">my_username</a>
def link_to_if(condition, name, options = {}, html_options = {}, &block)
- link_to_unless !condition, name, options, html_options, &block
+ if condition
+ link_to(name, options, html_options)
+ else
+ if block_given?
+ block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
+ else
+ ERB::Util.html_escape(name)
+ end
+ end
end
# Creates a mailto link tag to the specified +email_address+, which is
@@ -449,20 +462,18 @@ module ActionView
# <strong>Email me:</strong> <span>me@domain.com</span>
# </a>
def mail_to(email_address, name = nil, html_options = {}, &block)
- email_address = ERB::Util.html_escape(email_address)
-
html_options, name = name, nil if block_given?
html_options = (html_options || {}).stringify_keys
- extras = %w{ cc bcc body subject }.map { |item|
+ extras = %w{ cc bcc body subject }.map! { |item|
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
- extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
+ extras = extras.empty? ? '' : '?' + extras.join('&')
- html_options["href"] = "mailto:#{email_address}#{extras}".html_safe
+ html_options["href"] = "mailto:#{email_address}#{extras}"
- content_tag(:a, name || email_address.html_safe, html_options, &block)
+ content_tag(:a, name || email_address, html_options, &block)
end
# True if the current request URI was generated by the given +options+.