funicular 0.3.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +486 -1
- data/demo/local_notes.html +207 -0
- data/demo/test_chartjs.html +9 -9
- data/demo/test_component.html +8 -8
- data/demo/test_error_boundary.html +44 -41
- data/demo/test_router.html +48 -48
- data/demo/tic-tac-toe.html +25 -25
- data/docs/architecture.md +227 -12
- data/docs/local_database.md +1035 -0
- data/lib/funicular/assets/funicular.rb +14 -0
- data/lib/funicular/configuration.rb +65 -0
- data/lib/funicular/epoch_header.rb +69 -0
- data/lib/funicular/epoch_stamping.rb +66 -0
- data/lib/funicular/helpers/picoruby_helper.rb +96 -1
- data/lib/funicular/railtie.rb +30 -0
- data/lib/funicular/schema.rb +45 -12
- data/lib/funicular/session_epoch.rb +110 -0
- data/lib/funicular/ssr/runtime.rb +58 -12
- data/lib/funicular/ssr.rb +25 -0
- data/lib/funicular/testing/node_runner.mjs +19 -0
- data/lib/funicular/testing.rb +47 -0
- data/lib/funicular/vendor/mrbc/VERSION +1 -1
- data/lib/funicular/vendor/mrbc/mrbc.js +655 -574
- data/lib/funicular/vendor/mrbc/mrbc.wasm +0 -0
- data/lib/funicular/vendor/picoruby/VERSION +1 -1
- data/lib/funicular/vendor/picoruby/debug/picoruby.js +800 -530
- data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby/dist/picoruby.js +2 -2
- data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby-test-node/VERSION +1 -1
- data/lib/funicular/vendor/picoruby-test-node/picoruby.js +2 -6909
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
- data/lib/funicular/version.rb +1 -1
- data/lib/funicular.rb +1 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +37 -38
- data/lib/tasks/funicular.rake +10 -2
- data/minitest/callback_error_visibility_test.rb +48 -0
- data/minitest/configuration_test.rb +78 -0
- data/minitest/dsl_test.rb +264 -0
- data/minitest/epoch_header_test.rb +149 -0
- data/minitest/epoch_stamping_test.rb +225 -0
- data/minitest/fixtures/funicular_app/components/greeting_component.rb +5 -5
- data/minitest/fixtures/funicular_app/components/probe_component.rb +15 -0
- data/minitest/form_for_test.rb +2 -2
- data/minitest/hydration_test.rb +2 -2
- data/minitest/navigation_guard_test.rb +65 -0
- data/minitest/picoruby_helper_test.rb +236 -0
- data/minitest/schema_test.rb +47 -0
- data/minitest/session_epoch_test.rb +122 -0
- data/minitest/sig_tags_test.rb +30 -0
- data/minitest/ssr_database_test.rb +78 -0
- data/minitest/ssr_reload_test.rb +106 -0
- data/minitest/ssr_test.rb +41 -0
- data/minitest/testing_ensure_compiled_test.rb +52 -0
- data/minitest/validations_test.rb +35 -5
- data/minitest/view_context_test.rb +15 -15
- data/mrbgem.rake +2 -0
- data/mrblib/0_tags.rb +62 -0
- data/mrblib/cable.rb +1 -1
- data/mrblib/component.rb +226 -24
- data/mrblib/db.rb +3116 -0
- data/mrblib/error_boundary.rb +25 -19
- data/mrblib/file_upload.rb +17 -7
- data/mrblib/form_builder.rb +10 -10
- data/mrblib/funicular.rb +136 -17
- data/mrblib/http.rb +84 -107
- data/mrblib/model.rb +1178 -23
- data/mrblib/relation.rb +342 -0
- data/mrblib/router.rb +45 -4
- data/mrblib/styles.rb +122 -12
- data/mrblib/view_context.rb +3 -32
- data/sig/component.rbs +25 -4
- data/sig/db.rbs +328 -0
- data/sig/error_boundary.rbs +4 -4
- data/sig/funicular.rbs +5 -0
- data/sig/http.rbs +8 -21
- data/sig/model.rbs +101 -7
- data/sig/relation.rbs +44 -0
- data/sig/router.rbs +1 -0
- data/sig/styles.rbs +19 -5
- data/sig/tags.rbs +54 -0
- data/sig/view_context.rbs +47 -34
- metadata +23 -2
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +0 -1
data/mrblib/component.rb
CHANGED
|
@@ -52,9 +52,60 @@ module Funicular
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
include Tags
|
|
56
|
+
|
|
55
57
|
attr_accessor :props, :vdom, :dom_element, :mounted, :runtime, :children, :current_children
|
|
56
58
|
attr_reader :refs
|
|
57
59
|
|
|
60
|
+
# Opt out of DSL collision detection for the given method names. The
|
|
61
|
+
# shadowed tag remains reachable through tag(:name, ...).
|
|
62
|
+
def self.allow_dsl_override(*names)
|
|
63
|
+
@dsl_overrides ||= [] #: Array[Symbol]
|
|
64
|
+
@dsl_overrides.concat(names)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.dsl_overrides
|
|
68
|
+
@dsl_overrides ||= [] #: Array[Symbol]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Layer 1: catches `def`, `define_method`, and `alias` in subclasses at
|
|
72
|
+
# class-definition time. attr_* does not fire this hook on mruby, and
|
|
73
|
+
# included modules never do; validate_dsl_conflicts! covers those.
|
|
74
|
+
def self.method_added(name)
|
|
75
|
+
if self != Funicular::Component && Funicular::Tags::RESERVED_DSL[name] && !dsl_overrides.include?(name)
|
|
76
|
+
kind = Funicular::Tags::RESERVED_DSL[name] == :tag ? "<#{name}> tag helper" : "##{name} helper"
|
|
77
|
+
raise Funicular::DSLCollisionError,
|
|
78
|
+
"#{self}##{name} collides with the Funicular DSL (#{kind}). " \
|
|
79
|
+
"Rename it (e.g. `#{name}_value`), or declare `allow_dsl_override :#{name}` " \
|
|
80
|
+
"and use `tag(:#{name}, ...)` to emit the element."
|
|
81
|
+
end
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Layer 2: once per class, sweep methods the method_added hook cannot
|
|
86
|
+
# see (attr_* on mruby, user-included modules).
|
|
87
|
+
def self.validate_dsl_conflicts!
|
|
88
|
+
return if @dsl_validated
|
|
89
|
+
if instance_method(:render).arity != 0
|
|
90
|
+
raise Funicular::DSLCollisionError,
|
|
91
|
+
"#{self}#render must not take parameters as of Funicular 0.4.0: " \
|
|
92
|
+
"delete the parameter (`def render`) and drop the `h.` receivers " \
|
|
93
|
+
"inside; tags are bareword methods on the component now."
|
|
94
|
+
end
|
|
95
|
+
ancestors.each do |mod|
|
|
96
|
+
break if mod == Funicular::Component || mod == Funicular::Tags
|
|
97
|
+
next unless mod.is_a?(Module)
|
|
98
|
+
methods = mod.instance_methods(false) + mod.private_instance_methods(false)
|
|
99
|
+
offenders = methods.select { |m| Funicular::Tags::RESERVED_DSL[m] } - dsl_overrides
|
|
100
|
+
next if offenders.empty?
|
|
101
|
+
raise Funicular::DSLCollisionError,
|
|
102
|
+
"#{mod} defines methods that collide with the Funicular DSL: " \
|
|
103
|
+
"#{offenders.map { |m| m.to_s }.join(', ')}. Rename them, or declare " \
|
|
104
|
+
"`allow_dsl_override` and use `tag(...)` to emit the element."
|
|
105
|
+
end
|
|
106
|
+
@dsl_validated = true
|
|
107
|
+
end
|
|
108
|
+
|
|
58
109
|
def initialize(props = {})
|
|
59
110
|
@props = props
|
|
60
111
|
@state = initialize_state || {}
|
|
@@ -93,7 +144,7 @@ module Funicular
|
|
|
93
144
|
end
|
|
94
145
|
|
|
95
146
|
def styles
|
|
96
|
-
@style_accessor ||=
|
|
147
|
+
@style_accessor ||= self.class.style_accessor_class.new(self.class.styles_definitions)
|
|
97
148
|
end
|
|
98
149
|
|
|
99
150
|
# Override this method in subclasses to define initial state
|
|
@@ -120,6 +171,72 @@ module Funicular
|
|
|
120
171
|
self
|
|
121
172
|
end
|
|
122
173
|
|
|
174
|
+
# Bind a state key to a local-database Relation (docs decision 10):
|
|
175
|
+
# the block runs once now, and again after every change event on the
|
|
176
|
+
# relation's table. Each evaluation re-subscribes (a branchy block
|
|
177
|
+
# may return a different model's relation this time) and patches
|
|
178
|
+
# state[key], triggering a re-render. Subscriptions die with the
|
|
179
|
+
# component (see unmount), even when a lifecycle hook raises.
|
|
180
|
+
def watch(key, &block)
|
|
181
|
+
unless block
|
|
182
|
+
raise ArgumentError, "watch requires a block returning a Relation"
|
|
183
|
+
end
|
|
184
|
+
evaluate_watch(key, block)
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Framework use (the subscription re-enters here on every event).
|
|
189
|
+
def evaluate_watch(key, block)
|
|
190
|
+
relation = block.call
|
|
191
|
+
unless relation.is_a?(Funicular::Relation)
|
|
192
|
+
raise ArgumentError,
|
|
193
|
+
"the watch(:#{key}) block must return a Funicular::Relation, " \
|
|
194
|
+
"got #{relation.class}; for hashes, counts, or raw SQL, " \
|
|
195
|
+
"subscribe with Model.on_change and patch state yourself"
|
|
196
|
+
end
|
|
197
|
+
# Materialize BEFORE touching subscriptions: if the query raises,
|
|
198
|
+
# neither a fresh subscription leaks (initial watch) nor the
|
|
199
|
+
# previous healthy one is lost (re-evaluation). Next-tick delivery
|
|
200
|
+
# guarantees no change event interleaves with this evaluation.
|
|
201
|
+
records = relation.to_a
|
|
202
|
+
source = relation.__event_source
|
|
203
|
+
watches = (@__watches ||= {}) # steep:ignore UnannotatedEmptyCollection
|
|
204
|
+
old = watches[key]
|
|
205
|
+
Funicular::DB.unsubscribe(old) if old
|
|
206
|
+
component = self
|
|
207
|
+
watches[key] = Funicular::DB.subscribe(source[0], source[1]) do |r, t|
|
|
208
|
+
component.evaluate_watch(key, block)
|
|
209
|
+
end
|
|
210
|
+
if @mounted
|
|
211
|
+
# The normal update contract: patch runs the update lifecycle
|
|
212
|
+
# (component_will_update/component_updated/component_raised),
|
|
213
|
+
# normalizes values, and re-renders. The bus's next-tick
|
|
214
|
+
# delivery guarantees we are never inside another update here.
|
|
215
|
+
patch(key => records)
|
|
216
|
+
else
|
|
217
|
+
# Before mount there is no update lifecycle to honor yet: land
|
|
218
|
+
# the initial data directly so state is ready for the first
|
|
219
|
+
# render.
|
|
220
|
+
@state = @state.merge({ key => records })
|
|
221
|
+
@state_accessor = nil
|
|
222
|
+
end
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def cleanup_watches
|
|
227
|
+
watches = @__watches
|
|
228
|
+
return nil unless watches
|
|
229
|
+
keys = watches.keys
|
|
230
|
+
keys_size = keys.size
|
|
231
|
+
i = 0
|
|
232
|
+
while i < keys_size
|
|
233
|
+
Funicular::DB.unsubscribe(watches[keys[i]])
|
|
234
|
+
i += 1
|
|
235
|
+
end
|
|
236
|
+
@__watches = {}
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
|
|
123
240
|
# Load all registered suspense data
|
|
124
241
|
# Called automatically in component_mounted if suspense definitions exist
|
|
125
242
|
def load_suspense_data
|
|
@@ -230,21 +347,21 @@ module Funicular
|
|
|
230
347
|
# ) do
|
|
231
348
|
# div { user.name }
|
|
232
349
|
# end
|
|
233
|
-
def render_suspense(
|
|
350
|
+
def render_suspense(name, fallback:, error: nil, &block)
|
|
234
351
|
current_children = @current_children
|
|
235
352
|
child_count_before = current_children&.size
|
|
236
353
|
result = nil
|
|
237
354
|
|
|
238
355
|
if @suspense_states[name] == :rejected
|
|
239
356
|
result = if error
|
|
240
|
-
error.call(
|
|
357
|
+
error.call(@suspense_errors[name])
|
|
241
358
|
else
|
|
242
|
-
fallback.call
|
|
359
|
+
fallback.call
|
|
243
360
|
end
|
|
244
361
|
elsif suspense_loading?(name)
|
|
245
|
-
result = fallback.call
|
|
362
|
+
result = fallback.call
|
|
246
363
|
else
|
|
247
|
-
result = block.call(
|
|
364
|
+
result = block.call(resources)
|
|
248
365
|
end
|
|
249
366
|
|
|
250
367
|
if current_children && current_children.size == child_count_before
|
|
@@ -254,17 +371,25 @@ module Funicular
|
|
|
254
371
|
result
|
|
255
372
|
end
|
|
256
373
|
|
|
257
|
-
# Class methods for styles DSL
|
|
374
|
+
# Class methods for styles DSL. The block is instance_exec'd on a
|
|
375
|
+
# StyleBuilder cleanroom so barewords define styles; it also receives
|
|
376
|
+
# the builder for the explicit `styles { |css| css.define(...) }` form.
|
|
258
377
|
def self.styles(&block)
|
|
259
378
|
builder = StyleBuilder.new
|
|
260
|
-
|
|
379
|
+
builder.instance_exec(builder, &block) # steep:ignore
|
|
261
380
|
@styles_definitions = builder.to_definitions
|
|
381
|
+
@style_accessor_class = nil
|
|
262
382
|
end
|
|
263
383
|
|
|
264
384
|
def self.styles_definitions
|
|
265
385
|
@styles_definitions ||= {}
|
|
266
386
|
end
|
|
267
387
|
|
|
388
|
+
# Per-class accessor with one real method per declared style name.
|
|
389
|
+
def self.style_accessor_class
|
|
390
|
+
@style_accessor_class ||= StyleAccessor.accessor_for(styles_definitions)
|
|
391
|
+
end
|
|
392
|
+
|
|
268
393
|
# Suspense DSL - register async data loaders
|
|
269
394
|
#
|
|
270
395
|
# @param name [Symbol] Name of the suspense data (becomes accessible as method)
|
|
@@ -349,6 +474,10 @@ module Funicular
|
|
|
349
474
|
|
|
350
475
|
component_mounted if respond_to?(:component_mounted)
|
|
351
476
|
rescue => e
|
|
477
|
+
# A failed mount never reaches unmount (@mounted is still
|
|
478
|
+
# false), so watch subscriptions taken before/during the mount
|
|
479
|
+
# must be released here or they would pin the dead component.
|
|
480
|
+
cleanup_watches
|
|
352
481
|
component_raised(e) if respond_to?(:component_raised)
|
|
353
482
|
raise e
|
|
354
483
|
end
|
|
@@ -364,9 +493,12 @@ module Funicular
|
|
|
364
493
|
# window.__FUNICULAR_STATE__ before calling this.
|
|
365
494
|
def hydrate(dom_element)
|
|
366
495
|
return if @mounted
|
|
367
|
-
raise "hydrate: missing server DOM element" unless dom_element
|
|
368
496
|
|
|
369
497
|
begin
|
|
498
|
+
# Inside the begin so a nil element takes the same cleanup path
|
|
499
|
+
# (releasing watch subscriptions) as every other hydrate failure.
|
|
500
|
+
raise "hydrate: missing server DOM element" unless dom_element
|
|
501
|
+
|
|
370
502
|
component_will_mount if respond_to?(:component_will_mount)
|
|
371
503
|
|
|
372
504
|
# The router/start sets the container; without it, unmount could not
|
|
@@ -408,11 +540,30 @@ module Funicular
|
|
|
408
540
|
|
|
409
541
|
component_mounted if respond_to?(:component_mounted)
|
|
410
542
|
rescue => e
|
|
543
|
+
# Same as mount: a failed hydrate cannot be unmounted, so the
|
|
544
|
+
# watch subscriptions must not outlive it.
|
|
545
|
+
cleanup_watches
|
|
411
546
|
component_raised(e) if respond_to?(:component_raised)
|
|
412
547
|
raise e
|
|
413
548
|
end
|
|
414
549
|
end
|
|
415
550
|
|
|
551
|
+
# Navigation guard: return a String message to ask the user for
|
|
552
|
+
# confirmation before this component is navigated away from (router
|
|
553
|
+
# navigation, browser back/forward, reload, tab close), or nil to
|
|
554
|
+
# allow leaving freely. Consulted by the router and its beforeunload
|
|
555
|
+
# listener; override in components with unsaved work:
|
|
556
|
+
#
|
|
557
|
+
# def navigation_guard
|
|
558
|
+
# state[:dirty] ? "Unsaved changes will be lost. Leave?" : nil
|
|
559
|
+
# end
|
|
560
|
+
#
|
|
561
|
+
# Must not suspend (no fetch/HTTP): the beforeunload path runs it on
|
|
562
|
+
# the synchronous JS event dispatch stack.
|
|
563
|
+
def navigation_guard
|
|
564
|
+
nil
|
|
565
|
+
end
|
|
566
|
+
|
|
416
567
|
# Unmount component from DOM
|
|
417
568
|
def unmount
|
|
418
569
|
return unless @mounted
|
|
@@ -443,23 +594,29 @@ module Funicular
|
|
|
443
594
|
rescue => e
|
|
444
595
|
component_raised(e) if respond_to?(:component_raised)
|
|
445
596
|
raise e
|
|
597
|
+
ensure
|
|
598
|
+
# Watch subscriptions die with the component NO MATTER WHAT: a
|
|
599
|
+
# raising lifecycle hook must not leave a zombie watcher
|
|
600
|
+
# patching an unmounted component (docs decision 10).
|
|
601
|
+
cleanup_watches
|
|
446
602
|
end
|
|
447
603
|
end
|
|
448
604
|
|
|
449
605
|
# Override this method in subclasses to define render logic
|
|
450
|
-
def render
|
|
451
|
-
raise "Subclasses must implement render
|
|
606
|
+
def render
|
|
607
|
+
raise "Subclasses must implement render"
|
|
452
608
|
end
|
|
453
609
|
|
|
454
610
|
# Build VDOM tree from render method
|
|
455
611
|
# Called by VDOM::Renderer, Differ, and Patcher
|
|
456
612
|
def build_vdom
|
|
613
|
+
self.class.validate_dsl_conflicts!
|
|
457
614
|
previous_rendering = @rendering
|
|
458
615
|
previous_children = @current_children
|
|
459
616
|
@rendering = true
|
|
460
617
|
@current_children = nil
|
|
461
618
|
begin
|
|
462
|
-
result = render
|
|
619
|
+
result = render
|
|
463
620
|
ensure
|
|
464
621
|
@rendering = previous_rendering
|
|
465
622
|
@current_children = previous_children
|
|
@@ -499,6 +656,7 @@ module Funicular
|
|
|
499
656
|
begin
|
|
500
657
|
self.send(value, event)
|
|
501
658
|
rescue => e
|
|
659
|
+
report_handler_error(event_name, "##{value}", e)
|
|
502
660
|
component_raised(e) if respond_to?(:component_raised)
|
|
503
661
|
raise e
|
|
504
662
|
end
|
|
@@ -515,6 +673,7 @@ module Funicular
|
|
|
515
673
|
value.call(event)
|
|
516
674
|
end
|
|
517
675
|
rescue => e
|
|
676
|
+
report_handler_error(event_name, "##{value.name}", e)
|
|
518
677
|
component_raised(e) if respond_to?(:component_raised)
|
|
519
678
|
raise e
|
|
520
679
|
end
|
|
@@ -531,6 +690,7 @@ module Funicular
|
|
|
531
690
|
value.call(event)
|
|
532
691
|
end
|
|
533
692
|
rescue => e
|
|
693
|
+
report_handler_error(event_name, "(proc)", e)
|
|
534
694
|
component_raised(e) if respond_to?(:component_raised)
|
|
535
695
|
raise e
|
|
536
696
|
end
|
|
@@ -563,6 +723,18 @@ module Funicular
|
|
|
563
723
|
end
|
|
564
724
|
end
|
|
565
725
|
|
|
726
|
+
# Name the culprit before an event handler error is re-raised into
|
|
727
|
+
# the JS bridge, where "Callback <id>: ArgumentError: ..." carries no
|
|
728
|
+
# hint of which component or handler it came from. Uses puts so the
|
|
729
|
+
# message reaches the browser console (same idiom as the
|
|
730
|
+
# ErrorBoundary logger). Never raises itself.
|
|
731
|
+
def report_handler_error(event_name, handler, error)
|
|
732
|
+
puts "[Funicular] #{self.class}#{handler} (on#{event_name}) raised " \
|
|
733
|
+
"#{error.class}: #{error.message}"
|
|
734
|
+
rescue
|
|
735
|
+
nil
|
|
736
|
+
end
|
|
737
|
+
|
|
566
738
|
# Collect ref elements from VDOM
|
|
567
739
|
# Called by VDOM::Renderer and Patcher
|
|
568
740
|
def collect_refs(dom_element, vnode, refs_map = {})
|
|
@@ -622,9 +794,45 @@ module Funicular
|
|
|
622
794
|
normalize_vnode(value)
|
|
623
795
|
end
|
|
624
796
|
|
|
797
|
+
# Internal element factory shared by the Tags mixin, FormBuilder, and
|
|
798
|
+
# the framework helpers. One instance per component; ViewContext itself
|
|
799
|
+
# is stateless (the render cursor lives on the component).
|
|
800
|
+
def __view__
|
|
801
|
+
@__view__ ||= ViewContext.new(self)
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def routes
|
|
805
|
+
@runtime.routes
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# Bareword DSL helpers available inside render (self is the component).
|
|
809
|
+
def component(component_class, props = {}, &block)
|
|
810
|
+
__view__.component(component_class, props, &block)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def form_for(model_key, options = {}, &block)
|
|
814
|
+
build_form_for(__view__, model_key, options, &block)
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
def link_to(path, **options, &block)
|
|
818
|
+
build_link_to(__view__, path, **options, &block)
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def button_to(path, method: :post, **options, &block)
|
|
822
|
+
build_button_to(__view__, path, method: method, **options, &block)
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def suspense(name, fallback:, error: nil, &block)
|
|
826
|
+
render_suspense(name, fallback: fallback, error: error, &block)
|
|
827
|
+
end
|
|
828
|
+
|
|
625
829
|
def add_child_from_view(child)
|
|
830
|
+
unless @rendering
|
|
831
|
+
raise Funicular::RenderContextError,
|
|
832
|
+
"view DSL called outside render (tags may only be used while the component is rendering)"
|
|
833
|
+
end
|
|
626
834
|
current_children = @current_children
|
|
627
|
-
return unless
|
|
835
|
+
return unless current_children
|
|
628
836
|
|
|
629
837
|
normalized = normalize_vnode(child)
|
|
630
838
|
current_children << normalized if normalized
|
|
@@ -654,8 +862,8 @@ module Funicular
|
|
|
654
862
|
->(event) { event.preventDefault }
|
|
655
863
|
end
|
|
656
864
|
|
|
657
|
-
h.form({ onsubmit: submit_handler, class: form_class }.merge(options)) do
|
|
658
|
-
builder = Funicular::FormBuilder.new(self,
|
|
865
|
+
h.form({ onsubmit: submit_handler, class: form_class }.merge(options)) do
|
|
866
|
+
builder = Funicular::FormBuilder.new(self, h, model_key, options)
|
|
659
867
|
block.call(builder)
|
|
660
868
|
end
|
|
661
869
|
end
|
|
@@ -881,15 +1089,9 @@ module Funicular
|
|
|
881
1089
|
end
|
|
882
1090
|
|
|
883
1091
|
def event_target(event)
|
|
884
|
-
target
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
rescue
|
|
888
|
-
target = nil
|
|
889
|
-
end
|
|
890
|
-
return target if target
|
|
891
|
-
|
|
892
|
-
event.target if event.respond_to?(:target)
|
|
1092
|
+
event[:target]
|
|
1093
|
+
rescue
|
|
1094
|
+
nil
|
|
893
1095
|
end
|
|
894
1096
|
|
|
895
1097
|
def add_form_field_value(data, field)
|