super 0.19.0.rc1 → 0.21.0

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