super 0.0.7 → 0.0.12
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.
- checksums.yaml +4 -4
- data/.yardopts +0 -6
- data/CONTRIBUTING.md +56 -0
- data/README.md +49 -61
- data/Rakefile +16 -14
- data/app/assets/javascripts/super/application.js +297 -97
- data/app/assets/stylesheets/super/application.css +5600 -0
- data/app/controllers/super/application_controller.rb +20 -14
- data/app/helpers/super/form_builder_helper.rb +25 -0
- data/app/views/layouts/super/application.html.erb +23 -9
- data/app/views/super/application/{_super_schema_display_actions.html.erb → _display_actions.html.erb} +0 -0
- data/app/views/super/application/{_super_schema_display_index.html.erb → _display_index.html.erb} +6 -6
- data/app/views/super/application/_display_rich_text.html.erb +1 -0
- data/app/views/super/application/_display_show.html.erb +8 -0
- data/app/views/super/application/_filter.html.erb +5 -13
- data/app/views/super/application/_filter_type_select.html.erb +9 -19
- data/app/views/super/application/_filter_type_text.html.erb +7 -11
- data/app/views/super/application/_filter_type_timestamp.html.erb +6 -17
- data/app/views/super/application/_form.html.erb +15 -0
- data/app/views/super/application/_form_field__destroy.html.erb +1 -9
- data/app/views/super/application/_form_field_checkbox.html.erb +1 -0
- data/app/views/super/application/_form_field_rich_text_area.html.erb +1 -0
- data/app/views/super/application/_form_field_select.html.erb +1 -23
- data/app/views/super/application/_form_field_text.html.erb +1 -13
- data/app/views/super/application/{_super_layout.html.erb → _layout.html.erb} +7 -7
- data/app/views/super/application/{_super_pagination.html.erb → _pagination.html.erb} +1 -1
- data/app/views/super/application/{_super_panel.html.erb → _panel.html.erb} +2 -2
- data/app/views/super/application/_query.html.erb +18 -0
- data/app/views/super/application/_sort.html.erb +18 -0
- data/app/views/super/application/_sort_expression.html.erb +25 -0
- data/app/views/super/application/edit.html.erb +1 -0
- data/app/views/super/application/index.html.erb +1 -0
- data/app/views/super/application/new.html.erb +1 -0
- data/app/views/super/application/show.html.erb +1 -0
- data/app/views/super/feather/README.md +1 -0
- data/app/views/super/feather/_x.html +15 -0
- data/config/routes.rb +2 -0
- data/docs/cheat.md +8 -8
- data/frontend/super-frontend/dist/application.css +5600 -0
- data/frontend/super-frontend/dist/application.js +297 -97
- data/lib/generators/super/action_text/USAGE +23 -0
- data/lib/generators/super/action_text/action_text_generator.rb +32 -0
- data/lib/generators/super/action_text/templates/pack_super_action_text.css +23 -0
- data/lib/generators/super/action_text/templates/pack_super_action_text.js +4 -0
- data/lib/generators/super/install/install_generator.rb +18 -7
- data/lib/generators/super/install/templates/base_controller.rb.tt +9 -1
- data/lib/generators/super/install/templates/initializer.rb.tt +9 -2
- data/lib/generators/super/resource/resource_generator.rb +107 -30
- data/lib/generators/super/resource/templates/resources_controller.rb.tt +3 -39
- data/lib/generators/super/webpacker/USAGE +5 -4
- data/lib/generators/super/webpacker/webpacker_generator.rb +5 -2
- data/lib/super.rb +17 -0
- data/lib/super/action_inquirer.rb +2 -0
- data/lib/super/assets.rb +114 -38
- data/lib/super/client_error.rb +2 -0
- data/lib/super/compatibility.rb +2 -0
- data/lib/super/configuration.rb +16 -79
- data/lib/super/controls.rb +11 -25
- data/lib/super/controls/optional.rb +75 -16
- data/lib/super/controls/steps.rb +36 -58
- data/lib/super/controls/view.rb +55 -0
- data/lib/super/display.rb +88 -0
- data/lib/super/display/guesser.rb +36 -0
- data/lib/super/display/schema_types.rb +80 -78
- data/lib/super/engine.rb +5 -1
- data/lib/super/error.rb +21 -0
- data/lib/super/filter.rb +7 -130
- data/lib/super/filter/form_object.rb +94 -0
- data/lib/super/filter/guesser.rb +32 -0
- data/lib/super/filter/operator.rb +2 -0
- data/lib/super/filter/schema_types.rb +3 -7
- data/lib/super/form.rb +29 -40
- data/lib/super/form/builder.rb +206 -0
- data/lib/super/form/guesser.rb +29 -0
- data/lib/super/form/inline_errors.rb +28 -0
- data/lib/super/form/schema_types.rb +31 -29
- data/lib/super/form/strong_params.rb +31 -0
- data/lib/super/layout.rb +3 -1
- data/lib/super/link.rb +7 -0
- data/lib/super/navigation/automatic.rb +4 -2
- data/lib/super/pagination.rb +13 -17
- data/lib/super/panel.rb +3 -1
- data/lib/super/partial.rb +2 -0
- data/lib/super/partial/resolving.rb +2 -0
- data/lib/super/plugin.rb +2 -0
- data/lib/super/query/form_object.rb +48 -0
- data/lib/super/schema.rb +2 -25
- data/lib/super/schema/common.rb +27 -0
- data/lib/super/schema/guesser.rb +79 -0
- data/lib/super/sort.rb +110 -0
- data/lib/super/useful/builder.rb +25 -0
- data/lib/super/useful/enum.rb +63 -0
- data/lib/super/version.rb +3 -1
- data/lib/super/view_helper.rb +2 -19
- metadata +63 -34
- data/app/views/super/application/_form_inline_errors.html.erb +0 -10
- data/app/views/super/application/_super_schema_display_show.html.erb +0 -8
- data/app/views/super/application/_super_schema_form.html.erb +0 -15
- data/docs/README.md +0 -6
- data/docs/faq.md +0 -44
- data/docs/quick_start.md +0 -45
- data/docs/webpacker.md +0 -17
- data/docs/yard_customizations.rb +0 -41
- data/frontend/super-frontend/build.js +0 -36
- data/frontend/super-frontend/package.json +0 -21
- data/frontend/super-frontend/postcss.config.js +0 -6
- data/frontend/super-frontend/src/javascripts/super/application.ts +0 -18
- data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +0 -19
- data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +0 -1
- data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +0 -15
- data/frontend/super-frontend/src/stylesheets/super/application.css +0 -77
- data/frontend/super-frontend/tailwind.config.js +0 -15
- data/frontend/super-frontend/tsconfig.json +0 -13
- data/frontend/super-frontend/yarn.lock +0 -5448
- data/lib/super/controls/required.rb +0 -41
data/lib/super/engine.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Super
|
|
2
4
|
# Configures the host Rails app to work with Super
|
|
3
5
|
class Engine < ::Rails::Engine
|
|
6
|
+
isolate_namespace Super
|
|
7
|
+
|
|
4
8
|
initializer "super.assets.precompile" do |app|
|
|
5
|
-
if Super::Assets.sprockets_available?
|
|
9
|
+
if Super::Assets::Handler.sprockets_available?
|
|
6
10
|
app.config.assets.precompile << "config/super_manifest.js"
|
|
7
11
|
end
|
|
8
12
|
end
|
data/lib/super/error.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Super
|
|
2
4
|
# A container class for all internal errors thrown by this library
|
|
3
5
|
#
|
|
@@ -13,5 +15,24 @@ module Super
|
|
|
13
15
|
class ActionInquirerError < Error; end
|
|
14
16
|
# Error raised when a `Super::Link` couldn't be found
|
|
15
17
|
class LinkNotRegistered < Error; end
|
|
18
|
+
# Error raised when rendering if `@view` wasn't set by the controller
|
|
19
|
+
class NothingToRender < Error
|
|
20
|
+
def initialize(basename)
|
|
21
|
+
super(
|
|
22
|
+
"Super's built-in `#{basename}.html.erb` requires `@view` to be set " \
|
|
23
|
+
"by the controller, but it wasn't set"
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
# Error raised when something wasn't initalized correctly, and if there isn't
|
|
28
|
+
# a more specific error
|
|
29
|
+
class Initalization < Error; end
|
|
30
|
+
class ArgumentError < Error; end
|
|
31
|
+
|
|
32
|
+
class Enum < Error
|
|
33
|
+
class ImpossibleValue < Enum; end
|
|
34
|
+
class ArgumentError < Enum; end
|
|
35
|
+
class UndefinedCase < Enum; end
|
|
36
|
+
end
|
|
16
37
|
end
|
|
17
38
|
end
|
data/lib/super/filter.rb
CHANGED
|
@@ -1,137 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Super
|
|
2
4
|
class Filter
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@filter_form = controls.initialize_filtering(params: params, query_params: request.GET)
|
|
7
|
-
@records = controls.filter_records(filter_form: @filter_form, records: @records)
|
|
8
|
-
@view.asides.push(:@filter_form)
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
class FilterFormField
|
|
13
|
-
def initialize(humanized_field_name:, field_name:, type:, params:)
|
|
14
|
-
@humanized_field_name = humanized_field_name
|
|
15
|
-
@field_name = field_name
|
|
16
|
-
@field_type = type
|
|
17
|
-
@params = params
|
|
18
|
-
@specified_values =
|
|
19
|
-
type.q
|
|
20
|
-
.map do |query_field_name|
|
|
21
|
-
[
|
|
22
|
-
query_field_name,
|
|
23
|
-
(params || {})[query_field_name],
|
|
24
|
-
]
|
|
25
|
-
end
|
|
26
|
-
.to_h
|
|
27
|
-
|
|
28
|
-
@specified_values.each do |key, value|
|
|
29
|
-
define_singleton_method(key) { value }
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
attr_reader :humanized_field_name
|
|
34
|
-
attr_reader :field_name
|
|
35
|
-
attr_reader :field_type
|
|
36
|
-
attr_reader :specified_values
|
|
37
|
-
|
|
38
|
-
def op
|
|
39
|
-
(@params || {})[:op]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def operators
|
|
43
|
-
@field_type.operators
|
|
44
|
-
.map { |o| [o.name, o.identifier] }
|
|
45
|
-
.to_h
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def to_partial_path
|
|
49
|
-
@field_type.to_partial_path
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def initialize(model:, url:, schema:, params:)
|
|
54
|
-
@model = model
|
|
55
|
-
@url = url
|
|
56
|
-
@schema = schema
|
|
57
|
-
@params = params
|
|
58
|
-
|
|
59
|
-
@form_fields = {}
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def each_field
|
|
63
|
-
@schema.fields.each do |field_name, _field_type|
|
|
64
|
-
yield(form_field_for(field_name))
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def url
|
|
69
|
-
@url
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def to_partial_path
|
|
73
|
-
"filter"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def to_search_query(relation)
|
|
77
|
-
each_field do |form_field|
|
|
78
|
-
next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
|
|
79
|
-
next if !Super::Filter::Operator.registry.key?(form_field.op)
|
|
5
|
+
def initialize
|
|
6
|
+
@schema_type = Filter::SchemaTypes.new
|
|
7
|
+
@fields = Schema::Fields.new
|
|
80
8
|
|
|
81
|
-
|
|
82
|
-
updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
|
|
83
|
-
|
|
84
|
-
if updated_relation.is_a?(ActiveRecord::Relation)
|
|
85
|
-
relation = updated_relation
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
relation
|
|
9
|
+
yield(@fields, @schema_type)
|
|
90
10
|
end
|
|
91
11
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def form_field_for(field_name)
|
|
95
|
-
@form_fields[field_name] ||=
|
|
96
|
-
FilterFormField.new(
|
|
97
|
-
humanized_field_name: @model.human_attribute_name(field_name),
|
|
98
|
-
field_name: field_name,
|
|
99
|
-
type: @schema.fields[field_name],
|
|
100
|
-
params: (@params || {})[field_name]
|
|
101
|
-
)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
class Controls
|
|
106
|
-
module Optional
|
|
107
|
-
def filters_enabled?
|
|
108
|
-
actual.respond_to?(:filter_schema)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def filter_schema
|
|
112
|
-
actual.filter_schema
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
module Steps
|
|
117
|
-
def initialize_filtering(params:, query_params:)
|
|
118
|
-
if filters_enabled?
|
|
119
|
-
Super::Filter.new(
|
|
120
|
-
model: model,
|
|
121
|
-
schema: filter_schema,
|
|
122
|
-
params: params[:q],
|
|
123
|
-
url: query_params
|
|
124
|
-
)
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def filter_records(filter_form:, records:)
|
|
129
|
-
if filters_enabled? && records
|
|
130
|
-
filter_form.to_search_query(records)
|
|
131
|
-
else
|
|
132
|
-
records
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
12
|
+
attr_reader :fields
|
|
136
13
|
end
|
|
137
14
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Super
|
|
4
|
+
class Filter
|
|
5
|
+
class FormObject
|
|
6
|
+
class FilterFormField
|
|
7
|
+
def initialize(humanized_field_name:, field_name:, type:, params:)
|
|
8
|
+
@humanized_field_name = humanized_field_name
|
|
9
|
+
@field_name = field_name
|
|
10
|
+
@field_type = type
|
|
11
|
+
@params = params
|
|
12
|
+
@specified_values =
|
|
13
|
+
type.q
|
|
14
|
+
.map do |query_field_name|
|
|
15
|
+
[
|
|
16
|
+
query_field_name,
|
|
17
|
+
(params || {})[query_field_name],
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
.to_h
|
|
21
|
+
|
|
22
|
+
@specified_values.each do |key, value|
|
|
23
|
+
define_singleton_method(key) { value }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_reader :humanized_field_name
|
|
28
|
+
attr_reader :field_name
|
|
29
|
+
attr_reader :field_type
|
|
30
|
+
attr_reader :specified_values
|
|
31
|
+
|
|
32
|
+
def op
|
|
33
|
+
(@params || {})[:op]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def operators
|
|
37
|
+
@field_type.operators
|
|
38
|
+
.map { |o| [o.name, o.identifier] }
|
|
39
|
+
.to_h
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_partial_path
|
|
43
|
+
@field_type.to_partial_path
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(model:, params:, schema:)
|
|
48
|
+
@model = model
|
|
49
|
+
@params = params
|
|
50
|
+
@schema = schema
|
|
51
|
+
|
|
52
|
+
@form_fields = {}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def each_field
|
|
56
|
+
@schema.fields.each do |field_name, _field_type|
|
|
57
|
+
yield(form_field_for(field_name))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_partial_path
|
|
62
|
+
"filter"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def apply_changes(relation)
|
|
66
|
+
each_field do |form_field|
|
|
67
|
+
next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
|
|
68
|
+
next if !Super::Filter::Operator.registry.key?(form_field.op)
|
|
69
|
+
|
|
70
|
+
operator = Super::Filter::Operator.registry[form_field.op]
|
|
71
|
+
updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
|
|
72
|
+
|
|
73
|
+
if updated_relation.is_a?(ActiveRecord::Relation)
|
|
74
|
+
relation = updated_relation
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
relation
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def form_field_for(field_name)
|
|
84
|
+
@form_fields[field_name] ||=
|
|
85
|
+
FilterFormField.new(
|
|
86
|
+
humanized_field_name: @model.human_attribute_name(field_name),
|
|
87
|
+
field_name: field_name,
|
|
88
|
+
type: @schema.fields[field_name],
|
|
89
|
+
params: (@params || {})[field_name]
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Super
|
|
4
|
+
class Filter
|
|
5
|
+
class Guesser
|
|
6
|
+
def initialize(model:, fields:, type:)
|
|
7
|
+
@model = model
|
|
8
|
+
@fields = fields
|
|
9
|
+
@type = type
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
Schema::Guesser
|
|
14
|
+
.new(model: @model, fields: @fields, type: @type)
|
|
15
|
+
.assign_type { |attribute_name| attribute_type_for(attribute_name) }
|
|
16
|
+
.call
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def attribute_type_for(attribute_name)
|
|
22
|
+
type = @model.type_for_attribute(attribute_name).type
|
|
23
|
+
case type
|
|
24
|
+
when :datetime
|
|
25
|
+
@type.timestamp
|
|
26
|
+
else
|
|
27
|
+
@type.text
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Super
|
|
2
4
|
class Filter
|
|
3
5
|
# This schema type is used to configure the filtering form on your +#index+
|
|
@@ -13,7 +15,7 @@ module Super
|
|
|
13
15
|
# # ...
|
|
14
16
|
#
|
|
15
17
|
# def filter_schema
|
|
16
|
-
# Super::
|
|
18
|
+
# Super::Filter.new do |fields, type|
|
|
17
19
|
# fields[:name] = type.text(operators: [
|
|
18
20
|
# Super::Filter::Operator.eq,
|
|
19
21
|
# Super::Filter::Operator.contain,
|
|
@@ -90,12 +92,6 @@ module Super
|
|
|
90
92
|
end
|
|
91
93
|
end
|
|
92
94
|
|
|
93
|
-
def before_yield(fields:)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def after_yield
|
|
97
|
-
end
|
|
98
|
-
|
|
99
95
|
def select(collection:, operators: Filter::Operator.select_defaults)
|
|
100
96
|
Select.new(
|
|
101
97
|
collection: collection,
|
data/lib/super/form.rb
CHANGED
|
@@ -1,48 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Super
|
|
4
|
+
# This schema type is used on your +#edit+ and +#new+ forms
|
|
5
|
+
#
|
|
6
|
+
# ```ruby
|
|
7
|
+
# class MembersController::Controls
|
|
8
|
+
# # ...
|
|
9
|
+
#
|
|
10
|
+
# def new_schema
|
|
11
|
+
# Super::Form.new do |fields, type|
|
|
12
|
+
# fields[:name] = type.string
|
|
13
|
+
# fields[:rank] = type.select
|
|
14
|
+
# fields[:position] = type.string
|
|
15
|
+
# fields[:ship_id] = type.select(
|
|
16
|
+
# collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
|
|
17
|
+
# )
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # ...
|
|
22
|
+
# end
|
|
23
|
+
# ```
|
|
2
24
|
class Form
|
|
3
|
-
|
|
4
|
-
def error_wrapping(html_tag)
|
|
5
|
-
if Thread.current[:super_form_builder]
|
|
6
|
-
return html_tag
|
|
7
|
-
end
|
|
25
|
+
include Schema::Common
|
|
8
26
|
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
def initialize
|
|
28
|
+
@fields = Schema::Fields.new
|
|
29
|
+
@schema_types = SchemaTypes.new(fields: @fields)
|
|
30
|
+
yield(@fields, @schema_types)
|
|
11
31
|
end
|
|
12
32
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
# * actionview/lib/action_view/helpers/form_helper.rb
|
|
17
|
-
# * actionview/lib/action_view/helpers/form_options_helper.rb
|
|
18
|
-
# * actionview/lib/action_view/helpers/date_helper.rb
|
|
19
|
-
%w[
|
|
20
|
-
label text_field password_field hidden_field file_field text_area
|
|
21
|
-
check_box radio_button color_field search_field telephone_field
|
|
22
|
-
date_field time_field datetime_field month_field week_field url_field
|
|
23
|
-
email_field number_field range_field
|
|
24
|
-
|
|
25
|
-
select collection_select grouped_collection_select time_zone_select
|
|
26
|
-
collection_radio_buttons collection_check_boxes
|
|
27
|
-
|
|
28
|
-
time_select datetime_select date_select
|
|
29
|
-
].each do |field_type_method|
|
|
30
|
-
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
31
|
-
def #{field_type_method}(*)
|
|
32
|
-
Thread.current[:super_form_builder] = true
|
|
33
|
-
super
|
|
34
|
-
ensure
|
|
35
|
-
Thread.current[:super_form_builder] = nil
|
|
36
|
-
end
|
|
37
|
-
RUBY
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
alias datetime_local_field datetime_field
|
|
41
|
-
alias phone_field telephone_field
|
|
33
|
+
def to_partial_path
|
|
34
|
+
"form"
|
|
42
35
|
end
|
|
43
36
|
end
|
|
44
37
|
end
|
|
45
|
-
|
|
46
|
-
ActionView::Helpers::Tags::Base.class_eval do
|
|
47
|
-
prepend Super::Form::FieldErrorProc
|
|
48
|
-
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Super
|
|
4
|
+
class Form
|
|
5
|
+
# Example
|
|
6
|
+
#
|
|
7
|
+
# ```ruby
|
|
8
|
+
# super_form_for([:admin, @member]) do |f|
|
|
9
|
+
# # the long way
|
|
10
|
+
# f.super.label :name
|
|
11
|
+
# f.super.text_field :name
|
|
12
|
+
# f.super.inline_errors :name
|
|
13
|
+
#
|
|
14
|
+
# # the short way (slightly different from the long way, for alignment)
|
|
15
|
+
# f.super.text_field! :position
|
|
16
|
+
# end
|
|
17
|
+
# ```
|
|
18
|
+
#
|
|
19
|
+
# Refer to the Rails docs:
|
|
20
|
+
# https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html
|
|
21
|
+
class Builder < ActionView::Helpers::FormBuilder
|
|
22
|
+
FIELD_ERROR_PROC = proc { |html_tag, instance| html_tag }
|
|
23
|
+
FORM_BUILDER_DEFAULTS = { builder: self }.freeze
|
|
24
|
+
|
|
25
|
+
def super(**options)
|
|
26
|
+
@super_wrappers ||= Wrappers.new(self, @template)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Wrappers
|
|
30
|
+
def initialize(builder, template)
|
|
31
|
+
@builder = builder
|
|
32
|
+
@template = template
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inline_errors(attribute)
|
|
36
|
+
if @builder.object
|
|
37
|
+
messages = InlineErrors.error_messages(@builder.object, attribute).map do |msg|
|
|
38
|
+
error_content_tag(msg)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@template.safe_join(messages)
|
|
42
|
+
else
|
|
43
|
+
error_content_tag(<<~MSG.html_safe)
|
|
44
|
+
This form doesn't have an object, so something is probably wrong.
|
|
45
|
+
Maybe <code>accepts_nested_attributes_for</code> isn't set up?
|
|
46
|
+
MSG
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def label(attribute, text = nil, options = {}, &block)
|
|
51
|
+
options, defaults = split_defaults(options, class: "block")
|
|
52
|
+
options[:class] = join_classes(defaults[:class], options[:class])
|
|
53
|
+
|
|
54
|
+
@builder.label(attribute, text, options, &block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
|
|
58
|
+
@builder.check_box(attribute, options, checked_value, unchecked_value)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def password_field(attribute, options = {})
|
|
62
|
+
options, defaults = split_defaults(options, class: "super-input w-full")
|
|
63
|
+
options[:class] = join_classes(defaults[:class], options[:class])
|
|
64
|
+
|
|
65
|
+
@builder.password_field(attribute, options)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rich_text_area(attribute, options = {})
|
|
69
|
+
options, defaults = split_defaults(options, class: "trix-content super-input w-full")
|
|
70
|
+
options[:class] = join_classes(defaults[:class], options[:class])
|
|
71
|
+
|
|
72
|
+
@builder.rich_text_area(attribute, options)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def select(attribute, choices, options = {}, html_options = {}, &block)
|
|
76
|
+
options, defaults = split_defaults(options, include_blank: true)
|
|
77
|
+
options = defaults.merge(options)
|
|
78
|
+
html_options, html_defaults = split_defaults(html_options, class: "super-input super-input-select-field")
|
|
79
|
+
html_options[:class] = join_classes(html_defaults[:class], html_options[:class])
|
|
80
|
+
|
|
81
|
+
parts = [
|
|
82
|
+
%(<div class="super-input-select">).html_safe,
|
|
83
|
+
@builder.select(attribute, choices, options, html_options, &block),
|
|
84
|
+
<<~HTML.html_safe,
|
|
85
|
+
<div class="super-input-select-icon text-blue-700">
|
|
86
|
+
<span class="h-4 w-4">
|
|
87
|
+
HTML
|
|
88
|
+
@template.render("super/feather/chevron_down"),
|
|
89
|
+
<<~HTML.html_safe,
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
HTML
|
|
93
|
+
%(</div>).html_safe,
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
@template.safe_join(parts)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def submit(value = nil, options = {})
|
|
100
|
+
value, options = nil, value if value.is_a?(Hash)
|
|
101
|
+
options, defaults = split_defaults(options, class: "super-button")
|
|
102
|
+
options[:class] = join_classes(defaults[:class], options[:class])
|
|
103
|
+
|
|
104
|
+
@builder.submit(value, options)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def text_field(attribute, options = {})
|
|
108
|
+
options, defaults = split_defaults(options, class: "super-input w-full")
|
|
109
|
+
options[:class] = join_classes(defaults[:class], options[:class])
|
|
110
|
+
|
|
111
|
+
@builder.text_field(attribute, options)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def container(&block)
|
|
115
|
+
@template.content_tag(:div, class: "super-field-group", &block)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def check_box!(attribute, checked_value = "1", unchecked_value = "0", label: {}, field: {}, show_errors: true)
|
|
119
|
+
label[:super] ||= {}
|
|
120
|
+
label[:super] = { class: "select-none ml-1" }.merge(label[:super])
|
|
121
|
+
container do
|
|
122
|
+
compact_join([
|
|
123
|
+
"<div>".html_safe,
|
|
124
|
+
public_send(:check_box, attribute, field, checked_value, unchecked_value),
|
|
125
|
+
public_send(:label, attribute, nil, label),
|
|
126
|
+
"</div>".html_safe,
|
|
127
|
+
show_errors && inline_errors(attribute),
|
|
128
|
+
])
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def password_field!(attribute, label: {}, field: {}, show_errors: true)
|
|
133
|
+
container do
|
|
134
|
+
compact_join([
|
|
135
|
+
public_send(:label, attribute, label),
|
|
136
|
+
%(<div class="mt-1">).html_safe,
|
|
137
|
+
public_send(:password_field, attribute, field),
|
|
138
|
+
show_errors && inline_errors(attribute),
|
|
139
|
+
%(</div>).html_safe,
|
|
140
|
+
])
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def rich_text_area!(attribute, label: {}, field: {}, show_errors: true)
|
|
145
|
+
container do
|
|
146
|
+
compact_join([
|
|
147
|
+
public_send(:label, attribute, label),
|
|
148
|
+
%(<div class="mt-1">).html_safe,
|
|
149
|
+
public_send(:rich_text_area, attribute, field),
|
|
150
|
+
show_errors && inline_errors(attribute),
|
|
151
|
+
%(</div>).html_safe,
|
|
152
|
+
])
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def select!(attribute, collection, label: {}, field: {}, show_errors: true)
|
|
157
|
+
container do
|
|
158
|
+
compact_join([
|
|
159
|
+
public_send(:label, attribute, label),
|
|
160
|
+
%(<div class="mt-1">).html_safe,
|
|
161
|
+
public_send(:select, attribute, collection, field),
|
|
162
|
+
show_errors && inline_errors(attribute),
|
|
163
|
+
%(</div>).html_safe,
|
|
164
|
+
])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def text_field!(attribute, label: {}, field: {}, show_errors: true)
|
|
169
|
+
container do
|
|
170
|
+
compact_join([
|
|
171
|
+
public_send(:label, attribute, label),
|
|
172
|
+
%(<div class="mt-1">).html_safe,
|
|
173
|
+
public_send(:text_field, attribute, field),
|
|
174
|
+
show_errors && inline_errors(attribute),
|
|
175
|
+
%(</div>).html_safe,
|
|
176
|
+
])
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def split_defaults(options, **internal_defaults)
|
|
183
|
+
defaults = options.delete(:super) || {}
|
|
184
|
+
# prefer options set in `defaults`, since they are user overrides
|
|
185
|
+
defaults = internal_defaults.merge(defaults)
|
|
186
|
+
|
|
187
|
+
[options, defaults]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def join_classes(*class_lists)
|
|
191
|
+
class_lists.flatten.map(&:presence).compact
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def error_content_tag(content)
|
|
195
|
+
@template.content_tag(:p, content, class: "text-red-400 text-xs italic pt-1")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def compact_join(*parts)
|
|
199
|
+
@template.safe_join(
|
|
200
|
+
parts.flatten.map(&:presence).compact
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|