tennpipes-helper 3.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.rdoc +239 -0
  4. data/Rakefile +1 -0
  5. data/lib/tennpipes-helper.rb +62 -0
  6. data/lib/tennpipes-helper/asset_tag_helpers.rb +394 -0
  7. data/lib/tennpipes-helper/form_builder/abstract_form_builder.rb +279 -0
  8. data/lib/tennpipes-helper/form_builder/standard_form_builder.rb +40 -0
  9. data/lib/tennpipes-helper/form_helpers.rb +639 -0
  10. data/lib/tennpipes-helper/form_helpers/errors.rb +138 -0
  11. data/lib/tennpipes-helper/form_helpers/options.rb +98 -0
  12. data/lib/tennpipes-helper/form_helpers/security.rb +70 -0
  13. data/lib/tennpipes-helper/format_helpers.rb +372 -0
  14. data/lib/tennpipes-helper/locale/cs.yml +103 -0
  15. data/lib/tennpipes-helper/locale/da.yml +91 -0
  16. data/lib/tennpipes-helper/locale/de.yml +81 -0
  17. data/lib/tennpipes-helper/locale/en.yml +103 -0
  18. data/lib/tennpipes-helper/locale/es.yml +103 -0
  19. data/lib/tennpipes-helper/locale/fr.yml +79 -0
  20. data/lib/tennpipes-helper/locale/hu.yml +103 -0
  21. data/lib/tennpipes-helper/locale/it.yml +89 -0
  22. data/lib/tennpipes-helper/locale/ja.yml +103 -0
  23. data/lib/tennpipes-helper/locale/lv.yml +103 -0
  24. data/lib/tennpipes-helper/locale/nl.yml +82 -0
  25. data/lib/tennpipes-helper/locale/no.yml +91 -0
  26. data/lib/tennpipes-helper/locale/pl.yml +95 -0
  27. data/lib/tennpipes-helper/locale/pt_br.yml +103 -0
  28. data/lib/tennpipes-helper/locale/ro.yml +103 -0
  29. data/lib/tennpipes-helper/locale/ru.yml +103 -0
  30. data/lib/tennpipes-helper/locale/sv.yml +103 -0
  31. data/lib/tennpipes-helper/locale/tr.yml +103 -0
  32. data/lib/tennpipes-helper/locale/uk.yml +103 -0
  33. data/lib/tennpipes-helper/locale/zh_cn.yml +103 -0
  34. data/lib/tennpipes-helper/locale/zh_tw.yml +103 -0
  35. data/lib/tennpipes-helper/number_helpers.rb +283 -0
  36. data/lib/tennpipes-helper/output_helpers.rb +226 -0
  37. data/lib/tennpipes-helper/output_helpers/abstract_handler.rb +61 -0
  38. data/lib/tennpipes-helper/output_helpers/erb_handler.rb +27 -0
  39. data/lib/tennpipes-helper/output_helpers/haml_handler.rb +25 -0
  40. data/lib/tennpipes-helper/output_helpers/slim_handler.rb +18 -0
  41. data/lib/tennpipes-helper/render_helpers.rb +63 -0
  42. data/lib/tennpipes-helper/tag_helpers.rb +294 -0
  43. data/lib/tennpipes-helper/translation_helpers.rb +36 -0
  44. data/lib/tennpipes/rendering.rb +369 -0
  45. data/lib/tennpipes/rendering/erb_template.rb +40 -0
  46. data/lib/tennpipes/rendering/erubis_template.rb +66 -0
  47. data/lib/tennpipes/rendering/haml_template.rb +26 -0
  48. data/lib/tennpipes/rendering/slim_template.rb +20 -0
  49. data/test/fixtures/apps/render.rb +25 -0
  50. data/test/fixtures/apps/views/article/comment/show.slim +1 -0
  51. data/test/fixtures/apps/views/blog/post.erb +1 -0
  52. data/test/fixtures/apps/views/layouts/specific.erb +1 -0
  53. data/test/fixtures/apps/views/test/post.erb +1 -0
  54. data/test/fixtures/layouts/layout.erb +1 -0
  55. data/test/fixtures/markup_app/app.rb +87 -0
  56. data/test/fixtures/markup_app/views/button_to.erb +8 -0
  57. data/test/fixtures/markup_app/views/button_to.haml +5 -0
  58. data/test/fixtures/markup_app/views/button_to.slim +6 -0
  59. data/test/fixtures/markup_app/views/capture_concat.erb +14 -0
  60. data/test/fixtures/markup_app/views/capture_concat.haml +12 -0
  61. data/test/fixtures/markup_app/views/capture_concat.slim +12 -0
  62. data/test/fixtures/markup_app/views/content_for.erb +23 -0
  63. data/test/fixtures/markup_app/views/content_for.haml +19 -0
  64. data/test/fixtures/markup_app/views/content_for.slim +19 -0
  65. data/test/fixtures/markup_app/views/content_tag.erb +13 -0
  66. data/test/fixtures/markup_app/views/content_tag.haml +11 -0
  67. data/test/fixtures/markup_app/views/content_tag.slim +11 -0
  68. data/test/fixtures/markup_app/views/current_engine.erb +5 -0
  69. data/test/fixtures/markup_app/views/current_engine.haml +5 -0
  70. data/test/fixtures/markup_app/views/current_engine.slim +5 -0
  71. data/test/fixtures/markup_app/views/fields_for.erb +20 -0
  72. data/test/fixtures/markup_app/views/fields_for.haml +15 -0
  73. data/test/fixtures/markup_app/views/fields_for.slim +15 -0
  74. data/test/fixtures/markup_app/views/form_for.erb +72 -0
  75. data/test/fixtures/markup_app/views/form_for.haml +59 -0
  76. data/test/fixtures/markup_app/views/form_for.slim +59 -0
  77. data/test/fixtures/markup_app/views/form_tag.erb +95 -0
  78. data/test/fixtures/markup_app/views/form_tag.haml +78 -0
  79. data/test/fixtures/markup_app/views/form_tag.slim +79 -0
  80. data/test/fixtures/markup_app/views/link_to.erb +5 -0
  81. data/test/fixtures/markup_app/views/link_to.haml +4 -0
  82. data/test/fixtures/markup_app/views/link_to.slim +4 -0
  83. data/test/fixtures/markup_app/views/mail_to.erb +3 -0
  84. data/test/fixtures/markup_app/views/mail_to.haml +3 -0
  85. data/test/fixtures/markup_app/views/mail_to.slim +3 -0
  86. data/test/fixtures/markup_app/views/meta_tag.erb +3 -0
  87. data/test/fixtures/markup_app/views/meta_tag.haml +3 -0
  88. data/test/fixtures/markup_app/views/meta_tag.slim +3 -0
  89. data/test/fixtures/markup_app/views/partials/_erb.erb +1 -0
  90. data/test/fixtures/markup_app/views/partials/_haml.haml +1 -0
  91. data/test/fixtures/markup_app/views/partials/_slim.slim +1 -0
  92. data/test/fixtures/markup_app/views/simple_partial.erb +1 -0
  93. data/test/fixtures/markup_app/views/simple_partial.haml +1 -0
  94. data/test/fixtures/markup_app/views/simple_partial.slim +1 -0
  95. data/test/fixtures/render_app/app.rb +110 -0
  96. data/test/fixtures/render_app/views/_deep.erb +3 -0
  97. data/test/fixtures/render_app/views/_deep.haml +2 -0
  98. data/test/fixtures/render_app/views/_deep.slim +2 -0
  99. data/test/fixtures/render_app/views/_partial_block_erb.erb +10 -0
  100. data/test/fixtures/render_app/views/_partial_block_haml.haml +7 -0
  101. data/test/fixtures/render_app/views/_partial_block_slim.slim +7 -0
  102. data/test/fixtures/render_app/views/_unsafe.html.builder +2 -0
  103. data/test/fixtures/render_app/views/_unsafe_object.html.builder +2 -0
  104. data/test/fixtures/render_app/views/current_engine.haml +5 -0
  105. data/test/fixtures/render_app/views/current_engines/_erb.erb +1 -0
  106. data/test/fixtures/render_app/views/current_engines/_haml.haml +1 -0
  107. data/test/fixtures/render_app/views/current_engines/_slim.slim +1 -0
  108. data/test/fixtures/render_app/views/dive_inner_erb.erb +3 -0
  109. data/test/fixtures/render_app/views/dive_inner_haml.haml +2 -0
  110. data/test/fixtures/render_app/views/dive_inner_slim.slim +2 -0
  111. data/test/fixtures/render_app/views/dive_outer_erb.erb +3 -0
  112. data/test/fixtures/render_app/views/dive_outer_haml.haml +2 -0
  113. data/test/fixtures/render_app/views/dive_outer_slim.slim +2 -0
  114. data/test/fixtures/render_app/views/double_capture_erb.erb +3 -0
  115. data/test/fixtures/render_app/views/double_capture_haml.haml +2 -0
  116. data/test/fixtures/render_app/views/double_capture_slim.slim +2 -0
  117. data/test/fixtures/render_app/views/erb/test.erb +1 -0
  118. data/test/fixtures/render_app/views/explicit_engine.haml +5 -0
  119. data/test/fixtures/render_app/views/haml/test.haml +1 -0
  120. data/test/fixtures/render_app/views/render_block_erb.erb +5 -0
  121. data/test/fixtures/render_app/views/render_block_haml.haml +4 -0
  122. data/test/fixtures/render_app/views/render_block_slim.slim +4 -0
  123. data/test/fixtures/render_app/views/ruby_block_capture_erb.erb +1 -0
  124. data/test/fixtures/render_app/views/ruby_block_capture_haml.haml +1 -0
  125. data/test/fixtures/render_app/views/ruby_block_capture_slim.slim +1 -0
  126. data/test/fixtures/render_app/views/template/_user.haml +7 -0
  127. data/test/fixtures/render_app/views/template/haml_template.haml +1 -0
  128. data/test/fixtures/render_app/views/template/some_template.haml +2 -0
  129. data/test/fixtures/render_app/views/wrong_capture_erb.erb +3 -0
  130. data/test/fixtures/render_app/views/wrong_capture_haml.haml +2 -0
  131. data/test/fixtures/render_app/views/wrong_capture_slim.slim +2 -0
  132. data/test/helper.rb +88 -0
  133. data/test/test_asset_tag_helpers.rb +401 -0
  134. data/test/test_form_builder.rb +1216 -0
  135. data/test/test_form_helpers.rb +1056 -0
  136. data/test/test_format_helpers.rb +251 -0
  137. data/test/test_helpers.rb +10 -0
  138. data/test/test_locale.rb +20 -0
  139. data/test/test_number_helpers.rb +142 -0
  140. data/test/test_output_helpers.rb +157 -0
  141. data/test/test_render_helpers.rb +225 -0
  142. data/test/test_rendering.rb +706 -0
  143. data/test/test_rendering_extensions.rb +14 -0
  144. data/test/test_tag_helpers.rb +131 -0
  145. metadata +299 -0
@@ -0,0 +1,279 @@
1
+ module Tennpipes
2
+ module Helpers
3
+ module FormBuilder
4
+ # Base class for Tennpipes Form Builder
5
+ class AbstractFormBuilder
6
+ attr_accessor :template, :object, :multipart
7
+ attr_reader :namespace, :is_nested, :parent_form, :nested_index, :attributes_name, :model_name
8
+
9
+ def initialize(template, object, options={})
10
+ @template = template
11
+ fail "FormBuilder template must be initialized" unless template
12
+ @object = object.kind_of?(Symbol) ? build_object(object) : object
13
+ fail "FormBuilder object must be present. If there's no object, use a symbol instead (i.e. :user)" unless object
14
+ @options = options
15
+ @namespace = options[:namespace]
16
+ @model_name = options[:as] || @object.class.to_s.underscore.tr('/', '_')
17
+ nested = options[:nested]
18
+ if @is_nested = nested && (nested_parent = nested[:parent]) && nested_parent.respond_to?(:object)
19
+ @parent_form = nested_parent
20
+ @nested_index = nested[:index]
21
+ @attributes_name = "#{nested[:association]}_attributes"
22
+ end
23
+ end
24
+
25
+ def error_messages(*params)
26
+ @template.error_messages_for object, *params
27
+ end
28
+
29
+ def error_message_on(field, options={})
30
+ @template.error_message_on object, field, options
31
+ end
32
+
33
+ def label(field, options={}, &block)
34
+ options[:id] ||= nil
35
+ options[:caption] ||= I18n.t("#{model_name}.attributes.#{field}", :count => 1, :default => field.to_s.humanize, :scope => :models) + ': '
36
+ @template.label_tag(field_id(field), default_options(field, options), &block)
37
+ end
38
+
39
+ def hidden_field(field, options={})
40
+ @template.hidden_field_tag field_name(field), default_options(field, options)
41
+ end
42
+
43
+ def text_field(field, options={})
44
+ @template.text_field_tag field_name(field), default_options(field, options)
45
+ end
46
+
47
+ def number_field(field, options={})
48
+ @template.number_field_tag field_name(field), default_options(field, options)
49
+ end
50
+
51
+ def telephone_field(field, options={})
52
+ @template.telephone_field_tag field_name(field), default_options(field, options)
53
+ end
54
+ alias_method :phone_field, :telephone_field
55
+
56
+ def email_field(field, options={})
57
+ @template.email_field_tag field_name(field), default_options(field, options)
58
+ end
59
+
60
+ def search_field(field, options={})
61
+ @template.search_field_tag field_name(field), default_options(field, options)
62
+ end
63
+
64
+ def url_field(field, options={})
65
+ @template.url_field_tag field_name(field), default_options(field, options)
66
+ end
67
+
68
+ def text_area(field, options={})
69
+ @template.text_area_tag field_name(field), default_options(field, options)
70
+ end
71
+
72
+ def password_field(field, options={})
73
+ @template.password_field_tag field_name(field), default_options(field, options)
74
+ end
75
+
76
+ def select(field, options={})
77
+ @template.select_tag field_name(field), default_options(field, options)
78
+ end
79
+
80
+ def check_box_group(field, options={})
81
+ labeled_group(field, options) do |attributes|
82
+ @template.check_box_tag(field_name(field)+'[]', attributes)
83
+ end
84
+ end
85
+
86
+ def radio_button_group(field, options={})
87
+ labeled_group(field, options) do |attributes|
88
+ @template.radio_button_tag(field_name(field), attributes)
89
+ end
90
+ end
91
+
92
+ def check_box(field, options={})
93
+ options = default_options(field, options, :value => '1')
94
+ options[:checked] = true if is_checked?(field, options)
95
+ name = field_name(field)
96
+ html = @template.hidden_field_tag(name, :value => options.delete(:uncheck_value) || '0')
97
+ html << @template.check_box_tag(name, options)
98
+ end
99
+
100
+ def radio_button(field, options={})
101
+ options = default_options(field, options)
102
+ options[:checked] = true if is_checked?(field, options)
103
+ options[:id] = field_id(field, options[:value])
104
+ @template.radio_button_tag field_name(field), options
105
+ end
106
+
107
+ def file_field(field, options={})
108
+ self.multipart = true
109
+ @template.file_field_tag field_name(field), default_options(field, options).except(:value)
110
+ end
111
+
112
+ def submit(*args)
113
+ @template.submit_tag *args
114
+ end
115
+
116
+ def image_submit(source, options={})
117
+ @template.image_submit_tag source, options
118
+ end
119
+
120
+ ##
121
+ # Supports nested fields for a child model within a form.
122
+ # f.fields_for :addresses
123
+ # f.fields_for :addresses, address
124
+ # f.fields_for :addresses, @addresses
125
+ # f.fields_for :addresses, address, index: i
126
+ def fields_for(child_association, collection=nil, options={}, &block)
127
+ default_collection = self.object.send(child_association)
128
+ collection ||= default_collection
129
+ include_index = default_collection.respond_to?(:each)
130
+
131
+ nested_options = { :parent => self, :association => child_association }
132
+ Array(collection).each_with_index.inject(ActiveSupport::SafeBuffer.new) do |all,(child_instance,index)|
133
+ nested_options[:index] = options[:index] || (include_index ? index : nil)
134
+ all << @template.fields_for(child_instance, { :nested => nested_options, :builder => self.class }, &block) << "\n"
135
+ end
136
+ end
137
+
138
+ def csrf_token_field
139
+ @template.csrf_token_field
140
+ end
141
+
142
+ protected
143
+
144
+ # Returns the known field types for a Formbuilder.
145
+ def self.field_types
146
+ [:hidden_field, :text_field, :text_area, :password_field, :file_field, :radio_button, :check_box, :select]
147
+ end
148
+
149
+ ##
150
+ # Returns the name for the given field.
151
+ # field_name(:username) => "user[username]"
152
+ # field_name(:number) => "user[telephone_attributes][number]"
153
+ # field_name(:street) => "user[addresses_attributes][0][street]"
154
+ def field_name(field=nil)
155
+ result = field_name_fragment
156
+ result << "[#{field}]" unless field.blank?
157
+ result
158
+ end
159
+
160
+ ##
161
+ # Returns the id for the given field.
162
+ # field_id(:username) => "user_username"
163
+ # field_id(:gender, :male) => "user_gender_male"
164
+ # field_name(:number) => "user_telephone_attributes_number"
165
+ # field_name(:street) => "user_addresses_attributes_0_street"
166
+ def field_id(field=nil, value=nil)
167
+ result = (namespace && !is_nested) ? "#{namespace}_" : ''
168
+ result << field_id_fragment
169
+ result << "_#{field}" unless field.blank?
170
+ result << "_#{value}" unless value.blank?
171
+ result
172
+ end
173
+
174
+ ##
175
+ # Returns the child object if it exists.
176
+ #
177
+ def nested_object_id
178
+ is_nested && object.respond_to?(:new_record?) && !object.new_record? && object.id
179
+ end
180
+
181
+ ##
182
+ # Returns the value for the object's field.
183
+ #
184
+ def field_value(field)
185
+ @object.respond_to?(field) ? @object.send(field) : ''
186
+ end
187
+
188
+ ##
189
+ # Returns a record from template instance or create a record of specified class.
190
+ #
191
+ def build_object(symbol)
192
+ @template.instance_variable_get("@#{symbol}") || symbol.to_s.camelize.constantize.new
193
+ end
194
+
195
+ ##
196
+ # Builds a group of labels for radios or checkboxes.
197
+ #
198
+ def labeled_group(field, options={})
199
+ options = { :id => field_id(field), :selected => field_value(field) }.update(options)
200
+ options.update(error_class(field)){ |_,*values| values.compact.join(' ') }
201
+ selected_values = resolve_checked_values(field, options)
202
+ variants_for_group(options).inject(ActiveSupport::SafeBuffer.new) do |html, (caption,value)|
203
+ variant_id = "#{options[:id]}_#{value}"
204
+ attributes = { :value => value, :id => variant_id, :checked => selected_values.include?(value) }
205
+ caption = yield(attributes) << ' ' << caption
206
+ html << @template.label_tag("#{field_name(field)}[]", :for => variant_id, :caption => caption)
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ def is_checked?(field, options)
213
+ !options.has_key?(:checked) && [options[:value].to_s, 'true'].include?(field_value(field).to_s)
214
+ end
215
+
216
+ def variants_for_group(options)
217
+ if variants = options[:options]
218
+ variants.map{ |caption, value| [caption.to_s, (value||caption).to_s] }
219
+ elsif collection = options[:collection]
220
+ collection.map{ |variant| field_values(variant, options) }
221
+ else
222
+ []
223
+ end
224
+ end
225
+
226
+ def resolve_checked_values(field, options)
227
+ selected_values = Array(options[:selected] || field_value(field))
228
+ if options[:collection]
229
+ _, id_method = *field_methods(options)
230
+ selected_values.map do |value|
231
+ (value.respond_to?(id_method) ? value.send(id_method) : value).to_s
232
+ end
233
+ else
234
+ selected_values
235
+ end
236
+ end
237
+
238
+ def field_methods(options)
239
+ options[:fields] || [:name, :id]
240
+ end
241
+
242
+ def field_values(object, options)
243
+ field_methods(options).map{ |field| object.send(field).to_s }
244
+ end
245
+
246
+ def field_name_fragment
247
+ if is_nested
248
+ fragment = parent_form.field_name.dup << "[#{attributes_name}"
249
+ fragment << "][#{nested_index}" if nested_index
250
+ fragment << "]"
251
+ else
252
+ "#{model_name}"
253
+ end
254
+ end
255
+
256
+ def field_id_fragment
257
+ if is_nested
258
+ fragment = parent_form.field_id.dup << "_#{attributes_name}"
259
+ fragment << "_#{nested_index}" if nested_index
260
+ fragment
261
+ else
262
+ "#{model_name}"
263
+ end
264
+ end
265
+
266
+ def error_class(field)
267
+ error = @object.errors[field] if @object.respond_to?(:errors)
268
+ error.blank? ? {} : { :class => 'invalid' }
269
+ end
270
+
271
+ def default_options(field, options, defaults={})
272
+ { :value => field_value(field),
273
+ :id => field_id(field)
274
+ }.update(defaults).update(options).update(error_class(field)){ |_,*values| values.compact.join(' ') }
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/abstract_form_builder') unless defined?(AbstractFormBuilder)
2
+
3
+ module Tennpipes
4
+ module Helpers
5
+ module FormBuilder
6
+ class StandardFormBuilder < AbstractFormBuilder
7
+ ##
8
+ # StandardFormBuilder
9
+ #
10
+ # text_field_block(:username, { :class => 'long' }, { :class => 'wide-label' })
11
+ # text_area_block(:summary, { :class => 'long' }, { :class => 'wide-label' })
12
+ # password_field_block(:password, { :class => 'long' }, { :class => 'wide-label' })
13
+ # file_field_block(:photo, { :class => 'long' }, { :class => 'wide-label' })
14
+ # check_box_block(:remember_me, { :class => 'long' }, { :class => 'wide-label' })
15
+ # select_block(:color, :options => ['green', 'black'])
16
+ #
17
+ (self.field_types - [ :hidden_field, :radio_button ]).each do |field_type|
18
+ class_eval <<-EOF
19
+ def #{field_type}_block(field, options={}, label_options={})
20
+ label_options.reverse_merge!(:caption => options.delete(:caption)) if options[:caption]
21
+ field_html = label(field, label_options)
22
+ field_html << #{field_type}(field, options)
23
+ @template.content_tag(:p, field_html)
24
+ end
25
+ EOF
26
+ end
27
+
28
+ def submit_block(caption, options={})
29
+ submit_html = self.submit(caption, options)
30
+ @template.content_tag(:p, submit_html)
31
+ end
32
+
33
+ def image_submit_block(source, options={})
34
+ submit_html = self.image_submit(source, options)
35
+ @template.content_tag(:p, submit_html)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,639 @@
1
+ require 'tennpipes-helper/form_helpers/errors'
2
+ require 'tennpipes-helper/form_helpers/options'
3
+ require 'tennpipes-helper/form_helpers/security'
4
+
5
+ module Tennpipes
6
+ module Helpers
7
+ ##
8
+ # Helpers related to producing form related tags and inputs into templates.
9
+ #
10
+ module FormHelpers
11
+ def self.included(base)
12
+ base.send(:include, FormHelpers::Errors)
13
+ base.send(:include, FormHelpers::Options)
14
+ base.send(:include, FormHelpers::Security)
15
+ end
16
+
17
+ ##
18
+ # Constructs a form for object using given or default form_builder.
19
+ #
20
+ # @param [Object] object
21
+ # The object for which the form is being built.
22
+ # @param [String] URL
23
+ # The url this form will submit to.
24
+ # @param [Hash] options
25
+ # The settings associated with this form.
26
+ # Accepts a :namespace option that will be prepended to the id attributes of the form's elements.
27
+ # Also accepts HTML options.
28
+ # @option settings [String] :builder ("StandardFormBuilder")
29
+ # The FormBuilder class to use such as StandardFormBuilder.
30
+ # @option settings [Symbol] :as
31
+ # Sets custom form object name.
32
+ # @param [Proc] block
33
+ # The fields and content inside this form.
34
+ #
35
+ # @yield [AbstractFormBuilder] The form builder used to compose fields.
36
+ #
37
+ # @return [String] The html object-backed form with the specified options and input fields.
38
+ #
39
+ # @example
40
+ # form_for :user, '/register' do |f| ... end
41
+ # form_for @user, '/register', :id => 'register' do |f| ... end
42
+ # form_for @user, '/register', :as => :customer do |f| ... end
43
+ #
44
+ def form_for(object, url, options={}, &block)
45
+ instance = builder_instance(object, options)
46
+ # this can erect instance.multipart flag if the block calls instance.file_field
47
+ html = capture_html(instance, &block)
48
+ options = { :multipart => instance.multipart }.update(options.except(:namespace, :as))
49
+ form_tag(url, options) { html }
50
+ end
51
+
52
+ ##
53
+ # Constructs form fields for an object using given or default form_builder.
54
+ # Used within an existing form to allow alternate objects within one form.
55
+ #
56
+ # @param [Object] object
57
+ # The object for which the fields are being built.
58
+ # @param [Hash] options
59
+ # The settings associated with these fields. Accepts HTML options.
60
+ # @param [Proc] block
61
+ # The content inside this set of fields.
62
+ #
63
+ # @return [String] The html fields with the specified options.
64
+ #
65
+ # @example
66
+ # fields_for @user.assignment do |assignment| ... end
67
+ # fields_for :assignment do |assigment| ... end
68
+ #
69
+ def fields_for(object, options={}, &block)
70
+ instance = builder_instance(object, options)
71
+ fields_html = capture_html(instance, &block)
72
+ fields_html << instance.hidden_field(:id) if instance.send(:nested_object_id)
73
+ concat_content fields_html
74
+ end
75
+
76
+ ##
77
+ # Constructs a form without object based on options.
78
+ #
79
+ # @param [String] url
80
+ # The URL this form will submit to.
81
+ # @param [Hash] options
82
+ # The html options associated with this form.
83
+ # @param [Proc] block
84
+ # The fields and content inside this form.
85
+ #
86
+ # @return [String] The HTML form with the specified options and input fields.
87
+ #
88
+ # @example
89
+ # form_tag '/register', :class => "registration_form" do ... end
90
+ #
91
+ def form_tag(url, options={}, &block)
92
+ options = {
93
+ :action => url,
94
+ :protect_from_csrf => is_protected_from_csrf?,
95
+ 'accept-charset' => 'UTF-8'
96
+ }.update(options)
97
+ options[:enctype] = 'multipart/form-data' if options.delete(:multipart)
98
+
99
+ if (desired_method = options[:method]) =~ /get/i
100
+ options.delete(:protect_from_csrf)
101
+ else
102
+ options[:method] = 'post'
103
+ end
104
+ inner_form_html = hidden_form_method_field(desired_method)
105
+ inner_form_html << csrf_token_field if options.delete(:protect_from_csrf)
106
+ concat_content content_tag(:form, inner_form_html << capture_html(&block), options)
107
+ end
108
+
109
+ ##
110
+ # Returns the hidden method field for 'put' and 'delete' forms.
111
+ # Only 'get' and 'post' are allowed within browsers;
112
+ # 'put' and 'delete' are just specified using hidden fields with form action still 'put'.
113
+ #
114
+ # @param [String] desired_method
115
+ # The method this hidden field represents (i.e put or delete).
116
+ #
117
+ # @return [String] The hidden field representing the +desired_method+ for the form.
118
+ #
119
+ # @example
120
+ # # Generate: <input name="_method" value="delete" />
121
+ # hidden_form_method_field('delete')
122
+ #
123
+ def hidden_form_method_field(desired_method)
124
+ return ActiveSupport::SafeBuffer.new if desired_method.blank? || desired_method.to_s =~ /get|post/i
125
+ hidden_field_tag(:_method, :value => desired_method)
126
+ end
127
+
128
+ ##
129
+ # Constructs a field_set to group fields with given options.
130
+ #
131
+ # @overload field_set_tag(legend=nil, options={}, &block)
132
+ # @param [String] legend The legend caption for the fieldset
133
+ # @param [Hash] options The html options for the fieldset.
134
+ # @param [Proc] block The content inside the fieldset.
135
+ # @overload field_set_tag(options={}, &block)
136
+ # @param [Hash] options The html options for the fieldset.
137
+ # @param [Proc] block The content inside the fieldset.
138
+ #
139
+ # @return [String] The html for the fieldset tag based on given +options+.
140
+ #
141
+ # @example
142
+ # field_set_tag(:class => "office-set") { }
143
+ # field_set_tag("Office", :class => 'office-set') { }
144
+ #
145
+ def field_set_tag(*args, &block)
146
+ options = args.extract_options!
147
+ legend_text = args.first
148
+ legend_html = legend_text.blank? ? ActiveSupport::SafeBuffer.new : content_tag(:legend, legend_text)
149
+ concat_content content_tag(:fieldset, legend_html << capture_html(&block), options)
150
+ end
151
+
152
+ ##
153
+ # Constructs a label tag from the given options.
154
+ #
155
+ # @param [String] name
156
+ # The name of the field to label.
157
+ # @param [Hash] options
158
+ # The html options for this label.
159
+ # @option options :caption
160
+ # The caption for this label.
161
+ # @param [Proc] block
162
+ # The content to be inserted into the label.
163
+ #
164
+ # @return [String] The html for this label with the given +options+.
165
+ #
166
+ # @example
167
+ # label_tag :username, :class => 'long-label'
168
+ # label_tag :username, :class => 'long-label' do ... end
169
+ #
170
+ def label_tag(name, options={}, &block)
171
+ options = { :caption => "#{name.to_s.humanize}: ", :for => name }.update(options)
172
+ caption_text = ActiveSupport::SafeBuffer.new << options.delete(:caption)
173
+ caption_text << "<span class='required'>*</span> ".html_safe if options.delete(:required)
174
+
175
+ if block_given?
176
+ concat_content content_tag(:label, caption_text << capture_html(&block), options)
177
+ else
178
+ content_tag(:label, caption_text, options)
179
+ end
180
+ end
181
+
182
+ ##
183
+ # Creates a text field input with the given name and options.
184
+ #
185
+ # @macro [new] text_field
186
+ # @param [Symbol] name
187
+ # The name of the input to create.
188
+ # @param [Hash] options
189
+ # The HTML options to include in this field.
190
+ #
191
+ # @option options [String] :id
192
+ # Specifies a unique identifier for the field.
193
+ # @option options [String] :class
194
+ # Specifies the stylesheet class of the field.
195
+ # @option options [String] :name
196
+ # Specifies the name of the field.
197
+ # @option options [String] :accesskey
198
+ # Specifies a shortcut key to access the field.
199
+ # @option options [Integer] :tabindex
200
+ # Specifies the tab order of the field.
201
+ # @option options [Integer] :maxlength
202
+ # Specifies the maximum length, in characters, of the field.
203
+ # @option options [Integer] :size
204
+ # Specifies the width, in characters, of the field.
205
+ # @option options [String] :placeholder
206
+ # Specifies a short hint that describes the expected value of the field.
207
+ # @option options [Boolean] :hidden
208
+ # Specifies whether or not the field is hidden from view.
209
+ # @option options [Boolean] :spellcheck
210
+ # Specifies whether or not the field should have it's spelling and grammar checked for errors.
211
+ # @option options [Boolean] :draggable
212
+ # Specifies whether or not the field is draggable. (true, false, :auto).
213
+ # @option options [String] :pattern
214
+ # Specifies the regular expression pattern that the field's value is checked against.
215
+ # @option options [Symbol] :autocomplete
216
+ # Specifies whether or not the field should have autocomplete enabled. (:on, :off).
217
+ # @option options [Boolean] :autofocus
218
+ # Specifies whether or not the field should automatically get focus when the page loads.
219
+ # @option options [Boolean] :required
220
+ # Specifies whether or not the field is required to be completed before the form is submitted.
221
+ # @option options [Boolean] :readonly
222
+ # Specifies whether or not the field is read only.
223
+ # @option options [Boolean] :disabled
224
+ # Specifies whether or not the field is disabled.
225
+ #
226
+ # @return [String]
227
+ # Generated HTML with specified +options+.
228
+ #
229
+ # @example
230
+ # text_field_tag :first_name, :maxlength => 40, :required => true
231
+ # # => <input name="first_name" maxlength="40" required type="text" />
232
+ #
233
+ # text_field_tag :last_name, :class => 'string', :size => 40
234
+ # # => <input name="last_name" class="string" size="40" type="text" />
235
+ #
236
+ # text_field_tag :username, :placeholder => 'Your Username'
237
+ # # => <input name="username" placeholder="Your Username" type="text" />
238
+ #
239
+ def text_field_tag(name, options={})
240
+ input_tag(:text, { :name => name }.update(options))
241
+ end
242
+
243
+ ##
244
+ # Creates a number field input with the given name and options.
245
+ #
246
+ # @macro [new] number_field
247
+ # @param [Symbol] name
248
+ # The name of the input to create.
249
+ # @param [Hash] options
250
+ # The HTML options to include in this field.
251
+ #
252
+ # @option options [String] :id
253
+ # Specifies a unique identifier for the field.
254
+ # @option options [String] :class
255
+ # Specifies the stylesheet class of the field.
256
+ # @option options [String] :name
257
+ # Specifies the name of the field.
258
+ # @option options [String] :accesskey
259
+ # Specifies a shortcut key to access the field.
260
+ # @option options [Integer] :tabindex
261
+ # Specifies the tab order of the field.
262
+ # @option options [Integer] :min
263
+ # Specifies the minimum value of the field.
264
+ # @option options [Integer] :max
265
+ # Specifies the maximum value of the field.
266
+ # @option options [Integer] :step
267
+ # Specifies the legal number intervals of the field.
268
+ # @option options [Boolean] :hidden
269
+ # Specifies whether or not the field is hidden from view.
270
+ # @option options [Boolean] :spellcheck
271
+ # Specifies whether or not the field should have it's spelling and grammar checked for errors.
272
+ # @option options [Boolean] :draggable
273
+ # Specifies whether or not the field is draggable. (true, false, :auto).
274
+ # @option options [String] :pattern
275
+ # Specifies the regular expression pattern that the field's value is checked against.
276
+ # @option options [Symbol] :autocomplete
277
+ # Specifies whether or not the field should have autocomplete enabled. (:on, :off).
278
+ # @option options [Boolean] :autofocus
279
+ # Specifies whether or not the field should automatically get focus when the page loads.
280
+ # @option options [Boolean] :required
281
+ # Specifies whether or not the field is required to be completeled before the form is submitted.
282
+ # @option options [Boolean] :readonly
283
+ # Specifies whether or not the field is read only.
284
+ # @option options [Boolean] :disabled
285
+ # Specifies whether or not the field is disabled.
286
+ #
287
+ # @return [String]
288
+ # Generated HTML with specified +options+.
289
+ #
290
+ # @example
291
+ # number_field_tag :quantity, :class => 'numeric'
292
+ # # => <input name="quantity" class="numeric" type="number" />
293
+ #
294
+ # number_field_tag :zip_code, :pattern => /[0-9]{5}/
295
+ # # => <input name="zip_code" pattern="[0-9]{5}" type="number" />
296
+ #
297
+ # number_field_tag :credit_card, :autocomplete => :off
298
+ # # => <input name="credit_card" autocomplete="off" type="number" />
299
+ #
300
+ # number_field_tag :age, :min => 18, :max => 120, :step => 1
301
+ # # => <input name="age" min="18" max="120" step="1" type="number" />
302
+ #
303
+ def number_field_tag(name, options={})
304
+ input_tag(:number, { :name => name }.update(options))
305
+ end
306
+
307
+ ##
308
+ # Creates a telephone field input with the given name and options.
309
+ #
310
+ # @macro text_field
311
+ #
312
+ # @example
313
+ # telephone_field_tag :phone_number, :class => 'string'
314
+ # # => <input name="phone_number" class="string" type="tel" />
315
+ #
316
+ # telephone_field_tag :cell_phone, :tabindex => 1
317
+ # telephone_field_tag :work_phone, :tabindex => 2
318
+ # telephone_field_tag :home_phone, :tabindex => 3
319
+ #
320
+ # # => <input name="cell_phone" tabindex="1" type="tel" />
321
+ # # => <input name="work_phone" tabindex="2" type="tel" />
322
+ # # => <input name="home_phone" tabindex="3" type="tel" />
323
+ #
324
+ def telephone_field_tag(name, options={})
325
+ input_tag(:tel, { :name => name }.update(options))
326
+ end
327
+ alias_method :phone_field_tag, :telephone_field_tag
328
+
329
+ ##
330
+ # Creates an email field input with the given name and options.
331
+ #
332
+ # @macro text_field
333
+ #
334
+ # @example
335
+ # email_field_tag :email, :placeholder => 'you@example.com'
336
+ # # => <input name="email" placeholder="you@example.com" type="email" />
337
+ #
338
+ # email_field_tag :email, :value => 'tennpipesrb@gmail.com', :readonly => true
339
+ # # => <input name="email" value="tennpipesrb@gmail.com" readonly type="email" />
340
+ #
341
+ def email_field_tag(name, options={})
342
+ input_tag(:email, { :name => name }.update(options))
343
+ end
344
+
345
+ ##
346
+ # Creates a search field input with the given name and options.
347
+ #
348
+ # @macro text_field
349
+ #
350
+ # @example
351
+ # search_field_tag :search, :placeholder => 'Search this website...'
352
+ # # => <input name="search" placeholder="Search this website..." type="search" />
353
+ #
354
+ # search_field_tag :search, :maxlength => 15, :class => ['search', 'string']
355
+ # # => <input name="search" maxlength="15" class="search string" />
356
+ #
357
+ # search_field_tag :search, :id => 'search'
358
+ # # => <input name="search" id="search" type="search" />
359
+ #
360
+ # search_field_tag :search, :autofocus => true
361
+ # # => <input name="search" autofocus type="search" />
362
+ #
363
+ def search_field_tag(name, options={})
364
+ input_tag(:search, { :name => name }.update(options))
365
+ end
366
+
367
+ ##
368
+ # Creates a URL field input with the given name and options.
369
+ #
370
+ # @macro text_field
371
+ #
372
+ # @example
373
+ # url_field_tag :favorite_website, :placeholder => 'http://tennpipesrb.com'
374
+ # <input name="favorite_website" placeholder="http://tennpipesrb.com." type="url" />
375
+ #
376
+ # url_field_tag :home_page, :class => 'string url'
377
+ # <input name="home_page" class="string url", type="url" />
378
+ #
379
+ def url_field_tag(name, options={})
380
+ input_tag(:url, { :name => name }.update(options))
381
+ end
382
+
383
+ ##
384
+ # Constructs a hidden field input from the given options.
385
+ #
386
+ # @example
387
+ # hidden_field_tag :session_key, :value => "__secret__"
388
+ #
389
+ def hidden_field_tag(name, options={})
390
+ input_tag(:hidden, { :name => name }.update(options))
391
+ end
392
+
393
+ ##
394
+ # Constructs a text area input from the given options.
395
+ #
396
+ # @example
397
+ # text_area_tag :username, :class => 'long', :value => "Demo?"
398
+ #
399
+ def text_area_tag(name, options={})
400
+ inner_html = TagHelpers::NEWLINE + options.delete(:value).to_s
401
+ options = { :name => name, :rows => "", :cols => "" }.update(options)
402
+ content_tag(:textarea, inner_html, options)
403
+ end
404
+
405
+ ##
406
+ # Constructs a password field input from the given options.
407
+ #
408
+ # @example
409
+ # password_field_tag :password, :class => 'long'
410
+ #
411
+ # @api public
412
+ def password_field_tag(name, options={})
413
+ input_tag(:password, { :name => name }.update(options))
414
+ end
415
+
416
+ ##
417
+ # Constructs a check_box from the given options.
418
+ #
419
+ # @example
420
+ # check_box_tag :remember_me, :value => 'Yes'
421
+ #
422
+ def check_box_tag(name, options={})
423
+ input_tag(:checkbox, { :name => name, :value => '1' }.update(options))
424
+ end
425
+
426
+ ##
427
+ # Constructs a radio_button from the given options.
428
+ #
429
+ # @example
430
+ # radio_button_tag :remember_me, :value => 'true'
431
+ #
432
+ def radio_button_tag(name, options={})
433
+ input_tag(:radio, { :name => name }.update(options))
434
+ end
435
+
436
+ ##
437
+ # Constructs a file field input from the given options.
438
+ #
439
+ # @example
440
+ # file_field_tag :photo, :class => 'long'
441
+ #
442
+ # @api public
443
+ def file_field_tag(name, options={})
444
+ name = "#{name}[]" if options[:multiple]
445
+ input_tag(:file, { :name => name }.update(options))
446
+ end
447
+
448
+ ##
449
+ # Constructs a select from the given options.
450
+ #
451
+ # @example
452
+ # options = [['caption', 'value'], ['Green', 'green1'], ['Blue', 'blue1'], ['Black', "black1"]]
453
+ # options = ['option', 'red', 'yellow' ]
454
+ # select_tag(:favorite_color, :options => ['red', 'yellow'], :selected => 'green1')
455
+ # select_tag(:country, :collection => @countries, :fields => [:name, :code], :include_blank => 'None')
456
+ #
457
+ # # Optgroups can be generated using :grouped_options => (Hash or nested Array)
458
+ # grouped_options = [['Friends',['Yoda',['Obiwan',1]]],['Enemies',['Palpatine',['Darth Vader',3]]]]
459
+ # grouped_options = {'Friends' => ['Yoda',['Obiwan',1]],'Enemies' => ['Palpatine',['Darth Vader',3]]}
460
+ # select_tag(:color, :grouped_options => [['warm',['red','yellow']],['cool',['blue', 'purple']]])
461
+ #
462
+ # # Optgroups can be generated using the rails-style attribute hash.
463
+ # grouped_options = {
464
+ # "Friends" => ["Yoda", ["Obiwan", 2, {:magister => 'no'}], {:lame => 'yes'}],
465
+ # "Enemies" => [["Palpatine", "Palpatine", {:scary => 'yes', :old => 'yes'}], ["Darth Vader", 3, {:disabled => true}]]
466
+ # }
467
+ # select_tag(:name, :grouped_options => grouped_options)
468
+ #
469
+ # @param [String] name
470
+ # The name of the input field.
471
+ # @param [Hash] options
472
+ # The html options for the input field.
473
+ # @option options [Array<String, Array>] :options
474
+ # Explicit options to display in the select. Can be strings or string tuples.
475
+ # @option options [Array<Array>] :grouped_options
476
+ # List of options for each group in the select. See examples for details.
477
+ # @option options [Array<Object>] :collection
478
+ # Collection of objects used as options in the select.
479
+ # @option options [Array<Symbol>] :fields
480
+ # The attributes used as "label" and "value" for each +collection+ object.
481
+ # @option options [String] :selected (nil)
482
+ # The option value initially selected.
483
+ # @option options [Boolean] :include_blank (false)
484
+ # Include a blank option in the select.
485
+ # @option options [Boolean] :multiple (false)
486
+ # Allow multiple options to be selected at once.
487
+ #
488
+ # @return [String] The HTML input field based on the +options+ specified.
489
+ #
490
+ def select_tag(name, options={})
491
+ options = { :name => name }.merge(options)
492
+ options[:name] = "#{options[:name]}[]" if options[:multiple]
493
+ content_tag(:select, extract_option_tags!(options), options)
494
+ end
495
+
496
+ ##
497
+ # Constructs a button input from the given options.
498
+ #
499
+ # @param [String] caption
500
+ # The caption for the button.
501
+ # @param [Hash] options
502
+ # The html options for the input field.
503
+ #
504
+ # @return [String] The html button based on the +options+ specified.
505
+ #
506
+ # @example
507
+ # button_tag "Cancel", :class => 'clear'
508
+ #
509
+ def button_tag(caption, options = {})
510
+ input_tag(:button, { :value => caption }.update(options))
511
+ end
512
+
513
+ ##
514
+ # Constructs a submit button from the given options.
515
+ #
516
+ # @overload submit_tag(options={})
517
+ # @param [Hash] options The html options for the input field.
518
+ # @overload submit_tag(caption, options={})
519
+ # @param [String] caption The caption for the submit button.
520
+ # @param [Hash] options The html options for the input field.
521
+ #
522
+ # @return [String] The html submit button based on the +options+ specified.
523
+ #
524
+ # @example
525
+ # submit_tag "Create", :class => 'success'
526
+ # submit_tag :class => 'btn'
527
+ #
528
+ def submit_tag(*args)
529
+ options = args.extract_options!
530
+ caption = args.length >= 1 ? args.first : "Submit"
531
+ input_tag(:submit, { :value => caption }.merge(options))
532
+ end
533
+
534
+ ##
535
+ # Constructs a submit button from the given options.
536
+ #
537
+ # @param [String] source
538
+ # The source image path for the button.
539
+ # @param [Hash] options
540
+ # The html options for the input field.
541
+ #
542
+ # @return [String] The html image button based on the +options+ specified.
543
+ #
544
+ # @example
545
+ # image_submit_tag 'form/submit.png'
546
+ #
547
+ def image_submit_tag(source, options={})
548
+ input_tag(:image, { :src => image_path(source) }.update(options))
549
+ end
550
+
551
+ ##
552
+ # Creates a form containing a single button that submits to the URL.
553
+ #
554
+ # @overload button_to(caption, url, options={})
555
+ # @param [String] caption The text caption.
556
+ # @param [String] url The url href.
557
+ # @param [Hash] options The html options.
558
+ # @overload button_to(url, options={}, &block)
559
+ # @param [String] url The url href.
560
+ # @param [Hash] options The html options.
561
+ # @param [Proc] block The button content.
562
+ #
563
+ # @option options [Boolean] :multipart
564
+ # If true, this form will support multipart encoding.
565
+ # @option options [String] :remote
566
+ # Instructs ujs handler to handle the submit as ajax.
567
+ # @option options [Symbol] :method
568
+ # Instructs ujs handler to use different http method (i.e :post, :delete).
569
+ # @option options [Hash] :submit_options
570
+ # Hash of any options, that you want to pass to submit_tag (i.e :id, :class)
571
+ #
572
+ # @return [String] Form and button html with specified +options+.
573
+ #
574
+ # @example
575
+ # button_to 'Delete', url(:accounts_destroy, :id => account), :method => :delete, :class => :form
576
+ # # Generates:
577
+ # # <form class="form" action="/admin/accounts/destroy/2" method="post">
578
+ # # <input type="hidden" value="delete" name="_method" />
579
+ # # <input type="submit" value="Delete" />
580
+ # # </form>
581
+ #
582
+ def button_to(*args, &block)
583
+ options = args.extract_options!.dup
584
+ name, url = *args
585
+ options['data-remote'] = 'true' if options.delete(:remote)
586
+ submit_options = options.delete(:submit_options) || {}
587
+ form_tag(url || name, options) do
588
+ if block_given?
589
+ content_tag(:button, capture_html(&block), submit_options)
590
+ else
591
+ submit_tag(name, submit_options)
592
+ end
593
+ end
594
+ end
595
+
596
+ ##
597
+ # Constructs a range tag from the given options.
598
+ #
599
+ # @example
600
+ # range_field_tag('ranger_with_min_max', :min => 1, :max => 50)
601
+ # range_field_tag('ranger_with_range', :range => 1..5)
602
+ #
603
+ # @param [String] name
604
+ # The name of the range field.
605
+ # @param [Hash] options
606
+ # The html options for the range field.
607
+ # @option options [Integer] :min
608
+ # The min range of the range field.
609
+ # @option options [Integer] :max
610
+ # The max range of the range field.
611
+ # @option options [range] :range
612
+ # The range, in lieu of :min and :max. See examples for details.
613
+ # @return [String] The html range field
614
+ #
615
+ def range_field_tag(name, options = {})
616
+ options = { :name => name }.update(options)
617
+ if range = options.delete(:range)
618
+ options[:min], options[:max] = range.min, range.max
619
+ end
620
+ input_tag(:range, options)
621
+ end
622
+
623
+ private
624
+
625
+ ##
626
+ # Returns an initialized builder instance for the given object and settings.
627
+ #
628
+ # @example
629
+ # builder_instance(@account, :nested => { ... }) => <FormBuilder>
630
+ #
631
+ def builder_instance(object, options={})
632
+ default_builder = respond_to?(:settings) && settings.default_builder || 'StandardFormBuilder'
633
+ builder_class = options.delete(:builder) || default_builder
634
+ builder_class = "Tennpipes::Helpers::FormBuilder::#{builder_class}".constantize if builder_class.is_a?(String)
635
+ builder_class.new(self, object, options)
636
+ end
637
+ end
638
+ end
639
+ end