# frozen_string_literal: true
require "hanami/view"
require_relative "values"
module Hanami
module Helpers
module FormHelper
# A range of convenient methods for building the fields within an HTML form, integrating with
# request params and template locals to populate the fields with appropriate values.
#
# @see FormHelper#form_for
#
# @api public
# @since 2.1.0
class FormBuilder
# Set of HTTP methods that are understood by web browsers
#
# @since 2.1.0
# @api private
BROWSER_METHODS = %w[GET POST].freeze
private_constant :BROWSER_METHODS
# Set of HTTP methods that should NOT generate CSRF token
#
# @since 2.1.0
# @api private
EXCLUDED_CSRF_METHODS = %w[GET].freeze
private_constant :EXCLUDED_CSRF_METHODS
# Separator for accept attribute of file input
#
# @since 2.1.0
# @api private
#
# @see #file_input
ACCEPT_SEPARATOR = ","
private_constant :ACCEPT_SEPARATOR
# Default value for unchecked check box
#
# @since 2.1.0
# @api private
#
# @see #check_box
DEFAULT_UNCHECKED_VALUE = "0"
private_constant :DEFAULT_UNCHECKED_VALUE
# Default value for checked check box
#
# @since 2.1.0
# @api private
#
# @see #check_box
DEFAULT_CHECKED_VALUE = "1"
private_constant :DEFAULT_CHECKED_VALUE
# Input name separator
#
# @since 2.1.0
# @api private
INPUT_NAME_SEPARATOR = "."
private_constant :INPUT_NAME_SEPARATOR
# Empty string
#
# @since 2.1.0
# @api private
#
# @see #password_field
EMPTY_STRING = ""
private_constant :EMPTY_STRING
include Hanami::View::Helpers::EscapeHelper
include Hanami::View::Helpers::TagHelper
# @api private
# @since 2.1.0
attr_reader :base_name
private :base_name
# @api private
# @since 2.1.0
attr_reader :values
private :values
# @api private
# @since 2.1.0
attr_reader :inflector
private :inflector
# @api private
# @since 2.1.0
attr_reader :form_attributes
private :form_attributes
# Returns a new form builder.
#
# @param inflector [Dry::Inflector] the app inflector
# @param base_name [String, nil] the base name to use for all fields in the form
# @param values [Hanami::Helpers::FormHelper::Values] the values for the form
#
# @return [self]
#
# @see Hanami::Helpers::FormHelper#form_for
#
# @api private
# @since 2.1.0
def initialize(inflector:, form_attributes:, base_name: nil, values: Values.new)
@base_name = base_name
@values = values
@form_attributes = form_attributes
@inflector = inflector
end
# @api private
# @since 2.1.0
def call(content, **attributes)
attributes["accept-charset"] ||= DEFAULT_CHARSET
method_override, original_form_method = _form_method(attributes)
csrf_token, token = _csrf_token(values, attributes)
tag.form(**attributes) do
(+"").tap { |inner|
inner << input(type: "hidden", name: "_method", value: original_form_method) if method_override
inner << input(type: "hidden", name: "_csrf_token", value: token) if csrf_token
inner << content
}.html_safe
end
end
# Applies the base input name to all fields within the given block.
#
# This can be helpful when generating a set of nested fields.
#
# This is a convenience only. You can achieve the same result by including the base name at
# the beginning of each input name.
#
# @param name [String] the base name to be used for all fields in the block
# @yieldparam [FormBuilder] the form builder for the nested fields
#
# @example Basic usage
# <% f.fields_for "address" do |fa| %>
# <%= fa.text_field "street" %>
# <%= fa.text_field "suburb" %>
# <% end %>
#
# # A convenience for:
# # <%= f.text_field "address.street" %>
# # <%= f.text_field "address.suburb" %>
#
# =>
#
#
#
# @example Multiple levels of nesting
# <% f.fields_for "address" do |fa| %>
# <%= fa.text_field "street" %>
#
# <% fa.fields_for "location" do |fl| %>
# <%= fl.text_field "city" %>
# <% end %>
# <% end %>
#
# =>
#
#
#
# @api public
# @since 2.1.0
def fields_for(name, *yield_args)
new_base_name = [base_name, name.to_s].compact.join(INPUT_NAME_SEPARATOR)
builder = self.class.new(
base_name: new_base_name,
values: values,
form_attributes: form_attributes,
inflector: inflector
)
yield(builder, *yield_args)
end
# Yields to the given block for each element in the matching collection value, and applies
# the base input name to all fields within the block.
#
# Use this whenever generating form fields for an collection of nested fields.
#
# @param name [String] the input name, also used as the base input name for all fields
# within the block
# @yieldparam [FormBuilder] the form builder for the nested fields
# @yieldparam [Integer] the index of the iteration over the collection, starting from zero
# @yieldparam [Object] the value of the element from the collection
#
# @example Basic usage
# <% f.fields_for_collection("addresses") do |fa| %>
# <%= fa.text_field("street") %>
# <% end %>
#
# =>
#
#
#
# @example Yielding index and value
# <% f.fields_for_collection("bill.addresses") do |fa, i, address| %>
#
#
# @api public
# @since 2.1.0
def fields_for_collection(name, &block)
_value(name).each_with_index do |value, index|
fields_for("#{name}.#{index}", index, value, &block)
end
end
# Returns a label tag.
#
# @return [String] the tag
#
# @overload label(field_name, **attributes)
# Returns a label tag for the given field name, with a humanized version of the field name
# as the tag's content.
#
# @param field_name [String] the field name
# @param attributes [Hash] the tag attributes
#
# @example
# <%= f.label("book.extended_title") %>
# # =>
#
# @example HTML attributes
# <%= f.label("book.title", class: "form-label") %>
# # =>
#
# @overload label(content, **attributes)
# Returns a label tag for the field name given as `for:`, with the given content string as
# the tag's content.
#
# @param content [String] the tag's content
# @param for [String] the field name
# @param attributes [Hash] the tag attributes
#
# @example
# <%= f.label("Title", for: "book.extended_title") %>
# # =>
#
# f.label("book.extended_title", for: "ext-title")
# # =>
#
# @overload label(field_name, **attributes, &block)
# Returns a label tag for the given field name, with the return value of the given block
# as the tag's content.
#
# @param field_name [String] the field name
# @param attributes [Hash] the tag attributes
# @yieldreturn [String] the tag content
#
# @example
# <%= f.label for: "book.free_shipping" do %>
# Free shipping
# *
# <% end %>
#
# # =>
#
#
# @api public
# @since 2.1.0
def label(content = nil, **attributes, &block)
for_attribute_given = attributes.key?(:for)
attributes[:for] = _input_id(attributes[:for] || content)
if content && !for_attribute_given
content = inflector.humanize(content.to_s.split(INPUT_NAME_SEPARATOR).last)
end
tag.label(content, **attributes, &block)
end
# @overload fieldset(**attributes, &block)
# Returns a fieldset tag.
#
# @param attributes [Hash] the tag's HTML attributes
# @yieldreturn [String] the tag's content
#
# @return [String] the tag
#
# @example
# <%= f.fieldset do %>
# <%= f.legend("Author") %>
# <%= f.label("author.name") %>
# <%= f.text_field("author.name") %>
# <% end %>
#
# # =>
#
#
# @since 2.1.0
# @api public
def fieldset(...)
# This is here only for documentation purposes
tag.fieldset(...)
end
# Returns the tags for a check box.
#
# When editing a resource, the form automatically assigns the `checked` HTML attribute for
# the check box tag.
#
# Returns a hidden input tag in preceding the check box input tag. This ensures that
# unchecked values are submitted with the form.
#
# @param name [String] the input name
# @param attributes [Hash] the HTML attributes for the check box tag
# @option attributes [String] :checked_value (defaults to "1")
# @option attributes [String] :unchecked_value (defaults to "0")
#
# @return [String] the tags
#
# @example Basic usage
# f.check_box("delivery.free_shipping")
#
# # =>
#
#
#
# @example HTML Attributes
# f.check_box("delivery.free_shipping", class: "form-check-input")
#
# =>
#
#
#
# @example Specifying checked and unchecked values
# f.check_box("delivery.free_shipping", checked_value: "true", unchecked_value: "false")
#
# =>
#
#
#
# @example Automatic "checked" attribute
# # Given the request params:
# # {delivery: {free_shipping: "1"}}
# f.check_box("delivery.free_shipping")
#
# =>
#
#
#
# @example Forcing the "checked" attribute
# # Given the request params:
# # {delivery: {free_shipping: "0"}}
# f.check_box("deliver.free_shipping", checked: "checked")
#
# =>
#
#
#
# @example Multiple check boxes for an array of values
# f.check_box("book.languages", name: "book[languages][]", value: "italian", id: nil)
# f.check_box("book.languages", name: "book[languages][]", value: "english", id: nil)
#
# =>
#
#
#
# @example Automatic "checked" attribute for an array of values
# # Given the request params:
# # {book: {languages: ["italian"]}}
# f.check_box("book.languages", name: "book[languages][]", value: "italian", id: nil)
# f.check_box("book.languages", name: "book[languages][]", value: "english", id: nil)
#
# =>
#
#
#
# @api public
# @since 2.1.0
def check_box(name, **attributes)
(+"").tap { |output|
output << _hidden_field_for_check_box(name, attributes).to_s
output << input(**_attributes_for_check_box(name, attributes))
}.html_safe
end
# Returns a color input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.color_field("user.background")
# =>
#
# @example HTML Attributes
# f.color_field("user.background", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def color_field(name, **attributes)
input(**_attributes(:color, name, attributes))
end
# Returns a date input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.date_field("user.birth_date")
# # =>
#
# @example HTML Attributes
# f.date_field("user.birth_date", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def date_field(name, **attributes)
input(**_attributes(:date, name, attributes))
end
# Returns a datetime input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.datetime_field("delivery.delivered_at")
# =>
#
# @example HTML Attributes
# f.datetime_field("delivery.delivered_at", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def datetime_field(name, **attributes)
input(**_attributes(:datetime, name, attributes))
end
# Returns a datetime-local input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.datetime_local_field("delivery.delivered_at")
# =>
#
# @example HTML Attributes
# f.datetime_local_field("delivery.delivered_at", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def datetime_local_field(name, **attributes)
input(**_attributes(:"datetime-local", name, attributes))
end
# Returns a time input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.time_field("book.release_hour")
# =>
#
# @example HTML Attributes
# f.time_field("book.release_hour", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def time_field(name, **attributes)
input(**_attributes(:time, name, attributes))
end
# Returns a month input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.month_field("book.release_month")
# =>
#
# @example HTML Attributes
# f.month_field("book.release_month", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def month_field(name, **attributes)
input(**_attributes(:month, name, attributes))
end
# Returns a week input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.week_field("book.release_week")
# =>
#
# @example HTML Attributes
# f.week_field("book.release_week", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def week_field(name, **attributes)
input(**_attributes(:week, name, attributes))
end
# Returns an email input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.email_field("user.email")
# =>
#
# @example HTML Attributes
# f.email_field("user.email", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def email_field(name, **attributes)
input(**_attributes(:email, name, attributes))
end
# Returns a URL input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.url_field("user.website")
# =>
#
# @example HTML Attributes
# f.url_field("user.website", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def url_field(name, **attributes)
attributes[:value] = sanitize_url(attributes.fetch(:value) { _value(name) })
input(**_attributes(:url, name, attributes))
end
# Returns a telephone input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example
# f.tel_field("user.telephone")
# =>
#
# @example HTML Attributes
# f.tel_field("user.telephone", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def tel_field(name, **attributes)
input(**_attributes(:tel, name, attributes))
end
# Returns a hidden input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example
# f.hidden_field("delivery.customer_id")
# =>
#
# @api public
# @since 2.1.0
def hidden_field(name, **attributes)
input(**_attributes(:hidden, name, attributes))
end
# Returns a file input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
# @option attributes [String, Array] :accept Optional set of accepted MIME Types
# @option attributes [Boolean] :multiple allow multiple file upload
#
# @return [String] the tag
#
# @example Basic usage
# f.file_field("user.avatar")
# =>
#
# @example HTML Attributes
# f.file_field("user.avatar", class: "avatar-upload")
# =>
#
# @example Accepted MIME Types
# f.file_field("user.resume", accept: "application/pdf,application/ms-word")
# =>
#
# f.file_field("user.resume", accept: ["application/pdf", "application/ms-word"])
# =>
#
# @example Accept multiple file uploads
# f.file_field("user.resume", multiple: true)
# =>
#
# @api public
# @since 2.1.0
def file_field(name, **attributes)
form_attributes[:enctype] = "multipart/form-data"
attributes[:accept] = Array(attributes[:accept]).join(ACCEPT_SEPARATOR) if attributes.key?(:accept)
attributes = {type: :file, name: _input_name(name), id: _input_id(name), **attributes}
input(**attributes)
end
# Returns a number input tag.
#
# For this tag, you can make use of the `max`, `min`, and `step` HTML attributes.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.number_field("book.percent_read")
# =>
#
# @example Advanced attributes
# f.number_field("book.percent_read", min: 1, max: 100, step: 1)
# =>
#
# @api public
# @since 2.1.0
def number_field(name, **attributes)
input(**_attributes(:number, name, attributes))
end
# Returns a range input tag.
#
# For this tag, you can make use of the `max`, `min`, and `step` HTML attributes.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.range_field("book.discount_percentage")
# =>
#
# @example Advanced attributes
# f.range_field("book.discount_percentage", min: 1, max: 1'0, step: 1)
# =>
#
# @api public
# @since 2.1.0
def range_field(name, **attributes)
input(**_attributes(:range, name, attributes))
end
# Returns a textarea tag.
#
# @param name [String] the input name
# @param content [String] the content of the textarea
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.text_area("user.hobby")
# =>
#
# f.text_area "user.hobby", "Football"
# =>
#
#
# @example HTML attributes
# f.text_area "user.hobby", class: "form-control"
# =>
#
# @api public
# @since 2.1.0
def text_area(name, content = nil, **attributes)
if content.respond_to?(:to_hash)
attributes = content
content = nil
end
attributes = {name: _input_name(name), id: _input_id(name), **attributes}
tag.textarea(content || _value(name), **attributes)
end
# Returns a text input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.text_field("user.first_name")
# =>
#
# @example HTML Attributes
# f.text_field("user.first_name", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def text_field(name, **attributes)
input(**_attributes(:text, name, attributes))
end
alias_method :input_text, :text_field
# Returns a search input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.search_field("search.q")
# =>
#
# @example HTML Attributes
# f.search_field("search.q", class: "form-control")
# =>
#
# @api public
# @since 2.1.0
def search_field(name, **attributes)
input(**_attributes(:search, name, attributes))
end
# Returns a radio input tag.
#
# When editing a resource, the form automatically assigns the `checked` HTML attribute for
# the tag.
#
# @param name [String] the input name
# @param value [String] the input value
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.radio_button("book.category", "Fiction")
# f.radio_button("book.category", "Non-Fiction")
#
# =>
#
#
#
# @example HTML Attributes
# f.radio_button("book.category", "Fiction", class: "form-check")
# f.radio_button("book.category", "Non-Fiction", class: "form-check")
#
# =>
#
#
#
# @example Automatic checked value
# # Given the request params:
# # {book: {category: "Non-Fiction"}}
# f.radio_button("book.category", "Fiction")
# f.radio_button("book.category", "Non-Fiction")
#
# =>
#
#
#
# @api public
# @since 2.1.0
def radio_button(name, value, **attributes)
attributes = {type: :radio, name: _input_name(name), value: value, **attributes}
attributes[:checked] = true if _value(name).to_s == value.to_s
input(**attributes)
end
# Returns a password input tag.
#
# @param name [String] the input name
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.password_field("signup.password")
# =>
#
# @api public
# @since 2.1.0
def password_field(name, **attributes)
attrs = {type: :password, name: _input_name(name), id: _input_id(name), value: nil, **attributes}
attrs[:value] = EMPTY_STRING if attrs[:value].nil?
input(**attrs)
end
# Returns a select input tag containing option tags for the given values.
#
# The values should be an enumerable of pairs of content (the displayed text for the option)
# and value (the value for the option) strings.
#
# When editing a resource, automatically assigns the `selected` HTML attribute for any
# option tags matching the resource's values.
#
# @param name [String] the input name
# @param values [Hash] a Hash to generate `
` tags
# @param list [String] the name of list for the text input; also the id of datalist
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic Usage
# values = ["Italy", "Australia"]
# f.datalist("book.stores", values, "books")
#
# =>
#
#
#
# @example Options As Hash
# values = Hash["Italy" => "it", "Australia" => "au"]
# f.datalist("book.stores", values, "books")
#
# =>
#
#
#
# @example Specifying custom attributes for the datalist input
# values = ["Italy", "Australia"]
# f.datalist "book.stores", values, "books", datalist: {class: "form-control"}
#
# =>
#
#
#
# @example Specifying custom attributes for the options list
# values = ["Italy", "Australia"]
# f.datalist("book.stores", values, "books", options: {class: "form-control"})
#
# =>
#
#
#
# @api public
# @since 2.1.0
def datalist(name, values, list, **attributes)
options = attributes.delete(:options) || {}
datalist = attributes.delete(:datalist) || {}
attributes[:list] = list
datalist[:id] = list
(+"").tap { |output|
output << text_field(name, **attributes)
output << tag.datalist(**datalist) {
(+"").tap { |inner|
values.each do |value, content|
inner << tag.option(content, value: value, **options)
end
}.html_safe
}
}.html_safe
end
# Returns a button tag.
#
# @return [String] the tag
#
# @overload button(content, **attributes)
# Returns a button tag with the given content.
#
# @param content [String] the content for the tag
# @param attributes [Hash] the tag's HTML attributes
#
# @overload button(**attributes, &block)
# Returns a button tag with the return value of the given block as the tag's content.
#
# @param attributes [Hash] the tag's HTML attributes
# @yieldreturn [String] the tag content
#
# @example Basic usage
# f.button("Click me")
# =>
#
# @example HTML Attributes
# f.button("Click me", class: "btn btn-secondary")
# =>
#
# @example Returning content from a block
# <%= f.button class: "btn btn-secondary" do %>
#
# <% end %>
#
# =>
#
#
# @api public
# @since 2.1.0
def button(...)
tag.button(...)
end
# Returns an image input tag, to be used as a visual button for the form.
#
# For security reasons, you should use the absolute URL of the given image.
#
# @param source [String] The absolute URL of the image
# @param attributes [Hash] the tag's HTML attributes
#
# @return [String] the tag
#
# @example Basic usage
# f.image_button("https://hanamirb.org/assets/button.png")
# =>
#
# @example HTML Attributes
# f.image_button("https://hanamirb.org/assets/button.png", name: "image", width: "50")
# =>
#
# @api public
# @since 2.1.0
def image_button(source, **attributes)
attributes[:type] = :image
attributes[:src] = sanitize_url(source)
input(**attributes)
end
# Returns a submit button tag.
#
# @return [String] the tag
#
# @overload submit(content, **attributes)
# Returns a submit button tag with the given content.
#
# @param content [String] the content for the tag
# @param attributes [Hash] the tag's HTML attributes
#
# @overload submit(**attributes, &blk)
# Returns a submit button tag with the return value of the given block as the tag's
# content.
#
# @param attributes [Hash] the tag's HTML attributes
# @yieldreturn [String] the tag content
#
# @example Basic usage
# f.submit("Create")
# =>
#
# @example HTML Attributes
# f.submit("Create", class: "btn btn-primary")
# =>
#
# @example Returning content from a block
# <%= f.submit(class: "btn btn-primary") do %>
#
# <% end %>
#
# =>
#
#
# @api public
# @since 2.1.0
def submit(content = nil, **attributes, &blk)
if content.is_a?(::Hash)
attributes = content
content = nil
end
attributes = {type: :submit, **attributes}
tag.button(content, **attributes, &blk)
end
# Returns an input tag.
#
# Generates an input tag without any special handling. For more convenience and other
# advanced features, see the other methods of the form builder.
#
# @param attributes [Hash] the tag's HTML attributes
# @yieldreturn [String] the tag content
#
# @return [String] the tag
#
# @since 2.1.0
# @api public
#
# @example Basic usage
# f.input(type: :text, name: "book[title]", id: "book-title", value: book.title)
# =>
#
# @api public
# @since 2.1.0
def input(...)
tag.input(...)
end
private
# @api private
# @since 2.1.0
def _form_method(attributes)
attributes[:method] ||= DEFAULT_METHOD
attributes[:method] = attributes[:method].to_s.upcase
original_form_method = attributes[:method]
if (method_override = !BROWSER_METHODS.include?(attributes[:method]))
attributes[:method] = DEFAULT_METHOD
end
[method_override, original_form_method]
end
# @api private
# @since 2.1.0
def _csrf_token(values, attributes)
return [] if values.csrf_token.nil?
return [] if EXCLUDED_CSRF_METHODS.include?(attributes[:method])
[true, values.csrf_token]
end
# @api private
# @since 2.1.0
def _attributes(type, name, attributes)
attrs = {
type: type,
name: _input_name(name),
id: _input_id(name),
value: _value(name)
}
attrs.merge!(attributes)
attrs[:value] = escape_html(attrs[:value]).html_safe
attrs
end
# @api private
# @since 2.1.0
def _input_name(name)
tokens = _split_input_name(name)
result = tokens.shift
tokens.each do |t|
if t =~ %r{\A\d+\z}
result << "[]"
else
result << "[#{t}]"
end
end
result
end
# @api private
# @since 2.1.0
def _input_id(name)
[base_name, name].compact.join(INPUT_NAME_SEPARATOR).to_s.tr("._", "-")
end
# @api private
# @since 2.1.0
def _value(name)
values.get(*_split_input_name(name).map(&:to_sym))
end
# @api private
# @since 2.1.0
def _split_input_name(name)
[
*base_name.to_s.split(INPUT_NAME_SEPARATOR),
*name.to_s.split(INPUT_NAME_SEPARATOR)
].compact
end
# @api private
# @since 2.1.0
#
# @see #check_box
def _hidden_field_for_check_box(name, attributes)
return unless attributes[:value].nil? || !attributes[:unchecked_value].nil?
input(
type: :hidden,
name: attributes[:name] || _input_name(name),
value: (attributes.delete(:unchecked_value) || DEFAULT_UNCHECKED_VALUE).to_s
)
end
# @api private
# @since 2.1.0
#
# @see #check_box
def _attributes_for_check_box(name, attributes)
attributes = {
type: :checkbox,
name: _input_name(name),
id: _input_id(name),
value: (attributes.delete(:checked_value) || DEFAULT_CHECKED_VALUE).to_s,
**attributes
}
attributes[:checked] = true if _check_box_checked?(attributes[:value], _value(name))
attributes
end
# @api private
# @since 1.2.0
def _select_input_name(name, multiple)
select_name = _input_name(name)
select_name = "#{select_name}[]" if multiple
select_name
end
# @api private
# @since 1.2.0
def _select_option_selected?(value, selected, input_value, multiple)
if input_value && selected.nil?
value.to_s == input_value.to_s
else
(value == selected) ||
_is_current_value?(input_value, value) ||
_is_in_selected_values?(multiple, selected, value) ||
_is_in_input_values?(multiple, input_value, value)
end
end
# @api private
# @since 1.2.0
def _is_current_value?(input_value, value)
return unless input_value
value.to_s == input_value.to_s
end
# @api private
# @since 1.2.0
def _is_in_selected_values?(multiple, selected, value)
return unless multiple && selected.is_a?(Array)
selected.include?(value)
end
# @api private
# @since 1.2.0
def _is_in_input_values?(multiple, input_value, value)
return unless multiple && input_value.is_a?(Array)
input_value.include?(value)
end
# @api private
# @since 1.2.0
def _check_box_checked?(value, input_value)
!input_value.nil? &&
(input_value.to_s == value.to_s || input_value.is_a?(TrueClass) ||
(input_value.is_a?(Array) && input_value.include?(value)))
end
end
end
end
end