turbo-rails 2.0.5 → 2.0.6
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 +43 -5
- data/app/assets/javascripts/turbo.js +150 -121
- data/app/assets/javascripts/turbo.min.js +6 -6
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/channels/turbo/streams/broadcasts.rb +15 -3
- data/app/channels/turbo/streams_channel.rb +15 -15
- data/app/controllers/turbo/frames/frame_request.rb +2 -2
- data/app/helpers/turbo/drive_helper.rb +2 -2
- data/app/helpers/turbo/streams/action_helper.rb +3 -2
- data/app/models/concerns/turbo/broadcastable.rb +19 -11
- data/app/models/turbo/streams/tag_builder.rb +26 -0
- data/lib/turbo/engine.rb +21 -6
- data/lib/turbo/version.rb +1 -1
- metadata +5 -4
@@ -75,8 +75,15 @@ module Turbo::Streams::Broadcasts
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
|
78
|
-
|
79
|
-
|
78
|
+
streamables.flatten!
|
79
|
+
streamables.compact_blank!
|
80
|
+
|
81
|
+
if streamables.present?
|
82
|
+
target = convert_to_turbo_stream_dom_id(target)
|
83
|
+
targets = convert_to_turbo_stream_dom_id(targets, include_selector: true)
|
84
|
+
Turbo::Streams::ActionBroadcastJob.perform_later \
|
85
|
+
stream_name_from(streamables), action: action, target: target, targets: targets, attributes: attributes, **rendering
|
86
|
+
end
|
80
87
|
end
|
81
88
|
|
82
89
|
def broadcast_render_to(*streamables, **rendering)
|
@@ -88,7 +95,12 @@ module Turbo::Streams::Broadcasts
|
|
88
95
|
end
|
89
96
|
|
90
97
|
def broadcast_stream_to(*streamables, content:)
|
91
|
-
|
98
|
+
streamables.flatten!
|
99
|
+
streamables.compact_blank!
|
100
|
+
|
101
|
+
if streamables.present?
|
102
|
+
ActionCable.server.broadcast stream_name_from(streamables), content
|
103
|
+
end
|
92
104
|
end
|
93
105
|
|
94
106
|
def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc:
|
@@ -9,27 +9,27 @@
|
|
9
9
|
# helper modules like <tt>Turbo::Streams::StreamName</tt>:
|
10
10
|
#
|
11
11
|
# class CustomChannel < ActionCable::Channel::Base
|
12
|
-
#
|
13
|
-
#
|
12
|
+
# extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
|
13
|
+
# include Turbo::Streams::StreamName::ClassMethods
|
14
14
|
#
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# def subscribed
|
16
|
+
# if (stream_name = verified_stream_name_from_params).present? &&
|
17
17
|
# subscription_allowed?
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
18
|
+
# stream_from stream_name
|
19
|
+
# else
|
20
|
+
# reject
|
21
|
+
# end
|
22
|
+
# end
|
23
23
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
24
|
+
# def subscription_allowed?
|
25
|
+
# # ...
|
26
|
+
# end
|
27
27
|
# end
|
28
28
|
#
|
29
|
-
#
|
30
|
-
#
|
29
|
+
# This channel can be connected to a web page using <tt>:channel</tt> option in
|
30
|
+
# <tt>turbo_stream_from</tt> helper:
|
31
31
|
#
|
32
|
-
#
|
32
|
+
# <%= turbo_stream_from 'room', channel: CustomChannel %>
|
33
33
|
#
|
34
34
|
class Turbo::StreamsChannel < ActionCable::Channel::Base
|
35
35
|
extend Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
|
@@ -24,7 +24,7 @@ module Turbo::Frames::FrameRequest
|
|
24
24
|
layout -> { "turbo_rails/frame" if turbo_frame_request? }
|
25
25
|
etag { :frame if turbo_frame_request? }
|
26
26
|
|
27
|
-
helper_method :turbo_frame_request_id
|
27
|
+
helper_method :turbo_frame_request?, :turbo_frame_request_id
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -33,6 +33,6 @@ module Turbo::Frames::FrameRequest
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def turbo_frame_request_id
|
36
|
-
request
|
36
|
+
request.headers["Turbo-Frame"]
|
37
37
|
end
|
38
38
|
end
|
@@ -51,7 +51,7 @@ module Turbo::DriveHelper
|
|
51
51
|
# Configure how to handle page refreshes. A page refresh happens when
|
52
52
|
# Turbo loads the current page again with a *replace* visit:
|
53
53
|
#
|
54
|
-
#
|
54
|
+
# ==== Parameters:
|
55
55
|
#
|
56
56
|
# * <tt>method</tt> - Method to update the +<body>+ of the page
|
57
57
|
# during a page refresh. It can be one of:
|
@@ -64,7 +64,7 @@ module Turbo::DriveHelper
|
|
64
64
|
# * +reset:+: Resets scroll to the top, left corner. This is the default.
|
65
65
|
# * +preserve:+: Keeps the scroll.
|
66
66
|
#
|
67
|
-
#
|
67
|
+
# ==== Example Usage:
|
68
68
|
#
|
69
69
|
# turbo_refreshes_with(method: :morph, scroll: :preserve)
|
70
70
|
def turbo_refreshes_with(method: :replace, scroll: :reset)
|
@@ -44,8 +44,9 @@ module Turbo::Streams::ActionHelper
|
|
44
44
|
|
45
45
|
private
|
46
46
|
def convert_to_turbo_stream_dom_id(target, include_selector: false)
|
47
|
-
|
48
|
-
|
47
|
+
target_array = Array.wrap(target)
|
48
|
+
if target_array.any? { |value| value.respond_to?(:to_key) }
|
49
|
+
"#{"#" if include_selector}#{ActionView::RecordIdentifier.dom_id(*target_array)}"
|
49
50
|
else
|
50
51
|
target
|
51
52
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Turbo streams can be
|
1
|
+
# Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records).
|
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
|
#
|
@@ -79,19 +79,19 @@
|
|
79
79
|
#
|
80
80
|
# == Page refreshes
|
81
81
|
#
|
82
|
-
# You can broadcast "page refresh" stream actions. This will make subscribed clients reload the
|
82
|
+
# You can broadcast "page refresh" stream actions. This will make subscribed clients reload the
|
83
83
|
# page. For pages that configure morphing and scroll preservation, this will translate into smooth
|
84
84
|
# updates when it only updates the content that changed.
|
85
|
-
|
85
|
+
#
|
86
86
|
# This approach is an alternative to fine-grained stream actions targeting specific DOM elements. It
|
87
87
|
# offers good fidelity with a much simpler programming model. As a tradeoff, the fidelity you can reach
|
88
88
|
# is often not as high as with targeted stream actions since it renders the entire page again.
|
89
89
|
#
|
90
|
-
# The +
|
90
|
+
# The +broadcasts_refreshes+ class method configures the model to broadcast a "page refresh" on creates,
|
91
91
|
# updates, and destroys to a stream name derived at runtime by the <tt>stream</tt> symbol invocation. Examples
|
92
92
|
#
|
93
93
|
# class Board < ApplicationRecord
|
94
|
-
#
|
94
|
+
# broadcasts_refreshes
|
95
95
|
# end
|
96
96
|
#
|
97
97
|
# In this example, when a board is created, updated, or destroyed, a Turbo Stream for a
|
@@ -104,16 +104,16 @@
|
|
104
104
|
# belongs_to :board, touch: true # +Board+ will trigger a page refresh on column changes
|
105
105
|
# end
|
106
106
|
#
|
107
|
-
# You can also specify the streamable declaratively by passing a symbol to the +
|
107
|
+
# You can also specify the streamable declaratively by passing a symbol to the +broadcasts_refreshes_to+ method:
|
108
108
|
#
|
109
109
|
# class Column < ApplicationRecord
|
110
110
|
# belongs_to :board
|
111
|
-
#
|
111
|
+
# broadcasts_refreshes_to :board
|
112
112
|
# end
|
113
113
|
#
|
114
|
-
# For more granular control, you can also broadcast a "page refresh" to a stream name derived
|
114
|
+
# For more granular control, you can also broadcast a "page refresh" to a stream name derived
|
115
115
|
# from the passed <tt>streamables</tt> by using the instance-level methods <tt>broadcast_refresh_to</tt> or
|
116
|
-
# <tt>broadcast_refresh_later_to</tt>. These methods are particularly useful when you want to trigger
|
116
|
+
# <tt>broadcast_refresh_later_to</tt>. These methods are particularly useful when you want to trigger
|
117
117
|
# a page refresh for more specific scenarios. Example:
|
118
118
|
#
|
119
119
|
# class Clearance < ApplicationRecord
|
@@ -128,11 +128,11 @@
|
|
128
128
|
# end
|
129
129
|
# end
|
130
130
|
#
|
131
|
-
# In this example, a "page refresh" is broadcast to the stream named "identity:<identity-id>:clearances"
|
131
|
+
# In this example, a "page refresh" is broadcast to the stream named "identity:<identity-id>:clearances"
|
132
132
|
# after a new clearance is created. All clients subscribed to this stream will refresh the page to reflect
|
133
133
|
# the changes.
|
134
134
|
#
|
135
|
-
# When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one.
|
135
|
+
# When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one.
|
136
136
|
# This is meant for scenarios where you process records in mass. Because of the nature of such signals, it makes no sense to
|
137
137
|
# broadcast them repeatedly and individually.
|
138
138
|
# == Suppressing broadcasts
|
@@ -260,6 +260,10 @@ module Turbo::Broadcastable
|
|
260
260
|
# # Sends <turbo-stream action="replace" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
261
261
|
# # to the stream named "identity:2:clearances"
|
262
262
|
# clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
|
263
|
+
#
|
264
|
+
# # Sends <turbo-stream action="replace" method="morph" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
265
|
+
# # to the stream named "identity:2:clearances"
|
266
|
+
# clearance.broadcast_replace_to examiner.identity, :clearance, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
263
267
|
def broadcast_replace_to(*streamables, **rendering)
|
264
268
|
Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
265
269
|
end
|
@@ -279,6 +283,10 @@ module Turbo::Broadcastable
|
|
279
283
|
# # Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
280
284
|
# # to the stream named "identity:2:clearances"
|
281
285
|
# clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }
|
286
|
+
#
|
287
|
+
# # sends <turbo-stream action="update" method="morph" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
|
288
|
+
# # to the stream named "identity:2:clearances"
|
289
|
+
# # clearance.broadcast_update_to examiner.identity, :clearances, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
282
290
|
def broadcast_update_to(*streamables, **rendering)
|
283
291
|
Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
|
284
292
|
end
|
@@ -228,6 +228,32 @@ class Turbo::Streams::TagBuilder
|
|
228
228
|
action_all :prepend, targets, content, **rendering, &block
|
229
229
|
end
|
230
230
|
|
231
|
+
# Morph the <tt>target</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
|
232
|
+
# by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
|
233
|
+
#
|
234
|
+
# <%= turbo_stream.morph "clearance_5", "<div id='clearance_5'>Morph the dom target identified by clearance_5</div>" %>
|
235
|
+
# <%= turbo_stream.morph clearance %>
|
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:
|
246
|
+
#
|
247
|
+
# <%= turbo_stream.morph_all ".clearance_item", "<div class='clearance_item'>Morph the dom target identified by the class clearance_item</div>" %>
|
248
|
+
# <%= turbo_stream.morph_all clearance %>
|
249
|
+
# <%= turbo_stream.morph_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
|
250
|
+
# <%= turbo_stream.morph_all ".clearance_item" do %>
|
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
|
255
|
+
end
|
256
|
+
|
231
257
|
# Send an action of the type <tt>name</tt> to <tt>target</tt>. Options described in the concrete methods.
|
232
258
|
def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
233
259
|
template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
|
data/lib/turbo/engine.rb
CHANGED
@@ -15,8 +15,25 @@ module Turbo
|
|
15
15
|
#{root}/app/jobs
|
16
16
|
)
|
17
17
|
|
18
|
+
# If the parent application does not use Action Cable, app/channels cannot
|
19
|
+
# be eager loaded, because it references the ActionCable constant.
|
20
|
+
#
|
21
|
+
# When turbo-rails depends on Rails 7 or above, the entire block can be
|
22
|
+
# reduced to
|
23
|
+
#
|
24
|
+
# unless defined?(ActionCable)
|
25
|
+
# Rails.autoloaders.once.do_not_eager_load("#{root}/app/channels")
|
26
|
+
# end
|
27
|
+
#
|
18
28
|
initializer "turbo.no_action_cable", before: :set_eager_load_paths do
|
19
|
-
|
29
|
+
unless defined?(ActionCable)
|
30
|
+
if Rails.autoloaders.zeitwerk_enabled?
|
31
|
+
Rails.autoloaders.once.do_not_eager_load("#{root}/app/channels")
|
32
|
+
else
|
33
|
+
# This else branch only runs in Rails 6.x + classic mode.
|
34
|
+
config.eager_load_paths.delete("#{root}/app/channels")
|
35
|
+
end
|
36
|
+
end
|
20
37
|
end
|
21
38
|
|
22
39
|
# If you don't want to precompile Turbo's assets (eg. because you're using webpack),
|
@@ -63,11 +80,9 @@ module Turbo
|
|
63
80
|
end
|
64
81
|
|
65
82
|
initializer "turbo.renderer" do
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
turbo_streams_html
|
70
|
-
end
|
83
|
+
ActionController::Renderers.add :turbo_stream do |turbo_streams_html, options|
|
84
|
+
self.content_type = Mime[:turbo_stream] if media_type.nil?
|
85
|
+
turbo_streams_html
|
71
86
|
end
|
72
87
|
end
|
73
88
|
|
data/lib/turbo/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Stephenson
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-07-19 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activejob
|
@@ -107,7 +107,8 @@ files:
|
|
107
107
|
homepage: https://github.com/hotwired/turbo-rails
|
108
108
|
licenses:
|
109
109
|
- MIT
|
110
|
-
metadata:
|
110
|
+
metadata:
|
111
|
+
changelog_uri: https://github.com/hotwired/turbo-rails/releases
|
111
112
|
post_install_message:
|
112
113
|
rdoc_options: []
|
113
114
|
require_paths:
|
@@ -123,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
124
|
- !ruby/object:Gem::Version
|
124
125
|
version: '0'
|
125
126
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
127
|
+
rubygems_version: 3.5.11
|
127
128
|
signing_key:
|
128
129
|
specification_version: 4
|
129
130
|
summary: The speed of a single-page web application without having to write any JavaScript.
|