simple_form 1.2.2 → 1.3.0
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.
- 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
|