simple_form 3.0.0.beta1 → 3.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of simple_form might be problematic. Click here for more details.

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