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,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "time"
|
4
|
+
|
3
5
|
module Phlexi
|
4
6
|
module Form
|
5
7
|
module Components
|
@@ -21,7 +23,7 @@ module Phlexi
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def build_input_attributes
|
24
|
-
attributes.fetch(:type) { attributes[:type] = field.
|
26
|
+
attributes.fetch(:type) { attributes[:type] = field.inferred_string_field_type }
|
25
27
|
attributes.fetch(:disabled) { attributes[:disabled] = field.disabled? }
|
26
28
|
|
27
29
|
case attributes[:type]
|
@@ -48,12 +50,24 @@ module Phlexi
|
|
48
50
|
attributes.fetch(:autofocus) { attributes[:autofocus] = field.focused? }
|
49
51
|
attributes.fetch(:required) { attributes[:required] = field.required? }
|
50
52
|
attributes.fetch(:multiple) { attributes[:multiple] = field.multiple? }
|
51
|
-
when :date, :time, :
|
53
|
+
when :date, :time, :"datetime-local"
|
52
54
|
attributes.fetch(:autofocus) { attributes[:autofocus] = field.focused? }
|
53
55
|
attributes.fetch(:readonly) { attributes[:readonly] = field.readonly? }
|
54
56
|
attributes.fetch(:required) { attributes[:required] = field.required? }
|
55
57
|
attributes.fetch(:min) { attributes[:min] = field.min }
|
56
58
|
attributes.fetch(:max) { attributes[:max] = field.max }
|
59
|
+
|
60
|
+
# TODO: Investigate if this is Timezone complaint
|
61
|
+
if field.value.respond_to?(:strftime)
|
62
|
+
attributes[:value] = case attributes[:type]
|
63
|
+
when :date
|
64
|
+
field.value.strftime("%Y-%m-%d")
|
65
|
+
when :time
|
66
|
+
field.value.strftime("%H:%M:%S")
|
67
|
+
when :"datetime-local"
|
68
|
+
field.value.strftime("%Y-%m-%dT%H:%M:%S")
|
69
|
+
end
|
70
|
+
end
|
57
71
|
when :color
|
58
72
|
attributes.fetch(:autofocus) { attributes[:autofocus] = field.focused? }
|
59
73
|
when :range
|
@@ -43,6 +43,10 @@ module Phlexi
|
|
43
43
|
attributes[:disabled] = attributes.fetch(:disabled, field.disabled?)
|
44
44
|
attributes[:multiple] = attributes.fetch(:multiple, field.multiple?)
|
45
45
|
attributes[:size] = attributes.fetch(:size, field.limit)
|
46
|
+
|
47
|
+
if attributes[:multiple]
|
48
|
+
attributes[:name] = "#{attributes[:name].sub(/\[\]$/, "")}[]"
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def blank_option_text
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Phlexi
|
2
|
+
module Form
|
3
|
+
class HTML < (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)
|
4
|
+
module Behaviour
|
5
|
+
protected
|
6
|
+
|
7
|
+
def themed(component, field)
|
8
|
+
base_theme = Phlexi::Form::Theme.instance.resolve_theme(component)
|
9
|
+
validity_theme = Phlexi::Form::Theme.instance.resolve_validity_theme(component, field)
|
10
|
+
|
11
|
+
tokens(base_theme, validity_theme)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
include Behaviour
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
|
-
module
|
5
|
+
module Options
|
6
6
|
module Collection
|
7
7
|
def collection(collection = nil)
|
8
8
|
if collection.nil?
|
9
|
-
options
|
9
|
+
options.fetch(:collection) { options[:collection] = infer_collection }
|
10
10
|
else
|
11
11
|
options[:collection] = collection
|
12
12
|
self
|
@@ -16,6 +16,10 @@ module Phlexi
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def infer_collection
|
19
|
+
if object.class.respond_to?(:defined_enums)
|
20
|
+
return object.class.defined_enums.fetch(key.to_s).keys if object.class.defined_enums.key?(key.to_s)
|
21
|
+
end
|
22
|
+
|
19
23
|
collection_value_from_association || collection_value_from_validator
|
20
24
|
end
|
21
25
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
|
-
module
|
5
|
+
module Options
|
6
6
|
module Errors
|
7
7
|
def custom_error(error)
|
8
8
|
options[:error] = error
|
@@ -33,6 +33,16 @@ module Phlexi
|
|
33
33
|
!has_errors? && has_value?
|
34
34
|
end
|
35
35
|
|
36
|
+
# Determines if the associated object is in a valid state
|
37
|
+
#
|
38
|
+
# An object is considered valid if it is persisted and has no errors.
|
39
|
+
#
|
40
|
+
# @return [Boolean] true if the object is persisted and has no errors, false otherwise
|
41
|
+
def object_valid?
|
42
|
+
object.respond_to?(:persisted?) && object.persisted? &&
|
43
|
+
object.respond_to?(:errors) && !object.errors.empty?
|
44
|
+
end
|
45
|
+
|
36
46
|
protected
|
37
47
|
|
38
48
|
def error_text
|
@@ -80,16 +90,6 @@ module Phlexi
|
|
80
90
|
def has_custom_error?
|
81
91
|
options[:error].is_a?(String)
|
82
92
|
end
|
83
|
-
|
84
|
-
# Determines if the associated object is in a valid state
|
85
|
-
#
|
86
|
-
# An object is considered valid if it is persisted and has no errors.
|
87
|
-
#
|
88
|
-
# @return [Boolean] true if the object is persisted and has no errors, false otherwise
|
89
|
-
def object_valid?
|
90
|
-
object.respond_to?(:persisted?) && object.persisted? &&
|
91
|
-
object.respond_to?(:errors) && !object.errors.empty?
|
92
|
-
end
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Options
|
6
|
+
module InferredTypes
|
7
|
+
private
|
8
|
+
|
9
|
+
def infer_field_component
|
10
|
+
case inferred_field_type
|
11
|
+
when :string, :text
|
12
|
+
infer_string_field_type || inferred_field_type
|
13
|
+
when :integer, :float, :decimal
|
14
|
+
:number
|
15
|
+
when :json, :jsonb
|
16
|
+
:text
|
17
|
+
when :enum
|
18
|
+
:select
|
19
|
+
when :association
|
20
|
+
association_reflection.polymorphic? ? :"polymorphic_#{association_reflection.macro}" : association_reflection.macro
|
21
|
+
when :attachment, :binary
|
22
|
+
:file
|
23
|
+
when :date, :time, :datetime, :boolean, :hstore
|
24
|
+
inferred_field_type
|
25
|
+
else
|
26
|
+
:string
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
|
-
module
|
5
|
+
module Options
|
6
6
|
module Length
|
7
7
|
def minlength(minlength = nil)
|
8
8
|
if minlength.nil?
|
9
|
-
options
|
9
|
+
options.fetch(:minlength) { options[:minlength] = calculate_minlength }
|
10
10
|
else
|
11
11
|
options[:minlength] = minlength
|
12
12
|
self
|
@@ -15,7 +15,7 @@ module Phlexi
|
|
15
15
|
|
16
16
|
def maxlength(maxlength = nil)
|
17
17
|
if maxlength.nil?
|
18
|
-
options
|
18
|
+
options.fetch(:maxlength) { options[:maxlength] = calculate_maxlength }
|
19
19
|
else
|
20
20
|
options[:maxlength] = maxlength
|
21
21
|
self
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
|
-
module
|
5
|
+
module Options
|
6
6
|
module Limit
|
7
7
|
def limit(limit = nil)
|
8
8
|
if limit.nil?
|
9
|
-
options
|
9
|
+
options.fetch(:limit) { options[:limit] = calculate_limit }
|
10
10
|
else
|
11
11
|
options[:limit] = limit
|
12
12
|
self
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Options
|
6
|
+
module Max
|
7
|
+
def max(max_value = nil)
|
8
|
+
if max_value.nil?
|
9
|
+
options.fetch(:max) { options[:max] = calculate_max }
|
10
|
+
else
|
11
|
+
options[:max] = max_value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def calculate_max
|
19
|
+
if (numericality_validator = find_numericality_validator)
|
20
|
+
get_max_from_validator(numericality_validator)
|
21
|
+
elsif (max = get_max_from_attribute(key))
|
22
|
+
max
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_max_from_validator(validator)
|
27
|
+
options = validator.options
|
28
|
+
max = if options.key?(:less_than)
|
29
|
+
{value: options[:less_than], exclusive: true}
|
30
|
+
elsif options.key?(:less_than_or_equal_to)
|
31
|
+
{value: options[:less_than_or_equal_to], exclusive: false}
|
32
|
+
end
|
33
|
+
evaluate_and_adjust_max(max)
|
34
|
+
end
|
35
|
+
|
36
|
+
def evaluate_and_adjust_max(max)
|
37
|
+
return nil unless max
|
38
|
+
|
39
|
+
value = evaluate_numericality_validator_option(max[:value])
|
40
|
+
max[:exclusive] ? value - 1 : value
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_max_from_attribute(attribute)
|
44
|
+
if object.class.respond_to?(:attribute_types) && (attribute_type = object.class.attribute_types[attribute.to_s])
|
45
|
+
if (range = attribute_type.instance_variable_get(:@range))
|
46
|
+
range.max
|
47
|
+
elsif attribute_type.respond_to?(:precision) && (precision = attribute_type.precision)
|
48
|
+
(precision**8) - ((step && step != "any") ? step : 0.000001)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Options
|
6
|
+
module Min
|
7
|
+
def min(min_value = nil)
|
8
|
+
if min_value.nil?
|
9
|
+
options.fetch(:min) { options[:min] = calculate_min }
|
10
|
+
else
|
11
|
+
options[:min] = min_value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def calculate_min
|
19
|
+
if (numericality_validator = find_numericality_validator)
|
20
|
+
get_min_from_validator(numericality_validator)
|
21
|
+
elsif (min = get_min_from_attribute(key))
|
22
|
+
min
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_min_from_validator(validator)
|
27
|
+
options = validator.options
|
28
|
+
min = if options.key?(:greater_than)
|
29
|
+
{value: options[:greater_than], exclusive: true}
|
30
|
+
elsif options.key?(:greater_than_or_equal_to)
|
31
|
+
{value: options[:greater_than_or_equal_to], exclusive: false}
|
32
|
+
end
|
33
|
+
evaluate_and_adjust_min(min)
|
34
|
+
end
|
35
|
+
|
36
|
+
def evaluate_and_adjust_min(min)
|
37
|
+
return nil unless min
|
38
|
+
|
39
|
+
value = evaluate_numericality_validator_option(min[:value])
|
40
|
+
min[:exclusive] ? value + 1 : value
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_min_from_attribute(attribute)
|
44
|
+
if object.class.respond_to?(:attribute_types) && (attribute_type = object.class.attribute_types[attribute.to_s])
|
45
|
+
if (range = attribute_type.instance_variable_get(:@range))
|
46
|
+
range.min
|
47
|
+
elsif attribute_type.respond_to?(:precision) && (precision = attribute_type.precision)
|
48
|
+
-((precision**8) - ((step && step != "any") ? step : 0.000001))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
|
-
module
|
5
|
+
module Options
|
6
6
|
module Pattern
|
7
7
|
def pattern(pattern = nil)
|
8
8
|
if pattern.nil?
|
9
|
-
options
|
9
|
+
options.fetch(:pattern) { options[:pattern] = calculate_pattern }
|
10
10
|
else
|
11
11
|
options[:pattern] = pattern
|
12
12
|
self
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
|
-
module
|
5
|
+
module Options
|
6
6
|
module Required
|
7
7
|
def required?
|
8
|
-
options
|
8
|
+
options.fetch(:required) { options[:required] = calculate_required }
|
9
9
|
end
|
10
10
|
|
11
11
|
def required!(required = true)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Options
|
6
|
+
module Step
|
7
|
+
def step(value = nil)
|
8
|
+
if value.nil?
|
9
|
+
options.fetch(:step) { options[:step] = calculate_step }
|
10
|
+
else
|
11
|
+
options[:step] = value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def calculate_step
|
19
|
+
if (scale = get_scale_from_attribute(key))
|
20
|
+
return 1.fdiv(10**scale)
|
21
|
+
end
|
22
|
+
|
23
|
+
case inferred_field_type
|
24
|
+
when :integer
|
25
|
+
1
|
26
|
+
when :decimal, :float
|
27
|
+
"any"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_scale_from_attribute(attribute)
|
32
|
+
if object.class.respond_to?(:attribute_types) && (attribute_type = object.class.attribute_types[attribute.to_s])
|
33
|
+
attribute_type.scale if attribute_type.respond_to?(:scale)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlexi
|
4
|
+
module Form
|
5
|
+
module Options
|
6
|
+
module Validators
|
7
|
+
private
|
8
|
+
|
9
|
+
def find_numericality_validator
|
10
|
+
find_validator(:numericality)
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate_numericality_validator_option(option)
|
14
|
+
case option
|
15
|
+
when Proc
|
16
|
+
option.arity.zero? ? option.call : option.call(object)
|
17
|
+
else
|
18
|
+
option
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,24 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "phlex"
|
4
|
-
|
5
3
|
module Phlexi
|
6
4
|
module Form
|
7
5
|
module Structure
|
8
|
-
class FieldCollection
|
9
|
-
|
10
|
-
|
11
|
-
class Builder
|
12
|
-
include Phlex::Helpers
|
13
|
-
|
14
|
-
attr_reader :key, :index
|
15
|
-
|
16
|
-
def initialize(key, field, index)
|
17
|
-
@key = key.to_s
|
18
|
-
@field = field
|
19
|
-
@index = index
|
20
|
-
end
|
21
|
-
|
6
|
+
class FieldCollection < Phlexi::Field::Structure::FieldCollection
|
7
|
+
class Builder < Phlexi::Field::Structure::FieldCollection::Builder
|
22
8
|
def field(**options)
|
23
9
|
options = mix({input_attributes: @field.input_attributes}, options)
|
24
10
|
@field.class.new(key, **options, parent: @field).tap do |field|
|
@@ -35,22 +21,16 @@ module Phlexi
|
|
35
21
|
end
|
36
22
|
end
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_collection(collection)
|
27
|
+
case collection
|
41
28
|
when Range, Array
|
42
|
-
|
29
|
+
collection
|
43
30
|
when Integer
|
44
|
-
1..
|
31
|
+
1..collection
|
45
32
|
else
|
46
|
-
|
47
|
-
end
|
48
|
-
each(&) if block_given?
|
49
|
-
end
|
50
|
-
|
51
|
-
def each(&)
|
52
|
-
@range.each.with_index do |key, index|
|
53
|
-
yield Builder.new(key, @field, index)
|
33
|
+
collection.to_a
|
54
34
|
end
|
55
35
|
end
|
56
36
|
end
|
@@ -3,81 +3,13 @@
|
|
3
3
|
module Phlexi
|
4
4
|
module Form
|
5
5
|
module Structure
|
6
|
-
|
7
|
-
|
8
|
-
#
|
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.
|
18
|
-
class Namespace < Structure::Node
|
19
|
-
include Enumerable
|
20
|
-
|
21
|
-
attr_reader :builder_klass, :object
|
22
|
-
|
23
|
-
def initialize(key, parent:, builder_klass:, object: nil)
|
24
|
-
super(key, parent: parent)
|
25
|
-
@builder_klass = builder_klass
|
26
|
-
@object = object
|
27
|
-
@children = {}
|
28
|
-
yield self if block_given?
|
29
|
-
end
|
30
|
-
|
31
|
-
def field(key, **attributes)
|
32
|
-
create_child(key, attributes.delete(:builder_klass) || builder_klass, object: object, **attributes).tap do |field|
|
33
|
-
yield field if block_given?
|
34
|
-
end
|
35
|
-
end
|
6
|
+
class Namespace < Phlexi::Field::Structure::Namespace
|
7
|
+
class NamespaceCollection < Phlexi::Form::Structure::NamespaceCollection; end
|
36
8
|
|
37
9
|
def submit_button(key = :submit_button, **, &)
|
38
10
|
field(key).submit_button_tag(**, &)
|
39
11
|
end
|
40
12
|
|
41
|
-
# Creates a `Namespace` child instance with the parent set to the current instance, adds to
|
42
|
-
# the `@children` Hash to ensure duplicate child namespaces aren't created, then calls the
|
43
|
-
# method on the `@object` to get the child object to pass into that namespace.
|
44
|
-
#
|
45
|
-
# For example, if a `User#permission` returns a `Permission` object, we could map that to a
|
46
|
-
# form like this:
|
47
|
-
#
|
48
|
-
# ```ruby
|
49
|
-
# Phlexi::Form(User.new, as: :user) do
|
50
|
-
# nest_one :profile do |profile|
|
51
|
-
# render profile.field(:gender).input_tag
|
52
|
-
# end
|
53
|
-
# end
|
54
|
-
# ```
|
55
|
-
def nest_one(key, object: nil, &)
|
56
|
-
object ||= object_value_for(key: key)
|
57
|
-
create_child(key, self.class, object:, builder_klass:, &)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Wraps an array of objects in Namespace classes. For example, if `User#addresses` returns
|
61
|
-
# an enumerable or array of `Address` classes:
|
62
|
-
#
|
63
|
-
# ```ruby
|
64
|
-
# Phlexi::Form(User.new) do
|
65
|
-
# render field(:email).input_tag
|
66
|
-
# render field(:name).input_tag
|
67
|
-
# nest_many :addresses do |address|
|
68
|
-
# render address.field(:street).input_tag
|
69
|
-
# render address.field(:state).input_tag
|
70
|
-
# render address.field(:zip).input_tag
|
71
|
-
# end
|
72
|
-
# end
|
73
|
-
# ```
|
74
|
-
# The object within the block is a `Namespace` object that maps each object within the enumerable
|
75
|
-
# to another `Namespace` or `Field`.
|
76
|
-
def nest_many(key, collection: nil, &)
|
77
|
-
collection ||= Array(object_value_for(key: key))
|
78
|
-
create_child(key, NamespaceCollection, collection:, &)
|
79
|
-
end
|
80
|
-
|
81
13
|
def extract_input(params)
|
82
14
|
if params.is_a?(Array)
|
83
15
|
each_with_object({}) do |child, hash|
|
@@ -90,50 +22,6 @@ module Phlexi
|
|
90
22
|
{key => input}
|
91
23
|
end
|
92
24
|
end
|
93
|
-
|
94
|
-
# Iterates through the children of the current namespace, which could be `Namespace` or `Field`
|
95
|
-
# objects.
|
96
|
-
def each(&)
|
97
|
-
@children.values.each(&)
|
98
|
-
end
|
99
|
-
|
100
|
-
def dom_id
|
101
|
-
@dom_id ||= begin
|
102
|
-
id = if object.nil?
|
103
|
-
nil
|
104
|
-
elsif object.class.respond_to?(:primary_key)
|
105
|
-
object.public_send(object.class.primary_key) || :new
|
106
|
-
elsif object.respond_to?(:id)
|
107
|
-
object.id || :new
|
108
|
-
end
|
109
|
-
[key, id].compact.join("_").underscore
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# Creates a root Namespace, which is essentially a form.
|
114
|
-
def self.root(*, builder_klass:, **, &)
|
115
|
-
new(*, parent: nil, builder_klass:, **, &)
|
116
|
-
end
|
117
|
-
|
118
|
-
protected
|
119
|
-
|
120
|
-
# Calls the corresponding method on the object for the `key` name, if it exists. For example
|
121
|
-
# if the `key` is `email` on `User`, this method would call `User#email` if the method is
|
122
|
-
# present.
|
123
|
-
#
|
124
|
-
# This method could be overwritten if the mapping between the `@object` and `key` name is not
|
125
|
-
# a method call. For example, a `Hash` would be accessed via `user[:email]` instead of `user.send(:email)`
|
126
|
-
def object_value_for(key:)
|
127
|
-
@object.send(key) if @object.respond_to? key
|
128
|
-
end
|
129
|
-
|
130
|
-
private
|
131
|
-
|
132
|
-
# Checks if the child exists. If it does then it returns that. If it doesn't, it will
|
133
|
-
# build the child.
|
134
|
-
def create_child(key, child_class, **kwargs, &block)
|
135
|
-
@children.fetch(key) { @children[key] = child_class.new(key, parent: self, **kwargs, &block) }
|
136
|
-
end
|
137
25
|
end
|
138
26
|
end
|
139
27
|
end
|