roda-tags 0.1.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.
@@ -0,0 +1,919 @@
1
+ require 'roda'
2
+ require_relative '../../core_ext/string' unless ''.respond_to?(:titleize)
3
+ # require_relative '../../core_ext/object' unless :symbol.respond_to?(:in?)
4
+
5
+ class Roda
6
+
7
+ #
8
+ module RodaPlugins
9
+
10
+ # TODO: add documentation here
11
+ module RodaTagHelpers
12
+ # default options
13
+ OPTS = {
14
+ #
15
+ tags_label_required_str: '<span>*</span>',
16
+ #
17
+ tags_label_append_str: ':',
18
+ # the default classes for various form tags. ie: shortcut to automatically add
19
+ # BS3 'form-control'
20
+ tags_forms_default_class: '', # 'form-control',
21
+
22
+ }.freeze
23
+
24
+
25
+ # Depend on the render plugin, since this plugin only makes
26
+ # sense when the render plugin is used.
27
+ def self.load_dependencies(app, opts = OPTS)
28
+ app.plugin :tags, opts
29
+ end
30
+
31
+ def self.configure(app, opts = {})
32
+ if app.opts[:tag_helpers]
33
+ opts = app.opts[:tag_helpers][:orig_opts].merge(opts)
34
+ else
35
+ opts = OPTS.merge(opts)
36
+ end
37
+
38
+ app.opts[:tag_helpers] = opts.dup
39
+ app.opts[:tag_helpers][:orig_opts] = opts
40
+ end
41
+
42
+ #
43
+ module ClassMethods
44
+ # Return the uitags options for this class.
45
+ def tag_helpers_opts
46
+ opts[:tag_helpers]
47
+ end
48
+
49
+ end
50
+
51
+ #
52
+ module InstanceMethods
53
+
54
+ # Constructs a form without object based on options
55
+ #
56
+ # ==== Examples
57
+ #
58
+ # form_tag('/register') do
59
+ # ...
60
+ # end
61
+ # #=>
62
+ # <form action="/register" id="register-form" method="post">
63
+ # ...
64
+ # </form>
65
+ #
66
+ #
67
+ # <% form_tag('/register', method: :put, id: 'register-form' ) %>
68
+ # ...
69
+ # <% end %>
70
+ # #=>
71
+ # <form action="/register" id="register-form" method="post" >
72
+ # <input name="_method" type="hidden" value="put"/>
73
+ # ...
74
+ # </form>
75
+ #
76
+ # Multipart support via:
77
+ #
78
+ # <% form_tag('/register', multipart: true ) %>
79
+ #
80
+ # <% form_tag('/register', multipart: 'multipart/form-data' ) %>
81
+ #
82
+ # <% form_tag('/register', enctype: 'multipart/form-data' ) %>
83
+ # #=>
84
+ # <form enctype="multipart/form-data" method="post" action="/register">
85
+ # ...
86
+ # </form>
87
+ #
88
+ def form_tag(action, attrs = {}, &block)
89
+ attrs.reverse_merge!(method: :post, action: action)
90
+ method = attrs[:method]
91
+ # Unless the method is :get, fake out the method using :post
92
+ attrs[:method] = :post unless attrs[:method] == :get
93
+ faux_method_tag = method.to_s =~ /post|get/ ? '' : faux_method(method)
94
+ # set the enctype to multipart-form if we got a @multipart form
95
+ attrs[:enctype] = 'multipart/form-data' if attrs.delete(:multipart) || @multipart
96
+ captured_html = block_given? ? capture_html(&block) : ''
97
+ concat_content(tag(:form, faux_method_tag + captured_html, attrs))
98
+ end
99
+
100
+ # Constructs a label tag from the given options
101
+ #
102
+ # ==== Examples
103
+ #
104
+ # <%= label_tag(:name) %>
105
+ # #=> <label for="name">Name:</label>
106
+ #
107
+ # Should accept a custom label text.
108
+ #
109
+ # <%= label_tag(:name, label: 'Custom label') %>
110
+ # #=> <label for="name">Custom label:</label>
111
+ #
112
+ # If label value is nil, then renders the default label text.
113
+ #
114
+ # <%= label_tag(:name, label: nil) %>
115
+ # #=> <label for="name">Name:</label>
116
+ #
117
+ # Removes the label text when given :false.
118
+ #
119
+ # <%= label_tag(:name, label: false) %>
120
+ # #=> <label for="name"></label>
121
+ #
122
+ # Appends the <tt>app.forms_label_required_str</tt> value, when the label is required.
123
+ #
124
+ # <%= label_tag(:name, required: true) %>
125
+ # #=> <label for="name">Name: <span>*</span></label>
126
+ #
127
+ def label_tag(field, attrs = {}, &block)
128
+ attrs.reverse_merge!(label: field.to_s.titleize, for: field)
129
+
130
+ label_text = attrs.delete(:label)
131
+ # handle FALSE & nil values
132
+ label_text = '' if label_text == false
133
+ label_text = field.to_s.titleize if label_text.nil?
134
+
135
+ unless label_text.to_s.empty?
136
+ label_text << opts_tag_helpers[:tags_label_append_str]
137
+ if attrs.delete(:required)
138
+ label_text = "#{label_text} #{opts_tag_helpers[:tags_label_required_str]}"
139
+ end
140
+ end
141
+
142
+ if block_given? # label with inner content
143
+ label_content = label_text + capture_html(&block)
144
+ concat_content(tag(:label, label_content, attrs))
145
+ else # regular label
146
+ tag(:label, label_text, attrs)
147
+ end
148
+ end
149
+
150
+ # Constructs a hidden field input from the given options
151
+ #
152
+ # ==== Examples
153
+ #
154
+ # <%= hidden_field_tag(:snippet_name) %>
155
+ # #=>
156
+ # <input id="snippet_name" name="snippet_name" type="hidden">
157
+ #
158
+ # Providing a value:
159
+ #
160
+ # <%= hidden_field_tag(:snippet_name, value: 'myvalue') %>
161
+ # #=>
162
+ # <input id="snippet_name" name="snippet_name" type="hidden" value="myvalue">
163
+ #
164
+ # Setting a different <tt>:id</tt>
165
+ #
166
+ # <%= hidden_field_tag(:snippet_name, id: 'some-id') %>
167
+ # #=>
168
+ # <input id="some-id" name="snippet_name" type="hidden">
169
+ #
170
+ # Removing the <tt>:id</tt> attribute completely.
171
+ #
172
+ # <%= hidden_field_tag(:snippet_name, id: false ) %>
173
+ # #=>
174
+ # <input name="snippet_name" type="hidden">
175
+ #
176
+ def hidden_field_tag(name, attrs = {})
177
+ attrs.reverse_merge!(name: name, value: '', type: :hidden)
178
+ attrs = add_css_id(attrs, name)
179
+ tag(:input, attrs)
180
+ end
181
+ alias_method :hiddenfield_tag, :hidden_field_tag
182
+
183
+ # Creates a standard text field; use these text fields to input smaller chunks of text like
184
+ # a username or a search query.
185
+ #
186
+ # ==== Examples
187
+ #
188
+ # text_field_tag(:snippet_name)
189
+ # #=>
190
+ # <input class="text" id="snippet_name" name="snippet_name" type="text">
191
+ #
192
+ # Providing a value:
193
+ #
194
+ # text_field_tag(:snippet, value: 'some-value')
195
+ # #=>
196
+ # <input class="text" id="snippet" name="snippet" type="text" value="some-value">
197
+ #
198
+ # Setting a different <tt>:id</tt>
199
+ #
200
+ # text_field_tag(:snippet_name, id: 'some-id')
201
+ # #=>
202
+ # <input class="text" id="some-id" name="snippet_name" type="text">
203
+ #
204
+ # Removing the <tt>:id</tt> attribute completely. NB! bad practice.
205
+ #
206
+ # text_field_tag(:snippet_name, id: false)
207
+ # #=>
208
+ # <input class="text" name="snippet_name" type="text">
209
+ #
210
+ # Adding another CSS class. NB! appends the the class to the default class <tt>.text</tt>.
211
+ #
212
+ # text_field_tag(:snippet_name, class: :big )
213
+ # #=>
214
+ # <input class="big text" id="snippet_name" name="snippet_name" type="text">
215
+ #
216
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>.
217
+ #
218
+ # text_field_tag(:name, ui_hint: 'a user hint')
219
+ # #=>
220
+ # <input class="text" id="name" name="name" title="a user hint" type="text">
221
+ #
222
+ # Supports <tt>:maxlength</tt> & <tt>:size</tt> attributes.
223
+ #
224
+ # text_field_tag(:ip, maxlength: 15, size: 20)
225
+ # #=>
226
+ # <input class="text" id="ip" maxlength="15" name="ip" size="20" type="text">
227
+ #
228
+ # Supports <tt>:disabled</tt> & <tt>:readonly</tt> attributes.
229
+ #
230
+ # text_field_tag(:name, disabled: true)
231
+ # #=>
232
+ # <input class="text" disabled="disabled" id="name" name="name" type="text" >
233
+ #
234
+ # text_field_tag(:name, readonly: true)
235
+ # #=>
236
+ # <input class="text" id="name" name="name" readonly="readonly" type="text">
237
+ #
238
+ #
239
+ def text_field_tag(name, attrs = {})
240
+ attrs.reverse_merge!(name: name, type: :text)
241
+ attrs = add_css_id(attrs, name)
242
+ attrs = add_css_class(attrs, :text)
243
+ attrs = add_ui_hint(attrs)
244
+ tag(:input, attrs)
245
+ end
246
+ alias_method :textfield_tag, :text_field_tag
247
+
248
+ # Constructs a password field input from the given options
249
+ #
250
+ # ==== Examples
251
+ #
252
+ # password_field_tag(:snippet_name)
253
+ # #=> <input class="text" id="snippet_name" name="snippet_name" type="password">
254
+ #
255
+ # Providing a value:
256
+ #
257
+ # password_field_tag(:snippet_name, value: 'some-value')
258
+ # #=>
259
+ # <input class="text" id="snippet" name="snippet" type="password" value="some-value">
260
+ #
261
+ # Setting a different <tt>:id</tt>
262
+ #
263
+ # password_field_tag(:snippet_name, id: 'some-id')
264
+ # #=> <input class="text" id="some-id" name="snippet_name" type="password">
265
+ #
266
+ # Removing the <tt>:id</tt> attribute completely. NB! bad practice.
267
+ #
268
+ # password_field_tag(:snippet_name, id: false)
269
+ # #=> <input class="text" name="snippet_name" type="password">
270
+ #
271
+ # Adding another CSS class. NB! appends the the class to the default class <tt>.text</tt>.
272
+ #
273
+ # password_field_tag(:snippet_name, class: :big )
274
+ # #=> <input class="big text" id="snippet_name" name="snippet_name" type="password">
275
+ #
276
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>.
277
+ #
278
+ # password_field_tag(:name, ui_hint: 'a user hint')
279
+ # #=> <input class="text" id="name" name="name" title="a user hint" type="password">
280
+ #
281
+ # Supports <tt>:maxlength</tt>, <tt>:size</tt> & <tt>:disabled</tt> attributes.
282
+ #
283
+ # password_field_tag(:ip, maxlength: 15, size: 20)
284
+ # #=> <input class="text" id="ip" maxlength="15" name="ip" size="20" type="password">
285
+ #
286
+ # password_field_tag(:name, disabled: true)
287
+ # #=> <input class="text" id="name" disabled="disabled" name="name" type="password">
288
+ #
289
+ def password_field_tag(name, attrs = {})
290
+ attrs.reverse_merge!(name: name, type: :password)
291
+ attrs = add_css_id(attrs, name)
292
+ attrs = add_css_class(attrs, :text) # deliberately giving it the .text class
293
+ attrs = add_ui_hint(attrs)
294
+ tag(:input, attrs)
295
+ end
296
+ alias_method :passwordfield_tag, :password_field_tag
297
+
298
+
299
+ # Creates a file upload field. If you are using file uploads then you will also
300
+ # need to set the multipart option for the form tag:
301
+ #
302
+ # <% form_tag '/upload', :multipart => true do %>
303
+ # <label for="file">File to Upload</label>
304
+ # <%= file_field_tag "file" %>
305
+ # <%= submit_tag %>
306
+ # <% end %>
307
+ #
308
+ # The specified URL will then be passed a File object containing the selected file,
309
+ # or if the field was left blank, a StringIO object.
310
+ #
311
+ # ==== Examples
312
+ #
313
+ # file_field_tag('attachment')
314
+ # #=> <input class="file" id="attachment" name="attachment" type="file">
315
+ #
316
+ # Ignores the invalid <tt>:value</tt> attribute.
317
+ #
318
+ # file_field_tag(:photo, value: 'some-value')
319
+ # #=> <input class="file" id="photo" name="photo" type="file">
320
+ #
321
+ # Setting a different <tt>:id</tt>
322
+ #
323
+ # file_field_tag(:photo, id: 'some-id')
324
+ # #=> <input class="file" id="some-id" name="photo" type="file">
325
+ #
326
+ # Removing the <tt>:id</tt> attribute completely. NB! bad practice.
327
+ #
328
+ # file_field_tag(:photo, id: false)
329
+ # #=> <input class="file" name="photo" type="file">
330
+ #
331
+ # Adding another CSS class. NB! appends the the class to the default class +.text+.
332
+ #
333
+ # file_field_tag(:photo, class: :big )
334
+ # #=> <input class="big file" id="photo" name="photo" type="file">
335
+ #
336
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>. Also works with +:title+.
337
+ #
338
+ # file_field_tag(:photo, ui_hint: 'a user hint')
339
+ # #=> <input class="file" id="photo" name="photo" title="a user hint" type="file">
340
+ #
341
+ # Supports the <tt>:disabled</tt> attribute.
342
+ #
343
+ # file_field_tag(:photo, disabled: true)
344
+ # #=> <input class="file" disabled="disabled" id="photo" name="photo" type="file">
345
+ #
346
+ #
347
+ # Supports the <tt>:accept</tt> attribute, even though most browsers don't.
348
+ #
349
+ # file_field_tag(:photo, accept: 'image/png,image/jpeg')
350
+ # #=>
351
+ # <input accept="image/png,image/jpeg" class="file" ... type="file">
352
+ #
353
+ def file_field_tag(name, attrs = {})
354
+ attrs.reverse_merge!(name: name, type: :file)
355
+ attrs.delete(:value) # can't use value, so delete it if present
356
+ attrs = add_css_id(attrs, name)
357
+ attrs = add_css_class(attrs, :file)
358
+ attrs = add_ui_hint(attrs)
359
+ tag(:input, attrs)
360
+ end
361
+ alias_method :filefield_tag, :file_field_tag
362
+
363
+
364
+ # Constructs a textarea input from the given options
365
+ #
366
+ # TODO: enable :escape functionality...
367
+ #
368
+ # * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped. If you
369
+ # need unescaped contents, set this to false.
370
+ #
371
+ # Any other key creates standard HTML attributes for the tag.
372
+ #
373
+ # ==== Examples
374
+ #
375
+ # textarea_tag('post')
376
+ # #=> <textarea id="post" name="post">\n</textarea>
377
+ #
378
+ # Providing a value:
379
+ #
380
+ # textarea_tag(:bio, value: @actor.bio)
381
+ # #=> <textarea id="bio" name="bio">This is my biography.\n</textarea>
382
+ #
383
+ # Setting a different <tt>:id</tt>
384
+ #
385
+ # textarea_tag(:body, id: 'some-id')
386
+ # #=> <textarea id="some-id" name="post">\n\n</textarea>
387
+ #
388
+ # Adding a CSS class. NB! textarea has no other class by default.
389
+ #
390
+ # textarea_tag(:body, class: 'big')
391
+ # #=> <textarea class="big" id="post" name="post">\n</textarea>
392
+ #
393
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>.
394
+ #
395
+ # textarea_tag(:body, ui_hint: 'a user hint')
396
+ # #=> <textarea id="post" name="post" title="a user hint">\n</textarea>
397
+ #
398
+ # Supports <tt>:rows</tt> & <tt>:cols</tt> attributes.
399
+ #
400
+ # textarea_tag('body', rows: 10, cols: 25)
401
+ # #=> <textarea cols="25" id="body" name="body" rows="10">\n</textarea>
402
+ #
403
+ # Supports shortcut to +:rows+ & +:cols+ attributes, via the +:size+ attribute.
404
+ #
405
+ # textarea_tag( 'body', size: "25x10")
406
+ # #=> <textarea cols="25" id="body" name="body" rows="10"></textarea>
407
+ #
408
+ # Supports +:disabled+ & +:readonly+ attributes.
409
+ #
410
+ # textarea_tag(:description, disabled: true)
411
+ # #=> <textarea disabled="disabled" id="description" name="description"></textarea>
412
+ #
413
+ # textarea_tag(:description, readonly: true)
414
+ # #=> <textarea id="description" name="description" readonly="readonly"></textarea>
415
+ #
416
+ def textarea_tag(name, attrs = {})
417
+ attrs.reverse_merge!(name: name)
418
+ attrs = add_css_id(attrs, name)
419
+ if size = attrs.delete(:size)
420
+ attrs[:cols], attrs[:rows] = size.split('x') if size.respond_to?(:split)
421
+ end
422
+ content = attrs.delete(:value)
423
+ attrs = add_ui_hint(attrs)
424
+ tag(:textarea, content, attrs)
425
+ end
426
+ alias_method :text_area_tag, :textarea_tag
427
+
428
+ # Creates a field set for grouping HTML form elements.
429
+ #
430
+ # ==== Examples
431
+ #
432
+ # <% field_set_tag(:actor) %>
433
+ # #=>
434
+ # <fieldset id="fieldset-actor">
435
+ # ...
436
+ # </fieldset>
437
+ #
438
+ # Sets the <tt><legend></tt> and <tt>:id</tt> attribute when given a single argument.
439
+ #
440
+ # <% field_set_tag 'User Details' do %>
441
+ # <p><%= text_field_tag 'name' %></p>
442
+ # <% end %>
443
+ # #=>
444
+ # <fieldset id="fieldset-user-details">
445
+ # <legend>User Details</legend>
446
+ # <p><input name="name" class="text" id="name" type="text"></p>
447
+ # </fieldset>
448
+ #
449
+ # Supports <tt>:legend</tt> attribute for the <tt><legend></tt> tag.
450
+ #
451
+ # field_set_tag(:actor, legend: 'Your Details')
452
+ # #=>
453
+ # <fieldset id="fieldset-actor">
454
+ # <legend>Your Details</legend>
455
+ # <snip...>
456
+ #
457
+ # Adding a CSS class. NB! fieldset has no other class by default.
458
+ #
459
+ # field_set_tag(:actor, class: "legend-class")
460
+ # #=>
461
+ # <fieldset class="legend-class" id="fieldset-actor">
462
+ # <snip...>
463
+ #
464
+ #
465
+ # When passed +nil+ as the first argument the <tt>:id</tt> becomes 'fieldset'.
466
+ #
467
+ # field_set_tag( nil, class: 'format')
468
+ # #=>
469
+ # <fieldset class="format" id="fieldset">
470
+ # <snip...>
471
+ #
472
+ # Removing the <tt>:id</tt> attribute completely.
473
+ #
474
+ # field_set_tag('User Details', id: false)
475
+ # #=>
476
+ # <fieldset>
477
+ # <legend>User Details</legend>
478
+ # <snip...>
479
+ #
480
+ # @api public
481
+ def field_set_tag(*args, &block)
482
+ attrs = args.last.is_a?(Hash) ? args.pop : {}
483
+ attrs = add_css_id(attrs, ['fieldset', args.first].compact.join('-'))
484
+ legend_text = args.first.is_a?(String || Symbol) ? args.first : attrs.delete(:legend)
485
+ legend_html = legend_text.blank? ? '' : tag(:legend, legend_text)
486
+ captured_html = block_given? ? capture_html(&block) : ''
487
+ concat_content(tag(:fieldset, legend_html + captured_html, attrs))
488
+ end
489
+ alias_method :fieldset_tag, :field_set_tag
490
+
491
+ # Return a legend with _contents_.
492
+ #
493
+ # ==== Examples
494
+ #
495
+ # legend_tag('User Details')
496
+ # #=> <legend>User Details</legend>
497
+ #
498
+ # Adding an :id attribute.
499
+ #
500
+ # legend_tag('User Details', id: 'some-id')
501
+ # #=> <legend id="some-id">User Details</legend>
502
+ #
503
+ # Adding a CSS class. NB! legend has no other class by default.
504
+ #
505
+ # legend_tag('User Details', class: 'some-class')
506
+ # #=> <legend class="some-class">User Details</legend>
507
+ #
508
+ def legend_tag(contents, attrs = {})
509
+ tag(:legend, contents, attrs)
510
+ end
511
+
512
+ # Creates a checkbox element.
513
+ #
514
+ # ==== Examples
515
+ #
516
+ # check_box_tag(:accept)
517
+ # #=> <input class="checkbox" id="accept" name="accept" type="checkbox" value="1">
518
+ #
519
+ # Providing a value:
520
+ #
521
+ # check_box_tag(:rock, value: 'rock music')
522
+ # #=> <input class="checkbox" id="rock" name="rock" type="checkbox" value="rock music">
523
+ #
524
+ # Setting a different <tt>:id</tt>.
525
+ #
526
+ # check_box_tag(:rock, :id => 'some-id')
527
+ # #=> <input class="checkbox" id="some-id" name="rock" type="checkbox" value="1">
528
+ #
529
+ # Adding another CSS class. NB! appends the the class to the default class +.checkbox+.
530
+ #
531
+ # check_box_tag(:rock, class: 'small')
532
+ # #=> <input class="small checkbox" id="rock" name="rock" type="checkbox" value="1">
533
+ #
534
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>.
535
+ #
536
+ # check_box_tag(:rock, ui_hint: 'a user hint')
537
+ # #=> <input ... title="a user hint" type="checkbox" value="1">
538
+ #
539
+ # Supports the <tt>:disabled</tt> & <tt>:checked</tt> attributes.
540
+ #
541
+ # check_box_tag(:rock, checked: true)
542
+ # #=> <input checked="checked" ... type="checkbox" value="1">
543
+ #
544
+ # check_box_tag(:rock, disabled: true)
545
+ # #=> <input class="checkbox" disabled="disabled" ... type="checkbox" value="1">
546
+ #
547
+ def check_box_tag(name, attrs = {})
548
+ attrs.reverse_merge!(name: name, type: :checkbox, checked: false, value: 1)
549
+ attrs = add_css_id(attrs, name)
550
+ attrs = add_css_class(attrs, :checkbox)
551
+ attrs = add_ui_hint(attrs)
552
+ tag(:input, attrs)
553
+ end
554
+ alias_method :checkbox_tag, :check_box_tag
555
+
556
+ # Creates a radio button; use groups of radio buttons named the same to allow users to
557
+ # select from a group of options.
558
+ #
559
+ # ==== Examples
560
+ #
561
+ # radio_button_tag(:accept)
562
+ # #=> <input class="radio" id="accept_1" name="accept" type="radio" value="1">
563
+ #
564
+ # Providing a value:
565
+ #
566
+ # radio_button_tag(:rock, value: 'rock music')
567
+ # #=> <input ... type="radio" value="rock music">
568
+ #
569
+ # Setting a different <tt>:id</tt>.
570
+ #
571
+ # radio_button_tag(:rock, id: 'some-id')
572
+ # #=>
573
+ # <input class="radio" id="some-id_1" name="rock" type="radio" value="1">
574
+ #
575
+ # Adding another CSS class. NB! appends the the class to the default class +.radio+.
576
+ #
577
+ # radio_button_tag(:rock, class: 'big')
578
+ # #=> <input class="big radio" id="rock_1" name="rock" type="radio" value="1">
579
+ #
580
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>.
581
+ #
582
+ # radio_button_tag(:rock, ui_hint: 'a user hint')
583
+ # #=> <input ... title="a user hint" type="radio" value="1">
584
+ #
585
+ # Supports the <tt>:disabled</tt> & <tt>:checked</tt> attributes.
586
+ #
587
+ # radio_button_tag(:yes, checked: true)
588
+ # #=> <input checked="checked" class="checkbox" id="yes_1"...value="1">
589
+ #
590
+ # radio_button_tag(:yes, disabled: true)
591
+ # #=> <input disabled="disabled" class="checkbox" id="yes_1" ... value="1">
592
+ #
593
+ #
594
+ def radio_button_tag(name, attrs = {})
595
+ attrs.reverse_merge!(name: name, type: :radio, checked: false, value: 1)
596
+ attrs = add_css_id(attrs, name)
597
+ # id_value = [field.to_s,'_',value].join
598
+ attrs[:id] = [attrs[:id], html_safe_id(attrs[:value])].compact.join('_')
599
+ attrs = add_css_class(attrs, :radio)
600
+ attrs = add_ui_hint(attrs)
601
+ tag(:input, attrs)
602
+ end
603
+ alias_method :radiobutton_tag, :radio_button_tag
604
+
605
+ # Creates a submit button with the text value as the caption.
606
+ #
607
+ # ==== Examples
608
+ #
609
+ # <%= submit_tag %> || <%= submit_button %>
610
+ # => <input name="submit" type="submit" value="Save Form">
611
+ #
612
+ # <%= submit_tag(nil) %>
613
+ # => <input name="submit" type="submit" value="">
614
+ #
615
+ # <%= submit_tag("Custom Value") %>
616
+ # => <input name="submit" type="submit" value="Custom Value">
617
+ #
618
+ # Adding a CSS class. NB! input[:submit] has no other class by default.
619
+ #
620
+ # <%= submit_tag(class: 'some-class') %>
621
+ # #=> <input class="some-class" name="submit" type="submit" value="Save Form">
622
+ #
623
+ # Supports the <tt>:disabled</tt> attribute.
624
+ #
625
+ # <%= submit_tag(disabled: true) %>
626
+ # #=> <input disabled="disabled" name="submit" type="submit" value="Save Form">
627
+ #
628
+ # Adds a +:title+ attribute when passed +:ui_hint+. Also works with +:title+.
629
+ #
630
+ # <%= submit_tag(ui_hint: 'a user hint') %>
631
+ # #=> <input name="submit" title="a user hint" type="submit" value="Save Form">
632
+ #
633
+ def submit_tag(value = 'Save Form', attrs = {})
634
+ value, attrs = 'Save Form', value if value.is_a?(Hash)
635
+ attrs.reverse_merge!(type: :submit, name: :submit, value: value)
636
+ attrs = add_ui_hint(attrs)
637
+ self_closing_tag(:input, attrs)
638
+ end
639
+ alias_method :submit_button, :submit_tag
640
+
641
+ # Displays an image which when clicked will submit the form.
642
+ #
643
+ # ==== Examples
644
+ #
645
+ # @img = '/img/btn.png'
646
+ #
647
+ # image_submit_tag(@img)
648
+ # #=> <input src="/img/btn.png" type="image">
649
+ #
650
+ # image_submit_tag(@img, disabled: true)
651
+ # #=> <input disabled="disabled" src="/img/btn.png" type="image">
652
+ #
653
+ # image_submit_tag(@img, class 'search-button')
654
+ # #=> <input class="search-button" src="/img/btn.png" type="image">
655
+ #
656
+ def image_submit_tag(src, attrs = {})
657
+ tag(:input, { type: :image, src: src }.merge(attrs))
658
+ end
659
+ alias_method :imagesubmit_tag, :image_submit_tag
660
+
661
+ # Creates a reset button with the text value as the caption.
662
+ #
663
+ # ==== Examples
664
+ #
665
+ # <%= reset_tag %>
666
+ # => <input name="reset" type="reset" value="Reset Form">
667
+ #
668
+ # <%= reset_tag(nil) %>
669
+ # => <input name="reset" type="reset" value="">
670
+ #
671
+ # Adding a CSS class. NB! input[:reset] has no other class by default.
672
+ #
673
+ # <%= reset_tag('Custom Value', class: 'some-class') %>
674
+ # => <input class="some-class" name="reset" type="reset" value="Custom Value" >
675
+ #
676
+ # Supports the <tt>:disabled</tt> attribute.
677
+ #
678
+ # <%= reset_tag('Custom Value', disabled: true) %>
679
+ # => <input disabled="disabled" name="reset" type="reset" value="Custom Value">
680
+ #
681
+ # Adds a <tt>:title</tt> attribute when passed <tt>:ui_hint</tt>.
682
+ #
683
+ # <%= reset_tag('Custom Value', ui_hint: 'a user hint') %>
684
+ # => <input name="reset" title="a user hint" type="submit" value="Custom Value">
685
+ #
686
+ def reset_tag(value = 'Reset Form', attrs = {})
687
+ value, attrs = 'Reset Form', value if value.is_a?(Hash)
688
+ attrs.reverse_merge!(type: :reset, name: :reset, value: value)
689
+ attrs = add_ui_hint(attrs)
690
+ self_closing_tag(:input, attrs)
691
+ end
692
+ alias_method :reset_button, :reset_tag
693
+
694
+ # Creates a dropdown selection menu.
695
+ #
696
+ # If the :multiple option is set to true, a multiple choice selection box
697
+ # is created.
698
+ #
699
+ # ==== Attributes
700
+ #
701
+ # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
702
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
703
+ #
704
+ # Any other key creates standard HTML attributes for the tag.
705
+ #
706
+ #
707
+ # ==== Examples
708
+ #
709
+ # NB! the format for the options values must be [value, key].
710
+ #
711
+ # With Options values as a Hash
712
+ #
713
+ # select_tag(:letters, {a: 'A', b: 'B' })
714
+ # #=>
715
+ # <select id="letters" name="letters">
716
+ # <option value="a">A</option>
717
+ # <option value="b">B</option>
718
+ # </select>
719
+ #
720
+ # With Options values as an Array
721
+ #
722
+ # @letters = [[:a,'A'], [:b,'B']]
723
+ #
724
+ # select_tag(:letters, @letters)
725
+ # #=>
726
+ # <select id="letters" name="letters">
727
+ # <option value="a">A</option>
728
+ # <option value="b">B</option>
729
+ # </select>
730
+ #
731
+ #
732
+ # With Options values as an Array
733
+ #
734
+ # select_tag(:letters, @letters, selected: :a)
735
+ # #=>
736
+ # <select id="letters" name="letters">
737
+ # <option selected="selected" value="a">A</option>
738
+ # <snip...>
739
+ #
740
+ # When passing multiple items to :selected, the select menu automatically becomes
741
+ # a multiple select box. <b>NB! the [] on the <tt>:name</tt> attribute</b>
742
+ #
743
+ # select_tag(:letters, @letters, selected: [:a,'b'])
744
+ # #=>
745
+ # <select id="letters" multiple="multiple" name="letters[]">
746
+ # <option selected="selected" value="a">A</option>
747
+ # <option selected="selected" value="b">B</option>
748
+ # </select>
749
+ #
750
+ # When { multiple: true }, the select menu becomes a select box allowing multiple choices.
751
+ # <b>NB! the [] on the <tt>:name</tt> attribute</b>
752
+ #
753
+ # select_tag(:letters, @letters, multiple: true)
754
+ # #=>
755
+ # <select id="letters" name="letters[]" multiple="multiple">
756
+ # <snip...>
757
+ #
758
+ # select_tag(:letters, @letters, disabled: true)
759
+ # #=>
760
+ # <select id="letters" disabled="disabled" name="letters">
761
+ # <snip...>
762
+ #
763
+ # select_tag(:letters, @letters, id: 'my-letters')
764
+ # #=>
765
+ # <select id="my-letters" name="letters">
766
+ # <snip...>
767
+ #
768
+ # select_tag(:letters, @letters, class: 'funky-select')
769
+ # #=>
770
+ # <select class="funky-select" id="my-letters" name="letters">
771
+ # <snip...>
772
+ #
773
+ # select_tag(:letters, @letters, prompt: true)
774
+ # #=>
775
+ # <select id="letters" name="letters">
776
+ # <option selected="selected" value="">- Select -</option>
777
+ # <snip...>
778
+ #
779
+ # select_tag(:letters, @letters, prompt: 'Top Letters', selected: 'a')
780
+ # #=>
781
+ # <select id="letters" name="letters">
782
+ # <option value="">Top Letters</option>
783
+ # <option selected="selected" value="a">A</option>
784
+ # <snip...>
785
+ #
786
+ #
787
+ def select_tag(name, options, attrs = {})
788
+ options = options.to_a.reverse if options.is_a?(Hash)
789
+ attrs[:multiple] = true if attrs[:selected].is_a?(Array)
790
+ options_html = select_options(options, attrs)
791
+ attrs.delete(:selected)
792
+ # attrs = add_css_id(attrs, name)
793
+ add_css_id(attrs, name)
794
+ html_name = (attrs[:multiple] == true && !name.to_s.end_with?('[]')) ? "#{name}[]" : name
795
+ tag(:select, options_html, { name: html_name }.merge(attrs))
796
+ end
797
+
798
+ # Return select option _contents_ with _value_.
799
+ #
800
+ # ==== Examples
801
+ #
802
+ # select_option('a', 'Letter A') #=> <option value="a">Letter A</option>
803
+ #
804
+ # select_option('on', '') #=> <option value="on">On</option>
805
+ #
806
+ # select_option('a', 'Letter A', selected: true)
807
+ # #=> <option selected="selected" value="a">Letter A</option>
808
+ #
809
+ # select_option('a', 'Letter A', selected: false)
810
+ # #=> <option value="a">Letter A</option>
811
+ #
812
+ def select_option(value, key, attrs = {})
813
+ key = value.to_s.titleize if key.blank?
814
+ tag(:option, key, { value: value }.merge(attrs))
815
+ end
816
+
817
+ # Support Rack::MethodOverride
818
+ #
819
+ def faux_method(method = 'PUT')
820
+ hidden_field_tag(:input, name: '_method', value: method.to_s.upcase)
821
+ end
822
+
823
+
824
+ private
825
+
826
+ #
827
+ def opts_tag_helpers
828
+ opts[:tag_helpers]
829
+ end
830
+
831
+ #
832
+ def html_safe_id(id)
833
+ id.to_s.downcase.gsub(/\W/, '-').gsub('--', '-')
834
+ end
835
+
836
+ # do we have a class attrs already
837
+ def add_css_class(attrs, new_class = nil)
838
+ merge_attr_classes(attrs, new_class)
839
+ end
840
+
841
+ #
842
+ def add_css_id(attrs, new_id)
843
+ attrs = {} if attrs.nil?
844
+ new_id = '' if new_id.is_a?(Hash)
845
+ id_value = attrs[:id].nil? ? html_safe_id(new_id.to_s) : attrs.delete(:id)
846
+ attrs[:id] = id_value.to_s unless id_value == false
847
+ attrs[:id] = nil if attrs[:id] == '' # set to nil to remove from tag output
848
+ attrs
849
+ end
850
+
851
+ #
852
+ def add_ui_hint(attrs)
853
+ attrs[:title] = attrs.delete(:ui_hint) unless attrs[:ui_hint].nil?
854
+ attrs
855
+ end
856
+
857
+ # Return select option elements from _values_ with _options_ passed.
858
+ #
859
+ # === Options
860
+ #
861
+ # :selected string, symbol, or array of options selected
862
+ #
863
+ # ==== Examples
864
+ #
865
+ #
866
+ def select_options(values, attrs = {})
867
+ attrs = {} if attrs.blank?
868
+ values = [] if values.blank?
869
+ normalize_select_prompt(values, attrs)
870
+ # { :a => 'A' }
871
+ # [5, 'E']
872
+ # FIXME:: when passed a Hash of values, they become reversed (last first and so on..)
873
+
874
+ values.map do |value, key|
875
+ if value.is_a?(Hash)
876
+ tag(:optgroup, select_options(value, attrs), label: key)
877
+ elsif option_selected?(value, attrs[:selected])
878
+ select_option(value, key, selected: true)
879
+ else
880
+ select_option(value, key)
881
+ end
882
+ end.join
883
+ end
884
+
885
+ # Normalize select prompt.
886
+ #
887
+ # * When +attrs+ contains a :prompt string it is assigned as the prompt
888
+ # * When :prompt is true the default of '- Select Model -' will become the prompt
889
+ # * The prompt is selected unless a specific option is explicitly selected.
890
+ #
891
+ def normalize_select_prompt(values, attrs = {})
892
+ return unless attrs.key?(:prompt)
893
+ prompt = attrs.delete(:prompt)
894
+ attrs[:selected] = '' unless attrs.include?(:selected)
895
+ prompt_text = prompt == true ? '- Select -' : prompt
896
+ values.unshift(['', prompt_text])
897
+ end
898
+
899
+ # Check if option _key_ is _selected_.
900
+ def option_selected?(key, selection)
901
+ # if Array === selection
902
+ if selection.is_a?(Array)
903
+ # (selection.map { |s| s.to_s }).include?(key.to_s)
904
+ (selection.map(&:to_s)).include?(key.to_s)
905
+ else
906
+ selection.to_s == key.to_s
907
+ end
908
+ end
909
+
910
+
911
+ end # /InstanceMethods
912
+
913
+ end # /RodaTagHelpers
914
+
915
+ register_plugin(:tag_helpers, RodaTagHelpers)
916
+
917
+ end # /RodaPlugins
918
+
919
+ end