turbo-rails 2.0.6 → 2.0.13
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 +69 -1
- data/app/assets/javascripts/turbo.js +1165 -1033
- data/app/assets/javascripts/turbo.min.js +7 -7
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +17 -6
- data/app/controllers/turbo/native/navigation.rb +11 -8
- data/app/helpers/turbo/streams/action_helper.rb +1 -1
- data/app/helpers/turbo/streams_helper.rb +6 -0
- data/app/javascript/turbo/cable_stream_source_element.js +10 -0
- data/app/models/concerns/turbo/broadcastable.rb +32 -22
- data/app/models/turbo/streams/tag_builder.rb +28 -34
- data/config/routes.rb +3 -3
- data/lib/tasks/turbo_tasks.rake +0 -22
- data/lib/turbo/engine.rb +48 -3
- data/lib/turbo/system_test_helper.rb +128 -0
- data/lib/turbo/version.rb +1 -1
- metadata +9 -23
- data/lib/install/turbo_needs_redis.rb +0 -20
@@ -6,7 +6,7 @@ module Turbo::Streams::Broadcasts
|
|
6
6
|
include Turbo::Streams::ActionHelper
|
7
7
|
|
8
8
|
def broadcast_remove_to(*streamables, **opts)
|
9
|
-
broadcast_action_to(*streamables, action: :remove, **opts)
|
9
|
+
broadcast_action_to(*streamables, action: :remove, render: false, **opts)
|
10
10
|
end
|
11
11
|
|
12
12
|
def broadcast_replace_to(*streamables, **opts)
|
@@ -38,10 +38,9 @@ module Turbo::Streams::Broadcasts
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def broadcast_action_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
|
41
|
-
broadcast_stream_to(*streamables, content: turbo_stream_action_tag(
|
42
|
-
|
43
|
-
|
44
|
-
))
|
41
|
+
broadcast_stream_to(*streamables, content: turbo_stream_action_tag(
|
42
|
+
action, target: target, targets: targets, template: render_broadcast_action(rendering), **attributes)
|
43
|
+
)
|
45
44
|
end
|
46
45
|
|
47
46
|
def broadcast_replace_later_to(*streamables, **opts)
|
@@ -70,7 +69,7 @@ module Turbo::Streams::Broadcasts
|
|
70
69
|
|
71
70
|
def broadcast_refresh_later_to(*streamables, request_id: Turbo.current_request_id, **opts)
|
72
71
|
refresh_debouncer_for(*streamables, request_id: request_id).debounce do
|
73
|
-
Turbo::Streams::BroadcastStreamJob.perform_later stream_name_from(streamables), content: turbo_stream_refresh_tag(request_id: request_id, **opts)
|
72
|
+
Turbo::Streams::BroadcastStreamJob.perform_later stream_name_from(streamables), content: turbo_stream_refresh_tag(request_id: request_id, **opts).to_str # Sidekiq requires job arguments to be valid JSON types, such as String
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
@@ -111,4 +110,16 @@ module Turbo::Streams::Broadcasts
|
|
111
110
|
def render_format(format, **rendering)
|
112
111
|
ApplicationController.render(formats: [ format ], **rendering)
|
113
112
|
end
|
113
|
+
|
114
|
+
def render_broadcast_action(rendering)
|
115
|
+
content = rendering.delete(:content)
|
116
|
+
html = rendering.delete(:html)
|
117
|
+
render = rendering.delete(:render)
|
118
|
+
|
119
|
+
if render == false
|
120
|
+
nil
|
121
|
+
else
|
122
|
+
content || html || (render_format(:html, **rendering) if rendering.present?)
|
123
|
+
end
|
124
|
+
end
|
114
125
|
end
|
@@ -1,20 +1,23 @@
|
|
1
1
|
# Turbo is built to work with native navigation principles and present those alongside what's required for the web. When you
|
2
|
-
# have
|
3
|
-
# requests with three dedicated responses: <tt>recede</tt>, <tt>resume</tt>, <tt>refresh</tt>.
|
2
|
+
# have Hotwire Native clients running (see the Hotwire Native iOS and Hotwire Native Android projects for details),
|
3
|
+
# you can respond to native requests with three dedicated responses: <tt>recede</tt>, <tt>resume</tt>, <tt>refresh</tt>.
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Hotwire Native Android and Hotwire Native iOS handle these actions automatically.
|
6
6
|
module Turbo::Native::Navigation
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
included do
|
10
|
-
helper_method :turbo_native_app?
|
10
|
+
helper_method :hotwire_native_app?, :turbo_native_app?
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
14
|
-
|
15
|
-
|
13
|
+
# Hotwire Native applications are identified by having the string "Hotwire Native" as part of their user agent.
|
14
|
+
# Legacy Turbo Native applications use the "Turbo Native" string.
|
15
|
+
def hotwire_native_app?
|
16
|
+
request.user_agent.to_s.match?(/(Turbo|Hotwire) Native/)
|
16
17
|
end
|
17
|
-
|
18
|
+
|
19
|
+
alias_method :turbo_native_app?, :hotwire_native_app?
|
20
|
+
|
18
21
|
# Tell the Turbo Native app to dismiss a modal (if presented) or pop a screen off of the navigation stack. Otherwise redirect to the given URL if Turbo Native is not present.
|
19
22
|
def recede_or_redirect_to(url, **options)
|
20
23
|
turbo_native_action_or_redirect url, :recede, :to, options
|
@@ -39,7 +39,7 @@ module Turbo::Streams::ActionHelper
|
|
39
39
|
# turbo_stream_refresh_tag
|
40
40
|
# # => <turbo-stream action="refresh"></turbo-stream>
|
41
41
|
def turbo_stream_refresh_tag(request_id: Turbo.current_request_id, **attributes)
|
42
|
-
turbo_stream_action_tag(:refresh,
|
42
|
+
turbo_stream_action_tag(:refresh, "request-id": request_id.presence, **attributes)
|
43
43
|
end
|
44
44
|
|
45
45
|
private
|
@@ -48,7 +48,13 @@ module Turbo::StreamsHelper
|
|
48
48
|
# It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
|
49
49
|
#
|
50
50
|
# <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
|
51
|
+
#
|
52
|
+
# Raises an +ArgumentError+ if all streamables are blank
|
53
|
+
#
|
54
|
+
# <%= turbo_stream_from("") %> # => ArgumentError: streamables can't be blank
|
55
|
+
# <%= turbo_stream_from("", nil) %> # => ArgumentError: streamables can't be blank
|
51
56
|
def turbo_stream_from(*streamables, **attributes)
|
57
|
+
raise ArgumentError, "streamables can't be blank" unless streamables.any?(&:present?)
|
52
58
|
attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
|
53
59
|
attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
|
54
60
|
|
@@ -3,6 +3,8 @@ import { subscribeTo } from "./cable"
|
|
3
3
|
import snakeize from "./snakeize"
|
4
4
|
|
5
5
|
class TurboCableStreamSourceElement extends HTMLElement {
|
6
|
+
static observedAttributes = ["channel", "signed-stream-name"]
|
7
|
+
|
6
8
|
async connectedCallback() {
|
7
9
|
connectStreamSource(this)
|
8
10
|
this.subscription = await subscribeTo(this.channel, {
|
@@ -15,6 +17,14 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
15
17
|
disconnectedCallback() {
|
16
18
|
disconnectStreamSource(this)
|
17
19
|
if (this.subscription) this.subscription.unsubscribe()
|
20
|
+
this.subscriptionDisconnected()
|
21
|
+
}
|
22
|
+
|
23
|
+
attributeChangedCallback() {
|
24
|
+
if (this.subscription) {
|
25
|
+
this.disconnectedCallback()
|
26
|
+
this.connectedCallback()
|
27
|
+
}
|
18
28
|
}
|
19
29
|
|
20
30
|
dispatchMessageEvent(data) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records).
|
1
|
+
# Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records if ActiveJob is loaded).
|
2
2
|
# This makes it convenient to execute both synchronous and asynchronous updates, and render directly from callbacks in models
|
3
3
|
# or from controllers or jobs that act on those models. Here's an example:
|
4
4
|
#
|
@@ -241,13 +241,13 @@ module Turbo::Broadcastable
|
|
241
241
|
#
|
242
242
|
# # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
|
243
243
|
# clearance.broadcast_remove_to examiner.identity, :clearances
|
244
|
-
def broadcast_remove_to(*streamables, target: self)
|
245
|
-
Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
|
244
|
+
def broadcast_remove_to(*streamables, target: self, **rendering)
|
245
|
+
Turbo::StreamsChannel.broadcast_remove_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
246
246
|
end
|
247
247
|
|
248
248
|
# Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
|
249
|
-
def broadcast_remove
|
250
|
-
broadcast_remove_to self
|
249
|
+
def broadcast_remove(**rendering)
|
250
|
+
broadcast_remove_to self, **rendering
|
251
251
|
end
|
252
252
|
|
253
253
|
# Replace this broadcastable model in the dom for subscribers of the stream name identified by the passed
|
@@ -265,7 +265,7 @@ module Turbo::Broadcastable
|
|
265
265
|
# # to the stream named "identity:2:clearances"
|
266
266
|
# clearance.broadcast_replace_to examiner.identity, :clearance, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
267
267
|
def broadcast_replace_to(*streamables, **rendering)
|
268
|
-
Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self
|
268
|
+
Turbo::StreamsChannel.broadcast_replace_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
269
269
|
end
|
270
270
|
|
271
271
|
# Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -288,7 +288,7 @@ module Turbo::Broadcastable
|
|
288
288
|
# # to the stream named "identity:2:clearances"
|
289
289
|
# # clearance.broadcast_update_to examiner.identity, :clearances, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
290
290
|
def broadcast_update_to(*streamables, **rendering)
|
291
|
-
Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self
|
291
|
+
Turbo::StreamsChannel.broadcast_update_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
292
292
|
end
|
293
293
|
|
294
294
|
# Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -308,8 +308,10 @@ module Turbo::Broadcastable
|
|
308
308
|
# # to the stream named "identity:2:clearances"
|
309
309
|
# clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5",
|
310
310
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
311
|
-
def broadcast_before_to(*streamables, target
|
312
|
-
|
311
|
+
def broadcast_before_to(*streamables, target: nil, targets: nil, **rendering)
|
312
|
+
raise ArgumentError, "at least one of target or targets is required" unless target || targets
|
313
|
+
|
314
|
+
Turbo::StreamsChannel.broadcast_before_to(*streamables, **extract_options_and_add_target(rendering.merge(target: target, targets: targets)))
|
313
315
|
end
|
314
316
|
|
315
317
|
# Insert a rendering of this broadcastable model after the target identified by it's dom id passed as <tt>target</tt>
|
@@ -324,8 +326,10 @@ module Turbo::Broadcastable
|
|
324
326
|
# # to the stream named "identity:2:clearances"
|
325
327
|
# clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5",
|
326
328
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
327
|
-
def broadcast_after_to(*streamables, target
|
328
|
-
|
329
|
+
def broadcast_after_to(*streamables, target: nil, targets: nil, **rendering)
|
330
|
+
raise ArgumentError, "at least one of target or targets is required" unless target || targets
|
331
|
+
|
332
|
+
Turbo::StreamsChannel.broadcast_after_to(*streamables, **extract_options_and_add_target(rendering.merge(target: target, targets: targets)))
|
329
333
|
end
|
330
334
|
|
331
335
|
# Append a rendering of this broadcastable model to the target identified by it's dom id passed as <tt>target</tt>
|
@@ -341,7 +345,7 @@ module Turbo::Broadcastable
|
|
341
345
|
# clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
|
342
346
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
343
347
|
def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
|
344
|
-
Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target
|
348
|
+
Turbo::StreamsChannel.broadcast_append_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
345
349
|
end
|
346
350
|
|
347
351
|
# Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -362,7 +366,7 @@ module Turbo::Broadcastable
|
|
362
366
|
# clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
|
363
367
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
364
368
|
def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
|
365
|
-
Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target
|
369
|
+
Turbo::StreamsChannel.broadcast_prepend_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
366
370
|
end
|
367
371
|
|
368
372
|
# Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -389,7 +393,7 @@ module Turbo::Broadcastable
|
|
389
393
|
# # to the stream named "identity:2:clearances"
|
390
394
|
# clearance.broadcast_action_to examiner.identity, :clearances, action: :prepend, target: "clearances"
|
391
395
|
def broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
|
392
|
-
Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action,
|
396
|
+
Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, attributes: attributes, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
393
397
|
end
|
394
398
|
|
395
399
|
# Same as <tt>#broadcast_action_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -399,7 +403,7 @@ module Turbo::Broadcastable
|
|
399
403
|
|
400
404
|
# Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
401
405
|
def broadcast_replace_later_to(*streamables, **rendering)
|
402
|
-
Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self
|
406
|
+
Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
403
407
|
end
|
404
408
|
|
405
409
|
# Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -409,7 +413,7 @@ module Turbo::Broadcastable
|
|
409
413
|
|
410
414
|
# Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
411
415
|
def broadcast_update_later_to(*streamables, **rendering)
|
412
|
-
Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self
|
416
|
+
Turbo::StreamsChannel.broadcast_update_later_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
413
417
|
end
|
414
418
|
|
415
419
|
# Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -419,7 +423,7 @@ module Turbo::Broadcastable
|
|
419
423
|
|
420
424
|
# Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
421
425
|
def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
|
422
|
-
Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target
|
426
|
+
Turbo::StreamsChannel.broadcast_append_later_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
423
427
|
end
|
424
428
|
|
425
429
|
# Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -429,7 +433,7 @@ module Turbo::Broadcastable
|
|
429
433
|
|
430
434
|
# Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
431
435
|
def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
|
432
|
-
Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target
|
436
|
+
Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
433
437
|
end
|
434
438
|
|
435
439
|
# Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -449,7 +453,7 @@ module Turbo::Broadcastable
|
|
449
453
|
|
450
454
|
# Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
451
455
|
def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
|
452
|
-
Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action,
|
456
|
+
Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, attributes: attributes, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
453
457
|
end
|
454
458
|
|
455
459
|
# Same as <tt>#broadcast_action_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -485,7 +489,7 @@ module Turbo::Broadcastable
|
|
485
489
|
# desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
|
486
490
|
# be using +broadcast_render_later_to+, unless you specifically know why synchronous rendering is needed.
|
487
491
|
def broadcast_render_to(*streamables, **rendering)
|
488
|
-
Turbo::StreamsChannel.broadcast_render_to(*streamables, **
|
492
|
+
Turbo::StreamsChannel.broadcast_render_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
489
493
|
end
|
490
494
|
|
491
495
|
# Same as <tt>broadcast_render_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
@@ -496,7 +500,7 @@ module Turbo::Broadcastable
|
|
496
500
|
# Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
|
497
501
|
# <tt>streamables</tt>.
|
498
502
|
def broadcast_render_later_to(*streamables, **rendering)
|
499
|
-
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **
|
503
|
+
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **extract_options_and_add_target(rendering)) unless suppressed_turbo_broadcasts?
|
500
504
|
end
|
501
505
|
|
502
506
|
private
|
@@ -504,11 +508,17 @@ module Turbo::Broadcastable
|
|
504
508
|
self.class.broadcast_target_default
|
505
509
|
end
|
506
510
|
|
511
|
+
def extract_options_and_add_target(rendering = {}, target: broadcast_target_default)
|
512
|
+
broadcast_rendering_with_defaults(rendering).tap do |options|
|
513
|
+
options[:target] = target if !options.key?(:target) && !options.key?(:targets)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
507
517
|
def broadcast_rendering_with_defaults(options)
|
508
518
|
options.tap do |o|
|
509
519
|
# Add the current instance into the locals with the element name (which is the un-namespaced name)
|
510
520
|
# as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
|
511
|
-
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self
|
521
|
+
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self).compact
|
512
522
|
|
513
523
|
if o[:html] || o[:partial]
|
514
524
|
return o
|
@@ -77,8 +77,9 @@ class Turbo::Streams::TagBuilder
|
|
77
77
|
# <%= turbo_stream.replace "clearance_5" do %>
|
78
78
|
# <div id='clearance_5'>Replace the dom target identified by clearance_5</div>
|
79
79
|
# <% end %>
|
80
|
-
|
81
|
-
|
80
|
+
# <%= turbo_stream.replace clearance, "<div>Morph the dom target</div>", method: :morph %>
|
81
|
+
def replace(target, content = nil, method: nil, **rendering, &block)
|
82
|
+
action :replace, target, content, method: method, **rendering, &block
|
82
83
|
end
|
83
84
|
|
84
85
|
# Replace the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
|
@@ -90,8 +91,9 @@ class Turbo::Streams::TagBuilder
|
|
90
91
|
# <%= turbo_stream.replace_all ".clearance_item" do %>
|
91
92
|
# <div class='.clearance_item'>Replace the dom target identified by the class clearance_item</div>
|
92
93
|
# <% end %>
|
93
|
-
|
94
|
-
|
94
|
+
# <%= turbo_stream.replace_all clearance, "<div>Morph the dom target</div>", method: :morph %>
|
95
|
+
def replace_all(targets, content = nil, method: nil, **rendering, &block)
|
96
|
+
action_all :replace, targets, content, method: method, **rendering, &block
|
95
97
|
end
|
96
98
|
|
97
99
|
# Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
|
@@ -155,8 +157,9 @@ class Turbo::Streams::TagBuilder
|
|
155
157
|
# <%= turbo_stream.update "clearance_5" do %>
|
156
158
|
# Update the content of the dom target identified by clearance_5
|
157
159
|
# <% end %>
|
158
|
-
|
159
|
-
|
160
|
+
# <%= turbo_stream.update clearance, "<div>Morph the dom target</div>", method: :morph %>
|
161
|
+
def update(target, content = nil, method: nil, **rendering, &block)
|
162
|
+
action :update, target, content, method: method, **rendering, &block
|
160
163
|
end
|
161
164
|
|
162
165
|
# Update the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
|
@@ -168,8 +171,9 @@ class Turbo::Streams::TagBuilder
|
|
168
171
|
# <%= turbo_stream.update_all "clearance_item" do %>
|
169
172
|
# Update the content of the dom target identified by the class clearance_item
|
170
173
|
# <% end %>
|
171
|
-
|
172
|
-
|
174
|
+
# <%= turbo_stream.update_all clearance, "<div>Morph the dom target</div>", method: :morph %>
|
175
|
+
def update_all(targets, content = nil, method: nil, **rendering, &block)
|
176
|
+
action_all :update, targets, content, method: method, **rendering, &block
|
173
177
|
end
|
174
178
|
|
175
179
|
# Append to the target in the dom identified with <tt>target</tt> either the <tt>content</tt> passed in or a
|
@@ -228,53 +232,43 @@ class Turbo::Streams::TagBuilder
|
|
228
232
|
action_all :prepend, targets, content, **rendering, &block
|
229
233
|
end
|
230
234
|
|
231
|
-
#
|
232
|
-
#
|
235
|
+
# Creates a `turbo-stream` tag with an `[action="refresh"`] attribute and a
|
236
|
+
# `[request-id]` attribute that defaults to `Turbo.current_request_id`:
|
233
237
|
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
# <%= turbo_stream.morph clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
|
237
|
-
# <%= turbo_stream.morph "clearance_5" do %>
|
238
|
-
# <div id='clearance_5'>Morph the dom target identified by clearance_5</div>
|
239
|
-
# <% end %>
|
240
|
-
def morph(target, content = nil, **rendering, &block)
|
241
|
-
action :morph, target, content, **rendering, &block
|
242
|
-
end
|
243
|
-
|
244
|
-
# Morph the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
|
245
|
-
# by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
|
238
|
+
# turbo_stream.refresh
|
239
|
+
# # => <turbo-stream action="refresh" request-id="ef083d55-7516-41b1-ad28-16f553399c6a"></turbo-stream>
|
246
240
|
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
# <div class='clearance_item'>Morph the dom target identified by the class clearance_item</div>
|
252
|
-
# <% end %>
|
253
|
-
def morph_all(targets, content = nil, **rendering, &block)
|
254
|
-
action_all :morph, targets, content, **rendering, &block
|
241
|
+
# turbo_stream.refresh request_id: "abc123"
|
242
|
+
# # => <turbo-stream action="refresh" request-id="abc123"></turbo-stream>
|
243
|
+
def refresh(...)
|
244
|
+
turbo_stream_refresh_tag(...)
|
255
245
|
end
|
256
246
|
|
257
247
|
# Send an action of the type <tt>name</tt> to <tt>target</tt>. Options described in the concrete methods.
|
258
|
-
def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
248
|
+
def action(name, target, content = nil, method: nil, allow_inferred_rendering: true, **rendering, &block)
|
259
249
|
template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
|
260
250
|
|
261
|
-
turbo_stream_action_tag name, target: target, template: template
|
251
|
+
turbo_stream_action_tag name, target: target, template: template, method: method
|
262
252
|
end
|
263
253
|
|
264
254
|
# Send an action of the type <tt>name</tt> to <tt>targets</tt>. Options described in the concrete methods.
|
265
|
-
def action_all(name, targets, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
255
|
+
def action_all(name, targets, content = nil, method: nil, allow_inferred_rendering: true, **rendering, &block)
|
266
256
|
template = render_template(targets, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
|
267
257
|
|
268
|
-
turbo_stream_action_tag name, targets: targets, template: template
|
258
|
+
turbo_stream_action_tag name, targets: targets, template: template, method: method
|
269
259
|
end
|
270
260
|
|
271
261
|
private
|
272
262
|
def render_template(target, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
273
263
|
case
|
264
|
+
when target.respond_to?(:render_in) && content.nil?
|
265
|
+
target.render_in(@view_context, &block)
|
274
266
|
when content.respond_to?(:render_in)
|
275
267
|
content.render_in(@view_context, &block)
|
276
268
|
when content
|
277
269
|
allow_inferred_rendering ? (render_record(content) || content) : content
|
270
|
+
when block_given? && (rendering.key?(:partial) || rendering.key?(:layout))
|
271
|
+
@view_context.render(formats: [ :html ], layout: rendering[:partial], **rendering, &block)
|
278
272
|
when block_given?
|
279
273
|
@view_context.capture(&block)
|
280
274
|
when rendering.any?
|
data/config/routes.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
get "recede_historical_location"
|
3
|
-
get "resume_historical_location"
|
4
|
-
get "refresh_historical_location"
|
2
|
+
get "recede_historical_location", to: "turbo/native/navigation#recede", as: :turbo_recede_historical_location
|
3
|
+
get "resume_historical_location", to: "turbo/native/navigation#resume", as: :turbo_resume_historical_location
|
4
|
+
get "refresh_historical_location", to: "turbo/native/navigation#refresh", as: :turbo_refresh_historical_location
|
5
5
|
end if Turbo.draw_routes
|
data/lib/tasks/turbo_tasks.rake
CHANGED
@@ -5,20 +5,6 @@ module Turbo
|
|
5
5
|
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
|
6
6
|
end
|
7
7
|
|
8
|
-
def redis_installed?
|
9
|
-
Gem.win_platform? ?
|
10
|
-
system('where redis-server > NUL 2>&1') :
|
11
|
-
system('which redis-server > /dev/null')
|
12
|
-
end
|
13
|
-
|
14
|
-
def switch_on_redis_if_available
|
15
|
-
if redis_installed?
|
16
|
-
Rake::Task["turbo:install:redis"].invoke
|
17
|
-
else
|
18
|
-
puts "Run turbo:install:redis to switch on Redis and use it in development for turbo streams"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
8
|
def using_bun?
|
23
9
|
Rails.root.join("bun.config.js").exist?
|
24
10
|
end
|
@@ -43,24 +29,16 @@ namespace :turbo do
|
|
43
29
|
desc "Install Turbo into the app with asset pipeline"
|
44
30
|
task :importmap do
|
45
31
|
Turbo::Tasks.run_turbo_install_template "turbo_with_importmap"
|
46
|
-
Turbo::Tasks.switch_on_redis_if_available
|
47
32
|
end
|
48
33
|
|
49
34
|
desc "Install Turbo into the app with webpacker"
|
50
35
|
task :node do
|
51
36
|
Turbo::Tasks.run_turbo_install_template "turbo_with_node"
|
52
|
-
Turbo::Tasks.switch_on_redis_if_available
|
53
37
|
end
|
54
38
|
|
55
39
|
desc "Install Turbo into the app with bun"
|
56
40
|
task :bun do
|
57
41
|
Turbo::Tasks.run_turbo_install_template "turbo_with_bun"
|
58
|
-
Turbo::Tasks.switch_on_redis_if_available
|
59
|
-
end
|
60
|
-
|
61
|
-
desc "Switch on Redis and use it in development"
|
62
|
-
task :redis do
|
63
|
-
Turbo::Tasks.run_turbo_install_template "turbo_needs_redis"
|
64
42
|
end
|
65
43
|
end
|
66
44
|
end
|
data/lib/turbo/engine.rb
CHANGED
@@ -5,6 +5,7 @@ module Turbo
|
|
5
5
|
isolate_namespace Turbo
|
6
6
|
config.eager_load_namespaces << Turbo
|
7
7
|
config.turbo = ActiveSupport::OrderedOptions.new
|
8
|
+
config.turbo.test_connect_after_actions = %i[visit]
|
8
9
|
config.autoload_once_paths = %W(
|
9
10
|
#{root}/app/channels
|
10
11
|
#{root}/app/controllers
|
@@ -15,6 +16,27 @@ module Turbo
|
|
15
16
|
#{root}/app/jobs
|
16
17
|
)
|
17
18
|
|
19
|
+
# If the parent application does not use Active Job, app/jobs cannot
|
20
|
+
# be eager loaded, because it references the ActiveJob constant.
|
21
|
+
#
|
22
|
+
# When turbo-rails depends on Rails 7 or above, the entire block can be
|
23
|
+
# reduced to
|
24
|
+
#
|
25
|
+
# unless defined?(ActiveJob)
|
26
|
+
# Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
initializer "turbo.no_active_job", before: :set_eager_load_paths do
|
30
|
+
unless defined?(ActiveJob)
|
31
|
+
if Rails.autoloaders.zeitwerk_enabled?
|
32
|
+
Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
|
33
|
+
else
|
34
|
+
# This else branch only runs in Rails 6.x + classic mode.
|
35
|
+
config.eager_load_paths.delete("#{root}/app/jobs")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
18
40
|
# If the parent application does not use Action Cable, app/channels cannot
|
19
41
|
# be eager loaded, because it references the ActionCable constant.
|
20
42
|
#
|
@@ -71,7 +93,9 @@ module Turbo
|
|
71
93
|
|
72
94
|
initializer "turbo.broadcastable" do
|
73
95
|
ActiveSupport.on_load(:active_record) do
|
74
|
-
|
96
|
+
if defined?(ActiveJob)
|
97
|
+
include Turbo::Broadcastable
|
98
|
+
end
|
75
99
|
end
|
76
100
|
end
|
77
101
|
|
@@ -101,8 +125,10 @@ module Turbo
|
|
101
125
|
|
102
126
|
ActiveSupport.on_load(:action_cable) do
|
103
127
|
ActiveSupport.on_load(:active_support_test_case) do
|
104
|
-
|
105
|
-
|
128
|
+
if defined?(ActiveJob)
|
129
|
+
require "turbo/broadcastable/test_helper"
|
130
|
+
include Turbo::Broadcastable::TestHelper
|
131
|
+
end
|
106
132
|
end
|
107
133
|
end
|
108
134
|
|
@@ -126,5 +152,24 @@ module Turbo
|
|
126
152
|
end
|
127
153
|
end
|
128
154
|
end
|
155
|
+
|
156
|
+
initializer "turbo.system_test_helper" do
|
157
|
+
ActiveSupport.on_load(:action_dispatch_system_test_case) do
|
158
|
+
require "turbo/system_test_helper"
|
159
|
+
include Turbo::SystemTestHelper
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
config.after_initialize do |app|
|
164
|
+
ActiveSupport.on_load(:action_dispatch_system_test_case) do
|
165
|
+
app.config.turbo.test_connect_after_actions.map do |method|
|
166
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
167
|
+
def #{method}(...) # def visit(...)
|
168
|
+
super.tap { connect_turbo_cable_stream_sources } # super.tap { connect_turbo_cable_stream_sources }
|
169
|
+
end # end
|
170
|
+
RUBY
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
129
174
|
end
|
130
175
|
end
|