simple_form_with_client_validation 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. data/CHANGELOG.md +6 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +783 -0
  4. data/lib/generators/simple_form_with_client_validation/USAGE +3 -0
  5. data/lib/generators/simple_form_with_client_validation/install_generator.rb +32 -0
  6. data/lib/generators/simple_form_with_client_validation/templates/README +12 -0
  7. data/lib/generators/simple_form_with_client_validation/templates/_form.html.erb +13 -0
  8. data/lib/generators/simple_form_with_client_validation/templates/_form.html.haml +10 -0
  9. data/lib/generators/simple_form_with_client_validation/templates/_form.html.slim +10 -0
  10. data/lib/generators/simple_form_with_client_validation/templates/config/initializers/simple_form.rb.tt +179 -0
  11. data/lib/generators/simple_form_with_client_validation/templates/config/locales/simple_form.en.yml +26 -0
  12. data/lib/simple_form_with_client_validation/action_view_extensions/builder.rb +331 -0
  13. data/lib/simple_form_with_client_validation/action_view_extensions/form_helper.rb +74 -0
  14. data/lib/simple_form_with_client_validation/components/errors.rb +35 -0
  15. data/lib/simple_form_with_client_validation/components/hints.rb +18 -0
  16. data/lib/simple_form_with_client_validation/components/html5.rb +26 -0
  17. data/lib/simple_form_with_client_validation/components/label_input.rb +15 -0
  18. data/lib/simple_form_with_client_validation/components/labels.rb +79 -0
  19. data/lib/simple_form_with_client_validation/components/maxlength.rb +41 -0
  20. data/lib/simple_form_with_client_validation/components/min_max.rb +50 -0
  21. data/lib/simple_form_with_client_validation/components/minlength.rb +41 -0
  22. data/lib/simple_form_with_client_validation/components/pattern.rb +34 -0
  23. data/lib/simple_form_with_client_validation/components/placeholders.rb +16 -0
  24. data/lib/simple_form_with_client_validation/components/readonly.rb +22 -0
  25. data/lib/simple_form_with_client_validation/components.rb +21 -0
  26. data/lib/simple_form_with_client_validation/core_ext/hash.rb +16 -0
  27. data/lib/simple_form_with_client_validation/error_notification.rb +48 -0
  28. data/lib/simple_form_with_client_validation/form_builder.rb +484 -0
  29. data/lib/simple_form_with_client_validation/helpers/autofocus.rb +11 -0
  30. data/lib/simple_form_with_client_validation/helpers/disabled.rb +15 -0
  31. data/lib/simple_form_with_client_validation/helpers/readonly.rb +15 -0
  32. data/lib/simple_form_with_client_validation/helpers/required.rb +35 -0
  33. data/lib/simple_form_with_client_validation/helpers/validators.rb +44 -0
  34. data/lib/simple_form_with_client_validation/helpers.rb +12 -0
  35. data/lib/simple_form_with_client_validation/i18n_cache.rb +22 -0
  36. data/lib/simple_form_with_client_validation/inputs/base.rb +162 -0
  37. data/lib/simple_form_with_client_validation/inputs/block_input.rb +14 -0
  38. data/lib/simple_form_with_client_validation/inputs/boolean_input.rb +64 -0
  39. data/lib/simple_form_with_client_validation/inputs/collection_check_boxes_input.rb +21 -0
  40. data/lib/simple_form_with_client_validation/inputs/collection_input.rb +101 -0
  41. data/lib/simple_form_with_client_validation/inputs/collection_radio_buttons_input.rb +63 -0
  42. data/lib/simple_form_with_client_validation/inputs/collection_select_input.rb +14 -0
  43. data/lib/simple_form_with_client_validation/inputs/date_time_input.rb +28 -0
  44. data/lib/simple_form_with_client_validation/inputs/file_input.rb +9 -0
  45. data/lib/simple_form_with_client_validation/inputs/grouped_collection_select_input.rb +41 -0
  46. data/lib/simple_form_with_client_validation/inputs/hidden_input.rb +17 -0
  47. data/lib/simple_form_with_client_validation/inputs/numeric_input.rb +24 -0
  48. data/lib/simple_form_with_client_validation/inputs/password_input.rb +12 -0
  49. data/lib/simple_form_with_client_validation/inputs/priority_input.rb +24 -0
  50. data/lib/simple_form_with_client_validation/inputs/range_input.rb +14 -0
  51. data/lib/simple_form_with_client_validation/inputs/string_input.rb +23 -0
  52. data/lib/simple_form_with_client_validation/inputs/text_input.rb +11 -0
  53. data/lib/simple_form_with_client_validation/inputs.rb +21 -0
  54. data/lib/simple_form_with_client_validation/map_type.rb +16 -0
  55. data/lib/simple_form_with_client_validation/version.rb +3 -0
  56. data/lib/simple_form_with_client_validation/wrappers/builder.rb +115 -0
  57. data/lib/simple_form_with_client_validation/wrappers/many.rb +78 -0
  58. data/lib/simple_form_with_client_validation/wrappers/root.rb +34 -0
  59. data/lib/simple_form_with_client_validation/wrappers/single.rb +18 -0
  60. data/lib/simple_form_with_client_validation/wrappers.rb +8 -0
  61. data/lib/simple_form_with_client_validation.rb +218 -0
  62. data/test/action_view_extensions/builder_test.rb +577 -0
  63. data/test/action_view_extensions/form_helper_test.rb +104 -0
  64. data/test/components/label_test.rb +310 -0
  65. data/test/form_builder/association_test.rb +177 -0
  66. data/test/form_builder/button_test.rb +47 -0
  67. data/test/form_builder/error_notification_test.rb +79 -0
  68. data/test/form_builder/error_test.rb +121 -0
  69. data/test/form_builder/general_test.rb +356 -0
  70. data/test/form_builder/hint_test.rb +139 -0
  71. data/test/form_builder/input_field_test.rb +63 -0
  72. data/test/form_builder/label_test.rb +71 -0
  73. data/test/form_builder/wrapper_test.rb +149 -0
  74. data/test/generators/simple_form_generator_test.rb +32 -0
  75. data/test/inputs/boolean_input_test.rb +108 -0
  76. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  77. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  78. data/test/inputs/collection_select_input_test.rb +241 -0
  79. data/test/inputs/datetime_input_test.rb +99 -0
  80. data/test/inputs/disabled_test.rb +38 -0
  81. data/test/inputs/discovery_test.rb +61 -0
  82. data/test/inputs/file_input_test.rb +16 -0
  83. data/test/inputs/general_test.rb +69 -0
  84. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  85. data/test/inputs/hidden_input_test.rb +30 -0
  86. data/test/inputs/numeric_input_test.rb +173 -0
  87. data/test/inputs/priority_input_test.rb +43 -0
  88. data/test/inputs/readonly_test.rb +61 -0
  89. data/test/inputs/required_test.rb +113 -0
  90. data/test/inputs/string_input_test.rb +140 -0
  91. data/test/inputs/text_input_test.rb +29 -0
  92. data/test/simple_form_test.rb +9 -0
  93. data/test/support/discovery_inputs.rb +21 -0
  94. data/test/support/misc_helpers.rb +102 -0
  95. data/test/support/mock_controller.rb +24 -0
  96. data/test/support/mock_response.rb +14 -0
  97. data/test/support/models.rb +210 -0
  98. data/test/test_helper.rb +95 -0
  99. metadata +227 -0
@@ -0,0 +1,22 @@
1
+ module SimpleFormWithClientValidation
2
+ module Components
3
+ # Needs to be enabled in order to do automatic lookups.
4
+ module Readonly
5
+ def readonly
6
+ if readonly_attribute? && !has_readonly?
7
+ input_html_options[:readonly] ||= true
8
+ input_html_classes << :readonly
9
+ end
10
+ nil
11
+ end
12
+
13
+ private
14
+
15
+ def readonly_attribute?
16
+ object.class.respond_to?(:readonly_attributes) &&
17
+ object.persisted? &&
18
+ object.class.readonly_attributes.include?(attribute_name)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module SimpleFormWithClientValidation
2
+ # Components are a special type of helpers that can work on their own.
3
+ # For example, by using a component, it will automatically change the
4
+ # output under given circumstances without user input. For example,
5
+ # the disabled helper always need a :disabled => true option given
6
+ # to the input in order to be enabled. On the other hand, things like
7
+ # hints can generate output automatically by doing I18n lookups.
8
+ module Components
9
+ autoload :Errors, 'simple_form_with_client_validation/components/errors'
10
+ autoload :Hints, 'simple_form_with_client_validation/components/hints'
11
+ autoload :HTML5, 'simple_form_with_client_validation/components/html5'
12
+ autoload :LabelInput, 'simple_form_with_client_validation/components/label_input'
13
+ autoload :Labels, 'simple_form_with_client_validation/components/labels'
14
+ autoload :MinMax, 'simple_form_with_client_validation/components/min_max'
15
+ autoload :Maxlength, 'simple_form_with_client_validation/components/maxlength'
16
+ autoload :Minlength, 'simple_form_with_client_validation/components/minlength'
17
+ autoload :Pattern, 'simple_form_with_client_validation/components/pattern'
18
+ autoload :Placeholders, 'simple_form_with_client_validation/components/placeholders'
19
+ autoload :Readonly, 'simple_form_with_client_validation/components/readonly'
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # TODO: Delete this file when we drop support for Rails 3.0
2
+ # This method is already implemented in active_support 3.1
3
+
4
+ unless Hash.new.respond_to?(:deep_dup)
5
+ class Hash
6
+ # Returns a deep copy of hash.
7
+ def deep_dup
8
+ duplicate = self.dup
9
+ duplicate.each_pair do |k,v|
10
+ tv = duplicate[k]
11
+ duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v
12
+ end
13
+ duplicate
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ module SimpleFormWithClientValidation
2
+ class ErrorNotification
3
+ delegate :object, :object_name, :template, :to => :@builder
4
+
5
+ def initialize(builder, options)
6
+ @builder = builder
7
+ @message = options.delete(:message)
8
+ @options = options
9
+ end
10
+
11
+ def render
12
+ if has_errors?
13
+ template.content_tag(error_notification_tag, error_message, html_options)
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def errors
20
+ object.errors
21
+ end
22
+
23
+ def has_errors?
24
+ object && object.respond_to?(:errors) && errors.present?
25
+ end
26
+
27
+ def error_message
28
+ (@message || translate_error_notification).html_safe
29
+ end
30
+
31
+ def error_notification_tag
32
+ SimpleFormWithClientValidation.error_notification_tag
33
+ end
34
+
35
+ def html_options
36
+ @options[:class] = "#{SimpleFormWithClientValidation.error_notification_class} #{@options[:class]}".strip
37
+ @options
38
+ end
39
+
40
+ def translate_error_notification
41
+ lookups = []
42
+ lookups << :"#{object_name}"
43
+ lookups << :default_message
44
+ lookups << "Some errors were found, please take a look:"
45
+ I18n.t(lookups.shift, :scope => :"simple_form.error_notification", :default => lookups)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,484 @@
1
+ require 'simple_form_with_client_validation/core_ext/hash'
2
+
3
+ module SimpleFormWithClientValidation
4
+ class FormBuilder < ActionView::Helpers::FormBuilder
5
+ attr_reader :template, :object_name, :object, :wrapper
6
+
7
+ # When action is create or update, we still should use new and edit
8
+ ACTIONS = {
9
+ :create => :new,
10
+ :update => :edit
11
+ }
12
+
13
+ extend MapType
14
+ include SimpleFormWithClientValidation::Inputs
15
+
16
+ map_type :text, :to => SimpleFormWithClientValidation::Inputs::TextInput
17
+ map_type :file, :to => SimpleFormWithClientValidation::Inputs::FileInput
18
+ map_type :string, :email, :search, :tel, :url, :to => SimpleFormWithClientValidation::Inputs::StringInput
19
+ map_type :password, :to => SimpleFormWithClientValidation::Inputs::PasswordInput
20
+ map_type :integer, :decimal, :float, :to => SimpleFormWithClientValidation::Inputs::NumericInput
21
+ map_type :range, :to => SimpleFormWithClientValidation::Inputs::RangeInput
22
+ map_type :check_boxes, :to => SimpleFormWithClientValidation::Inputs::CollectionCheckBoxesInput
23
+ map_type :radio_buttons, :to => SimpleFormWithClientValidation::Inputs::CollectionRadioButtonsInput
24
+ map_type :select, :to => SimpleFormWithClientValidation::Inputs::CollectionSelectInput
25
+ map_type :grouped_select, :to => SimpleFormWithClientValidation::Inputs::GroupedCollectionSelectInput
26
+ map_type :date, :time, :datetime, :to => SimpleFormWithClientValidation::Inputs::DateTimeInput
27
+ map_type :country, :time_zone, :to => SimpleFormWithClientValidation::Inputs::PriorityInput
28
+ map_type :boolean, :to => SimpleFormWithClientValidation::Inputs::BooleanInput
29
+
30
+ def self.discovery_cache
31
+ @discovery_cache ||= {}
32
+ end
33
+
34
+ def initialize(*) #:nodoc:
35
+ super
36
+ @defaults = options[:defaults]
37
+ @wrapper = SimpleFormWithClientValidation.wrapper(options[:wrapper] || SimpleFormWithClientValidation.default_wrapper)
38
+ end
39
+
40
+ # Basic input helper, combines all components in the stack to generate
41
+ # input html based on options the user define and some guesses through
42
+ # database column information. By default a call to input will generate
43
+ # label + input + hint (when defined) + errors (when exists), and all can
44
+ # be configured inside a wrapper html.
45
+ #
46
+ # == Examples
47
+ #
48
+ # # Imagine @user has error "can't be blank" on name
49
+ # simple_form_for @user do |f|
50
+ # f.input :name, :hint => 'My hint'
51
+ # end
52
+ #
53
+ # This is the output html (only the input portion, not the form):
54
+ #
55
+ # <label class="string required" for="user_name">
56
+ # <abbr title="required">*</abbr> Super User Name!
57
+ # </label>
58
+ # <input class="string required" id="user_name" maxlength="100"
59
+ # name="user[name]" size="100" type="text" value="Carlos" />
60
+ # <span class="hint">My hint</span>
61
+ # <span class="error">can't be blank</span>
62
+ #
63
+ # Each database type will render a default input, based on some mappings and
64
+ # heuristic to determine which is the best option.
65
+ #
66
+ # You have some options for the input to enable/disable some functions:
67
+ #
68
+ # :as => allows you to define the input type you want, for instance you
69
+ # can use it to generate a text field for a date column.
70
+ #
71
+ # :required => defines whether this attribute is required or not. True
72
+ # by default.
73
+ #
74
+ # The fact SimpleFormWithClientValidation is built in components allow the interface to be unified.
75
+ # So, for instance, if you need to disable :hint for a given input, you can pass
76
+ # :hint => false. The same works for :error, :label and :wrapper.
77
+ #
78
+ # Besides the html for any component can be changed. So, if you want to change
79
+ # the label html you just need to give a hash to :label_html. To configure the
80
+ # input html, supply :input_html instead and so on.
81
+ #
82
+ # == Options
83
+ #
84
+ # Some inputs, as datetime, time and select allow you to give extra options, like
85
+ # prompt and/or include blank. Such options are given in plainly:
86
+ #
87
+ # f.input :created_at, :include_blank => true
88
+ #
89
+ # == Collection
90
+ #
91
+ # When playing with collections (:radio_buttons, :check_boxes and :select
92
+ # inputs), you have three extra options:
93
+ #
94
+ # :collection => use to determine the collection to generate the radio or select
95
+ #
96
+ # :label_method => the method to apply on the array collection to get the label
97
+ #
98
+ # :value_method => the method to apply on the array collection to get the value
99
+ #
100
+ # == Priority
101
+ #
102
+ # Some inputs, as :time_zone and :country accepts a :priority option. If none is
103
+ # given SimpleFormWithClientValidation.time_zone_priority and
104
+ # SimpleFormWithClientValidation.country_priority are used respectivelly.
105
+ #
106
+ def input(attribute_name, options={}, &block)
107
+ options = @defaults.deep_dup.deep_merge(options) if @defaults
108
+
109
+ #SimpleFormWithClientValidation.wrapper(name) is a module method that
110
+ # Retrieves a given wrapper
111
+ chosen =
112
+ if name = options[:wrapper]
113
+ name.respond_to?(:render) ? name : SimpleFormWithClientValidation.wrapper(name)
114
+ else
115
+ wrapper
116
+ end
117
+
118
+ chosen.render find_input(attribute_name, options, &block)
119
+ end
120
+ alias :attribute :input
121
+
122
+ # Creates a input tag for the given attribute. All the given options
123
+ # are sent as :input_html.
124
+ #
125
+ # == Examples
126
+ #
127
+ # simple_form_for @user do |f|
128
+ # f.input_field :name
129
+ # end
130
+ #
131
+ # This is the output html (only the input portion, not the form):
132
+ #
133
+ # <input class="string required" id="user_name" maxlength="100"
134
+ # name="user[name]" size="100" type="text" value="Carlos" />
135
+ #
136
+ def input_field(attribute_name, options={})
137
+ options = options.dup
138
+ options[:input_html] = options.except(:as, :collection, :label_method, :value_method)
139
+ SimpleFormWithClientValidation::Wrappers::Root.new([:input], :wrapper => false).render find_input(attribute_name, options)
140
+ end
141
+
142
+ # Helper for dealing with association selects/radios, generating the
143
+ # collection automatically. It's just a wrapper to input, so all options
144
+ # supported in input are also supported by association. Some extra options
145
+ # can also be given:
146
+ #
147
+ # == Examples
148
+ #
149
+ # simple_form_for @user do |f|
150
+ # f.association :company # Company.all
151
+ # end
152
+ #
153
+ # f.association :company, :collection => Company.all(:order => 'name')
154
+ # # Same as using :order option, but overriding collection
155
+ #
156
+ # == Block
157
+ #
158
+ # When a block is given, association simple behaves as a proxy to
159
+ # simple_fields_for:
160
+ #
161
+ # f.association :company do |c|
162
+ # c.input :name
163
+ # c.input :type
164
+ # end
165
+ #
166
+ # From the options above, only :collection can also be supplied.
167
+ #
168
+ def association(association, options={}, &block)
169
+ options = options.dup
170
+
171
+ return simple_fields_for(*[association,
172
+ options.delete(:collection), options].compact, &block) if block_given?
173
+
174
+ raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
175
+
176
+ reflection = find_association_reflection(association)
177
+ raise "Association #{association.inspect} not found" unless reflection
178
+
179
+ options[:as] ||= :select
180
+ options[:collection] ||= reflection.klass.all(reflection.options.slice(:conditions, :order))
181
+
182
+ attribute = case reflection.macro
183
+ when :belongs_to
184
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
185
+ when :has_one
186
+ raise ":has_one associations are not supported by f.association"
187
+ else
188
+ if options[:as] == :select
189
+ html_options = options[:input_html] ||= {}
190
+ html_options[:size] ||= 5
191
+ html_options[:multiple] = true unless html_options.key?(:multiple)
192
+ end
193
+
194
+ # Force the association to be preloaded for performance.
195
+ if options[:preload] != false && object.respond_to?(association)
196
+ target = object.send(association)
197
+ target.to_a if target.respond_to?(:to_a)
198
+ end
199
+
200
+ :"#{reflection.name.to_s.singularize}_ids"
201
+ end
202
+
203
+ input(attribute, options.merge(:reflection => reflection))
204
+ end
205
+
206
+ # Creates a button:
207
+ #
208
+ # form_for @user do |f|
209
+ # f.button :submit
210
+ # end
211
+ #
212
+ # It just acts as a proxy to method name given. We also alias original Rails
213
+ # button implementation (3.2 forward (to delegate to the original when
214
+ # calling `f.button :button`.
215
+ #
216
+ # TODO: remove if condition when supporting only Rails 3.2 forward.
217
+ alias_method :button_button, :button if method_defined?(:button)
218
+ def button(type, *args, &block)
219
+ options = args.extract_options!.dup
220
+ options[:class] = [SimpleFormWithClientValidation.button_class, options[:class]].compact
221
+ args << options
222
+ if respond_to?("#{type}_button")
223
+ send("#{type}_button", *args, &block)
224
+ else
225
+ send(type, *args, &block)
226
+ end
227
+ end
228
+
229
+ # Creates an error tag based on the given attribute, only when the attribute
230
+ # contains errors. All the given options are sent as :error_html.
231
+ #
232
+ # == Examples
233
+ #
234
+ # f.error :name
235
+ # f.error :name, :id => "cool_error"
236
+ #
237
+ def error(attribute_name, options={})
238
+ options = options.dup
239
+
240
+ options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
241
+ column = find_attribute_column(attribute_name)
242
+ input_type = default_input_type(attribute_name, column, options)
243
+ wrapper.find(:error).
244
+ render(SimpleFormWithClientValidation::Inputs::Base.new(self, attribute_name, column, input_type, options))
245
+ end
246
+
247
+ # Return the error but also considering its name. This is used
248
+ # when errors for a hidden field need to be shown.
249
+ #
250
+ # == Examples
251
+ #
252
+ # f.full_error :token #=> <span class="error">Token is invalid</span>
253
+ #
254
+ def full_error(attribute_name, options={})
255
+ options = options.dup
256
+
257
+ options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
258
+ object.class.human_attribute_name(attribute_name.to_s)
259
+ else
260
+ attribute_name.to_s.humanize
261
+ end
262
+
263
+ error(attribute_name, options)
264
+ end
265
+
266
+ # Creates a hint tag for the given attribute. Accepts a symbol indicating
267
+ # an attribute for I18n lookup or a string. All the given options are sent
268
+ # as :hint_html.
269
+ #
270
+ # == Examples
271
+ #
272
+ # f.hint :name # Do I18n lookup
273
+ # f.hint :name, :id => "cool_hint"
274
+ # f.hint "Don't forget to accept this"
275
+ #
276
+ def hint(attribute_name, options={})
277
+ options = options.dup
278
+
279
+ options[:hint_html] = options.except(:hint_tag, :hint)
280
+ if attribute_name.is_a?(String)
281
+ options[:hint] = attribute_name
282
+ attribute_name, column, input_type = nil, nil, nil
283
+ else
284
+ column = find_attribute_column(attribute_name)
285
+ input_type = default_input_type(attribute_name, column, options)
286
+ end
287
+
288
+ wrapper.find(:hint).
289
+ render(SimpleFormWithClientValidation::Inputs::Base.new(self, attribute_name, column, input_type, options))
290
+ end
291
+
292
+ # Creates a default label tag for the given attribute. You can give a label
293
+ # through the :label option or using i18n. All the given options are sent
294
+ # as :label_html.
295
+ #
296
+ # == Examples
297
+ #
298
+ # f.label :name # Do I18n lookup
299
+ # f.label :name, "Name" # Same behavior as Rails, do not add required tag
300
+ # f.label :name, :label => "Name" # Same as above, but adds required tag
301
+ #
302
+ # f.label :name, :required => false
303
+ # f.label :name, :id => "cool_label"
304
+ #
305
+ def label(attribute_name, *args)
306
+ return super if args.first.is_a?(String) || block_given?
307
+
308
+ options = args.extract_options!.dup
309
+ options[:label_html] = options.except(:label, :required, :as)
310
+
311
+ column = find_attribute_column(attribute_name)
312
+ input_type = default_input_type(attribute_name, column, options)
313
+ SimpleFormWithClientValidation::Inputs::Base.new(self, attribute_name, column, input_type, options).label
314
+ end
315
+
316
+ # Creates an error notification message that only appears when the form object
317
+ # has some error. You can give a specific message with the :message option,
318
+ # otherwise it will look for a message using I18n. All other options given are
319
+ # passed straight as html options to the html tag.
320
+ #
321
+ # == Examples
322
+ #
323
+ # f.error_notification
324
+ # f.error_notification :message => 'Something went wrong'
325
+ # f.error_notification :id => 'user_error_message', :class => 'form_error'
326
+ #
327
+ def error_notification(options={})
328
+ SimpleFormWithClientValidation::ErrorNotification.new(self, options).render
329
+ end
330
+
331
+ # Extract the model names from the object_name mess, ignoring numeric and
332
+ # explicit child indexes.
333
+ #
334
+ # Example:
335
+ #
336
+ # route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
337
+ # ["route", "blocks", "blocks_learning_object", "foo"]
338
+ #
339
+ def lookup_model_names
340
+ @lookup_model_names ||= begin
341
+ child_index = options[:child_index]
342
+ names = object_name.to_s.scan(/([a-zA-Z_]+)/).flatten
343
+ names.delete(child_index) if child_index
344
+ names.each { |name| name.gsub!('_attributes', '') }
345
+ names.freeze
346
+ end
347
+ end
348
+
349
+ # The action to be used in lookup.
350
+ def lookup_action
351
+ @lookup_action ||= begin
352
+ action = template.controller.action_name
353
+ return unless action
354
+ action = action.to_sym
355
+ ACTIONS[action] || action
356
+ end
357
+ end
358
+
359
+ private
360
+
361
+ # Find an input based on the attribute name.
362
+ def find_input(attribute_name, options={}, &block) #:nodoc:
363
+ column = find_attribute_column(attribute_name)
364
+ input_type = default_input_type(attribute_name, column, options)
365
+
366
+ if input_type == :radio
367
+ SimpleFormWithClientValidation.deprecation_warn "Using `:as => :radio` as input type is " \
368
+ "deprecated, please change it to `:as => :radio_buttons`."
369
+ input_type = :radio_buttons
370
+ end
371
+
372
+ if block_given?
373
+ SimpleFormWithClientValidation::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
374
+ else
375
+ find_mapping(input_type).new(self, attribute_name, column, input_type, options)
376
+ end
377
+ end
378
+
379
+ # Attempt to guess the better input type given the defined options. By
380
+ # default always fallback to the user :as option, or to a :select when a
381
+ # collection is given.
382
+ def default_input_type(attribute_name, column, options) #:nodoc:
383
+
384
+ #return user :as option if present
385
+ return options[:as].to_sym if options[:as]
386
+
387
+ #return select when a collection is given
388
+ return :select if options[:collection]
389
+
390
+ #return any custom type assigned to SimpleFormWithClientValidation.input_mappings
391
+ custom_type = find_custom_type(attribute_name.to_s) and return custom_type
392
+
393
+ #grab the ActiveModel column.type and parse it from there
394
+ input_type = column.try(:type)
395
+ case input_type
396
+ when :timestamp
397
+ :datetime
398
+ when :string, nil
399
+ case attribute_name.to_s
400
+ when /password/ then :password
401
+ when /time_zone/ then :time_zone
402
+ when /country/ then :country
403
+ when /email/ then :email
404
+ when /phone/ then :tel
405
+ when /url/ then :url
406
+ else
407
+ file_method?(attribute_name) ? :file : (input_type || :string)
408
+ end
409
+ else
410
+ input_type
411
+ end
412
+ end
413
+
414
+ #this only comes into play if the SimpleFormWithClientValidation
415
+ #was initialized with/given input_mappings
416
+ def find_custom_type(attribute_name) #:nodoc:
417
+ SimpleFormWithClientValidation.input_mappings.find { |match, type|
418
+ attribute_name =~ match
419
+ }.try(:last) if SimpleFormWithClientValidation.input_mappings
420
+ end
421
+
422
+ def file_method?(attribute_name) #:nodoc:
423
+ file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
424
+ file && SimpleFormWithClientValidation.file_methods.any? { |m| file.respond_to?(m) }
425
+ end
426
+
427
+ def find_attribute_column(attribute_name) #:nodoc:
428
+ if @object.respond_to?(:column_for_attribute)
429
+ @object.column_for_attribute(attribute_name)
430
+ end
431
+ end
432
+
433
+ def find_association_reflection(association) #:nodoc:
434
+ if @object.class.respond_to?(:reflect_on_association)
435
+ @object.class.reflect_on_association(association)
436
+ end
437
+ end
438
+
439
+ # Attempts to find a mapping. It follows the following rules:
440
+ #
441
+ # 1) It tries to find a registered mapping, if succeeds:
442
+ # a) Try to find an alternative with the same name in the Object scope
443
+ # b) Or use the found mapping
444
+ # 2) If not, fallbacks to #{input_type}Input
445
+ # 3) If not, fallbacks to SimpleFormWithClientValidation::Inputs::#{input_type}Input
446
+ def find_mapping(input_type) #:nodoc:
447
+ discovery_cache[input_type] ||=
448
+ if mapping = self.class.mappings[input_type]
449
+ mapping_override(mapping) || mapping
450
+ else
451
+ camelized = "#{input_type.to_s.camelize}Input"
452
+ attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
453
+ raise("No input found for #{input_type}")
454
+ end
455
+ end
456
+
457
+ # If cache_discovery is enabled, use the class level cache that persists
458
+ # between requests, otherwise use the instance one.
459
+ def discovery_cache #:nodoc:
460
+ if SimpleFormWithClientValidation.cache_discovery
461
+ self.class.discovery_cache
462
+ else
463
+ @discovery_cache ||= {}
464
+ end
465
+ end
466
+
467
+ def mapping_override(klass) #:nodoc:
468
+ name = klass.name
469
+ if name =~ /^SimpleFormWithClientValidation::Inputs/
470
+ attempt_mapping name.split("::").last, Object
471
+ end
472
+ end
473
+
474
+ def attempt_mapping(mapping, at) #:nodoc:
475
+ return if SimpleFormWithClientValidation.inputs_discovery == false && at == Object
476
+
477
+ begin
478
+ at.const_get(mapping)
479
+ rescue NameError => e
480
+ e.message =~ /#{mapping}$/ ? nil : raise
481
+ end
482
+ end
483
+ end
484
+ end