padrino-helpers 0.10.5 → 0.10.6.a

Sign up to get free protection for your applications and to get access to all the features.
@@ -153,7 +153,7 @@ module Padrino
153
153
  # @api public
154
154
  def feed_tag(mime, url, options={})
155
155
  full_mime = (mime == :atom) ? 'application/atom+xml' : 'application/rss+xml'
156
- content_tag(:link, options.reverse_merge(:rel => 'alternate', :type => full_mime, :title => mime, :href => url))
156
+ tag(:link, options.reverse_merge(:rel => 'alternate', :type => full_mime, :title => mime, :href => url))
157
157
  end
158
158
 
159
159
  ##
@@ -291,9 +291,9 @@ module Padrino
291
291
  # @api public
292
292
  def javascript_include_tag(*sources)
293
293
  options = sources.extract_options!.symbolize_keys
294
- options.reverse_merge!(:type => 'text/javascript', :content => "")
294
+ options.reverse_merge!(:type => 'text/javascript')
295
295
  sources.flatten.map { |source|
296
- tag(:script, options.reverse_merge(:src => asset_path(:js, source)))
296
+ content_tag(:script, nil, options.reverse_merge(:src => asset_path(:js, source)))
297
297
  }.join("\n")
298
298
  end
299
299
 
@@ -42,6 +42,37 @@ module Padrino
42
42
  @template.text_field_tag field_name(field), options
43
43
  end
44
44
 
45
+ def number_field(field, options={})
46
+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
47
+ options.merge!(:class => field_error(field, options))
48
+ @template.number_field_tag field_name(field), options
49
+ end
50
+
51
+ def telephone_field(field, options={})
52
+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
53
+ options.merge!(:class => field_error(field, options))
54
+ @template.telephone_field_tag field_name(field), options
55
+ end
56
+ alias_method :phone_field, :telephone_field
57
+
58
+ def email_field(field, options={})
59
+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
60
+ options.merge!(:class => field_error(field, options))
61
+ @template.email_field_tag field_name(field), options
62
+ end
63
+
64
+ def search_field(field, options={})
65
+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
66
+ options.merge!(:class => field_error(field, options))
67
+ @template.search_field_tag field_name(field), options
68
+ end
69
+
70
+ def url_field(field, options={})
71
+ options.reverse_merge!(:value => field_value(field), :id => field_id(field))
72
+ options.merge!(:class => field_error(field, options))
73
+ @template.url_field_tag field_name(field), options
74
+ end
75
+
45
76
  # f.text_area :summary, :value => "(enter summary)", :id => 'summary'
46
77
  def text_area(field, options={})
47
78
  options.reverse_merge!(:value => field_value(field), :id => field_id(field))
@@ -140,9 +140,9 @@ module Padrino
140
140
  # @param [Hash] options Error message display options.
141
141
  # @option options [String] :header_tag ("h2")
142
142
  # Used for the header of the error div
143
- # @option options [String] :id ("errorExplanation")
143
+ # @option options [String] :id ("field-errors")
144
144
  # The id of the error div.
145
- # @option options [String] :class ("errorExplanation")
145
+ # @option options [String] :class ("field-errors")
146
146
  # The class of the error div.
147
147
  # @option options [Array<Object>] :object
148
148
  # The object (or array of objects) for which to display errors,
@@ -222,7 +222,7 @@ module Padrino
222
222
  # The field on the +object+ to display the error for.
223
223
  # @param [Hash] options
224
224
  # The options to control the error display.
225
- # @option options [String] :tag ("div")
225
+ # @option options [String] :tag ("span")
226
226
  # The tag that encloses the error.
227
227
  # @option options [String] :prepend ("")
228
228
  # The text to prepend before the field error.
@@ -289,29 +289,216 @@ module Padrino
289
289
  end
290
290
 
291
291
  ##
292
- # Constructs a text field input from the given options
292
+ # Creates a text field input with the given name and options
293
293
  #
294
- # @macro [new] input_field_doc
295
- # @param [String] name
296
- # The name of the input field.
294
+ # @macro [new] text_field
295
+ # @param [Symbol] name
296
+ # The name of the input to create.
297
297
  # @param [Hash] options
298
- # The html options for the input field.
299
- #
300
- # @return [String] The html input field based on the +options+ specified
298
+ # The HTML options to include in this field.
299
+ #
300
+ # @option options [String] :id
301
+ # Specifies a unique identifier for the field.
302
+ # @option options [String] :class
303
+ # Specifies the stylesheet class of the field.
304
+ # @option options [String] :name
305
+ # Specifies the name of the field.
306
+ # @option options [String] :accesskey
307
+ # Specifies a shortcut key to access the field.
308
+ # @option options [Integer] :tabindex
309
+ # Specifies the tab order of the field.
310
+ # @option options [Integer] :maxlength
311
+ # Specifies the maximum length, in characters, of the field.
312
+ # @option options [Integer] :size
313
+ # Specifies the width, in characters, of the field.
314
+ # @option options [String] :placeholder
315
+ # Specifies a short hint that describes the expected value of the field.
316
+ # @option options [Boolean] :hidden
317
+ # Specifies whether or not the field is hidden from view.
318
+ # @option options [Boolean] :spellcheck
319
+ # Specifies whether or not the field should have it's spelling and grammar checked for errors.
320
+ # @option options [Boolean] :draggable
321
+ # Specifies whether or not the field is draggable. (true, false, :auto)
322
+ # @option options [String] :pattern
323
+ # Specifies the regular expression pattern that the field's value is checked against.
324
+ # @option options [Symbol] :autocomplete
325
+ # Specifies whether or not the field should have autocomplete enabled. (:on, :off)
326
+ # @option options [Boolean] :autofocus
327
+ # Specifies whether or not the field should automatically get focus when the page loads.
328
+ # @option options [Boolean] :required
329
+ # Specifies whether or not the field is required to be completeled before the form is submitted.
330
+ # @option options [Boolean] :readonly
331
+ # Specifies whether or not the field is read only.
332
+ # @option options [Boolean] :disabled
333
+ # Specifies whether or not the field is disabled.
334
+ #
335
+ # @return [String]
336
+ # Generated HTML with specified +options+
301
337
  #
302
338
  # @example
303
- # text_field_tag :username, :class => 'long'
339
+ # text_field_tag :first_name, :maxlength => 40, :required => true
340
+ # # => <input name="first_name" maxlength="40" required type="text">
341
+ #
342
+ # text_field_tag :last_name, :class => 'string', :size => 40
343
+ # # => <input name="last_name" class="string" size="40" type="text">
344
+ #
345
+ # text_field_tag :username, :placeholder => 'Your Username'
346
+ # # => <input name="username" placeholder="Your Username" type="text">
304
347
  #
305
348
  # @api public
306
349
  def text_field_tag(name, options={})
307
- options.reverse_merge!(:name => name)
308
- input_tag(:text, options)
350
+ input_tag(:text, options.reverse_merge!(:name => name))
351
+ end
352
+
353
+ ##
354
+ # Creates a number field input with the given name and options
355
+ #
356
+ # @macro [new] number_field
357
+ # @param [Symbol] name
358
+ # The name of the input to create.
359
+ # @param [Hash] options
360
+ # The HTML options to include in this field.
361
+ #
362
+ # @option options [String] :id
363
+ # Specifies a unique identifier for the field.
364
+ # @option options [String] :class
365
+ # Specifies the stylesheet class of the field.
366
+ # @option options [String] :name
367
+ # Specifies the name of the field.
368
+ # @option options [String] :accesskey
369
+ # Specifies a shortcut key to access the field.
370
+ # @option options [Integer] :tabindex
371
+ # Specifies the tab order of the field.
372
+ # @option options [Integer] :min
373
+ # Specifies the minimum value of the field.
374
+ # @option options [Integer] :max
375
+ # Specifies the maximum value of the field.
376
+ # @option options [Integer] :step
377
+ # Specifies the legal number intervals of the field.
378
+ # @option options [Boolean] :hidden
379
+ # Specifies whether or not the field is hidden from view.
380
+ # @option options [Boolean] :spellcheck
381
+ # Specifies whether or not the field should have it's spelling and grammar checked for errors.
382
+ # @option options [Boolean] :draggable
383
+ # Specifies whether or not the field is draggable. (true, false, :auto)
384
+ # @option options [String] :pattern
385
+ # Specifies the regular expression pattern that the field's value is checked against.
386
+ # @option options [Symbol] :autocomplete
387
+ # Specifies whether or not the field should have autocomplete enabled. (:on, :off)
388
+ # @option options [Boolean] :autofocus
389
+ # Specifies whether or not the field should automatically get focus when the page loads.
390
+ # @option options [Boolean] :required
391
+ # Specifies whether or not the field is required to be completeled before the form is submitted.
392
+ # @option options [Boolean] :readonly
393
+ # Specifies whether or not the field is read only.
394
+ # @option options [Boolean] :disabled
395
+ # Specifies whether or not the field is disabled.
396
+ #
397
+ # @return [String]
398
+ # Generated HTML with specified +options+
399
+ #
400
+ # @example
401
+ # number_field_tag :quanity, :class => 'numeric'
402
+ # # => <input name="quanity" class="numeric" type="number">
403
+ #
404
+ # number_field_tag :zip_code, :pattern => /[0-9]{5}/
405
+ # # => <input name="zip_code" pattern="[0-9]{5}" type="number">
406
+ #
407
+ # number_field_tag :credit_card, :autocomplete => :off
408
+ # # => <input name="credit_card" autocomplete="off" type="number">
409
+ #
410
+ # number_field_tag :age, :min => 18, :max => 120, :step => 1
411
+ # # => <input name="age" min="18" max="120" step="1" type="number">
412
+ #
413
+ # @api public
414
+ def number_field_tag(name, options={})
415
+ input_tag(:number, options.reverse_merge(:name => name))
416
+ end
417
+
418
+ ##
419
+ # Creates a telephone field input with the given name and options
420
+ #
421
+ # @macro text_field
422
+ #
423
+ # @example
424
+ # telephone_field_tag :phone_number, :class => 'string'
425
+ # # => <input name="phone_number" class="string" type="tel">
426
+ #
427
+ # telephone_field_tag :cell_phone, :tabindex => 1
428
+ # telephone_field_tag :work_phone, :tabindex => 2
429
+ # telephone_field_tag :home_phone, :tabindex => 3
430
+ #
431
+ # # => <input name="cell_phone" tabindex="1" type="tel">
432
+ # # => <input name="work_phone" tabindex="2" type="tel">
433
+ # # => <input name="home_phone" tabindex="3" type="tel">
434
+ #
435
+ # @api public
436
+ def telephone_field_tag(name, options={})
437
+ input_tag(:tel, options.reverse_merge(:name => name))
438
+ end
439
+ alias_method :phone_field_tag, :telephone_field_tag
440
+
441
+ ##
442
+ # Creates an email field input with the given name and options
443
+ #
444
+ # @macro text_field
445
+ #
446
+ # @example
447
+ # email_field_tag :email, :placeholder => 'you@example.com'
448
+ # # => <input name="email" placeholder="you@example.com" type="email">
449
+ #
450
+ # email_field_tag :email, :value => 'padrinorb@gmail.com', :readonly => true
451
+ # # => <input name="email" value="padrinorb@gmail.com" readonly type="email">
452
+ #
453
+ # @api public
454
+ def email_field_tag(name, options={})
455
+ input_tag(:email, options.reverse_merge(:name => name))
456
+ end
457
+
458
+ ##
459
+ # Creates a search field input with the given name and options
460
+ #
461
+ # @macro text_field
462
+ #
463
+ # @example
464
+ # search_field_tag :search, :placeholder => 'Search this website...'
465
+ # # => <input name="search" placeholder="Search this website..." type="search">
466
+ #
467
+ # search_field_tag :search, :maxlength => 15, :class => ['search', 'string']
468
+ # # => <input name="search" maxlength="15" class="search string">
469
+ #
470
+ # search_field_tag :search, :id => 'search'
471
+ # # => <input name="search" id="search" type="search">
472
+ #
473
+ # search_field_tag :search, :autofocus => true
474
+ # # => <input name="search" autofocus type="search">
475
+ #
476
+ # @api public
477
+ def search_field_tag(name, options={})
478
+ input_tag(:search, options.reverse_merge(:name => name))
479
+ end
480
+
481
+ ##
482
+ # Creates a url field input with the given name and options
483
+ #
484
+ # @macro text_field
485
+ #
486
+ # @example
487
+ # url_field_tag :favorite_website, :placeholder => 'http://padrinorb.com'
488
+ # <input name="favorite_website" placeholder="http://padrinorb.com." type="url">
489
+ #
490
+ # url_field_tag :home_page, :class => 'string url'
491
+ # <input name="home_page" class="string url", type="url">
492
+ #
493
+ # @api public
494
+ def url_field_tag(name, options={})
495
+ input_tag(:url, options.reverse_merge(:name => name))
309
496
  end
310
497
 
311
498
  ##
312
499
  # Constructs a hidden field input from the given options
313
500
  #
314
- # @macro input_field_doc
501
+ # @macro text_field
315
502
  #
316
503
  # @example
317
504
  # hidden_field_tag :session_key, :value => "__secret__"
@@ -325,7 +512,7 @@ module Padrino
325
512
  ##
326
513
  # Constructs a text area input from the given options
327
514
  #
328
- # @macro input_field_doc
515
+ # @macro text_field
329
516
  #
330
517
  # @example
331
518
  # text_area_tag :username, :class => 'long', :value => "Demo?"
@@ -339,7 +526,7 @@ module Padrino
339
526
  ##
340
527
  # Constructs a password field input from the given options
341
528
  #
342
- # @macro input_field_doc
529
+ # @macro text_field
343
530
  #
344
531
  # @example
345
532
  # password_field_tag :password, :class => 'long'
@@ -353,7 +540,7 @@ module Padrino
353
540
  ##
354
541
  # Constructs a check_box from the given options
355
542
  #
356
- # @macro input_field_doc
543
+ # @macro text_field
357
544
  #
358
545
  # @example
359
546
  # check_box_tag :remember_me, :value => 'Yes'
@@ -367,7 +554,7 @@ module Padrino
367
554
  ##
368
555
  # Constructs a radio_button from the given options
369
556
  #
370
- # @macro input_field_doc
557
+ # @macro text_field
371
558
  #
372
559
  # @example
373
560
  # radio_button_tag :remember_me, :value => 'true'
@@ -381,7 +568,7 @@ module Padrino
381
568
  ##
382
569
  # Constructs a file field input from the given options
383
570
  #
384
- # @macro input_field_doc
571
+ # @macro text_field
385
572
  #
386
573
  # @example
387
574
  # file_field_tag :photo, :class => 'long'
@@ -81,11 +81,11 @@ module Padrino
81
81
  # @api public
82
82
  def simple_format(text, options={})
83
83
  t = options.delete(:tag) || :p
84
- start_tag = tag(t, options.merge(:open => true))
84
+ start_tag = tag(t, options)
85
85
  text = text.to_s.dup
86
86
  text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
87
87
  text.gsub!(/\n\n+/, "</#{t}>\n\n#{start_tag}") # 2+ newline -> paragraph
88
- text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
88
+ text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br>') # 1 newline -> br
89
89
  text.insert 0, start_tag
90
90
  text << "</#{t}>"
91
91
  end
@@ -2,99 +2,212 @@ module Padrino
2
2
  module Helpers
3
3
  ##
4
4
  # Helpers related to producing html tags within templates.
5
- #
5
+ ##
6
6
  module TagHelpers
7
7
  ##
8
8
  # Tag values escaped to html entities
9
- #
9
+ ##
10
10
  ESCAPE_VALUES = {
11
11
  "<" => "&lt;",
12
12
  ">" => "&gt;",
13
13
  '"' => "&quot;"
14
14
  }
15
15
 
16
+ BOOLEAN_ATTRIBUTES = [
17
+ :autoplay,
18
+ :autofocus,
19
+ :formnovalidate,
20
+ :checked,
21
+ :disabled,
22
+ :hidden,
23
+ :loop,
24
+ :multiple,
25
+ :muted,
26
+ :readonly,
27
+ :required,
28
+ :selected
29
+ ]
30
+
16
31
  ##
17
- # Creates an html tag with given name, content and options
32
+ # Creates an HTML tag with given name, content, and options
18
33
  #
19
- # @overload content_tag(name, content, options)
20
- # @param [Symbol] name The html type of tag.
21
- # @param [String] content The contents in the tag.
22
- # @param [Hash] options The html options to include in this tag.
23
- # @overload content_tag(name, options, &block)
24
- # @param [Symbol] name The html type of tag.
25
- # @param [Hash] options The html options to include in this tag.
26
- # @param [Proc] block The block returning html content
34
+ # @overload content_tag(name, content, options = nil)
35
+ # @param [Symbol] name
36
+ # The name of the HTML tag to create.
37
+ # @param [String] content
38
+ # The content inside of the the tag.
39
+ # @param [Hash] options
40
+ # The HTML options to include in this tag.
27
41
  #
28
- # @return [String] The html generated for the tag.
42
+ # @overload content_tag(name, options = nil, &block)
43
+ # @param [Symbol] name
44
+ # The name of the HTML tag to create.
45
+ # @param [Hash] options
46
+ # The HTML options to include in this tag.
47
+ # @param [Proc] block
48
+ # The block returning HTML content.
49
+ #
50
+ # @macro [new] global_html_attributes
51
+ # @option options [String] :id
52
+ # Specifies a unique identifier for the element.
53
+ # @option options [String] :class
54
+ # Specifies the stylesheet class of the element.
55
+ # @option options [String] :title
56
+ # Specifies the title for the element.
57
+ # @option options [String] :accesskey
58
+ # Specifies a shortcut key to access the element.
59
+ # @option options [Symbol] :dropzone
60
+ # Specifies what happens when dragged items are dropped on the element. (:copy, :link, :move)
61
+ # @option options [Boolean] :hidden
62
+ # Specifies whether or not the element is hidden from view.
63
+ # @option options [Boolean] :draggable
64
+ # Specifies whether or not the element is draggable. (true, false, :auto)
65
+ # @option options [Boolean] :contenteditable
66
+ # Specifies whether or not the element is editable.
67
+ #
68
+ # @return [String]
69
+ # Generated HTML with specified +options+
29
70
  #
30
71
  # @example
31
- # content_tag(:p, "hello", :class => 'light')
32
- # content_tag(:p, :class => 'dark') { ... }
72
+ # content_tag(:p, 'Hello World', :class => 'light')
73
+ #
74
+ # # => <p class="light">
75
+ # # => Hello World
76
+ # # => </p>
77
+ #
78
+ # content_tag(:p, :class => 'dark') do
79
+ # link_to 'Padrino', 'http://www.padrinorb.com'
80
+ # end
81
+ #
82
+ # # => <p class="dark">
83
+ # # => <a href="http://www.padrinorb.com">Padrino</a>
84
+ # # => </p>
33
85
  #
34
86
  # @api public
35
- def content_tag(*args, &block)
36
- name = args.first
37
- options = args.extract_options!
38
- tag_html = block_given? ? capture_html(&block) : args[1]
39
- tag_result = tag(name, options.merge(:content => tag_html))
40
- block_is_template?(block) ? concat_content(tag_result) : tag_result
87
+ def content_tag(name, content = nil, options = nil, &block)
88
+ if block_given?
89
+ options = content if content.is_a?(Hash)
90
+ content = capture_html(&block)
91
+ end
92
+
93
+ content = content.join("\n") if content.respond_to?(:join)
94
+
95
+ output = "<#{name}#{tag_options(options) if options}>#{content}</#{name}>"
96
+ block_is_template?(block) ? concat_content(output) : output
41
97
  end
42
98
 
43
99
  ##
44
- # Creates an html input field with given type and options
100
+ # Creates an HTML input field with the given type and options
45
101
  #
46
102
  # @param [Symbol] type
47
- # The html type of tag to create.
103
+ # The type of input to create.
48
104
  # @param [Hash] options
49
- # The html options to include in this tag.
105
+ # The HTML options to include in this input.
50
106
  #
51
- # @return [String] The html for the input tag.
107
+ # @option options [String] :id
108
+ # Specifies a unique identifier for the input.
109
+ # @option options [String] :class
110
+ # Specifies the stylesheet class of the input.
111
+ # @option options [String] :name
112
+ # Specifies the name of the input.
113
+ # @option options [String] :accesskey
114
+ # Specifies a shortcut key to access the input.
115
+ # @option options [Integer] :tabindex
116
+ # Specifies the tab order of the input.
117
+ # @option options [Boolean] :hidden
118
+ # Specifies whether or not the input is hidden from view.
119
+ # @option options [Boolean] :spellcheck
120
+ # Specifies whether or not the input should have it's spelling and grammar checked for errors.
121
+ # @option options [Boolean] :draggable
122
+ # Specifies whether or not the input is draggable. (true, false, :auto)
123
+ # @option options [String] :pattern
124
+ # Specifies the regular expression pattern that the input's value is checked against.
125
+ # @option options [Symbol] :autocomplete
126
+ # Specifies whether or not the input should have autocomplete enabled. (:on, :off)
127
+ # @option options [Boolean] :autofocus
128
+ # Specifies whether or not the input should automatically get focus when the page loads.
129
+ # @option options [Boolean] :required
130
+ # Specifies whether or not the input is required to be completeled before the form is submitted.
131
+ # @option options [Boolean] :readonly
132
+ # Specifies whether or not the input is read only.
133
+ # @option options [Boolean] :disabled
134
+ # Specifies whether or not the input is disabled.
135
+ #
136
+ # @return [String]
137
+ # Generated HTML with specified +options+
52
138
  #
53
139
  # @example
54
- # input_tag :text, :class => "test"
55
- # input_tag :password, :size => "20"
140
+ # input_tag :text, :name => 'handle'
141
+ # # => <input type="test" name="handle">
142
+ #
143
+ # input_tag :password, :name => 'password', :size => 20
144
+ # # => <input type="password" name="password" size="20">
145
+ #
146
+ # input_tag :text, :name => 'username', :required => true, :autofocus => true
147
+ # # => <input type="text" name="username" required autofocus>
148
+ #
149
+ # input_tag :number, :name => 'credit_card', :autocomplete => :off
150
+ # # => <input type="number" autocomplete="off">
56
151
  #
57
152
  # @api semipublic
58
153
  def input_tag(type, options = {})
59
- options.reverse_merge!(:type => type)
60
- tag(:input, options)
154
+ tag(:input, options.reverse_merge!(:type => type))
61
155
  end
62
156
 
63
157
  ##
64
- # Creates an html tag with the given name and options
158
+ # Creates an HTML tag with the given name and options
65
159
  #
66
- # @param [Symbol] type
67
- # The html type of tag to create.
160
+ # @param [Symbol] name
161
+ # The name of the HTML tag to create.
68
162
  # @param [Hash] options
69
- # The html options to include in this tag.
163
+ # The HTML options to include in this tag.
164
+ #
165
+ # @macro global_html_attributes
70
166
  #
71
- # @return [String] The html for the input tag.
167
+ # @return [String]
168
+ # Generated HTML with specified +options+
72
169
  #
73
170
  # @example
74
- # tag(:br, :style => 'clear:both')
75
- # tag(:p, :content => "hello", :class => 'large')
171
+ # tag :hr, :class => 'dotted'
172
+ # # => <hr class="dotted">
173
+ #
174
+ # tag :input, :name => 'username', :type => :text
175
+ # # => <input name="username" type="text">
176
+ #
177
+ # tag :img, :src => 'images/pony.jpg', :alt => 'My Little Pony'
178
+ # # => <img src="images/pony.jpg" alt="My Little Pony">
179
+ #
180
+ # tag :img, :src => 'sinatra.jpg, :data => { :nsfw => false, :geo => [34.087, -118.407] }
181
+ # # => <img src="sinatra.jpg" data-nsfw="false" data-geo="34.087 -118.407">
76
182
  #
77
183
  # @api public
78
- def tag(name, options={})
79
- content, open_tag = options.delete(:content), options.delete(:open)
80
- content = content.join("\n") if content.respond_to?(:join)
81
- identity_tag_attributes.each { |attr| options[attr] = attr.to_s if options[attr] }
82
- html_attrs = options.map { |a, v| v.nil? || v == false ? nil : "#{a}=\"#{escape_value(v)}\"" }.compact.join(" ")
83
- base_tag = (html_attrs.present? ? "<#{name} #{html_attrs}" : "<#{name}")
84
- base_tag << (open_tag ? ">" : (content ? ">#{content}</#{name}>" : " />"))
184
+ def tag(name, options = nil)
185
+ "<#{name}#{tag_options(options) if options}>"
85
186
  end
86
187
 
87
188
  private
88
189
  ##
89
- # Returns a list of attributes which can only contain an identity value (i.e selected)
90
- #
91
- def identity_tag_attributes
92
- [:checked, :disabled, :selected, :multiple]
190
+ # Returns a compiled list of HTML attributes
191
+ ##
192
+ def tag_options(options)
193
+ return if options.blank?
194
+ attributes = []
195
+ options.each do |attribute, value|
196
+ next if value.nil? || value == false
197
+ if attribute == :data && value.is_a?(Hash)
198
+ value.each { |k, v| attributes << %[data-#{k.to_s.dasherize}="#{escape_value(v)}"] }
199
+ elsif BOOLEAN_ATTRIBUTES.include?(attribute)
200
+ attributes << attribute.to_s
201
+ else
202
+ attributes << %[#{attribute}="#{escape_value(value)}"]
203
+ end
204
+ end
205
+ " #{attributes.join(' ')}"
93
206
  end
94
207
 
95
208
  ##
96
209
  # Escape tag values to their HTML/XML entities.
97
- #
210
+ ##
98
211
  def escape_value(string)
99
212
  string.to_s.gsub(Regexp.union(*ESCAPE_VALUES.keys)){|c| ESCAPE_VALUES[c] }
100
213
  end