bootstrap_form 4.1.0 → 4.2.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.rubocop.yml +3 -2
  4. data/.travis.yml +7 -1
  5. data/CHANGELOG.md +15 -1
  6. data/CONTRIBUTING.md +11 -0
  7. data/Dangerfile +4 -4
  8. data/Gemfile +7 -2
  9. data/OLD-README.md +795 -0
  10. data/README.md +150 -93
  11. data/Rakefile +2 -4
  12. data/bootstrap_form.gemspec +2 -1
  13. data/demo/.postcssrc.yml +3 -0
  14. data/demo/app/assets/config/manifest.js +2 -0
  15. data/demo/app/assets/stylesheets/actiontext.scss +38 -0
  16. data/demo/app/assets/stylesheets/application.scss +1 -0
  17. data/demo/app/helpers/bootstrap_helper.rb +16 -10
  18. data/demo/app/javascript/channels/consumer.js +6 -0
  19. data/demo/app/javascript/channels/index.js +5 -0
  20. data/demo/app/javascript/packs/application.js +11 -0
  21. data/demo/app/models/user.rb +2 -0
  22. data/demo/app/views/active_storage/blobs/_blob.html.erb +14 -0
  23. data/demo/app/views/bootstrap/form.html.erb +2 -1
  24. data/demo/app/views/layouts/application.html.erb +3 -0
  25. data/demo/bin/webpack +15 -0
  26. data/demo/bin/webpack-dev-server +15 -0
  27. data/demo/config/application.rb +2 -3
  28. data/demo/config/environments/development.rb +3 -1
  29. data/demo/config/environments/production.rb +48 -0
  30. data/demo/config/webpack/development.js +5 -0
  31. data/demo/config/webpack/environment.js +3 -0
  32. data/demo/config/webpack/production.js +5 -0
  33. data/demo/config/webpack/test.js +5 -0
  34. data/demo/config/webpacker.yml +92 -0
  35. data/demo/db/schema.rb +63 -18
  36. data/demo/package.json +13 -1
  37. data/demo/test/fixtures/action_text/rich_texts.yml +4 -0
  38. data/demo/yarn.lock +6257 -0
  39. data/lib/bootstrap_form.rb +34 -8
  40. data/lib/bootstrap_form/action_view_extensions/form_helper.rb +71 -0
  41. data/lib/bootstrap_form/components.rb +17 -0
  42. data/lib/bootstrap_form/components/hints.rb +60 -0
  43. data/lib/bootstrap_form/components/labels.rb +56 -0
  44. data/lib/bootstrap_form/components/layout.rb +39 -0
  45. data/lib/bootstrap_form/components/validation.rb +61 -0
  46. data/lib/bootstrap_form/engine.rb +10 -0
  47. data/lib/bootstrap_form/form_builder.rb +54 -524
  48. data/lib/bootstrap_form/form_group.rb +64 -0
  49. data/lib/bootstrap_form/form_group_builder.rb +103 -0
  50. data/lib/bootstrap_form/helpers.rb +9 -0
  51. data/lib/bootstrap_form/helpers/bootstrap.rb +39 -31
  52. data/lib/bootstrap_form/inputs.rb +40 -0
  53. data/lib/bootstrap_form/inputs/base.rb +40 -0
  54. data/lib/bootstrap_form/inputs/check_box.rb +89 -0
  55. data/lib/bootstrap_form/inputs/collection_check_boxes.rb +23 -0
  56. data/lib/bootstrap_form/inputs/collection_radio_buttons.rb +21 -0
  57. data/lib/bootstrap_form/inputs/collection_select.rb +25 -0
  58. data/lib/bootstrap_form/inputs/color_field.rb +14 -0
  59. data/lib/bootstrap_form/inputs/date_field.rb +14 -0
  60. data/lib/bootstrap_form/inputs/date_select.rb +14 -0
  61. data/lib/bootstrap_form/inputs/datetime_field.rb +14 -0
  62. data/lib/bootstrap_form/inputs/datetime_local_field.rb +14 -0
  63. data/lib/bootstrap_form/inputs/datetime_select.rb +14 -0
  64. data/lib/bootstrap_form/inputs/email_field.rb +14 -0
  65. data/lib/bootstrap_form/inputs/file_field.rb +35 -0
  66. data/lib/bootstrap_form/inputs/grouped_collection_select.rb +29 -0
  67. data/lib/bootstrap_form/inputs/inputs_collection.rb +44 -0
  68. data/lib/bootstrap_form/inputs/month_field.rb +14 -0
  69. data/lib/bootstrap_form/inputs/number_field.rb +14 -0
  70. data/lib/bootstrap_form/inputs/password_field.rb +14 -0
  71. data/lib/bootstrap_form/inputs/phone_field.rb +14 -0
  72. data/lib/bootstrap_form/inputs/radio_button.rb +77 -0
  73. data/lib/bootstrap_form/inputs/range_field.rb +14 -0
  74. data/lib/bootstrap_form/inputs/rich_text_area.rb +23 -0
  75. data/lib/bootstrap_form/inputs/search_field.rb +14 -0
  76. data/lib/bootstrap_form/inputs/select.rb +22 -0
  77. data/lib/bootstrap_form/inputs/telephone_field.rb +14 -0
  78. data/lib/bootstrap_form/inputs/text_area.rb +14 -0
  79. data/lib/bootstrap_form/inputs/text_field.rb +14 -0
  80. data/lib/bootstrap_form/inputs/time_field.rb +14 -0
  81. data/lib/bootstrap_form/inputs/time_select.rb +14 -0
  82. data/lib/bootstrap_form/inputs/time_zone_select.rb +22 -0
  83. data/lib/bootstrap_form/inputs/url_field.rb +14 -0
  84. data/lib/bootstrap_form/inputs/week_field.rb +14 -0
  85. data/lib/bootstrap_form/version.rb +1 -1
  86. metadata +79 -6
  87. data/.rubocop_todo.yml +0 -104
  88. data/lib/bootstrap_form/aliasing.rb +0 -35
  89. data/lib/bootstrap_form/helper.rb +0 -52
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ module FormGroup
5
+ extend ActiveSupport::Concern
6
+
7
+ def form_group(*args, &block)
8
+ options = args.extract_options!
9
+ name = args.first
10
+
11
+ options[:class] = form_group_classes(options)
12
+
13
+ content_tag(:div, options.except(:append, :id, :label, :help, :icon,
14
+ :input_group_class, :label_col, :control_col,
15
+ :add_control_col_class, :layout, :prepend)) do
16
+ form_group_content(
17
+ generate_label(options[:id], name, options[:label], options[:label_col], options[:layout]),
18
+ generate_help(name, options[:help]), options, &block
19
+ )
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def form_group_content_tag(name, field_name, without_field_name, options, html_options)
26
+ html_class = control_specific_class(field_name)
27
+ html_class = "#{html_class} form-inline" if @layout == :horizontal && options[:skip_inline].blank?
28
+ content_tag(:div, class: html_class) do
29
+ input_with_error(name) do
30
+ send(without_field_name, name, options, html_options)
31
+ end
32
+ end
33
+ end
34
+
35
+ def form_group_content(label, help_text, options, &block)
36
+ if group_layout_horizontal?(options[:layout])
37
+ concat(label).concat(content_tag(:div, capture(&block) + help_text, class: form_group_control_class(options)))
38
+ else
39
+ concat(label)
40
+ concat(capture(&block))
41
+ concat(help_text) if help_text
42
+ end
43
+ end
44
+
45
+ def form_group_control_class(options)
46
+ classes = [options[:control_col] || control_col]
47
+ classes << options[:add_control_col_class] if options[:add_control_col_class]
48
+ classes << offset_col(options[:label_col] || @label_col) unless options[:label]
49
+ classes.flatten.compact
50
+ end
51
+
52
+ def form_group_classes(options)
53
+ classes = ["form-group", options[:class].try(:split)].flatten.compact
54
+ classes << "row" if group_layout_horizontal?(options[:layout]) && classes.exclude?("form-row")
55
+ classes << "form-inline" if field_inline_override?(options[:layout])
56
+ classes << feedback_class if options[:icon]
57
+ classes
58
+ end
59
+
60
+ def group_layout_horizontal?(layout)
61
+ get_group_layout(layout) == :horizontal
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ module FormGroupBuilder
5
+ extend ActiveSupport::Concern
6
+
7
+ private
8
+
9
+ def form_group_builder(method, options, html_options=nil)
10
+ no_wrapper = options[:wrapper] == false
11
+
12
+ options = form_group_builder_options(options, method)
13
+
14
+ form_group_options = form_group_opts(options, form_group_css_options(method, html_options.try(:symbolize_keys!), options))
15
+
16
+ options.except!(
17
+ :help, :icon, :label_col, :control_col, :add_control_col_class, :layout, :skip_label, :label, :label_class,
18
+ :hide_label, :skip_required, :label_as_placeholder, :wrapper_class, :wrapper
19
+ )
20
+
21
+ if no_wrapper
22
+ yield
23
+ else
24
+ form_group(method, form_group_options) { yield }
25
+ end
26
+ end
27
+
28
+ def form_group_builder_options(options, method)
29
+ options.symbolize_keys!
30
+ options = convert_form_tag_options(method, options) if acts_like_form_tag
31
+ unless options[:skip_label]
32
+ options[:required] = form_group_required(options) if options.key?(:skip_required)
33
+ end
34
+ options
35
+ end
36
+
37
+ def convert_form_tag_options(method, options={})
38
+ unless @options[:skip_default_ids]
39
+ options[:name] ||= method
40
+ options[:id] ||= method
41
+ end
42
+ options
43
+ end
44
+
45
+ def form_group_opts(options, css_options)
46
+ wrapper_options = options[:wrapper]
47
+ form_group_options = {
48
+ id: options[:id], help: options[:help], icon: options[:icon],
49
+ label_col: options[:label_col], control_col: options[:control_col],
50
+ add_control_col_class: options[:add_control_col_class],
51
+ layout: get_group_layout(options[:layout]), class: options[:wrapper_class]
52
+ }
53
+
54
+ form_group_options.merge!(wrapper_options) if wrapper_options.is_a?(Hash)
55
+ form_group_options[:label] = form_group_label(options, css_options) unless options[:skip_label]
56
+ form_group_options
57
+ end
58
+
59
+ def form_group_label(options, css_options)
60
+ hash = {
61
+ text: form_group_label_text(options[:label]),
62
+ class: form_group_label_class(options),
63
+ required: options[:required]
64
+ }.merge(css_options[:id].present? ? { for: css_options[:id] } : {})
65
+ hash
66
+ end
67
+
68
+ def form_group_label_text(label)
69
+ text = label[:text] if label.is_a?(Hash)
70
+ text ||= label if label.is_a?(String)
71
+ text
72
+ end
73
+
74
+ def form_group_label_class(options)
75
+ return hide_class if options[:hide_label] || options[:label_as_placeholder]
76
+
77
+ classes = options[:label][:class] if options[:label].is_a?(Hash)
78
+ classes ||= options[:label_class]
79
+ classes
80
+ end
81
+
82
+ def form_group_required(options)
83
+ return unless options.key?(:skip_required)
84
+
85
+ warn "`:skip_required` is deprecated, use `:required: false` instead"
86
+ options[:skip_required] ? false : :default
87
+ end
88
+
89
+ def form_group_css_options(method, html_options, options)
90
+ css_options = html_options || options
91
+ # Add control_class; allow it to be overridden by :control_class option
92
+ control_classes = css_options.delete(:control_class) { control_class }
93
+ css_options[:class] = [control_classes, css_options[:class]].compact.join(" ")
94
+ css_options[:class] << " is-invalid" if error?(method)
95
+ css_options[:placeholder] = form_group_placeholder(options, method) if options[:label_as_placeholder]
96
+ css_options
97
+ end
98
+
99
+ def form_group_placeholder(options, method)
100
+ form_group_label_text(options[:label]) || object.class.human_attribute_name(method)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ module Helpers
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Bootstrap
8
+ end
9
+ end
@@ -24,35 +24,34 @@ module BootstrapForm
24
24
 
25
25
  def alert_message(title, options={})
26
26
  css = options[:class] || "alert alert-danger"
27
+ return unless object.respond_to?(:errors) && object.errors.full_messages.any?
27
28
 
28
- if object.respond_to?(:errors) && object.errors.full_messages.any?
29
- content_tag :div, class: css do
30
- concat content_tag :p, title
31
- concat error_summary unless options[:error_summary] == false
32
- end
29
+ content_tag :div, class: css do
30
+ concat content_tag :p, title
31
+ concat error_summary unless options[:error_summary] == false
33
32
  end
34
33
  end
35
34
 
36
35
  def error_summary
37
- if object.errors.any?
38
- content_tag :ul, class: "rails-bootstrap-forms-error-summary" do
39
- object.errors.full_messages.each do |error|
40
- concat content_tag(:li, error)
41
- end
36
+ return unless object.errors.any?
37
+
38
+ content_tag :ul, class: "rails-bootstrap-forms-error-summary" do
39
+ object.errors.full_messages.each do |error|
40
+ concat content_tag(:li, error)
42
41
  end
43
42
  end
44
43
  end
45
44
 
46
45
  def errors_on(name, options={})
47
- if has_error?(name)
48
- hide_attribute_name = options[:hide_attribute_name] || false
49
-
50
- content_tag :div, class: "alert alert-danger" do
51
- if hide_attribute_name
52
- object.errors[name].join(", ")
53
- else
54
- object.errors.full_messages_for(name).join(", ")
55
- end
46
+ return unless error?(name)
47
+
48
+ hide_attribute_name = options[:hide_attribute_name] || false
49
+
50
+ content_tag :div, class: "alert alert-danger" do
51
+ if hide_attribute_name
52
+ object.errors[name].join(", ")
53
+ else
54
+ object.errors.full_messages_for(name).join(", ")
56
55
  end
57
56
  end
58
57
  end
@@ -63,7 +62,7 @@ module BootstrapForm
63
62
 
64
63
  static_options = options.merge(
65
64
  readonly: true,
66
- control_class: [options[:control_class], static_class].compact.join(" ")
65
+ control_class: [options[:control_class], static_class].compact
67
66
  )
68
67
 
69
68
  static_options[:value] = object.send(name) unless static_options.key?(:value)
@@ -80,14 +79,13 @@ module BootstrapForm
80
79
 
81
80
  def prepend_and_append_input(name, options, &block)
82
81
  options = options.extract!(:prepend, :append, :input_group_class)
83
- input_group_class = ["input-group", options[:input_group_class]].compact.join(" ")
84
82
 
85
- input = capture(&block) || "".html_safe
83
+ input = capture(&block) || ActiveSupport::SafeBuffer.new
86
84
 
87
- input = content_tag(:div, input_group_content(options[:prepend]), class: "input-group-prepend") + input if options[:prepend]
88
- input << content_tag(:div, input_group_content(options[:append]), class: "input-group-append") if options[:append]
89
- input << generate_error(name)
90
- input = content_tag(:div, input, class: input_group_class) unless options.empty?
85
+ input = prepend_input(options) + input + append_input(options)
86
+ input += generate_error(name)
87
+ options.present? &&
88
+ input = content_tag(:div, input, class: ["input-group", options[:input_group_class]].compact)
91
89
  input
92
90
  end
93
91
 
@@ -108,13 +106,23 @@ module BootstrapForm
108
106
 
109
107
  private
110
108
 
109
+ def append_input(options)
110
+ html = content_tag(:div, input_group_content(options[:append]), class: "input-group-append") if options[:append]
111
+ html || ActiveSupport::SafeBuffer.new
112
+ end
113
+
114
+ def prepend_input(options)
115
+ html = content_tag(:div, input_group_content(options[:prepend]), class: "input-group-prepend") if options[:prepend]
116
+ html || ActiveSupport::SafeBuffer.new
117
+ end
118
+
111
119
  def setup_css_class(the_class, options={})
112
- unless options.key? :class
113
- if (extra_class = options.delete(:extra_class))
114
- the_class = "#{the_class} #{extra_class}"
115
- end
116
- options[:class] = the_class
120
+ return if options.key? :class
121
+
122
+ if (extra_class = options.delete(:extra_class))
123
+ the_class = "#{the_class} #{extra_class}"
117
124
  end
125
+ options[:class] = the_class
118
126
  end
119
127
  end
120
128
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ module Inputs
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Base
8
+ autoload :InputsCollection
9
+ autoload :CheckBox
10
+ autoload :CollectionCheckBoxes
11
+ autoload :CollectionRadioButtons
12
+ autoload :CollectionSelect
13
+ autoload :ColorField
14
+ autoload :DateField
15
+ autoload :DateSelect
16
+ autoload :DatetimeField
17
+ autoload :DatetimeLocalField
18
+ autoload :DatetimeSelect
19
+ autoload :EmailField
20
+ autoload :FileField
21
+ autoload :GroupedCollectionSelect
22
+ autoload :MonthField
23
+ autoload :NumberField
24
+ autoload :PasswordField
25
+ autoload :PhoneField
26
+ autoload :RadioButton
27
+ autoload :RangeField
28
+ autoload :RichTextArea if Rails::VERSION::MAJOR >= 6
29
+ autoload :SearchField
30
+ autoload :Select
31
+ autoload :TelephoneField
32
+ autoload :TextArea
33
+ autoload :TextField
34
+ autoload :TimeField
35
+ autoload :TimeSelect
36
+ autoload :TimeZoneSelect
37
+ autoload :UrlField
38
+ autoload :WeekField
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ module Inputs
5
+ module Base
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def bootstrap_field(field_name)
10
+ define_method "#{field_name}_with_bootstrap" do |name, options={}|
11
+ form_group_builder(name, options) do
12
+ prepend_and_append_input(name, options) do
13
+ send("#{field_name}_without_bootstrap".to_sym, name, options)
14
+ end
15
+ end
16
+ end
17
+
18
+ bootstrap_alias field_name
19
+ end
20
+
21
+ def bootstrap_select_group(field_name)
22
+ with_field_name = "#{field_name}_with_bootstrap"
23
+ without_field_name = "#{field_name}_without_bootstrap"
24
+ define_method(with_field_name) do |name, options={}, html_options={}|
25
+ form_group_builder(name, options, html_options) do
26
+ form_group_content_tag(name, field_name, without_field_name, options, html_options)
27
+ end
28
+ end
29
+
30
+ bootstrap_alias field_name
31
+ end
32
+
33
+ def bootstrap_alias(field_name)
34
+ alias_method "#{field_name}_without_bootstrap".to_sym, field_name
35
+ alias_method field_name, "#{field_name}_with_bootstrap".to_sym
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BootstrapForm
4
+ module Inputs
5
+ module CheckBox
6
+ extend ActiveSupport::Concern
7
+ include Base
8
+
9
+ included do
10
+ def check_box_with_bootstrap(name, options={}, checked_value="1", unchecked_value="0", &block)
11
+ options = options.symbolize_keys!
12
+ check_box_options = options.except(:class, :label, :label_class, :error_message, :help,
13
+ :inline, :custom, :hide_label, :skip_label, :wrapper_class)
14
+ check_box_options[:class] = check_box_classes(name, options)
15
+
16
+ content_tag(:div, class: check_box_wrapper_class(options)) do
17
+ html = check_box_without_bootstrap(name, check_box_options, checked_value, unchecked_value)
18
+ html.concat(check_box_label(name, options, checked_value, &block)) unless options[:skip_label]
19
+ html.concat(generate_error(name)) if options[:error_message]
20
+ html
21
+ end
22
+ end
23
+
24
+ bootstrap_alias :check_box
25
+ end
26
+
27
+ private
28
+
29
+ def check_box_label(name, options, checked_value, &block)
30
+ label_name = if options[:multiple]
31
+ check_box_value(name, checked_value)
32
+ else
33
+ name
34
+ end
35
+ label_options = { class: check_box_label_class(options) }
36
+ label_options[:for] = options[:id] if options[:id].present?
37
+ label(label_name, check_box_description(name, options, &block), label_options)
38
+ end
39
+
40
+ def check_box_description(name, options, &block)
41
+ content = block_given? ? capture(&block) : options[:label]
42
+ content || (object && object.class.human_attribute_name(name)) || name.to_s.humanize
43
+ end
44
+
45
+ def check_box_value(name, value)
46
+ # label's `for` attribute needs to match checkbox tag's id,
47
+ # IE sanitized value, IE
48
+ # https://github.com/rails/rails/blob/5-0-stable/actionview/lib/action_view/helpers/tags/base.rb#L123-L125
49
+ "#{name}_#{value.to_s.gsub(/\s/, '_').gsub(/[^-[[:word:]]]/, '').mb_chars.downcase}"
50
+ end
51
+
52
+ def check_box_classes(name, options)
53
+ classes = [options[:class]]
54
+ classes << (options[:custom] ? "custom-control-input" : "form-check-input")
55
+ classes << "is-invalid" if error?(name)
56
+ classes << "position-static" if options[:skip_label] || options[:hide_label]
57
+ classes.flatten.compact
58
+ end
59
+
60
+ def check_box_label_class(options)
61
+ classes = []
62
+ classes << (options[:custom] ? "custom-control-label" : "form-check-label")
63
+ classes << options[:label_class]
64
+ classes << hide_class if options[:hide_label]
65
+ classes.flatten.compact
66
+ end
67
+
68
+ def check_box_wrapper_class(options)
69
+ classes = []
70
+ if options[:custom]
71
+ classes << custom_check_box_wrapper_class(options)
72
+ else
73
+ classes << "form-check"
74
+ classes << "form-check-inline" if layout_inline?(options[:inline])
75
+ end
76
+ classes << options[:wrapper_class] if options[:wrapper_class].present?
77
+ classes.flatten.compact
78
+ end
79
+
80
+ def custom_check_box_wrapper_class(options)
81
+ classes = []
82
+ classes << "custom-control"
83
+ classes << (options[:custom] == :switch ? "custom-switch" : "custom-checkbox")
84
+ classes << "custom-control-inline" if layout_inline?(options[:inline])
85
+ classes
86
+ end
87
+ end
88
+ end
89
+ end