funicular 0.2.0 → 0.2.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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Funicular
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class FormForTest < Minitest::Test
6
+ def setup
7
+ Funicular::SSR::Runtime.load_framework!
8
+ end
9
+
10
+ def component_class
11
+ Class.new(Funicular::Component) do
12
+ attr_reader :submitted
13
+
14
+ def initialize_state
15
+ { comment: { body: "" } }
16
+ end
17
+
18
+ def handle_submit(data)
19
+ @submitted = data
20
+ end
21
+
22
+ def render
23
+ form_for(:comment, on_submit: :handle_submit) do |f|
24
+ f.textarea(:body)
25
+ f.submit("Post")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ class SubmitEvent
32
+ attr_reader :target
33
+
34
+ def initialize(target)
35
+ @target = target
36
+ @prevented = false
37
+ end
38
+
39
+ def preventDefault
40
+ @prevented = true
41
+ end
42
+
43
+ def prevented?
44
+ @prevented
45
+ end
46
+
47
+ def [](key)
48
+ key == :target ? @target : nil
49
+ end
50
+ end
51
+
52
+ class FormElement
53
+ def initialize(elements)
54
+ @elements = Elements.new(elements)
55
+ end
56
+
57
+ def [](key)
58
+ key == :elements ? @elements : nil
59
+ end
60
+ end
61
+
62
+ class Elements
63
+ def initialize(elements)
64
+ @elements = elements
65
+ end
66
+
67
+ def [](key)
68
+ return @elements.length if key == :length
69
+
70
+ @elements[key]
71
+ end
72
+ end
73
+
74
+ class Field
75
+ def initialize(attrs)
76
+ @attrs = attrs
77
+ end
78
+
79
+ def [](key)
80
+ @attrs[key]
81
+ end
82
+ end
83
+
84
+ def test_form_for_submits_current_dom_values
85
+ component = component_class.new
86
+ vnode = component.build_vdom
87
+ event = SubmitEvent.new(
88
+ FormElement.new([
89
+ Field.new(name: "body", type: "textarea", value: "fresh")
90
+ ])
91
+ )
92
+
93
+ vnode.props[:onsubmit].call(event)
94
+
95
+ assert_predicate event, :prevented?
96
+ assert_equal({ body: "fresh" }, component.submitted)
97
+ end
98
+
99
+ def test_form_builder_fields_include_names
100
+ component = component_class.new
101
+ vnode = component.build_vdom
102
+ textarea = vnode.children[0].children[0]
103
+
104
+ assert_equal "body", textarea.props[:name]
105
+ end
106
+ end
data/mrblib/component.rb CHANGED
@@ -854,20 +854,7 @@ module Funicular
854
854
  ->(event) do
855
855
  event.preventDefault
856
856
 
857
- # Collect form data from state
858
- model_data = state.send(model_key)
859
- form_data = if model_data.is_a?(Hash)
860
- model_data
861
- elsif model_data.respond_to?(:instance_variables)
862
- data = {} #: Hash[Symbol, untyped]
863
- model_data.instance_variables.each do |var|
864
- key = var.to_s.sub('@', '').to_sym
865
- data[key] = model_data.instance_variable_get(var)
866
- end
867
- data
868
- else
869
- {} #: Hash[Symbol, untyped]
870
- end
857
+ form_data = collect_form_data(event, model_key)
871
858
 
872
859
  # Call the submit handler (Symbol, Method, or Proc)
873
860
  case on_submit
@@ -892,6 +879,79 @@ module Funicular
892
879
  end
893
880
  end
894
881
 
882
+ def collect_form_data(event, model_key)
883
+ form_data = collect_dom_form_data(event)
884
+ return form_data unless form_data.empty?
885
+
886
+ collect_state_form_data(model_key)
887
+ end
888
+
889
+ def collect_dom_form_data(event)
890
+ form = event_target(event)
891
+ return {} unless form
892
+
893
+ elements = form[:elements]
894
+ return {} unless elements
895
+
896
+ data = {} #: Hash[Symbol, untyped]
897
+ length_value = elements[:length]
898
+ length = length_value ? length_value.to_i : 0
899
+ i = 0
900
+ while i < length
901
+ field = elements[i]
902
+ add_form_field_value(data, field) if field
903
+ i += 1
904
+ end
905
+ data
906
+ end
907
+
908
+ def event_target(event)
909
+ target = nil
910
+ begin
911
+ target = event[:target]
912
+ rescue
913
+ target = nil
914
+ end
915
+ return target if target
916
+
917
+ event.target if event.respond_to?(:target)
918
+ end
919
+
920
+ def add_form_field_value(data, field)
921
+ name = field[:name].to_s
922
+ return if name.empty?
923
+
924
+ type = field[:type].to_s.downcase
925
+ return if %w[submit button reset].include?(type)
926
+ return if type == "radio" && !field[:checked]
927
+
928
+ value = if type == "checkbox"
929
+ field[:checked]
930
+ elsif type == "file"
931
+ field[:files]
932
+ else
933
+ field[:value]
934
+ end
935
+
936
+ data[name.to_sym] = value
937
+ end
938
+
939
+ def collect_state_form_data(model_key)
940
+ model_data = state.send(model_key)
941
+ if model_data.is_a?(Hash)
942
+ model_data
943
+ elsif model_data.respond_to?(:instance_variables)
944
+ data = {} #: Hash[Symbol, untyped]
945
+ model_data.instance_variables.each do |var|
946
+ key = var.to_s.sub('@', '').to_sym
947
+ data[key] = model_data.instance_variable_get(var)
948
+ end
949
+ data
950
+ else
951
+ {} #: Hash[Symbol, untyped]
952
+ end
953
+ end
954
+
895
955
  # Rails-style link_to helper
896
956
  # Default behavior: Fetch API for same-page actions (SPA-friendly)
897
957
  # Use navigate: true for router navigation (different component tree)
@@ -53,6 +53,7 @@ module Funicular
53
53
 
54
54
  # Build field attributes
55
55
  attrs = {
56
+ name: field_options[:name] || field_key,
56
57
  type: field_type,
57
58
  value: value,
58
59
  oninput: on_input
@@ -117,13 +118,15 @@ module Funicular
117
118
  end
118
119
 
119
120
  attrs = {
121
+ name: options[:name] || field_key,
122
+ value: value,
120
123
  oninput: on_input
121
124
  }.merge(options.reject { |k, _| k == :class })
122
125
 
123
126
  attrs[:class] = css_class unless css_class.empty?
124
127
 
125
128
  @component.div do
126
- @component.textarea(attrs) { value }
129
+ @component.textarea(attrs)
127
130
  if has_error
128
131
  @component.div(class: @error_class) { error_message }
129
132
  end
@@ -142,6 +145,7 @@ module Funicular
142
145
  end
143
146
 
144
147
  attrs = {
148
+ name: options[:name] || field_key,
145
149
  type: "checkbox",
146
150
  checked: value,
147
151
  onchange: on_change
@@ -180,6 +184,7 @@ module Funicular
180
184
  end
181
185
 
182
186
  attrs = {
187
+ name: options[:name] || field_key,
183
188
  onchange: on_change
184
189
  }.merge(options.reject { |k, _| k == :class })
185
190
 
@@ -222,6 +227,7 @@ module Funicular
222
227
  on_change = custom_handler || default_handler
223
228
 
224
229
  attrs = {
230
+ name: options[:name] || field_key,
225
231
  type: "file",
226
232
  onchange: on_change
227
233
  }.merge(options)
data/mrblib/patcher.rb CHANGED
@@ -187,8 +187,10 @@ module Funicular
187
187
  if BOOLEAN_ATTRIBUTES.include?(key_str)
188
188
  if value.nil? || value.to_s == "false"
189
189
  element.removeAttribute(key_str)
190
+ element[key_str] = false
190
191
  else
191
192
  element.setAttribute(key_str, key_str)
193
+ element[key_str] = true
192
194
  end
193
195
  elsif value.nil?
194
196
  element.removeAttribute(key_str)
@@ -232,8 +234,10 @@ module Funicular
232
234
  element[:value] = value.to_s
233
235
  elsif BOOLEAN_ATTRIBUTES.include?(key_str)
234
236
  if value.nil? || value.to_s == "false"
237
+ element[key_str] = false
235
238
  else
236
239
  element.setAttribute(key_str, key_str)
240
+ element[key_str] = true
237
241
  end
238
242
  else
239
243
  element.setAttribute(key_str, value.to_s)
data/mrblib/vdom.rb CHANGED
@@ -132,8 +132,10 @@ module Funicular
132
132
  # Handle boolean attributes
133
133
  if value.nil? || value.to_s == "false"
134
134
  # Do not set attribute (leave it absent)
135
+ dom_node[key_str] = false
135
136
  else
136
137
  dom_node.setAttribute(key_str, key_str)
138
+ dom_node[key_str] = true
137
139
  end
138
140
  else
139
141
  # Attribute
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: funicular
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - HASUMI Hitoshi
@@ -98,6 +98,7 @@ files:
98
98
  - lib/tasks/funicular.rake
99
99
  - minitest/fixtures/funicular_app/components/greeting_component.rb
100
100
  - minitest/fixtures/funicular_app/initializer.rb
101
+ - minitest/form_for_test.rb
101
102
  - minitest/funicular_test.rb
102
103
  - minitest/hydration_test.rb
103
104
  - minitest/plugin_test.rb