diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 9d669c7cd816a..52c11ad2664ce 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,34 @@
+* New syntax for tag helpers. Avoid positional parameters and suport HTML5 by default.
+ Example usage of tag helpers before:
+
+ ```ruby
+ tag(:br)
+
+ content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
+ ```
+
+ ```html
+ <%= content_tag :div, class: "strong" do -%>
+ Hello world!
+ <% end -%>
+ ```
+
+ Example usage of tag helpers after:
+
+ ```ruby
+ tag.br
+
+ tag.div tag.p("Hello world!"), class: "strong"
+ ```
+
+ ```html
+ <%= tag.div class: "strong" do %>
+ Hello world!
+ <% end %>
+ ```
+
+ *Marek Kirejczyk*
+
* `select_tag`'s `include_blank` option for generation for blank option tag, now adds an empty space label,
when the value as well as content for option tag are empty, so that we confirm with html specification.
Ref: https://www.w3.org/TR/html5/forms.html#the-option-element.
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 9414ac876b6b9..abb308a506320 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -5,7 +5,7 @@ module ActionView
# = Action View Tag Helpers
module Helpers #:nodoc:
# Provides methods to generate HTML tags programmatically when you can't use
- # a Builder. By default, they output XHTML compliant tags.
+ # a Builder.
module TagHelper
extend ActiveSupport::Concern
include CaptureHelper
@@ -26,44 +26,67 @@ module TagHelper
PRE_CONTENT_STRINGS[:textarea] = "\n"
PRE_CONTENT_STRINGS["textarea"] = "\n"
- # TagBuilder work in progress
- # TODO:
- # * Documentation
- # * More tests
- # * support for NEED_CLOSING element
- # * Method missing -> raise if unknown html tag
- # * fill NEED_CLOSING
- # * include support for escape argument
- # * blocks
- # * Extract to sepearete file (?)
-
- class TagBuilder
- include ActionView::Helpers::TagHelper
-
- VOID_ELEMENTS = %w(base br col embed hr img input keygen link meta param source track wbr).to_set
+ class TagBuilder #:nodoc:
+ include TagHelper
+
+ VOID_ELEMENTS = %w(base br col embed hr img input keygen link
+ meta param source track wbr).to_set
+
VOID_ELEMENTS.merge(VOID_ELEMENTS.map(&:to_sym))
+ ELEMENTS = %w(a abbr acronym address applet area article aside audio b
+ base basefont bdi bdo bgsound big blink blockquote body br
+ button canvas caption center cite code col colgroup command
+ content data datalist dd del details dfn dialog dir div dl
+ dt element em embed fieldset figcaption figure font footer
+ form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr
+ html i iframe image img input ins isindex kbd keygen label
+ legend li link listing main map mark marquee menu menuitem
+ meta meter multicol nav nextid nobr noembed noframes
+ noscript object ol optgroup option output p param picture
+ plaintext pre progress q rp rt rtc ruby s samp script
+ section select shadow small source spacer span strike strong
+ style sub summary sup table tbody td template textarea tfoot
+ th thead time title tr track tt u ul var video wbr xmp
+ ).to_set
+
+ ELEMENTS.merge(ELEMENTS.map(&:to_sym))
+
+ def initialize(view_context)
+ @view_context = view_context
+ end
+
private
- def render_tag(name, content_or_options = nil, options = nil, &block)
+ def tag_string(name, content_or_options = nil, options_or_escape = nil, escape = true, &block)
+ raise_if_void_tag_with_content(name, content_or_options, &block)
if block_given?
- content_tag(name, yield(self), content_or_options)
+ content_tag_string(name, @view_context.capture(self, &block), content_or_options, options_or_escape)
elsif content_or_options.is_a? String
- content_tag(name, content_or_options, options)
+ content_tag_string(name, content_or_options, options_or_escape, escape)
elsif VOID_ELEMENTS.include?(name)
- tag(name, content_or_options, false, escape = true)
+ options_or_escape = true if options_or_escape.nil?
+ "<#{name}#{tag_options(content_or_options, options_or_escape)}>".html_safe
else
- content_tag(name, "", content_or_options)
+ options_or_escape = true if options_or_escape.nil?
+ content_tag_string(name, "", content_or_options, options_or_escape)
end
end
- def method_missing(called, *args, &block)
- render_tag(called, args[0], args[1], &block)
+ def raise_if_void_tag_with_content(name, content_or_options, &block)
+ has_content = block_given? || content_or_options.is_a?(String)
+ void_with_content = has_content && VOID_ELEMENTS.include?(name)
+ raise ArgumentError, "Void tag with content" if void_with_content
end
+ def method_missing(called, *args, &block)
+ return tag_string(called, *args, &block) if ELEMENTS.include?(called)
+ super
+ end
end
-
+ # Returns an HTML tag. Supports two syntax variants: traditonal and modern.
+ # === Traditional syntax
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
@@ -109,8 +132,63 @@ def method_missing(called, *args, &block)
#
# tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
# # =>
+ #
+ # === Modern syntax
+ # Modern syntax uses one of following format:
+ # tag.(options, escape)
+ # tag.(content, options, escape)
+ # Returns an HTML tag. Conetent has to be a string. If content is passed tag is surrounding the content. Otherwise tag will be empty. You can also use a block to pass the content inside ERB templates. Result is by default is HTML5 compliant. Set escape parameter to false to disable attribute value escaping. The tag will be generated with related closing tag unless tag is a void[https://www.w3.org/TR/html5/syntax.html#void-elements] element. Method will rise NoMethodError if element is not a part of the standard and ArgumentError if you try to pass content to a void element.
+ #
+ # ==== Options
+ # Like with traditional syntax the options hash can be used with attributes with no value like (disabled and readonly), which you can give a value of true in the options hash. You can use symbols or strings for the attribute names.
+ #
+ # ==== Examples
+ # tag.span
+ # # =>
+ #
+ # tag.span(class: "bookmark")
+ # # =>
+ #
+ # tag.input type: 'text', disabled: true
+ # # =>
+ #
+ # tag.input type: 'text', class: ["strong", "highlight"]
+ # # =>
+ #
+ # tag.img src: "open & shut.png"
+ # # =>
+ #
+ # tag.img({src: "open & shut.png"}, nil, false)
+ # # =>
+ #
+ # tag.div(data: {name: 'Stephen', city_state: %w(Chicago IL)})
+ # # =>
+ #
+ # tag.p "Hello world!"
+ # # =>