phlexi-form 0.3.0.rc1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -8
- data/gemfiles/default.gemfile.lock +1 -1
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/lib/phlexi/form/base.rb +28 -36
- data/lib/phlexi/form/components/base.rb +2 -2
- data/lib/phlexi/form/components/collection_checkboxes.rb +1 -1
- data/lib/phlexi/form/components/collection_radio_buttons.rb +1 -1
- data/lib/phlexi/form/components/concerns/extracts_input.rb +53 -0
- data/lib/phlexi/form/components/concerns/handles_input.rb +4 -34
- data/lib/phlexi/form/components/concerns/submits_form.rb +8 -0
- data/lib/phlexi/form/components/error.rb +1 -1
- data/lib/phlexi/form/components/file_input.rb +1 -0
- data/lib/phlexi/form/components/hint.rb +1 -1
- data/lib/phlexi/form/components/input.rb +1 -5
- data/lib/phlexi/form/components/input_array.rb +1 -1
- data/lib/phlexi/form/components/select.rb +0 -3
- data/lib/phlexi/form/components/textarea.rb +2 -3
- data/lib/phlexi/form/field_options/associations.rb +2 -2
- data/lib/phlexi/form/field_options/collection.rb +8 -8
- data/lib/phlexi/form/field_options/errors.rb +7 -3
- data/lib/phlexi/form/field_options/hints.rb +5 -1
- data/lib/phlexi/form/field_options/inferred_types.rb +14 -10
- data/lib/phlexi/form/field_options/multiple.rb +1 -1
- data/lib/phlexi/form/field_options/required.rb +1 -1
- data/lib/phlexi/form/field_options/validators.rb +2 -2
- data/lib/phlexi/form/structure/dom.rb +2 -2
- data/lib/phlexi/form/structure/field_builder.rb +59 -52
- data/lib/phlexi/form/structure/field_collection.rb +7 -2
- data/lib/phlexi/form/structure/namespace.rb +19 -14
- data/lib/phlexi/form/structure/namespace_collection.rb +1 -1
- data/lib/phlexi/form/structure/node.rb +13 -3
- data/lib/phlexi/form/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d20cf0d6ccfcdf3cbb3a05a518b25a9b6176ef827e32f9cab47fddb43391f470
|
4
|
+
data.tar.gz: '0788efe146100a6629f679d913a080ff7cc2039881477bc9ffda16dfe0f6dc9f'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e671e22b8daa6e5e243725f79302e26b9b1d5395d92a54013d0aed981bc9eb2dfd756d8214a69446e0328186b339dc21dd3e1853c5236270f048e974c4ae114
|
7
|
+
data.tar.gz: d7441ae010f74f188baf43b6ac90e5cdd49b88563a22261e223b11d3c20de6135e5f610c0f278eaae28d49bc3627df3ec0b3012fe49d5e41626751d15200fd26
|
data/README.md
CHANGED
@@ -159,14 +159,16 @@ Phlexi::Form supports theming through a flexible theming system:
|
|
159
159
|
|
160
160
|
```ruby
|
161
161
|
class ThemedForm < Phlexi::Form::Base
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
162
|
+
class FieldBuilder < FieldBuilder
|
163
|
+
private
|
164
|
+
|
165
|
+
def default_theme
|
166
|
+
{
|
167
|
+
input: "border rounded px-2 py-1",
|
168
|
+
label: "font-bold text-gray-700",
|
169
|
+
# Add more theme options here
|
170
|
+
}
|
171
|
+
end
|
170
172
|
end
|
171
173
|
end
|
172
174
|
```
|
data/lib/phlexi/form/base.rb
CHANGED
@@ -10,7 +10,7 @@ module Phlexi
|
|
10
10
|
# A form component for building flexible and customizable forms.
|
11
11
|
#
|
12
12
|
# @example Basic usage
|
13
|
-
# Phlexi::Form
|
13
|
+
# Phlexi::Form(user, action: '/users', method: 'post') do
|
14
14
|
# render field(:name).placeholder("Name").input_tag
|
15
15
|
# render field(:email).placeholder("Email").input_tag
|
16
16
|
# end
|
@@ -22,14 +22,6 @@ module Phlexi
|
|
22
22
|
|
23
23
|
class FieldBuilder < Structure::FieldBuilder; end
|
24
24
|
|
25
|
-
class << self
|
26
|
-
def inherited(subclass)
|
27
|
-
subclass.const_set(:Namespace, Class.new(self::Namespace)) unless subclass.const_defined?(:Namespace)
|
28
|
-
subclass.const_set(:FieldBuilder, Class.new(self::FieldBuilder)) unless subclass.const_defined?(:FieldBuilder)
|
29
|
-
super
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
25
|
attr_reader :key, :object
|
34
26
|
|
35
27
|
delegate :field, :submit_button, :nest_one, :nest_many, to: :@namespace
|
@@ -73,18 +65,6 @@ module Phlexi
|
|
73
65
|
instance_exec(&@_content_block) if @_content_block
|
74
66
|
end
|
75
67
|
|
76
|
-
# Renders the form tag with its contents.
|
77
|
-
#
|
78
|
-
# @yield The form's content
|
79
|
-
# @return [void]
|
80
|
-
def form_tag(&)
|
81
|
-
form(**form_attributes) do
|
82
|
-
render_hidden_method_field
|
83
|
-
render_authenticity_token if authenticity_token?
|
84
|
-
yield
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
68
|
def extract_input(params)
|
89
69
|
call unless @_rendered
|
90
70
|
@namespace.extract_input(params)
|
@@ -110,10 +90,11 @@ module Phlexi
|
|
110
90
|
else
|
111
91
|
@object = record
|
112
92
|
if @key.nil?
|
113
|
-
|
114
|
-
|
93
|
+
@key = if object.respond_to?(:model_name) && object.model_name.respond_to?(:param_key) && object.model_name.param_key.present?
|
94
|
+
object.model_name.param_key
|
95
|
+
else
|
96
|
+
object.class.name.demodulize.underscore
|
115
97
|
end
|
116
|
-
@key = object.model_name.param_key
|
117
98
|
end
|
118
99
|
end
|
119
100
|
@key = @key.to_sym
|
@@ -133,6 +114,23 @@ module Phlexi
|
|
133
114
|
attributes.fetch(:accept_charset) { attributes[:accept_charset] = "UTF-8" }
|
134
115
|
end
|
135
116
|
|
117
|
+
# Retrieves the form's CSS classes.
|
118
|
+
#
|
119
|
+
# @return [String] The form's CSS classes
|
120
|
+
attr_reader :form_class
|
121
|
+
|
122
|
+
# Renders the form tag with its contents.
|
123
|
+
#
|
124
|
+
# @yield The form's content
|
125
|
+
# @return [void]
|
126
|
+
def form_tag(&)
|
127
|
+
form(**form_attributes) do
|
128
|
+
render_hidden_method_field
|
129
|
+
render_authenticity_token if has_authenticity_token?
|
130
|
+
yield
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
136
134
|
# Determines the form's action URL.
|
137
135
|
#
|
138
136
|
# @return [String, nil] The form's action URL
|
@@ -156,16 +154,11 @@ module Phlexi
|
|
156
154
|
ActiveSupport::StringInquirer.new(@form_method)
|
157
155
|
end
|
158
156
|
|
159
|
-
# Retrieves the form's CSS classes.
|
160
|
-
#
|
161
|
-
# @return [String] The form's CSS classes
|
162
|
-
attr_reader :form_class
|
163
|
-
|
164
157
|
# Checks if the authenticity token should be included.
|
165
158
|
#
|
166
159
|
# @return [Boolean] True if the authenticity token should be included, false otherwise
|
167
|
-
def
|
168
|
-
defined?(helpers) && options
|
160
|
+
def has_authenticity_token?
|
161
|
+
!form_method.get? && ((defined?(helpers) && helpers) || options[:authenticity_token])
|
169
162
|
end
|
170
163
|
|
171
164
|
# Retrieves the authenticity token.
|
@@ -221,13 +214,12 @@ module Phlexi
|
|
221
214
|
#
|
222
215
|
# @return [Hash] The form attributes
|
223
216
|
def form_attributes
|
224
|
-
{
|
217
|
+
mix({
|
225
218
|
id: @namespace.dom_id,
|
226
|
-
action: form_action,
|
227
|
-
method: standardized_form_method,
|
228
219
|
class: form_class,
|
229
|
-
|
230
|
-
|
220
|
+
action: form_action,
|
221
|
+
method: standardized_form_method
|
222
|
+
}, attributes)
|
231
223
|
end
|
232
224
|
|
233
225
|
# Renders the authenticity token if required.
|
@@ -23,15 +23,15 @@ module Phlexi
|
|
23
23
|
def append_attribute_classes
|
24
24
|
return if attributes[:class] == false
|
25
25
|
|
26
|
-
|
26
|
+
default_classes = tokens(
|
27
27
|
component_name,
|
28
|
-
attributes[:class],
|
29
28
|
-> { attributes[:required] } => "required",
|
30
29
|
-> { !attributes[:required] } => "optional",
|
31
30
|
-> { field.has_errors? } => "invalid",
|
32
31
|
-> { attributes[:readonly] } => "readonly",
|
33
32
|
-> { attributes[:disabled] } => "disabled"
|
34
33
|
)
|
34
|
+
attributes[:class] = tokens(default_classes, attributes[:class])
|
35
35
|
end
|
36
36
|
|
37
37
|
def component_name
|
@@ -10,7 +10,7 @@ module Phlexi
|
|
10
10
|
|
11
11
|
def view_template
|
12
12
|
div(**attributes.slice(:id, :class)) do
|
13
|
-
field.
|
13
|
+
field.repeated(option_mapper.values) do |builder|
|
14
14
|
render builder.hidden_field_tag if builder.index == 0
|
15
15
|
|
16
16
|
field = builder.field(
|
@@ -9,7 +9,7 @@ module Phlexi
|
|
9
9
|
|
10
10
|
def view_template
|
11
11
|
div(**attributes.slice(:id, :class)) do
|
12
|
-
field.
|
12
|
+
field.repeated(option_mapper.values) do |builder|
|
13
13
|
render builder.hidden_field_tag if builder.index == 0
|
14
14
|
|
15
15
|
field = builder.field(
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Components
|
6
|
+
module Concerns
|
7
|
+
module ExtractsInput
|
8
|
+
# Collects parameters matching the input field's param key.
|
9
|
+
#
|
10
|
+
# @param params [Hash] the parameters to collect from.
|
11
|
+
# @return [Hash] the collected parameters.
|
12
|
+
def extract_input(params)
|
13
|
+
# # Handles multi parameter attributes
|
14
|
+
# # https://www.cookieshq.co.uk/posts/rails-spelunking-date-select
|
15
|
+
# # https://www.cookieshq.co.uk/posts/multiparameter-attributes
|
16
|
+
|
17
|
+
# # Matches
|
18
|
+
# # - parameter
|
19
|
+
# # - parameter(1)
|
20
|
+
# # - parameter(2)
|
21
|
+
# # - parameter(1i)
|
22
|
+
# # - parameter(2f)
|
23
|
+
# regex = /^#{param}(\(\d+[if]?\))?$/
|
24
|
+
# keys = params.select { |key, _| regex.match?(key) }.keys
|
25
|
+
# params.slice(*keys)
|
26
|
+
|
27
|
+
params ||= {}
|
28
|
+
{input_param => normalize_input(params[field.key])}
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def build_attributes
|
34
|
+
super
|
35
|
+
@input_param = attributes.delete(:input_param) || field.key
|
36
|
+
end
|
37
|
+
|
38
|
+
def input_param
|
39
|
+
@input_param
|
40
|
+
end
|
41
|
+
|
42
|
+
def normalize_input(input_value)
|
43
|
+
normalize_simple_input(input_value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def normalize_simple_input(input_value)
|
47
|
+
input_value.to_s.presence
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -5,46 +5,16 @@ module Phlexi
|
|
5
5
|
module Components
|
6
6
|
module Concerns
|
7
7
|
module HandlesInput
|
8
|
-
|
9
|
-
#
|
10
|
-
# @param params [Hash] the parameters to collect from.
|
11
|
-
# @return [Hash] the collected parameters.
|
12
|
-
def extract_input(params)
|
13
|
-
# # Handles multi parameter attributes
|
14
|
-
# # https://www.cookieshq.co.uk/posts/rails-spelunking-date-select
|
15
|
-
# # https://www.cookieshq.co.uk/posts/multiparameter-attributes
|
16
|
-
|
17
|
-
# # Matches
|
18
|
-
# # - parameter
|
19
|
-
# # - parameter(1)
|
20
|
-
# # - parameter(2)
|
21
|
-
# # - parameter(1i)
|
22
|
-
# # - parameter(2f)
|
23
|
-
# regex = /^#{param}(\(\d+[if]?\))?$/
|
24
|
-
# keys = params.select { |key, _| regex.match?(key) }.keys
|
25
|
-
# params.slice(*keys)
|
26
|
-
|
27
|
-
params ||= {}
|
28
|
-
{input_param => normalize_input(params[field.key])}
|
29
|
-
end
|
8
|
+
include Phlexi::Form::Components::Concerns::ExtractsInput
|
30
9
|
|
31
10
|
protected
|
32
11
|
|
33
12
|
def build_attributes
|
34
13
|
super
|
35
|
-
@input_param = attributes.delete(:input_param) || field.key
|
36
|
-
end
|
37
|
-
|
38
|
-
def input_param
|
39
|
-
@input_param
|
40
|
-
end
|
41
|
-
|
42
|
-
def normalize_input(input_value)
|
43
|
-
normalize_simple_input(input_value)
|
44
|
-
end
|
45
14
|
|
46
|
-
|
47
|
-
|
15
|
+
# only overwrite id if it was set in Base
|
16
|
+
attributes[:id] = field.dom.id if attributes[:id] == "#{field.dom.id}_#{component_name}"
|
17
|
+
attributes[:name] = field.dom.name
|
48
18
|
end
|
49
19
|
end
|
50
20
|
end
|
@@ -5,6 +5,14 @@ module Phlexi
|
|
5
5
|
module Components
|
6
6
|
module Concerns
|
7
7
|
module SubmitsForm
|
8
|
+
include Phlexi::Form::Components::Concerns::ExtractsInput
|
9
|
+
|
10
|
+
def extract_input(params)
|
11
|
+
{}
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
8
16
|
def submit_type_value
|
9
17
|
if field.object.respond_to?(:persisted?)
|
10
18
|
field.object.persisted? ? :update : :create
|
@@ -15,17 +15,13 @@ module Phlexi
|
|
15
15
|
def build_attributes
|
16
16
|
super
|
17
17
|
|
18
|
-
# only overwrite id if it was set in Base
|
19
|
-
attributes[:id] = field.dom.id if attributes[:id] == "#{field.dom.id}_#{component_name}"
|
20
|
-
|
21
|
-
attributes[:name] = field.dom.name
|
22
18
|
attributes[:value] = field.dom.value
|
23
19
|
|
24
20
|
build_input_attributes
|
25
21
|
end
|
26
22
|
|
27
23
|
def build_input_attributes
|
28
|
-
attributes.fetch(:type) { attributes[:type] = field.
|
24
|
+
attributes.fetch(:type) { attributes[:type] = field.inferred_input_component_subtype }
|
29
25
|
attributes.fetch(:disabled) { attributes[:disabled] = field.disabled? }
|
30
26
|
|
31
27
|
case attributes[:type]
|
@@ -4,6 +4,8 @@ module Phlexi
|
|
4
4
|
module Form
|
5
5
|
module Components
|
6
6
|
class Textarea < Base
|
7
|
+
include Concerns::HandlesInput
|
8
|
+
|
7
9
|
def view_template
|
8
10
|
textarea(**attributes) { field.dom.value }
|
9
11
|
end
|
@@ -13,9 +15,6 @@ module Phlexi
|
|
13
15
|
def build_attributes
|
14
16
|
super
|
15
17
|
|
16
|
-
attributes[:id] = field.dom.id
|
17
|
-
attributes[:name] = field.dom.name
|
18
|
-
|
19
18
|
build_textarea_attributes
|
20
19
|
end
|
21
20
|
|
@@ -20,19 +20,19 @@ module Phlexi
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def collection_value_from_association
|
23
|
-
return unless
|
23
|
+
return unless association_reflection
|
24
24
|
|
25
|
-
relation =
|
25
|
+
relation = association_reflection.klass.all
|
26
26
|
|
27
|
-
if
|
28
|
-
relation = if
|
29
|
-
|
27
|
+
if association_reflection.respond_to?(:scope) && association_reflection.scope
|
28
|
+
relation = if association_reflection.scope.parameters.any?
|
29
|
+
association_reflection.klass.instance_exec(object, &association_reflection.scope)
|
30
30
|
else
|
31
|
-
|
31
|
+
association_reflection.klass.instance_exec(&association_reflection.scope)
|
32
32
|
end
|
33
33
|
else
|
34
|
-
order =
|
35
|
-
conditions =
|
34
|
+
order = association_reflection.options[:order]
|
35
|
+
conditions = association_reflection.options[:conditions]
|
36
36
|
conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
|
37
37
|
|
38
38
|
relation = relation.where(conditions) if relation.respond_to?(:where) && conditions.present?
|
@@ -21,10 +21,14 @@ module Phlexi
|
|
21
21
|
object_with_errors? || !object && has_custom_error?
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def can_show_errors?
|
25
25
|
options[:error] != false
|
26
26
|
end
|
27
27
|
|
28
|
+
def show_errors?
|
29
|
+
can_show_errors? && has_errors?
|
30
|
+
end
|
31
|
+
|
28
32
|
def valid?
|
29
33
|
!has_errors? && has_value?
|
30
34
|
end
|
@@ -66,11 +70,11 @@ module Phlexi
|
|
66
70
|
end
|
67
71
|
|
68
72
|
def errors_on_association
|
69
|
-
|
73
|
+
association_reflection ? object.errors[association_reflection.name] : []
|
70
74
|
end
|
71
75
|
|
72
76
|
def full_errors_on_association
|
73
|
-
|
77
|
+
association_reflection ? object.errors.full_messages_for(association_reflection.name) : []
|
74
78
|
end
|
75
79
|
|
76
80
|
def has_custom_error?
|
@@ -10,20 +10,22 @@ module Phlexi
|
|
10
10
|
@inferred_db_type ||= infer_db_type
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
# This will give you the component type e.g. input, textarea, select
|
14
|
+
def inferred_component_type
|
15
|
+
@inferred_component_type ||= infer_component_type
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
# This will give you the subtype of the input component e.g input type="text|date|checkbox" etc
|
19
|
+
def inferred_input_component_subtype
|
20
|
+
@inferred_input_component_subtype ||= infer_input_component_subtype(inferred_component_type)
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
25
|
# this returns the element type
|
24
|
-
# one of :input, :textarea, :select
|
25
|
-
def
|
26
|
-
return :select unless collection.
|
26
|
+
# one of :input, :textarea, :select
|
27
|
+
def infer_component_type
|
28
|
+
return :select unless collection.nil?
|
27
29
|
|
28
30
|
case inferred_db_type
|
29
31
|
when :text, :json, :jsonb, :hstore
|
@@ -35,7 +37,7 @@ module Phlexi
|
|
35
37
|
|
36
38
|
# this only applies when input_component is `:input`
|
37
39
|
# resolves the type attribute of input components
|
38
|
-
def
|
40
|
+
def infer_input_component_subtype(component)
|
39
41
|
case inferred_db_type
|
40
42
|
when :string
|
41
43
|
infer_string_input_type(key)
|
@@ -64,7 +66,7 @@ module Phlexi
|
|
64
66
|
if object.class.respond_to?(:attribute_types)
|
65
67
|
# ActiveModel::Attributes
|
66
68
|
custom_type = object.class.attribute_types[key.to_s]
|
67
|
-
return custom_type.type if custom_type
|
69
|
+
return custom_type.type if custom_type&.type
|
68
70
|
end
|
69
71
|
|
70
72
|
# Check if object responds to the key
|
@@ -81,8 +83,10 @@ module Phlexi
|
|
81
83
|
case value
|
82
84
|
when Integer
|
83
85
|
:integer
|
84
|
-
when Float
|
86
|
+
when Float
|
85
87
|
:float
|
88
|
+
when BigDecimal
|
89
|
+
:decimal
|
86
90
|
when TrueClass, FalseClass
|
87
91
|
:boolean
|
88
92
|
when Date
|
@@ -16,7 +16,7 @@ module Phlexi
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def calculate_multiple_field_value
|
19
|
-
return true if
|
19
|
+
return true if association_reflection&.macro == :has_many
|
20
20
|
return true if multiple_field_array_attribute?
|
21
21
|
|
22
22
|
check_multiple_field_from_validators
|
@@ -24,7 +24,7 @@ module Phlexi
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def required_by_validators?
|
27
|
-
(attribute_validators +
|
27
|
+
(attribute_validators + association_reflection_validators).any? { |v| v.kind == :presence && valid_validator?(v) }
|
28
28
|
end
|
29
29
|
|
30
30
|
def required_by_default?
|
@@ -14,8 +14,8 @@ module Phlexi
|
|
14
14
|
object.class.validators_on(key)
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
17
|
+
def association_reflection_validators
|
18
|
+
association_reflection ? object.class.validators_on(association_reflection.name) : []
|
19
19
|
end
|
20
20
|
|
21
21
|
def valid_validator?(validator)
|
@@ -5,7 +5,7 @@ module Phlexi
|
|
5
5
|
module Structure
|
6
6
|
# Generates DOM IDs, names, etc. for a Field, Namespace, or Node based on
|
7
7
|
# norms that were established by Rails. These can be used outsidef or Rails in
|
8
|
-
# other Ruby web frameworks since it has
|
8
|
+
# other Ruby web frameworks since it has no dependencies on Rails.
|
9
9
|
class DOM
|
10
10
|
def initialize(field:)
|
11
11
|
@field = field
|
@@ -17,7 +17,7 @@ module Phlexi
|
|
17
17
|
@field.value.to_s
|
18
18
|
end
|
19
19
|
|
20
|
-
# Walks from the current node to the parent node, grabs the names, and
|
20
|
+
# Walks from the current node to the parent node, grabs the names, and separates
|
21
21
|
# them with a `_` for a DOM ID.
|
22
22
|
def id
|
23
23
|
@id ||= begin
|
@@ -57,28 +57,28 @@ module Phlexi
|
|
57
57
|
#
|
58
58
|
# @param attributes [Hash] Additional attributes for the label.
|
59
59
|
# @return [Components::Label] The label component.
|
60
|
-
def label_tag(
|
61
|
-
create_component(Components::Label, :label,
|
60
|
+
def label_tag(**, &)
|
61
|
+
create_component(Components::Label, :label, **, &)
|
62
62
|
end
|
63
63
|
|
64
64
|
# Creates an input tag for the field.
|
65
65
|
#
|
66
66
|
# @param attributes [Hash] Additional attributes for the input.
|
67
67
|
# @return [Components::Input] The input component.
|
68
|
-
def input_tag(
|
69
|
-
create_component(Components::Input, :input,
|
68
|
+
def input_tag(**, &)
|
69
|
+
create_component(Components::Input, :input, **, &)
|
70
70
|
end
|
71
71
|
|
72
|
-
def file_input_tag(
|
73
|
-
create_component(Components::FileInput, :file,
|
72
|
+
def file_input_tag(**, &)
|
73
|
+
create_component(Components::FileInput, :file, **, &)
|
74
74
|
end
|
75
75
|
|
76
76
|
# Creates a checkbox tag for the field.
|
77
77
|
#
|
78
78
|
# @param attributes [Hash] Additional attributes for the checkbox.
|
79
79
|
# @return [Components::Checkbox] The checkbox component.
|
80
|
-
def checkbox_tag(
|
81
|
-
create_component(Components::Checkbox, :checkbox,
|
80
|
+
def checkbox_tag(**, &)
|
81
|
+
create_component(Components::Checkbox, :checkbox, **, &)
|
82
82
|
end
|
83
83
|
|
84
84
|
# Creates collection checkboxes for the field.
|
@@ -86,16 +86,16 @@ module Phlexi
|
|
86
86
|
# @param attributes [Hash] Additional attributes for the collection checkboxes.
|
87
87
|
# @yield [block] The block to be executed for each checkbox.
|
88
88
|
# @return [Components::CollectionCheckboxes] The collection checkboxes component.
|
89
|
-
def collection_checkboxes_tag(
|
90
|
-
create_component(Components::CollectionCheckboxes, :collection_checkboxes,
|
89
|
+
def collection_checkboxes_tag(**, &)
|
90
|
+
create_component(Components::CollectionCheckboxes, :collection_checkboxes, **, &)
|
91
91
|
end
|
92
92
|
|
93
93
|
# Creates a radio button tag for the field.
|
94
94
|
#
|
95
95
|
# @param attributes [Hash] Additional attributes for the radio button.
|
96
96
|
# @return [Components::RadioButton] The radio button component.
|
97
|
-
def radio_button_tag(
|
98
|
-
create_component(Components::RadioButton, :radio,
|
97
|
+
def radio_button_tag(**, &)
|
98
|
+
create_component(Components::RadioButton, :radio, **, &)
|
99
99
|
end
|
100
100
|
|
101
101
|
# Creates collection radio buttons for the field.
|
@@ -103,52 +103,52 @@ module Phlexi
|
|
103
103
|
# @param attributes [Hash] Additional attributes for the collection radio buttons.
|
104
104
|
# @yield [block] The block to be executed for each radio button.
|
105
105
|
# @return [Components::CollectionRadioButtons] The collection radio buttons component.
|
106
|
-
def collection_radio_buttons_tag(
|
107
|
-
create_component(Components::CollectionRadioButtons, :collection_radio_buttons,
|
106
|
+
def collection_radio_buttons_tag(**, &)
|
107
|
+
create_component(Components::CollectionRadioButtons, :collection_radio_buttons, **, &)
|
108
108
|
end
|
109
109
|
|
110
110
|
# Creates a textarea tag for the field.
|
111
111
|
#
|
112
112
|
# @param attributes [Hash] Additional attributes for the textarea.
|
113
113
|
# @return [Components::Textarea] The textarea component.
|
114
|
-
def textarea_tag(
|
115
|
-
create_component(Components::Textarea, :textarea,
|
114
|
+
def textarea_tag(**, &)
|
115
|
+
create_component(Components::Textarea, :textarea, **, &)
|
116
116
|
end
|
117
117
|
|
118
118
|
# Creates a select tag for the field.
|
119
119
|
#
|
120
120
|
# @param attributes [Hash] Additional attributes for the select.
|
121
121
|
# @return [Components::Select] The select component.
|
122
|
-
def select_tag(
|
123
|
-
create_component(Components::Select, :select,
|
122
|
+
def select_tag(**, &)
|
123
|
+
create_component(Components::Select, :select, **, &)
|
124
124
|
end
|
125
125
|
|
126
|
-
def input_array_tag(
|
127
|
-
create_component(Components::InputArray, :array,
|
126
|
+
def input_array_tag(**, &)
|
127
|
+
create_component(Components::InputArray, :array, **, &)
|
128
128
|
end
|
129
129
|
|
130
130
|
# Creates a hint tag for the field.
|
131
131
|
#
|
132
132
|
# @param attributes [Hash] Additional attributes for the hint.
|
133
133
|
# @return [Components::Hint] The hint component.
|
134
|
-
def hint_tag(
|
135
|
-
create_component(Components::Hint, :hint,
|
134
|
+
def hint_tag(**, &)
|
135
|
+
create_component(Components::Hint, :hint, **, &)
|
136
136
|
end
|
137
137
|
|
138
138
|
# Creates an error tag for the field.
|
139
139
|
#
|
140
140
|
# @param attributes [Hash] Additional attributes for the error.
|
141
141
|
# @return [Components::Error] The error component.
|
142
|
-
def error_tag(
|
143
|
-
create_component(Components::Error, :error,
|
142
|
+
def error_tag(**, &)
|
143
|
+
create_component(Components::Error, :error, **, &)
|
144
144
|
end
|
145
145
|
|
146
146
|
# Creates a full error tag for the field.
|
147
147
|
#
|
148
148
|
# @param attributes [Hash] Additional attributes for the full error.
|
149
149
|
# @return [Components::FullError] The full error component.
|
150
|
-
def full_error_tag(
|
151
|
-
create_component(Components::FullError, :full_error,
|
150
|
+
def full_error_tag(**, &)
|
151
|
+
create_component(Components::FullError, :full_error, **, &)
|
152
152
|
end
|
153
153
|
|
154
154
|
# Wraps the field with additional markup.
|
@@ -158,17 +158,18 @@ module Phlexi
|
|
158
158
|
# @yield [block] The block to be executed within the wrapper.
|
159
159
|
# @return [Components::Wrapper] The wrapper component.
|
160
160
|
def wrapped(inner: {}, **attributes, &)
|
161
|
-
|
162
|
-
inner
|
163
|
-
Components::Wrapper.new(self,
|
161
|
+
attributes = apply_component_theme(attributes, :wrapper)
|
162
|
+
inner = apply_component_theme(inner, :inner_wrapper)
|
163
|
+
Components::Wrapper.new(self, inner: inner, **attributes, &)
|
164
164
|
end
|
165
165
|
|
166
|
-
# Creates a
|
166
|
+
# Creates a repeated field collection.
|
167
167
|
#
|
168
|
-
# @param range [Integer, #to_a] The range of keys for each field.
|
168
|
+
# @param range [Integer, #to_a] The range of keys for each field.
|
169
|
+
# If an integer (e.g. 6) is passed, it is converted to a range = 1..6
|
169
170
|
# @yield [block] The block to be executed for each item in the collection.
|
170
171
|
# @return [FieldCollection] The field collection.
|
171
|
-
def
|
172
|
+
def repeated(range = nil, &)
|
172
173
|
FieldCollection.new(field: self, range: range, &)
|
173
174
|
end
|
174
175
|
|
@@ -176,31 +177,37 @@ module Phlexi
|
|
176
177
|
#
|
177
178
|
# @param attributes [Hash] Additional attributes for the submit.
|
178
179
|
# @return [Components::SubmitButton] The submit button component.
|
179
|
-
def submit_button_tag(
|
180
|
-
create_component(Components::SubmitButton, :submit_button,
|
180
|
+
def submit_button_tag(**, &)
|
181
|
+
create_component(Components::SubmitButton, :submit_button, **, &)
|
181
182
|
end
|
182
183
|
|
183
184
|
def extract_input(params)
|
184
|
-
raise "field##{dom.name} did not define an input component" unless @
|
185
|
+
raise "field##{dom.name} did not define an input component" unless @field_input_extractor
|
185
186
|
|
186
|
-
@
|
187
|
+
@field_input_extractor.extract_input(params)
|
187
188
|
end
|
188
189
|
|
189
190
|
protected
|
190
191
|
|
191
|
-
def create_component(component_class, theme_key, **attributes)
|
192
|
-
if component_class.include?(Phlexi::Form::Components::Concerns::HandlesInput)
|
193
|
-
|
192
|
+
def create_component(component_class, theme_key, **attributes, &)
|
193
|
+
attributes = mix(input_attributes, attributes) if component_class.include?(Phlexi::Form::Components::Concerns::HandlesInput)
|
194
|
+
component = component_class.new(self, **apply_component_theme(attributes, theme_key), &)
|
195
|
+
if component_class.include?(Components::Concerns::ExtractsInput)
|
196
|
+
raise "input component already defined: #{@field_input_extractor.inspect}" if @field_input_extractor
|
194
197
|
|
195
|
-
|
196
|
-
@field_input_component = component_class.new(self, class: component_class_for(theme_key, attributes), **attributes)
|
197
|
-
else
|
198
|
-
component_class.new(self, class: component_class_for(theme_key, attributes), **attributes)
|
198
|
+
@field_input_extractor = component
|
199
199
|
end
|
200
|
+
|
201
|
+
component
|
200
202
|
end
|
201
203
|
|
202
|
-
def
|
203
|
-
|
204
|
+
def apply_component_theme(attributes, theme_key)
|
205
|
+
theme_key = attributes.delete(:theme) || theme_key
|
206
|
+
if attributes.key?(:class!)
|
207
|
+
attributes
|
208
|
+
else
|
209
|
+
mix({class: themed(theme_key)}, attributes)
|
210
|
+
end
|
204
211
|
end
|
205
212
|
|
206
213
|
def has_value?
|
@@ -210,24 +217,24 @@ module Phlexi
|
|
210
217
|
def determine_initial_value(value)
|
211
218
|
return value unless value == NIL_VALUE
|
212
219
|
|
213
|
-
|
220
|
+
determine_value_from_association || determine_value_from_object
|
214
221
|
end
|
215
222
|
|
216
223
|
def determine_value_from_object
|
217
224
|
object.respond_to?(key) ? object.public_send(key) : nil
|
218
225
|
end
|
219
226
|
|
220
|
-
def
|
221
|
-
return nil unless
|
227
|
+
def determine_value_from_association
|
228
|
+
return nil unless association_reflection.present?
|
222
229
|
|
223
230
|
value = object.public_send(key)
|
224
|
-
case
|
231
|
+
case association_reflection.macro
|
225
232
|
when :has_many, :has_and_belongs_to_many
|
226
|
-
value&.map { |v| v.public_send(
|
233
|
+
value&.map { |v| v.public_send(association_reflection.klass.primary_key) }
|
227
234
|
when :belongs_to, :has_one
|
228
|
-
value&.public_send(
|
235
|
+
value&.public_send(association_reflection.klass.primary_key)
|
229
236
|
else
|
230
|
-
raise ArgumentError, "Unsupported association type: #{
|
237
|
+
raise ArgumentError, "Unsupported association type: #{association_reflection.macro}"
|
231
238
|
end
|
232
239
|
end
|
233
240
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "phlex"
|
4
|
+
|
3
5
|
module Phlexi
|
4
6
|
module Form
|
5
7
|
module Structure
|
@@ -7,6 +9,8 @@ module Phlexi
|
|
7
9
|
include Enumerable
|
8
10
|
|
9
11
|
class Builder
|
12
|
+
include Phlex::Helpers
|
13
|
+
|
10
14
|
attr_reader :key, :index
|
11
15
|
|
12
16
|
def initialize(key, field, index)
|
@@ -15,8 +19,9 @@ module Phlexi
|
|
15
19
|
@index = index
|
16
20
|
end
|
17
21
|
|
18
|
-
def field(**)
|
19
|
-
|
22
|
+
def field(**options)
|
23
|
+
options = mix({input_attributes: @field.input_attributes}, options)
|
24
|
+
@field.class.new(key, **options, parent: @field).tap do |field|
|
20
25
|
yield field if block_given?
|
21
26
|
end
|
22
27
|
end
|
@@ -3,13 +3,18 @@
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
5
|
module Structure
|
6
|
-
# A Namespace maps
|
6
|
+
# A Namespace maps an object to values, but doesn't actually have a value itself. For
|
7
7
|
# example, a `User` object or ActiveRecord model could be passed into the `:user` namespace.
|
8
|
-
# To access the values on a Namespace, the `field` can be called for single values.
|
9
8
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
9
|
+
# To access single values on a Namespace, #field can be used.
|
10
|
+
#
|
11
|
+
# To access nested objects within a namespace, two methods are available:
|
12
|
+
#
|
13
|
+
# 1. #nest_one: Used for single nested objects, such as if a `User belongs_to :profile` in
|
14
|
+
# ActiveRecord. This method returns another Namespace object.
|
15
|
+
#
|
16
|
+
# 2. #nest_many: Used for collections of nested objects, such as if a `User has_many :addresses` in
|
17
|
+
# ActiveRecord. This method returns a NamespaceCollection object.
|
13
18
|
class Namespace < Structure::Node
|
14
19
|
include Enumerable
|
15
20
|
|
@@ -29,8 +34,8 @@ module Phlexi
|
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
|
-
def submit_button(key =
|
33
|
-
field(key
|
37
|
+
def submit_button(key = :submit_button, **, &)
|
38
|
+
field(key).submit_button_tag(**, &)
|
34
39
|
end
|
35
40
|
|
36
41
|
# Creates a `Namespace` child instance with the parent set to the current instance, adds to
|
@@ -41,9 +46,9 @@ module Phlexi
|
|
41
46
|
# form like this:
|
42
47
|
#
|
43
48
|
# ```ruby
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
49
|
+
# Phlexi::Form(User.new, as: :user) do
|
50
|
+
# nest_one :profile do |profile|
|
51
|
+
# render profile.field(:gender).input_tag
|
47
52
|
# end
|
48
53
|
# end
|
49
54
|
# ```
|
@@ -56,10 +61,10 @@ module Phlexi
|
|
56
61
|
# an enumerable or array of `Address` classes:
|
57
62
|
#
|
58
63
|
# ```ruby
|
59
|
-
# Phlexi::Form
|
60
|
-
# render
|
61
|
-
# render
|
62
|
-
#
|
64
|
+
# Phlexi::Form(User.new) do
|
65
|
+
# render field(:email).input_tag
|
66
|
+
# render field(:name).input_tag
|
67
|
+
# nest_many :addresses do |address|
|
63
68
|
# render address.field(:street).input_tag
|
64
69
|
# render address.field(:state).input_tag
|
65
70
|
# render address.field(:zip).input_tag
|
@@ -32,7 +32,7 @@ module Phlexi
|
|
32
32
|
|
33
33
|
# Builds and memoizes namespaces for the collection.
|
34
34
|
#
|
35
|
-
# @return [Array<
|
35
|
+
# @return [Array<Namespace>] An array of namespace objects.
|
36
36
|
def namespaces
|
37
37
|
@namespaces ||= @collection.map.with_index do |object, key|
|
38
38
|
build_namespace(key, object: object)
|
@@ -3,15 +3,25 @@
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
5
|
module Structure
|
6
|
-
# Superclass for Namespace and Field classes.
|
7
|
-
#
|
6
|
+
# Superclass for Namespace and Field classes. Represents a node in the form tree structure.
|
7
|
+
#
|
8
|
+
# @attr_reader [Symbol] key The node's key
|
9
|
+
# @attr_reader [Node, nil] parent The node's parent in the tree structure
|
8
10
|
class Node
|
9
11
|
attr_reader :key, :parent
|
10
12
|
|
13
|
+
# Initializes a new Node instance.
|
14
|
+
#
|
15
|
+
# @param key [Symbol, String] The key for the node
|
16
|
+
# @param parent [Node, nil] The parent node
|
11
17
|
def initialize(key, parent:)
|
12
|
-
@key = key
|
18
|
+
@key = :"#{key}"
|
13
19
|
@parent = parent
|
14
20
|
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"<#{self.class.name} key=#{key.inspect} parent=#{id.inspect} />"
|
24
|
+
end
|
15
25
|
end
|
16
26
|
end
|
17
27
|
end
|
data/lib/phlexi/form/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phlexi-form
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.0
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -194,6 +194,7 @@ files:
|
|
194
194
|
- lib/phlexi/form/components/checkbox.rb
|
195
195
|
- lib/phlexi/form/components/collection_checkboxes.rb
|
196
196
|
- lib/phlexi/form/components/collection_radio_buttons.rb
|
197
|
+
- lib/phlexi/form/components/concerns/extracts_input.rb
|
197
198
|
- lib/phlexi/form/components/concerns/handles_array_input.rb
|
198
199
|
- lib/phlexi/form/components/concerns/handles_input.rb
|
199
200
|
- lib/phlexi/form/components/concerns/has_options.rb
|
@@ -260,7 +261,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
260
261
|
- !ruby/object:Gem::Version
|
261
262
|
version: '0'
|
262
263
|
requirements: []
|
263
|
-
rubygems_version: 3.
|
264
|
+
rubygems_version: 3.4.10
|
264
265
|
signing_key:
|
265
266
|
specification_version: 4
|
266
267
|
summary: Build forms in Rails
|