simple_form 1.2.2 → 1.3.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.
Potentially problematic release.
This version of simple_form might be problematic. Click here for more details.
- data/README.rdoc +80 -14
- data/lib/generators/simple_form/templates/_form.html.erb +1 -11
- data/lib/generators/simple_form/templates/simple_form.rb +31 -6
- data/lib/simple_form.rb +38 -2
- data/lib/simple_form/action_view_extensions/builder.rb +53 -20
- data/lib/simple_form/components.rb +6 -5
- data/lib/simple_form/components/errors.rb +4 -6
- data/lib/simple_form/components/hints.rb +2 -2
- data/lib/simple_form/components/labels.rb +3 -5
- data/lib/simple_form/components/placeholders.rb +22 -0
- data/lib/simple_form/components/wrapper.rb +7 -0
- data/lib/simple_form/error_notification.rb +4 -6
- data/lib/simple_form/form_builder.rb +61 -55
- data/lib/simple_form/has_errors.rb +14 -0
- data/lib/simple_form/inputs/base.rb +45 -17
- data/lib/simple_form/inputs/block_input.rb +3 -2
- data/lib/simple_form/inputs/collection_input.rb +42 -17
- data/lib/simple_form/inputs/date_time_input.rb +3 -1
- data/lib/simple_form/inputs/hidden_input.rb +11 -2
- data/lib/simple_form/inputs/mapping_input.rb +16 -3
- data/lib/simple_form/inputs/numeric_input.rb +45 -8
- data/lib/simple_form/inputs/string_input.rb +13 -9
- data/lib/simple_form/map_type.rb +4 -4
- data/lib/simple_form/version.rb +1 -1
- data/test/action_view_extensions/builder_test.rb +155 -51
- data/test/components/error_test.rb +10 -8
- data/test/components/hint_test.rb +4 -8
- data/test/components/label_test.rb +22 -9
- data/test/components/wrapper_test.rb +16 -7
- data/test/error_notification_test.rb +4 -3
- data/test/form_builder_test.rb +81 -34
- data/test/inputs_test.rb +242 -3
- data/test/support/misc_helpers.rb +6 -4
- data/test/support/models.rb +26 -3
- data/test/test_helper.rb +13 -5
- metadata +25 -8
@@ -1,9 +1,10 @@
|
|
1
1
|
module SimpleForm
|
2
2
|
module Components
|
3
|
-
autoload :Errors,
|
4
|
-
autoload :Hints,
|
5
|
-
autoload :LabelInput,
|
6
|
-
autoload :Labels,
|
7
|
-
autoload :
|
3
|
+
autoload :Errors, 'simple_form/components/errors'
|
4
|
+
autoload :Hints, 'simple_form/components/hints'
|
5
|
+
autoload :LabelInput, 'simple_form/components/label_input'
|
6
|
+
autoload :Labels, 'simple_form/components/labels'
|
7
|
+
autoload :Placeholders, 'simple_form/components/placeholders'
|
8
|
+
autoload :Wrapper, 'simple_form/components/wrapper'
|
8
9
|
end
|
9
10
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module SimpleForm
|
2
2
|
module Components
|
3
3
|
module Errors
|
4
|
+
include SimpleForm::HasErrors
|
5
|
+
|
4
6
|
def error
|
5
7
|
template.content_tag(error_tag, error_text, error_html_options) if has_errors?
|
6
8
|
end
|
@@ -18,15 +20,11 @@ module SimpleForm
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def error_html_options
|
21
|
-
html_options_for(:error, [
|
23
|
+
html_options_for(:error, [SimpleForm.error_class])
|
22
24
|
end
|
23
25
|
|
24
26
|
protected
|
25
27
|
|
26
|
-
def has_errors?
|
27
|
-
object && errors.present?
|
28
|
-
end
|
29
|
-
|
30
28
|
def errors
|
31
29
|
@errors ||= (errors_on_attribute + errors_on_association).compact
|
32
30
|
end
|
@@ -40,4 +38,4 @@ module SimpleForm
|
|
40
38
|
end
|
41
39
|
end
|
42
40
|
end
|
43
|
-
end
|
41
|
+
end
|
@@ -52,16 +52,14 @@ module SimpleForm
|
|
52
52
|
attribute_required? ? self.class.translate_required_html.dup : ''
|
53
53
|
end
|
54
54
|
|
55
|
-
# First check
|
55
|
+
# First check labels translation and then human attribute name.
|
56
56
|
def label_translation #:nodoc:
|
57
|
-
|
57
|
+
translate(:labels) || if object.class.respond_to?(:human_attribute_name)
|
58
58
|
object.class.human_attribute_name(reflection_or_attribute_name.to_s)
|
59
59
|
else
|
60
60
|
attribute_name.to_s.humanize
|
61
61
|
end
|
62
|
-
|
63
|
-
translate(:labels, default)
|
64
62
|
end
|
65
63
|
end
|
66
64
|
end
|
67
|
-
end
|
65
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Components
|
3
|
+
module Placeholders
|
4
|
+
def placeholder
|
5
|
+
input_html_options[:placeholder] ||= placeholder_text if has_placeholder?
|
6
|
+
nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_placeholder?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def placeholder_present?
|
14
|
+
options[:placeholder] != false && placeholder_text.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
def placeholder_text
|
18
|
+
@placeholder ||= options[:placeholder] || translate(:placeholders)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -24,8 +24,15 @@ module SimpleForm
|
|
24
24
|
def wrapper_html_options
|
25
25
|
css_classes = input_html_classes.unshift(wrapper_class)
|
26
26
|
css_classes << wrapper_error_class if has_errors?
|
27
|
+
css_classes << disabled_class if disabled?
|
27
28
|
html_options_for(:wrapper, css_classes)
|
28
29
|
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def disabled_class
|
34
|
+
'disabled'
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|
31
38
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module SimpleForm
|
2
2
|
class ErrorNotification
|
3
3
|
delegate :object, :object_name, :template, :to => :@builder
|
4
|
+
include SimpleForm::HasErrors
|
4
5
|
|
5
6
|
def initialize(builder, options)
|
6
7
|
@builder = builder
|
@@ -24,12 +25,9 @@ module SimpleForm
|
|
24
25
|
SimpleForm.error_notification_tag
|
25
26
|
end
|
26
27
|
|
27
|
-
def has_errors?
|
28
|
-
object && object.respond_to?(:errors) && object.errors.present?
|
29
|
-
end
|
30
|
-
|
31
28
|
def html_options
|
32
|
-
@options[:class] = "
|
29
|
+
@options[:class] = "#{SimpleForm.error_notification_class} #{@options[:class]}".strip
|
30
|
+
@options[:id] = SimpleForm.error_notification_id if SimpleForm.error_notification_id
|
33
31
|
@options
|
34
32
|
end
|
35
33
|
|
@@ -41,4 +39,4 @@ module SimpleForm
|
|
41
39
|
I18n.t(lookups.shift, :scope => :"simple_form.error_notification", :default => lookups)
|
42
40
|
end
|
43
41
|
end
|
44
|
-
end
|
42
|
+
end
|
@@ -1,18 +1,17 @@
|
|
1
1
|
module SimpleForm
|
2
2
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
-
attr_reader :template, :object_name, :object
|
4
|
-
:reflection, :input_type, :options
|
3
|
+
attr_reader :template, :object_name, :object
|
5
4
|
|
6
5
|
extend MapType
|
7
6
|
include SimpleForm::Inputs
|
8
7
|
|
9
|
-
map_type :password, :text, :file,
|
10
|
-
map_type :string, :email, :url,
|
11
|
-
map_type :integer, :decimal, :float,
|
12
|
-
map_type :select, :radio, :check_boxes,
|
13
|
-
map_type :date, :time, :datetime,
|
14
|
-
map_type :country, :time_zone,
|
15
|
-
map_type :boolean,
|
8
|
+
map_type :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
|
9
|
+
map_type :string, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
|
10
|
+
map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
|
11
|
+
map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
|
12
|
+
map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
|
13
|
+
map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
|
14
|
+
map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
|
16
15
|
|
17
16
|
# Basic input helper, combines all components in the stack to generate
|
18
17
|
# input html based on options the user define and some guesses through
|
@@ -80,14 +79,14 @@ module SimpleForm
|
|
80
79
|
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
|
81
80
|
#
|
82
81
|
def input(attribute_name, options={}, &block)
|
83
|
-
|
82
|
+
column = find_attribute_column(attribute_name)
|
83
|
+
input_type = default_input_type(attribute_name, column, options)
|
84
84
|
|
85
85
|
if block_given?
|
86
|
-
SimpleForm::Inputs::BlockInput.new(self, block).render
|
86
|
+
SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block).render
|
87
87
|
else
|
88
|
-
klass = self.class.mappings[input_type] ||
|
89
|
-
|
90
|
-
klass.new(self).render
|
88
|
+
klass = self.class.mappings[input_type] || self.class.const_get("#{input_type.to_s.camelize}Input")
|
89
|
+
klass.new(self, attribute_name, column, input_type, options).render
|
91
90
|
end
|
92
91
|
end
|
93
92
|
alias :attribute :input
|
@@ -124,30 +123,28 @@ module SimpleForm
|
|
124
123
|
|
125
124
|
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
|
126
125
|
|
126
|
+
reflection = find_association_reflection(association)
|
127
|
+
raise "Association #{association.inspect} not found" unless reflection
|
128
|
+
|
127
129
|
options[:as] ||= :select
|
128
|
-
|
129
|
-
raise "Association #{association.inspect} not found" unless @reflection
|
130
|
+
options[:collection] ||= reflection.klass.all(reflection.options.slice(:conditions, :order))
|
130
131
|
|
131
|
-
case
|
132
|
+
attribute = case reflection.macro
|
132
133
|
when :belongs_to
|
133
|
-
|
134
|
+
reflection.options[:foreign_key] || :"#{reflection.name}_id"
|
134
135
|
when :has_one
|
135
136
|
raise ":has_one association are not supported by f.association"
|
136
137
|
else
|
137
|
-
attribute = :"#{@reflection.name.to_s.singularize}_ids"
|
138
|
-
|
139
138
|
if options[:as] == :select
|
140
139
|
html_options = options[:input_html] ||= {}
|
141
140
|
html_options[:size] ||= 5
|
142
141
|
html_options[:multiple] = true unless html_options.key?(:multiple)
|
143
142
|
end
|
144
|
-
end
|
145
143
|
|
146
|
-
|
147
|
-
|
148
|
-
)
|
144
|
+
:"#{reflection.name.to_s.singularize}_ids"
|
145
|
+
end
|
149
146
|
|
150
|
-
input(attribute, options
|
147
|
+
input(attribute, options.merge(:reflection => reflection))
|
151
148
|
end
|
152
149
|
|
153
150
|
# Creates a button:
|
@@ -159,8 +156,11 @@ module SimpleForm
|
|
159
156
|
# It just acts as a proxy to method name given.
|
160
157
|
#
|
161
158
|
def button(type, *args, &block)
|
162
|
-
|
163
|
-
|
159
|
+
options = args.extract_options!
|
160
|
+
options[:class] = "button #{options[:class]}".strip
|
161
|
+
args << options
|
162
|
+
if respond_to?("#{type}_button")
|
163
|
+
send("#{type}_button", *args, &block)
|
164
164
|
else
|
165
165
|
send(type, *args, &block)
|
166
166
|
end
|
@@ -175,8 +175,10 @@ module SimpleForm
|
|
175
175
|
# f.error :name, :id => "cool_error"
|
176
176
|
#
|
177
177
|
def error(attribute_name, options={})
|
178
|
-
|
179
|
-
|
178
|
+
options[:error_html] = options
|
179
|
+
column = find_attribute_column(attribute_name)
|
180
|
+
input_type = default_input_type(attribute_name, column, options)
|
181
|
+
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
|
180
182
|
end
|
181
183
|
|
182
184
|
# Creates a hint tag for the given attribute. Accepts a symbol indicating
|
@@ -190,9 +192,15 @@ module SimpleForm
|
|
190
192
|
# f.hint "Don't forget to accept this"
|
191
193
|
#
|
192
194
|
def hint(attribute_name, options={})
|
193
|
-
|
194
|
-
|
195
|
-
|
195
|
+
options[:hint_html] = options
|
196
|
+
if attribute_name.is_a?(String)
|
197
|
+
options[:hint] = attribute_name
|
198
|
+
attribute_name, column, input_type = nil, nil, nil
|
199
|
+
else
|
200
|
+
column = find_attribute_column(attribute_name)
|
201
|
+
input_type = default_input_type(attribute_name, column, options)
|
202
|
+
end
|
203
|
+
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).hint
|
196
204
|
end
|
197
205
|
|
198
206
|
# Creates a default label tag for the given attribute. You can give a label
|
@@ -211,9 +219,12 @@ module SimpleForm
|
|
211
219
|
def label(attribute_name, *args)
|
212
220
|
return super if args.first.is_a?(String)
|
213
221
|
options = args.extract_options!
|
214
|
-
|
215
|
-
|
216
|
-
|
222
|
+
options[:label] = options.delete(:label)
|
223
|
+
options[:label_html] = options
|
224
|
+
options[:required] = options.delete(:required)
|
225
|
+
column = find_attribute_column(attribute_name)
|
226
|
+
input_type = default_input_type(attribute_name, column, options)
|
227
|
+
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
|
217
228
|
end
|
218
229
|
|
219
230
|
# Creates an error notification message that only appears when the form object
|
@@ -233,52 +244,47 @@ module SimpleForm
|
|
233
244
|
|
234
245
|
private
|
235
246
|
|
236
|
-
# Setup default simple form attributes.
|
237
|
-
def define_simple_form_attributes(attribute_name, options) #:nodoc:
|
238
|
-
@options = options
|
239
|
-
|
240
|
-
if @attribute_name = attribute_name
|
241
|
-
@column = find_attribute_column
|
242
|
-
@input_type = default_input_type
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
247
|
# Attempt to guess the better input type given the defined options. By
|
247
248
|
# default alwayls fallback to the user :as option, or to a :select when a
|
248
249
|
# collection is given.
|
249
|
-
def default_input_type #:nodoc:
|
250
|
-
return
|
251
|
-
return :select
|
250
|
+
def default_input_type(attribute_name, column, options) #:nodoc:
|
251
|
+
return options[:as].to_sym if options[:as]
|
252
|
+
return :select if options[:collection]
|
252
253
|
|
253
|
-
input_type =
|
254
|
+
input_type = column.try(:type)
|
254
255
|
|
255
256
|
case input_type
|
256
257
|
when :timestamp
|
257
258
|
:datetime
|
258
259
|
when :string, nil
|
259
|
-
match = case
|
260
|
+
match = case attribute_name.to_s
|
260
261
|
when /password/ then :password
|
261
262
|
when /time_zone/ then :time_zone
|
262
263
|
when /country/ then :country
|
263
264
|
when /email/ then :email
|
265
|
+
when /phone/ then :tel
|
264
266
|
when /url/ then :url
|
267
|
+
else
|
268
|
+
SimpleForm.input_mappings.find { |match, type|
|
269
|
+
attribute_name.to_s =~ match
|
270
|
+
}.try(:last) if SimpleForm.input_mappings
|
265
271
|
end
|
266
272
|
|
267
|
-
match || input_type || file_method? || :string
|
273
|
+
match || input_type || file_method?(attribute_name) || :string
|
268
274
|
else
|
269
275
|
input_type
|
270
276
|
end
|
271
277
|
end
|
272
278
|
|
273
279
|
# Checks if attribute is a file_method.
|
274
|
-
def file_method? #:nodoc:
|
275
|
-
file = @object.send(
|
280
|
+
def file_method?(attribute_name) #:nodoc:
|
281
|
+
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
|
276
282
|
:file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
277
283
|
end
|
278
284
|
|
279
285
|
# Finds the database column for the given attribute
|
280
|
-
def find_attribute_column #:nodoc:
|
281
|
-
@object.column_for_attribute(
|
286
|
+
def find_attribute_column(attribute_name) #:nodoc:
|
287
|
+
@object.column_for_attribute(attribute_name) if @object.respond_to?(:column_for_attribute)
|
282
288
|
end
|
283
289
|
|
284
290
|
# Find reflection related to association
|
@@ -12,13 +12,25 @@ module SimpleForm
|
|
12
12
|
include SimpleForm::Components::Errors
|
13
13
|
include SimpleForm::Components::Hints
|
14
14
|
include SimpleForm::Components::LabelInput
|
15
|
+
include SimpleForm::Components::Placeholders
|
15
16
|
include SimpleForm::Components::Wrapper
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
attr_reader :attribute_name, :column, :input_type, :reflection,
|
19
|
+
:options, :input_html_options
|
20
|
+
|
21
|
+
delegate :template, :object, :object_name, :to => :@builder
|
22
|
+
|
23
|
+
def initialize(builder, attribute_name, column, input_type, options = {})
|
24
|
+
@builder = builder
|
25
|
+
@attribute_name = attribute_name
|
26
|
+
@column = column
|
27
|
+
@input_type = input_type
|
28
|
+
@reflection = options.delete(:reflection)
|
29
|
+
@options = options
|
30
|
+
@input_html_options = html_options_for(:input, input_html_classes).tap do |o|
|
31
|
+
o[:required] = true if attribute_required?
|
32
|
+
o[:disabled] = true if disabled?
|
33
|
+
end
|
22
34
|
end
|
23
35
|
|
24
36
|
def input
|
@@ -29,10 +41,6 @@ module SimpleForm
|
|
29
41
|
options
|
30
42
|
end
|
31
43
|
|
32
|
-
def input_html_options
|
33
|
-
html_options_for(:input, input_html_classes)
|
34
|
-
end
|
35
|
-
|
36
44
|
def input_html_classes
|
37
45
|
[input_type, required_class]
|
38
46
|
end
|
@@ -41,7 +49,8 @@ module SimpleForm
|
|
41
49
|
content = "".html_safe
|
42
50
|
components_list.each do |component|
|
43
51
|
next if options[component] == false
|
44
|
-
|
52
|
+
rendered = send(component)
|
53
|
+
content.safe_concat rendered.to_s if rendered
|
45
54
|
end
|
46
55
|
wrap(content)
|
47
56
|
end
|
@@ -49,19 +58,31 @@ module SimpleForm
|
|
49
58
|
protected
|
50
59
|
|
51
60
|
def components_list
|
52
|
-
SimpleForm.components
|
61
|
+
options[:components] || SimpleForm.components
|
53
62
|
end
|
54
63
|
|
55
64
|
def attribute_required?
|
56
|
-
if options.
|
65
|
+
if !options[:required].nil?
|
57
66
|
options[:required]
|
58
|
-
elsif
|
59
|
-
|
67
|
+
elsif has_validators?
|
68
|
+
(attribute_validators + reflection_validators).any? { |v| v.kind == :presence }
|
60
69
|
else
|
61
70
|
attribute_required_by_default?
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
74
|
+
def has_validators?
|
75
|
+
object.class.respond_to?(:validators_on)
|
76
|
+
end
|
77
|
+
|
78
|
+
def attribute_validators
|
79
|
+
object.class.validators_on(attribute_name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def reflection_validators
|
83
|
+
reflection ? object.class.validators_on(reflection.name) : []
|
84
|
+
end
|
85
|
+
|
65
86
|
def attribute_required_by_default?
|
66
87
|
SimpleForm.required_by_default
|
67
88
|
end
|
@@ -82,6 +103,10 @@ module SimpleForm
|
|
82
103
|
html_options
|
83
104
|
end
|
84
105
|
|
106
|
+
def disabled?
|
107
|
+
options[:disabled]
|
108
|
+
end
|
109
|
+
|
85
110
|
# Lookup translations for the given namespace using I18n, based on object name,
|
86
111
|
# actual action and attribute name. Lookup priority as follows:
|
87
112
|
#
|
@@ -107,19 +132,22 @@ module SimpleForm
|
|
107
132
|
#
|
108
133
|
# Take a look at our locale example file.
|
109
134
|
def translate(namespace, default='')
|
135
|
+
return nil unless SimpleForm.translate
|
110
136
|
lookups = []
|
111
137
|
lookups << :"#{object_name}.#{lookup_action}.#{reflection_or_attribute_name}"
|
112
138
|
lookups << :"#{object_name}.#{reflection_or_attribute_name}"
|
113
139
|
lookups << :"#{reflection_or_attribute_name}"
|
114
140
|
lookups << default
|
115
|
-
I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups)
|
141
|
+
I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
|
116
142
|
end
|
117
143
|
|
118
144
|
# The action to be used in lookup.
|
119
145
|
def lookup_action
|
120
|
-
action = template.controller.action_name
|
146
|
+
action = template.controller.action_name
|
147
|
+
return unless action
|
148
|
+
action = action.to_sym
|
121
149
|
ACTIONS[action] || action
|
122
150
|
end
|
123
151
|
end
|
124
152
|
end
|
125
|
-
end
|
153
|
+
end
|