simple_form 2.0.0 → 2.1.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.
- data/CHANGELOG.md +34 -231
- data/MIT-LICENSE +1 -1
- data/README.md +221 -159
- data/lib/generators/simple_form/install_generator.rb +14 -5
- data/lib/generators/simple_form/templates/config/initializers/{simple_form.rb.tt → simple_form.rb} +8 -42
- data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +45 -0
- data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +26 -0
- data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -5
- data/lib/simple_form/action_view_extensions/builder.rb +46 -30
- data/lib/simple_form/action_view_extensions/builder.rb.orig +247 -0
- data/lib/simple_form/action_view_extensions/form_helper.rb +7 -9
- data/lib/simple_form/components/hints.rb +3 -2
- data/lib/simple_form/components/label_input.rb +1 -1
- data/lib/simple_form/components/labels.rb +1 -1
- data/lib/simple_form/components/maxlength.rb +1 -1
- data/lib/simple_form/components/min_max.rb +2 -1
- data/lib/simple_form/components/pattern.rb +1 -1
- data/lib/simple_form/components/readonly.rb +1 -1
- data/lib/simple_form/components.rb +12 -10
- data/lib/simple_form/error_notification.rb +1 -1
- data/lib/simple_form/form_builder.rb +18 -8
- data/lib/simple_form/form_builder.rb.orig +486 -0
- data/lib/simple_form/helpers/validators.rb +2 -2
- data/lib/simple_form/inputs/base.rb +33 -8
- data/lib/simple_form/inputs/boolean_input.rb +19 -5
- data/lib/simple_form/inputs/collection_radio_buttons_input.rb +2 -6
- data/lib/simple_form/inputs/date_time_input.rb +0 -4
- data/lib/simple_form/inputs.rb +19 -17
- data/lib/simple_form/version.rb +1 -1
- data/lib/simple_form/version.rb.orig +7 -0
- data/lib/simple_form/wrappers/many.rb +5 -1
- data/lib/simple_form/wrappers/root.rb +4 -2
- data/lib/simple_form/wrappers/single.rb +6 -0
- data/lib/simple_form.rb +17 -8
- data/test/action_view_extensions/builder_test.rb +145 -66
- data/test/action_view_extensions/form_helper_test.rb +59 -20
- data/test/components/label_test.rb +34 -17
- data/test/form_builder/association_test.rb +19 -10
- data/test/form_builder/error_notification_test.rb +1 -1
- data/test/form_builder/general_test.rb +74 -12
- data/test/form_builder/hint_test.rb +15 -0
- data/test/form_builder/input_field_test.rb +45 -0
- data/test/form_builder/label_test.rb +6 -0
- data/test/form_builder/wrapper_test.rb +58 -4
- data/test/generators/simple_form_generator_test.rb +11 -1
- data/test/inputs/boolean_input_test.rb +39 -0
- data/test/inputs/datetime_input_test.rb +2 -2
- data/test/inputs/disabled_test.rb +51 -11
- data/test/inputs/discovery_test.rb +9 -1
- data/test/inputs/general_test.rb +58 -11
- data/test/inputs/grouped_collection_select_input_test.rb +15 -0
- data/test/inputs/numeric_input_test.rb +6 -0
- data/test/inputs/readonly_test.rb +51 -11
- data/test/inputs/string_input_test.rb +6 -0
- data/test/support/discovery_inputs.rb +6 -0
- data/test/support/misc_helpers.rb +41 -1
- data/test/support/models.rb +8 -4
- data/test/test_helper.rb +8 -6
- metadata +26 -13
- data/test/support/mock_response.rb +0 -14
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'simple_form/core_ext/hash'
|
|
2
|
+
require 'simple_form/map_type'
|
|
2
3
|
|
|
3
4
|
module SimpleForm
|
|
4
5
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
@@ -100,19 +101,20 @@ module SimpleForm
|
|
|
100
101
|
# == Priority
|
|
101
102
|
#
|
|
102
103
|
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
|
|
103
|
-
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used
|
|
104
|
+
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
|
|
104
105
|
#
|
|
105
106
|
def input(attribute_name, options={}, &block)
|
|
106
107
|
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
108
|
+
input = find_input(attribute_name, options, &block)
|
|
107
109
|
|
|
108
110
|
chosen =
|
|
109
|
-
if name = options[:wrapper]
|
|
111
|
+
if name = options[:wrapper] || find_wrapper_mapping(input.input_type)
|
|
110
112
|
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
|
|
111
113
|
else
|
|
112
114
|
wrapper
|
|
113
115
|
end
|
|
114
116
|
|
|
115
|
-
chosen.render
|
|
117
|
+
chosen.render input
|
|
116
118
|
end
|
|
117
119
|
alias :attribute :input
|
|
118
120
|
|
|
@@ -133,7 +135,9 @@ module SimpleForm
|
|
|
133
135
|
def input_field(attribute_name, options={})
|
|
134
136
|
options = options.dup
|
|
135
137
|
options[:input_html] = options.except(:as, :collection, :label_method, :value_method)
|
|
136
|
-
|
|
138
|
+
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
139
|
+
|
|
140
|
+
SimpleForm::Wrappers::Root.new([:min_max, :maxlength, :placeholder, :pattern, :readonly, :input], :wrapper => false).render find_input(attribute_name, options)
|
|
137
141
|
end
|
|
138
142
|
|
|
139
143
|
# Helper for dealing with association selects/radios, generating the
|
|
@@ -174,13 +178,15 @@ module SimpleForm
|
|
|
174
178
|
raise "Association #{association.inspect} not found" unless reflection
|
|
175
179
|
|
|
176
180
|
options[:as] ||= :select
|
|
177
|
-
options[:collection] ||=
|
|
181
|
+
options[:collection] ||= options.fetch(:collection) {
|
|
182
|
+
reflection.klass.all(reflection.options.slice(:conditions, :order))
|
|
183
|
+
}
|
|
178
184
|
|
|
179
185
|
attribute = case reflection.macro
|
|
180
186
|
when :belongs_to
|
|
181
187
|
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
|
|
182
188
|
when :has_one
|
|
183
|
-
raise ":has_one associations are not supported by f.association"
|
|
189
|
+
raise ArgumentError, ":has_one associations are not supported by f.association"
|
|
184
190
|
else
|
|
185
191
|
if options[:as] == :select
|
|
186
192
|
html_options = options[:input_html] ||= {}
|
|
@@ -303,7 +309,7 @@ module SimpleForm
|
|
|
303
309
|
return super if args.first.is_a?(String) || block_given?
|
|
304
310
|
|
|
305
311
|
options = args.extract_options!.dup
|
|
306
|
-
options[:label_html] = options.except(:label, :required)
|
|
312
|
+
options[:label_html] = options.except(:label, :required, :as)
|
|
307
313
|
|
|
308
314
|
column = find_attribute_column(attribute_name)
|
|
309
315
|
input_type = default_input_type(attribute_name, column, options)
|
|
@@ -442,6 +448,10 @@ module SimpleForm
|
|
|
442
448
|
end
|
|
443
449
|
end
|
|
444
450
|
|
|
451
|
+
def find_wrapper_mapping(input_type) #:nodoc:
|
|
452
|
+
SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
|
|
453
|
+
end
|
|
454
|
+
|
|
445
455
|
# If cache_discovery is enabled, use the class level cache that persists
|
|
446
456
|
# between requests, otherwise use the instance one.
|
|
447
457
|
def discovery_cache #:nodoc:
|
|
@@ -465,7 +475,7 @@ module SimpleForm
|
|
|
465
475
|
begin
|
|
466
476
|
at.const_get(mapping)
|
|
467
477
|
rescue NameError => e
|
|
468
|
-
e.message
|
|
478
|
+
raise if e.message !~ /#{mapping}$/
|
|
469
479
|
end
|
|
470
480
|
end
|
|
471
481
|
end
|
|
@@ -0,0 +1,486 @@
|
|
|
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
|
|
@@ -36,8 +36,8 @@ module SimpleForm
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def find_validator(
|
|
40
|
-
attribute_validators.find { |v|
|
|
39
|
+
def find_validator(kind)
|
|
40
|
+
attribute_validators.find { |v| v.kind == kind } if has_validators?
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'simple_form/i18n_cache'
|
|
2
|
+
|
|
1
3
|
module SimpleForm
|
|
2
4
|
module Inputs
|
|
3
5
|
class Base
|
|
@@ -48,6 +50,7 @@ module SimpleForm
|
|
|
48
50
|
def initialize(builder, attribute_name, column, input_type, options = {})
|
|
49
51
|
super
|
|
50
52
|
|
|
53
|
+
options = options.dup
|
|
51
54
|
@builder = builder
|
|
52
55
|
@attribute_name = attribute_name
|
|
53
56
|
@column = column
|
|
@@ -59,9 +62,7 @@ module SimpleForm
|
|
|
59
62
|
# Notice that html_options_for receives a reference to input_html_classes.
|
|
60
63
|
# This means that classes added dynamically to input_html_classes will
|
|
61
64
|
# still propagate to input_html_options.
|
|
62
|
-
@html_classes = SimpleForm.additional_classes_for(:input) {
|
|
63
|
-
[input_type, required_class, readonly_class, disabled_class].compact
|
|
64
|
-
}
|
|
65
|
+
@html_classes = SimpleForm.additional_classes_for(:input) { additional_classes }
|
|
65
66
|
|
|
66
67
|
@input_html_classes = @html_classes.dup
|
|
67
68
|
@input_html_options = html_options_for(:input, input_html_classes).tap do |o|
|
|
@@ -79,6 +80,14 @@ module SimpleForm
|
|
|
79
80
|
options
|
|
80
81
|
end
|
|
81
82
|
|
|
83
|
+
def additional_classes
|
|
84
|
+
@additional_classes ||= [input_type, required_class, readonly_class, disabled_class].compact
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def input_class
|
|
88
|
+
"#{lookup_model_names.join("_")}_#{reflection_or_attribute_name}"
|
|
89
|
+
end
|
|
90
|
+
|
|
82
91
|
private
|
|
83
92
|
|
|
84
93
|
def add_size!
|
|
@@ -86,7 +95,22 @@ module SimpleForm
|
|
|
86
95
|
end
|
|
87
96
|
|
|
88
97
|
def limit
|
|
89
|
-
|
|
98
|
+
if column
|
|
99
|
+
decimal_or_float? ? decimal_limit : column_limit
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def column_limit
|
|
104
|
+
column.limit
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Add one for decimal point
|
|
108
|
+
def decimal_limit
|
|
109
|
+
column_limit && (column_limit + 1)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def decimal_or_float?
|
|
113
|
+
column.number? && column.type != :integer
|
|
90
114
|
end
|
|
91
115
|
|
|
92
116
|
def nested_boolean_style?
|
|
@@ -95,14 +119,15 @@ module SimpleForm
|
|
|
95
119
|
|
|
96
120
|
# Find reflection name when available, otherwise use attribute
|
|
97
121
|
def reflection_or_attribute_name
|
|
98
|
-
reflection ? reflection.name : attribute_name
|
|
122
|
+
@reflection_or_attribute_name ||= reflection ? reflection.name : attribute_name
|
|
99
123
|
end
|
|
100
124
|
|
|
101
125
|
# Retrieve options for the given namespace from the options hash
|
|
102
126
|
def html_options_for(namespace, css_classes)
|
|
103
|
-
html_options = options[:"#{namespace}_html"]
|
|
127
|
+
html_options = options[:"#{namespace}_html"]
|
|
128
|
+
html_options = html_options ? html_options.dup : {}
|
|
104
129
|
css_classes << html_options[:class] if html_options.key?(:class)
|
|
105
|
-
html_options[:class] = css_classes
|
|
130
|
+
html_options[:class] = css_classes unless css_classes.empty?
|
|
106
131
|
html_options
|
|
107
132
|
end
|
|
108
133
|
|
|
@@ -151,7 +176,7 @@ module SimpleForm
|
|
|
151
176
|
lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
|
|
152
177
|
end
|
|
153
178
|
lookups << :"defaults.#{lookup_action}.#{reflection_or_attribute_name}"
|
|
154
|
-
lookups << :"defaults.#{
|
|
179
|
+
lookups << :"defaults.#{reflection_or_attribute_name}"
|
|
155
180
|
lookups << default
|
|
156
181
|
|
|
157
182
|
I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
|