hanami-helpers 0.0.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,911 @@
1
+ require 'hanami/helpers/form_helper/html_node'
2
+ require 'hanami/helpers/form_helper/values'
3
+ require 'hanami/helpers/html_helper/html_builder'
4
+ require 'hanami/utils/string'
5
+
6
+ module Hanami
7
+ module Helpers
8
+ module FormHelper
9
+ # Form builder
10
+ #
11
+ # @since 0.2.0
12
+ #
13
+ # @see Hanami::Helpers::HtmlHelper::HtmlBuilder
14
+ class FormBuilder < ::Hanami::Helpers::HtmlHelper::HtmlBuilder
15
+ # Set of HTTP methods that are understood by web browsers
16
+ #
17
+ # @since 0.2.0
18
+ # @api private
19
+ BROWSER_METHODS = ['GET', 'POST'].freeze
20
+
21
+ # Set of HTTP methods that should NOT generate CSRF token
22
+ #
23
+ # @since 0.2.0
24
+ # @api private
25
+ EXCLUDED_CSRF_METHODS = ['GET'].freeze
26
+
27
+ # Checked attribute value
28
+ #
29
+ # @since 0.2.0
30
+ # @api private
31
+ #
32
+ # @see Hanami::Helpers::FormHelper::FormBuilder#radio_button
33
+ CHECKED = 'checked'.freeze
34
+
35
+ # Selected attribute value for option
36
+ #
37
+ # @since 0.2.0
38
+ # @api private
39
+ #
40
+ # @see Hanami::Helpers::FormHelper::FormBuilder#select
41
+ SELECTED = 'selected'.freeze
42
+
43
+ # Separator for accept attribute of file input
44
+ #
45
+ # @since 0.2.0
46
+ # @api private
47
+ #
48
+ # @see Hanami::Helpers::FormHelper::FormBuilder#file_input
49
+ ACCEPT_SEPARATOR = ','.freeze
50
+
51
+ # Replacement for input id interpolation
52
+ #
53
+ # @since 0.2.0
54
+ # @api private
55
+ #
56
+ # @see Hanami::Helpers::FormHelper::FormBuilder#_input_id
57
+ INPUT_ID_REPLACEMENT = '-\k<token>'.freeze
58
+
59
+ # Replacement for input value interpolation
60
+ #
61
+ # @since 0.2.0
62
+ # @api private
63
+ #
64
+ # @see Hanami::Helpers::FormHelper::FormBuilder#_value
65
+ INPUT_VALUE_REPLACEMENT = '.\k<token>'.freeze
66
+
67
+ # Default value for unchecked check box
68
+ #
69
+ # @since 0.2.0
70
+ # @api private
71
+ #
72
+ # @see Hanami::Helpers::FormHelper::FormBuilder#check_box
73
+ DEFAULT_UNCHECKED_VALUE = '0'.freeze
74
+
75
+ # Default value for checked check box
76
+ #
77
+ # @since 0.2.0
78
+ # @api private
79
+ #
80
+ # @see Hanami::Helpers::FormHelper::FormBuilder#check_box
81
+ DEFAULT_CHECKED_VALUE = '1'.freeze
82
+
83
+ # ENCTYPE_MULTIPART = 'multipart/form-data'.freeze
84
+
85
+ self.html_node = ::Hanami::Helpers::FormHelper::HtmlNode
86
+
87
+ # Instantiate a form builder
88
+ #
89
+ # @overload initialize(form, attributes, params, &blk)
90
+ # Top level form
91
+ # @param form [Hanami::Helpers:FormHelper::Form] the form
92
+ # @param attributes [::Hash] a set of HTML attributes
93
+ # @param params [Hanami::Action::Params] request params
94
+ # @param blk [Proc] a block that describes the contents of the form
95
+ #
96
+ # @overload initialize(form, attributes, params, &blk)
97
+ # Nested form
98
+ # @param form [Hanami::Helpers:FormHelper::Form] the form
99
+ # @param attributes [Hanami::Helpers::FormHelper::Values] user defined
100
+ # values
101
+ # @param blk [Proc] a block that describes the contents of the form
102
+ #
103
+ # @return [Hanami::Helpers::FormHelper::FormBuilder] the form builder
104
+ #
105
+ # @since 0.2.0
106
+ # @api private
107
+ def initialize(form, attributes, context = nil, &blk)
108
+ super()
109
+
110
+ @context = context
111
+ @blk = blk
112
+
113
+ # Nested form
114
+ if @context.nil? && attributes.is_a?(Values)
115
+ @values = attributes
116
+ @attributes = {}
117
+ @name = form
118
+ else
119
+ @form = form
120
+ @name = form.name
121
+ @values = Values.new(form.values, @context.params)
122
+ @attributes = attributes
123
+ @verb_method = verb_method
124
+ @csrf_token = csrf_token
125
+ end
126
+ end
127
+
128
+ # Resolves all the nodes and generates the markup
129
+ #
130
+ # @return [Hanami::Utils::Escape::SafeString] the output
131
+ #
132
+ # @since 0.2.0
133
+ # @api private
134
+ #
135
+ # @see Hanami::Helpers::HtmlHelper::HtmlBuilder#to_s
136
+ # @see http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/Escape/SafeString
137
+ def to_s
138
+ if toplevel?
139
+ _method_override!
140
+ form(@blk, @attributes)
141
+ end
142
+
143
+ super
144
+ end
145
+
146
+ # Nested fields
147
+ #
148
+ # The inputs generated by the wrapped block will be prefixed with the given name
149
+ # It supports infinite levels of nesting.
150
+ #
151
+ # @param name [Symbol] the nested name, it's used to generate input
152
+ # names, ids, and to lookup params to fill values.
153
+ #
154
+ # @since 0.2.0
155
+ #
156
+ # @example Basic usage
157
+ # <%=
158
+ # form_for :delivery, routes.deliveries_path do
159
+ # text_field :customer_name
160
+ #
161
+ # fields_for :address do
162
+ # text_field :street
163
+ # end
164
+ #
165
+ # submit 'Create'
166
+ # end
167
+ # %>
168
+ #
169
+ # Output:
170
+ # # <form action="/deliveries" method="POST" accept-charset="utf-8" id="delivery-form">
171
+ # # <input type="text" name="delivery[customer_name]" id="delivery-customer-name" value="">
172
+ # # <input type="text" name="delivery[address][street]" id="delivery-address-street" value="">
173
+ # #
174
+ # # <button type="submit">Create</button>
175
+ # # </form>
176
+ #
177
+ # @example Multiple levels of nesting
178
+ # <%=
179
+ # form_for :delivery, routes.deliveries_path do
180
+ # text_field :customer_name
181
+ #
182
+ # fields_for :address do
183
+ # text_field :street
184
+ #
185
+ # fields_for :location do
186
+ # text_field :city
187
+ # text_field :country
188
+ # end
189
+ # end
190
+ #
191
+ # submit 'Create'
192
+ # end
193
+ # %>
194
+ #
195
+ # Output:
196
+ # # <form action="/deliveries" method="POST" accept-charset="utf-8" id="delivery-form">
197
+ # # <input type="text" name="delivery[customer_name]" id="delivery-customer-name" value="">
198
+ # # <input type="text" name="delivery[address][street]" id="delivery-address-street" value="">
199
+ # # <input type="text" name="delivery[address][location][city]" id="delivery-address-location-city" value="">
200
+ # # <input type="text" name="delivery[address][location][country]" id="delivery-address-location-country" value="">
201
+ # #
202
+ # # <button type="submit">Create</button>
203
+ # # </form>
204
+ def fields_for(name)
205
+ current_name = @name
206
+ @name = _input_name(name)
207
+ yield
208
+ ensure
209
+ @name = current_name
210
+ end
211
+
212
+ # Label tag
213
+ #
214
+ # The first param <tt>content</tt> can be a <tt>Symbol</tt> that represents
215
+ # the target field (Eg. <tt>:extended_title</tt>), or a <tt>String</tt>
216
+ # which is used as it is.
217
+ #
218
+ # @param content [Symbol,String] the field name or a content string
219
+ # @param attributes [Hash] HTML attributes to pass to the label tag
220
+ #
221
+ # @since 0.2.0
222
+ #
223
+ # @example Basic usage
224
+ # <%=
225
+ # # ...
226
+ # label :extended_title
227
+ # %>
228
+ #
229
+ # # Output:
230
+ # # <label for="book-extended-title">Extended title</label>
231
+ #
232
+ # @example Custom content
233
+ # <%=
234
+ # # ...
235
+ # label 'Title', for: :extended_title
236
+ # %>
237
+ #
238
+ # # Output:
239
+ # # <label for="book-extended-title">Title</label>
240
+ #
241
+ # @example Custom "for" attribute
242
+ # <%=
243
+ # # ...
244
+ # label :extended_title, for: 'ext-title'
245
+ # %>
246
+ #
247
+ # # Output:
248
+ # # <label for="ext-title">Extended title</label>
249
+ #
250
+ # @example Nested fields usage
251
+ # <%=
252
+ # # ...
253
+ # fields_for :address do
254
+ # label :city
255
+ # text_field :city
256
+ # end
257
+ # %>
258
+ #
259
+ # # Output:
260
+ # # <label for="delivery-address-city">City</label>
261
+ # # <input type="text" name="delivery[address][city] id="delivery-address-city" value="">
262
+ def label(content, attributes = {})
263
+ attributes = { for: _for(content, attributes.delete(:for)) }.merge(attributes)
264
+ content = case content
265
+ when String, Hanami::Utils::String
266
+ content
267
+ else
268
+ Utils::String.new(content).capitalize
269
+ end
270
+
271
+ super(content, attributes)
272
+ end
273
+
274
+ # Check box
275
+ #
276
+ # It renders a check box input.
277
+ #
278
+ # When a form is submitted, browsers don't send the value of unchecked
279
+ # check boxes. If an user unchecks a check box, their browser won't send
280
+ # the unchecked value. On the server side the corresponding value is
281
+ # missing, so the application will assume that the user action never
282
+ # happened.
283
+ #
284
+ # To solve this problem the form renders a hidden field with the
285
+ # "unchecked value". When the user unchecks the input, the browser will
286
+ # ignore it, but it will still send the value of the hidden input. See
287
+ # the examples below.
288
+ #
289
+ # When editing a resource, the form automatically assigns the
290
+ # <tt>checked="checked"</tt> attribute.
291
+ #
292
+ # @param name [Symbol] the input name
293
+ # @param attributes [Hash] HTML attributes to pass to the input tag
294
+ # @option attributes [String] :checked_value (defaults to "1")
295
+ # @option attributes [String] :unchecked_value (defaults to "0")
296
+ #
297
+ # @since 0.2.0
298
+ #
299
+ # @example Basic usage
300
+ # <%=
301
+ # check_box :free_shipping
302
+ # %>
303
+ #
304
+ # # Output:
305
+ # # <input type="hidden" name="delivery[free_shipping]" value="0">
306
+ # # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1">
307
+ #
308
+ # @example Specify (un)checked values
309
+ # <%=
310
+ # check_box :free_shipping, checked_value: 'true', unchecked_value: 'false'
311
+ # %>
312
+ #
313
+ # # Output:
314
+ # # <input type="hidden" name="delivery[free_shipping]" value="false">
315
+ # # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="true">
316
+ #
317
+ # @example Automatic "checked" attribute
318
+ # # For this example the params are:
319
+ # #
320
+ # # { delivery: { free_shipping: '1' } }
321
+ # <%=
322
+ # check_box :free_shipping
323
+ # %>
324
+ #
325
+ # # Output:
326
+ # # <input type="hidden" name="delivery[free_shipping]" value="0">
327
+ # # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1" checked="checked">
328
+ #
329
+ # @example Force "checked" attribute
330
+ # # For this example the params are:
331
+ # #
332
+ # # { delivery: { free_shipping: '0' } }
333
+ # <%=
334
+ # check_box :free_shipping, checked: 'checked'
335
+ # %>
336
+ #
337
+ # # Output:
338
+ # # <input type="hidden" name="delivery[free_shipping]" value="0">
339
+ # # <input type="checkbox" name="delivery[free_shipping]" id="delivery-free-shipping" value="1" checked="checked">
340
+ #
341
+ # @example Multiple check boxes
342
+ # <%=
343
+ # check_box :languages, name: 'book[languages][]', value: 'italian', id: nil
344
+ # check_box :languages, name: 'book[languages][]', value: 'english', id: nil
345
+ # %>
346
+ #
347
+ # # Output:
348
+ # # <input type="checkbox" name="book[languages][]" value="italian">
349
+ # # <input type="checkbox" name="book[languages][]" value="english">
350
+ #
351
+ # @example Automatic "checked" attribute for multiple check boxes
352
+ # # For this example the params are:
353
+ # #
354
+ # # { book: { languages: ['italian'] } }
355
+ # <%=
356
+ # check_box :languages, name: 'book[languages][]', value: 'italian', id: nil
357
+ # check_box :languages, name: 'book[languages][]', value: 'english', id: nil
358
+ # %>
359
+ #
360
+ # # Output:
361
+ # # <input type="checkbox" name="book[languages][]" value="italian" checked="checked">
362
+ # # <input type="checkbox" name="book[languages][]" value="english">
363
+ def check_box(name, attributes = {})
364
+ _hidden_field_for_check_box( name, attributes)
365
+ input _attributes_for_check_box(name, attributes)
366
+ end
367
+
368
+ # Color input
369
+ #
370
+ # @param name [Symbol] the input name
371
+ # @param attributes [Hash] HTML attributes to pass to the input tag
372
+ #
373
+ # @since 0.2.0
374
+ #
375
+ # @example Basic usage
376
+ # <%=
377
+ # # ...
378
+ # color_field :background
379
+ # %>
380
+ #
381
+ # # Output:
382
+ # # <input type="color" name="user[background]" id="user-background" value="">
383
+ def color_field(name, attributes = {})
384
+ input _attributes(:color, name, attributes)
385
+ end
386
+
387
+ # Date input
388
+ #
389
+ # @param name [Symbol] the input name
390
+ # @param attributes [Hash] HTML attributes to pass to the input tag
391
+ #
392
+ # @since 0.2.0
393
+ #
394
+ # @example Basic usage
395
+ # <%=
396
+ # # ...
397
+ # date_field :birth_date
398
+ # %>
399
+ #
400
+ # # Output:
401
+ # # <input type="date" name="user[birth_date]" id="user-birth-date" value="">
402
+ def date_field(name, attributes = {})
403
+ input _attributes(:date, name, attributes)
404
+ end
405
+
406
+ # Datetime input
407
+ #
408
+ # @param name [Symbol] the input name
409
+ # @param attributes [Hash] HTML attributes to pass to the input tag
410
+ #
411
+ # @since 0.2.0
412
+ #
413
+ # @example Basic usage
414
+ # <%=
415
+ # # ...
416
+ # datetime_field :delivered_at
417
+ # %>
418
+ #
419
+ # # Output:
420
+ # # <input type="datetime" name="delivery[delivered_at]" id="delivery-delivered-at" value="">
421
+ def datetime_field(name, attributes = {})
422
+ input _attributes(:datetime, name, attributes)
423
+ end
424
+
425
+ # Datetime Local input
426
+ #
427
+ # @param name [Symbol] the input name
428
+ # @param attributes [Hash] HTML attributes to pass to the input tag
429
+ #
430
+ # @since 0.2.0
431
+ #
432
+ # @example Basic usage
433
+ # <%=
434
+ # # ...
435
+ # datetime_local_field :delivered_at
436
+ # %>
437
+ #
438
+ # # Output:
439
+ # # <input type="datetime-local" name="delivery[delivered_at]" id="delivery-delivered-at" value="">
440
+ def datetime_local_field(name, attributes = {})
441
+ input _attributes(:'datetime-local', name, attributes)
442
+ end
443
+
444
+ # Email input
445
+ #
446
+ # @param name [Symbol] the input name
447
+ # @param attributes [Hash] HTML attributes to pass to the input tag
448
+ #
449
+ # @since 0.2.0
450
+ #
451
+ # @example Basic usage
452
+ # <%=
453
+ # # ...
454
+ # email_field :email
455
+ # %>
456
+ #
457
+ # # Output:
458
+ # # <input type="email" name="user[email]" id="user-email" value="">
459
+ def email_field(name, attributes = {})
460
+ input _attributes(:email, name, attributes)
461
+ end
462
+
463
+ # Hidden input
464
+ #
465
+ # @param name [Symbol] the input name
466
+ # @param attributes [Hash] HTML attributes to pass to the input tag
467
+ #
468
+ # @since 0.2.0
469
+ #
470
+ # @example Basic usage
471
+ # <%=
472
+ # # ...
473
+ # hidden_field :customer_id
474
+ # %>
475
+ #
476
+ # # Output:
477
+ # # <input type="hidden" name="delivery[customer_id]" id="delivery-customer-id" value="">
478
+ def hidden_field(name, attributes = {})
479
+ input _attributes(:hidden, name, attributes)
480
+ end
481
+
482
+ # File input
483
+ #
484
+ # PLEASE REMEMBER TO ADD <tt>enctype: 'multipart/form-data'</tt> ATTRIBUTE TO THE FORM
485
+ #
486
+ # @param name [Symbol] the input name
487
+ # @param attributes [Hash] HTML attributes to pass to the input tag
488
+ # @option attributes [String,Array] :accept Optional set of accepted MIME Types
489
+ #
490
+ # @since 0.2.0
491
+ #
492
+ # @example Basic usage
493
+ # <%=
494
+ # # ...
495
+ # file_field :avatar
496
+ # %>
497
+ #
498
+ # # Output:
499
+ # # <input type="file" name="user[avatar]" id="user-avatar">
500
+ #
501
+ # @example Accepted mime types
502
+ # <%=
503
+ # # ...
504
+ # file_field :resume, accept: 'application/pdf,application/ms-word'
505
+ # %>
506
+ #
507
+ # # Output:
508
+ # # <input type="file" name="user[resume]" id="user-resume" accept="application/pdf,application/ms-word">
509
+ #
510
+ # @example Accepted mime types (as array)
511
+ # <%=
512
+ # # ...
513
+ # file_field :resume, accept: ['application/pdf', 'application/ms-word']
514
+ # %>
515
+ #
516
+ # # Output:
517
+ # # <input type="file" name="user[resume]" id="user-resume" accept="application/pdf,application/ms-word">
518
+ def file_field(name, attributes = {})
519
+ attributes[:accept] = Array(attributes[:accept]).join(ACCEPT_SEPARATOR) if attributes.key?(:accept)
520
+ attributes = { type: :file, name: _input_name(name), id: _input_id(name) }.merge(attributes)
521
+
522
+ input(attributes)
523
+ end
524
+
525
+ # Number input
526
+ #
527
+ # @param name [Symbol] the input name
528
+ # @param attributes [Hash] HTML attributes to pass to the number input
529
+ #
530
+ # @example Basic usage
531
+ # <%=
532
+ # # ...
533
+ # number_field :percent_read
534
+ # %>
535
+ #
536
+ # # Output:
537
+ # # <input type="number" name="book[percent_read]" id="book-percent-read" value="">
538
+ #
539
+ # You can also make use of the 'max', 'min', and 'step' attributes for
540
+ # the HTML5 number field.
541
+ #
542
+ # @example Advanced attributes
543
+ # <%=
544
+ # # ...
545
+ # number_field :priority, min: 1, max: 10, step: 1
546
+ # %>
547
+ #
548
+ # # Output:
549
+ # # <input type="number" name="book[percent_read]" id="book-precent-read" value="" min="1" max="10" step="1">
550
+ def number_field(name, attributes = {})
551
+ input _attributes(:number, name, attributes)
552
+ end
553
+
554
+ # Text-area input
555
+ #
556
+ # @param name [Symbol] the input name
557
+ # @param content [String] the content of the textarea
558
+ # @param attributes [Hash] HTML attributes to pass to the textarea tag
559
+ #
560
+ # @since 0.2.5
561
+ #
562
+ # @example Basic usage
563
+ # <%=
564
+ # # ...
565
+ # text_area :hobby
566
+ # %>
567
+ #
568
+ # # Output:
569
+ # # <textarea name="user[hobby]" id="user-hobby"></textarea>
570
+ #
571
+ # @example Set content
572
+ # <%=
573
+ # # ...
574
+ # text_area :hobby, 'Football'
575
+ # %>
576
+ #
577
+ # # Output:
578
+ # # <textarea name="user[hobby]" id="user-hobby">Football</textarea>
579
+ #
580
+ # @example Set content and HTML attributes
581
+ # <%=
582
+ # # ...
583
+ # text_area :hobby, 'Football', class: 'form-control'
584
+ # %>
585
+ #
586
+ # # Output:
587
+ # # <textarea name="user[hobby]" id="user-hobby" class="form-control">Football</textarea>
588
+ #
589
+ # @example Omit content and specify HTML attributes
590
+ # <%=
591
+ # # ...
592
+ # text_area :hobby, class: 'form-control'
593
+ # %>
594
+ #
595
+ # # Output:
596
+ # # <textarea name="user[hobby]" id="user-hobby" class="form-control"></textarea>
597
+ #
598
+ # @example Force blank value
599
+ # <%=
600
+ # # ...
601
+ # text_area :hobby, '', class: 'form-control'
602
+ # %>
603
+ #
604
+ # # Output:
605
+ # # <textarea name="user[hobby]" id="user-hobby" class="form-control"></textarea>
606
+ def text_area(name, content = nil, attributes = {})
607
+ if content.respond_to?(:to_hash)
608
+ attributes = content
609
+ content = nil
610
+ end
611
+
612
+ attributes = {name: _input_name(name), id: _input_id(name)}.merge(attributes)
613
+ textarea(content || _value(name), attributes)
614
+ end
615
+
616
+ # Text input
617
+ #
618
+ # @param name [Symbol] the input name
619
+ # @param attributes [Hash] HTML attributes to pass to the input tag
620
+ #
621
+ # @since 0.2.0
622
+ #
623
+ # @example Basic usage
624
+ # <%=
625
+ # # ...
626
+ # text_field :first_name
627
+ # %>
628
+ #
629
+ # # Output:
630
+ # # <input type="text" name="user[first_name]" id="user-first-name" value="">
631
+ def text_field(name, attributes = {})
632
+ input _attributes(:text, name, attributes)
633
+ end
634
+ alias_method :input_text, :text_field
635
+
636
+ # Radio input
637
+ #
638
+ # If request params have a value that corresponds to the given value,
639
+ # it automatically sets the <tt>checked</tt> attribute.
640
+ # This Hanami::Controller integration happens without any developer intervention.
641
+ #
642
+ # @param name [Symbol] the input name
643
+ # @param value [String] the input value
644
+ # @param attributes [Hash] HTML attributes to pass to the input tag
645
+ #
646
+ # @since 0.2.0
647
+ #
648
+ # @example Basic usage
649
+ # <%=
650
+ # # ...
651
+ # radio_button :category, 'Fiction'
652
+ # radio_button :category, 'Non-Fiction'
653
+ # %>
654
+ #
655
+ # # Output:
656
+ # # <input type="radio" name="book[category]" value="Fiction">
657
+ # # <input type="radio" name="book[category]" value="Non-Fiction">
658
+ #
659
+ # @example Automatic checked value
660
+ # # Given the following params:
661
+ # #
662
+ # # book: {
663
+ # # category: 'Non-Fiction'
664
+ # # }
665
+ #
666
+ # <%=
667
+ # # ...
668
+ # radio_button :category, 'Fiction'
669
+ # radio_button :category, 'Non-Fiction'
670
+ # %>
671
+ #
672
+ # # Output:
673
+ # # <input type="radio" name="book[category]" value="Fiction">
674
+ # # <input type="radio" name="book[category]" value="Non-Fiction" checked="checked">
675
+ def radio_button(name, value, attributes = {})
676
+ attributes = { type: :radio, name: _input_name(name), value: value }.merge(attributes)
677
+ attributes[:checked] = CHECKED if _value(name) == value
678
+ input(attributes)
679
+ end
680
+
681
+ # Password input
682
+ #
683
+ # @param name [Symbol] the input name
684
+ # @param attributes [Hash] HTML attributes to pass to the input tag
685
+ #
686
+ # @since 0.2.0
687
+ #
688
+ # @example Basic usage
689
+ # <%=
690
+ # # ...
691
+ # password_field :password
692
+ # %>
693
+ #
694
+ # # Output:
695
+ # # <input type="password" name="signup[password]" id="signup-password" value="">
696
+ def password_field(name, attributes = {})
697
+ input({ type: :password, name: _input_name(name), id: _input_id(name), value: nil }.merge(attributes))
698
+ end
699
+
700
+ # Select input
701
+ #
702
+ # @param name [Symbol] the input name
703
+ # @param values [Hash] a Hash to generate <tt><option></tt> tags.
704
+ # Keys correspond to <tt>value</tt> and values correspond to the content.
705
+ # @param attributes [Hash] HTML attributes to pass to the input tag
706
+ #
707
+ # If request params have a value that corresponds to one of the given values,
708
+ # it automatically sets the <tt>selected</tt> attribute on the <tt><option></tt> tag.
709
+ # This Hanami::Controller integration happens without any developer intervention.
710
+ #
711
+ # @since 0.2.0
712
+ #
713
+ # @example Basic usage
714
+ # <%=
715
+ # # ...
716
+ # values = Hash['it' => 'Italy', 'us' => 'United States']
717
+ # select :stores, values
718
+ # %>
719
+ #
720
+ # # Output:
721
+ # # <select name="book[store]" id="book-store">
722
+ # # <option value="it">Italy</option>
723
+ # # <option value="us">United States</option>
724
+ # # </select>
725
+ #
726
+ # @example Automatic selected option
727
+ # # Given the following params:
728
+ # #
729
+ # # book: {
730
+ # # store: 'it'
731
+ # # }
732
+ #
733
+ # <%=
734
+ # # ...
735
+ # values = Hash['it' => 'Italy', 'us' => 'United States']
736
+ # select :stores, values
737
+ # %>
738
+ #
739
+ # # Output:
740
+ # # <select name="book[store]" id="book-store">
741
+ # # <option value="it" selected="selected">Italy</option>
742
+ # # <option value="us">United States</option>
743
+ # # </select>
744
+ def select(name, values, attributes = {})
745
+ options = attributes.delete(:options) || {}
746
+ attributes = { name: _input_name(name), id: _input_id(name) }.merge(attributes)
747
+
748
+ super(attributes) do
749
+ values.each do |value, content|
750
+ if _value(name) == value
751
+ option(content, {value: value, selected: SELECTED}.merge(options))
752
+ else
753
+ option(content, {value: value}.merge(options))
754
+ end
755
+ end
756
+ end
757
+ end
758
+
759
+ # Submit button
760
+ #
761
+ # @param content [String] The content
762
+ # @param attributes [Hash] HTML attributes to pass to the button tag
763
+ #
764
+ # @since 0.2.0
765
+ #
766
+ # @example Basic usage
767
+ # <%=
768
+ # # ...
769
+ # submit 'Create'
770
+ # %>
771
+ #
772
+ # # Output:
773
+ # # <button type="submit">Create</button>
774
+ def submit(content, attributes = {})
775
+ attributes = { type: :submit }.merge(attributes)
776
+ button(content, attributes)
777
+ end
778
+
779
+ protected
780
+ # A set of options to pass to the sub form helpers.
781
+ #
782
+ # @api private
783
+ # @since 0.2.0
784
+ def options
785
+ Hash[name: @name, values: @values, verb: @verb, csrf_token: @csrf_token]
786
+ end
787
+
788
+ private
789
+ # Check the current builder is top-level
790
+ #
791
+ # @api private
792
+ # @since 0.2.0
793
+ def toplevel?
794
+ @attributes.any?
795
+ end
796
+
797
+ # Prepare for method override
798
+ #
799
+ # @api private
800
+ # @since 0.2.0
801
+ def _method_override!
802
+ if BROWSER_METHODS.include?(@verb_method)
803
+ @attributes[:method] = @verb_method
804
+ else
805
+ @attributes[:method] = DEFAULT_METHOD
806
+ @verb = @verb_method
807
+ end
808
+ end
809
+
810
+ # Return the method from attributes
811
+ #
812
+ # @api private
813
+ def verb_method
814
+ (@attributes.fetch(:method) { DEFAULT_METHOD }).to_s.upcase
815
+ end
816
+
817
+ # Return CSRF Protection token from view context
818
+ #
819
+ # @api private
820
+ # @since 0.2.0
821
+ def csrf_token
822
+ @context.csrf_token if @context.respond_to?(:csrf_token) && !EXCLUDED_CSRF_METHODS.include?(@verb_method)
823
+ end
824
+
825
+ # Return a set of default HTML attributes
826
+ #
827
+ # @api private
828
+ # @since 0.2.0
829
+ def _attributes(type, name, attributes)
830
+ { type: type, name: _input_name(name), id: _input_id(name), value: _value(name) }.merge(attributes)
831
+ end
832
+
833
+ # Input <tt>name</tt> HTML attribute
834
+ #
835
+ # @api private
836
+ # @since 0.2.0
837
+ def _input_name(name)
838
+ "#{ @name }[#{ name }]"
839
+ end
840
+
841
+ # Input <tt>id</tt> HTML attribute
842
+ #
843
+ # @api private
844
+ # @since 0.2.0
845
+ def _input_id(name)
846
+ name = _input_name(name).gsub(/\[(?<token>[[[:word:]]\-]*)\]/, INPUT_ID_REPLACEMENT)
847
+ Utils::String.new(name).dasherize
848
+ end
849
+
850
+ # Input <tt>value</tt> HTML attribute
851
+ #
852
+ # @api private
853
+ # @since 0.2.0
854
+ def _value(name)
855
+ name = _input_name(name).gsub(/\[(?<token>[[:word:]]*)\]/, INPUT_VALUE_REPLACEMENT)
856
+ @values.get(name)
857
+ end
858
+
859
+ # Input <tt>for</tt> HTML attribute
860
+ #
861
+ # @api private
862
+ # @since 0.2.0
863
+ def _for(content, name)
864
+ case name
865
+ when String, Hanami::Utils::String
866
+ name
867
+ else
868
+ _input_id(name || content)
869
+ end
870
+ end
871
+
872
+ # Hidden field for check box
873
+ #
874
+ # @api private
875
+ # @since 0.2.0
876
+ #
877
+ # @see Hanami::Helpers::FormHelper::FormBuilder#check_box
878
+ def _hidden_field_for_check_box(name, attributes)
879
+ if attributes[:value].nil? || !attributes[:unchecked_value].nil?
880
+ input({
881
+ type: :hidden,
882
+ name: attributes[:name] || _input_name(name),
883
+ value: attributes.delete(:unchecked_value) || DEFAULT_UNCHECKED_VALUE
884
+ })
885
+ end
886
+ end
887
+
888
+ # HTML attributes for check box
889
+ #
890
+ # @api private
891
+ # @since 0.2.0
892
+ #
893
+ # @see Hanami::Helpers::FormHelper::FormBuilder#check_box
894
+ def _attributes_for_check_box(name, attributes)
895
+ attributes = {
896
+ type: :checkbox,
897
+ name: _input_name(name),
898
+ id: _input_id(name),
899
+ value: attributes.delete(:checked_value) || DEFAULT_CHECKED_VALUE
900
+ }.merge(attributes)
901
+
902
+ value = _value(name)
903
+ attributes[:checked] = CHECKED if value &&
904
+ ( value == attributes[:value] || value.include?(attributes[:value]) )
905
+
906
+ attributes
907
+ end
908
+ end
909
+ end
910
+ end
911
+ end