diff options
Diffstat (limited to 'actionview/lib/action_view/helpers/tag_helper.rb')
-rw-r--r-- | actionview/lib/action_view/helpers/tag_helper.rb | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb new file mode 100644 index 0000000000..a87c223a71 --- /dev/null +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -0,0 +1,188 @@ +require 'active_support/core_ext/string/output_safety' +require 'set' + +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. + module TagHelper + extend ActiveSupport::Concern + include CaptureHelper + include OutputSafetyHelper + + BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer + autoplay controls loop selected hidden scoped async + defer reversed ismap seamless muted required + autofocus novalidate formnovalidate open pubdate + itemscope allowfullscreen default inert sortable + truespeed typemustmatch).to_set + + BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym)) + + TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set + + PRE_CONTENT_STRINGS = { + :textarea => "\n" + } + + # 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 + # hash to +options+. Set +escape+ to false to disable attribute value + # escaping. + # + # ==== Options + # You can use symbols or strings for the attribute names. + # + # Use +true+ with boolean attributes that can render with no value, like + # +disabled+ and +readonly+. + # + # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key + # pointing to a hash of sub-attributes. + # + # To play nicely with JavaScript conventions sub-attributes are dasherized. + # 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, symbols and + # BigDecimals. + # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> + # from 1.4.3. + # + # ==== Examples + # tag("br") + # # => <br /> + # + # tag("br", nil, true) + # # => <br> + # + # 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 & shut.png" /> + # + # tag("img", {src: "open & shut.png"}, false, false) + # # => <img src="open & shut.png" /> + # + # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)}) + # # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" /> + def tag(name, options = nil, open = false, escape = true) + "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe + end + + # Returns an HTML block tag of type +name+ surrounding the +content+. Add + # HTML attributes by passing an attributes hash to +options+. + # Instead of passing the content as an argument, you can also use a block + # in which case, you pass your +options+ as the second parameter. + # Set escape to false to disable attribute value escaping. + # + # ==== Options + # 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. + # + # ==== Examples + # content_tag(:p, "Hello world!") + # # => <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> + # + # <%= content_tag :div, class: "strong" do -%> + # Hello world! + # <% end -%> + # # => <div class="strong">Hello world!</div> + def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) + if block_given? + options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) + content_tag_string(name, capture(&block), options, escape) + else + content_tag_string(name, content_or_options_with_block, options, escape) + end + end + + # Returns a CDATA section with the given +content+. CDATA sections + # are used to escape blocks of text containing characters which would + # otherwise be recognized as markup. CDATA sections begin with the string + # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>. + # + # cdata_section("<hello world>") + # # => <![CDATA[<hello world>]]> + # + # cdata_section(File.read("hello_world.txt")) + # # => <![CDATA[<hello from a text file]]> + # + # cdata_section("hello]]>world") + # # => <![CDATA[hello]]]]><![CDATA[>world]]> + def cdata_section(content) + splitted = content.to_s.gsub(/\]\]\>/, ']]]]><![CDATA[>') + "<![CDATA[#{splitted}]]>".html_safe + end + + # Returns an escaped version of +html+ without affecting existing escaped entities. + # + # escape_once("1 < 2 & 3") + # # => "1 < 2 & 3" + # + # escape_once("<< Accept & Checkout") + # # => "<< Accept & Checkout" + def escape_once(html) + ERB::Util.html_escape_once(html) + end + + private + + def content_tag_string(name, content, options, escape = true) + tag_options = tag_options(options, escape) if options + content = ERB::Util.unwrapped_html_escape(content) if escape + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe + end + + def tag_options(options, escape = true) + return if options.blank? + attrs = [] + options.each_pair do |key, value| + if TAG_PREFIXES.include?(key) && value.is_a?(Hash) + value.each_pair do |k, v| + attrs << prefix_tag_option(key, k, v, escape) + end + elsif BOOLEAN_ATTRIBUTES.include?(key) + attrs << boolean_tag_option(key) if value + elsif !value.nil? + attrs << tag_option(key, value, escape) + end + end + " #{attrs * ' '}" unless attrs.empty? + end + + 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 + tag_option(key, value, escape) + end + + def boolean_tag_option(key) + %(#{key}="#{key}") + end + + def tag_option(key, value, 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 + end +end |