super 0.19.0.rc1 → 0.21.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/app/controllers/super/application_controller.rb +10 -19
  4. data/app/controllers/super/sitewide_controller.rb +33 -0
  5. data/app/controllers/super/substructure_controller.rb +63 -85
  6. data/app/controllers/super/view_controller.rb +55 -0
  7. data/app/views/super/application/_batch_checkbox.html.erb +4 -0
  8. data/app/views/super/application/_display_index.html.erb +8 -2
  9. data/app/views/super/application/_display_show.html.erb +10 -2
  10. data/app/views/super/application/_filter.html.erb +63 -59
  11. data/app/views/super/application/_form.html.erb +1 -1
  12. data/app/views/super/application/_layout.html.erb +0 -2
  13. data/app/views/super/application/_pagination.html.erb +1 -4
  14. data/app/views/super/application/_query.html.erb +13 -12
  15. data/app/views/super/application/_site_footer.html.erb +1 -1
  16. data/app/views/super/application/_sort.html.erb +18 -14
  17. data/app/views/super/application/_view_chain.html.erb +23 -4
  18. data/config/locales/en.yml +10 -1
  19. data/frontend/super-frontend/dist/application.js +6276 -6286
  20. data/frontend/super-frontend/dist/package.json +2 -2
  21. data/lib/super/action_inquirer.rb +3 -0
  22. data/lib/super/badge.rb +11 -2
  23. data/lib/super/cheat.rb +6 -1
  24. data/lib/super/display/schema_types.rb +37 -14
  25. data/lib/super/display.rb +5 -4
  26. data/lib/super/error.rb +3 -2
  27. data/lib/super/form/schema_types.rb +8 -36
  28. data/lib/super/form_builder/action_text_methods.rb +16 -0
  29. data/lib/super/form_builder/base_methods.rb +73 -0
  30. data/lib/super/form_builder/flatpickr_methods.rb +75 -0
  31. data/lib/super/form_builder/option_methods.rb +73 -0
  32. data/lib/super/form_builder.rb +143 -0
  33. data/{app/helpers → lib}/super/form_builder_helper.rb +15 -4
  34. data/lib/super/link.rb +47 -63
  35. data/lib/super/link_builder.rb +24 -43
  36. data/lib/super/navigation.rb +40 -40
  37. data/lib/super/query.rb +61 -0
  38. data/lib/super/{engine.rb → railtie.rb} +7 -1
  39. data/lib/super/reset.rb +7 -0
  40. data/lib/super/schema.rb +8 -2
  41. data/lib/super/sort.rb +1 -3
  42. data/lib/super/useful/deprecations.rb +18 -0
  43. data/lib/super/useful/i19.rb +35 -0
  44. data/lib/super/version.rb +1 -1
  45. data/lib/super/view_chain.rb +14 -4
  46. data/lib/super.rb +14 -3
  47. metadata +21 -56
  48. data/config/routes.rb +0 -4
  49. data/lib/super/form/builder.rb +0 -289
  50. data/lib/super/query/form_object.rb +0 -48
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@superadministration/super",
3
3
  "description": "The frontend code for Super, a Rails admin framework",
4
- "homepage": "https://github.com/zachahn/super",
4
+ "homepage": "https://github.com/superadministration/super",
5
5
  "license": "LGPL-3.0-only",
6
6
  "main": "application.js",
7
7
  "files": [
@@ -9,5 +9,5 @@
9
9
  "application.css",
10
10
  "package.json"
11
11
  ],
12
- "version": "0.19.0-rc1"
12
+ "version": "0.20.0"
13
13
  }
@@ -22,6 +22,9 @@ module Super
22
22
  "read" => %w[index show new edit],
23
23
  "write" => %w[create update destroy],
24
24
  "delete" => %w[destroy],
25
+
26
+ "collection" => %w[index new create],
27
+ "member" => %w[show edit update destroy],
25
28
  }
26
29
  end
27
30
 
data/lib/super/badge.rb CHANGED
@@ -12,9 +12,18 @@ module Super
12
12
  purple: "bg-purple-800 text-white",
13
13
  }
14
14
 
15
- def initialize(text, styles: nil)
15
+ def initialize(text, style: nil, styles: nil)
16
16
  @text = text
17
- @requested_styles = Array(styles.presence).flatten
17
+ if styles.present?
18
+ Super::Useful::Deprecation["0.22"].deprecation_warning("styles:", "use `style:` with a single style")
19
+ end
20
+ @requested_styles = Array(styles.presence).flatten + Array(style.presence).flatten
21
+ if @requested_styles.any? { |s| s.is_a?(String) }
22
+ Super::Useful::Deprecation["0.22"].warn("Super::Badge.new(text, style:) accepts exactly one Symbol style from this list #{STYLES.keys.inspect}")
23
+ end
24
+ if @requested_styles.size != 1
25
+ Super::Useful::Deprecation["0.22"].warn("Super::Badge.new(text, style:) accepts exactly one style, but it received #{@requested_styles.size}")
26
+ end
18
27
  end
19
28
 
20
29
  def styles
data/lib/super/cheat.rb CHANGED
@@ -3,7 +3,12 @@
3
3
  module Super
4
4
  class Cheat
5
5
  def controller
6
- paths = %w[../../app/controllers/super/substructure_controller.rb]
6
+ paths = %w[
7
+ ../../app/controllers/super/application_controller.rb
8
+ ../../app/controllers/super/substructure_controller.rb
9
+ ../../app/controllers/super/view_controller.rb
10
+ ../../app/controllers/super/sitewide_controller.rb
11
+ ]
7
12
  methods =
8
13
  paths
9
14
  .map { |f| File.read(File.expand_path(f, __dir__)) }
@@ -3,7 +3,7 @@
3
3
  module Super
4
4
  class Display
5
5
  class SchemaTypes
6
- TYPES = Useful::Enum.new(:column, :record, :none)
6
+ TYPES = Useful::Enum.new(:attribute, :record, :none)
7
7
 
8
8
  class Builder
9
9
  extend Useful::Builder
@@ -15,37 +15,47 @@ module Super
15
15
  builder def real; @real = true; end
16
16
  builder def computed; @real = false; end
17
17
 
18
- builder def column; @type = :column; end
18
+ # @deprecated Prefer {#attribute}
19
+ builder def column
20
+ Useful::Deprecation["0.22"].deprecation_warning(":column", "use :attribute instead")
21
+ @type = :attribute
22
+ end
23
+ builder def attribute; @type = :attribute; end
19
24
  builder def record; @type = :record; end
20
25
  builder def none; @type = :none; end
21
26
 
22
27
  builder def ignore_nil; @ignore_nil = true; end
23
28
 
29
+ builder def attribute_name(name); @attribute_name = name; end
30
+
24
31
  def build
25
32
  Built.new(
26
33
  real: @real,
27
34
  type: @type,
28
35
  ignore_nil: !!@ignore_nil,
36
+ attribute_name: @attribute_name,
29
37
  &@transform_block
30
38
  )
31
39
  end
32
40
  end
33
41
 
34
42
  class Built
35
- def initialize(real:, type:, ignore_nil:, &transform_block)
43
+ def initialize(real:, type:, ignore_nil:, attribute_name:, &transform_block)
36
44
  @real = real
37
45
  @type = type
38
46
  @ignore_nil = ignore_nil
47
+ @attribute_name = attribute_name
39
48
  @transform_block = transform_block
40
49
  end
41
50
 
42
51
  def real?; @real; end
43
52
  attr_reader :type
53
+ attr_reader :attribute_name
44
54
 
45
55
  def present(attribute_name, value = nil)
46
56
  if @transform_block.nil?
47
57
  if attribute_name
48
- raise Error::ArgumentError, "Transformation block is not set for column: #{attribute_name}"
58
+ raise Error::ArgumentError, "Transformation block is not set for attribute: #{attribute_name}"
49
59
  else
50
60
  raise Error::ArgumentError, "Transformation block is not set!"
51
61
  end
@@ -61,6 +71,7 @@ module Super
61
71
  end
62
72
  end
63
73
 
74
+ # @deprecated
64
75
  class Badge
65
76
  extend Useful::Builder
66
77
 
@@ -107,26 +118,36 @@ module Super
107
118
  @fields = fields
108
119
  end
109
120
 
110
- def real(type = :column, &transform_block)
121
+ def real(type = :attribute, &transform_block)
122
+ if type == :column
123
+ Useful::Deprecation["0.22"].deprecation_warning(":column", "use :attribute instead")
124
+ type = :attribute
125
+ end
126
+
111
127
  TYPES
112
128
  .case(type)
113
- .when(:column) { Builder.new.real.ignore_nil.column.transform(&transform_block) }
114
- .when(:record) { Builder.new.real.ignore_nil.record.transform(&transform_block) }
115
- .when(:none) { Builder.new.real.ignore_nil.none.transform(&transform_block) }
129
+ .when(:attribute) { Builder.new.real.ignore_nil.attribute.transform(&transform_block) }
130
+ .when(:record) { Builder.new.real.ignore_nil.record.transform(&transform_block) }
131
+ .when(:none) { Builder.new.real.ignore_nil.none.transform(&transform_block) }
116
132
  .result
117
133
  end
118
134
 
119
- def computed(type = :column, &transform_block)
135
+ def computed(type = :attribute, &transform_block)
136
+ if type == :column
137
+ Useful::Deprecation["0.22"].deprecation_warning(":column", "use :attribute instead")
138
+ type = :attribute
139
+ end
140
+
120
141
  TYPES
121
142
  .case(type)
122
- .when(:column) { Builder.new.computed.ignore_nil.column.transform(&transform_block) }
123
- .when(:record) { Builder.new.computed.ignore_nil.record.transform(&transform_block) }
124
- .when(:none) { Builder.new.computed.ignore_nil.none.transform(&transform_block) }
143
+ .when(:attribute) { Builder.new.computed.ignore_nil.attribute.transform(&transform_block) }
144
+ .when(:record) { Builder.new.computed.ignore_nil.record.transform(&transform_block) }
145
+ .when(:none) { Builder.new.computed.ignore_nil.none.transform(&transform_block) }
125
146
  .result
126
147
  end
127
148
 
128
149
  def manual(&transform_block)
129
- real(:column, &transform_block)
150
+ real(:attribute, &transform_block)
130
151
  end
131
152
 
132
153
  def batch
@@ -146,8 +167,10 @@ module Super
146
167
  end
147
168
  end
148
169
 
170
+ # @deprecated Use {#real} or {#computed} instead, and return an instance of {Super::Badge}
149
171
  def badge(*builder_methods)
150
- builder_methods = %i[real ignore_nil column] if builder_methods.empty?
172
+ Useful::Deprecation["0.22"].deprecation_warning("#badge", "use #real or #computed instead, and return an instance of Super::Badge")
173
+ builder_methods = %i[real ignore_nil attribute] if builder_methods.empty?
151
174
  builder = builder_methods.each_with_object(Builder.new) do |builder_method, builder|
152
175
  builder.public_send(builder_method)
153
176
  end
data/lib/super/display.rb CHANGED
@@ -26,7 +26,9 @@ module Super
26
26
  include Schema::Common
27
27
 
28
28
  def initialize
29
- @fields = Super::Schema::Fields.new
29
+ @fields = Super::Schema::Fields.new(
30
+ transform_value_on_set: -> (val) { if val.respond_to?(:build) then val.build else val end }
31
+ )
30
32
  @schema_types = SchemaTypes.new(fields: @fields)
31
33
 
32
34
  yield(@fields, @schema_types)
@@ -43,7 +45,7 @@ module Super
43
45
 
44
46
  def to_partial_path
45
47
  if @action_inquirer.nil?
46
- raise Super::Error::Initalization,
48
+ raise Super::Error::Initialization,
47
49
  "You must call the `#apply` method after instantiating Super::Display"
48
50
  elsif @action_inquirer.index?
49
51
  "display_index"
@@ -57,7 +59,6 @@ module Super
57
59
  # @private
58
60
  def render_attribute(template:, record:, column:)
59
61
  formatter = @fields[column]
60
- formatter = formatter.build if formatter.respond_to?(:build)
61
62
 
62
63
  formatted =
63
64
  SchemaTypes::TYPES
@@ -65,7 +66,7 @@ module Super
65
66
  .when(:record) do
66
67
  formatter.present(column, record)
67
68
  end
68
- .when(:column) do
69
+ .when(:attribute) do
69
70
  value = record.public_send(column)
70
71
  formatter.present(column, value)
71
72
  end
data/lib/super/error.rb CHANGED
@@ -24,13 +24,14 @@ module Super
24
24
  )
25
25
  end
26
26
  end
27
- # Error raised when something wasn't initalized correctly, and if there isn't
27
+ # Error raised when something wasn't initialized correctly, and if there isn't
28
28
  # a more specific error
29
- class Initalization < Error; end
29
+ class Initialization < Error; end
30
30
  class ArgumentError < Error; end
31
31
  class AlreadyRegistered < Error; end
32
32
  class AlreadyTranscribed < Error; end
33
33
  class NotImplementedError < Error; end
34
+ class IncompleteBuilder < Error; end
34
35
 
35
36
  class Enum < Error
36
37
  class ImpossibleValue < Enum; end
@@ -105,42 +105,6 @@ module Super
105
105
  Direct.new(super_builder: super_builder, method_name: method_name, args: args, kwargs: kwargs)
106
106
  end
107
107
 
108
- def select(*args, **kwargs)
109
- Direct.new(super_builder: true, method_name: :select!, args: args, kwargs: kwargs)
110
- end
111
-
112
- def text_field(*args, **kwargs)
113
- Direct.new(super_builder: true, method_name: :text_field!, args: args, kwargs: kwargs)
114
- end
115
-
116
- def rich_text_area(*args, **kwargs)
117
- Direct.new(super_builder: true, method_name: :rich_text_area!, args: args, kwargs: kwargs)
118
- end
119
-
120
- def check_box(*args, **kwargs)
121
- Direct.new(super_builder: true, method_name: :check_box!, args: args, kwargs: kwargs)
122
- end
123
-
124
- def date_flatpickr(*args, **kwargs)
125
- Direct.new(super_builder: true, method_name: :date_flatpickr!, args: args, kwargs: kwargs)
126
- end
127
-
128
- def datetime_flatpickr(*args, **kwargs)
129
- Direct.new(super_builder: true, method_name: :datetime_flatpickr!, args: args, kwargs: kwargs)
130
- end
131
-
132
- def hidden_field(*args, **kwargs)
133
- Direct.new(super_builder: false, method_name: :hidden_field, args: args, kwargs: kwargs)
134
- end
135
-
136
- def password_field(*args, **kwargs)
137
- Direct.new(super_builder: true, method_name: :password_field!, args: args, kwargs: kwargs)
138
- end
139
-
140
- def time_flatpickr(*args, **kwargs)
141
- Direct.new(super_builder: true, method_name: :time_flatpickr!, args: args, kwargs: kwargs)
142
- end
143
-
144
108
  def has_many(reader, **extras)
145
109
  subfields = Schema::Fields.new
146
110
  @fields.nested do
@@ -176,6 +140,14 @@ module Super
176
140
  nested: {}
177
141
  )
178
142
  end
143
+
144
+ def self.define_schema_type_for(method_name)
145
+ class_eval(<<~RUBY)
146
+ def #{method_name}(*args, **kwargs)
147
+ Direct.new(super_builder: true, method_name: :#{method_name}!, args: args, kwargs: kwargs)
148
+ end
149
+ RUBY
150
+ end
179
151
  end
180
152
  end
181
153
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class FormBuilder
5
+ class Wrappers
6
+ def rich_text_area(attribute, options = {})
7
+ options, defaults = split_defaults(options, class: "trix-content super-input w-full")
8
+ options[:class] = join_classes(defaults[:class], options[:class])
9
+
10
+ @builder.rich_text_area(attribute, options)
11
+ end
12
+
13
+ define_convenience :rich_text_area
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class FormBuilder
5
+ class Wrappers
6
+ skipped_field_helpers = [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]
7
+ (ActionView::Helpers::FormBuilder.field_helpers - skipped_field_helpers).each do |builder_method_name|
8
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
9
+ def #{builder_method_name}(attribute, options = {})
10
+ options, defaults = split_defaults(options, class: "super-input w-full")
11
+ options[:class] = join_classes(defaults[:class], options[:class])
12
+
13
+ @builder.#{builder_method_name}(attribute, options)
14
+ end
15
+
16
+ define_convenience :#{builder_method_name}
17
+ RUBY
18
+ end
19
+
20
+ def label(attribute, text = nil, options = {}, &block)
21
+ options, defaults = split_defaults(options, class: "block")
22
+ options[:class] = join_classes(defaults[:class], options[:class])
23
+
24
+ @builder.label(attribute, text, options, &block)
25
+ end
26
+
27
+ def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
28
+ @builder.check_box(attribute, options, checked_value, unchecked_value)
29
+ end
30
+
31
+ def check_box!(attribute, checked_value: "1", unchecked_value: "0", label_text: nil, label: {}, field: {}, show_errors: true)
32
+ label[:super] ||= {}
33
+ label[:super] = { class: "select-none ml-1" }.merge(label[:super])
34
+ container do
35
+ compact_join([
36
+ "<div>".html_safe,
37
+ public_send(:check_box, attribute, field, checked_value, unchecked_value),
38
+ public_send(:label, attribute, label_text, label),
39
+ "</div>".html_safe,
40
+ show_errors && inline_errors(attribute),
41
+ ])
42
+ end
43
+ end
44
+
45
+ ::Super::Form::SchemaTypes.define_schema_type_for(:check_box)
46
+
47
+ # def file_field(attribute, options = {})
48
+ # end
49
+
50
+ # def file_field!(attribute, label_text: nil, label: {}, field: {}, show_errors: true)
51
+ # end
52
+
53
+ def hidden_field(attribute, options = {})
54
+ @builder.hidden_field(attribute, options)
55
+ end
56
+
57
+ # def radio_button(attribute, tag_value, options = {})
58
+ # @builder.radio_button(attribute, tag_value, options)
59
+ # end
60
+
61
+ # def radio_button(attribute, tag_value, label_text: nil, label: {}, field: {}, show_errors: true)
62
+ # end
63
+
64
+ def submit(value = nil, options = {})
65
+ value, options = nil, value if value.is_a?(Hash)
66
+ options, defaults = split_defaults(options, class: "super-button")
67
+ options[:class] = join_classes(defaults[:class], options[:class])
68
+
69
+ @builder.submit(value, options)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class FormBuilder
5
+ class Wrappers
6
+ def date_flatpickr(attribute, options = {})
7
+ options, defaults = split_defaults(
8
+ options,
9
+ class: "super-input w-full",
10
+ data: {
11
+ controller: "flatpickr",
12
+ flatpickr_options_value: {
13
+ dateFormat: "Y-m-d",
14
+ }
15
+ }
16
+ )
17
+ options[:class] = join_classes(defaults[:class], options[:class])
18
+ options[:data] = defaults[:data].deep_merge(options[:data] || {})
19
+ options[:value] = @builder.object.public_send(attribute).presence
20
+ options[:value] = options[:value].iso8601 if options[:value].respond_to?(:iso8601)
21
+
22
+ @builder.text_field(attribute, options)
23
+ end
24
+
25
+ define_convenience :date_flatpickr
26
+
27
+ def datetime_flatpickr(attribute, options = {})
28
+ options, defaults = split_defaults(
29
+ options,
30
+ class: "super-input w-full",
31
+ data: {
32
+ controller: "flatpickr",
33
+ flatpickr_options_value: {
34
+ enableSeconds: true,
35
+ enableTime: true,
36
+ dateFormat: "Z",
37
+ }
38
+ }
39
+ )
40
+ options[:class] = join_classes(defaults[:class], options[:class])
41
+ options[:data] = defaults[:data].deep_merge(options[:data] || {})
42
+ options[:value] = @builder.object.public_send(attribute).presence
43
+ options[:value] = options[:value].iso8601 if options[:value].respond_to?(:iso8601)
44
+
45
+ @builder.text_field(attribute, options)
46
+ end
47
+
48
+ define_convenience :datetime_flatpickr
49
+
50
+ def time_flatpickr(attribute, options = {})
51
+ options, defaults = split_defaults(
52
+ options,
53
+ class: "super-input w-full",
54
+ data: {
55
+ controller: "flatpickr",
56
+ flatpickr_options_value: {
57
+ enableSeconds: true,
58
+ enableTime: true,
59
+ noCalendar: true,
60
+ dateFormat: "H:i:S",
61
+ }
62
+ }
63
+ )
64
+ options[:class] = join_classes(defaults[:class], options[:class])
65
+ options[:data] = defaults[:data].deep_merge(options[:data] || {})
66
+ options[:value] = @builder.object.public_send(attribute).presence
67
+ options[:value] = options[:value].strftime("%H:%M:%S") if options[:value].respond_to?(:strftime)
68
+
69
+ @builder.text_field(attribute, options)
70
+ end
71
+
72
+ define_convenience :time_flatpickr
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class FormBuilder
5
+ class Wrappers
6
+ def select(attribute, choices, options = {}, html_options = {}, &block)
7
+ options, defaults = split_defaults(options, include_blank: true)
8
+ options = defaults.merge(options)
9
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
10
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
11
+
12
+ @builder.select(attribute, choices, options, html_options, &block)
13
+ end
14
+
15
+ define_convenience :select
16
+
17
+ def collection_select(attribute, collection, value_method, text_method, options = {}, html_options = {})
18
+ options, defaults = split_defaults(options, include_blank: true)
19
+ options = defaults.merge(options)
20
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
21
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
22
+
23
+ @builder.collection_select(attribute, collection, value_method, text_method, options, html_options)
24
+ end
25
+
26
+ define_convenience :collection_select
27
+
28
+ def grouped_collection_select(attribute, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
29
+ options, defaults = split_defaults(options, include_blank: true)
30
+ options = defaults.merge(options)
31
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
32
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
33
+
34
+ @builder.grouped_collection_select(attribute, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
35
+ end
36
+
37
+ define_convenience :grouped_collection_select
38
+
39
+ def time_zone_select(attribute, priority_zones = nil, options = {}, html_options = {})
40
+ options, defaults = split_defaults(options, include_blank: true)
41
+ options = defaults.merge(options)
42
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
43
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
44
+
45
+ @builder.time_zone_select(attribute, priority_zones, options, html_options)
46
+ end
47
+
48
+ define_convenience :time_zone_select, priority_zones: "nil"
49
+
50
+ def collection_check_boxes(attribute, collection, value_method, text_method, options = {}, html_options = {}, &block)
51
+ options, defaults = split_defaults(options, include_blank: true)
52
+ options = defaults.merge(options)
53
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
54
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
55
+
56
+ @builder.collection_check_boxes(attribute, collection, value_method, text_method, options, html_options, &block)
57
+ end
58
+
59
+ define_convenience :collection_check_boxes
60
+
61
+ def collection_radio_buttons(attribute, collection, value_method, text_method, options = {}, html_options = {}, &block)
62
+ options, defaults = split_defaults(options, include_blank: true)
63
+ options = defaults.merge(options)
64
+ html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select")
65
+ html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
66
+
67
+ @builder.collection_radio_buttons(attribute, collection, value_method, text_method, options, html_options, &block)
68
+ end
69
+
70
+ define_convenience :collection_radio_buttons
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ # Example
5
+ #
6
+ # ```ruby
7
+ # super_form_for([:admin, @member]) do |f|
8
+ # # the long way
9
+ # f.super.label :name
10
+ # f.super.text_field :name
11
+ # f.super.inline_errors :name
12
+ #
13
+ # # the short way (slightly different from the long way, for alignment)
14
+ # f.super.text_field! :position
15
+ # end
16
+ # ```
17
+ #
18
+ # Refer to the Rails docs:
19
+ # https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html
20
+ class FormBuilder < ActionView::Helpers::FormBuilder
21
+ FIELD_ERROR_PROC = proc { |html_tag, instance| html_tag }
22
+ FORM_BUILDER_DEFAULTS = { builder: self }.freeze
23
+
24
+ def super(**options)
25
+ @super_wrappers ||= Wrappers.new(self, @template)
26
+ end
27
+
28
+ class Wrappers
29
+ def initialize(builder, template)
30
+ @builder = builder
31
+ @template = template
32
+ end
33
+
34
+ def inline_errors(attribute)
35
+ if @builder.object
36
+ messages = Form::InlineErrors.error_messages(@builder.object, attribute).map do |msg|
37
+ error_content_tag(msg)
38
+ end
39
+
40
+ @template.safe_join(messages)
41
+ else
42
+ error_content_tag(<<~MSG.html_safe)
43
+ This form doesn't have an object, so something is probably wrong.
44
+ Maybe <code>accepts_nested_attributes_for</code> isn't set up?
45
+ MSG
46
+ end
47
+ end
48
+
49
+ def container(&block)
50
+ @template.content_tag(:div, class: "super-field-group", &block)
51
+ end
52
+
53
+ private
54
+
55
+ private_class_method def self.define_with_label_tag(method_name, **optionals)
56
+ parameters = instance_method(method_name).parameters
57
+ definition = []
58
+ call = []
59
+ definition_last = ["label_text: nil", "label: {}", "show_errors: true"]
60
+ call_last = []
61
+ parameters.each do |type, name|
62
+ if name == :options && type == :opt
63
+ definition.push("field: {}")
64
+ call.push("field")
65
+ elsif name == :html_options && type == :opt
66
+ definition.push("field_html: {}")
67
+ call.push("field_html")
68
+ elsif type == :block
69
+ definition_last.push("&#{name}")
70
+ call_last.push("&#{name}")
71
+ else
72
+ if type == :req
73
+ definition.push(name.to_s)
74
+ call.push(name.to_s)
75
+ elsif type == :opt || type == :key
76
+ if !optionals.key?(name)
77
+ raise Super::Error::ArgumentError, "Form bang method has optional argument, but doesn't know the default value: #{name}"
78
+ end
79
+
80
+ default_value = optionals[name]
81
+
82
+ if type == :opt
83
+ definition.push("#{name} = #{default_value}")
84
+ call.push(name.to_s)
85
+ elsif type == :key
86
+ definition.push("#{name}: #{default_value}")
87
+ call.push("#{name}: #{name}")
88
+ else
89
+ raise Super::Error::ArgumentError, "Form bang method has a unprocessable argument with name #{name}"
90
+ end
91
+ else
92
+ raise Super::Error::ArgumentError, "Form bang method has keyword argument type #{type} and name #{name}"
93
+ end
94
+ end
95
+ end
96
+
97
+ definition += definition_last
98
+ call += call_last
99
+
100
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
101
+ def #{method_name}!(#{definition.join(", ")})
102
+ container do
103
+ compact_join([
104
+ public_send(:label, attribute, label_text, label),
105
+ %(<div class="mt-1">).html_safe,
106
+ #{method_name}(#{call.join(", ")}),
107
+ show_errors && inline_errors(attribute),
108
+ %(</div>).html_safe,
109
+ ])
110
+ end
111
+ end
112
+ RUBY
113
+ end
114
+
115
+ private_class_method def self.define_convenience(method_name, *args, **kwargs)
116
+ define_with_label_tag(method_name, *args, **kwargs)
117
+ ::Super::Form::SchemaTypes.define_schema_type_for(method_name)
118
+ end
119
+
120
+ def split_defaults(options, **internal_defaults)
121
+ defaults = options.delete(:super) || {}
122
+ # prefer options set in `defaults`, since they are user overrides
123
+ defaults = internal_defaults.merge(defaults)
124
+
125
+ [options, defaults]
126
+ end
127
+
128
+ def join_classes(*class_lists)
129
+ class_lists.flatten.map(&:presence).compact
130
+ end
131
+
132
+ def error_content_tag(content)
133
+ @template.content_tag(:p, content, class: "text-red-400 text-xs italic pt-1")
134
+ end
135
+
136
+ def compact_join(*parts)
137
+ @template.safe_join(
138
+ parts.flatten.map(&:presence).compact
139
+ )
140
+ end
141
+ end
142
+ end
143
+ end