simple_form 0.5 → 1.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.
Potentially problematic release.
This version of simple_form might be problematic. Click here for more details.
- data/README.rdoc +339 -6
- data/generators/simple_form_install/USAGE +3 -0
- data/generators/simple_form_install/simple_form_install_generator.rb +19 -0
- data/generators/simple_form_install/templates/simple_form.rb +38 -0
- data/init.rb +1 -0
- data/lib/simple_form.rb +57 -1
- data/lib/simple_form/action_view_extensions/builder.rb +122 -0
- data/lib/simple_form/action_view_extensions/form_helper.rb +33 -0
- data/lib/simple_form/action_view_extensions/instance_tag.rb +37 -0
- data/lib/simple_form/components.rb +8 -0
- data/lib/simple_form/components/errors.rb +35 -0
- data/lib/simple_form/components/hints.rb +21 -0
- data/lib/simple_form/components/labels.rb +68 -0
- data/lib/simple_form/components/wrapper.rb +21 -0
- data/lib/simple_form/form_builder.rb +332 -0
- data/lib/simple_form/i18n_cache.rb +22 -0
- data/lib/simple_form/inputs.rb +12 -0
- data/lib/simple_form/inputs/base.rb +107 -0
- data/lib/simple_form/inputs/block_input.rb +13 -0
- data/lib/simple_form/inputs/collection_input.rb +58 -0
- data/lib/simple_form/inputs/date_time_input.rb +18 -0
- data/lib/simple_form/inputs/hidden_input.rb +11 -0
- data/lib/simple_form/inputs/mapping_input.rb +23 -0
- data/lib/simple_form/inputs/priority_input.rb +20 -0
- data/lib/simple_form/inputs/text_field_input.rb +16 -0
- data/lib/simple_form/locale/en.yml +14 -0
- data/lib/simple_form/map_type.rb +13 -0
- data/lib/simple_form/version.rb +3 -0
- data/test/action_view_extensions/builder_test.rb +172 -0
- data/test/action_view_extensions/form_helper_test.rb +50 -0
- data/test/components/error_test.rb +45 -0
- data/test/components/hint_test.rb +78 -0
- data/test/components/label_test.rb +170 -0
- data/test/form_builder_test.rb +550 -0
- data/test/inputs_test.rb +337 -0
- data/test/simple_form_test.rb +9 -0
- data/test/support/country_select/init.rb +1 -0
- data/test/support/country_select/install.rb +2 -0
- data/test/support/country_select/lib/country_select.rb +84 -0
- data/test/support/country_select/uninstall.rb +1 -0
- data/test/support/misc_helpers.rb +29 -0
- data/test/support/mock_controller.rb +11 -0
- data/test/support/mock_response.rb +14 -0
- data/test/support/models.rb +100 -0
- data/test/test_helper.rb +60 -0
- metadata +50 -10
- data/CHANGELOG +0 -27
- data/Rakefile +0 -17
@@ -0,0 +1,122 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module ActionViewExtensions
|
3
|
+
# A collection of methods required by simple_form but added to rails default form.
|
4
|
+
# This means that you can use such methods outside simple_form context.
|
5
|
+
module Builder
|
6
|
+
|
7
|
+
# Create a collection of radio inputs for the attribute. Basically this
|
8
|
+
# helper will create a radio input associated with a label for each
|
9
|
+
# text/value option in the collection, using value_method and text_method
|
10
|
+
# to convert these text/value. Based on collection_select.
|
11
|
+
#
|
12
|
+
# == Examples
|
13
|
+
#
|
14
|
+
# form_for @user do |f|
|
15
|
+
# f.collection_radio :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# <input id="user_options_true" name="user[options]" type="radio" value="true" />
|
19
|
+
# <label class="collection_radio" for="user_options_true">Yes</label>
|
20
|
+
# <input id="user_options_false" name="user[options]" type="radio" value="false" />
|
21
|
+
# <label class="collection_radio" for="user_options_false">No</label>
|
22
|
+
#
|
23
|
+
# == Options
|
24
|
+
#
|
25
|
+
# Collection radio accepts some extra options:
|
26
|
+
#
|
27
|
+
# * checked => the value that should be checked initially.
|
28
|
+
#
|
29
|
+
# * disabled => the value or values that should be disabled. Accepts a single
|
30
|
+
# item or an array of items.
|
31
|
+
#
|
32
|
+
def collection_radio(attribute, collection, value_method, text_method, options={}, html_options={})
|
33
|
+
collection.inject('') do |result, item|
|
34
|
+
value = item.send value_method
|
35
|
+
text = item.send text_method
|
36
|
+
|
37
|
+
default_html_options = default_html_options_for_collection(item, value, options, html_options)
|
38
|
+
|
39
|
+
result << radio_button(attribute, value, default_html_options) <<
|
40
|
+
label("#{attribute}_#{value}", text, :class => "collection_radio")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a collection of check boxes for each item in the collection, associated
|
45
|
+
# with a clickable label. Use value_method and text_method to convert items in
|
46
|
+
# the collection for use as text/value in check boxes.
|
47
|
+
#
|
48
|
+
# == Examples
|
49
|
+
#
|
50
|
+
# form_for @user do |f|
|
51
|
+
# f.collection_check_box :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# <input name="user[options][]" type="hidden" value="" />
|
55
|
+
# <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
|
56
|
+
# <label class="collection_check_box" for="user_options_true">Yes</label>
|
57
|
+
# <input name="user[options][]" type="hidden" value="" />
|
58
|
+
# <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
|
59
|
+
# <label class="collection_check_box" for="user_options_false">No</label>
|
60
|
+
#
|
61
|
+
# == Options
|
62
|
+
#
|
63
|
+
# Collection check box accepts some extra options:
|
64
|
+
#
|
65
|
+
# * checked => the value or values that should be checked initially. Accepts
|
66
|
+
# a single item or an array of items.
|
67
|
+
#
|
68
|
+
# * disabled => the value or values that should be disabled. Accepts a single
|
69
|
+
# item or an array of items.
|
70
|
+
#
|
71
|
+
def collection_check_boxes(attribute, collection, value_method, text_method, options={}, html_options={})
|
72
|
+
collection.inject('') do |result, item|
|
73
|
+
value = item.send value_method
|
74
|
+
text = item.send text_method
|
75
|
+
|
76
|
+
default_html_options = default_html_options_for_collection(item, value, options, html_options)
|
77
|
+
default_html_options[:multiple] = true
|
78
|
+
|
79
|
+
result << check_box(attribute, default_html_options, value, '') <<
|
80
|
+
label("#{attribute}_#{value}", text, :class => "collection_check_boxes")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Wrapper for using simple form inside a default rails form.
|
85
|
+
# Example:
|
86
|
+
#
|
87
|
+
# form_for @user do |f|
|
88
|
+
# f.simple_fields_for :posts do |posts_form|
|
89
|
+
# # Here you have all simple_form methods available
|
90
|
+
# posts_form.input :title
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
def simple_fields_for(*args, &block)
|
94
|
+
options = args.extract_options!
|
95
|
+
options[:builder] = SimpleForm::FormBuilder
|
96
|
+
fields_for(*(args << options), &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Generate default options for collection helpers, such as :checked and
|
102
|
+
# :disabled.
|
103
|
+
def default_html_options_for_collection(item, value, options, html_options) #:nodoc:
|
104
|
+
returning(html_options.dup) do |default_html_options|
|
105
|
+
[:checked, :disabled].each do |option|
|
106
|
+
next unless options[option]
|
107
|
+
|
108
|
+
accept = if options[option].is_a?(Proc)
|
109
|
+
options[option].call(item)
|
110
|
+
else
|
111
|
+
Array(options[option]).include?(value)
|
112
|
+
end
|
113
|
+
|
114
|
+
default_html_options[option] = true if accept
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
ActionView::Helpers::FormBuilder.send :include, SimpleForm::ActionViewExtensions::Builder
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module ActionViewExtensions
|
3
|
+
# This modules create simple form wrappers around default form_for,
|
4
|
+
# fields_for and remote_form_for.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# simple_form_for @user do |f|
|
9
|
+
# f.input :name, :hint => 'My hint'
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
module FormHelper
|
13
|
+
[:form_for, :fields_for, :remote_form_for].each do |helper|
|
14
|
+
class_eval <<-METHOD, __FILE__, __LINE__
|
15
|
+
def simple_#{helper}(record_or_name_or_array, *args, &block)
|
16
|
+
options = args.extract_options!
|
17
|
+
options[:builder] = SimpleForm::FormBuilder
|
18
|
+
css_class = case record_or_name_or_array
|
19
|
+
when String, Symbol then record_or_name_or_array.to_s
|
20
|
+
when Array then dom_class(record_or_name_or_array.last)
|
21
|
+
else dom_class(record_or_name_or_array)
|
22
|
+
end
|
23
|
+
options[:html] ||= {}
|
24
|
+
options[:html][:class] = "simple_form \#{css_class} \#{options[:html][:class]}".strip
|
25
|
+
#{helper}(record_or_name_or_array, *(args << options), &block)
|
26
|
+
end
|
27
|
+
METHOD
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ActionView::Base.send :include, SimpleForm::ActionViewExtensions::FormHelper
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module ActionViewExtensions
|
3
|
+
module InstanceTag #:nodoc:
|
4
|
+
# Overwrite to_check_box_tag to make it available to work with :multiple => true
|
5
|
+
def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
|
6
|
+
options = options.stringify_keys
|
7
|
+
options["type"] = "checkbox"
|
8
|
+
options["value"] = checked_value
|
9
|
+
|
10
|
+
if options.has_key?("checked")
|
11
|
+
cv = options.delete "checked"
|
12
|
+
checked = cv == true || cv == "checked"
|
13
|
+
else
|
14
|
+
checked = self.class.check_box_checked?(value(object), checked_value)
|
15
|
+
end
|
16
|
+
options["checked"] = "checked" if checked
|
17
|
+
|
18
|
+
# The only part added to deal with multiple check box is this conditional.
|
19
|
+
if options["multiple"]
|
20
|
+
add_default_name_and_id_for_value(checked_value, options)
|
21
|
+
options.delete("multiple")
|
22
|
+
else
|
23
|
+
add_default_name_and_id(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
|
27
|
+
checkbox = tag("input", options)
|
28
|
+
|
29
|
+
result = hidden + checkbox
|
30
|
+
result.respond_to?(:html_safe!) ? result.html_safe! : result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ActionView::Helpers::InstanceTag.send :remove_method, :to_check_box_tag
|
37
|
+
ActionView::Helpers::InstanceTag.send :include, SimpleForm::ActionViewExtensions::InstanceTag
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Components
|
3
|
+
module Errors
|
4
|
+
def error
|
5
|
+
template.content_tag(error_tag, error_text, error_html_options) if object && errors.present?
|
6
|
+
end
|
7
|
+
|
8
|
+
def error_tag
|
9
|
+
options[:error_tag] || SimpleForm.error_tag
|
10
|
+
end
|
11
|
+
|
12
|
+
def error_text
|
13
|
+
errors.to_sentence
|
14
|
+
end
|
15
|
+
|
16
|
+
def error_html_options
|
17
|
+
html_options_for(:error, :error)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def errors
|
23
|
+
@errors ||= (errors_on_attribute + errors_on_association).compact
|
24
|
+
end
|
25
|
+
|
26
|
+
def errors_on_attribute
|
27
|
+
Array(object.errors[attribute_name])
|
28
|
+
end
|
29
|
+
|
30
|
+
def errors_on_association
|
31
|
+
reflection ? Array(object.errors[reflection.name]) : []
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Components
|
3
|
+
module Hints
|
4
|
+
def hint
|
5
|
+
template.content_tag(hint_tag, hint_text, hint_html_options) unless hint_text.blank?
|
6
|
+
end
|
7
|
+
|
8
|
+
def hint_tag
|
9
|
+
options[:hint_tag] || SimpleForm.hint_tag
|
10
|
+
end
|
11
|
+
|
12
|
+
def hint_text
|
13
|
+
@hint_text ||= options[:hint] || translate(:hints)
|
14
|
+
end
|
15
|
+
|
16
|
+
def hint_html_options
|
17
|
+
html_options_for(:hint, :hint)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Components
|
3
|
+
module Labels
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods #:nodoc:
|
9
|
+
def translate_required_html
|
10
|
+
i18n_cache :translate_required_html do
|
11
|
+
I18n.t(:"simple_form.required.html", :default =>
|
12
|
+
%[<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>]
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def translate_required_text
|
18
|
+
I18n.t(:"simple_form.required.text", :default => 'required')
|
19
|
+
end
|
20
|
+
|
21
|
+
def translate_required_mark
|
22
|
+
I18n.t(:"simple_form.required.mark", :default => '*')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def label
|
27
|
+
@builder.label(label_target, label_text, label_html_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def label_text
|
31
|
+
SimpleForm.label_text.call(raw_label_text, required_label_text)
|
32
|
+
end
|
33
|
+
|
34
|
+
def label_target
|
35
|
+
attribute_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def label_html_options
|
39
|
+
label_options = html_options_for(:label, input_type, required_class)
|
40
|
+
label_options[:for] = options[:input_html][:id] if options.key?(:input_html)
|
41
|
+
label_options
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def raw_label_text #:nodoc:
|
47
|
+
options[:label] || label_translation
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default required text when attribute is required.
|
51
|
+
def required_label_text #:nodoc:
|
52
|
+
attribute_required? ? self.class.translate_required_html.dup : ''
|
53
|
+
end
|
54
|
+
|
55
|
+
# First check human attribute name and then labels.
|
56
|
+
# TODO Remove me in Rails > 2.3.5
|
57
|
+
def label_translation #:nodoc:
|
58
|
+
default = if object.class.respond_to?(:human_attribute_name)
|
59
|
+
object.class.human_attribute_name(reflection_or_attribute_name.to_s)
|
60
|
+
else
|
61
|
+
attribute_name.to_s.humanize
|
62
|
+
end
|
63
|
+
|
64
|
+
translate(:labels, default)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Components
|
3
|
+
module Wrapper
|
4
|
+
def wrap(content)
|
5
|
+
if wrapper_tag && options[:wrapper] != false
|
6
|
+
template.content_tag(wrapper_tag, content, wrapper_html_options)
|
7
|
+
else
|
8
|
+
content
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def wrapper_tag
|
13
|
+
options[:wrapper_tag] || SimpleForm.wrapper_tag
|
14
|
+
end
|
15
|
+
|
16
|
+
def wrapper_html_options
|
17
|
+
html_options_for(:wrapper, input_type, required_class)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
attr_reader :template, :object_name, :object, :attribute_name, :column,
|
4
|
+
:reflection, :input_type, :options
|
5
|
+
|
6
|
+
extend MapType
|
7
|
+
include SimpleForm::Inputs
|
8
|
+
|
9
|
+
map_type :boolean, :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
|
10
|
+
map_type :string, :integer, :decimal, :float, :to => SimpleForm::Inputs::TextFieldInput
|
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
|
+
|
15
|
+
# Basic input helper, combines all components in the stack to generate
|
16
|
+
# input html based on options the user define and some guesses through
|
17
|
+
# database column information. By default a call to input will generate
|
18
|
+
# label + input + hint (when defined) + errors (when exists), and all can
|
19
|
+
# be configured inside a wrapper html.
|
20
|
+
#
|
21
|
+
# == Examples
|
22
|
+
#
|
23
|
+
# # Imagine @user has error "can't be blank" on name
|
24
|
+
# simple_form_for @user do |f|
|
25
|
+
# f.input :name, :hint => 'My hint'
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# This is the output html (only the input portion, not the form):
|
29
|
+
#
|
30
|
+
# <label class="string required" for="user_name">
|
31
|
+
# <abbr title="required">*</abbr> Super User Name!
|
32
|
+
# </label>
|
33
|
+
# <input class="string required" id="user_name" maxlength="100"
|
34
|
+
# name="user[name]" size="100" type="text" value="Carlos" />
|
35
|
+
# <span class="hint">My hint</span>
|
36
|
+
# <span class="error">can't be blank</span>
|
37
|
+
#
|
38
|
+
# Each database type will render a default input, based on some mappings and
|
39
|
+
# heuristic to determine which is the best option.
|
40
|
+
#
|
41
|
+
# You have some options for the input to enable/disable some functions:
|
42
|
+
#
|
43
|
+
# :as => allows you to define the input type you want, for instance you
|
44
|
+
# can use it to generate a text field for a date column.
|
45
|
+
#
|
46
|
+
# :required => defines whether this attribute is required or not. True
|
47
|
+
# by default.
|
48
|
+
#
|
49
|
+
# The fact SimpleForm is built in components allow the interface to be unified.
|
50
|
+
# So, for instance, if you need to disable :hint for a given input, you can pass
|
51
|
+
# :hint => false. The same works for :error, :label and :wrapper.
|
52
|
+
#
|
53
|
+
# Besides the html for any component can be changed. So, if you want to change
|
54
|
+
# the label html you just need to give a hash to :label_html. To configure the
|
55
|
+
# input html, supply :input_html instead and so on.
|
56
|
+
#
|
57
|
+
# == Options
|
58
|
+
#
|
59
|
+
# Some inputs, as datetime, time and select allow you to give extra options, like
|
60
|
+
# prompt and/or include blank. Such options are given in plainly:
|
61
|
+
#
|
62
|
+
# f.input :created_at, :include_blank => true
|
63
|
+
#
|
64
|
+
# == Collection
|
65
|
+
#
|
66
|
+
# When playing with collections (:radio and :select inputs), you have three extra
|
67
|
+
# options:
|
68
|
+
#
|
69
|
+
# :collection => use to determine the collection to generate the radio or select
|
70
|
+
#
|
71
|
+
# :label_method => the method to apply on the array collection to get the label
|
72
|
+
#
|
73
|
+
# :value_method => the method to apply on the array collection to get the value
|
74
|
+
#
|
75
|
+
# == Priority
|
76
|
+
#
|
77
|
+
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
|
78
|
+
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
|
79
|
+
#
|
80
|
+
def input(attribute_name, options={}, &block)
|
81
|
+
define_simple_form_attributes(attribute_name, options)
|
82
|
+
|
83
|
+
if block_given?
|
84
|
+
SimpleForm::Inputs::BlockInput.new(self, block).render
|
85
|
+
else
|
86
|
+
klass = self.class.mappings[input_type] ||
|
87
|
+
self.class.const_get(:"#{input_type.to_s.camelize}Input")
|
88
|
+
klass.new(self).render
|
89
|
+
end
|
90
|
+
end
|
91
|
+
alias :attribute :input
|
92
|
+
|
93
|
+
# Helper for dealing with association selects/radios, generating the
|
94
|
+
# collection automatically. It's just a wrapper to input, so all options
|
95
|
+
# supported in input are also supported by association. Some extra options
|
96
|
+
# can also be given:
|
97
|
+
#
|
98
|
+
# == Options
|
99
|
+
#
|
100
|
+
# * :conditions - Given as conditions when retrieving the collection
|
101
|
+
#
|
102
|
+
# * :include - Given as include when retrieving the collection
|
103
|
+
#
|
104
|
+
# * :joins - Given as joins when retrieving the collection
|
105
|
+
#
|
106
|
+
# * :order - Given as order when retrieving the collection
|
107
|
+
#
|
108
|
+
# * :scope - Given as scopes when retrieving the collection
|
109
|
+
#
|
110
|
+
# == Examples
|
111
|
+
#
|
112
|
+
# simple_form_for @user do |f|
|
113
|
+
# f.association :company # Company.all
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# f.association :company, :order => 'name'
|
117
|
+
# # Company.all(:order => 'name')
|
118
|
+
#
|
119
|
+
# f.association :company, :conditions => { :active => true }
|
120
|
+
# # Company.all(:conditions => { :active => true })
|
121
|
+
#
|
122
|
+
# f.association :company, :collection => Company.all(:order => 'name')
|
123
|
+
# # Same as using :order option, but overriding collection
|
124
|
+
#
|
125
|
+
# f.association :company, :scope => [ :public, :not_broken ]
|
126
|
+
# # Same as doing Company.public.not_broken.all
|
127
|
+
#
|
128
|
+
# == Block
|
129
|
+
#
|
130
|
+
# When a block is given, association simple behaves as a proxy to
|
131
|
+
# simple_fields_for:
|
132
|
+
#
|
133
|
+
# f.association :company do |c|
|
134
|
+
# c.input :name
|
135
|
+
# c.input :type
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# From the options above, only :collection can also be supplied.
|
139
|
+
#
|
140
|
+
def association(association, options={}, &block)
|
141
|
+
return simple_fields_for(*[association,
|
142
|
+
options.delete(:collection), options].compact, &block) if block_given?
|
143
|
+
|
144
|
+
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
|
145
|
+
|
146
|
+
options[:as] ||= :select
|
147
|
+
@reflection = find_association_reflection(association)
|
148
|
+
raise "Association #{association.inspect} not found" unless @reflection
|
149
|
+
|
150
|
+
case @reflection.macro
|
151
|
+
when :belongs_to
|
152
|
+
attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
|
153
|
+
when :has_one
|
154
|
+
raise ":has_one association are not supported by f.association"
|
155
|
+
else
|
156
|
+
attribute = :"#{@reflection.name.to_s.singularize}_ids"
|
157
|
+
|
158
|
+
if options[:as] == :select
|
159
|
+
html_options = options[:input_html] ||= {}
|
160
|
+
html_options[:size] ||= 5
|
161
|
+
html_options[:multiple] = true unless html_options.key?(:multiple)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
options[:collection] ||= begin
|
166
|
+
finders = options.slice(:conditions, :order, :include, :joins)
|
167
|
+
finders[:conditions] = @reflection.klass.merge_conditions(finders[:conditions],
|
168
|
+
@reflection.options[:conditions])
|
169
|
+
klass = Array(options[:scope]).inject(@reflection.klass) do |klass, scope|
|
170
|
+
klass.send(scope)
|
171
|
+
end
|
172
|
+
klass.all(finders)
|
173
|
+
end
|
174
|
+
|
175
|
+
returning(input(attribute, options)) { @reflection = nil }
|
176
|
+
end
|
177
|
+
|
178
|
+
# Creates a button:
|
179
|
+
#
|
180
|
+
# form_for @user do |f|
|
181
|
+
# f.button :submit
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# If the record is a new_record?, it will create a button with label "Create User",
|
185
|
+
# otherwise it will create with label "Update User". You can overwrite the label
|
186
|
+
# giving a second parameter or giving :label.
|
187
|
+
#
|
188
|
+
# f.button :submit, "Create a new user"
|
189
|
+
# f.button :submit, :label => "Create a new user"
|
190
|
+
#
|
191
|
+
# button is actually just a wrapper that adds a default text, that said, f.button
|
192
|
+
# above is just calling:
|
193
|
+
#
|
194
|
+
# submit_tag "Create a new user"
|
195
|
+
#
|
196
|
+
# All options given to button are given straight to submit_tag. That said, you can
|
197
|
+
# use :confirm normally:
|
198
|
+
#
|
199
|
+
# f.button :submit, :confirm => "Are you sure?"
|
200
|
+
#
|
201
|
+
# And if you want to use image_submit_tag, just give it as an option:
|
202
|
+
#
|
203
|
+
# f.button :image_submit, "/images/foo/bar.png"
|
204
|
+
#
|
205
|
+
# This comes with a bonus that any method added to your ApplicationController can
|
206
|
+
# be used by SimpleForm, as long as it ends with _tag. So is quite easy to customize
|
207
|
+
# your buttons.
|
208
|
+
#
|
209
|
+
def button(type, *args)
|
210
|
+
options = args.extract_options!
|
211
|
+
value = args.first || options.delete(:label)
|
212
|
+
key = @object ? (@object.new_record? ? :create : :update) : :submit
|
213
|
+
|
214
|
+
value ||= begin
|
215
|
+
model = if @object.class.respond_to?(:human_name)
|
216
|
+
@object.class.human_name
|
217
|
+
else
|
218
|
+
@object_name.to_s.humanize
|
219
|
+
end
|
220
|
+
|
221
|
+
I18n.t(:"simple_form.buttons.#{key}", :model => model, :default => "#{key.to_s.humanize} #{model}")
|
222
|
+
end
|
223
|
+
|
224
|
+
options[:class] = "#{key} #{options[:class]}".strip
|
225
|
+
@template.send(:"#{type}_tag", value, options)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Creates an error tag based on the given attribute, only when the attribute
|
229
|
+
# contains errors. All the given options are sent as :error_html.
|
230
|
+
#
|
231
|
+
# == Examples
|
232
|
+
#
|
233
|
+
# f.error :name
|
234
|
+
# f.error :name, :id => "cool_error"
|
235
|
+
#
|
236
|
+
def error(attribute_name, options={})
|
237
|
+
define_simple_form_attributes(attribute_name, :error_html => options)
|
238
|
+
SimpleForm::Inputs::Base.new(self).error
|
239
|
+
end
|
240
|
+
|
241
|
+
# Creates a hint tag for the given attribute. Accepts a symbol indicating
|
242
|
+
# an attribute for I18n lookup or a string. All the given options are sent
|
243
|
+
# as :hint_html.
|
244
|
+
#
|
245
|
+
# == Examples
|
246
|
+
#
|
247
|
+
# f.hint :name # Do I18n lookup
|
248
|
+
# f.hint :name, :id => "cool_hint"
|
249
|
+
# f.hint "Don't forget to accept this"
|
250
|
+
#
|
251
|
+
def hint(attribute_name, options={})
|
252
|
+
attribute_name, options[:hint] = nil, attribute_name if attribute_name.is_a?(String)
|
253
|
+
define_simple_form_attributes(attribute_name, :hint => options.delete(:hint), :hint_html => options)
|
254
|
+
SimpleForm::Inputs::Base.new(self).hint
|
255
|
+
end
|
256
|
+
|
257
|
+
# Creates a default label tag for the given attribute. You can give a label
|
258
|
+
# through the :label option or using i18n. All the given options are sent
|
259
|
+
# as :label_html.
|
260
|
+
#
|
261
|
+
# == Examples
|
262
|
+
#
|
263
|
+
# f.label :name # Do I18n lookup
|
264
|
+
# f.label :name, "Name" # Same behavior as Rails, do not add required tag
|
265
|
+
# f.label :name, :label => "Name" # Same as above, but adds required tag
|
266
|
+
#
|
267
|
+
# f.label :name, :required => false
|
268
|
+
# f.label :name, :id => "cool_label"
|
269
|
+
#
|
270
|
+
def label(attribute_name, *args)
|
271
|
+
return super if args.first.is_a?(String)
|
272
|
+
options = args.extract_options!
|
273
|
+
define_simple_form_attributes(attribute_name, :label => options.delete(:label),
|
274
|
+
:label_html => options, :required => options.delete(:required))
|
275
|
+
SimpleForm::Inputs::Base.new(self).label
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
# Setup default simple form attributes.
|
281
|
+
def define_simple_form_attributes(attribute_name, options) #:nodoc:
|
282
|
+
@options = options
|
283
|
+
|
284
|
+
if @attribute_name = attribute_name
|
285
|
+
@column = find_attribute_column
|
286
|
+
@input_type = default_input_type
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Attempt to guess the better input type given the defined options. By
|
291
|
+
# default alwayls fallback to the user :as option, or to a :select when a
|
292
|
+
# collection is given.
|
293
|
+
def default_input_type #:nodoc:
|
294
|
+
return @options[:as].to_sym if @options[:as]
|
295
|
+
return :select if @options[:collection]
|
296
|
+
|
297
|
+
input_type = @column.try(:type)
|
298
|
+
|
299
|
+
case input_type
|
300
|
+
when :timestamp
|
301
|
+
:datetime
|
302
|
+
when :string, nil
|
303
|
+
match = case @attribute_name.to_s
|
304
|
+
when /password/ then :password
|
305
|
+
when /time_zone/ then :time_zone
|
306
|
+
when /country/ then :country
|
307
|
+
end
|
308
|
+
|
309
|
+
match || input_type || file_method? || :string
|
310
|
+
else
|
311
|
+
input_type
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Checks if attribute is a file_method.
|
316
|
+
def file_method? #:nodoc:
|
317
|
+
file = @object.send(@attribute_name) if @object.respond_to?(@attribute_name)
|
318
|
+
:file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
319
|
+
end
|
320
|
+
|
321
|
+
# Finds the database column for the given attribute
|
322
|
+
def find_attribute_column #:nodoc:
|
323
|
+
@object.column_for_attribute(@attribute_name) if @object.respond_to?(:column_for_attribute)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Find reflection related to association
|
327
|
+
def find_association_reflection(association) #:nodoc:
|
328
|
+
@object.class.reflect_on_association(association) if @object.class.respond_to?(:reflect_on_association)
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
end
|