turbo-rails 2.0.5 → 2.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|