piko-lite-box 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/hanami-3.0.1/CHANGELOG.md +1701 -0
  3. data/hanami-3.0.1/LICENSE +20 -0
  4. data/hanami-3.0.1/README.md +89 -0
  5. data/hanami-3.0.1/hanami.gemspec +49 -0
  6. data/hanami-3.0.1/lib/hanami/app.rb +197 -0
  7. data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
  8. data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
  9. data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
  10. data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
  11. data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
  12. data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
  13. data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
  14. data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
  15. data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
  16. data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
  17. data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
  18. data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
  19. data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
  20. data/hanami-3.0.1/lib/hanami/config.rb +564 -0
  21. data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
  22. data/hanami-3.0.1/lib/hanami/env.rb +52 -0
  23. data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
  24. data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
  25. data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
  26. data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
  27. data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
  28. data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
  29. data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
  30. data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
  31. data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
  32. data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
  33. data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
  34. data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
  35. data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
  36. data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
  37. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
  38. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
  39. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
  40. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
  41. data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
  42. data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
  43. data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
  44. data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
  45. data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
  46. data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
  47. data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
  48. data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
  49. data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
  50. data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
  51. data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
  52. data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
  53. data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
  54. data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
  55. data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
  56. data/hanami-3.0.1/lib/hanami/port.rb +45 -0
  57. data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
  58. data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
  59. data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
  60. data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
  61. data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
  62. data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
  63. data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
  64. data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
  65. data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
  66. data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
  67. data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
  68. data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
  69. data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
  70. data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
  71. data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
  72. data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
  73. data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
  74. data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
  75. data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
  76. data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
  77. data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
  78. data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
  79. data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
  80. data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
  81. data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
  82. data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
  83. data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
  84. data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
  85. data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
  86. data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
  87. data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
  88. data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
  89. data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
  90. data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
  91. data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
  92. data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
  93. data/hanami-3.0.1/lib/hanami/version.rb +45 -0
  94. data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
  95. data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
  96. data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
  97. data/hanami-3.0.1/lib/hanami.rb +276 -0
  98. data/piko-lite-box.gemspec +11 -0
  99. metadata +137 -0
@@ -0,0 +1,1389 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/view"
4
+ require_relative "values"
5
+
6
+ module Hanami
7
+ module Helpers
8
+ module FormHelper
9
+ # A range of convenient methods for building the fields within an HTML form, integrating with
10
+ # request params and template locals to populate the fields with appropriate values.
11
+ #
12
+ # @see FormHelper#form_for
13
+ #
14
+ # @api public
15
+ # @since 2.1.0
16
+ class FormBuilder
17
+ # Set of HTTP methods that are understood by web browsers
18
+ #
19
+ # @since 2.1.0
20
+ # @api private
21
+ BROWSER_METHODS = %w[GET POST].freeze
22
+ private_constant :BROWSER_METHODS
23
+
24
+ # Set of HTTP methods that should NOT generate CSRF token
25
+ #
26
+ # @since 2.1.0
27
+ # @api private
28
+ EXCLUDED_CSRF_METHODS = %w[GET].freeze
29
+ private_constant :EXCLUDED_CSRF_METHODS
30
+
31
+ # Separator for accept attribute of file input
32
+ #
33
+ # @since 2.1.0
34
+ # @api private
35
+ #
36
+ # @see #file_input
37
+ ACCEPT_SEPARATOR = ","
38
+ private_constant :ACCEPT_SEPARATOR
39
+
40
+ # Default value for unchecked check box
41
+ #
42
+ # @since 2.1.0
43
+ # @api private
44
+ #
45
+ # @see #check_box
46
+ DEFAULT_UNCHECKED_VALUE = "0"
47
+ private_constant :DEFAULT_UNCHECKED_VALUE
48
+
49
+ # Default value for checked check box
50
+ #
51
+ # @since 2.1.0
52
+ # @api private
53
+ #
54
+ # @see #check_box
55
+ DEFAULT_CHECKED_VALUE = "1"
56
+ private_constant :DEFAULT_CHECKED_VALUE
57
+
58
+ # Input name separator
59
+ #
60
+ # @since 2.1.0
61
+ # @api private
62
+ INPUT_NAME_SEPARATOR = "."
63
+ private_constant :INPUT_NAME_SEPARATOR
64
+
65
+ # Empty string
66
+ #
67
+ # @since 2.1.0
68
+ # @api private
69
+ #
70
+ # @see #password_field
71
+ EMPTY_STRING = ""
72
+ private_constant :EMPTY_STRING
73
+
74
+ include Hanami::View::Helpers::EscapeHelper
75
+ include Hanami::View::Helpers::TagHelper
76
+
77
+ # @api private
78
+ # @since 2.1.0
79
+ attr_reader :base_name
80
+ private :base_name
81
+
82
+ # @api private
83
+ # @since 2.1.0
84
+ attr_reader :values
85
+ private :values
86
+
87
+ # @api private
88
+ # @since 2.1.0
89
+ attr_reader :inflector
90
+ private :inflector
91
+
92
+ # @api private
93
+ # @since 2.1.0
94
+ attr_reader :form_attributes
95
+ private :form_attributes
96
+
97
+ # Returns a new form builder.
98
+ #
99
+ # @param inflector [Dry::Inflector] the app inflector
100
+ # @param base_name [String, nil] the base name to use for all fields in the form
101
+ # @param values [Hanami::Helpers::FormHelper::Values] the values for the form
102
+ #
103
+ # @return [self]
104
+ #
105
+ # @see Hanami::Helpers::FormHelper#form_for
106
+ #
107
+ # @api private
108
+ # @since 2.1.0
109
+ def initialize(inflector:, form_attributes:, base_name: nil, values: Values.new)
110
+ @base_name = base_name
111
+ @values = values
112
+ @form_attributes = form_attributes
113
+ @inflector = inflector
114
+ end
115
+
116
+ # @api private
117
+ # @since 2.1.0
118
+ def call(content, **attributes)
119
+ attributes["accept-charset"] ||= DEFAULT_CHARSET
120
+
121
+ method_override, original_form_method = _form_method(attributes)
122
+ csrf_token, token = _csrf_token(values, attributes)
123
+
124
+ tag.form(**attributes) do
125
+ (+"").tap { |inner|
126
+ inner << input(type: "hidden", name: "_method", value: original_form_method) if method_override
127
+ inner << input(type: "hidden", name: "_csrf_token", value: token) if csrf_token
128
+ inner << content
129
+ }.html_safe
130
+ end
131
+ end
132
+
133
+ # Applies the base input name to all fields within the given block.
134
+ #
135
+ # This can be helpful when generating a set of nested fields.
136
+ #
137
+ # This is a convenience only. You can achieve the same result by including the base name at
138
+ # the beginning of each input name.
139
+ #
140
+ # @param name [String] the base name to be used for all fields in the block
141
+ # @yieldparam [FormBuilder] the form builder for the nested fields
142
+ #
143
+ # @example Basic usage
144
+ # <% f.fields_for "address" do |fa| %>
145
+ # <%= fa.text_field "street" %>
146
+ # <%= fa.text_field "suburb" %>
147
+ # <% end %>
148
+ #
149
+ # # A convenience for:
150
+ # # <%= f.text_field "address.street" %>
151
+ # # <%= f.text_field "address.suburb" %>
152
+ #
153
+ # =>
154
+ # <input type="text" name="delivery[customer_name]" id="delivery-customer-name" value="">
155
+ # <input type="text" name="delivery[address][street]" id="delivery-address-street" value="">
156
+ #
157
+ # @example Multiple levels of nesting
158
+ # <% f.fields_for "address" do |fa| %>
159
+ # <%= fa.text_field "street" %>
160
+ #
161
+ # <% fa.fields_for "location" do |fl| %>
162
+ # <%= fl.text_field "city" %>
163
+ # <% end %>
164
+ # <% end %>
165
+ #
166
+ # =>
167
+ # <input type="text" name="delivery[address][street]" id="delivery-address-street" value="">
168
+ # <input type="text" name="delivery[address][location][city]" id="delivery-address-location-city" value="">
169
+ #
170
+ # @api public
171
+ # @since 2.1.0
172
+ def fields_for(name, *yield_args)
173
+ new_base_name = [base_name, name.to_s].compact.join(INPUT_NAME_SEPARATOR)
174
+
175
+ builder = self.class.new(
176
+ base_name: new_base_name,
177
+ values: values,
178
+ form_attributes: form_attributes,
179
+ inflector: inflector
180
+ )
181
+
182
+ yield(builder, *yield_args)
183
+ end
184
+
185
+ # Yields to the given block for each element in the matching collection value, and applies
186
+ # the base input name to all fields within the block.
187
+ #
188
+ # Use this whenever generating form fields for an collection of nested fields.
189
+ #
190
+ # @param name [String] the input name, also used as the base input name for all fields
191
+ # within the block
192
+ # @yieldparam [FormBuilder] the form builder for the nested fields
193
+ # @yieldparam [Integer] the index of the iteration over the collection, starting from zero
194
+ # @yieldparam [Object] the value of the element from the collection
195
+ #
196
+ # @example Basic usage
197
+ # <% f.fields_for_collection("addresses") do |fa| %>
198
+ # <%= fa.text_field("street") %>
199
+ # <% end %>
200
+ #
201
+ # =>
202
+ # <input type="text" name="delivery[addresses][][street]" id="delivery-address-0-street" value="">
203
+ # <input type="text" name="delivery[addresses][][street]" id="delivery-address-1-street" value="">
204
+ #
205
+ # @example Yielding index and value
206
+ # <% f.fields_for_collection("bill.addresses") do |fa, i, address| %>
207
+ # <div class="form-group">
208
+ # Address id: <%= address.id %>
209
+ # <%= fa.label("street") %>
210
+ # <%= fa.text_field("street", data: {index: i.to_s}) %>
211
+ # </div>
212
+ # <% end %>
213
+ #
214
+ # =>
215
+ # <div class="form-group">
216
+ # Address id: 23
217
+ # <label for="bill-addresses-0-street">Street</label>
218
+ # <input type="text" name="bill[addresses][][street]" id="bill-addresses-0-street" value="5th Ave" data-index="0">
219
+ # </div>
220
+ # <div class="form-group">
221
+ # Address id: 42
222
+ # <label for="bill-addresses-1-street">Street</label>
223
+ # <input type="text" name="bill[addresses][][street]" id="bill-addresses-1-street" value="4th Ave" data-index="1">
224
+ # </div>
225
+ #
226
+ # @api public
227
+ # @since 2.1.0
228
+ def fields_for_collection(name, &block)
229
+ _value(name).each_with_index do |value, index|
230
+ fields_for("#{name}.#{index}", index, value, &block)
231
+ end
232
+ end
233
+
234
+ # Returns a label tag.
235
+ #
236
+ # @return [String] the tag
237
+ #
238
+ # @overload label(field_name, **attributes)
239
+ # Returns a label tag for the given field name, with a humanized version of the field name
240
+ # as the tag's content.
241
+ #
242
+ # @param field_name [String] the field name
243
+ # @param attributes [Hash] the tag attributes
244
+ #
245
+ # @example
246
+ # <%= f.label("book.extended_title") %>
247
+ # # => <label for="book-extended-title">Extended title</label>
248
+ #
249
+ # @example HTML attributes
250
+ # <%= f.label("book.title", class: "form-label") %>
251
+ # # => <label for="book-title" class="form-label">Title</label>
252
+ #
253
+ # @overload label(content, **attributes)
254
+ # Returns a label tag for the field name given as `for:`, with the given content string as
255
+ # the tag's content.
256
+ #
257
+ # @param content [String] the tag's content
258
+ # @param for [String] the field name
259
+ # @param attributes [Hash] the tag attributes
260
+ #
261
+ # @example
262
+ # <%= f.label("Title", for: "book.extended_title") %>
263
+ # # => <label for="book-extended-title">Title</label>
264
+ #
265
+ # f.label("book.extended_title", for: "ext-title")
266
+ # # => <label for="ext-title">Extended title</label>
267
+ #
268
+ # @overload label(field_name, **attributes, &block)
269
+ # Returns a label tag for the given field name, with the return value of the given block
270
+ # as the tag's content.
271
+ #
272
+ # @param field_name [String] the field name
273
+ # @param attributes [Hash] the tag attributes
274
+ # @yieldreturn [String] the tag content
275
+ #
276
+ # @example
277
+ # <%= f.label for: "book.free_shipping" do %>
278
+ # Free shipping
279
+ # <abbr title="optional" aria-label="optional">*</abbr>
280
+ # <% end %>
281
+ #
282
+ # # =>
283
+ # <label for="book-free-shipping">
284
+ # Free shipping
285
+ # <abbr title="optional" aria-label="optional">*</abbr>
286
+ # </label>
287
+ #
288
+ # @api public
289
+ # @since 2.1.0
290
+ def label(content = nil, **attributes, &block)
291
+ for_attribute_given = attributes.key?(:for)
292
+
293
+ attributes[:for] = _input_id(attributes[:for] || content)
294
+
295
+ if content && !for_attribute_given
296
+ content = inflector.humanize(content.to_s.split(INPUT_NAME_SEPARATOR).last)
297
+ end
298
+
299
+ tag.label(content, **attributes, &block)
300
+ end
301
+
302
+ # @overload fieldset(**attributes, &block)
303
+ # Returns a fieldset tag.
304
+ #
305
+ # @param attributes [Hash] the tag's HTML attributes
306
+ # @yieldreturn [String] the tag's content
307
+ #
308
+ # @return [String] the tag
309
+ #
310
+ # @example
311
+ # <%= f.fieldset do %>
312
+ # <%= f.legend("Author") %>
313
+ # <%= f.label("author.name") %>
314
+ # <%= f.text_field("author.name") %>
315
+ # <% end %>
316
+ #
317
+ # # =>
318
+ # <fieldset>
319
+ # <legend>Author</legend>
320
+ # <label for="book-author-name">Name</label>
321
+ # <input type="text" name="book[author][name]" id="book-author-name" value="">
322
+ # </fieldset>
323
+ #
324
+ # @since 2.1.0
325
+ # @api public
326
+ def fieldset(...)
327
+ # This is here only for documentation purposes
328
+ tag.fieldset(...)
329
+ end
330
+
331
+ # Returns the tags for a check box.
332
+ #
333
+ # When editing a resource, the form automatically assigns the `checked` HTML attribute for
334
+ # the check box tag.
335
+ #
336
+ # Returns a hidden input tag in preceding the check box input tag. This ensures that
337
+ # unchecked values are submitted with the form.
338
+ #
339
+ # @param name [String] the input name
340
+ # @param attributes [Hash] the HTML attributes for the check box tag
341
+ # @option attributes [String] :checked_value (defaults to "1")
342
+ # @option attributes [String] :unchecked_value (defaults to "0")
343
+ #
344
+ # @return [String] the tags
345
+ #
346
+ # @example Basic usage
347
+ # f.check_box("delivery.free_shipping")
348
+ #
349
+ # # =>
350
+ # <input type="hidden" name="delivery[free_shipping]" value="0">
351
+ # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1">
352
+ #
353
+ # @example HTML Attributes
354
+ # f.check_box("delivery.free_shipping", class: "form-check-input")
355
+ #
356
+ # =>
357
+ # <input type="hidden" name="delivery[free_shipping]" value="0">
358
+ # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1" class="form-check-input">
359
+ #
360
+ # @example Specifying checked and unchecked values
361
+ # f.check_box("delivery.free_shipping", checked_value: "true", unchecked_value: "false")
362
+ #
363
+ # =>
364
+ # <input type="hidden" name="delivery[free_shipping]" value="false">
365
+ # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="true">
366
+ #
367
+ # @example Automatic "checked" attribute
368
+ # # Given the request params:
369
+ # # {delivery: {free_shipping: "1"}}
370
+ # f.check_box("delivery.free_shipping")
371
+ #
372
+ # =>
373
+ # <input type="hidden" name="delivery[free_shipping]" value="0">
374
+ # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1" checked="checked">
375
+ #
376
+ # @example Forcing the "checked" attribute
377
+ # # Given the request params:
378
+ # # {delivery: {free_shipping: "0"}}
379
+ # f.check_box("deliver.free_shipping", checked: "checked")
380
+ #
381
+ # =>
382
+ # <input type="hidden" name="delivery[free_shipping]" value="0">
383
+ # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1" checked="checked">
384
+ #
385
+ # @example Multiple check boxes for an array of values
386
+ # f.check_box("book.languages", name: "book[languages][]", value: "italian", id: nil)
387
+ # f.check_box("book.languages", name: "book[languages][]", value: "english", id: nil)
388
+ #
389
+ # =>
390
+ # <input type="checkbox" name="book[languages][]" value="italian">
391
+ # <input type="checkbox" name="book[languages][]" value="english">
392
+ #
393
+ # @example Automatic "checked" attribute for an array of values
394
+ # # Given the request params:
395
+ # # {book: {languages: ["italian"]}}
396
+ # f.check_box("book.languages", name: "book[languages][]", value: "italian", id: nil)
397
+ # f.check_box("book.languages", name: "book[languages][]", value: "english", id: nil)
398
+ #
399
+ # =>
400
+ # <input type="checkbox" name="book[languages][]" value="italian" checked="checked">
401
+ # <input type="checkbox" name="book[languages][]" value="english">
402
+ #
403
+ # @api public
404
+ # @since 2.1.0
405
+ def check_box(name, **attributes)
406
+ (+"").tap { |output|
407
+ output << _hidden_field_for_check_box(name, attributes).to_s
408
+ output << input(**_attributes_for_check_box(name, attributes))
409
+ }.html_safe
410
+ end
411
+
412
+ # Returns a color input tag.
413
+ #
414
+ # @param name [String] the input name
415
+ # @param attributes [Hash] the tag's HTML attributes
416
+ #
417
+ # @return [String] the tag
418
+ #
419
+ # @example Basic usage
420
+ # f.color_field("user.background")
421
+ # => <input type="color" name="user[background]" id="user-background" value="">
422
+ #
423
+ # @example HTML Attributes
424
+ # f.color_field("user.background", class: "form-control")
425
+ # => <input type="color" name="user[background]" id="user-background" value="" class="form-control">
426
+ #
427
+ # @api public
428
+ # @since 2.1.0
429
+ def color_field(name, **attributes)
430
+ input(**_attributes(:color, name, attributes))
431
+ end
432
+
433
+ # Returns a date input tag.
434
+ #
435
+ # @param name [String] the input name
436
+ # @param attributes [Hash] the tag's HTML attributes
437
+ #
438
+ # @return [String] the tag
439
+ #
440
+ # @example Basic usage
441
+ # f.date_field("user.birth_date")
442
+ # # => <input type="date" name="user[birth_date]" id="user-birth-date" value="">
443
+ #
444
+ # @example HTML Attributes
445
+ # f.date_field("user.birth_date", class: "form-control")
446
+ # => <input type="date" name="user[birth_date]" id="user-birth-date" value="" class="form-control">
447
+ #
448
+ # @api public
449
+ # @since 2.1.0
450
+ def date_field(name, **attributes)
451
+ input(**_attributes(:date, name, attributes))
452
+ end
453
+
454
+ # Returns a datetime input tag.
455
+ #
456
+ # @param name [String] the input name
457
+ # @param attributes [Hash] the tag's HTML attributes
458
+ #
459
+ # @return [String] the tag
460
+ #
461
+ # @example Basic usage
462
+ # f.datetime_field("delivery.delivered_at")
463
+ # => <input type="datetime" name="delivery[delivered_at]" id="delivery-delivered-at" value="">
464
+ #
465
+ # @example HTML Attributes
466
+ # f.datetime_field("delivery.delivered_at", class: "form-control")
467
+ # => <input type="datetime" name="delivery[delivered_at]" id="delivery-delivered-at" value="" class="form-control">
468
+ #
469
+ # @api public
470
+ # @since 2.1.0
471
+ def datetime_field(name, **attributes)
472
+ input(**_attributes(:datetime, name, attributes))
473
+ end
474
+
475
+ # Returns a datetime-local input tag.
476
+ #
477
+ # @param name [String] the input name
478
+ # @param attributes [Hash] the tag's HTML attributes
479
+ #
480
+ # @return [String] the tag
481
+ #
482
+ # @example Basic usage
483
+ # f.datetime_local_field("delivery.delivered_at")
484
+ # => <input type="datetime-local" name="delivery[delivered_at]" id="delivery-delivered-at" value="">
485
+ #
486
+ # @example HTML Attributes
487
+ # f.datetime_local_field("delivery.delivered_at", class: "form-control")
488
+ # => <input type="datetime-local" name="delivery[delivered_at]" id="delivery-delivered-at" value="" class="form-control">
489
+ #
490
+ # @api public
491
+ # @since 2.1.0
492
+ def datetime_local_field(name, **attributes)
493
+ input(**_attributes(:"datetime-local", name, attributes))
494
+ end
495
+
496
+ # Returns a time input tag.
497
+ #
498
+ # @param name [String] the input name
499
+ # @param attributes [Hash] the tag's HTML attributes
500
+ #
501
+ # @return [String] the tag
502
+ #
503
+ # @example Basic usage
504
+ # f.time_field("book.release_hour")
505
+ # => <input type="time" name="book[release_hour]" id="book-release-hour" value="">
506
+ #
507
+ # @example HTML Attributes
508
+ # f.time_field("book.release_hour", class: "form-control")
509
+ # => <input type="time" name="book[release_hour]" id="book-release-hour" value="" class="form-control">
510
+ #
511
+ # @api public
512
+ # @since 2.1.0
513
+ def time_field(name, **attributes)
514
+ input(**_attributes(:time, name, attributes))
515
+ end
516
+
517
+ # Returns a month input tag.
518
+ #
519
+ # @param name [String] the input name
520
+ # @param attributes [Hash] the tag's HTML attributes
521
+ #
522
+ # @return [String] the tag
523
+ #
524
+ # @example Basic usage
525
+ # f.month_field("book.release_month")
526
+ # => <input type="month" name="book[release_month]" id="book-release-month" value="">
527
+ #
528
+ # @example HTML Attributes
529
+ # f.month_field("book.release_month", class: "form-control")
530
+ # => <input type="month" name="book[release_month]" id="book-release-month" value="" class="form-control">
531
+ #
532
+ # @api public
533
+ # @since 2.1.0
534
+ def month_field(name, **attributes)
535
+ input(**_attributes(:month, name, attributes))
536
+ end
537
+
538
+ # Returns a week input tag.
539
+ #
540
+ # @param name [String] the input name
541
+ # @param attributes [Hash] the tag's HTML attributes
542
+ #
543
+ # @return [String] the tag
544
+ #
545
+ # @example Basic usage
546
+ # f.week_field("book.release_week")
547
+ # => <input type="week" name="book[release_week]" id="book-release-week" value="">
548
+ #
549
+ # @example HTML Attributes
550
+ # f.week_field("book.release_week", class: "form-control")
551
+ # => <input type="week" name="book[release_week]" id="book-release-week" value="" class="form-control">
552
+ #
553
+ # @api public
554
+ # @since 2.1.0
555
+ def week_field(name, **attributes)
556
+ input(**_attributes(:week, name, attributes))
557
+ end
558
+
559
+ # Returns an email input tag.
560
+ #
561
+ # @param name [String] the input name
562
+ # @param attributes [Hash] the tag's HTML attributes
563
+ #
564
+ # @return [String] the tag
565
+ #
566
+ # @example Basic usage
567
+ # f.email_field("user.email")
568
+ # => <input type="email" name="user[email]" id="user-email" value="">
569
+ #
570
+ # @example HTML Attributes
571
+ # f.email_field("user.email", class: "form-control")
572
+ # => <input type="email" name="user[email]" id="user-email" value="" class="form-control">
573
+ #
574
+ # @api public
575
+ # @since 2.1.0
576
+ def email_field(name, **attributes)
577
+ input(**_attributes(:email, name, attributes))
578
+ end
579
+
580
+ # Returns a URL input tag.
581
+ #
582
+ # @param name [String] the input name
583
+ # @param attributes [Hash] the tag's HTML attributes
584
+ #
585
+ # @return [String] the tag
586
+ #
587
+ # @example Basic usage
588
+ # f.url_field("user.website")
589
+ # => <input type="url" name="user[website]" id="user-website" value="">
590
+ #
591
+ # @example HTML Attributes
592
+ # f.url_field("user.website", class: "form-control")
593
+ # => <input type="url" name="user[website]" id="user-website" value="" class="form-control">
594
+ #
595
+ # @api public
596
+ # @since 2.1.0
597
+ def url_field(name, **attributes)
598
+ attributes[:value] = sanitize_url(attributes.fetch(:value) { _value(name) })
599
+
600
+ input(**_attributes(:url, name, attributes))
601
+ end
602
+
603
+ # Returns a telephone input tag.
604
+ #
605
+ # @param name [String] the input name
606
+ # @param attributes [Hash] the tag's HTML attributes
607
+ #
608
+ # @return [String] the tag
609
+ #
610
+ # @example
611
+ # f.tel_field("user.telephone")
612
+ # => <input type="tel" name="user[telephone]" id="user-telephone" value="">
613
+ #
614
+ # @example HTML Attributes
615
+ # f.tel_field("user.telephone", class: "form-control")
616
+ # => <input type="tel" name="user[telephone]" id="user-telephone" value="" class="form-control">
617
+ #
618
+ # @api public
619
+ # @since 2.1.0
620
+ def tel_field(name, **attributes)
621
+ input(**_attributes(:tel, name, attributes))
622
+ end
623
+
624
+ # Returns a hidden input tag.
625
+ #
626
+ # @param name [String] the input name
627
+ # @param attributes [Hash] the tag's HTML attributes
628
+ #
629
+ # @return [String] the tag
630
+ #
631
+ # @example
632
+ # f.hidden_field("delivery.customer_id")
633
+ # => <input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="">
634
+ #
635
+ # @api public
636
+ # @since 2.1.0
637
+ def hidden_field(name, **attributes)
638
+ input(**_attributes(:hidden, name, attributes))
639
+ end
640
+
641
+ # Returns a file input tag.
642
+ #
643
+ # @param name [String] the input name
644
+ # @param attributes [Hash] the tag's HTML attributes
645
+ # @option attributes [String, Array] :accept Optional set of accepted MIME Types
646
+ # @option attributes [Boolean] :multiple allow multiple file upload
647
+ #
648
+ # @return [String] the tag
649
+ #
650
+ # @example Basic usage
651
+ # f.file_field("user.avatar")
652
+ # => <input type="file" name="user[avatar]" id="user-avatar">
653
+ #
654
+ # @example HTML Attributes
655
+ # f.file_field("user.avatar", class: "avatar-upload")
656
+ # => <input type="file" name="user[avatar]" id="user-avatar" class="avatar-upload">
657
+ #
658
+ # @example Accepted MIME Types
659
+ # f.file_field("user.resume", accept: "application/pdf,application/ms-word")
660
+ # => <input type="file" name="user[resume]" id="user-resume" accept="application/pdf,application/ms-word">
661
+ #
662
+ # f.file_field("user.resume", accept: ["application/pdf", "application/ms-word"])
663
+ # => <input type="file" name="user[resume]" id="user-resume" accept="application/pdf,application/ms-word">
664
+ #
665
+ # @example Accept multiple file uploads
666
+ # f.file_field("user.resume", multiple: true)
667
+ # => <input type="file" name="user[resume]" id="user-resume" multiple="multiple">
668
+ #
669
+ # @api public
670
+ # @since 2.1.0
671
+ def file_field(name, **attributes)
672
+ form_attributes[:enctype] = "multipart/form-data"
673
+
674
+ attributes[:accept] = Array(attributes[:accept]).join(ACCEPT_SEPARATOR) if attributes.key?(:accept)
675
+ attributes = {type: :file, name: _input_name(name), id: _input_id(name), **attributes}
676
+
677
+ input(**attributes)
678
+ end
679
+
680
+ # Returns a number input tag.
681
+ #
682
+ # For this tag, you can make use of the `max`, `min`, and `step` HTML attributes.
683
+ #
684
+ # @param name [String] the input name
685
+ # @param attributes [Hash] the tag's HTML attributes
686
+ #
687
+ # @return [String] the tag
688
+ #
689
+ # @example Basic usage
690
+ # f.number_field("book.percent_read")
691
+ # => <input type="number" name="book[percent_read]" id="book-percent-read" value="">
692
+ #
693
+ # @example Advanced attributes
694
+ # f.number_field("book.percent_read", min: 1, max: 100, step: 1)
695
+ # => <input type="number" name="book[percent_read]" id="book-percent-read" value="" min="1" max="100" step="1">
696
+ #
697
+ # @api public
698
+ # @since 2.1.0
699
+ def number_field(name, **attributes)
700
+ input(**_attributes(:number, name, attributes))
701
+ end
702
+
703
+ # Returns a range input tag.
704
+ #
705
+ # For this tag, you can make use of the `max`, `min`, and `step` HTML attributes.
706
+ #
707
+ # @param name [String] the input name
708
+ # @param attributes [Hash] the tag's HTML attributes
709
+ #
710
+ # @return [String] the tag
711
+ #
712
+ # @example Basic usage
713
+ # f.range_field("book.discount_percentage")
714
+ # => <input type="range" name="book[discount_percentage]" id="book-discount-percentage" value="">
715
+ #
716
+ # @example Advanced attributes
717
+ # f.range_field("book.discount_percentage", min: 1, max: 1'0, step: 1)
718
+ # => <input type="number" name="book[discount_percentage]" id="book-discount-percentage" value="" min="1" max="100" step="1">
719
+ #
720
+ # @api public
721
+ # @since 2.1.0
722
+ def range_field(name, **attributes)
723
+ input(**_attributes(:range, name, attributes))
724
+ end
725
+
726
+ # Returns a textarea tag.
727
+ #
728
+ # @param name [String] the input name
729
+ # @param content [String] the content of the textarea
730
+ # @param attributes [Hash] the tag's HTML attributes
731
+ #
732
+ # @return [String] the tag
733
+ #
734
+ # @example Basic usage
735
+ # f.text_area("user.hobby")
736
+ # => <textarea name="user[hobby]" id="user-hobby"></textarea>
737
+ #
738
+ # f.text_area "user.hobby", "Football"
739
+ # =>
740
+ # <textarea name="user[hobby]" id="user-hobby">
741
+ # Football</textarea>
742
+ #
743
+ # @example HTML attributes
744
+ # f.text_area "user.hobby", class: "form-control"
745
+ # => <textarea name="user[hobby]" id="user-hobby" class="form-control"></textarea>
746
+ #
747
+ # @api public
748
+ # @since 2.1.0
749
+ def text_area(name, content = nil, **attributes)
750
+ if content.respond_to?(:to_hash)
751
+ attributes = content
752
+ content = nil
753
+ end
754
+
755
+ attributes = {name: _input_name(name), id: _input_id(name), **attributes}
756
+ tag.textarea(content || _value(name), **attributes)
757
+ end
758
+
759
+ # Returns a text input tag.
760
+ #
761
+ # @param name [String] the input name
762
+ # @param attributes [Hash] the tag's HTML attributes
763
+ #
764
+ # @return [String] the tag
765
+ #
766
+ # @example Basic usage
767
+ # f.text_field("user.first_name")
768
+ # => <input type="text" name="user[first_name]" id="user-first-name" value="">
769
+ #
770
+ # @example HTML Attributes
771
+ # f.text_field("user.first_name", class: "form-control")
772
+ # => <input type="text" name="user[first_name]" id="user-first-name" value="" class="form-control">
773
+ #
774
+ # @api public
775
+ # @since 2.1.0
776
+ def text_field(name, **attributes)
777
+ input(**_attributes(:text, name, attributes))
778
+ end
779
+ alias_method :input_text, :text_field
780
+
781
+ # Returns a search input tag.
782
+ #
783
+ # @param name [String] the input name
784
+ # @param attributes [Hash] the tag's HTML attributes
785
+ #
786
+ # @return [String] the tag
787
+ #
788
+ # @example Basic usage
789
+ # f.search_field("search.q")
790
+ # => <input type="search" name="search[q]" id="search-q" value="">
791
+ #
792
+ # @example HTML Attributes
793
+ # f.search_field("search.q", class: "form-control")
794
+ # => <input type="search" name="search[q]" id="search-q" value="" class="form-control">
795
+ #
796
+ # @api public
797
+ # @since 2.1.0
798
+ def search_field(name, **attributes)
799
+ input(**_attributes(:search, name, attributes))
800
+ end
801
+
802
+ # Returns a radio input tag.
803
+ #
804
+ # When editing a resource, the form automatically assigns the `checked` HTML attribute for
805
+ # the tag.
806
+ #
807
+ # @param name [String] the input name
808
+ # @param value [String] the input value
809
+ # @param attributes [Hash] the tag's HTML attributes
810
+ #
811
+ # @return [String] the tag
812
+ #
813
+ # @example Basic usage
814
+ # f.radio_button("book.category", "Fiction")
815
+ # f.radio_button("book.category", "Non-Fiction")
816
+ #
817
+ # =>
818
+ # <input type="radio" name="book[category]" value="Fiction">
819
+ # <input type="radio" name="book[category]" value="Non-Fiction">
820
+ #
821
+ # @example HTML Attributes
822
+ # f.radio_button("book.category", "Fiction", class: "form-check")
823
+ # f.radio_button("book.category", "Non-Fiction", class: "form-check")
824
+ #
825
+ # =>
826
+ # <input type="radio" name="book[category]" value="Fiction" class="form-check">
827
+ # <input type="radio" name="book[category]" value="Non-Fiction" class="form-check">
828
+ #
829
+ # @example Automatic checked value
830
+ # # Given the request params:
831
+ # # {book: {category: "Non-Fiction"}}
832
+ # f.radio_button("book.category", "Fiction")
833
+ # f.radio_button("book.category", "Non-Fiction")
834
+ #
835
+ # =>
836
+ # <input type="radio" name="book[category]" value="Fiction">
837
+ # <input type="radio" name="book[category]" value="Non-Fiction" checked="checked">
838
+ #
839
+ # @api public
840
+ # @since 2.1.0
841
+ def radio_button(name, value, **attributes)
842
+ attributes = {type: :radio, name: _input_name(name), value: value, **attributes}
843
+ attributes[:checked] = true if _value(name).to_s == value.to_s
844
+
845
+ input(**attributes)
846
+ end
847
+
848
+ # Returns a password input tag.
849
+ #
850
+ # @param name [String] the input name
851
+ # @param attributes [Hash] the tag's HTML attributes
852
+ #
853
+ # @return [String] the tag
854
+ #
855
+ # @example Basic usage
856
+ # f.password_field("signup.password")
857
+ # => <input type="password" name="signup[password]" id="signup-password" value="">
858
+ #
859
+ # @api public
860
+ # @since 2.1.0
861
+ def password_field(name, **attributes)
862
+ attrs = {type: :password, name: _input_name(name), id: _input_id(name), value: nil, **attributes}
863
+ attrs[:value] = EMPTY_STRING if attrs[:value].nil?
864
+
865
+ input(**attrs)
866
+ end
867
+
868
+ # Returns a select input tag containing option tags for the given values.
869
+ #
870
+ # The values should be an enumerable of pairs of content (the displayed text for the option)
871
+ # and value (the value for the option) strings.
872
+ #
873
+ # When editing a resource, automatically assigns the `selected` HTML attribute for any
874
+ # option tags matching the resource's values.
875
+ #
876
+ # @param name [String] the input name
877
+ # @param values [Hash] a Hash to generate `<option>` tags.
878
+ # @param attributes [Hash] the tag's HTML attributes
879
+ #
880
+ # @return [String] the tag
881
+ #
882
+ # @example Basic usage
883
+ # values = {"Italy" => "it", "Australia" => "au"}
884
+ # f.select("book.store", values)
885
+ #
886
+ # =>
887
+ # <select name="book[store]" id="book-store">
888
+ # <option value="it">Italy</option>
889
+ # <option value="au">Australia</option>
890
+ # </select>
891
+ #
892
+ # @example HTML Attributes
893
+ # values = {"Italy" => "it", "Australia" => "au"}
894
+ # f.select("book.store", values, class: "form-control")
895
+ #
896
+ # =>
897
+ # <select name="book[store]" id="book-store" class="form-control">
898
+ # <option value="it">Italy</option>
899
+ # <option value="au">Australia</option>
900
+ # </select>
901
+ #
902
+ # @example Selected options
903
+ # # Given the following request params:
904
+ # # {book: {store: "it"}}
905
+ # values = {"Italy" => "it", "Australia" => "au"}
906
+ # f.select("book.store", values)
907
+ #
908
+ # =>
909
+ # <select name="book[store]" id="book-store">
910
+ # <option value="it" selected="selected">Italy</option>
911
+ # <option value="au">Australia</option>
912
+ # </select>
913
+ #
914
+ # @example Prompt option
915
+ # values = {"Italy" => "it", "Australia" => "au"}
916
+ # f.select("book.store", values, options: {prompt: "Select a store"})
917
+ #
918
+ # =>
919
+ # <select name="book[store]" id="book-store">
920
+ # <option>Select a store</option>
921
+ # <option value="it">Italy</option>
922
+ # <option value="au">Australia</option>
923
+ # </select>
924
+ #
925
+ # @example Selected option
926
+ # values = {"Italy" => "it", "Australia" => "au"}
927
+ # f.select("book.store", values, options: {selected: "it"})
928
+ #
929
+ # =>
930
+ # <select name="book[store]" id="book-store">
931
+ # <option value="it" selected="selected">Italy</option>
932
+ # <option value="au">Australia</option>
933
+ # </select>
934
+ #
935
+ # @example Prompt option and HTML attributes
936
+ # values = {"Italy" => "it", "Australia" => "au"}
937
+ # f.select("book.store", values, options: {prompt: "Select a store"}, class: "form-control")
938
+ #
939
+ # =>
940
+ # <select name="book[store]" id="book-store" class="form-control">
941
+ # <option>Select a store</option>
942
+ # <option value="it">Italy</option>
943
+ # <option value="au">Australia</option>
944
+ # </select>
945
+ #
946
+ # @example Multiple select
947
+ # values = {"Italy" => "it", "Australia" => "au"}
948
+ # f.select("book.stores", values, multiple: true)
949
+ #
950
+ # =>
951
+ # <select name="book[store][]" id="book-store" multiple="multiple">
952
+ # <option value="it">Italy</option>
953
+ # <option value="au">Australia</option>
954
+ # </select>
955
+ #
956
+ # @example Multiple select and HTML attributes
957
+ # values = {"Italy" => "it", "Australia" => "au"}
958
+ # f.select("book.stores", values, multiple: true, class: "form-control")
959
+ #
960
+ # =>
961
+ # <select name="book[store][]" id="book-store" multiple="multiple" class="form-control">
962
+ # <option value="it">Italy</option>
963
+ # <option value="au">Australia</option>
964
+ # </select>
965
+ #
966
+ # @example Values as an array, supporting repeated entries
967
+ # values = [["Italy", "it"],
968
+ # ["---", ""],
969
+ # ["Afghanistan", "af"],
970
+ # ...
971
+ # ["Italy", "it"],
972
+ # ...
973
+ # ["Zimbabwe", "zw"]]
974
+ # f.select("book.stores", values)
975
+ #
976
+ # =>
977
+ # <select name="book[store]" id="book-store">
978
+ # <option value="it">Italy</option>
979
+ # <option value="">---</option>
980
+ # <option value="af">Afghanistan</option>
981
+ # ...
982
+ # <option value="it">Italy</option>
983
+ # ...
984
+ # <option value="zw">Zimbabwe</option>
985
+ # </select>
986
+ #
987
+ # @api public
988
+ # @since 2.1.0
989
+ def select(name, values, **attributes) # rubocop:disable Metrics/AbcSize
990
+ options = attributes.delete(:options) { {} }
991
+ multiple = attributes[:multiple]
992
+ attributes = {name: _select_input_name(name, multiple), id: _input_id(name), **attributes}
993
+ prompt = options.delete(:prompt)
994
+ selected = options.delete(:selected)
995
+ input_value = _value(name)
996
+
997
+ option_tags = []
998
+ option_tags << tag.option(prompt) if prompt
999
+
1000
+ already_selected = nil
1001
+ values.each do |content, value|
1002
+ if (multiple || !already_selected) &&
1003
+ (already_selected = _select_option_selected?(value, selected, input_value, multiple))
1004
+ option_tags << tag.option(content, value: value, selected: true, **options)
1005
+ else
1006
+ option_tags << tag.option(content, value: value, **options)
1007
+ end
1008
+ end
1009
+
1010
+ tag.select(option_tags.join.html_safe, **attributes)
1011
+ end
1012
+
1013
+ # Returns a datalist input tag.
1014
+ #
1015
+ # @param name [String] the input name
1016
+ # @param values [Array,Hash] a collection that is transformed into `<option>` tags
1017
+ # @param list [String] the name of list for the text input; also the id of datalist
1018
+ # @param attributes [Hash] the tag's HTML attributes
1019
+ #
1020
+ # @return [String] the tag
1021
+ #
1022
+ # @example Basic Usage
1023
+ # values = ["Italy", "Australia"]
1024
+ # f.datalist("book.stores", values, "books")
1025
+ #
1026
+ # =>
1027
+ # <input type="text" name="book[store]" id="book-store" value="" list="books">
1028
+ # <datalist id="books">
1029
+ # <option value="Italy"></option>
1030
+ # <option value="Australia"></option>
1031
+ # </datalist>
1032
+ #
1033
+ # @example Options As Hash
1034
+ # values = Hash["Italy" => "it", "Australia" => "au"]
1035
+ # f.datalist("book.stores", values, "books")
1036
+ #
1037
+ # =>
1038
+ # <input type="text" name="book[store]" id="book-store" value="" list="books">
1039
+ # <datalist id="books">
1040
+ # <option value="Italy">it</option>
1041
+ # <option value="Australia">au</option>
1042
+ # </datalist>
1043
+ #
1044
+ # @example Specifying custom attributes for the datalist input
1045
+ # values = ["Italy", "Australia"]
1046
+ # f.datalist "book.stores", values, "books", datalist: {class: "form-control"}
1047
+ #
1048
+ # =>
1049
+ # <input type="text" name="book[store]" id="book-store" value="" list="books">
1050
+ # <datalist id="books" class="form-control">
1051
+ # <option value="Italy"></option>
1052
+ # <option value="Australia"></option>
1053
+ # </datalist>
1054
+ #
1055
+ # @example Specifying custom attributes for the options list
1056
+ # values = ["Italy", "Australia"]
1057
+ # f.datalist("book.stores", values, "books", options: {class: "form-control"})
1058
+ #
1059
+ # =>
1060
+ # <input type="text" name="book[store]" id="book-store" value="" list="books">
1061
+ # <datalist id="books">
1062
+ # <option value="Italy" class="form-control"></option>
1063
+ # <option value="Australia" class="form-control"></option>
1064
+ # </datalist>
1065
+ #
1066
+ # @api public
1067
+ # @since 2.1.0
1068
+ def datalist(name, values, list, **attributes)
1069
+ options = attributes.delete(:options) || {}
1070
+ datalist = attributes.delete(:datalist) || {}
1071
+
1072
+ attributes[:list] = list
1073
+ datalist[:id] = list
1074
+
1075
+ (+"").tap { |output|
1076
+ output << text_field(name, **attributes)
1077
+ output << tag.datalist(**datalist) {
1078
+ (+"").tap { |inner|
1079
+ values.each do |value, content|
1080
+ inner << tag.option(content, value: value, **options)
1081
+ end
1082
+ }.html_safe
1083
+ }
1084
+ }.html_safe
1085
+ end
1086
+
1087
+ # Returns a button tag.
1088
+ #
1089
+ # @return [String] the tag
1090
+ #
1091
+ # @overload button(content, **attributes)
1092
+ # Returns a button tag with the given content.
1093
+ #
1094
+ # @param content [String] the content for the tag
1095
+ # @param attributes [Hash] the tag's HTML attributes
1096
+ #
1097
+ # @overload button(**attributes, &block)
1098
+ # Returns a button tag with the return value of the given block as the tag's content.
1099
+ #
1100
+ # @param attributes [Hash] the tag's HTML attributes
1101
+ # @yieldreturn [String] the tag content
1102
+ #
1103
+ # @example Basic usage
1104
+ # f.button("Click me")
1105
+ # => <button>Click me</button>
1106
+ #
1107
+ # @example HTML Attributes
1108
+ # f.button("Click me", class: "btn btn-secondary")
1109
+ # => <button class="btn btn-secondary">Click me</button>
1110
+ #
1111
+ # @example Returning content from a block
1112
+ # <%= f.button class: "btn btn-secondary" do %>
1113
+ # <span class="oi oi-check">
1114
+ # <% end %>
1115
+ #
1116
+ # =>
1117
+ # <button class="btn btn-secondary">
1118
+ # <span class="oi oi-check"></span>
1119
+ # </button>
1120
+ #
1121
+ # @api public
1122
+ # @since 2.1.0
1123
+ def button(...)
1124
+ tag.button(...)
1125
+ end
1126
+
1127
+ # Returns an image input tag, to be used as a visual button for the form.
1128
+ #
1129
+ # For security reasons, you should use the absolute URL of the given image.
1130
+ #
1131
+ # @param source [String] The absolute URL of the image
1132
+ # @param attributes [Hash] the tag's HTML attributes
1133
+ #
1134
+ # @return [String] the tag
1135
+ #
1136
+ # @example Basic usage
1137
+ # f.image_button("https://hanamirb.org/assets/button.png")
1138
+ # => <input type="image" src="https://hanamirb.org/assets/button.png">
1139
+ #
1140
+ # @example HTML Attributes
1141
+ # f.image_button("https://hanamirb.org/assets/button.png", name: "image", width: "50")
1142
+ # => <input name="image" width="50" type="image" src="https://hanamirb.org/assets/button.png">
1143
+ #
1144
+ # @api public
1145
+ # @since 2.1.0
1146
+ def image_button(source, **attributes)
1147
+ attributes[:type] = :image
1148
+ attributes[:src] = sanitize_url(source)
1149
+
1150
+ input(**attributes)
1151
+ end
1152
+
1153
+ # Returns a submit button tag.
1154
+ #
1155
+ # @return [String] the tag
1156
+ #
1157
+ # @overload submit(content, **attributes)
1158
+ # Returns a submit button tag with the given content.
1159
+ #
1160
+ # @param content [String] the content for the tag
1161
+ # @param attributes [Hash] the tag's HTML attributes
1162
+ #
1163
+ # @overload submit(**attributes, &blk)
1164
+ # Returns a submit button tag with the return value of the given block as the tag's
1165
+ # content.
1166
+ #
1167
+ # @param attributes [Hash] the tag's HTML attributes
1168
+ # @yieldreturn [String] the tag content
1169
+ #
1170
+ # @example Basic usage
1171
+ # f.submit("Create")
1172
+ # => <button type="submit">Create</button>
1173
+ #
1174
+ # @example HTML Attributes
1175
+ # f.submit("Create", class: "btn btn-primary")
1176
+ # => <button type="submit" class="btn btn-primary">Create</button>
1177
+ #
1178
+ # @example Returning content from a block
1179
+ # <%= f.submit(class: "btn btn-primary") do %>
1180
+ # <span class="oi oi-check">
1181
+ # <% end %>
1182
+ #
1183
+ # =>
1184
+ # <button type="submit" class="btn btn-primary">
1185
+ # <span class="oi oi-check"></span>
1186
+ # </button>
1187
+ #
1188
+ # @api public
1189
+ # @since 2.1.0
1190
+ def submit(content = nil, **attributes, &blk)
1191
+ if content.is_a?(::Hash)
1192
+ attributes = content
1193
+ content = nil
1194
+ end
1195
+
1196
+ attributes = {type: :submit, **attributes}
1197
+ tag.button(content, **attributes, &blk)
1198
+ end
1199
+
1200
+ # Returns an input tag.
1201
+ #
1202
+ # Generates an input tag without any special handling. For more convenience and other
1203
+ # advanced features, see the other methods of the form builder.
1204
+ #
1205
+ # @param attributes [Hash] the tag's HTML attributes
1206
+ # @yieldreturn [String] the tag content
1207
+ #
1208
+ # @return [String] the tag
1209
+ #
1210
+ # @since 2.1.0
1211
+ # @api public
1212
+ #
1213
+ # @example Basic usage
1214
+ # f.input(type: :text, name: "book[title]", id: "book-title", value: book.title)
1215
+ # => <input type="text" name="book[title]" id="book-title" value="Hanami book">
1216
+ #
1217
+ # @api public
1218
+ # @since 2.1.0
1219
+ def input(...)
1220
+ tag.input(...)
1221
+ end
1222
+
1223
+ private
1224
+
1225
+ # @api private
1226
+ # @since 2.1.0
1227
+ def _form_method(attributes)
1228
+ attributes[:method] ||= DEFAULT_METHOD
1229
+ attributes[:method] = attributes[:method].to_s.upcase
1230
+
1231
+ original_form_method = attributes[:method]
1232
+
1233
+ if (method_override = !BROWSER_METHODS.include?(attributes[:method]))
1234
+ attributes[:method] = DEFAULT_METHOD
1235
+ end
1236
+
1237
+ [method_override, original_form_method]
1238
+ end
1239
+
1240
+ # @api private
1241
+ # @since 2.1.0
1242
+ def _csrf_token(values, attributes)
1243
+ return [] if values.csrf_token.nil?
1244
+
1245
+ return [] if EXCLUDED_CSRF_METHODS.include?(attributes[:method])
1246
+
1247
+ [true, values.csrf_token]
1248
+ end
1249
+
1250
+ # @api private
1251
+ # @since 2.1.0
1252
+ def _attributes(type, name, attributes)
1253
+ attrs = {
1254
+ type: type,
1255
+ name: _input_name(name),
1256
+ id: _input_id(name),
1257
+ value: _value(name)
1258
+ }
1259
+ attrs.merge!(attributes)
1260
+ attrs[:value] = escape_html(attrs[:value]).html_safe
1261
+ attrs
1262
+ end
1263
+
1264
+ # @api private
1265
+ # @since 2.1.0
1266
+ def _input_name(name)
1267
+ tokens = _split_input_name(name)
1268
+ result = tokens.shift
1269
+
1270
+ tokens.each do |t|
1271
+ if t =~ %r{\A\d+\z}
1272
+ result << "[]"
1273
+ else
1274
+ result << "[#{t}]"
1275
+ end
1276
+ end
1277
+
1278
+ result
1279
+ end
1280
+
1281
+ # @api private
1282
+ # @since 2.1.0
1283
+ def _input_id(name)
1284
+ [base_name, name].compact.join(INPUT_NAME_SEPARATOR).to_s.tr("._", "-")
1285
+ end
1286
+
1287
+ # @api private
1288
+ # @since 2.1.0
1289
+ def _value(name)
1290
+ values.get(*_split_input_name(name).map(&:to_sym))
1291
+ end
1292
+
1293
+ # @api private
1294
+ # @since 2.1.0
1295
+ def _split_input_name(name)
1296
+ [
1297
+ *base_name.to_s.split(INPUT_NAME_SEPARATOR),
1298
+ *name.to_s.split(INPUT_NAME_SEPARATOR)
1299
+ ].compact
1300
+ end
1301
+
1302
+ # @api private
1303
+ # @since 2.1.0
1304
+ #
1305
+ # @see #check_box
1306
+ def _hidden_field_for_check_box(name, attributes)
1307
+ return unless attributes[:value].nil? || !attributes[:unchecked_value].nil?
1308
+
1309
+ input(
1310
+ type: :hidden,
1311
+ name: attributes[:name] || _input_name(name),
1312
+ value: (attributes.delete(:unchecked_value) || DEFAULT_UNCHECKED_VALUE).to_s
1313
+ )
1314
+ end
1315
+
1316
+ # @api private
1317
+ # @since 2.1.0
1318
+ #
1319
+ # @see #check_box
1320
+ def _attributes_for_check_box(name, attributes)
1321
+ attributes = {
1322
+ type: :checkbox,
1323
+ name: _input_name(name),
1324
+ id: _input_id(name),
1325
+ value: (attributes.delete(:checked_value) || DEFAULT_CHECKED_VALUE).to_s,
1326
+ **attributes
1327
+ }
1328
+
1329
+ attributes[:checked] = true if _check_box_checked?(attributes[:value], _value(name))
1330
+
1331
+ attributes
1332
+ end
1333
+
1334
+ # @api private
1335
+ # @since 1.2.0
1336
+ def _select_input_name(name, multiple)
1337
+ select_name = _input_name(name)
1338
+ select_name = "#{select_name}[]" if multiple
1339
+ select_name
1340
+ end
1341
+
1342
+ # @api private
1343
+ # @since 1.2.0
1344
+ def _select_option_selected?(value, selected, input_value, multiple)
1345
+ if input_value && selected.nil?
1346
+ value.to_s == input_value.to_s
1347
+ else
1348
+ (value == selected) ||
1349
+ _is_current_value?(input_value, value) ||
1350
+ _is_in_selected_values?(multiple, selected, value) ||
1351
+ _is_in_input_values?(multiple, input_value, value)
1352
+ end
1353
+ end
1354
+
1355
+ # @api private
1356
+ # @since 1.2.0
1357
+ def _is_current_value?(input_value, value)
1358
+ return unless input_value
1359
+
1360
+ value.to_s == input_value.to_s
1361
+ end
1362
+
1363
+ # @api private
1364
+ # @since 1.2.0
1365
+ def _is_in_selected_values?(multiple, selected, value)
1366
+ return unless multiple && selected.is_a?(Array)
1367
+
1368
+ selected.include?(value)
1369
+ end
1370
+
1371
+ # @api private
1372
+ # @since 1.2.0
1373
+ def _is_in_input_values?(multiple, input_value, value)
1374
+ return unless multiple && input_value.is_a?(Array)
1375
+
1376
+ input_value.include?(value)
1377
+ end
1378
+
1379
+ # @api private
1380
+ # @since 1.2.0
1381
+ def _check_box_checked?(value, input_value)
1382
+ !input_value.nil? &&
1383
+ (input_value.to_s == value.to_s || input_value.is_a?(TrueClass) ||
1384
+ (input_value.is_a?(Array) && input_value.include?(value)))
1385
+ end
1386
+ end
1387
+ end
1388
+ end
1389
+ end