form_props 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +714 -0
  3. data/lib/form_props/action_view_extensions/form_helper.rb +120 -0
  4. data/lib/form_props/form_builder.rb +223 -0
  5. data/lib/form_props/form_options_helper.rb +158 -0
  6. data/lib/form_props/inputs/base.rb +164 -0
  7. data/lib/form_props/inputs/check_box.rb +69 -0
  8. data/lib/form_props/inputs/collection_check_boxes.rb +36 -0
  9. data/lib/form_props/inputs/collection_helpers.rb +53 -0
  10. data/lib/form_props/inputs/collection_radio_buttons.rb +35 -0
  11. data/lib/form_props/inputs/collection_select.rb +30 -0
  12. data/lib/form_props/inputs/color_field.rb +27 -0
  13. data/lib/form_props/inputs/date_field.rb +17 -0
  14. data/lib/form_props/inputs/datetime_field.rb +32 -0
  15. data/lib/form_props/inputs/datetime_local_field.rb +17 -0
  16. data/lib/form_props/inputs/email_field.rb +13 -0
  17. data/lib/form_props/inputs/file_field.rb +13 -0
  18. data/lib/form_props/inputs/grouped_collection_select.rb +31 -0
  19. data/lib/form_props/inputs/hidden_field.rb +16 -0
  20. data/lib/form_props/inputs/month_field.rb +17 -0
  21. data/lib/form_props/inputs/number_field.rb +21 -0
  22. data/lib/form_props/inputs/password_field.rb +18 -0
  23. data/lib/form_props/inputs/radio_button.rb +48 -0
  24. data/lib/form_props/inputs/range_field.rb +13 -0
  25. data/lib/form_props/inputs/search_field.rb +28 -0
  26. data/lib/form_props/inputs/select.rb +42 -0
  27. data/lib/form_props/inputs/submit.rb +26 -0
  28. data/lib/form_props/inputs/tel_field.rb +13 -0
  29. data/lib/form_props/inputs/text_area.rb +37 -0
  30. data/lib/form_props/inputs/text_field.rb +28 -0
  31. data/lib/form_props/inputs/time_field.rb +17 -0
  32. data/lib/form_props/inputs/time_zone_select.rb +22 -0
  33. data/lib/form_props/inputs/url_field.rb +13 -0
  34. data/lib/form_props/inputs/week_field.rb +17 -0
  35. data/lib/form_props/inputs/weekday_select.rb +28 -0
  36. data/lib/form_props/version.rb +5 -0
  37. data/lib/form_props.rb +45 -0
  38. metadata +120 -0
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class CheckBox < Base
6
+ def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
7
+ @checked_value = checked_value
8
+ @unchecked_value = unchecked_value
9
+ super(object_name, method_name, template_object, options)
10
+ end
11
+
12
+ def input_checked?(options)
13
+ if options.has_key?(:checked)
14
+ checked = options.delete(:checked)
15
+ checked == true || checked == "checked"
16
+ else
17
+ checked?(value)
18
+ end
19
+ end
20
+
21
+ def render(flatten = false)
22
+ options = @options.stringify_keys
23
+ options[:type] = "checkbox"
24
+ options[:value] = @checked_value
25
+ options[:checked] = true if input_checked?(options)
26
+ options[:unchecked_value] = @unchecked_value || ""
27
+ options[:include_hidden] = options.fetch(:include_hidden) { true }
28
+
29
+ body_block = -> {
30
+ if options[:multiple]
31
+ add_default_name_and_id_for_value(@checked_value, options)
32
+ options.delete(:multiple)
33
+ else
34
+ add_default_name_and_id(options)
35
+ end
36
+
37
+ input_props(options)
38
+ }
39
+
40
+ if flatten
41
+ body_block.call
42
+ else
43
+ json.set!(sanitized_method_name) do
44
+ body_block.call
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def checked?(value)
52
+ case value
53
+ when TrueClass, FalseClass
54
+ value == !!@checked_value
55
+ when NilClass
56
+ false
57
+ when String
58
+ value == @checked_value
59
+ else
60
+ if value.respond_to?(:include?)
61
+ value.include?(@checked_value)
62
+ else
63
+ value.to_i == @checked_value.to_i
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/collection_helpers"
4
+
5
+ module FormProps
6
+ module Inputs
7
+ class CollectionCheckBoxes < Base
8
+ include ActionView::Helpers::Tags::CollectionHelpers
9
+ include CollectionHelpers
10
+
11
+ class CheckBoxBuilder < Builder
12
+ def render(extra_html_options = {})
13
+ html_options = extra_html_options.merge(@input_html_options)
14
+ html_options[:multiple] = true
15
+ html_options[:skip_default_ids] = false
16
+
17
+ checkbox = CheckBox.new(@object_name, @method_name, @template_object, @value, nil, html_options)
18
+ checkbox.render(true)
19
+ checkbox.json.label @text
20
+ end
21
+ end
22
+
23
+ def render
24
+ json.set!(sanitized_method_name) do
25
+ json.collection do
26
+ render_collection_for(CheckBoxBuilder)
27
+ end
28
+
29
+ json.include_hidden(@options.fetch(:include_hidden) { true })
30
+
31
+ input_props(@html_options)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ module CollectionHelpers
6
+ def render_collection
7
+ json.array! @collection do |item|
8
+ value = value_for_collection(item, @value_method)
9
+ text = value_for_collection(item, @text_method)
10
+ default_html_options = default_html_options_for_collection(item, value)
11
+ additional_html_options = option_html_attributes(item)
12
+
13
+ yield item, value, text, default_html_options.merge(additional_html_options)
14
+ end
15
+ end
16
+
17
+ def default_html_options_for_collection(item, value)
18
+ html_options = @html_options.dup
19
+
20
+ [:checked, :selected, :disabled, :read_only].each do |option|
21
+ current_value = @options[option]
22
+ next if current_value.nil?
23
+
24
+ accept = if current_value.respond_to?(:call)
25
+ current_value.call(item)
26
+ else
27
+ Array(current_value).map(&:to_s).include?(value.to_s)
28
+ end
29
+
30
+ if accept
31
+ html_options[option] = true
32
+ elsif option == :checked
33
+ html_options[option] = false
34
+ end
35
+ end
36
+
37
+ html_options[:object] = @object
38
+ html_options
39
+ end
40
+
41
+ def render_collection_for(builder_class, &block)
42
+ render_collection do |item, value, text, default_html_options|
43
+ builder = instantiate_builder(builder_class, item, value, text, default_html_options)
44
+ builder.render
45
+ end
46
+ end
47
+
48
+ def hidden_field_name
49
+ @html_options[:name] || tag_name(false, @options[:index]).to_s
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/collection_helpers"
4
+
5
+ module FormProps
6
+ module Inputs
7
+ class CollectionRadioButtons < Base
8
+ include ActionView::Helpers::Tags::CollectionHelpers
9
+ include CollectionHelpers
10
+
11
+ class RadioButtonBuilder < Builder
12
+ def render(extra_html_options = {})
13
+ html_options = extra_html_options.merge(@input_html_options)
14
+ html_options[:skip_default_ids] = false
15
+
16
+ checkbox = RadioButton.new(@object_name, @method_name, @template_object, @value, html_options)
17
+ checkbox.render(true)
18
+ checkbox.json.label @text
19
+ end
20
+ end
21
+
22
+ def render
23
+ json.set!(sanitized_method_name) do
24
+ json.collection do
25
+ render_collection_for(RadioButtonBuilder)
26
+ end
27
+
28
+ json.include_hidden(@options.fetch(:include_hidden) { true })
29
+
30
+ input_props(@html_options)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class CollectionSelect < Base # :nodoc:
6
+ include FormOptionsHelper
7
+
8
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
9
+ @collection = collection
10
+ @value_method = value_method
11
+ @text_method = text_method
12
+ @html_options = html_options
13
+
14
+ super(object_name, method_name, template_object, options)
15
+ end
16
+
17
+ def render
18
+ option_tags_options = {
19
+ selected: @options.fetch(:selected) { value },
20
+ disabled: @options[:disabled]
21
+ }
22
+
23
+ select_content_props(
24
+ options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
25
+ @options, @html_options
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class ColorField < TextField
6
+ def render
7
+ @options["value"] ||= validate_color_string(value)
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ def validate_color_string(string)
14
+ regex = /#[0-9a-fA-F]{6}/
15
+ if regex.match?(string)
16
+ string.downcase
17
+ else
18
+ "#000000"
19
+ end
20
+ end
21
+
22
+ def field_type
23
+ "color"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class DateField < DatetimeField
6
+ private
7
+
8
+ def format_date(value)
9
+ value&.strftime("%Y-%m-%d")
10
+ end
11
+
12
+ def field_type
13
+ "date"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class DatetimeField < TextField
6
+ def render
7
+ @options[:value] ||= format_date(value)
8
+ @options[:min] = format_date(datetime_value(@options["min"]))
9
+ @options[:max] = format_date(datetime_value(@options["max"]))
10
+ super
11
+ end
12
+
13
+ private
14
+
15
+ def format_date(value)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def datetime_value(value)
20
+ if value.is_a? String
21
+ begin
22
+ DateTime.parse(value)
23
+ rescue
24
+ nil
25
+ end
26
+ else
27
+ value
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class DatetimeLocalField < DatetimeField
6
+ private
7
+
8
+ def format_date(value)
9
+ value&.strftime("%Y-%m-%dT%T")
10
+ end
11
+
12
+ def field_type
13
+ "datetime-local"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class EmailField < TextField
6
+ private
7
+
8
+ def field_type
9
+ "email"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class FileField < TextField
6
+ private
7
+
8
+ def field_type
9
+ "file"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class GroupedCollectionSelect < Base
6
+ include FormOptionsHelper
7
+
8
+ def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
9
+ @collection = collection
10
+ @group_method = group_method
11
+ @group_label_method = group_label_method
12
+ @option_key_method = option_key_method
13
+ @option_value_method = option_value_method
14
+ @html_options = html_options
15
+
16
+ super(object_name, method_name, template_object, options)
17
+ end
18
+
19
+ def render
20
+ option_tags_options = {
21
+ selected: @options.fetch(:selected) { value },
22
+ disabled: @options[:disabled]
23
+ }
24
+
25
+ select_content_props(
26
+ option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class HiddenField < TextField
6
+ def render
7
+ @options[:autocomplete] = "off"
8
+ super
9
+ end
10
+
11
+ def field_type
12
+ "hidden"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class MonthField < DatetimeField
6
+ private
7
+
8
+ def format_date(value)
9
+ value&.strftime("%Y-%m")
10
+ end
11
+
12
+ def field_type
13
+ "month"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class NumberField < TextField
6
+ def render
7
+ if (range = @options.delete("in") || @options.delete("within"))
8
+ @options.update("min" => range.min, "max" => range.max)
9
+ end
10
+
11
+ super
12
+ end
13
+
14
+ private
15
+
16
+ def field_type
17
+ "number"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class PasswordField < TextField
6
+ def render
7
+ @options = {value: nil}.merge!(@options)
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ def field_type
14
+ "password"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class RadioButton < Base
6
+ def initialize(object_name, method_name, template_object, tag_value, options)
7
+ @tag_value = tag_value
8
+ super(object_name, method_name, template_object, options)
9
+ end
10
+
11
+ def input_checked?(options)
12
+ if options.has_key?(:checked)
13
+ checked = options.delete(:checked)
14
+ checked == true || checked == "checked"
15
+ else
16
+ checked?(value)
17
+ end
18
+ end
19
+
20
+ def render(flatten = false)
21
+ @options[:type] = "radio"
22
+ @options[:value] = @tag_value
23
+ @options[:checked] = true if input_checked?(@options)
24
+
25
+ name_for_key = sanitized_method_name + "_#{sanitized_value(@tag_value)}"
26
+
27
+ body_block = -> {
28
+ add_default_name_and_id_for_value(@tag_value, @options)
29
+ input_props(@options)
30
+ }
31
+
32
+ if flatten
33
+ body_block.call
34
+ else
35
+ json.set!(name_for_key) do
36
+ body_block.call
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def checked?(value)
44
+ value.to_s == @tag_value.to_s
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class RangeField < NumberField
6
+ private
7
+
8
+ def field_type
9
+ "range"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class SearchField < FormProps::Inputs::TextField
6
+ def render
7
+ if @options[:autosave]
8
+ if @options[:autosave] == true
9
+ @options[:autosave] = request.host.split(".").reverse.join(".")
10
+ end
11
+ @options[:results] ||= 10
12
+ end
13
+
14
+ if @options[:onsearch]
15
+ @options[:incremental] = true unless @options.has_key?(:incremental)
16
+ end
17
+
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def field_type
24
+ "search"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class Select < Base
6
+ include FormOptionsHelper
7
+
8
+ def initialize(object_name, method_name, template_object, choices, options, html_options)
9
+ @choices = choices
10
+ @choices = @choices.to_a if @choices.is_a?(Range)
11
+
12
+ @html_options = html_options
13
+ super(object_name, method_name, template_object, options)
14
+ end
15
+
16
+ def render
17
+ option_tags_options = {
18
+ selected: @options.fetch(:selected) { value.nil? ? "" : value },
19
+ disabled: @options[:disabled]
20
+ }
21
+
22
+ option_tags = if grouped_choices?
23
+ grouped_options_for_select(@choices, option_tags_options)
24
+ else
25
+ options_for_select(@choices, option_tags_options)
26
+ end
27
+
28
+ select_content_props(option_tags, @options, @html_options)
29
+ end
30
+
31
+ private
32
+
33
+ # Grouped choices look like this:
34
+ #
35
+ # [nil, []]
36
+ # { nil => [] }
37
+ def grouped_choices?
38
+ !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class Submit < Base
6
+ def initialize(template_object, options)
7
+ @template_object = template_object
8
+ @options = options.with_indifferent_access
9
+ end
10
+
11
+ def render
12
+ @options[:type] = field_type
13
+
14
+ json.set!(:submit) do
15
+ input_props(@options)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def field_type
22
+ "submit"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FormProps
4
+ module Inputs
5
+ class TelField < TextField
6
+ private
7
+
8
+ def field_type
9
+ "tel"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/placeholderable"
4
+
5
+ module FormProps
6
+ module Inputs
7
+ class TextArea < Base
8
+ include ActionView::Helpers::Tags::Placeholderable
9
+
10
+ def render
11
+ json.set!(sanitized_method_name) do
12
+ add_default_name_and_id(@options)
13
+ @options[:type] ||= field_type
14
+ @options[:value] = @options.fetch(:value) { value_before_type_cast }
15
+
16
+ if (size = @options.delete(:size))
17
+ @options[:cols], @options[:rows] = size.split("x") if size.respond_to?(:split)
18
+ end
19
+
20
+ input_props(@options)
21
+ end
22
+ end
23
+
24
+ class << self
25
+ def field_type
26
+ @field_type ||= name.split("::").last.sub("Field", "").downcase
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def field_type
33
+ self.class.field_type
34
+ end
35
+ end
36
+ end
37
+ end