formtastic 2.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitattributes +1 -0
- data/.github/workflows/test.yml +61 -0
- data/.gitignore +4 -2
- data/CHANGELOG.md +52 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +105 -0
- data/MIT-LICENSE +1 -1
- data/{README.textile → README.md} +204 -219
- data/RELEASE_PROCESS +3 -1
- data/Rakefile +27 -29
- data/app/assets/stylesheets/formtastic.css +3 -2
- data/bin/appraisal +8 -0
- data/formtastic.gemspec +11 -14
- data/gemfiles/rails_5.2/Gemfile +5 -0
- data/gemfiles/rails_6.0/Gemfile +5 -0
- data/gemfiles/rails_6.1/Gemfile +5 -0
- data/gemfiles/rails_edge/Gemfile +13 -0
- data/lib/formtastic/action_class_finder.rb +18 -0
- data/lib/formtastic/actions/button_action.rb +55 -60
- data/lib/formtastic/actions/input_action.rb +59 -57
- data/lib/formtastic/actions/link_action.rb +68 -67
- data/lib/formtastic/actions.rb +6 -3
- data/lib/formtastic/deprecation.rb +5 -0
- data/lib/formtastic/engine.rb +3 -1
- data/lib/formtastic/form_builder.rb +35 -16
- data/lib/formtastic/helpers/action_helper.rb +34 -28
- data/lib/formtastic/helpers/enum.rb +13 -0
- data/lib/formtastic/helpers/errors_helper.rb +2 -2
- data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -12
- data/lib/formtastic/helpers/form_helper.rb +19 -16
- data/lib/formtastic/helpers/input_helper.rb +69 -97
- data/lib/formtastic/helpers/inputs_helper.rb +35 -25
- data/lib/formtastic/helpers/reflection.rb +4 -4
- data/lib/formtastic/helpers.rb +1 -2
- data/lib/formtastic/html_attributes.rb +12 -1
- data/lib/formtastic/i18n.rb +1 -1
- data/lib/formtastic/input_class_finder.rb +18 -0
- data/lib/formtastic/inputs/base/choices.rb +2 -2
- data/lib/formtastic/inputs/base/collections.rb +46 -14
- data/lib/formtastic/inputs/base/database.rb +7 -2
- data/lib/formtastic/inputs/base/datetime_pickerish.rb +85 -0
- data/lib/formtastic/inputs/base/errors.rb +7 -7
- data/lib/formtastic/inputs/base/hints.rb +2 -2
- data/lib/formtastic/inputs/base/html.rb +10 -9
- data/lib/formtastic/inputs/base/labelling.rb +5 -8
- data/lib/formtastic/inputs/base/naming.rb +4 -4
- data/lib/formtastic/inputs/base/numeric.rb +1 -1
- data/lib/formtastic/inputs/base/options.rb +3 -4
- data/lib/formtastic/inputs/base/stringish.rb +10 -2
- data/lib/formtastic/inputs/base/timeish.rb +34 -22
- data/lib/formtastic/inputs/base/validations.rb +41 -13
- data/lib/formtastic/inputs/base/wrapping.rb +29 -26
- data/lib/formtastic/inputs/base.rb +22 -15
- data/lib/formtastic/inputs/boolean_input.rb +26 -12
- data/lib/formtastic/inputs/check_boxes_input.rb +39 -31
- data/lib/formtastic/inputs/color_input.rb +41 -0
- data/lib/formtastic/inputs/country_input.rb +24 -5
- data/lib/formtastic/inputs/datalist_input.rb +41 -0
- data/lib/formtastic/inputs/date_picker_input.rb +93 -0
- data/lib/formtastic/inputs/{date_input.rb → date_select_input.rb} +1 -1
- data/lib/formtastic/inputs/datetime_picker_input.rb +103 -0
- data/lib/formtastic/inputs/{datetime_input.rb → datetime_select_input.rb} +1 -1
- data/lib/formtastic/inputs/file_input.rb +2 -2
- data/lib/formtastic/inputs/hidden_input.rb +2 -6
- data/lib/formtastic/inputs/radio_input.rb +28 -22
- data/lib/formtastic/inputs/select_input.rb +36 -39
- data/lib/formtastic/inputs/time_picker_input.rb +99 -0
- data/lib/formtastic/inputs/{time_input.rb → time_select_input.rb} +6 -2
- data/lib/formtastic/inputs/time_zone_input.rb +16 -6
- data/lib/formtastic/inputs.rb +32 -21
- data/lib/formtastic/localized_string.rb +1 -1
- data/lib/formtastic/localizer.rb +24 -24
- data/lib/formtastic/namespaced_class_finder.rb +99 -0
- data/lib/formtastic/version.rb +1 -1
- data/lib/formtastic.rb +20 -10
- data/lib/generators/formtastic/form/form_generator.rb +10 -4
- data/lib/generators/formtastic/input/input_generator.rb +46 -0
- data/lib/generators/formtastic/install/install_generator.rb +5 -19
- data/lib/generators/templates/_form.html.slim +2 -2
- data/lib/generators/templates/formtastic.rb +46 -25
- data/lib/generators/templates/input.rb +19 -0
- data/sample/basic_inputs.html +23 -3
- data/script/integration-template.rb +74 -0
- data/script/integration.sh +19 -0
- data/spec/action_class_finder_spec.rb +12 -0
- data/spec/actions/button_action_spec.rb +8 -8
- data/spec/actions/generic_action_spec.rb +92 -56
- data/spec/actions/input_action_spec.rb +7 -7
- data/spec/actions/link_action_spec.rb +10 -10
- data/spec/builder/custom_builder_spec.rb +36 -20
- data/spec/builder/error_proc_spec.rb +4 -4
- data/spec/builder/semantic_fields_for_spec.rb +28 -29
- data/spec/fast_spec_helper.rb +12 -0
- data/spec/generators/formtastic/form/form_generator_spec.rb +45 -32
- data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
- data/spec/generators/formtastic/install/install_generator_spec.rb +9 -9
- data/spec/helpers/action_helper_spec.rb +75 -103
- data/spec/helpers/actions_helper_spec.rb +17 -17
- data/spec/helpers/form_helper_spec.rb +84 -33
- data/spec/helpers/input_helper_spec.rb +333 -285
- data/spec/helpers/inputs_helper_spec.rb +167 -121
- data/spec/helpers/reflection_helper_spec.rb +3 -3
- data/spec/helpers/semantic_errors_helper_spec.rb +23 -23
- data/spec/i18n_spec.rb +26 -26
- data/spec/input_class_finder_spec.rb +10 -0
- data/spec/inputs/base/collections_spec.rb +76 -0
- data/spec/inputs/base/validations_spec.rb +480 -0
- data/spec/inputs/boolean_input_spec.rb +100 -65
- data/spec/inputs/check_boxes_input_spec.rb +200 -101
- data/spec/inputs/color_input_spec.rb +85 -0
- data/spec/inputs/country_input_spec.rb +20 -20
- data/spec/inputs/custom_input_spec.rb +3 -4
- data/spec/inputs/datalist_input_spec.rb +61 -0
- data/spec/inputs/date_picker_input_spec.rb +449 -0
- data/spec/inputs/date_select_input_spec.rb +249 -0
- data/spec/inputs/datetime_picker_input_spec.rb +490 -0
- data/spec/inputs/datetime_select_input_spec.rb +209 -0
- data/spec/inputs/email_input_spec.rb +5 -5
- data/spec/inputs/file_input_spec.rb +6 -6
- data/spec/inputs/hidden_input_spec.rb +22 -35
- data/spec/inputs/include_blank_spec.rb +11 -11
- data/spec/inputs/label_spec.rb +62 -25
- data/spec/inputs/number_input_spec.rb +112 -112
- data/spec/inputs/password_input_spec.rb +5 -5
- data/spec/inputs/phone_input_spec.rb +5 -5
- data/spec/inputs/placeholder_spec.rb +6 -6
- data/spec/inputs/radio_input_spec.rb +99 -55
- data/spec/inputs/range_input_spec.rb +66 -66
- data/spec/inputs/readonly_spec.rb +50 -0
- data/spec/inputs/search_input_spec.rb +5 -5
- data/spec/inputs/select_input_spec.rb +170 -170
- data/spec/inputs/string_input_spec.rb +68 -16
- data/spec/inputs/text_input_spec.rb +16 -16
- data/spec/inputs/time_picker_input_spec.rb +455 -0
- data/spec/inputs/time_select_input_spec.rb +261 -0
- data/spec/inputs/time_zone_input_spec.rb +54 -28
- data/spec/inputs/url_input_spec.rb +5 -5
- data/spec/inputs/with_options_spec.rb +7 -7
- data/spec/localizer_spec.rb +39 -17
- data/spec/namespaced_class_finder_spec.rb +79 -0
- data/spec/schema.rb +21 -0
- data/spec/spec_helper.rb +254 -221
- data/spec/support/custom_macros.rb +128 -95
- data/spec/support/shared_examples.rb +12 -0
- data/spec/support/specialized_class_finder_shared_example.rb +27 -0
- data/spec/support/test_environment.rb +26 -10
- metadata +177 -238
- data/.travis.yml +0 -8
- data/Appraisals +0 -11
- data/CHANGELOG +0 -371
- data/gemfiles/rails-3.0.gemfile +0 -7
- data/gemfiles/rails-3.1.gemfile +0 -7
- data/gemfiles/rails-3.2.gemfile +0 -7
- data/lib/formtastic/helpers/buttons_helper.rb +0 -310
- data/lib/formtastic/inputs/base/grouped_collections.rb +0 -77
- data/lib/formtastic/util.rb +0 -25
- data/lib/tasks/verify_rcov.rb +0 -44
- data/spec/helpers/buttons_helper_spec.rb +0 -166
- data/spec/helpers/commit_button_helper_spec.rb +0 -530
- data/spec/inputs/date_input_spec.rb +0 -227
- data/spec/inputs/datetime_input_spec.rb +0 -185
- data/spec/inputs/time_input_spec.rb +0 -267
- data/spec/support/deferred_garbage_collection.rb +0 -21
|
@@ -47,10 +47,6 @@ module Formtastic
|
|
|
47
47
|
include Formtastic::Helpers::FieldsetWrapper
|
|
48
48
|
include Formtastic::LocalizedString
|
|
49
49
|
|
|
50
|
-
# Which columns to skip when automatically rendering a form without any fields specified.
|
|
51
|
-
SKIPPED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
|
52
|
-
|
|
53
|
-
|
|
54
50
|
# {#inputs} creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
|
|
55
51
|
# called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
|
|
56
52
|
# or with a list of fields (accepting all default arguments and options). These two examples
|
|
@@ -136,7 +132,7 @@ module Formtastic
|
|
|
136
132
|
# <% end %>
|
|
137
133
|
# <% end %>
|
|
138
134
|
#
|
|
139
|
-
# {#inputs} also provides a DSL similar to `fields_for` / `semantic_fields_for` to reduce the
|
|
135
|
+
# {#inputs} also provides a DSL similar to `fields_for` / `semantic_fields_for` to reduce the
|
|
140
136
|
# lines of code a little:
|
|
141
137
|
#
|
|
142
138
|
# <% semantic_form_for @user do |f| %>
|
|
@@ -162,7 +158,7 @@ module Formtastic
|
|
|
162
158
|
# All options except `:name`, `:title` and `:for` will be passed down to the fieldset as HTML
|
|
163
159
|
# attributes (id, class, style, etc).
|
|
164
160
|
#
|
|
165
|
-
# When nesting `inputs()` inside another `inputs()` block, the nested content will
|
|
161
|
+
# When nesting `inputs()` inside another `inputs()` block, the nested content will
|
|
166
162
|
# automatically be wrapped in an `<li>` tag to preserve the HTML validity (a `<fieldset>`
|
|
167
163
|
# cannot be a direct descendant of an `<ol>`.
|
|
168
164
|
#
|
|
@@ -182,9 +178,13 @@ module Formtastic
|
|
|
182
178
|
# <%= f.inputs %>
|
|
183
179
|
# <% end %>
|
|
184
180
|
#
|
|
181
|
+
# @example Quick form: Skip one or more fields
|
|
182
|
+
# <%= f.inputs :except => [:featured, :something_for_admin_only] %>
|
|
183
|
+
# <%= f.inputs :except => :featured %>
|
|
184
|
+
#
|
|
185
185
|
# @example Short hand: Render inputs for a named set of attributes and simple associations on the model, with all default arguments and options
|
|
186
186
|
# <% semantic_form_for @post do |form| %>
|
|
187
|
-
# <%= f.inputs
|
|
187
|
+
# <%= f.inputs :title, :body, :user, :categories %>
|
|
188
188
|
# <% end %>
|
|
189
189
|
#
|
|
190
190
|
# @example Block: Render inputs for attributes and simple associations with full control over arguments and options
|
|
@@ -215,7 +215,7 @@ module Formtastic
|
|
|
215
215
|
# <%= f.input :title ... %>
|
|
216
216
|
# <%= f.input :body ... %>
|
|
217
217
|
# <% end %>
|
|
218
|
-
# <%= f.inputs
|
|
218
|
+
# <%= f.inputs :name => 'Advanced options:' do %>
|
|
219
219
|
# <%= f.input :user ... %>
|
|
220
220
|
# <%= f.input :categories ... %>
|
|
221
221
|
# <% end %>
|
|
@@ -279,11 +279,12 @@ module Formtastic
|
|
|
279
279
|
def inputs(*args, &block)
|
|
280
280
|
wrap_it = @already_in_an_inputs_block ? true : false
|
|
281
281
|
@already_in_an_inputs_block = true
|
|
282
|
-
|
|
282
|
+
|
|
283
283
|
title = field_set_title_from_args(*args)
|
|
284
284
|
html_options = args.extract_options!
|
|
285
285
|
html_options[:class] ||= "inputs"
|
|
286
286
|
html_options[:name] = title
|
|
287
|
+
skipped_args = Array.wrap html_options.delete(:except)
|
|
287
288
|
|
|
288
289
|
out = begin
|
|
289
290
|
if html_options[:for] # Nested form
|
|
@@ -292,27 +293,27 @@ module Formtastic
|
|
|
292
293
|
field_set_and_list_wrapping(*(args << html_options), &block)
|
|
293
294
|
else
|
|
294
295
|
legend = args.shift if args.first.is_a?(::String)
|
|
295
|
-
args = default_columns_for_object if @object && args.empty?
|
|
296
|
+
args = default_columns_for_object - skipped_args if @object && args.empty?
|
|
296
297
|
contents = fieldset_contents_from_column_list(args)
|
|
297
298
|
args.unshift(legend) if legend.present?
|
|
298
299
|
field_set_and_list_wrapping(*((args << html_options) << contents))
|
|
299
300
|
end
|
|
300
301
|
end
|
|
301
|
-
|
|
302
|
+
|
|
302
303
|
out = template.content_tag(:li, out, :class => "input") if wrap_it
|
|
303
304
|
@already_in_an_inputs_block = wrap_it
|
|
304
305
|
out
|
|
305
306
|
end
|
|
306
307
|
|
|
307
308
|
protected
|
|
308
|
-
|
|
309
|
+
|
|
309
310
|
def default_columns_for_object
|
|
310
311
|
cols = association_columns(:belongs_to)
|
|
311
312
|
cols += content_columns
|
|
312
|
-
cols -=
|
|
313
|
+
cols -= skipped_columns
|
|
313
314
|
cols.compact
|
|
314
315
|
end
|
|
315
|
-
|
|
316
|
+
|
|
316
317
|
def fieldset_contents_from_column_list(columns)
|
|
317
318
|
columns.collect do |method|
|
|
318
319
|
if @object
|
|
@@ -323,22 +324,22 @@ module Formtastic
|
|
|
323
324
|
elsif @object.class.respond_to?(:associations)
|
|
324
325
|
if (@object.class.associations[method.to_sym] && @object.class.associations[method.to_sym].options[:polymorphic] == true)
|
|
325
326
|
raise PolymorphicInputWithoutCollectionError.new("Please provide a collection for :#{method} input (you'll need to use block form syntax). Inputs for polymorphic associations can only be used when an explicit :collection is provided.")
|
|
326
|
-
end
|
|
327
|
-
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
328
329
|
end
|
|
329
330
|
input(method.to_sym)
|
|
330
331
|
end
|
|
331
332
|
end
|
|
332
|
-
|
|
333
|
+
|
|
333
334
|
# Collects association columns (relation columns) for the current form object class. Skips
|
|
334
335
|
# polymorphic associations because we can't guess which class to use for an automatically
|
|
335
336
|
# generated input.
|
|
336
|
-
def association_columns(*by_associations)
|
|
337
|
+
def association_columns(*by_associations) # @private
|
|
337
338
|
if @object.present? && @object.class.respond_to?(:reflections)
|
|
338
339
|
@object.class.reflections.collect do |name, association_reflection|
|
|
339
340
|
if by_associations.present?
|
|
340
341
|
if by_associations.include?(association_reflection.macro) && association_reflection.options[:polymorphic] != true
|
|
341
|
-
name
|
|
342
|
+
name
|
|
342
343
|
end
|
|
343
344
|
else
|
|
344
345
|
name
|
|
@@ -349,12 +350,21 @@ module Formtastic
|
|
|
349
350
|
end
|
|
350
351
|
end
|
|
351
352
|
|
|
353
|
+
# Collects all foreign key columns
|
|
354
|
+
def foreign_key_columns # @private
|
|
355
|
+
if @object.present? && @object.class.respond_to?(:reflect_on_all_associations)
|
|
356
|
+
@object.class.reflect_on_all_associations(:belongs_to).map{ |reflection| reflection.foreign_key.to_sym }
|
|
357
|
+
else
|
|
358
|
+
[]
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
352
362
|
# Collects content columns (non-relation columns) for the current form object class.
|
|
353
|
-
def content_columns
|
|
363
|
+
def content_columns # @private
|
|
354
364
|
# TODO: NameError is raised by Inflector.constantize. Consider checking if it exists instead.
|
|
355
365
|
begin klass = model_name.constantize; rescue NameError; return [] end
|
|
356
366
|
return [] unless klass.respond_to?(:content_columns)
|
|
357
|
-
klass.content_columns.collect { |c| c.name.to_sym }.compact
|
|
367
|
+
klass.content_columns.collect { |c| c.name.to_sym }.compact - foreign_key_columns
|
|
358
368
|
end
|
|
359
369
|
|
|
360
370
|
# Deals with :for option when it's supplied to inputs methods. Additional
|
|
@@ -362,7 +372,7 @@ module Formtastic
|
|
|
362
372
|
# key.
|
|
363
373
|
#
|
|
364
374
|
# It should raise an error if a block with arity zero is given.
|
|
365
|
-
def inputs_for_nested_attributes(*args, &block)
|
|
375
|
+
def inputs_for_nested_attributes(*args, &block) # @private
|
|
366
376
|
options = args.extract_options!
|
|
367
377
|
args << options.merge!(:parent => { :builder => self, :for => options[:for] })
|
|
368
378
|
|
|
@@ -372,10 +382,10 @@ module Formtastic
|
|
|
372
382
|
lambda do |f|
|
|
373
383
|
contents = f.inputs(*args) do
|
|
374
384
|
if block.arity == 1 # for backwards compatibility with REE & Ruby 1.8.x
|
|
375
|
-
|
|
385
|
+
yield(f)
|
|
376
386
|
else
|
|
377
387
|
index = parent_child_index(options[:parent]) if options[:parent]
|
|
378
|
-
|
|
388
|
+
yield(f, index)
|
|
379
389
|
end
|
|
380
390
|
end
|
|
381
391
|
template.concat(contents)
|
|
@@ -391,7 +401,7 @@ module Formtastic
|
|
|
391
401
|
fields_for(*fields_for_args, &fields_for_block)
|
|
392
402
|
end
|
|
393
403
|
|
|
394
|
-
def field_set_title_from_args(*args)
|
|
404
|
+
def field_set_title_from_args(*args) # @private
|
|
395
405
|
options = args.extract_options!
|
|
396
406
|
options[:name] ||= options.delete(:title)
|
|
397
407
|
title = options[:name]
|
|
@@ -4,20 +4,20 @@ module Formtastic
|
|
|
4
4
|
module Reflection
|
|
5
5
|
# If an association method is passed in (f.input :author) try to find the
|
|
6
6
|
# reflection object.
|
|
7
|
-
def reflection_for(method)
|
|
7
|
+
def reflection_for(method) # @private
|
|
8
8
|
if @object.class.respond_to?(:reflect_on_association)
|
|
9
|
-
@object.class.reflect_on_association(method)
|
|
9
|
+
@object.class.reflect_on_association(method)
|
|
10
10
|
elsif @object.class.respond_to?(:associations) # MongoMapper uses the 'associations(method)' instead
|
|
11
11
|
@object.class.associations[method]
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def association_macro_for_method(method)
|
|
15
|
+
def association_macro_for_method(method) # @private
|
|
16
16
|
reflection = reflection_for(method)
|
|
17
17
|
reflection.macro if reflection
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def association_primary_key_for_method(method)
|
|
20
|
+
def association_primary_key_for_method(method) # @private
|
|
21
21
|
reflection = reflection_for(method)
|
|
22
22
|
if reflection
|
|
23
23
|
case association_macro_for_method(method)
|
data/lib/formtastic/helpers.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
module Formtastic
|
|
2
|
-
# @private
|
|
3
2
|
module Helpers
|
|
4
3
|
autoload :ButtonsHelper, 'formtastic/helpers/buttons_helper'
|
|
5
4
|
autoload :ActionHelper, 'formtastic/helpers/action_helper'
|
|
@@ -10,8 +9,8 @@ module Formtastic
|
|
|
10
9
|
autoload :FormHelper, 'formtastic/helpers/form_helper'
|
|
11
10
|
autoload :InputHelper, 'formtastic/helpers/input_helper'
|
|
12
11
|
autoload :InputsHelper, 'formtastic/helpers/inputs_helper'
|
|
13
|
-
autoload :LabelHelper, 'formtastic/helpers/label_helper'
|
|
14
12
|
autoload :Reflection, 'formtastic/helpers/reflection'
|
|
13
|
+
autoload :Enum, 'formtastic/helpers/enum'
|
|
15
14
|
end
|
|
16
15
|
end
|
|
17
16
|
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
module Formtastic
|
|
2
2
|
# @private
|
|
3
3
|
module HtmlAttributes
|
|
4
|
+
# Returns a namespace passed by option or inherited from parent builders / class configuration
|
|
5
|
+
def dom_id_namespace
|
|
6
|
+
namespace = options[:custom_namespace]
|
|
7
|
+
parent = options[:parent_builder]
|
|
8
|
+
|
|
9
|
+
case
|
|
10
|
+
when namespace then namespace
|
|
11
|
+
when parent && parent != self then parent.dom_id_namespace
|
|
12
|
+
else custom_namespace
|
|
13
|
+
end
|
|
14
|
+
end
|
|
4
15
|
|
|
5
16
|
protected
|
|
6
17
|
|
|
@@ -18,4 +29,4 @@ module Formtastic
|
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
end
|
|
21
|
-
end
|
|
32
|
+
end
|
data/lib/formtastic/i18n.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Formtastic
|
|
|
23
23
|
options = args.extract_options!
|
|
24
24
|
options.reverse_merge!(:default => DEFAULT_VALUES[key])
|
|
25
25
|
options[:scope] = [DEFAULT_SCOPE, options[:scope]].flatten.compact
|
|
26
|
-
::I18n.translate(key, *
|
|
26
|
+
::I18n.translate(key, *args, **options)
|
|
27
27
|
end
|
|
28
28
|
alias :t :translate
|
|
29
29
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Formtastic
|
|
2
|
+
|
|
3
|
+
# Uses the {Formtastic::NamespacedClassFinder} to look up input class names.
|
|
4
|
+
#
|
|
5
|
+
# See {Formtastic::FormBuilder#namespaced_input_class} for details.
|
|
6
|
+
#
|
|
7
|
+
class InputClassFinder < NamespacedClassFinder
|
|
8
|
+
|
|
9
|
+
# @param builder [FormBuilder]
|
|
10
|
+
def initialize(builder)
|
|
11
|
+
super builder.input_namespaces
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def class_name(as)
|
|
15
|
+
"#{super}Input"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -64,7 +64,7 @@ module Formtastic
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def custom_choice_html_options(choice)
|
|
67
|
-
(choice.is_a?(Array) && choice.size > 2) ? choice
|
|
67
|
+
(choice.is_a?(Array) && choice.size > 2) ? choice[-1] : {}
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def choice_html_safe_value(choice)
|
|
@@ -73,7 +73,7 @@ module Formtastic
|
|
|
73
73
|
|
|
74
74
|
def choice_input_dom_id(choice)
|
|
75
75
|
[
|
|
76
|
-
builder.
|
|
76
|
+
builder.dom_id_namespace,
|
|
77
77
|
sanitized_object_name,
|
|
78
78
|
builder.options[:index],
|
|
79
79
|
association_primary_key || method,
|
|
@@ -12,7 +12,7 @@ module Formtastic
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def value_method
|
|
15
|
-
@value_method ||= (value_method_from_options || label_and_value_method
|
|
15
|
+
@value_method ||= (value_method_from_options || label_and_value_method[-1])
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def value_method_from_options
|
|
@@ -24,7 +24,7 @@ module Formtastic
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def label_and_value_method_from_collection(_collection)
|
|
27
|
-
sample = _collection.first || _collection
|
|
27
|
+
sample = _collection.first || _collection[-1]
|
|
28
28
|
|
|
29
29
|
case sample
|
|
30
30
|
when Array
|
|
@@ -43,16 +43,16 @@ module Formtastic
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def raw_collection
|
|
46
|
-
@raw_collection ||= (collection_from_options || collection_from_association || collection_for_boolean)
|
|
46
|
+
@raw_collection ||= (collection_from_options || collection_from_enum || collection_from_association || collection_for_boolean)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def collection
|
|
50
50
|
# Return if we have a plain string
|
|
51
|
-
return raw_collection if raw_collection.
|
|
51
|
+
return raw_collection if raw_collection.is_a?(String)
|
|
52
52
|
|
|
53
|
-
# Return if we have an Array of strings,
|
|
53
|
+
# Return if we have an Array of strings, integers or arrays
|
|
54
54
|
return raw_collection if (raw_collection.instance_of?(Array) || raw_collection.instance_of?(Range)) &&
|
|
55
|
-
[Array,
|
|
55
|
+
([Array, String].include?(raw_collection.first.class) || raw_collection.first.is_a?(Integer)) &&
|
|
56
56
|
!(options.include?(:member_label) || options.include?(:member_value))
|
|
57
57
|
|
|
58
58
|
raw_collection.map { |o| [send_or_call(label_method, o), send_or_call(value_method, o)] }
|
|
@@ -78,21 +78,53 @@ module Formtastic
|
|
|
78
78
|
) if reflection.options[:polymorphic] == true
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
find_options_from_options = options[:find_options] || {}
|
|
82
|
-
conditions_from_options = find_options_from_options[:conditions] || {}
|
|
83
81
|
conditions_from_reflection = (reflection.respond_to?(:options) && reflection.options[:conditions]) || {}
|
|
84
82
|
conditions_from_reflection = conditions_from_reflection.call if conditions_from_reflection.is_a?(Proc)
|
|
85
83
|
|
|
86
84
|
scope_conditions = conditions_from_reflection.empty? ? nil : {:conditions => conditions_from_reflection}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
where_conditions = (scope_conditions && scope_conditions[:conditions]) || {}
|
|
86
|
+
|
|
87
|
+
reflection.klass.where(where_conditions)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Assuming the following model:
|
|
92
|
+
#
|
|
93
|
+
# class Post < ActiveRecord::Base
|
|
94
|
+
# enum :status => [ :active, :archived ]
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
# We would end up with a collection like this:
|
|
98
|
+
#
|
|
99
|
+
# [["Active", "active"], ["Archived", "archived"]
|
|
100
|
+
#
|
|
101
|
+
# The first element in each array uses String#humanize, but I18n
|
|
102
|
+
# translations are available too. Set them with the following structure.
|
|
103
|
+
#
|
|
104
|
+
# en:
|
|
105
|
+
# activerecord:
|
|
106
|
+
# attributes:
|
|
107
|
+
# post:
|
|
108
|
+
# statuses:
|
|
109
|
+
# active: Custom Active Label Here
|
|
110
|
+
# archived: Custom Archived Label Here
|
|
111
|
+
def collection_from_enum
|
|
112
|
+
if collection_from_enum?
|
|
113
|
+
method_name = method.to_s
|
|
114
|
+
|
|
115
|
+
enum_options_hash = object.defined_enums[method_name]
|
|
116
|
+
enum_options_hash.map do |name, value|
|
|
117
|
+
key = "activerecord.attributes.#{object_name}.#{method_name.pluralize}.#{name}"
|
|
118
|
+
label = ::I18n.translate(key, :default => name.humanize)
|
|
119
|
+
[label, name]
|
|
92
120
|
end
|
|
93
121
|
end
|
|
94
122
|
end
|
|
95
123
|
|
|
124
|
+
def collection_from_enum?
|
|
125
|
+
object.respond_to?(:defined_enums) && object.defined_enums.has_key?(method.to_s)
|
|
126
|
+
end
|
|
127
|
+
|
|
96
128
|
def collection_for_boolean
|
|
97
129
|
true_text = options[:true] || Formtastic::I18n.t(:yes)
|
|
98
130
|
false_text = options[:false] || Formtastic::I18n.t(:no)
|
|
@@ -113,7 +145,7 @@ module Formtastic
|
|
|
113
145
|
# Avoids an issue where `send_or_call` can be a String and duck can be something simple like
|
|
114
146
|
# `:first`, which obviously String responds to.
|
|
115
147
|
def send_or_call_or_object(duck, object)
|
|
116
|
-
return object if object.is_a?(String) # TODO what about other classes etc?
|
|
148
|
+
return object if object.is_a?(String) || object.is_a?(Integer) || object.is_a?(Symbol) # TODO what about other classes etc?
|
|
117
149
|
send_or_call(duck, object)
|
|
118
150
|
end
|
|
119
151
|
|
|
@@ -4,7 +4,12 @@ module Formtastic
|
|
|
4
4
|
module Database
|
|
5
5
|
|
|
6
6
|
def column
|
|
7
|
-
|
|
7
|
+
if object.respond_to?(:column_for_attribute)
|
|
8
|
+
# Remove deprecation wrapper & review after Rails 5.0 ships
|
|
9
|
+
ActiveSupport::Deprecation.silence do
|
|
10
|
+
object.column_for_attribute(method)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
def column?
|
|
@@ -14,4 +19,4 @@ module Formtastic
|
|
|
14
19
|
end
|
|
15
20
|
end
|
|
16
21
|
end
|
|
17
|
-
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Formtastic
|
|
2
|
+
module Inputs
|
|
3
|
+
module Base
|
|
4
|
+
module DatetimePickerish
|
|
5
|
+
include Base::Placeholder
|
|
6
|
+
|
|
7
|
+
def html_input_type
|
|
8
|
+
raise NotImplementedError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def default_size
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def value
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def input_html_options
|
|
20
|
+
super.merge(extra_input_html_options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def extra_input_html_options
|
|
24
|
+
{
|
|
25
|
+
:type => html_input_type,
|
|
26
|
+
:size => size,
|
|
27
|
+
:maxlength => maxlength,
|
|
28
|
+
:step => step,
|
|
29
|
+
:value => value
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def size
|
|
34
|
+
return options[:size] if options.key?(:size)
|
|
35
|
+
return options[:input_html][:size] if options[:input_html] && options[:input_html].key?(:size)
|
|
36
|
+
default_size
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def step
|
|
40
|
+
return step_from_macro(options[:input_html][:step]) if options[:input_html] && options[:input_html][:step] && options[:input_html][:step].is_a?(Symbol)
|
|
41
|
+
return options[:input_html][:step] if options[:input_html] && options[:input_html].key?(:step)
|
|
42
|
+
default_step
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def maxlength
|
|
46
|
+
return options[:maxlength] if options.key?(:maxlength)
|
|
47
|
+
return options[:input_html][:maxlength] if options[:input_html] && options[:input_html].key?(:maxlength)
|
|
48
|
+
default_size
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def default_maxlength
|
|
52
|
+
default_size
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def default_step
|
|
56
|
+
1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
|
|
61
|
+
def step_from_macro(sym)
|
|
62
|
+
case sym
|
|
63
|
+
|
|
64
|
+
# date
|
|
65
|
+
when :day then "1"
|
|
66
|
+
when :seven_days, :week then "7"
|
|
67
|
+
when :two_weeks, :fortnight then "14"
|
|
68
|
+
when :four_weeks then "28"
|
|
69
|
+
when :thirty_days then "30"
|
|
70
|
+
|
|
71
|
+
# time
|
|
72
|
+
when :second then "1"
|
|
73
|
+
when :minute then "60"
|
|
74
|
+
when :fifteen_minutes, :quarter_hour then "900"
|
|
75
|
+
when :thirty_minutes, :half_hour then "1800"
|
|
76
|
+
when :sixty_minutes, :hour then "3600"
|
|
77
|
+
|
|
78
|
+
else sym
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -8,22 +8,22 @@ module Formtastic
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def error_sentence_html
|
|
11
|
-
error_class =
|
|
12
|
-
template.content_tag(:p,
|
|
11
|
+
error_class = builder.default_inline_error_class
|
|
12
|
+
template.content_tag(:p, errors.to_sentence, :class => error_class)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def error_list_html
|
|
16
|
-
error_class =
|
|
16
|
+
error_class = builder.default_error_list_class
|
|
17
17
|
list_elements = []
|
|
18
18
|
errors.each do |error|
|
|
19
|
-
list_elements << template.content_tag(:li,
|
|
19
|
+
list_elements << template.content_tag(:li, error.html_safe)
|
|
20
20
|
end
|
|
21
|
-
template.content_tag(:ul,
|
|
21
|
+
template.content_tag(:ul, list_elements.join("\n").html_safe, :class => error_class)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def error_first_html
|
|
25
|
-
error_class =
|
|
26
|
-
template.content_tag(:p,
|
|
25
|
+
error_class = builder.default_inline_error_class
|
|
26
|
+
template.content_tag(:p, errors.first.untaint.html_safe, :class => error_class)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def error_none_html
|
|
@@ -2,7 +2,7 @@ module Formtastic
|
|
|
2
2
|
module Inputs
|
|
3
3
|
module Base
|
|
4
4
|
module Html
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
# Defines how the instance of an input should be rendered to a HTML string.
|
|
7
7
|
#
|
|
8
8
|
# @abstract Implement this method in your input class to describe how the input should render itself.
|
|
@@ -17,24 +17,25 @@ module Formtastic
|
|
|
17
17
|
def to_html
|
|
18
18
|
raise NotImplementedError
|
|
19
19
|
end
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def input_html_options
|
|
22
|
-
{
|
|
22
|
+
{
|
|
23
23
|
:id => dom_id,
|
|
24
24
|
:required => required_attribute?,
|
|
25
|
-
:autofocus => autofocus
|
|
25
|
+
:autofocus => autofocus?,
|
|
26
|
+
:readonly => readonly?
|
|
26
27
|
}.merge(options[:input_html] || {})
|
|
27
28
|
end
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
def dom_id
|
|
30
31
|
[
|
|
31
|
-
builder.
|
|
32
|
-
sanitized_object_name,
|
|
33
|
-
dom_index,
|
|
32
|
+
builder.dom_id_namespace,
|
|
33
|
+
sanitized_object_name,
|
|
34
|
+
dom_index,
|
|
34
35
|
association_primary_key || sanitized_method_name
|
|
35
36
|
].reject { |x| x.blank? }.join('_')
|
|
36
37
|
end
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
def dom_index
|
|
39
40
|
if builder.options.has_key?(:index)
|
|
40
41
|
builder.options[:index]
|
|
@@ -10,17 +10,14 @@ module Formtastic
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def label_html_options
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
opts[:class] << 'label'
|
|
18
|
-
|
|
19
|
-
opts
|
|
13
|
+
{
|
|
14
|
+
:for => input_html_options[:id],
|
|
15
|
+
:class => ['label'],
|
|
16
|
+
}
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
def label_text
|
|
23
|
-
((localized_label || humanized_method_name)
|
|
20
|
+
((localized_label || humanized_method_name) + requirement_text).html_safe
|
|
24
21
|
end
|
|
25
22
|
|
|
26
23
|
# TODO: why does this need to be memoized in order to make the inputs_spec tests pass?
|
|
@@ -4,9 +4,9 @@ module Formtastic
|
|
|
4
4
|
module Naming
|
|
5
5
|
|
|
6
6
|
def as
|
|
7
|
-
self.class.name.split("::").
|
|
7
|
+
self.class.name.split("::")[-1].underscore.gsub(/_input$/, '')
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
def sanitized_object_name
|
|
11
11
|
object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
|
12
12
|
end
|
|
@@ -18,7 +18,7 @@ module Formtastic
|
|
|
18
18
|
def attributized_method_name
|
|
19
19
|
method.to_s.gsub(/_id$/, '').to_sym
|
|
20
20
|
end
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
def humanized_method_name
|
|
23
23
|
if builder.label_str_method != :humanize
|
|
24
24
|
# Special case where label_str_method should trump the human_attribute_name
|
|
@@ -39,4 +39,4 @@ module Formtastic
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
|
-
end
|
|
42
|
+
end
|