require 'hanami/helpers/form_helper/html_node'
require 'hanami/helpers/form_helper/values'
require 'hanami/helpers/html_helper/html_builder'
require 'hanami/helpers/escape_helper'
require 'hanami/utils/string'
module Hanami
module Helpers
module FormHelper
# Form builder
#
# @since 0.2.0
#
# @see Hanami::Helpers::HtmlHelper::HtmlBuilder
class FormBuilder < ::Hanami::Helpers::HtmlHelper::HtmlBuilder # rubocop:disable Metrics/ClassLength
# Set of HTTP methods that are understood by web browsers
#
# @since 0.2.0
# @api private
BROWSER_METHODS = %w(GET POST).freeze
# Set of HTTP methods that should NOT generate CSRF token
#
# @since 0.2.0
# @api private
EXCLUDED_CSRF_METHODS = %w(GET).freeze
# Checked attribute value
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::FormHelper::FormBuilder#radio_button
CHECKED = 'checked'.freeze
# Selected attribute value for option
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::FormHelper::FormBuilder#select
SELECTED = 'selected'.freeze
# Separator for accept attribute of file input
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::FormHelper::FormBuilder#file_input
ACCEPT_SEPARATOR = ','.freeze
# Replacement for input id interpolation
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::FormHelper::FormBuilder#_input_id
INPUT_ID_REPLACEMENT = '-\k'.freeze
# Default value for unchecked check box
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::FormHelper::FormBuilder#check_box
DEFAULT_UNCHECKED_VALUE = '0'.freeze
# Default value for checked check box
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::FormHelper::FormBuilder#check_box
DEFAULT_CHECKED_VALUE = '1'.freeze
# ENCTYPE_MULTIPART = 'multipart/form-data'.freeze
include Helpers::EscapeHelper
self.html_node = ::Hanami::Helpers::FormHelper::HtmlNode
# Instantiate a form builder
#
# @overload initialize(form, attributes, params, &blk)
# Top level form
# @param form [Hanami::Helpers:FormHelper::Form] the form
# @param attributes [::Hash] a set of HTML attributes
# @param params [Hanami::Action::Params] request params
# @param blk [Proc] a block that describes the contents of the form
#
# @overload initialize(form, attributes, params, &blk)
# Nested form
# @param form [Hanami::Helpers:FormHelper::Form] the form
# @param attributes [Hanami::Helpers::FormHelper::Values] user defined
# values
# @param blk [Proc] a block that describes the contents of the form
#
# @return [Hanami::Helpers::FormHelper::FormBuilder] the form builder
#
# @since 0.2.0
# @api private
def initialize(form, attributes, context = nil, &blk) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
super()
@context = context
@blk = blk
@verb = nil
@csrf_token = nil
# Nested form
if @context.nil? && attributes.is_a?(Values)
@values = attributes
@attributes = {}
@name = form
else
@form = form
@name = form.name
@values = Values.new(form.values, @context.params)
@attributes = attributes
@verb_method = verb_method
@csrf_token = csrf_token
end
end
# Resolves all the nodes and generates the markup
#
# @return [Hanami::Utils::Escape::SafeString] the output
#
# @since 0.2.0
# @api private
#
# @see Hanami::Helpers::HtmlHelper::HtmlBuilder#to_s
# @see http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/Escape/SafeString
def to_s
if toplevel?
_method_override!
form(@blk, @attributes)
end
super
end
# Nested fields
#
# The inputs generated by the wrapped block will be prefixed with the given name
# It supports infinite levels of nesting.
#
# @param name [Symbol] the nested name, it's used to generate input
# names, ids, and to lookup params to fill values.
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# form_for :delivery, routes.deliveries_path do
# text_field :customer_name
#
# fields_for :address do
# text_field :street
# end
#
# submit 'Create'
# end
# %>
#
#
#
#
# @example Multiple levels of nesting
# <%=
# form_for :delivery, routes.deliveries_path do
# text_field :customer_name
#
# fields_for :address do
# text_field :street
#
# fields_for :location do
# text_field :city
# text_field :country
# end
# end
#
# submit 'Create'
# end
# %>
#
#
#
def fields_for(name)
current_name = @name
@name = _input_name(name)
yield(name)
ensure
@name = current_name
end
# Nested collections
#
# Supports nesting for collections, with infinite
# levels of nesting.
#
# @param name [Symbol] the nested name, it's used to generate input
# names, ids, and to lookup params to fill values.
#
# @example Basic usage
# <%=
# form_for :delivery, routes.deliveries_path do
# text_field :customer_name
#
# fields_for_collection :addresses do
# text_field :street
# end
#
# submit 'Create'
# end
# %>
#
#
#
def fields_for_collection(name, &block)
current_name = @name
base_value = _value(name)
@name = _input_name(name)
base_value.count.times do |index|
fields_for(index, &block)
end
ensure
@name = current_name
end
# Label tag
#
# The first param content can be a Symbol that represents
# the target field (Eg. :extended_title), or a String
# which is used as it is.
#
# @param content [Symbol,String] the field name or a content string
# @param attributes [Hash] HTML attributes to pass to the label tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# label :extended_title
# %>
#
#
#
#
# @example HTML attributes
# <%=
# # ...
# label :title, class: "form-label"
# %>
#
#
#
#
# @example Custom content
# <%=
# # ...
# label 'Title', for: :extended_title
# %>
#
#
#
#
# @example Custom "for" attribute
# <%=
# # ...
# label :extended_title, for: 'ext-title'
# %>
#
#
#
#
# @example Nested fields usage
# <%=
# # ...
# fields_for :address do
# label :city
# text_field :city
# end
# %>
#
#
#
#
def label(content, attributes = {})
attributes = { for: _for(content, attributes.delete(:for)) }.merge(attributes)
content = case content
when String, Hanami::Utils::String
content
else
Utils::String.new(content).capitalize
end
super(content, attributes)
end
# Fieldset
#
# @param content [Symbol,String,NilClass] the content
# @param attributes [Hash] HTML attributes to pass to the label tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# fieldset do
# legend "Author"
#
# fields_for :author do
# label :name
# text_field :name
# end
# end
# %>
#
#
#
def fieldset(content = nil, attributes = {})
# This is here only for documentation purposes
super
end
# Check box
#
# It renders a check box input.
#
# When a form is submitted, browsers don't send the value of unchecked
# check boxes. If an user unchecks a check box, their browser won't send
# the unchecked value. On the server side the corresponding value is
# missing, so the application will assume that the user action never
# happened.
#
# To solve this problem the form renders a hidden field with the
# "unchecked value". When the user unchecks the input, the browser will
# ignore it, but it will still send the value of the hidden input. See
# the examples below.
#
# When editing a resource, the form automatically assigns the
# checked="checked" attribute.
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
# @option attributes [String] :checked_value (defaults to "1")
# @option attributes [String] :unchecked_value (defaults to "0")
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# check_box :free_shipping
# %>
#
#
#
#
#
# @example HTML Attributes
# <%=
# check_box :free_shipping, class: "form-check-input"
# %>
#
#
#
#
#
# @example Specify (un)checked values
# <%=
# check_box :free_shipping, checked_value: 'true', unchecked_value: 'false'
# %>
#
#
#
#
#
# @example Automatic "checked" attribute
# # For this example the params are:
# #
# # { delivery: { free_shipping: '1' } }
# <%=
# check_box :free_shipping
# %>
#
#
#
#
#
# @example Force "checked" attribute
# # For this example the params are:
# #
# # { delivery: { free_shipping: '0' } }
# <%=
# check_box :free_shipping, checked: 'checked'
# %>
#
#
#
#
#
# @example Multiple check boxes
# <%=
# check_box :languages, name: 'book[languages][]', value: 'italian', id: nil
# check_box :languages, name: 'book[languages][]', value: 'english', id: nil
# %>
#
#
#
#
#
# @example Automatic "checked" attribute for multiple check boxes
# # For this example the params are:
# #
# # { book: { languages: ['italian'] } }
# <%=
# check_box :languages, name: 'book[languages][]', value: 'italian', id: nil
# check_box :languages, name: 'book[languages][]', value: 'english', id: nil
# %>
#
#
#
#
def check_box(name, attributes = {})
_hidden_field_for_check_box(name, attributes)
input _attributes_for_check_box(name, attributes)
end
# Color input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# color_field :background
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# color_field :background, class: "form-control"
# %>
#
#
#
def color_field(name, attributes = {})
input _attributes(:color, name, attributes)
end
# Date input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# date_field :birth_date
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# date_field :birth_date, class: "form-control"
# %>
#
#
#
def date_field(name, attributes = {})
input _attributes(:date, name, attributes)
end
# Datetime input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# datetime_field :delivered_at
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# datetime_field :delivered_at, class: "form-control"
# %>
#
#
#
def datetime_field(name, attributes = {})
input _attributes(:datetime, name, attributes)
end
# Datetime Local input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# datetime_local_field :delivered_at
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# datetime_local_field :delivered_at, class: "form-control"
# %>
#
#
#
def datetime_local_field(name, attributes = {})
input _attributes(:'datetime-local', name, attributes)
end
# Time field
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# time_field :release_hour
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# time_field :release_hour, class: "form-control"
# %>
#
#
#
def time_field(name, attributes = {})
input _attributes(:time, name, attributes)
end
# Month field
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# month_field :release_month
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# month_field :release_month, class: "form-control"
# %>
#
#
#
def month_field(name, attributes = {})
input _attributes(:month, name, attributes)
end
# Week field
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# week_field :release_week
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# week_field :release_week, class: "form-control"
# %>
#
#
#
def week_field(name, attributes = {})
input _attributes(:week, name, attributes)
end
# Email input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# email_field :email
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# email_field :email, class: "form-control"
# %>
#
#
#
def email_field(name, attributes = {})
input _attributes(:email, name, attributes)
end
# URL input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# url_field :website
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# url_field :website, class: "form-control"
# %>
#
#
#
def url_field(name, attributes = {})
attrs = attributes.dup
attrs[:value] = escape_url(attrs.fetch(:value) { _value(name) })
input _attributes(:url, name, attrs)
end
# Telephone input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# tel_field :telephone
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# telurl_field :telephone, class: "form-control"
# %>
#
#
#
def tel_field(name, attributes = {})
input _attributes(:tel, name, attributes)
end
# Hidden input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# hidden_field :customer_id
# %>
#
#
#
def hidden_field(name, attributes = {})
input _attributes(:hidden, name, attributes)
end
# File input
#
# **PLEASE REMEMBER TO ADD enctype: 'multipart/form-data' ATTRIBUTE TO THE FORM**
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
# @option attributes [String,Array] :accept Optional set of accepted MIME Types
# @option attributes [TrueClass,FalseClass] :multiple Optional, allow multiple file upload
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# file_field :avatar
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# file_field :avatar, class: "avatar-upload"
# %>
#
#
#
#
# @example Accepted MIME Types
# <%=
# # ...
# file_field :resume, accept: 'application/pdf,application/ms-word'
# %>
#
#
#
#
# @example Accepted MIME Types (as array)
# <%=
# # ...
# file_field :resume, accept: ['application/pdf', 'application/ms-word']
# %>
#
#
#
#
# @example Accepted multiple file upload (as array)
# <%=
# # ...
# file_field :resume, multiple: true
# %>
#
#
#
def file_field(name, attributes = {})
attributes[:accept] = Array(attributes[:accept]).join(ACCEPT_SEPARATOR) if attributes.key?(:accept)
attributes = { type: :file, name: _input_name(name), id: _input_id(name) }.merge(attributes)
input(attributes)
end
# Number input
#
# You can also make use of the `max`, `min`, and `step` attributes for
# the HTML5 number field.
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the number input
#
# @example Basic usage
# <%=
# # ...
# number_field :percent_read
# %>
#
#
#
#
# @example Advanced attributes
# <%=
# # ...
# number_field :priority, min: 1, max: 10, step: 1
# %>
#
#
#
def number_field(name, attributes = {})
input _attributes(:number, name, attributes)
end
# Range input
#
# You can also make use of the `max`, `min`, and `step` attributes for
# the HTML5 number field.
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the number input
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# range_field :discount_percentage
# %>
#
#
#
#
# @example Advanced attributes
# <%=
# # ...
# range_field :discount_percentage, min: 1, max: 10, step: 1
# %>
#
#
#
def range_field(name, attributes = {})
input _attributes(:range, name, attributes)
end
# Text-area input
#
# @param name [Symbol] the input name
# @param content [String] the content of the textarea
# @param attributes [Hash] HTML attributes to pass to the textarea tag
#
# @since 0.2.5
#
# @example Basic usage
# <%=
# # ...
# text_area :hobby
# %>
#
#
#
#
# @example Set content
# <%=
# # ...
# text_area :hobby, 'Football'
# %>
#
#
#
#
# @example Set content and HTML attributes
# <%=
# # ...
# text_area :hobby, 'Football', class: 'form-control'
# %>
#
#
#
#
# @example Omit content and specify HTML attributes
# <%=
# # ...
# text_area :hobby, class: 'form-control'
# %>
#
#
#
#
# @example Force blank value
# <%=
# # ...
# text_area :hobby, '', class: 'form-control'
# %>
#
#
#
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) }.merge(attributes)
textarea(content || _value(name), attributes)
end
# Text input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# text_field :first_name
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# text_field :first_name, class: "form-control"
# %>
#
#
#
def text_field(name, attributes = {})
input _attributes(:text, name, attributes)
end
alias input_text text_field
# Search input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# search_field :q
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# search_field :q, class: "form-control"
# %>
#
#
#
def search_field(name, attributes = {})
input _attributes(:search, name, attributes)
end
alias input_text text_field
# Radio input
#
# If request params have a value that corresponds to the given value,
# it automatically sets the checked attribute.
# This Hanami::Controller integration happens without any developer intervention.
#
# @param name [Symbol] the input name
# @param value [String] the input value
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# radio_button :category, 'Fiction'
# radio_button :category, 'Non-Fiction'
# %>
#
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# radio_button :category, 'Fiction', class: "form-check"
# radio_button :category, 'Non-Fiction', class: "form-check"
# %>
#
#
#
#
#
# @example Automatic checked value
# # Given the following params:
# #
# # book: {
# # category: 'Non-Fiction'
# # }
#
# <%=
# # ...
# radio_button :category, 'Fiction'
# radio_button :category, 'Non-Fiction'
# %>
#
#
#
#
def radio_button(name, value, attributes = {})
attributes = { type: :radio, name: _input_name(name), value: value }.merge(attributes)
attributes[:checked] = CHECKED if _value(name).to_s == value.to_s
input(attributes)
end
# Password input
#
# @param name [Symbol] the input name
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# password_field :password
# %>
#
#
#
def password_field(name, attributes = {})
input({ type: :password, name: _input_name(name), id: _input_id(name), value: nil }.merge(attributes))
end
# Select input
#
# @param name [Symbol] the input name
# @param values [Hash] a Hash to generate tags.
# Values correspond to value and keys correspond to the content.
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# If request params have a value that corresponds to one of the given values,
# it automatically sets the selected attribute on the tag.
# This Hanami::Controller integration happens without any developer intervention.
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# values = Hash['Italy' => 'it', 'United States' => 'us']
# select :store, values, class: "form-control"
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# values = Hash['Italy' => 'it', 'United States' => 'us']
# select :store, values
# %>
#
#
#
#
# @example Automatic selected option
# # Given the following params:
# #
# # book: {
# # store: 'it'
# # }
#
# <%=
# # ...
# values = Hash['it' => 'Italy', 'us' => 'United States']
# select :store, values
# %>
#
#
#
#
# @example Prompt option
# <%=
# # ...
# values = Hash['it' => 'Italy', 'us' => 'United States']
# select :store, values, options: { prompt: 'Select a store' }
# %>
#
#
#
#
# @example Selected option
# <%=
# # ...
# values = Hash['it' => 'Italy', 'us' => 'United States']
# select :store, values, options: { selected: book.store }
# %>
#
#
#
#
# @example Prompt option and HTML attributes
# <%=
# # ...
# values = Hash['it' => 'Italy', 'us' => 'United States']
# select :store, values, options: { prompt: 'Select a store' }, class: "form-control"
# %>
#
#
#
#
# @example Multiple select
# <%=
# # ...
# values = Hash['it' => 'Italy', 'us' => 'United States']
# select :stores, values, multiple: true
# %>
#
#
#
#
# @example Multiple select and HTML attributes
# <%=
# # ...
# values = Hash['it' => 'Italy', 'us' => 'United States']
# select :stores, values, multiple: true, class: "form-control"
# %>
#
#
#
def select(name, values, attributes = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
options = attributes.delete(:options) { {} }
attributes = { name: _select_input_name(name, attributes[:multiple]), id: _input_id(name) }.merge(attributes)
prompt = options.delete(:prompt)
selected = options.delete(:selected)
super(attributes) do
option(prompt) unless prompt.nil?
values.each do |content, value|
if _select_option_selected?(value, selected, _value(name), attributes[:multiple])
option(content, { value: value, selected: SELECTED }.merge(options))
else
option(content, { value: value }.merge(options))
end
end
end
end
# Datalist input
#
# @param name [Symbol] the input name
# @param values [Array,Hash] a collection that is transformed into tags.
# @param list [String] the name of list for the text input, it's also the id of datalist
# @param attributes [Hash] HTML attributes to pass to the input tag
#
# @since 0.4.0
#
# @example Basic Usage
# <%=
# # ...
# values = ['Italy', 'United States']
# datalist :stores, values, 'books'
# %>
#
#
#
#
#
# @example Options As Hash
# <%=
# # ...
# values = Hash['Italy' => 'it', 'United States' => 'us']
# datalist :stores, values, 'books'
# %>
#
#
#
#
#
# @example Specify Custom Attributes For Datalist Input
# <%=
# # ...
# values = ['Italy', 'United States']
# datalist :stores, values, 'books', datalist: { class: 'form-control' }
# %>
#
#
#
#
#
# @example Specify Custom Attributes For Options List
# <%=
# # ...
# values = ['Italy', 'United States']
# datalist :stores, values, 'books', options: { class: 'form-control' }
# %>
#
#
#
#
def datalist(name, values, list, attributes = {}) # rubocop:disable Metrics/MethodLength
attrs = attributes.dup
options = attrs.delete(:options) || {}
datalist = attrs.delete(:datalist) || {}
attrs[:list] = list
datalist[:id] = list
text_field(name, attrs)
super(datalist) do
values.each do |value, content|
option(content, { value: value }.merge(options))
end
end
end
# Button
#
# @param content [String] The content
# @param attributes [Hash] HTML attributes to pass to the button tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# button 'Click me'
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# button 'Click me', class: "btn btn-secondary"
# %>
#
#
#
def button(content, attributes = {})
# This is here only for documentation purposes
super
end
# Image button
#
# Visual submit button
#
# **Please note:** for security reasons, please use the absolute URL of the image
#
# @param source [String] The **absolute URL** of the image
# @param attributes [Hash] HTML attributes to pass to the button tag
#
# @since 1.0.0.beta2
#
# @example Basic usage
# <%=
# # ...
# image_button "https://hanamirb.org/assets/button.png"
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# image_button "https://hanamirb.org/assets/button.png", name: "image", width: "50"
# %>
#
#
#
def image_button(source, attributes = {})
attrs = attributes.dup
attrs[:type] = :image
attrs[:src] = escape_url(source)
input attrs
end
# Submit button
#
# @param content [String] The content
# @param attributes [Hash] HTML attributes to pass to the button tag
#
# @since 0.2.0
#
# @example Basic usage
# <%=
# # ...
# submit 'Create'
# %>
#
#
#
#
# @example HTML Attributes
# <%=
# # ...
# submit 'Create', class: "btn btn-primary"
# %>
#
#
#
def submit(content, attributes = {})
attributes = { type: :submit }.merge(attributes)
button(content, attributes)
end
protected
# A set of options to pass to the sub form helpers.
#
# @api private
# @since 0.2.0
def options
Hash[name: @name, values: @values, verb: @verb, csrf_token: @csrf_token]
end
private
# Check the current builder is top-level
#
# @api private
# @since 0.2.0
def toplevel?
@attributes.any?
end
# Prepare for method override
#
# @api private
# @since 0.2.0
def _method_override!
if BROWSER_METHODS.include?(@verb_method)
@attributes[:method] = @verb_method
else
@attributes[:method] = DEFAULT_METHOD
@verb = @verb_method
end
end
# Return the method from attributes
#
# @api private
def verb_method
(@attributes.fetch(:method) { DEFAULT_METHOD }).to_s.upcase
end
# Return CSRF Protection token from view context
#
# @api private
# @since 0.2.0
def csrf_token
@context.csrf_token if @context.respond_to?(:csrf_token) && !EXCLUDED_CSRF_METHODS.include?(@verb_method)
end
# Return a set of default HTML attributes
#
# @api private
# @since 0.2.0
def _attributes(type, name, attributes)
attrs = { type: type, name: _displayed_input_name(name), id: _input_id(name), value: _value(name) }
attrs.merge!(attributes)
attrs[:value] = escape_html(attrs[:value])
attrs
end
# Full input name, used to construct the input
# attributes.
#
# @api private
# @since 0.2.0
def _input_name(name)
"#{@name}[#{name}]"
end
# Input name HTML attribute
#
# @api private
# @since 1.0.0.beta1
def _displayed_input_name(name)
_input_name(name).gsub(/\[\d+\]/, '[]')
end
# Input id HTML attribute
#
# @api private
# @since 0.2.0
def _input_id(name)
name = _input_name(name).gsub(/\[(?[[[:word:]]\-]*)\]/, INPUT_ID_REPLACEMENT)
Utils::String.new(name).dasherize
end
# Input value HTML attribute
#
# @api private
# @since 0.2.0
def _value(name)
@values.get(
*_input_name(name).split(/[\[\]]+/).map(&:to_sym)
)
end
# Input for HTML attribute
#
# @api private
# @since 0.2.0
def _for(content, name)
case name
when String, Hanami::Utils::String
name
else
_input_id(name || content)
end
end
# Hidden field for check box
#
# @api private
# @since 0.2.0
#
# @see Hanami::Helpers::FormHelper::FormBuilder#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
)
end
# HTML attributes for check box
#
# @api private
# @since 0.2.0
#
# @see Hanami::Helpers::FormHelper::FormBuilder#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
}.merge(attributes)
attributes[:checked] = CHECKED if _check_box_checked?(attributes[:value], _value(name))
attributes
end
# @api private
def _select_input_name(name, multiple)
select_name = _input_name(name)
select_name = "#{select_name}[]" if multiple
select_name
end
# TODO: this has to be refactored
#
# @api private
#
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def _select_option_selected?(value, selected, input_value, multiple)
value == selected || (multiple && (selected.is_a?(Array) && selected.include?(value))) ||
value.to_s == input_value.to_s || (multiple && (input_value.is_a?(Array) && input_value.include?(value)))
end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# @api private
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