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.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/lib/funicular/compiler.rb +2 -3
- data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
- data/lib/funicular/vendor/picoruby/debug/picoruby.js +30 -21
- data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby/dist/picoruby.js +1 -1
- data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby-test-node/picoruby.js +113 -90
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -1
- data/lib/funicular/version.rb +1 -1
- data/minitest/form_for_test.rb +106 -0
- data/mrblib/component.rb +74 -14
- data/mrblib/form_builder.rb +7 -1
- data/mrblib/patcher.rb +4 -0
- data/mrblib/vdom.rb +2 -0
- metadata +2 -1
data/lib/funicular/version.rb
CHANGED
|
@@ -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
|
-
|
|
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)
|
data/mrblib/form_builder.rb
CHANGED
|
@@ -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)
|
|
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.
|
|
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
|