simple_form_with_client_validation 0.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.
Files changed (99) hide show
  1. data/CHANGELOG.md +6 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +783 -0
  4. data/lib/generators/simple_form_with_client_validation/USAGE +3 -0
  5. data/lib/generators/simple_form_with_client_validation/install_generator.rb +32 -0
  6. data/lib/generators/simple_form_with_client_validation/templates/README +12 -0
  7. data/lib/generators/simple_form_with_client_validation/templates/_form.html.erb +13 -0
  8. data/lib/generators/simple_form_with_client_validation/templates/_form.html.haml +10 -0
  9. data/lib/generators/simple_form_with_client_validation/templates/_form.html.slim +10 -0
  10. data/lib/generators/simple_form_with_client_validation/templates/config/initializers/simple_form.rb.tt +179 -0
  11. data/lib/generators/simple_form_with_client_validation/templates/config/locales/simple_form.en.yml +26 -0
  12. data/lib/simple_form_with_client_validation/action_view_extensions/builder.rb +331 -0
  13. data/lib/simple_form_with_client_validation/action_view_extensions/form_helper.rb +74 -0
  14. data/lib/simple_form_with_client_validation/components/errors.rb +35 -0
  15. data/lib/simple_form_with_client_validation/components/hints.rb +18 -0
  16. data/lib/simple_form_with_client_validation/components/html5.rb +26 -0
  17. data/lib/simple_form_with_client_validation/components/label_input.rb +15 -0
  18. data/lib/simple_form_with_client_validation/components/labels.rb +79 -0
  19. data/lib/simple_form_with_client_validation/components/maxlength.rb +41 -0
  20. data/lib/simple_form_with_client_validation/components/min_max.rb +50 -0
  21. data/lib/simple_form_with_client_validation/components/minlength.rb +41 -0
  22. data/lib/simple_form_with_client_validation/components/pattern.rb +34 -0
  23. data/lib/simple_form_with_client_validation/components/placeholders.rb +16 -0
  24. data/lib/simple_form_with_client_validation/components/readonly.rb +22 -0
  25. data/lib/simple_form_with_client_validation/components.rb +21 -0
  26. data/lib/simple_form_with_client_validation/core_ext/hash.rb +16 -0
  27. data/lib/simple_form_with_client_validation/error_notification.rb +48 -0
  28. data/lib/simple_form_with_client_validation/form_builder.rb +484 -0
  29. data/lib/simple_form_with_client_validation/helpers/autofocus.rb +11 -0
  30. data/lib/simple_form_with_client_validation/helpers/disabled.rb +15 -0
  31. data/lib/simple_form_with_client_validation/helpers/readonly.rb +15 -0
  32. data/lib/simple_form_with_client_validation/helpers/required.rb +35 -0
  33. data/lib/simple_form_with_client_validation/helpers/validators.rb +44 -0
  34. data/lib/simple_form_with_client_validation/helpers.rb +12 -0
  35. data/lib/simple_form_with_client_validation/i18n_cache.rb +22 -0
  36. data/lib/simple_form_with_client_validation/inputs/base.rb +162 -0
  37. data/lib/simple_form_with_client_validation/inputs/block_input.rb +14 -0
  38. data/lib/simple_form_with_client_validation/inputs/boolean_input.rb +64 -0
  39. data/lib/simple_form_with_client_validation/inputs/collection_check_boxes_input.rb +21 -0
  40. data/lib/simple_form_with_client_validation/inputs/collection_input.rb +101 -0
  41. data/lib/simple_form_with_client_validation/inputs/collection_radio_buttons_input.rb +63 -0
  42. data/lib/simple_form_with_client_validation/inputs/collection_select_input.rb +14 -0
  43. data/lib/simple_form_with_client_validation/inputs/date_time_input.rb +28 -0
  44. data/lib/simple_form_with_client_validation/inputs/file_input.rb +9 -0
  45. data/lib/simple_form_with_client_validation/inputs/grouped_collection_select_input.rb +41 -0
  46. data/lib/simple_form_with_client_validation/inputs/hidden_input.rb +17 -0
  47. data/lib/simple_form_with_client_validation/inputs/numeric_input.rb +24 -0
  48. data/lib/simple_form_with_client_validation/inputs/password_input.rb +12 -0
  49. data/lib/simple_form_with_client_validation/inputs/priority_input.rb +24 -0
  50. data/lib/simple_form_with_client_validation/inputs/range_input.rb +14 -0
  51. data/lib/simple_form_with_client_validation/inputs/string_input.rb +23 -0
  52. data/lib/simple_form_with_client_validation/inputs/text_input.rb +11 -0
  53. data/lib/simple_form_with_client_validation/inputs.rb +21 -0
  54. data/lib/simple_form_with_client_validation/map_type.rb +16 -0
  55. data/lib/simple_form_with_client_validation/version.rb +3 -0
  56. data/lib/simple_form_with_client_validation/wrappers/builder.rb +115 -0
  57. data/lib/simple_form_with_client_validation/wrappers/many.rb +78 -0
  58. data/lib/simple_form_with_client_validation/wrappers/root.rb +34 -0
  59. data/lib/simple_form_with_client_validation/wrappers/single.rb +18 -0
  60. data/lib/simple_form_with_client_validation/wrappers.rb +8 -0
  61. data/lib/simple_form_with_client_validation.rb +218 -0
  62. data/test/action_view_extensions/builder_test.rb +577 -0
  63. data/test/action_view_extensions/form_helper_test.rb +104 -0
  64. data/test/components/label_test.rb +310 -0
  65. data/test/form_builder/association_test.rb +177 -0
  66. data/test/form_builder/button_test.rb +47 -0
  67. data/test/form_builder/error_notification_test.rb +79 -0
  68. data/test/form_builder/error_test.rb +121 -0
  69. data/test/form_builder/general_test.rb +356 -0
  70. data/test/form_builder/hint_test.rb +139 -0
  71. data/test/form_builder/input_field_test.rb +63 -0
  72. data/test/form_builder/label_test.rb +71 -0
  73. data/test/form_builder/wrapper_test.rb +149 -0
  74. data/test/generators/simple_form_generator_test.rb +32 -0
  75. data/test/inputs/boolean_input_test.rb +108 -0
  76. data/test/inputs/collection_check_boxes_input_test.rb +224 -0
  77. data/test/inputs/collection_radio_buttons_input_test.rb +326 -0
  78. data/test/inputs/collection_select_input_test.rb +241 -0
  79. data/test/inputs/datetime_input_test.rb +99 -0
  80. data/test/inputs/disabled_test.rb +38 -0
  81. data/test/inputs/discovery_test.rb +61 -0
  82. data/test/inputs/file_input_test.rb +16 -0
  83. data/test/inputs/general_test.rb +69 -0
  84. data/test/inputs/grouped_collection_select_input_test.rb +118 -0
  85. data/test/inputs/hidden_input_test.rb +30 -0
  86. data/test/inputs/numeric_input_test.rb +173 -0
  87. data/test/inputs/priority_input_test.rb +43 -0
  88. data/test/inputs/readonly_test.rb +61 -0
  89. data/test/inputs/required_test.rb +113 -0
  90. data/test/inputs/string_input_test.rb +140 -0
  91. data/test/inputs/text_input_test.rb +29 -0
  92. data/test/simple_form_test.rb +9 -0
  93. data/test/support/discovery_inputs.rb +21 -0
  94. data/test/support/misc_helpers.rb +102 -0
  95. data/test/support/mock_controller.rb +24 -0
  96. data/test/support/mock_response.rb +14 -0
  97. data/test/support/models.rb +210 -0
  98. data/test/test_helper.rb +95 -0
  99. metadata +227 -0
@@ -0,0 +1,3 @@
1
+ To copy a SimpleFormWithClientValidation initializer to your Rails App, with some configuration values, just do:
2
+
3
+ rails generate simple_form_with_client_validation:install
@@ -0,0 +1,32 @@
1
+ module SimpleFormWithClientValidation
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ desc "Copy SimpleFormWithClientValidation default files"
5
+ source_root File.expand_path('../templates', __FILE__)
6
+ class_option :template_engine, :desc => 'Template engine to be invoked (erb, haml or slim).'
7
+ class_option :bootstrap, :type => :boolean, :desc => 'Add the Twitter Bootstrap wrappers to the SimpleFormWithClientValidation initializer.'
8
+
9
+ def info_bootstrap
10
+ return if options.bootstrap?
11
+ puts "SimpleFormWithClientValidation supports Twitter bootstrap. In case you want to " \
12
+ "generate bootstrap configuration, please re-run this " \
13
+ "generator passing --bootstrap as option."
14
+ end
15
+
16
+ def copy_config
17
+ directory 'config'
18
+ end
19
+
20
+ def copy_scaffold_template
21
+ engine = options[:template_engine]
22
+ copy_file "_form.html.#{engine}", "lib/templates/#{engine}/scaffold/_form.html.#{engine}"
23
+ end
24
+
25
+ def show_readme
26
+ if behavior == :invoke && options.bootstrap?
27
+ readme "README"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ ===============================================================================
2
+
3
+ Be sure to have a copy of the Bootstrap stylesheet available on your
4
+ application, you can get it on http://twitter.github.com/bootstrap.
5
+
6
+ Inside your views, use the 'simple_form_for' with one of the Bootstrap form
7
+ classes, '.form-horizontal', '.form-inline', '.form-search' or
8
+ '.form-vertical', as the following:
9
+
10
+ = simple_form_for(@user, :html => {:class => 'form-horizontal' }) do |form|
11
+
12
+ ===============================================================================
@@ -0,0 +1,13 @@
1
+ <%%= simple_form_for(@<%= singular_table_name %>) do |f| %>
2
+ <%%= f.error_notification %>
3
+
4
+ <div class="form-inputs">
5
+ <%- attributes.each do |attribute| -%>
6
+ <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %>
7
+ <%- end -%>
8
+ </div>
9
+
10
+ <div class="form-actions">
11
+ <%%= f.button :submit %>
12
+ </div>
13
+ <%% end %>
@@ -0,0 +1,10 @@
1
+ = simple_form_for(@<%= singular_table_name %>) do |f|
2
+ = f.error_notification
3
+
4
+ .form-inputs
5
+ <%- attributes.each do |attribute| -%>
6
+ = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>
7
+ <%- end -%>
8
+
9
+ .form-actions
10
+ = f.button :submit
@@ -0,0 +1,10 @@
1
+ = simple_form_for(@<%= singular_table_name %>) do |f|
2
+ = f.error_notification
3
+
4
+ .form-inputs
5
+ <%- attributes.each do |attribute| -%>
6
+ = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>
7
+ <%- end -%>
8
+
9
+ .form-actions
10
+ = f.button :submit
@@ -0,0 +1,179 @@
1
+ # Use this setup block to configure all options available in SimpleFormWithClientValidation.
2
+ SimpleFormWithClientValidation.setup do |config|
3
+ # Wrappers are used by the form builder to generate a
4
+ # complete input. You can remove any component from the
5
+ # wrapper, change the order or even add your own to the
6
+ # stack. The options given below are used to wrap the
7
+ # whole input.
8
+ config.wrappers :default, :class => :input,
9
+ :hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
10
+ ## Extensions enabled by default
11
+ # Any of these extensions can be disabled for a
12
+ # given input by passing: `f.input EXTENSION_NAME => false`.
13
+ # You can make any of these extensions optional by
14
+ # renaming `b.use` to `b.optional`.
15
+
16
+ # Determines whether to use HTML5 (:email, :url, ...)
17
+ # and required attributes
18
+ b.use :html5
19
+
20
+ # Calculates placeholders automatically from I18n
21
+ # You can also pass a string as f.input :placeholder => "Placeholder"
22
+ b.use :placeholder
23
+
24
+ ## Optional extensions
25
+ # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup`
26
+ # to the input. If so, they will retrieve the values from the model
27
+ # if any exists. If you want to enable the lookup for any of those
28
+ # extensions by default, you can change `b.optional` to `b.use`.
29
+
30
+ # Calculates maxlength from length validations for string inputs
31
+ b.optional :maxlength
32
+
33
+ # Calculates pattern from format validations for string inputs
34
+ b.optional :pattern
35
+
36
+ # Calculates min and max from length validations for numeric inputs
37
+ b.optional :min_max
38
+
39
+ # Calculates readonly automatically from readonly attributes
40
+ b.optional :readonly
41
+
42
+ ## Inputs
43
+ b.use :label_input
44
+ b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
45
+ b.use :error, :wrap_with => { :tag => :span, :class => :error }
46
+ end
47
+ <% if options.bootstrap? %>
48
+ config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
49
+ b.use :html5
50
+ b.use :placeholder
51
+ b.use :label
52
+ b.wrapper :tag => 'div', :class => 'controls' do |ba|
53
+ ba.use :input
54
+ ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
55
+ ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
56
+ end
57
+ end
58
+
59
+ config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
60
+ b.use :html5
61
+ b.use :placeholder
62
+ b.use :label
63
+ b.wrapper :tag => 'div', :class => 'controls' do |input|
64
+ input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend|
65
+ prepend.use :input
66
+ end
67
+ input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
68
+ input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
69
+ end
70
+ end
71
+
72
+ config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
73
+ b.use :html5
74
+ b.use :placeholder
75
+ b.use :label
76
+ b.wrapper :tag => 'div', :class => 'controls' do |input|
77
+ input.wrapper :tag => 'div', :class => 'input-append' do |append|
78
+ append.use :input
79
+ end
80
+ input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
81
+ input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
82
+ end
83
+ end
84
+
85
+ # Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
86
+ # Check the Bootstrap docs (http://twitter.github.com/bootstrap)
87
+ # to learn about the different styles for forms and inputs,
88
+ # buttons and other elements.
89
+ config.default_wrapper = :bootstrap
90
+ <% else %>
91
+ # The default wrapper to be used by the FormBuilder.
92
+ config.default_wrapper = :default
93
+ <% end %>
94
+ # Define the way to render check boxes / radio buttons with labels.
95
+ # Defaults to :nested for bootstrap config.
96
+ # :inline => input + label
97
+ # :nested => label > input
98
+ config.boolean_style = :nested
99
+
100
+ # Default class for buttons
101
+ config.button_class = 'btn'
102
+
103
+ # Method used to tidy up errors.
104
+ # config.error_method = :first
105
+
106
+ # Default tag used for error notification helper.
107
+ config.error_notification_tag = :div
108
+
109
+ # CSS class to add for error notification helper.
110
+ config.error_notification_class = 'alert alert-error'
111
+
112
+ # ID to add for error notification helper.
113
+ # config.error_notification_id = nil
114
+
115
+ # Series of attempts to detect a default label method for collection.
116
+ # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
117
+
118
+ # Series of attempts to detect a default value method for collection.
119
+ # config.collection_value_methods = [ :id, :to_s ]
120
+
121
+ # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
122
+ # config.collection_wrapper_tag = nil
123
+
124
+ # You can define the class to use on all collection wrappers. Defaulting to none.
125
+ # config.collection_wrapper_class = nil
126
+
127
+ # You can wrap each item in a collection of radio/check boxes with a tag,
128
+ # defaulting to :span. Please note that when using :boolean_style = :nested,
129
+ # SimpleFormWithClientValidation will force this option to be a label.
130
+ # config.item_wrapper_tag = :span
131
+
132
+ # You can define a class to use in all item wrappers. Defaulting to none.
133
+ # config.item_wrapper_class = nil
134
+
135
+ # How the label text should be generated altogether with the required text.
136
+ # config.label_text = lambda { |label, required| "#{required} #{label}" }
137
+
138
+ # You can define the class to use on all labels. Default is nil.
139
+ config.label_class = 'control-label'
140
+
141
+ # You can define the class to use on all forms. Default is simple_form.
142
+ # config.form_class = :simple_form
143
+
144
+ # You can define which elements should obtain additional classes
145
+ # config.generate_additional_classes_for = [:wrapper, :label, :input]
146
+
147
+ # Whether attributes are required by default (or not). Default is true.
148
+ # config.required_by_default = true
149
+
150
+ # Tell browsers whether to use default HTML5 validations (novalidate option).
151
+ # Default is enabled.
152
+ config.browser_validations = false
153
+
154
+ # Collection of methods to detect if a file type was given.
155
+ # config.file_methods = [ :mounted_as, :file?, :public_filename ]
156
+
157
+ # Custom mappings for input types. This should be a hash containing a regexp
158
+ # to match as key, and the input type that will be used when the field name
159
+ # matches the regexp as value.
160
+ # config.input_mappings = { /count/ => :integer }
161
+
162
+ # Default priority for time_zone inputs.
163
+ # config.time_zone_priority = nil
164
+
165
+ # Default priority for country inputs.
166
+ # config.country_priority = nil
167
+
168
+ # Default size for text inputs.
169
+ # config.default_input_size = 50
170
+
171
+ # When false, do not use translations for labels.
172
+ # config.translate_labels = true
173
+
174
+ # Automatically discover new inputs in Rails' autoload path.
175
+ # config.inputs_discovery = true
176
+
177
+ # Cache SimpleFormWithClientValidation inputs discovery
178
+ # config.cache_discovery = !Rails.env.development?
179
+ end
@@ -0,0 +1,26 @@
1
+ en:
2
+ simple_form:
3
+ "yes": 'Yes'
4
+ "no": 'No'
5
+ required:
6
+ text: 'required'
7
+ mark: '*'
8
+ # You can uncomment the line below if you need to overwrite the whole required html.
9
+ # When using html, text and mark won't be used.
10
+ # html: '<abbr title="required">*</abbr>'
11
+ error_notification:
12
+ default_message: "Some errors were found, please take a look:"
13
+ # Labels and hints examples
14
+ # labels:
15
+ # defaults:
16
+ # password: 'Password'
17
+ # user:
18
+ # new:
19
+ # email: 'E-mail to sign in.'
20
+ # edit:
21
+ # email: 'E-mail.'
22
+ # hints:
23
+ # defaults:
24
+ # username: 'User name to sign in.'
25
+ # password: 'No special characters, please.'
26
+
@@ -0,0 +1,331 @@
1
+ module SimpleFormWithClientValidation
2
+ module ActionViewExtensions
3
+ # Base builder to handle each instance of a collection of radio buttons / check boxes.
4
+ # Based on (at this time upcoming) Rails 4 collection builders.
5
+ class BuilderBase #:nodoc:
6
+ attr_reader :object, :text, :value
7
+
8
+ def initialize(form_builder, method_name, object, sanitized_attribute_name, text,
9
+ value, input_html_options)
10
+ @form_builder = form_builder
11
+ @method_name = method_name
12
+ @object = object
13
+ @sanitized_attribute_name = sanitized_attribute_name
14
+ @text = text
15
+ @value = value
16
+ @input_html_options = input_html_options
17
+ end
18
+
19
+ def label(label_html_options={}, &block)
20
+ @form_builder.label(@sanitized_attribute_name, @text, label_html_options, &block)
21
+ end
22
+ end
23
+
24
+ # Handles generating an instance of radio + label for collection_radio_buttons.
25
+ class RadioButtonBuilder < BuilderBase #:nodoc:
26
+ def radio_button(extra_html_options={})
27
+ html_options = extra_html_options.merge(@input_html_options)
28
+ @form_builder.radio_button(@method_name, @value, html_options)
29
+ end
30
+ end
31
+
32
+ # Handles generating an instance of check box + label for collection_check_boxes.
33
+ class CheckBoxBuilder < BuilderBase #:nodoc:
34
+ def check_box(extra_html_options={})
35
+ html_options = extra_html_options.merge(@input_html_options)
36
+ @form_builder.check_box(@method_name, html_options, @value, nil)
37
+ end
38
+ end
39
+
40
+ # A collection of methods required by simple_form but added to rails default form.
41
+ # This means that you can use such methods outside simple_form context.
42
+ module Builder
43
+ # Create a collection of radio inputs for the attribute. Basically this
44
+ # helper will create a radio input associated with a label for each
45
+ # text/value option in the collection, using value_method and text_method
46
+ # to convert these text/value. You can give a symbol or a proc to both
47
+ # value_method and text_method, that will be evaluated for each item in
48
+ # the collection.
49
+ #
50
+ # == Examples
51
+ #
52
+ # form_for @user do |f|
53
+ # f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
54
+ # end
55
+ #
56
+ # <input id="user_options_true" name="user[options]" type="radio" value="true" />
57
+ # <label class="collection_radio_buttons" for="user_options_true">Yes</label>
58
+ # <input id="user_options_false" name="user[options]" type="radio" value="false" />
59
+ # <label class="collection_radio_buttons" for="user_options_false">No</label>
60
+ #
61
+ # It is also possible to give a block that should generate the radio +
62
+ # label. To wrap the radio with the label, for instance:
63
+ #
64
+ # form_for @user do |f|
65
+ # f.collection_radio_buttons(
66
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
67
+ # ) do |b|
68
+ # b.label { b.radio_button + b.text }
69
+ # end
70
+ # end
71
+ #
72
+ # == Options
73
+ #
74
+ # Collection radio accepts some extra options:
75
+ #
76
+ # * checked => the value that should be checked initially.
77
+ #
78
+ # * disabled => the value or values that should be disabled. Accepts a single
79
+ # item or an array of items.
80
+ #
81
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
82
+ #
83
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
84
+ #
85
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
86
+ #
87
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
88
+ #
89
+ # * a block => to generate the label + radio or any other component.
90
+ #
91
+ def collection_radio_buttons(attribute, collection, value_method, text_method, options={}, html_options={})
92
+ rendered_collection = render_collection(
93
+ collection, value_method, text_method, options, html_options
94
+ ) do |item, value, text, default_html_options|
95
+ builder = instantiate_builder(RadioButtonBuilder, attribute, item, value, text, default_html_options)
96
+
97
+ if block_given?
98
+ yield builder
99
+ else
100
+ builder.radio_button + builder.label(:class => "collection_radio_buttons")
101
+ end
102
+ end
103
+
104
+ wrap_rendered_collection(rendered_collection, options)
105
+ end
106
+
107
+ # Creates a collection of check boxes for each item in the collection,
108
+ # associated with a clickable label. Use value_method and text_method to
109
+ # convert items in the collection for use as text/value in check boxes.
110
+ # You can give a symbol or a proc to both value_method and text_method,
111
+ # that will be evaluated for each item in the collection.
112
+ #
113
+ # == Examples
114
+ #
115
+ # form_for @user do |f|
116
+ # f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
117
+ # end
118
+ #
119
+ # <input name="user[options][]" type="hidden" value="" />
120
+ # <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
121
+ # <label class="collection_check_boxes" for="user_options_true">Yes</label>
122
+ # <input name="user[options][]" type="hidden" value="" />
123
+ # <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
124
+ # <label class="collection_check_boxes" for="user_options_false">No</label>
125
+ #
126
+ # It is also possible to give a block that should generate the check box +
127
+ # label. To wrap the check box with the label, for instance:
128
+ #
129
+ # form_for @user do |f|
130
+ # f.collection_check_boxes(
131
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
132
+ # ) do |b|
133
+ # b.label { b.check_box + b.text }
134
+ # end
135
+ # end
136
+ #
137
+ # == Options
138
+ #
139
+ # Collection check box accepts some extra options:
140
+ #
141
+ # * checked => the value or values that should be checked initially. Accepts
142
+ # a single item or an array of items. It overrides existing associations.
143
+ #
144
+ # * disabled => the value or values that should be disabled. Accepts a single
145
+ # item or an array of items.
146
+ #
147
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
148
+ #
149
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
150
+ #
151
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
152
+ #
153
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
154
+ #
155
+ # * a block => to generate the label + check box or any other component.
156
+ #
157
+ def collection_check_boxes(attribute, collection, value_method, text_method, options={}, html_options={})
158
+ rendered_collection = render_collection(
159
+ collection, value_method, text_method, options, html_options
160
+ ) do |item, value, text, default_html_options|
161
+ default_html_options[:multiple] = true
162
+ builder = instantiate_builder(CheckBoxBuilder, attribute, item, value, text, default_html_options)
163
+
164
+ if block_given?
165
+ yield builder
166
+ else
167
+ builder.check_box + builder.label(:class => "collection_check_boxes")
168
+ end
169
+ end
170
+
171
+ # Append a hidden field to make sure something will be sent back to the
172
+ # server if all checkboxes are unchecked.
173
+ hidden = template.hidden_field_tag("#{object_name}[#{attribute}][]", "", :id => nil)
174
+
175
+ wrap_rendered_collection(rendered_collection + hidden, options)
176
+ end
177
+
178
+ # Wrapper for using SimpleFormWithClientValidation inside a default rails form.
179
+ # Example:
180
+ #
181
+ # form_for @user do |f|
182
+ # f.simple_fields_for :posts do |posts_form|
183
+ # # Here you have all simple_form methods available
184
+ # posts_form.input :title
185
+ # end
186
+ # end
187
+ def simple_fields_for(*args, &block)
188
+ options = args.extract_options!
189
+ options[:wrapper] ||= self.options[:wrapper]
190
+
191
+ if self.class < ActionView::Helpers::FormBuilder
192
+ options[:builder] ||= self.class
193
+ else
194
+ options[:builder] ||= SimpleFormWithClientValidation::FormBuilder
195
+ end
196
+ fields_for(*(args << options), &block)
197
+ end
198
+
199
+ private
200
+
201
+ def instantiate_builder(builder_class, attribute, item, value, text, html_options)
202
+ builder_class.new(self, attribute, item,
203
+ sanitize_attribute_name(attribute, value), text, value, html_options)
204
+ end
205
+
206
+ # Generate default options for collection helpers, such as :checked and
207
+ # :disabled.
208
+ def default_html_options_for_collection(item, value, options, html_options) #:nodoc:
209
+ html_options = html_options.dup
210
+
211
+ [:checked, :selected, :disabled].each do |option|
212
+ current_option = options[option]
213
+ next if current_option.nil?
214
+
215
+ accept = if current_option.respond_to?(:call)
216
+ current_option.call(item)
217
+ else
218
+ Array(current_option).include?(value)
219
+ end
220
+
221
+ if accept
222
+ html_options[option] = true
223
+ elsif option == :checked
224
+ html_options[option] = false
225
+ end
226
+ end
227
+
228
+ html_options
229
+ end
230
+
231
+ def sanitize_attribute_name(attribute, value) #:nodoc:
232
+ "#{attribute}_#{value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase}"
233
+ end
234
+
235
+ def render_collection(collection, value_method, text_method, options={}, html_options={}) #:nodoc:
236
+ item_wrapper_tag = options.fetch(:item_wrapper_tag, :span)
237
+ item_wrapper_class = options[:item_wrapper_class]
238
+
239
+ collection.map do |item|
240
+ value = value_for_collection(item, value_method)
241
+ text = value_for_collection(item, text_method)
242
+ default_html_options = default_html_options_for_collection(item, value, options, html_options)
243
+
244
+ rendered_item = yield item, value, text, default_html_options
245
+
246
+ item_wrapper_tag ? @template.content_tag(item_wrapper_tag, rendered_item, :class => item_wrapper_class) : rendered_item
247
+ end.join.html_safe
248
+ end
249
+
250
+ def value_for_collection(item, value) #:nodoc:
251
+ value.respond_to?(:call) ? value.call(item) : item.send(value)
252
+ end
253
+
254
+ def wrap_rendered_collection(collection, options)
255
+ wrapper_tag = options[:collection_wrapper_tag]
256
+
257
+ if wrapper_tag
258
+ wrapper_class = options[:collection_wrapper_class]
259
+ @template.content_tag(wrapper_tag, collection, :class => wrapper_class)
260
+ else
261
+ collection
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ module ActionView::Helpers
269
+ class FormBuilder
270
+ include SimpleFormWithClientValidation::ActionViewExtensions::Builder
271
+
272
+ # Override default Rails collection_select helper to handle lambdas/procs in
273
+ # text and value methods, so it works the same way as collection_radio_buttons
274
+ # and collection_check_boxes in SimpleFormWithClientValidation. If none of text/value methods is a
275
+ # callable object, then it just delegates back to original collection select.
276
+ #
277
+ alias :original_collection_select :collection_select
278
+ def collection_select(attribute, collection, value_method, text_method, options={}, html_options={})
279
+ if value_method.respond_to?(:call) || text_method.respond_to?(:call)
280
+ collection = collection.map do |item|
281
+ value = value_for_collection(item, value_method)
282
+ text = value_for_collection(item, text_method)
283
+
284
+ default_html_options = default_html_options_for_collection(item, value, options, html_options)
285
+ disabled = value if default_html_options[:disabled]
286
+ selected = value if default_html_options[:selected]
287
+
288
+ [value, text, selected, disabled]
289
+ end
290
+
291
+ [:disabled, :selected].each do |option|
292
+ option_value = collection.map(&:pop).compact
293
+ options[option] = option_value if option_value.present?
294
+ end
295
+ value_method, text_method = :first, :last
296
+ end
297
+
298
+ original_collection_select(attribute, collection, value_method, text_method, options, html_options)
299
+ end
300
+ end
301
+
302
+ # Backport Rails fix to checkbox tag element, which does not generate the
303
+ # hidden input when given nil as unchecked value. This is to make SimpleFormWithClientValidation
304
+ # collection check boxes helper to work fine with nested boolean style, when
305
+ # they are wrapped in labels. Without that, clicking in the label would
306
+ # actually change the hidden input, instead of the checkbox.
307
+ # FIXME: remove when support only Rails >= 3.2.2.
308
+ class InstanceTag
309
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
310
+ options = options.stringify_keys
311
+ options["type"] = "checkbox"
312
+ options["value"] = checked_value
313
+ if options.has_key?("checked")
314
+ cv = options.delete "checked"
315
+ checked = cv == true || cv == "checked"
316
+ else
317
+ checked = self.class.check_box_checked?(value(object), checked_value)
318
+ end
319
+ options["checked"] = "checked" if checked
320
+ if options["multiple"]
321
+ add_default_name_and_id_for_value(checked_value, options)
322
+ options.delete("multiple")
323
+ else
324
+ add_default_name_and_id(options)
325
+ end
326
+ hidden = unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value, "disabled" => options["disabled"]) : "".html_safe
327
+ checkbox = tag("input", options)
328
+ hidden + checkbox
329
+ end
330
+ end
331
+ end