simple_form 3.0.1 → 4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -25
- data/MIT-LICENSE +1 -1
- data/README.md +441 -107
- data/lib/generators/simple_form/install_generator.rb +4 -3
- data/lib/generators/simple_form/templates/README +4 -5
- data/lib/generators/simple_form/templates/_form.html.erb +2 -0
- data/lib/generators/simple_form/templates/_form.html.haml +2 -0
- data/lib/generators/simple_form/templates/_form.html.slim +1 -0
- data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +48 -11
- data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +417 -23
- data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +101 -5
- data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
- data/lib/simple_form/action_view_extensions/builder.rb +2 -0
- data/lib/simple_form/action_view_extensions/form_helper.rb +10 -3
- data/lib/simple_form/components/errors.rb +38 -3
- data/lib/simple_form/components/hints.rb +3 -2
- data/lib/simple_form/components/html5.rb +16 -5
- data/lib/simple_form/components/label_input.rb +21 -2
- data/lib/simple_form/components/labels.rb +22 -11
- data/lib/simple_form/components/maxlength.rb +9 -5
- data/lib/simple_form/components/min_max.rb +2 -1
- data/lib/simple_form/components/minlength.rb +38 -0
- data/lib/simple_form/components/pattern.rb +2 -1
- data/lib/simple_form/components/placeholders.rb +4 -3
- data/lib/simple_form/components/readonly.rb +2 -1
- data/lib/simple_form/components.rb +2 -0
- data/lib/simple_form/error_notification.rb +1 -0
- data/lib/simple_form/form_builder.rb +193 -80
- data/lib/simple_form/helpers/autofocus.rb +1 -0
- data/lib/simple_form/helpers/disabled.rb +1 -0
- data/lib/simple_form/helpers/readonly.rb +1 -0
- data/lib/simple_form/helpers/required.rb +1 -0
- data/lib/simple_form/helpers/validators.rb +2 -1
- data/lib/simple_form/helpers.rb +6 -5
- data/lib/simple_form/i18n_cache.rb +1 -0
- data/lib/simple_form/inputs/base.rb +57 -14
- data/lib/simple_form/inputs/block_input.rb +2 -1
- data/lib/simple_form/inputs/boolean_input.rb +39 -16
- data/lib/simple_form/inputs/collection_check_boxes_input.rb +2 -1
- data/lib/simple_form/inputs/collection_input.rb +37 -14
- data/lib/simple_form/inputs/collection_radio_buttons_input.rb +9 -13
- data/lib/simple_form/inputs/collection_select_input.rb +5 -2
- data/lib/simple_form/inputs/date_time_input.rb +24 -9
- data/lib/simple_form/inputs/file_input.rb +5 -2
- data/lib/simple_form/inputs/grouped_collection_select_input.rb +16 -3
- data/lib/simple_form/inputs/hidden_input.rb +5 -2
- data/lib/simple_form/inputs/numeric_input.rb +6 -4
- data/lib/simple_form/inputs/password_input.rb +6 -3
- data/lib/simple_form/inputs/priority_input.rb +5 -2
- data/lib/simple_form/inputs/range_input.rb +2 -1
- data/lib/simple_form/inputs/string_input.rb +6 -3
- data/lib/simple_form/inputs/text_input.rb +6 -3
- data/lib/simple_form/inputs.rb +1 -0
- data/lib/simple_form/map_type.rb +1 -0
- data/lib/simple_form/railtie.rb +8 -0
- data/lib/simple_form/tags.rb +7 -0
- data/lib/simple_form/version.rb +2 -1
- data/lib/simple_form/wrappers/builder.rb +7 -6
- data/lib/simple_form/wrappers/leaf.rb +29 -0
- data/lib/simple_form/wrappers/many.rb +7 -6
- data/lib/simple_form/wrappers/root.rb +3 -1
- data/lib/simple_form/wrappers/single.rb +7 -4
- data/lib/simple_form/wrappers.rb +2 -0
- data/lib/simple_form.rb +117 -18
- data/test/action_view_extensions/builder_test.rb +42 -41
- data/test/action_view_extensions/form_helper_test.rb +36 -16
- data/test/components/custom_components_test.rb +62 -0
- data/test/components/label_test.rb +70 -41
- data/test/form_builder/association_test.rb +79 -37
- data/test/form_builder/button_test.rb +11 -10
- data/test/form_builder/error_notification_test.rb +2 -1
- data/test/form_builder/error_test.rb +173 -24
- data/test/form_builder/general_test.rb +171 -73
- data/test/form_builder/hint_test.rb +24 -18
- data/test/form_builder/input_field_test.rb +132 -56
- data/test/form_builder/label_test.rb +68 -13
- data/test/form_builder/wrapper_test.rb +190 -22
- data/test/generators/simple_form_generator_test.rb +8 -7
- data/test/inputs/boolean_input_test.rb +89 -6
- data/test/inputs/collection_check_boxes_input_test.rb +109 -25
- data/test/inputs/collection_radio_buttons_input_test.rb +168 -54
- data/test/inputs/collection_select_input_test.rb +183 -77
- data/test/inputs/datetime_input_test.rb +131 -50
- data/test/inputs/disabled_test.rb +16 -15
- data/test/inputs/discovery_test.rb +58 -6
- data/test/inputs/file_input_test.rb +3 -2
- data/test/inputs/general_test.rb +23 -22
- data/test/inputs/grouped_collection_select_input_test.rb +54 -17
- data/test/inputs/hidden_input_test.rb +5 -4
- data/test/inputs/numeric_input_test.rb +48 -44
- data/test/inputs/priority_input_test.rb +23 -14
- data/test/inputs/readonly_test.rb +20 -19
- data/test/inputs/required_test.rb +58 -13
- data/test/inputs/string_input_test.rb +62 -33
- data/test/inputs/text_input_test.rb +20 -7
- data/test/simple_form_test.rb +9 -0
- data/test/support/discovery_inputs.rb +33 -2
- data/test/support/misc_helpers.rb +113 -5
- data/test/support/mock_controller.rb +7 -1
- data/test/support/models.rb +137 -37
- data/test/test_helper.rb +15 -1
- metadata +25 -33
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'active_support/core_ext/object/deep_dup'
|
|
2
3
|
require 'simple_form/map_type'
|
|
3
4
|
require 'simple_form/tags'
|
|
@@ -8,28 +9,29 @@ module SimpleForm
|
|
|
8
9
|
|
|
9
10
|
# When action is create or update, we still should use new and edit
|
|
10
11
|
ACTIONS = {
|
|
11
|
-
create
|
|
12
|
-
update
|
|
12
|
+
'create' => 'new',
|
|
13
|
+
'update' => 'edit'
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
ATTRIBUTE_COMPONENTS = [
|
|
16
|
+
ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
|
|
16
17
|
|
|
17
18
|
extend MapType
|
|
18
19
|
include SimpleForm::Inputs
|
|
19
20
|
|
|
20
|
-
map_type :text,
|
|
21
|
-
map_type :file,
|
|
22
|
-
map_type :string, :email, :search, :tel, :url, to: SimpleForm::Inputs::StringInput
|
|
23
|
-
map_type :password,
|
|
24
|
-
map_type :integer, :decimal, :float,
|
|
25
|
-
map_type :range,
|
|
26
|
-
map_type :check_boxes,
|
|
27
|
-
map_type :radio_buttons,
|
|
28
|
-
map_type :select,
|
|
29
|
-
map_type :grouped_select,
|
|
30
|
-
map_type :date, :time, :datetime,
|
|
31
|
-
map_type :country, :time_zone,
|
|
32
|
-
map_type :boolean,
|
|
21
|
+
map_type :text, :hstore, :json, :jsonb, to: SimpleForm::Inputs::TextInput
|
|
22
|
+
map_type :file, to: SimpleForm::Inputs::FileInput
|
|
23
|
+
map_type :string, :email, :search, :tel, :url, :uuid, :citext, 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
|
+
map_type :hidden, to: SimpleForm::Inputs::HiddenInput
|
|
33
35
|
|
|
34
36
|
def self.discovery_cache
|
|
35
37
|
@discovery_cache ||= {}
|
|
@@ -37,6 +39,7 @@ module SimpleForm
|
|
|
37
39
|
|
|
38
40
|
def initialize(*) #:nodoc:
|
|
39
41
|
super
|
|
42
|
+
@object = convert_to_model(@object)
|
|
40
43
|
@defaults = options[:defaults]
|
|
41
44
|
@wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
|
|
42
45
|
end
|
|
@@ -47,6 +50,11 @@ module SimpleForm
|
|
|
47
50
|
# label + input + hint (when defined) + errors (when exists), and all can
|
|
48
51
|
# be configured inside a wrapper html.
|
|
49
52
|
#
|
|
53
|
+
# If a block is given, the contents of the block will replace the input
|
|
54
|
+
# field that would otherwise be generated automatically. The content will
|
|
55
|
+
# be given a label and wrapper div to make it consistent with the other
|
|
56
|
+
# elements in the form.
|
|
57
|
+
#
|
|
50
58
|
# == Examples
|
|
51
59
|
#
|
|
52
60
|
# # Imagine @user has error "can't be blank" on name
|
|
@@ -106,18 +114,13 @@ module SimpleForm
|
|
|
106
114
|
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
|
|
107
115
|
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
|
|
108
116
|
#
|
|
109
|
-
def input(attribute_name, options={}, &block)
|
|
117
|
+
def input(attribute_name, options = {}, &block)
|
|
110
118
|
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
111
|
-
input = find_input(attribute_name, options, &block)
|
|
112
119
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
|
|
116
|
-
else
|
|
117
|
-
wrapper
|
|
118
|
-
end
|
|
120
|
+
input = find_input(attribute_name, options, &block)
|
|
121
|
+
wrapper = find_wrapper(input.input_type, options)
|
|
119
122
|
|
|
120
|
-
|
|
123
|
+
wrapper.render input
|
|
121
124
|
end
|
|
122
125
|
alias :attribute :input
|
|
123
126
|
|
|
@@ -135,12 +138,41 @@ module SimpleForm
|
|
|
135
138
|
# <input class="string required" id="user_name" maxlength="100"
|
|
136
139
|
# name="user[name]" type="text" value="Carlos" />
|
|
137
140
|
#
|
|
138
|
-
|
|
141
|
+
# It also support validation classes once it is configured.
|
|
142
|
+
#
|
|
143
|
+
# # config/initializers/simple_form.rb
|
|
144
|
+
# SimpleForm.setup do |config|
|
|
145
|
+
# config.input_field_valid_class = 'is-valid'
|
|
146
|
+
# config.input_field_error_class = 'is-invalid'
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# simple_form_for @user do |f|
|
|
150
|
+
# f.input_field :name
|
|
151
|
+
# end
|
|
152
|
+
#
|
|
153
|
+
# When the validation happens, the input will be rendered with
|
|
154
|
+
# the class configured according to the validation:
|
|
155
|
+
#
|
|
156
|
+
# - when the input is valid:
|
|
157
|
+
#
|
|
158
|
+
# <input class="is-valid string required" id="user_name" value="Carlos" />
|
|
159
|
+
#
|
|
160
|
+
# - when the input is invalid:
|
|
161
|
+
#
|
|
162
|
+
# <input class="is-invalid string required" id="user_name" value="" />
|
|
163
|
+
#
|
|
164
|
+
def input_field(attribute_name, options = {})
|
|
165
|
+
components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
|
|
166
|
+
|
|
139
167
|
options = options.dup
|
|
140
|
-
options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *
|
|
168
|
+
options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, :prompt, *components)
|
|
141
169
|
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
142
170
|
|
|
143
|
-
|
|
171
|
+
input = find_input(attribute_name, options)
|
|
172
|
+
wrapper = find_wrapper(input.input_type, options)
|
|
173
|
+
components = build_input_field_components(components.push(:input))
|
|
174
|
+
|
|
175
|
+
SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
|
|
144
176
|
end
|
|
145
177
|
|
|
146
178
|
# Helper for dealing with association selects/radios, generating the
|
|
@@ -171,7 +203,7 @@ module SimpleForm
|
|
|
171
203
|
#
|
|
172
204
|
# Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
|
|
173
205
|
#
|
|
174
|
-
def association(association, options={}, &block)
|
|
206
|
+
def association(association, options = {}, &block)
|
|
175
207
|
options = options.dup
|
|
176
208
|
|
|
177
209
|
return simple_fields_for(*[association,
|
|
@@ -183,31 +215,9 @@ module SimpleForm
|
|
|
183
215
|
raise "Association #{association.inspect} not found" unless reflection
|
|
184
216
|
|
|
185
217
|
options[:as] ||= :select
|
|
186
|
-
options[:collection] ||= options
|
|
187
|
-
conditions = reflection.options[:conditions]
|
|
188
|
-
conditions = conditions.call if conditions.respond_to?(:call)
|
|
189
|
-
reflection.klass.where(conditions).order(reflection.options[:order])
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
attribute = case reflection.macro
|
|
193
|
-
when :belongs_to
|
|
194
|
-
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
|
|
195
|
-
when :has_one
|
|
196
|
-
raise ArgumentError, ":has_one associations are not supported by f.association"
|
|
197
|
-
else
|
|
198
|
-
if options[:as] == :select
|
|
199
|
-
html_options = options[:input_html] ||= {}
|
|
200
|
-
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# Force the association to be preloaded for performance.
|
|
204
|
-
if options[:preload] != false && object.respond_to?(association)
|
|
205
|
-
target = object.send(association)
|
|
206
|
-
target.to_a if target.respond_to?(:to_a)
|
|
207
|
-
end
|
|
218
|
+
options[:collection] ||= fetch_association_collection(reflection, options)
|
|
208
219
|
|
|
209
|
-
|
|
210
|
-
end
|
|
220
|
+
attribute = build_association_attribute(reflection, association, options)
|
|
211
221
|
|
|
212
222
|
input(attribute, options.merge(reflection: reflection))
|
|
213
223
|
end
|
|
@@ -227,8 +237,8 @@ module SimpleForm
|
|
|
227
237
|
options = args.extract_options!.dup
|
|
228
238
|
options[:class] = [SimpleForm.button_class, options[:class]].compact
|
|
229
239
|
args << options
|
|
230
|
-
if respond_to?("#{type}_button")
|
|
231
|
-
send("#{type}_button", *args, &block)
|
|
240
|
+
if respond_to?(:"#{type}_button")
|
|
241
|
+
send(:"#{type}_button", *args, &block)
|
|
232
242
|
else
|
|
233
243
|
send(type, *args, &block)
|
|
234
244
|
end
|
|
@@ -242,7 +252,7 @@ module SimpleForm
|
|
|
242
252
|
# f.error :name
|
|
243
253
|
# f.error :name, id: "cool_error"
|
|
244
254
|
#
|
|
245
|
-
def error(attribute_name, options={})
|
|
255
|
+
def error(attribute_name, options = {})
|
|
246
256
|
options = options.dup
|
|
247
257
|
|
|
248
258
|
options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
|
|
@@ -259,7 +269,7 @@ module SimpleForm
|
|
|
259
269
|
#
|
|
260
270
|
# f.full_error :token #=> <span class="error">Token is invalid</span>
|
|
261
271
|
#
|
|
262
|
-
def full_error(attribute_name, options={})
|
|
272
|
+
def full_error(attribute_name, options = {})
|
|
263
273
|
options = options.dup
|
|
264
274
|
|
|
265
275
|
options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
|
|
@@ -281,7 +291,7 @@ module SimpleForm
|
|
|
281
291
|
# f.hint :name, id: "cool_hint"
|
|
282
292
|
# f.hint "Don't forget to accept this"
|
|
283
293
|
#
|
|
284
|
-
def hint(attribute_name, options={})
|
|
294
|
+
def hint(attribute_name, options = {})
|
|
285
295
|
options = options.dup
|
|
286
296
|
|
|
287
297
|
options[:hint_html] = options.except(:hint_tag, :hint)
|
|
@@ -305,7 +315,7 @@ module SimpleForm
|
|
|
305
315
|
#
|
|
306
316
|
# f.label :name # Do I18n lookup
|
|
307
317
|
# f.label :name, "Name" # Same behavior as Rails, do not add required tag
|
|
308
|
-
# f.label :name, label: "Name"
|
|
318
|
+
# f.label :name, label: "Name" # Same as above, but adds required tag
|
|
309
319
|
#
|
|
310
320
|
# f.label :name, required: false
|
|
311
321
|
# f.label :name, id: "cool_label"
|
|
@@ -314,7 +324,7 @@ module SimpleForm
|
|
|
314
324
|
return super if args.first.is_a?(String) || block_given?
|
|
315
325
|
|
|
316
326
|
options = args.extract_options!.dup
|
|
317
|
-
options[:label_html] = options.except(:label, :required, :as)
|
|
327
|
+
options[:label_html] = options.except(:label, :label_text, :required, :as)
|
|
318
328
|
|
|
319
329
|
column = find_attribute_column(attribute_name)
|
|
320
330
|
input_type = default_input_type(attribute_name, column, options)
|
|
@@ -332,7 +342,7 @@ module SimpleForm
|
|
|
332
342
|
# f.error_notification message: 'Something went wrong'
|
|
333
343
|
# f.error_notification id: 'user_error_message', class: 'form_error'
|
|
334
344
|
#
|
|
335
|
-
def error_notification(options={})
|
|
345
|
+
def error_notification(options = {})
|
|
336
346
|
SimpleForm::ErrorNotification.new(self, options).render
|
|
337
347
|
end
|
|
338
348
|
|
|
@@ -464,15 +474,60 @@ module SimpleForm
|
|
|
464
474
|
@lookup_action ||= begin
|
|
465
475
|
action = template.controller && template.controller.action_name
|
|
466
476
|
return unless action
|
|
467
|
-
action = action.
|
|
477
|
+
action = action.to_s
|
|
468
478
|
ACTIONS[action] || action
|
|
469
479
|
end
|
|
470
480
|
end
|
|
471
481
|
|
|
472
482
|
private
|
|
473
483
|
|
|
484
|
+
def fetch_association_collection(reflection, options)
|
|
485
|
+
options.fetch(:collection) do
|
|
486
|
+
relation = reflection.klass.all
|
|
487
|
+
|
|
488
|
+
if reflection.respond_to?(:scope) && reflection.scope
|
|
489
|
+
if reflection.scope.parameters.any?
|
|
490
|
+
relation = reflection.klass.instance_exec(object, &reflection.scope)
|
|
491
|
+
else
|
|
492
|
+
relation = reflection.klass.instance_exec(&reflection.scope)
|
|
493
|
+
end
|
|
494
|
+
else
|
|
495
|
+
order = reflection.options[:order]
|
|
496
|
+
conditions = reflection.options[:conditions]
|
|
497
|
+
conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
|
|
498
|
+
|
|
499
|
+
relation = relation.where(conditions) if relation.respond_to?(:where)
|
|
500
|
+
relation = relation.order(order) if relation.respond_to?(:order)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
relation
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def build_association_attribute(reflection, association, options)
|
|
508
|
+
case reflection.macro
|
|
509
|
+
when :belongs_to
|
|
510
|
+
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
|
|
511
|
+
when :has_one
|
|
512
|
+
raise ArgumentError, ":has_one associations are not supported by f.association"
|
|
513
|
+
else
|
|
514
|
+
if options[:as] == :select
|
|
515
|
+
html_options = options[:input_html] ||= {}
|
|
516
|
+
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Force the association to be preloaded for performance.
|
|
520
|
+
if options[:preload] != false && object.respond_to?(association)
|
|
521
|
+
target = object.send(association)
|
|
522
|
+
target.to_a if target.respond_to?(:to_a)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
:"#{reflection.name.to_s.singularize}_ids"
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
474
529
|
# Find an input based on the attribute name.
|
|
475
|
-
def find_input(attribute_name, options={}, &block)
|
|
530
|
+
def find_input(attribute_name, options = {}, &block)
|
|
476
531
|
column = find_attribute_column(attribute_name)
|
|
477
532
|
input_type = default_input_type(attribute_name, column, options)
|
|
478
533
|
|
|
@@ -484,18 +539,18 @@ module SimpleForm
|
|
|
484
539
|
end
|
|
485
540
|
|
|
486
541
|
# Attempt to guess the better input type given the defined options. By
|
|
487
|
-
# default
|
|
542
|
+
# default always fallback to the user :as option, or to a :select when a
|
|
488
543
|
# collection is given.
|
|
489
|
-
def default_input_type(attribute_name, column, options)
|
|
544
|
+
def default_input_type(attribute_name, column, options)
|
|
490
545
|
return options[:as].to_sym if options[:as]
|
|
491
|
-
return :select if options[:collection]
|
|
492
546
|
custom_type = find_custom_type(attribute_name.to_s) and return custom_type
|
|
547
|
+
return :select if options[:collection]
|
|
493
548
|
|
|
494
549
|
input_type = column.try(:type)
|
|
495
550
|
case input_type
|
|
496
551
|
when :timestamp
|
|
497
552
|
:datetime
|
|
498
|
-
when :string, nil
|
|
553
|
+
when :string, :citext, nil
|
|
499
554
|
case attribute_name.to_s
|
|
500
555
|
when /password/ then :password
|
|
501
556
|
when /time_zone/ then :time_zone
|
|
@@ -511,24 +566,26 @@ module SimpleForm
|
|
|
511
566
|
end
|
|
512
567
|
end
|
|
513
568
|
|
|
514
|
-
def find_custom_type(attribute_name)
|
|
569
|
+
def find_custom_type(attribute_name)
|
|
515
570
|
SimpleForm.input_mappings.find { |match, type|
|
|
516
571
|
attribute_name =~ match
|
|
517
572
|
}.try(:last) if SimpleForm.input_mappings
|
|
518
573
|
end
|
|
519
574
|
|
|
520
|
-
def file_method?(attribute_name)
|
|
575
|
+
def file_method?(attribute_name)
|
|
521
576
|
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
|
|
522
577
|
file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
|
523
578
|
end
|
|
524
579
|
|
|
525
|
-
def find_attribute_column(attribute_name)
|
|
526
|
-
if @object.respond_to?(:
|
|
580
|
+
def find_attribute_column(attribute_name)
|
|
581
|
+
if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
|
|
582
|
+
@object.type_for_attribute(attribute_name.to_s)
|
|
583
|
+
elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
|
|
527
584
|
@object.column_for_attribute(attribute_name)
|
|
528
585
|
end
|
|
529
586
|
end
|
|
530
587
|
|
|
531
|
-
def find_association_reflection(association)
|
|
588
|
+
def find_association_reflection(association)
|
|
532
589
|
if @object.class.respond_to?(:reflect_on_association)
|
|
533
590
|
@object.class.reflect_on_association(association)
|
|
534
591
|
end
|
|
@@ -541,24 +598,42 @@ module SimpleForm
|
|
|
541
598
|
# b) Or use the found mapping
|
|
542
599
|
# 2) If not, fallbacks to #{input_type}Input
|
|
543
600
|
# 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
|
|
544
|
-
def find_mapping(input_type)
|
|
601
|
+
def find_mapping(input_type)
|
|
545
602
|
discovery_cache[input_type] ||=
|
|
546
603
|
if mapping = self.class.mappings[input_type]
|
|
547
604
|
mapping_override(mapping) || mapping
|
|
548
605
|
else
|
|
549
606
|
camelized = "#{input_type.to_s.camelize}Input"
|
|
550
|
-
|
|
607
|
+
attempt_mapping_with_custom_namespace(camelized) ||
|
|
608
|
+
attempt_mapping(camelized, Object) ||
|
|
609
|
+
attempt_mapping(camelized, self.class) ||
|
|
551
610
|
raise("No input found for #{input_type}")
|
|
552
611
|
end
|
|
553
612
|
end
|
|
554
613
|
|
|
555
|
-
|
|
556
|
-
|
|
614
|
+
# Attempts to find a wrapper mapping. It follows the following rules:
|
|
615
|
+
#
|
|
616
|
+
# 1) It tries to find a wrapper for the current form
|
|
617
|
+
# 2) If not, it tries to find a config
|
|
618
|
+
def find_wrapper_mapping(input_type)
|
|
619
|
+
if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
|
|
620
|
+
options[:wrapper_mappings][input_type]
|
|
621
|
+
else
|
|
622
|
+
SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def find_wrapper(input_type, options)
|
|
627
|
+
if name = options[:wrapper] || find_wrapper_mapping(input_type)
|
|
628
|
+
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
|
|
629
|
+
else
|
|
630
|
+
wrapper
|
|
631
|
+
end
|
|
557
632
|
end
|
|
558
633
|
|
|
559
634
|
# If cache_discovery is enabled, use the class level cache that persists
|
|
560
635
|
# between requests, otherwise use the instance one.
|
|
561
|
-
def discovery_cache
|
|
636
|
+
def discovery_cache
|
|
562
637
|
if SimpleForm.cache_discovery
|
|
563
638
|
self.class.discovery_cache
|
|
564
639
|
else
|
|
@@ -566,14 +641,16 @@ module SimpleForm
|
|
|
566
641
|
end
|
|
567
642
|
end
|
|
568
643
|
|
|
569
|
-
def mapping_override(klass)
|
|
644
|
+
def mapping_override(klass)
|
|
570
645
|
name = klass.name
|
|
571
646
|
if name =~ /^SimpleForm::Inputs/
|
|
572
|
-
|
|
647
|
+
input_name = name.split("::").last
|
|
648
|
+
attempt_mapping_with_custom_namespace(input_name) ||
|
|
649
|
+
attempt_mapping(input_name, Object)
|
|
573
650
|
end
|
|
574
651
|
end
|
|
575
652
|
|
|
576
|
-
def attempt_mapping(mapping, at)
|
|
653
|
+
def attempt_mapping(mapping, at)
|
|
577
654
|
return if SimpleForm.inputs_discovery == false && at == Object
|
|
578
655
|
|
|
579
656
|
begin
|
|
@@ -582,5 +659,41 @@ module SimpleForm
|
|
|
582
659
|
raise if e.message !~ /#{mapping}$/
|
|
583
660
|
end
|
|
584
661
|
end
|
|
662
|
+
|
|
663
|
+
def attempt_mapping_with_custom_namespace(input_name)
|
|
664
|
+
SimpleForm.custom_inputs_namespaces.each do |namespace|
|
|
665
|
+
if (mapping = attempt_mapping(input_name, namespace.constantize))
|
|
666
|
+
return mapping
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
nil
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def build_input_field_components(components)
|
|
674
|
+
components.map do |component|
|
|
675
|
+
if component == :input
|
|
676
|
+
SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
|
|
677
|
+
else
|
|
678
|
+
SimpleForm::Wrappers::Leaf.new(component)
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def build_input_field_options
|
|
684
|
+
input_field_options = {}
|
|
685
|
+
valid_class = SimpleForm.input_field_valid_class
|
|
686
|
+
error_class = SimpleForm.input_field_error_class
|
|
687
|
+
|
|
688
|
+
if error_class.present?
|
|
689
|
+
input_field_options[:error_class] = error_class
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
if valid_class.present?
|
|
693
|
+
input_field_options[:valid_class] = valid_class
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
input_field_options
|
|
697
|
+
end
|
|
585
698
|
end
|
|
586
699
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SimpleForm
|
|
2
3
|
module Helpers
|
|
3
4
|
module Validators
|
|
@@ -24,7 +25,7 @@ module SimpleForm
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def action_validator_match?(validator)
|
|
27
|
-
return true
|
|
28
|
+
return true unless validator.options.include?(:on)
|
|
28
29
|
|
|
29
30
|
case validator.options[:on]
|
|
30
31
|
when :save
|
data/lib/simple_form/helpers.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SimpleForm
|
|
2
3
|
# Helpers are made of several helpers that cannot be turned on automatically.
|
|
3
4
|
# For instance, disabled cannot be turned on automatically, it requires the
|
|
4
5
|
# user to explicitly pass the option disabled: true so it may work.
|
|
5
6
|
module Helpers
|
|
6
|
-
autoload :Autofocus,
|
|
7
|
-
autoload :Disabled,
|
|
8
|
-
autoload :Readonly,
|
|
9
|
-
autoload :Required,
|
|
10
|
-
autoload :Validators,
|
|
7
|
+
autoload :Autofocus, 'simple_form/helpers/autofocus'
|
|
8
|
+
autoload :Disabled, 'simple_form/helpers/disabled'
|
|
9
|
+
autoload :Readonly, 'simple_form/helpers/readonly'
|
|
10
|
+
autoload :Required, 'simple_form/helpers/required'
|
|
11
|
+
autoload :Validators, 'simple_form/helpers/validators'
|
|
11
12
|
end
|
|
12
13
|
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'simple_form/i18n_cache'
|
|
2
3
|
require 'active_support/core_ext/string/output_safety'
|
|
4
|
+
require 'action_view/helpers'
|
|
3
5
|
|
|
4
6
|
module SimpleForm
|
|
5
7
|
module Inputs
|
|
6
8
|
class Base
|
|
7
9
|
include ERB::Util
|
|
10
|
+
include ActionView::Helpers::TranslationHelper
|
|
8
11
|
|
|
9
12
|
extend I18nCache
|
|
10
13
|
|
|
@@ -19,6 +22,7 @@ module SimpleForm
|
|
|
19
22
|
include SimpleForm::Components::HTML5
|
|
20
23
|
include SimpleForm::Components::LabelInput
|
|
21
24
|
include SimpleForm::Components::Maxlength
|
|
25
|
+
include SimpleForm::Components::Minlength
|
|
22
26
|
include SimpleForm::Components::MinMax
|
|
23
27
|
include SimpleForm::Components::Pattern
|
|
24
28
|
include SimpleForm::Components::Placeholders
|
|
@@ -48,19 +52,19 @@ module SimpleForm
|
|
|
48
52
|
enable :hint
|
|
49
53
|
|
|
50
54
|
# Usually disabled, needs to be enabled explicitly passing true as option.
|
|
51
|
-
disable :maxlength, :placeholder, :pattern, :min_max
|
|
55
|
+
disable :maxlength, :minlength, :placeholder, :pattern, :min_max
|
|
52
56
|
|
|
53
57
|
def initialize(builder, attribute_name, column, input_type, options = {})
|
|
54
58
|
super
|
|
55
59
|
|
|
56
|
-
options
|
|
57
|
-
@builder
|
|
58
|
-
@attribute_name
|
|
59
|
-
@column
|
|
60
|
-
@input_type
|
|
61
|
-
@reflection
|
|
62
|
-
@options
|
|
63
|
-
@required
|
|
60
|
+
options = options.dup
|
|
61
|
+
@builder = builder
|
|
62
|
+
@attribute_name = attribute_name
|
|
63
|
+
@column = column
|
|
64
|
+
@input_type = input_type
|
|
65
|
+
@reflection = options.delete(:reflection)
|
|
66
|
+
@options = options.reverse_merge!(self.class.default_options)
|
|
67
|
+
@required = calculate_required
|
|
64
68
|
|
|
65
69
|
# Notice that html_options_for receives a reference to input_html_classes.
|
|
66
70
|
# This means that classes added dynamically to input_html_classes will
|
|
@@ -79,7 +83,7 @@ module SimpleForm
|
|
|
79
83
|
end
|
|
80
84
|
end
|
|
81
85
|
|
|
82
|
-
def input
|
|
86
|
+
def input(wrapper_options = nil)
|
|
83
87
|
raise NotImplementedError
|
|
84
88
|
end
|
|
85
89
|
|
|
@@ -92,7 +96,7 @@ module SimpleForm
|
|
|
92
96
|
end
|
|
93
97
|
|
|
94
98
|
def input_class
|
|
95
|
-
"#{lookup_model_names.join(
|
|
99
|
+
"#{lookup_model_names.join('_')}_#{reflection_or_attribute_name}"
|
|
96
100
|
end
|
|
97
101
|
|
|
98
102
|
private
|
|
@@ -113,7 +117,7 @@ module SimpleForm
|
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def decimal_or_float?
|
|
116
|
-
column.
|
|
120
|
+
column.type == :float || column.type == :decimal
|
|
117
121
|
end
|
|
118
122
|
|
|
119
123
|
def nested_boolean_style?
|
|
@@ -167,7 +171,7 @@ module SimpleForm
|
|
|
167
171
|
# email: 'E-mail.'
|
|
168
172
|
#
|
|
169
173
|
# Take a look at our locale example file.
|
|
170
|
-
def
|
|
174
|
+
def translate_from_namespace(namespace, default = '')
|
|
171
175
|
model_names = lookup_model_names.dup
|
|
172
176
|
lookups = []
|
|
173
177
|
|
|
@@ -182,7 +186,46 @@ module SimpleForm
|
|
|
182
186
|
lookups << :"defaults.#{reflection_or_attribute_name}"
|
|
183
187
|
lookups << default
|
|
184
188
|
|
|
185
|
-
I18n.t(lookups.shift, scope: :"
|
|
189
|
+
I18n.t(lookups.shift, scope: :"#{i18n_scope}.#{namespace}", default: lookups).presence
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def merge_wrapper_options(options, wrapper_options)
|
|
193
|
+
if wrapper_options
|
|
194
|
+
wrapper_options = set_input_classes(wrapper_options)
|
|
195
|
+
|
|
196
|
+
wrapper_options.merge(options) do |key, oldval, newval|
|
|
197
|
+
case key.to_s
|
|
198
|
+
when "class"
|
|
199
|
+
Array(oldval) + Array(newval)
|
|
200
|
+
when "data", "aria"
|
|
201
|
+
oldval.merge(newval)
|
|
202
|
+
else
|
|
203
|
+
newval
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
else
|
|
207
|
+
options
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def set_input_classes(wrapper_options)
|
|
212
|
+
wrapper_options = wrapper_options.dup
|
|
213
|
+
error_class = wrapper_options.delete(:error_class)
|
|
214
|
+
valid_class = wrapper_options.delete(:valid_class)
|
|
215
|
+
|
|
216
|
+
if error_class.present? && has_errors?
|
|
217
|
+
wrapper_options[:class] = "#{wrapper_options[:class]} #{error_class}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if valid_class.present? && valid?
|
|
221
|
+
wrapper_options[:class] = "#{wrapper_options[:class]} #{valid_class}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
wrapper_options
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def i18n_scope
|
|
228
|
+
SimpleForm.i18n_scope
|
|
186
229
|
end
|
|
187
230
|
end
|
|
188
231
|
end
|