phlexi-form 0.3.0 → 0.4.1
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 +1 -1
- data/gemfiles/default.gemfile.lock +34 -32
- data/gemfiles/rails_7.gemfile.lock +16 -18
- data/lib/phlexi/form/base.rb +18 -9
- data/lib/phlexi/form/builder.rb +297 -0
- data/lib/phlexi/form/components/base.rb +1 -1
- data/lib/phlexi/form/components/input.rb +16 -2
- data/lib/phlexi/form/components/select.rb +4 -0
- data/lib/phlexi/form/html.rb +18 -0
- data/lib/phlexi/form/{field_options → options}/autofocus.rb +1 -1
- data/lib/phlexi/form/{field_options → options}/collection.rb +6 -2
- data/lib/phlexi/form/{field_options → options}/disabled.rb +1 -1
- data/lib/phlexi/form/{field_options → options}/errors.rb +11 -11
- data/lib/phlexi/form/options/hints.rb +13 -0
- data/lib/phlexi/form/options/inferred_types.rb +32 -0
- data/lib/phlexi/form/{field_options → options}/length.rb +3 -3
- data/lib/phlexi/form/{field_options → options}/limit.rb +2 -2
- data/lib/phlexi/form/options/max.rb +55 -0
- data/lib/phlexi/form/options/min.rb +55 -0
- data/lib/phlexi/form/{field_options → options}/pattern.rb +2 -2
- data/lib/phlexi/form/{field_options → options}/readonly.rb +1 -1
- data/lib/phlexi/form/{field_options → options}/required.rb +2 -2
- data/lib/phlexi/form/options/step.rb +39 -0
- data/lib/phlexi/form/options/validators.rb +24 -0
- data/lib/phlexi/form/structure/field_collection.rb +9 -29
- data/lib/phlexi/form/structure/namespace.rb +2 -114
- data/lib/phlexi/form/structure/namespace_collection.rb +1 -32
- data/lib/phlexi/form/theme.rb +160 -0
- data/lib/phlexi/form/version.rb +1 -1
- data/lib/phlexi/form.rb +3 -6
- metadata +34 -23
- data/lib/phlexi/form/field_options/associations.rb +0 -21
- data/lib/phlexi/form/field_options/hints.rb +0 -26
- data/lib/phlexi/form/field_options/inferred_types.rb +0 -159
- data/lib/phlexi/form/field_options/labels.rb +0 -28
- data/lib/phlexi/form/field_options/min_max.rb +0 -92
- data/lib/phlexi/form/field_options/multiple.rb +0 -65
- data/lib/phlexi/form/field_options/placeholder.rb +0 -18
- data/lib/phlexi/form/field_options/themes.rb +0 -207
- data/lib/phlexi/form/field_options/validators.rb +0 -48
- data/lib/phlexi/form/structure/dom.rb +0 -62
- data/lib/phlexi/form/structure/field_builder.rb +0 -243
- data/lib/phlexi/form/structure/node.rb +0 -28
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Form
|
5
|
-
module FieldOptions
|
6
|
-
module Multiple
|
7
|
-
def multiple?
|
8
|
-
options[:multiple] = options.fetch(:multiple) { calculate_multiple_field_value }
|
9
|
-
end
|
10
|
-
|
11
|
-
def multiple!(multiple = true)
|
12
|
-
options[:multiple] = multiple
|
13
|
-
self
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def calculate_multiple_field_value
|
19
|
-
return true if association_reflection&.macro == :has_many
|
20
|
-
return true if multiple_field_array_attribute?
|
21
|
-
|
22
|
-
check_multiple_field_from_validators
|
23
|
-
end
|
24
|
-
|
25
|
-
def multiple_field_array_attribute?
|
26
|
-
return false unless object.class.respond_to?(:columns_hash)
|
27
|
-
|
28
|
-
column = object.class.columns_hash[key.to_s]
|
29
|
-
return false unless column
|
30
|
-
|
31
|
-
case object.class.connection.adapter_name.downcase
|
32
|
-
when "postgresql"
|
33
|
-
column.array? || (column.type == :string && column.sql_type.include?("[]"))
|
34
|
-
end # || object.class.attribute_types[key.to_s].is_a?(ActiveRecord::Type::Serialized)
|
35
|
-
rescue
|
36
|
-
# Rails.logger.warn("Error checking multiple field array attribute: #{e.message}")
|
37
|
-
false
|
38
|
-
end
|
39
|
-
|
40
|
-
def check_multiple_field_from_validators
|
41
|
-
inclusion_validator = find_validator(:inclusion)
|
42
|
-
length_validator = find_validator(:length)
|
43
|
-
|
44
|
-
return false unless inclusion_validator || length_validator
|
45
|
-
|
46
|
-
check_multiple_field_inclusion_validator(inclusion_validator) ||
|
47
|
-
check_multiple_field_length_validator(length_validator)
|
48
|
-
end
|
49
|
-
|
50
|
-
def check_multiple_field_inclusion_validator(validator)
|
51
|
-
return false unless validator
|
52
|
-
in_option = validator.options[:in]
|
53
|
-
return false unless in_option.is_a?(Array)
|
54
|
-
|
55
|
-
validator.options[:multiple] == true || (multiple_field_array_attribute? && in_option.size > 1)
|
56
|
-
end
|
57
|
-
|
58
|
-
def check_multiple_field_length_validator(validator)
|
59
|
-
return false unless validator
|
60
|
-
validator.options[:maximum].to_i > 1 if validator.options[:maximum]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Form
|
5
|
-
module FieldOptions
|
6
|
-
module Placeholder
|
7
|
-
def placeholder(placeholder = nil)
|
8
|
-
if placeholder.nil?
|
9
|
-
options[:placeholder]
|
10
|
-
else
|
11
|
-
options[:placeholder] = placeholder
|
12
|
-
self
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,207 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Form
|
5
|
-
module FieldOptions
|
6
|
-
module Themes
|
7
|
-
# Resolves theme classes for components based on their type and the validity state of the field.
|
8
|
-
#
|
9
|
-
# This method is responsible for determining the appropriate CSS classes for a given form component.
|
10
|
-
# It considers both the base theme for the component type and any additional theming based on the
|
11
|
-
# component's validity state (valid, invalid, or neutral). The method supports a hierarchical
|
12
|
-
# theming system, allowing for cascading themes and easy customization.
|
13
|
-
#
|
14
|
-
# @param component [Symbol, String] The type of form component (e.g., :input, :label, :wrapper)
|
15
|
-
#
|
16
|
-
# @return [String, nil] A string of CSS classes for the component, or nil if no theme is applied
|
17
|
-
#
|
18
|
-
# @example Basic usage
|
19
|
-
# themed(:input)
|
20
|
-
# # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700"
|
21
|
-
#
|
22
|
-
# @example Usage with validity state
|
23
|
-
# # Assuming the field has errors
|
24
|
-
# themed(:input)
|
25
|
-
# # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700 bg-red-50 border-red-500 text-red-900"
|
26
|
-
#
|
27
|
-
# @example Cascading themes
|
28
|
-
# # Assuming textarea inherits from input in the theme definition
|
29
|
-
# themed(:textarea)
|
30
|
-
# # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700"
|
31
|
-
#
|
32
|
-
# @note The actual CSS classes returned will depend on the theme definitions in the `theme` hash
|
33
|
-
# and any overrides specified in the `options` hash.
|
34
|
-
#
|
35
|
-
# @see #resolve_theme
|
36
|
-
# @see #resolve_validity_theme
|
37
|
-
# @see #theme
|
38
|
-
def themed(component)
|
39
|
-
return unless component
|
40
|
-
|
41
|
-
tokens(resolve_theme(component), resolve_validity_theme(component)).presence
|
42
|
-
end
|
43
|
-
|
44
|
-
protected
|
45
|
-
|
46
|
-
# Recursively resolves the theme for a given property, handling nested symbol references
|
47
|
-
#
|
48
|
-
# @param property [Symbol, String] The theme property to resolve
|
49
|
-
# @param visited [Set] Set of already visited properties to prevent infinite recursion
|
50
|
-
# @return [String, nil] The resolved theme value or nil if not found
|
51
|
-
#
|
52
|
-
# @example Resolving a nested theme
|
53
|
-
# # Assuming the theme is: { input: :base_input, base_input: "some-class" }
|
54
|
-
# resolve_theme(:input)
|
55
|
-
# # => "some-class"
|
56
|
-
def resolve_theme(property, visited = Set.new)
|
57
|
-
return nil if !property.present? || visited.include?(property)
|
58
|
-
visited.add(property)
|
59
|
-
|
60
|
-
result = theme[property]
|
61
|
-
if result.is_a?(Symbol)
|
62
|
-
resolve_theme(result, visited)
|
63
|
-
else
|
64
|
-
result
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Resolves the theme for a component based on its current validity state
|
69
|
-
#
|
70
|
-
# This method determines the validity state of the field (valid, invalid, or neutral)
|
71
|
-
# and returns the corresponding theme by prepending the state to the component name.
|
72
|
-
#
|
73
|
-
# @param property [Symbol, String] The base theme property to resolve
|
74
|
-
# @return [String, nil] The resolved validity-specific theme or nil if not found
|
75
|
-
#
|
76
|
-
# @example Resolving a validity theme
|
77
|
-
# # Assuming the field has errors and the theme includes { invalid_input: "error-class" }
|
78
|
-
# resolve_validity_theme(:input)
|
79
|
-
# # => "error-class"
|
80
|
-
def resolve_validity_theme(property)
|
81
|
-
validity_property = if has_errors?
|
82
|
-
:"invalid_#{property}"
|
83
|
-
elsif object_valid?
|
84
|
-
:"valid_#{property}"
|
85
|
-
else
|
86
|
-
:"neutral_#{property}"
|
87
|
-
end
|
88
|
-
|
89
|
-
resolve_theme(validity_property)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Retrieves or initializes the theme hash for the form builder.
|
93
|
-
#
|
94
|
-
# This method returns a hash containing theme definitions for various form components.
|
95
|
-
# If a theme has been explicitly set in the options, it returns that. Otherwise, it
|
96
|
-
# initializes and returns a default theme.
|
97
|
-
#
|
98
|
-
# The theme hash defines CSS classes or references to other theme keys for different
|
99
|
-
# components and their states (e.g., valid, invalid, neutral).
|
100
|
-
#
|
101
|
-
# @return [Hash] A hash containing theme definitions for form components
|
102
|
-
#
|
103
|
-
# @example Accessing the theme
|
104
|
-
# theme[:input]
|
105
|
-
# # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700"
|
106
|
-
#
|
107
|
-
# @example Accessing a validity-specific theme
|
108
|
-
# theme[:invalid_input]
|
109
|
-
# # => "bg-red-50 border-red-500 text-red-900 placeholder-red-700 focus:ring-red-500 focus:border-red-500"
|
110
|
-
#
|
111
|
-
# @example Theme inheritance
|
112
|
-
# theme[:textarea] # Returns :input, indicating textarea inherits input's theme
|
113
|
-
# theme[:valid_textarea] # Returns :valid_input
|
114
|
-
#
|
115
|
-
# @note The actual content of the theme hash depends on the default_theme method
|
116
|
-
# and any theme overrides specified in the options when initializing the field builder.
|
117
|
-
#
|
118
|
-
# @see #default_theme
|
119
|
-
def theme
|
120
|
-
@theme ||= options[:theme] || default_theme
|
121
|
-
end
|
122
|
-
|
123
|
-
# Defines and returns the default theme hash for the field builder.
|
124
|
-
#
|
125
|
-
# This method returns a hash containing the base theme definitions for various components.
|
126
|
-
# It sets up the default styling and relationships between different components and their states.
|
127
|
-
# The theme uses a combination of explicit CSS classes and symbolic references to other theme keys,
|
128
|
-
# allowing for a flexible and inheritance-based theming system.
|
129
|
-
#
|
130
|
-
# @return [Hash] A frozen hash containing default theme definitions for components
|
131
|
-
#
|
132
|
-
# @example Accessing the default theme
|
133
|
-
# default_theme[:input]
|
134
|
-
# # => nil (indicates that :input doesn't have a default and should be defined by the user)
|
135
|
-
#
|
136
|
-
# @example Theme inheritance
|
137
|
-
# default_theme[:textarea]
|
138
|
-
# # => :input (indicates that :textarea inherits from :input)
|
139
|
-
#
|
140
|
-
# @example Validity state theming
|
141
|
-
# default_theme[:valid_textarea]
|
142
|
-
# # => :valid_input (indicates that :valid_textarea inherits from :valid_input)
|
143
|
-
#
|
144
|
-
# @note This method returns a frozen hash to prevent accidental modifications.
|
145
|
-
# To customize the theme, users should provide their own theme hash when initializing the field builder.
|
146
|
-
# @note Most theme values are set to nil or commented out in the default theme to encourage users
|
147
|
-
# to define their own styles while maintaining the relationships between components and states.
|
148
|
-
#
|
149
|
-
# @see #theme
|
150
|
-
def default_theme
|
151
|
-
{
|
152
|
-
# # input
|
153
|
-
# input: nil,
|
154
|
-
# valid_input: nil,
|
155
|
-
# invalid_input: nil,
|
156
|
-
# neutral_input: nil,
|
157
|
-
|
158
|
-
# textarea
|
159
|
-
textarea: :input,
|
160
|
-
valid_textarea: :valid_input,
|
161
|
-
invalid_textarea: :invalid_input,
|
162
|
-
neutral_textarea: :neutral_input,
|
163
|
-
|
164
|
-
# select
|
165
|
-
select: :input,
|
166
|
-
valid_select: :valid_input,
|
167
|
-
invalid_select: :invalid_input,
|
168
|
-
neutral_select: :neutral_input,
|
169
|
-
|
170
|
-
# file
|
171
|
-
file: :input,
|
172
|
-
valid_file: :valid_input,
|
173
|
-
invalid_file: :invalid_input,
|
174
|
-
neutral_file: :neutral_input,
|
175
|
-
|
176
|
-
# misc
|
177
|
-
# label: nil,
|
178
|
-
# hint: nil,
|
179
|
-
# error: nil,
|
180
|
-
full_error: :error,
|
181
|
-
# wrapper: nil,
|
182
|
-
# inner_wrapper: nil,
|
183
|
-
submit_button: :button
|
184
|
-
|
185
|
-
# # label themes
|
186
|
-
# label: "md:w-1/6 mt-2 block mb-2 text-sm font-medium",
|
187
|
-
# invalid_label: "text-red-700 dark:text-red-500",
|
188
|
-
# valid_label: "text-green-700 dark:text-green-500",
|
189
|
-
# neutral_label: "text-gray-700 dark:text-white",
|
190
|
-
# # input themes
|
191
|
-
# input: "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700",
|
192
|
-
# invalid_input: "bg-red-50 border-red-500 dark:border-red-500 text-red-900 dark:text-red-500 placeholder-red-700 dark:placeholder-red-500 focus:ring-red-500 focus:border-red-500",
|
193
|
-
# valid_input: "bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500",
|
194
|
-
# neutral_input: "border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-primary-500 focus:border-primary-500",
|
195
|
-
# # hint themes
|
196
|
-
# hint: "mt-2 text-sm text-gray-500 dark:text-gray-200",
|
197
|
-
# # error themes
|
198
|
-
# error: "mt-2 text-sm text-red-600 dark:text-red-500",
|
199
|
-
# # wrapper themes
|
200
|
-
# wrapper: "flex flex-col md:flex-row items-start space-y-2 md:space-y-0 md:space-x-2 mb-4",
|
201
|
-
# inner_wrapper: "md:w-5/6 w-full",
|
202
|
-
}.freeze
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Form
|
5
|
-
module FieldOptions
|
6
|
-
module Validators
|
7
|
-
private
|
8
|
-
|
9
|
-
def has_validators?
|
10
|
-
@has_validators ||= object.class.respond_to?(:validators_on)
|
11
|
-
end
|
12
|
-
|
13
|
-
def attribute_validators
|
14
|
-
object.class.validators_on(key)
|
15
|
-
end
|
16
|
-
|
17
|
-
def association_reflection_validators
|
18
|
-
association_reflection ? object.class.validators_on(association_reflection.name) : []
|
19
|
-
end
|
20
|
-
|
21
|
-
def valid_validator?(validator)
|
22
|
-
!conditional_validators?(validator) && action_validator_match?(validator)
|
23
|
-
end
|
24
|
-
|
25
|
-
def conditional_validators?(validator)
|
26
|
-
validator.options.include?(:if) || validator.options.include?(:unless)
|
27
|
-
end
|
28
|
-
|
29
|
-
def action_validator_match?(validator)
|
30
|
-
return true unless validator.options.include?(:on)
|
31
|
-
|
32
|
-
case validator.options[:on]
|
33
|
-
when :save
|
34
|
-
true
|
35
|
-
when :create
|
36
|
-
!object.persisted?
|
37
|
-
when :update
|
38
|
-
object.persisted?
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def find_validator(kind)
|
43
|
-
attribute_validators.find { |v| v.kind == kind && valid_validator?(v) } if has_validators?
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Form
|
5
|
-
module Structure
|
6
|
-
# Generates DOM IDs, names, etc. for a Field, Namespace, or Node based on
|
7
|
-
# norms that were established by Rails. These can be used outsidef or Rails in
|
8
|
-
# other Ruby web frameworks since it has no dependencies on Rails.
|
9
|
-
class DOM
|
10
|
-
def initialize(field:)
|
11
|
-
@field = field
|
12
|
-
end
|
13
|
-
|
14
|
-
# Converts the value of the field to a String, which is required to work
|
15
|
-
# with Phlex. Assumes that `Object#to_s` emits a format suitable for the web form.
|
16
|
-
def value
|
17
|
-
@field.value.to_s
|
18
|
-
end
|
19
|
-
|
20
|
-
# Walks from the current node to the parent node, grabs the names, and separates
|
21
|
-
# them with a `_` for a DOM ID.
|
22
|
-
def id
|
23
|
-
@id ||= begin
|
24
|
-
root, *rest = lineage
|
25
|
-
root_key = root.respond_to?(:dom_id) ? root.dom_id : root.key
|
26
|
-
rest.map(&:key).unshift(root_key).join("_")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# The `name` attribute of a node, which is influenced by Rails.
|
31
|
-
# All node names, except the parent node, are wrapped in a `[]` and collections
|
32
|
-
# are left empty. For example, `user[addresses][][street]` would be created for a form with
|
33
|
-
# data shaped like `{user: {addresses: [{street: "Sesame Street"}]}}`.
|
34
|
-
def name
|
35
|
-
@name ||= begin
|
36
|
-
root, *names = keys
|
37
|
-
names.map { |name| "[#{name}]" }.unshift(root).join
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# One-liner way of walking from the current node all the way up to the parent.
|
42
|
-
def lineage
|
43
|
-
@lineage ||= Enumerator.produce(@field, &:parent).take_while(&:itself).reverse
|
44
|
-
end
|
45
|
-
|
46
|
-
# Emit the id, name, and value in an HTML tag-ish that doesnt have an element.
|
47
|
-
def inspect
|
48
|
-
"<#{self.class.name} id=#{id.inspect} name=#{name.inspect} value=#{value.inspect}/>"
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def keys
|
54
|
-
@keys ||= lineage.map do |node|
|
55
|
-
# If the parent of a field is a field, the name should be nil.
|
56
|
-
node.key unless node.parent.is_a? FieldBuilder
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,243 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "phlex"
|
4
|
-
|
5
|
-
module Phlexi
|
6
|
-
module Form
|
7
|
-
module Structure
|
8
|
-
# FieldBuilder class is responsible for building form fields with various options and components.
|
9
|
-
#
|
10
|
-
# @attr_reader [Structure::DOM] dom The DOM structure for the field.
|
11
|
-
# @attr_reader [Hash] options Options for the field.
|
12
|
-
# @attr_reader [Object] object The object associated with the field.
|
13
|
-
# @attr_reader [Hash] attributes Attributes for the field.
|
14
|
-
# @attr_accessor [Object] value The value of the field.
|
15
|
-
class FieldBuilder < Node
|
16
|
-
include Phlex::Helpers
|
17
|
-
include FieldOptions::Associations
|
18
|
-
include FieldOptions::Themes
|
19
|
-
include FieldOptions::Validators
|
20
|
-
include FieldOptions::Labels
|
21
|
-
include FieldOptions::Hints
|
22
|
-
include FieldOptions::Errors
|
23
|
-
include FieldOptions::InferredTypes
|
24
|
-
include FieldOptions::Collection
|
25
|
-
include FieldOptions::Placeholder
|
26
|
-
include FieldOptions::Required
|
27
|
-
include FieldOptions::Autofocus
|
28
|
-
include FieldOptions::Disabled
|
29
|
-
include FieldOptions::Readonly
|
30
|
-
include FieldOptions::Length
|
31
|
-
include FieldOptions::MinMax
|
32
|
-
include FieldOptions::Pattern
|
33
|
-
include FieldOptions::Multiple
|
34
|
-
include FieldOptions::Limit
|
35
|
-
|
36
|
-
attr_reader :dom, :options, :object, :input_attributes, :value
|
37
|
-
|
38
|
-
# Initializes a new FieldBuilder instance.
|
39
|
-
#
|
40
|
-
# @param key [Symbol, String] The key for the field.
|
41
|
-
# @param parent [Structure::Namespace] The parent object.
|
42
|
-
# @param object [Object, nil] The associated object.
|
43
|
-
# @param value [Object] The initial value for the field.
|
44
|
-
# @param input_attributes [Hash] Default attributes to apply to input fields.
|
45
|
-
# @param options [Hash] Additional options for the field.
|
46
|
-
def initialize(key, parent:, object: nil, value: NIL_VALUE, input_attributes: {}, **options)
|
47
|
-
super(key, parent: parent)
|
48
|
-
|
49
|
-
@object = object
|
50
|
-
@value = determine_initial_value(value)
|
51
|
-
@input_attributes = input_attributes
|
52
|
-
@options = options
|
53
|
-
@dom = Structure::DOM.new(field: self)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Creates a label tag for the field.
|
57
|
-
#
|
58
|
-
# @param attributes [Hash] Additional attributes for the label.
|
59
|
-
# @return [Components::Label] The label component.
|
60
|
-
def label_tag(**, &)
|
61
|
-
create_component(Components::Label, :label, **, &)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Creates an input tag for the field.
|
65
|
-
#
|
66
|
-
# @param attributes [Hash] Additional attributes for the input.
|
67
|
-
# @return [Components::Input] The input component.
|
68
|
-
def input_tag(**, &)
|
69
|
-
create_component(Components::Input, :input, **, &)
|
70
|
-
end
|
71
|
-
|
72
|
-
def file_input_tag(**, &)
|
73
|
-
create_component(Components::FileInput, :file, **, &)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Creates a checkbox tag for the field.
|
77
|
-
#
|
78
|
-
# @param attributes [Hash] Additional attributes for the checkbox.
|
79
|
-
# @return [Components::Checkbox] The checkbox component.
|
80
|
-
def checkbox_tag(**, &)
|
81
|
-
create_component(Components::Checkbox, :checkbox, **, &)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Creates collection checkboxes for the field.
|
85
|
-
#
|
86
|
-
# @param attributes [Hash] Additional attributes for the collection checkboxes.
|
87
|
-
# @yield [block] The block to be executed for each checkbox.
|
88
|
-
# @return [Components::CollectionCheckboxes] The collection checkboxes component.
|
89
|
-
def collection_checkboxes_tag(**, &)
|
90
|
-
create_component(Components::CollectionCheckboxes, :collection_checkboxes, **, &)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Creates a radio button tag for the field.
|
94
|
-
#
|
95
|
-
# @param attributes [Hash] Additional attributes for the radio button.
|
96
|
-
# @return [Components::RadioButton] The radio button component.
|
97
|
-
def radio_button_tag(**, &)
|
98
|
-
create_component(Components::RadioButton, :radio, **, &)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Creates collection radio buttons for the field.
|
102
|
-
#
|
103
|
-
# @param attributes [Hash] Additional attributes for the collection radio buttons.
|
104
|
-
# @yield [block] The block to be executed for each radio button.
|
105
|
-
# @return [Components::CollectionRadioButtons] The collection radio buttons component.
|
106
|
-
def collection_radio_buttons_tag(**, &)
|
107
|
-
create_component(Components::CollectionRadioButtons, :collection_radio_buttons, **, &)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Creates a textarea tag for the field.
|
111
|
-
#
|
112
|
-
# @param attributes [Hash] Additional attributes for the textarea.
|
113
|
-
# @return [Components::Textarea] The textarea component.
|
114
|
-
def textarea_tag(**, &)
|
115
|
-
create_component(Components::Textarea, :textarea, **, &)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Creates a select tag for the field.
|
119
|
-
#
|
120
|
-
# @param attributes [Hash] Additional attributes for the select.
|
121
|
-
# @return [Components::Select] The select component.
|
122
|
-
def select_tag(**, &)
|
123
|
-
create_component(Components::Select, :select, **, &)
|
124
|
-
end
|
125
|
-
|
126
|
-
def input_array_tag(**, &)
|
127
|
-
create_component(Components::InputArray, :array, **, &)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Creates a hint tag for the field.
|
131
|
-
#
|
132
|
-
# @param attributes [Hash] Additional attributes for the hint.
|
133
|
-
# @return [Components::Hint] The hint component.
|
134
|
-
def hint_tag(**, &)
|
135
|
-
create_component(Components::Hint, :hint, **, &)
|
136
|
-
end
|
137
|
-
|
138
|
-
# Creates an error tag for the field.
|
139
|
-
#
|
140
|
-
# @param attributes [Hash] Additional attributes for the error.
|
141
|
-
# @return [Components::Error] The error component.
|
142
|
-
def error_tag(**, &)
|
143
|
-
create_component(Components::Error, :error, **, &)
|
144
|
-
end
|
145
|
-
|
146
|
-
# Creates a full error tag for the field.
|
147
|
-
#
|
148
|
-
# @param attributes [Hash] Additional attributes for the full error.
|
149
|
-
# @return [Components::FullError] The full error component.
|
150
|
-
def full_error_tag(**, &)
|
151
|
-
create_component(Components::FullError, :full_error, **, &)
|
152
|
-
end
|
153
|
-
|
154
|
-
# Wraps the field with additional markup.
|
155
|
-
#
|
156
|
-
# @param inner [Hash] Attributes for the inner wrapper.
|
157
|
-
# @param attributes [Hash] Additional attributes for the wrapper.
|
158
|
-
# @yield [block] The block to be executed within the wrapper.
|
159
|
-
# @return [Components::Wrapper] The wrapper component.
|
160
|
-
def wrapped(inner: {}, **attributes, &)
|
161
|
-
attributes = apply_component_theme(attributes, :wrapper)
|
162
|
-
inner = apply_component_theme(inner, :inner_wrapper)
|
163
|
-
Components::Wrapper.new(self, inner: inner, **attributes, &)
|
164
|
-
end
|
165
|
-
|
166
|
-
# Creates a repeated field collection.
|
167
|
-
#
|
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
|
170
|
-
# @yield [block] The block to be executed for each item in the collection.
|
171
|
-
# @return [FieldCollection] The field collection.
|
172
|
-
def repeated(range = nil, &)
|
173
|
-
FieldCollection.new(field: self, range: range, &)
|
174
|
-
end
|
175
|
-
|
176
|
-
# Creates a submit button
|
177
|
-
#
|
178
|
-
# @param attributes [Hash] Additional attributes for the submit.
|
179
|
-
# @return [Components::SubmitButton] The submit button component.
|
180
|
-
def submit_button_tag(**, &)
|
181
|
-
create_component(Components::SubmitButton, :submit_button, **, &)
|
182
|
-
end
|
183
|
-
|
184
|
-
def extract_input(params)
|
185
|
-
raise "field##{dom.name} did not define an input component" unless @field_input_extractor
|
186
|
-
|
187
|
-
@field_input_extractor.extract_input(params)
|
188
|
-
end
|
189
|
-
|
190
|
-
protected
|
191
|
-
|
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
|
197
|
-
|
198
|
-
@field_input_extractor = component
|
199
|
-
end
|
200
|
-
|
201
|
-
component
|
202
|
-
end
|
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
|
211
|
-
end
|
212
|
-
|
213
|
-
def has_value?
|
214
|
-
value.present?
|
215
|
-
end
|
216
|
-
|
217
|
-
def determine_initial_value(value)
|
218
|
-
return value unless value == NIL_VALUE
|
219
|
-
|
220
|
-
determine_value_from_association || determine_value_from_object
|
221
|
-
end
|
222
|
-
|
223
|
-
def determine_value_from_object
|
224
|
-
object.respond_to?(key) ? object.public_send(key) : nil
|
225
|
-
end
|
226
|
-
|
227
|
-
def determine_value_from_association
|
228
|
-
return nil unless association_reflection.present?
|
229
|
-
|
230
|
-
value = object.public_send(key)
|
231
|
-
case association_reflection.macro
|
232
|
-
when :has_many, :has_and_belongs_to_many
|
233
|
-
value&.map { |v| v.public_send(association_reflection.klass.primary_key) }
|
234
|
-
when :belongs_to, :has_one
|
235
|
-
value&.public_send(association_reflection.klass.primary_key)
|
236
|
-
else
|
237
|
-
raise ArgumentError, "Unsupported association type: #{association_reflection.macro}"
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Phlexi
|
4
|
-
module Form
|
5
|
-
module Structure
|
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
|
10
|
-
class Node
|
11
|
-
attr_reader :key, :parent
|
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
|
17
|
-
def initialize(key, parent:)
|
18
|
-
@key = :"#{key}"
|
19
|
-
@parent = parent
|
20
|
-
end
|
21
|
-
|
22
|
-
def inspect
|
23
|
-
"<#{self.class.name} key=#{key.inspect} parent=#{id.inspect} />"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|