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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -8
  3. data/gemfiles/default.gemfile.lock +1 -1
  4. data/gemfiles/rails_7.gemfile.lock +1 -1
  5. data/lib/phlexi/form/base.rb +28 -36
  6. data/lib/phlexi/form/components/base.rb +2 -2
  7. data/lib/phlexi/form/components/collection_checkboxes.rb +1 -1
  8. data/lib/phlexi/form/components/collection_radio_buttons.rb +1 -1
  9. data/lib/phlexi/form/components/concerns/extracts_input.rb +53 -0
  10. data/lib/phlexi/form/components/concerns/handles_input.rb +4 -34
  11. data/lib/phlexi/form/components/concerns/submits_form.rb +8 -0
  12. data/lib/phlexi/form/components/error.rb +1 -1
  13. data/lib/phlexi/form/components/file_input.rb +1 -0
  14. data/lib/phlexi/form/components/hint.rb +1 -1
  15. data/lib/phlexi/form/components/input.rb +1 -5
  16. data/lib/phlexi/form/components/input_array.rb +1 -1
  17. data/lib/phlexi/form/components/select.rb +0 -3
  18. data/lib/phlexi/form/components/textarea.rb +2 -3
  19. data/lib/phlexi/form/field_options/associations.rb +2 -2
  20. data/lib/phlexi/form/field_options/collection.rb +8 -8
  21. data/lib/phlexi/form/field_options/errors.rb +7 -3
  22. data/lib/phlexi/form/field_options/hints.rb +5 -1
  23. data/lib/phlexi/form/field_options/inferred_types.rb +14 -10
  24. data/lib/phlexi/form/field_options/multiple.rb +1 -1
  25. data/lib/phlexi/form/field_options/required.rb +1 -1
  26. data/lib/phlexi/form/field_options/validators.rb +2 -2
  27. data/lib/phlexi/form/structure/dom.rb +2 -2
  28. data/lib/phlexi/form/structure/field_builder.rb +59 -52
  29. data/lib/phlexi/form/structure/field_collection.rb +7 -2
  30. data/lib/phlexi/form/structure/namespace.rb +19 -14
  31. data/lib/phlexi/form/structure/namespace_collection.rb +1 -1
  32. data/lib/phlexi/form/structure/node.rb +13 -3
  33. data/lib/phlexi/form/version.rb +1 -1
  34. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f0a5677bd380aa749f9de4f25243f6c7367839c66a3bfe3f59dfae0f1b32ab6
4
- data.tar.gz: 9107d443cb28c47c1c0a113af182af3b2b6c0a386451e913447a3b834676bbbe
3
+ metadata.gz: d20cf0d6ccfcdf3cbb3a05a518b25a9b6176ef827e32f9cab47fddb43391f470
4
+ data.tar.gz: '0788efe146100a6629f679d913a080ff7cc2039881477bc9ffda16dfe0f6dc9f'
5
5
  SHA512:
6
- metadata.gz: 0bedfa08fae8602d5cfc632d4907fb12a41c3ac7a64deaad4cc977c82fb3451f976c37e404e449388f6f88127921fe235cec2f42ffedeadc118865c341f924d8
7
- data.tar.gz: db75b28ad2166e117054c2f5eccd5e73e0efcfc7de330d17b02e46468cf46d0aa80934ab31fbd273910c9c80a66b1fdff4184ccf88dd3cbb814b758f36023c38
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
- private
163
-
164
- def default_theme
165
- {
166
- input: "border rounded px-2 py-1",
167
- label: "font-bold text-gray-700",
168
- # Add more theme options here
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
  ```
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- phlexi-form (0.2.0)
4
+ phlexi-form (0.3.0.rc1)
5
5
  activesupport
6
6
  phlex (~> 1.11)
7
7
  zeitwerk
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- phlexi-form (0.2.0)
4
+ phlexi-form (0.3.0.rc1)
5
5
  activesupport
6
6
  phlex (~> 1.11)
7
7
  zeitwerk
@@ -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.new(user, action: '/users', method: 'post') do |f|
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
- unless object.respond_to?(:model_name) && object.model_name.respond_to?(:param_key) && object.model_name.param_key.present?
114
- raise ArgumentError, "record must respond to #model_name.param_key with a non nil value or set `key` option e.g. Phlexi::Form(record, key: :record)"
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 authenticity_token?
168
- defined?(helpers) && options.fetch(:authenticity_token) { !form_method.get? }
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
- **attributes
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
- attributes[:class] = tokens(
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.multi(option_mapper.values) do |builder|
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.multi(option_mapper.values) do |builder|
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
- # 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
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
- def normalize_simple_input(input_value)
47
- input_value.to_s.presence
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
@@ -13,7 +13,7 @@ module Phlexi
13
13
  private
14
14
 
15
15
  def render?
16
- field.show_errors? && field.has_errors?
16
+ field.show_errors?
17
17
  end
18
18
  end
19
19
  end
@@ -14,6 +14,7 @@ module Phlexi
14
14
  def build_input_attributes
15
15
  attributes[:type] = :file
16
16
  super
17
+ # ensure we are always setting it to false
17
18
  attributes[:value] = false
18
19
  end
19
20
 
@@ -13,7 +13,7 @@ module Phlexi
13
13
  private
14
14
 
15
15
  def render?
16
- field.hint.present? && (!field.show_errors? || !field.has_errors?)
16
+ field.show_hint?
17
17
  end
18
18
  end
19
19
  end
@@ -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.inferred_input_type }
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]
@@ -9,7 +9,7 @@ module Phlexi
9
9
 
10
10
  def view_template
11
11
  div(**attributes.slice(:id, :class)) do
12
- field.multi(values.length) do |builder|
12
+ field.repeated(values.length) do |builder|
13
13
  render builder.hidden_field_tag if builder.index == 0
14
14
 
15
15
  field = builder.field(
@@ -31,9 +31,6 @@ module Phlexi
31
31
  def build_attributes
32
32
  super
33
33
 
34
- attributes[:id] = field.dom.id
35
- attributes[:name] = field.dom.name
36
-
37
34
  build_select_attributes
38
35
  end
39
36
 
@@ -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
 
@@ -6,8 +6,8 @@ module Phlexi
6
6
  module Associations
7
7
  protected
8
8
 
9
- def reflection
10
- @reflection ||= find_association_reflection
9
+ def association_reflection
10
+ @association_reflection ||= find_association_reflection
11
11
  end
12
12
 
13
13
  def find_association_reflection
@@ -20,19 +20,19 @@ module Phlexi
20
20
  end
21
21
 
22
22
  def collection_value_from_association
23
- return unless reflection
23
+ return unless association_reflection
24
24
 
25
- relation = reflection.klass.all
25
+ relation = association_reflection.klass.all
26
26
 
27
- if reflection.respond_to?(:scope) && reflection.scope
28
- relation = if reflection.scope.parameters.any?
29
- reflection.klass.instance_exec(object, &reflection.scope)
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
- reflection.klass.instance_exec(&reflection.scope)
31
+ association_reflection.klass.instance_exec(&association_reflection.scope)
32
32
  end
33
33
  else
34
- order = reflection.options[:order]
35
- conditions = reflection.options[: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 show_errors?
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
- reflection ? object.errors[reflection.name] : []
73
+ association_reflection ? object.errors[association_reflection.name] : []
70
74
  end
71
75
 
72
76
  def full_errors_on_association
73
- reflection ? object.errors.full_messages_for(reflection.name) : []
77
+ association_reflection ? object.errors.full_messages_for(association_reflection.name) : []
74
78
  end
75
79
 
76
80
  def has_custom_error?
@@ -14,7 +14,11 @@ module Phlexi
14
14
  end
15
15
 
16
16
  def has_hint?
17
- options[:hint] != false && hint.present?
17
+ hint.present?
18
+ end
19
+
20
+ def show_hint?
21
+ has_hint? && !show_errors?
18
22
  end
19
23
  end
20
24
  end
@@ -10,20 +10,22 @@ module Phlexi
10
10
  @inferred_db_type ||= infer_db_type
11
11
  end
12
12
 
13
- def inferred_input_component
14
- @inferred_input_component ||= infer_input_component
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
- def inferred_input_type
18
- @inferred_input_type ||= infer_input_type(inferred_input_component)
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, :botton
25
- def infer_input_component
26
- return :select unless collection.blank?
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 infer_input_type(component)
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, BigDecimal
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 reflection&.macro == :has_many
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 + reflection_validators).any? { |v| v.kind == :presence && valid_validator?(v) }
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 reflection_validators
18
- reflection ? object.class.validators_on(reflection.name) : []
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 now dependencies on Rails.
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 seperates
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(**attributes)
61
- create_component(Components::Label, :label, **attributes)
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(**attributes)
69
- create_component(Components::Input, :input, **attributes)
68
+ def input_tag(**, &)
69
+ create_component(Components::Input, :input, **, &)
70
70
  end
71
71
 
72
- def file_input_tag(**attributes)
73
- create_component(Components::FileInput, :file, **attributes)
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(**attributes)
81
- create_component(Components::Checkbox, :checkbox, **attributes)
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(**attributes, &)
90
- create_component(Components::CollectionCheckboxes, :collection_checkboxes, **attributes, &)
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(**attributes)
98
- create_component(Components::RadioButton, :radio, **attributes)
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(**attributes, &)
107
- create_component(Components::CollectionRadioButtons, :collection_radio_buttons, **attributes, &)
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(**attributes)
115
- create_component(Components::Textarea, :textarea, **attributes)
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(**attributes)
123
- create_component(Components::Select, :select, **attributes)
122
+ def select_tag(**, &)
123
+ create_component(Components::Select, :select, **, &)
124
124
  end
125
125
 
126
- def input_array_tag(**attributes)
127
- create_component(Components::InputArray, :array, **attributes)
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(**attributes)
135
- create_component(Components::Hint, :hint, **attributes)
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(**attributes)
143
- create_component(Components::Error, :error, **attributes)
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(**attributes)
151
- create_component(Components::FullError, :full_error, **attributes)
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
- wrapper_class = attributes.delete(:class) || themed(attributes.delete(:theme) || :wrapper)
162
- inner[:class] = inner.delete(:class) || themed(inner.delete(:theme) || :inner_wrapper)
163
- Components::Wrapper.new(self, class: wrapper_class, inner: 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
164
  end
165
165
 
166
- # Creates a multi-value field collection.
166
+ # Creates a repeated field collection.
167
167
  #
168
- # @param range [Integer, #to_a] The range of keys for each field. If an integer is passed, keys will begin from 1.
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 multi(range = nil, &)
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(**attributes, &)
180
- create_component(Components::SubmitButton, :submit_button, **attributes, &)
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 @field_input_component
185
+ raise "field##{dom.name} did not define an input component" unless @field_input_extractor
185
186
 
186
- @field_input_component.extract_input(params)
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
- raise "input component already defined: #{@field_input_component.inspect}" if @field_input_component
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
- attributes = input_attributes.deep_merge(attributes)
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 component_class_for(theme_key, attributes)
203
- attributes.delete(:class) || themed(attributes.key?(:theme) ? attributes.delete(:theme) : theme_key)
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
- determine_from_association || determine_value_from_object
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 determine_from_association
221
- return nil unless reflection.present?
227
+ def determine_value_from_association
228
+ return nil unless association_reflection.present?
222
229
 
223
230
  value = object.public_send(key)
224
- case reflection.macro
231
+ case association_reflection.macro
225
232
  when :has_many, :has_and_belongs_to_many
226
- value&.map { |v| v.public_send(reflection.klass.primary_key) }
233
+ value&.map { |v| v.public_send(association_reflection.klass.primary_key) }
227
234
  when :belongs_to, :has_one
228
- value&.public_send(reflection.klass.primary_key)
235
+ value&.public_send(association_reflection.klass.primary_key)
229
236
  else
230
- raise ArgumentError, "Unsupported association type: #{reflection.macro}"
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
- @field.class.new(key, input_attributes: @field.input_attributes, **, parent: @field).tap do |field|
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 and object to values, but doesn't actually have a value itself. For
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
- # Additionally, to access namespaces within a namespace, such as if a `User has_many :addresses` in
11
- # ActiveRecord, the `namespace` method can be called which will return another Namespace object and
12
- # set the current Namespace as the parent.
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 = nil, **attributes, &)
33
- field(key || SecureRandom.hex).submit_button_tag(**attributes, &)
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
- # Superform :user, object: User.new do |form|
45
- # form.nest_one :permission do |permission|
46
- # form.field :role
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.new User.new do |form|
60
- # render form.field(:email).input_tag
61
- # render form.field(:name).input_tag
62
- # form.nest_many :addresses do |address|
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<Hash>] An array of namespace hashes.
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. Not much to it other than it has a `name`
7
- # and `parent` node attribute. Think of it as a tree.
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.to_s.to_sym
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlexi
4
4
  module Form
5
- VERSION = "0.3.0.rc1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
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.rc1
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-08-08 00:00:00.000000000 Z
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.5.11
264
+ rubygems_version: 3.4.10
264
265
  signing_key:
265
266
  specification_version: 4
266
267
  summary: Build forms in Rails