action_form 0.1.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.
- checksums.yaml +7 -0
- data/.qlty/.gitignore +7 -0
- data/.qlty/qlty.toml +86 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +1169 -0
- data/Rakefile +12 -0
- data/lib/action_form/base.rb +110 -0
- data/lib/action_form/element.rb +153 -0
- data/lib/action_form/elements_dsl.rb +46 -0
- data/lib/action_form/input.rb +72 -0
- data/lib/action_form/rails/base.rb +116 -0
- data/lib/action_form/rails/rendering.rb +42 -0
- data/lib/action_form/rails/subform.rb +44 -0
- data/lib/action_form/rendering.rb +74 -0
- data/lib/action_form/schema_dsl.rb +41 -0
- data/lib/action_form/subform.rb +57 -0
- data/lib/action_form/subforms_collection.rb +96 -0
- data/lib/action_form/version.rb +5 -0
- data/lib/action_form.rb +21 -0
- data/sig/action_form.rbs +4 -0
- data/sig/easy_form.rbs +4 -0
- metadata +109 -0
data/Rakefile
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Base class for ActionForm components that provides form building functionality
|
5
|
+
# and integrates with Phlex for HTML rendering.
|
6
|
+
class Base < ::Phlex::HTML
|
7
|
+
include ActionForm::SchemaDSL
|
8
|
+
include ActionForm::ElementsDSL
|
9
|
+
include ActionForm::Rendering
|
10
|
+
|
11
|
+
attr_reader :elements_instances, :scope, :object, :html_options, :errors
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :elements, :scope
|
15
|
+
|
16
|
+
def subform_class
|
17
|
+
ActionForm::Subform
|
18
|
+
end
|
19
|
+
|
20
|
+
def scope(scope = nil)
|
21
|
+
return @scope unless scope
|
22
|
+
|
23
|
+
@scope = scope
|
24
|
+
end
|
25
|
+
|
26
|
+
def inherited(subclass)
|
27
|
+
super
|
28
|
+
subclass.elements = elements.dup
|
29
|
+
subclass.scope = scope.dup
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(object: nil, scope: self.class.scope, params: nil, **html_options)
|
34
|
+
super()
|
35
|
+
@object = object
|
36
|
+
@scope ||= scope
|
37
|
+
@params = @scope && params.respond_to?(@scope) ? params.public_send(@scope) : params
|
38
|
+
@html_options = html_options
|
39
|
+
@elements_instances = []
|
40
|
+
build_from_object
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_from_object
|
44
|
+
self.class.elements.each do |name, element_definition|
|
45
|
+
if element_definition < ActionForm::SubformsCollection
|
46
|
+
@elements_instances << build_many_subforms(name, element_definition)
|
47
|
+
@elements_instances.last << build_subform_template(name, element_definition.subform_definition)
|
48
|
+
elsif element_definition < ActionForm::Subform
|
49
|
+
@elements_instances << build_subform(name, element_definition)
|
50
|
+
elsif element_definition < ActionForm::Element
|
51
|
+
@elements_instances << element_definition.new(name, @params || @object, parent_name: @scope)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def view_template
|
57
|
+
render_form do
|
58
|
+
render_elements
|
59
|
+
render_submit
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def render_in(view_context)
|
64
|
+
@_view_context = view_context
|
65
|
+
view_context.render html: call.html_safe
|
66
|
+
end
|
67
|
+
|
68
|
+
def helpers
|
69
|
+
@_view_context
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def build_many_subforms(name, collection_definition)
|
75
|
+
collection = collection_definition.new(name)
|
76
|
+
Array(subform_value(name)).each.with_index do |item, index|
|
77
|
+
collection << build_subform(name, collection_definition.subform_definition, value: item, index: index)
|
78
|
+
end
|
79
|
+
collection
|
80
|
+
end
|
81
|
+
|
82
|
+
def subform_html_name(name, index: nil)
|
83
|
+
if index
|
84
|
+
@scope ? "#{@scope}[#{name}][#{index}]" : "[#{name}][#{index}]"
|
85
|
+
else
|
86
|
+
@scope ? "#{@scope}[#{name}]" : name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def subform_value(name)
|
91
|
+
@object.public_send(name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_subform(name, form_definition, value: subform_value(name), index: nil)
|
95
|
+
html_name = subform_html_name(name, index: index)
|
96
|
+
form_definition.new(name: name, scope: html_name, model: value, index: index).tap do |subform|
|
97
|
+
subform.helpers = helpers
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def build_subform_template(name, form_definition)
|
102
|
+
html_name = subform_html_name(name, index: "NEW_RECORD")
|
103
|
+
elements_keys = form_definition.elements.keys.push(:persisted?)
|
104
|
+
value = Struct.new(*elements_keys).new
|
105
|
+
form_definition.new(name: name, scope: html_name, model: value, template: true).tap do |subform|
|
106
|
+
subform.helpers = helpers
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Represents a form element with input/output configuration and HTML attributes
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
6
|
+
class Element
|
7
|
+
attr_reader :name, :input_options, :output_options, :html_name, :html_id, :select_options, :tags, :errors_messages
|
8
|
+
attr_accessor :helpers
|
9
|
+
|
10
|
+
def initialize(name, object, parent_name: nil)
|
11
|
+
@name = name
|
12
|
+
@object = object
|
13
|
+
@html_name = build_html_name(name, parent_name)
|
14
|
+
@html_id = build_html_id(name, parent_name)
|
15
|
+
@tags = self.class.tags_list.dup
|
16
|
+
@errors_messages = extract_errors_messages(object, name)
|
17
|
+
tags.merge!(errors: errors_messages.any?)
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def label_options
|
22
|
+
@label_options ||= [{ text: nil, display: true }, {}]
|
23
|
+
end
|
24
|
+
|
25
|
+
def select_options
|
26
|
+
@select_options ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def output_options
|
30
|
+
@output_options ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def input_options
|
34
|
+
@input_options ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def tags_list
|
38
|
+
@tags_list ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def input(type:, **options)
|
42
|
+
@input_options = { type: type }.merge(options)
|
43
|
+
tags_list.merge!(input: type)
|
44
|
+
end
|
45
|
+
|
46
|
+
def output(type:, **options)
|
47
|
+
@output_options = { type: type }.merge(options)
|
48
|
+
tags_list.merge!(output: type)
|
49
|
+
end
|
50
|
+
|
51
|
+
def options(collection)
|
52
|
+
@select_options = collection
|
53
|
+
tags_list.merge!(options: true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def label(text: nil, display: true, **html_options)
|
57
|
+
@label_options = [{ text: text, display: display }, html_options]
|
58
|
+
end
|
59
|
+
|
60
|
+
def tags(**tags_list)
|
61
|
+
tags_list.merge!(tags_list)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def label_text
|
66
|
+
self.class.label_options.first[:text] || name.to_s.tr("_", " ").capitalize
|
67
|
+
end
|
68
|
+
|
69
|
+
def label_html_attributes
|
70
|
+
{ for: html_id }.merge(self.class.label_options.last)
|
71
|
+
end
|
72
|
+
|
73
|
+
def html_value
|
74
|
+
if input_type == :checkbox
|
75
|
+
value ? "1" : "0"
|
76
|
+
elsif detached?
|
77
|
+
self.class.input_options[:value]
|
78
|
+
elsif object.is_a?(EasyParams::Base)
|
79
|
+
object.public_send(name)
|
80
|
+
else
|
81
|
+
value.to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def html_checked
|
86
|
+
return unless input_type == :checkbox
|
87
|
+
|
88
|
+
value
|
89
|
+
end
|
90
|
+
|
91
|
+
def input_html_attributes # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
92
|
+
attrs = self.class.input_options.dup
|
93
|
+
attrs[:name] ||= html_name
|
94
|
+
attrs[:id] ||= html_id
|
95
|
+
attrs[:value] ||= html_value
|
96
|
+
attrs[:checked] ||= html_checked
|
97
|
+
attrs[:disabled] ||= disabled?
|
98
|
+
attrs[:readonly] ||= readonly?
|
99
|
+
unless input_tag?
|
100
|
+
attrs.delete(:type)
|
101
|
+
attrs.delete(:value)
|
102
|
+
end
|
103
|
+
attrs
|
104
|
+
end
|
105
|
+
|
106
|
+
def value
|
107
|
+
return unless object
|
108
|
+
|
109
|
+
object.public_send(name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def render?
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def detached?
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
def disabled?
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
def readonly?
|
125
|
+
false
|
126
|
+
end
|
127
|
+
|
128
|
+
def input_type
|
129
|
+
self.class.input_options[:type].to_sym
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
attr_reader :object
|
135
|
+
|
136
|
+
def input_tag?
|
137
|
+
!%i[select textarea].include?(input_type)
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_html_name(name, parent_name)
|
141
|
+
parent_name ? "#{parent_name}[#{name}]" : name
|
142
|
+
end
|
143
|
+
|
144
|
+
def build_html_id(name, parent_name)
|
145
|
+
parent_name.to_s.split(/\[|\]/).reject(&:blank?).push(name).compact.join("_")
|
146
|
+
end
|
147
|
+
|
148
|
+
def extract_errors_messages(object, name)
|
149
|
+
(object.respond_to?(:errors) && object&.errors&.messages_for(name)) || []
|
150
|
+
end
|
151
|
+
end
|
152
|
+
# rubocop:enable Metrics/ClassLength
|
153
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Provides a DSL for defining form elements with input and output configurations.
|
5
|
+
# This module allows form classes to define elements using a simple block syntax.
|
6
|
+
# Elements can be configured with input types, output formats, labels and other options.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class UserForm < ActionForm::Base
|
10
|
+
# element :name do
|
11
|
+
# input type: :text
|
12
|
+
# label text: "Full Name"
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
module ElementsDSL
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
21
|
+
def elements
|
22
|
+
@elements ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: add support for outputless elements
|
26
|
+
def element(name, &block)
|
27
|
+
elements[name] = Class.new(ActionForm::Element)
|
28
|
+
elements[name].class_eval(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def many(name, default: nil, &block)
|
32
|
+
subform_definition = Class.new(ActionForm::SubformsCollection)
|
33
|
+
subform_definition.host_class = self
|
34
|
+
subform_definition.class_eval(&block) if block
|
35
|
+
elements[name] = subform_definition
|
36
|
+
elements[name].default = default if default
|
37
|
+
end
|
38
|
+
|
39
|
+
def subform(name, default: nil, &block)
|
40
|
+
elements[name] = Class.new(subform_class)
|
41
|
+
elements[name].class_eval(&block)
|
42
|
+
elements[name].default = default if default
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Represents a form element with input/output configuration and HTML attributes
|
5
|
+
class Input < Phlex::HTML
|
6
|
+
attr_reader :element, :html_attributes
|
7
|
+
|
8
|
+
def initialize(element, **html_attributes)
|
9
|
+
super()
|
10
|
+
@element = element
|
11
|
+
@html_attributes = html_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def view_template
|
15
|
+
if %i[checkbox radio select textarea].include?(element.input_type)
|
16
|
+
send("render_#{element.input_type}")
|
17
|
+
else
|
18
|
+
input(**mix(element.input_html_attributes, html_attributes))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def render_checkbox # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
25
|
+
if element.class.select_options.any?
|
26
|
+
element.class.select_options.each do |value, label_text|
|
27
|
+
checkbox_id = "#{element.html_id}_#{value}"
|
28
|
+
checkbox_attrs = element.input_html_attributes.merge(
|
29
|
+
value: value,
|
30
|
+
id: checkbox_id,
|
31
|
+
name: "#{element.html_name}[]",
|
32
|
+
checked: Array(element.value).include?(value)
|
33
|
+
)
|
34
|
+
|
35
|
+
input(**mix(checkbox_attrs, html_attributes))
|
36
|
+
label(**element.label_html_attributes, for: checkbox_id) { label_text }
|
37
|
+
end
|
38
|
+
else
|
39
|
+
input(name: element.html_name, type: "hidden", value: "0", autocomplete: "off")
|
40
|
+
input(**mix(element.input_html_attributes, html_attributes), type: "checkbox", value: "1")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_radio
|
45
|
+
element.class.select_options.each do |value, label_text|
|
46
|
+
label(**element.label_html_attributes) { label_text }
|
47
|
+
input(**mix(element.input_html_attributes, html_attributes), type: "radio", value: value,
|
48
|
+
checked: value == element.value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def render_select
|
53
|
+
select(**mix(element.input_html_attributes, html_attributes)) do
|
54
|
+
element.class.select_options.each do |value, label_text|
|
55
|
+
option(value: value, selected: option_selected?(value)) { label_text }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_textarea
|
61
|
+
textarea(**mix(element.input_html_attributes, html_attributes)) { element.value }
|
62
|
+
end
|
63
|
+
|
64
|
+
def option_selected?(value)
|
65
|
+
if element.class.input_options[:multiple]
|
66
|
+
Array(element.value).include?(value)
|
67
|
+
else
|
68
|
+
value == element.value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
module ActionForm
|
6
|
+
module Rails
|
7
|
+
# RailsForm class for ActionForm that handles Rails-specific form rendering.
|
8
|
+
# It integrates with Rails form helpers and provides a Rails-friendly interface
|
9
|
+
# for building forms.
|
10
|
+
class Base < ActionForm::Base
|
11
|
+
include ActionForm::Rails::Rendering
|
12
|
+
|
13
|
+
def self.subform_class
|
14
|
+
ActionForm::Rails::Subform
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :namespaced_model
|
18
|
+
|
19
|
+
def initialize(model: nil, scope: self.class.scope, params: nil, **html_options)
|
20
|
+
@namespaced_model = model
|
21
|
+
@object = model.is_a?(Array) ? Array(model).last : model
|
22
|
+
@scope = scope.nil? && @object.nil? ? nil : (scope || param_key)
|
23
|
+
super(object: @object, scope: @scope, params: params, **html_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def resource_model(model = nil)
|
28
|
+
return @resource_model unless model
|
29
|
+
|
30
|
+
@resource_model = model
|
31
|
+
@scope = model.model_name.param_key.to_sym
|
32
|
+
end
|
33
|
+
|
34
|
+
def params_definition(scope: self.scope)
|
35
|
+
return super unless scope
|
36
|
+
|
37
|
+
@params_definitions ||= Hash.new do |h, key|
|
38
|
+
h[key] = begin
|
39
|
+
klass = super
|
40
|
+
Class.new(params_class) { has scope, klass }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@params_definitions[scope]
|
44
|
+
end
|
45
|
+
|
46
|
+
def many(name, default: nil, &block)
|
47
|
+
super
|
48
|
+
elements[name].subform_definition.add_primary_key_element
|
49
|
+
elements[name].subform_definition.add_delete_element
|
50
|
+
end
|
51
|
+
|
52
|
+
def subform(name, default: nil, &block)
|
53
|
+
super
|
54
|
+
elements[name].add_primary_key_element
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def view_template
|
59
|
+
render_form do
|
60
|
+
render_elements
|
61
|
+
render_submit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_params(form_params)
|
66
|
+
self.class.new(model: @namespaced_model, scope: @scope, params: form_params, **html_options)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def subform_html_name(name, index: nil)
|
72
|
+
if index
|
73
|
+
@scope ? "#{@scope}[#{name}_attributes][#{index}]" : "[#{name}_attributes][#{index}]"
|
74
|
+
else
|
75
|
+
@scope ? "#{@scope}[#{name}_attributes]" : "#{name}_attributes"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def subform_value(name)
|
80
|
+
if @params
|
81
|
+
@params.send("#{name}_attributes")
|
82
|
+
else
|
83
|
+
@object.public_send(name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def model_name
|
88
|
+
@object&.model_name
|
89
|
+
end
|
90
|
+
|
91
|
+
def param_key
|
92
|
+
model_name.param_key.to_sym
|
93
|
+
end
|
94
|
+
|
95
|
+
def resource_action
|
96
|
+
return :search if @object.nil?
|
97
|
+
|
98
|
+
@object.persisted? ? :update : :create
|
99
|
+
end
|
100
|
+
|
101
|
+
def http_method
|
102
|
+
return "get" if @object.nil?
|
103
|
+
|
104
|
+
@object.persisted? ? "patch" : "post"
|
105
|
+
end
|
106
|
+
|
107
|
+
def html_action
|
108
|
+
html_options[:action] ||= helpers.polymorphic_path(@namespaced_model)
|
109
|
+
end
|
110
|
+
|
111
|
+
def html_method
|
112
|
+
html_options[:method] = html_options[:method].to_s.downcase == "get" ? "get" : "post"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
module Rails
|
5
|
+
# Rendering module for ActionForm Rails integration that provides form rendering functionality.
|
6
|
+
# Handles rendering of forms with error messages, authenticity tokens, UTF-8 encoding,
|
7
|
+
# and other Rails-specific form requirements. Also provides helper methods for rendering
|
8
|
+
# submit buttons and form elements.
|
9
|
+
module Rendering
|
10
|
+
def render_form(&block)
|
11
|
+
form(**{ method: html_method, action: html_action, "accept-charset" => "UTF-8" }, **@html_options) do
|
12
|
+
render_utf8_input
|
13
|
+
render_authenticity_token
|
14
|
+
render_method_input
|
15
|
+
yield if block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_authenticity_token
|
20
|
+
input(name: "authenticity_token", type: "hidden", value: helpers.form_authenticity_token)
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_method_input
|
24
|
+
input(name: "_method", type: "hidden", value: http_method, autocomplete: "off")
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_utf8_input
|
28
|
+
input(name: "utf8", type: "hidden", value: "✓", autocomplete: "off")
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_submit(**html_attributes)
|
32
|
+
input(name: "commit", type: "submit", value: submit_value, **html_attributes)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def submit_value
|
38
|
+
"#{resource_action.to_s.capitalize} #{model_name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
module Rails
|
5
|
+
# Subform class for ActionForm that handles nested form structures.
|
6
|
+
# It allows building forms within forms, supporting has_one and has_many relationships.
|
7
|
+
# Includes schema and element DSL functionality for defining form elements.
|
8
|
+
class Subform < ActionForm::Subform
|
9
|
+
class << self
|
10
|
+
def add_primary_key_element
|
11
|
+
return if elements.key?(:id)
|
12
|
+
|
13
|
+
element :id do
|
14
|
+
input(type: :hidden, autocomplete: :off)
|
15
|
+
output(type: :integer)
|
16
|
+
|
17
|
+
def render?
|
18
|
+
object.persisted? || (object.is_a?(EasyParams::Base) && !object.id.nil?)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_delete_element
|
24
|
+
element :_destroy do
|
25
|
+
input(type: :hidden, autocomplete: :off, value: "0")
|
26
|
+
output(type: :bool)
|
27
|
+
|
28
|
+
def render?
|
29
|
+
object.persisted? || (object.is_a?(EasyParams::Base) && !object._destroy.nil?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def detached?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def html_class
|
40
|
+
object.id.nil? ? "new_#{name}" : super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionForm
|
4
|
+
# Provides methods for rendering form elements and forms
|
5
|
+
module Rendering
|
6
|
+
def render_elements(elements = elements_instances) # rubocop:disable Metrics/MethodLength
|
7
|
+
elements.select(&:render?).each do |element|
|
8
|
+
element.helpers = helpers
|
9
|
+
if element.is_a?(SubformsCollection)
|
10
|
+
render_many_subforms(element)
|
11
|
+
elsif element.is_a?(Subform)
|
12
|
+
render_subform(element)
|
13
|
+
elsif element.input_type == :hidden
|
14
|
+
input(**element.input_html_attributes)
|
15
|
+
else
|
16
|
+
render_element(element)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_element(element)
|
22
|
+
render_label(element)
|
23
|
+
render_input(element)
|
24
|
+
render_inline_errors(element) if element.tags[:errors]
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_label(element)
|
28
|
+
return if hide_label?(element)
|
29
|
+
|
30
|
+
label(**element.label_html_attributes) { element.label_text }
|
31
|
+
end
|
32
|
+
|
33
|
+
def render_input(element, **html_attributes)
|
34
|
+
render Input.new(element, **html_attributes)
|
35
|
+
end
|
36
|
+
|
37
|
+
def render_inline_errors(element)
|
38
|
+
div(class: "error-messages") { element.errors_messages.join(", ") }
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_form(**html_attributes, &block)
|
42
|
+
form(**@html_options, **html_attributes, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_subform(subform)
|
46
|
+
render(subform)
|
47
|
+
end
|
48
|
+
|
49
|
+
def render_many_subforms(subforms)
|
50
|
+
render(subforms)
|
51
|
+
end
|
52
|
+
|
53
|
+
def render_submit(**html_attributes)
|
54
|
+
input(name: "commit", type: "submit", value: "Submit", **html_attributes)
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_remove_subform_button(**html_attributes, &block)
|
58
|
+
a(**html_attributes, onclick: safe("easyFormRemoveSubform(event)"), &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_new_subform_button(**html_attributes, &block)
|
62
|
+
a(**html_attributes, onclick: safe("easyFormAddSubform(event)"), &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def hide_label?(element)
|
68
|
+
return true unless element.class.label_options.first[:display]
|
69
|
+
|
70
|
+
element.input_type == :hidden ||
|
71
|
+
(%i[checkbox radio].include?(element.input_type) && element.class.select_options.any?)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|