bootstrap_form 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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