# frozen_string_literal: true # Based on ActionView::Helpers::TagHelper, also released under the MIT licence. # # Copyright (c) David Heinemeier Hansson module Hanami class View module Helpers # Helper methods for generating HTML tags. # # When using full Hanami apps, these helpers will be automatically available in your view # templates, part classes and scope classes. # # When using hanami-view standalone, include this module directly in your base part and scope # classes, or in specific classes as required. # # @example Standalone usage # class BasePart < Hanami::View::Part # include Hanami::View::Helpers::TagHelper # end # # class BaseScope < Hanami::View::Scope # include Hanami::View::Helpers::TagHelper # end # # class BaseView < Hanami::View # config.part_class = BasePart # config.scope_class = BaseScope # end # # @api public # @since 2.1.0 module TagHelper extend self # Returns a tag builder for building HTML tag strings. # # @example General usage # tag.div # =>
# tag.img # => # # tag.div("hello") # =>
hello
# tag.div { "hello" } # =>
hello
# tag.div(tag.p("hello")) # =>

hello

# # tag.div(class: ["a", "b"]) # =>
# tag.div(class: {"a": true, "b": false}) # =>
# # tag.div(id: "el", data: {x: "y"}) # =>
# tag.div(id: "el", aria: {x: "y"}) # =>
# # tag.custom_tag("hello") # => hello # # @example Escaping # tag.p("") # =>

<script>alert()</script>

# tag.p(class: "") # =>

# tag.p("safe content".html_safe) # =>

safe content

# # @example Within templates # <%= tag.div(id: "el") do %> #

Template content can be mixed in.

# <%= tag.p("Also nested tag builders.") %> # <% end %> # # @api public # @since 2.1.0 def tag tag_builder end # Returns an anchor tag for the given contents and URL. # # The tag's contents are automatically escaped (unless marked as HTML safe). # # Uses the {#tag} builder to prepare the tag, so all tag builder options are also used. # # @overload link_to(content, url, **attributes) # Returns a tag using a given string as the contents. # # @param content [String] content used in the a tag # @param url [String] URL to be used in the `href` attribute # @param attributes [Hash] HTML attributes to include in the tag # # @overload link_to(url, **attributes, &block) # Returns a tag using the given block's return value as the contents. # # @param url [String] URL to be used in the `href` attribute # @param attributes [Hash] HTML attributes to include in the tag # @param block [Proc] block that returns the contents of the tag # # @return [String] HTML markup for the anchor tag # # @example # link_to("Home", "/") # # => Home # # link_to("/") { "Home" } # # => Home # # link_to("Home", "/", class: "button") %> # # => Home # # @example Escaping # link_to("", "/") # # => <script>alert('xss')</script> # # link_to("/") { "" } # # => <script>alert('xss')</script> # # @see #tag # # @api public # @since 2.1.0 def link_to(content, url = nil, **attributes, &block) if block raise ArgumentError if url && content url = content content = nil end attributes[:href] = url or raise ArgumentError tag.a(content, **attributes, &block) end # Returns a string of space-separated tokens from a range of given arguments. # # This is intended for building an HTML tag attribute value, such as a list of class names. # # @return [String] # # @example # token_list("foo", "bar") # # => "foo bar" # # token_list("foo", "foo bar") # # => "foo bar" # # token_list({ foo: true, bar: false }) # # => "foo" # # token_list(nil, false, 123, "", "foo", { bar: true }) # # => "123 foo bar" # # @api public # @since 2.1.0 def token_list(*args) tokens = build_tag_values(*args).flat_map { |value| safe = value.html_safe? value.split(/\s+/).map { |s| safe ? s.html_safe : s } } EscapeHelper.escape_join(tokens, " ") end # @see #token_list # # @api public # @since 2.1.0 def class_names(...) token_list(...) end # @api private # @since 2.1.0 def build_tag_values(*args) tag_values = [] args.each do |tag_value| case tag_value when Hash tag_value.each do |key, val| tag_values << key.to_s if val && !key.to_s.empty? end when Array tag_values.concat build_tag_values(*tag_value) else tag_values << tag_value.to_s unless tag_value.to_s.empty? end end tag_values end # @api private # @since 2.1.0 def tag_builder @tag_builder ||= begin TagBuilder.new(inflector: tag_builder_inflector) end end # @api private # @since 2.1.0 def tag_builder_inflector if respond_to?(:_context) return _context.inflector end # TODO: When hanami-view moves to Zeitwerk (and the only external require is for # "dry/view"), remove this. require "dry/inflector" Dry::Inflector.new end end end end end