phlexi-form 0.3.0.rc1 → 0.3.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 +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
|