turbo-rails 1.0.1 → 1.4.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/README.md +1 -1
- data/Rakefile +15 -2
- data/app/assets/javascripts/turbo.js +1235 -557
- 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 +1 -1
- data/app/controllers/turbo/frames/frame_request.rb +15 -7
- data/app/controllers/turbo/native/navigation.rb +3 -1
- data/app/helpers/turbo/drive_helper.rb +16 -3
- data/app/helpers/turbo/frames_helper.rb +14 -2
- data/app/helpers/turbo/streams/action_helper.rb +8 -6
- data/app/helpers/turbo/streams_helper.rb +5 -0
- data/app/javascript/turbo/cable_stream_source_element.js +19 -3
- data/app/javascript/turbo/fetch_requests.js +50 -0
- data/app/javascript/turbo/index.js +4 -0
- data/app/javascript/turbo/snakeize.js +31 -0
- data/app/models/concerns/turbo/broadcastable.rb +38 -13
- data/app/models/turbo/streams/tag_builder.rb +6 -4
- data/app/views/layouts/turbo_rails/frame.html.erb +8 -0
- data/lib/tasks/turbo_tasks.rake +3 -1
- data/lib/turbo/engine.rb +6 -4
- data/lib/turbo/test_assertions.rb +10 -4
- data/lib/turbo/version.rb +1 -1
- metadata +20 -3
@@ -35,7 +35,7 @@ module Turbo::Streams::Broadcasts
|
|
35
35
|
|
36
36
|
def broadcast_action_to(*streamables, action:, target: nil, targets: nil, **rendering)
|
37
37
|
broadcast_stream_to(*streamables, content: turbo_stream_action_tag(action, target: target, targets: targets, template:
|
38
|
-
rendering.delete(:content) || (rendering.any? ? render_format(:html, **rendering) : nil)
|
38
|
+
rendering.delete(:content) || rendering.delete(:html) || (rendering.any? ? render_format(:html, **rendering) : nil)
|
39
39
|
))
|
40
40
|
end
|
41
41
|
|
@@ -1,19 +1,27 @@
|
|
1
1
|
# Turbo frame requests are requests made from within a turbo frame with the intention of replacing the content of just
|
2
2
|
# that frame, not the whole page. They are automatically tagged as such by the Turbo Frame JavaScript, which adds a
|
3
|
-
# <tt>Turbo-Frame</tt> header to the request.
|
4
|
-
# template layout is skipped (since we're only working on an in-page frame, thus can skip the weight of the layout), and
|
5
|
-
# that the etag for the page is changed (such that a cache for a layout-less request isn't served on a normal request
|
6
|
-
# and vice versa).
|
3
|
+
# <tt>Turbo-Frame</tt> header to the request.
|
7
4
|
#
|
8
|
-
#
|
9
|
-
#
|
5
|
+
# When that header is detected by the controller, we substitute our own minimal layout in place of the
|
6
|
+
# application-supplied layout (since we're only working on an in-page frame, thus can skip the weight of the layout). We
|
7
|
+
# use a minimal layout, rather than avoid the layout entirely, so that it's still possible to render content into the
|
8
|
+
# <tt>head<tt>.
|
9
|
+
#
|
10
|
+
# Accordingly, we ensure that the etag for the page is changed, such that a cache for a minimal-layout request isn't
|
11
|
+
# served on a normal request and vice versa.
|
12
|
+
#
|
13
|
+
# This is merely a rendering optimization. Everything would still work just fine if we rendered everything including the
|
14
|
+
# full layout. Turbo Frames knows how to fish out the relevant frame regardless.
|
15
|
+
#
|
16
|
+
# The layout used is <tt>turbo_rails/frame.html.erb</tt>. If there's a need to customize this layout, an application can
|
17
|
+
# supply its own (such as <tt>app/views/layouts/turbo_rails/frame.html.erb</tt>) which will be used instead.
|
10
18
|
#
|
11
19
|
# This module is automatically included in <tt>ActionController::Base</tt>.
|
12
20
|
module Turbo::Frames::FrameRequest
|
13
21
|
extend ActiveSupport::Concern
|
14
22
|
|
15
23
|
included do
|
16
|
-
layout -> {
|
24
|
+
layout -> { "turbo_rails/frame" if turbo_frame_request? }
|
17
25
|
etag { :frame if turbo_frame_request? }
|
18
26
|
end
|
19
27
|
|
@@ -33,8 +33,10 @@ module Turbo::Native::Navigation
|
|
33
33
|
|
34
34
|
# :nodoc:
|
35
35
|
def turbo_native_action_or_redirect(url, action, redirect_type, options = {})
|
36
|
+
native_params = options.delete(:native_params) || {}
|
37
|
+
|
36
38
|
if turbo_native_app?
|
37
|
-
redirect_to send("turbo_#{action}_historical_location_url", notice: options[:notice]
|
39
|
+
redirect_to send("turbo_#{action}_historical_location_url", notice: options[:notice], **native_params)
|
38
40
|
elsif redirect_type == :back
|
39
41
|
redirect_back fallback_location: url, **options
|
40
42
|
else
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Turbo::DriveHelper
|
2
|
-
#
|
3
|
-
# Note: This requires a +yield :head+ provision in the application layout.
|
2
|
+
# Note: These helpers require a +yield :head+ provision in the layout.
|
4
3
|
#
|
5
4
|
# ==== Example
|
6
5
|
#
|
@@ -10,7 +9,21 @@ module Turbo::DriveHelper
|
|
10
9
|
# # app/views/trays/index.html.erb
|
11
10
|
# <% turbo_exempts_page_from_cache %>
|
12
11
|
# <p>Page that shouldn't be cached by Turbo</p>
|
12
|
+
|
13
|
+
# Pages that are more likely than not to be a cache miss can skip turbo cache to avoid visual jitter.
|
14
|
+
# Cannot be used along with +turbo_exempts_page_from_preview+.
|
13
15
|
def turbo_exempts_page_from_cache
|
14
|
-
provide :head,
|
16
|
+
provide :head, tag.meta(name: "turbo-cache-control", content: "no-cache")
|
17
|
+
end
|
18
|
+
|
19
|
+
# Specify that a cached version of the page should not be shown as a preview during an application visit.
|
20
|
+
# Cannot be used along with +turbo_exempts_page_from_cache+.
|
21
|
+
def turbo_exempts_page_from_preview
|
22
|
+
provide :head, tag.meta(name: "turbo-cache-control", content: "no-preview")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Force the page, when loaded by Turbo, to be cause a full page reload.
|
26
|
+
def turbo_page_requires_reload
|
27
|
+
provide :head, tag.meta(name: "turbo-visit-control", content: "reload")
|
15
28
|
end
|
16
29
|
end
|
@@ -23,8 +23,20 @@ module Turbo::FramesHelper
|
|
23
23
|
# <div>My tray frame!</div>
|
24
24
|
# <% end %>
|
25
25
|
# # => <turbo-frame id="tray"><div>My tray frame!</div></turbo-frame>
|
26
|
-
|
27
|
-
|
26
|
+
#
|
27
|
+
# The `turbo_frame_tag` helper will convert the arguments it receives to their
|
28
|
+
# `dom_id` if applicable to easily generate unique ids for Turbo Frames:
|
29
|
+
#
|
30
|
+
# <%= turbo_frame_tag(Article.find(1)) %>
|
31
|
+
# # => <turbo-frame id="article_1"></turbo-frame>
|
32
|
+
#
|
33
|
+
# <%= turbo_frame_tag(Article.find(1), "comments") %>
|
34
|
+
# # => <turbo-frame id="article_1_comments"></turbo-frame>
|
35
|
+
#
|
36
|
+
# <%= turbo_frame_tag(Article.find(1), Comment.new) %>
|
37
|
+
# # => <turbo-frame id="article_1_new_comment"></turbo-frame>
|
38
|
+
def turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)
|
39
|
+
id = ids.map { |id| id.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(id) : id }.join("_")
|
28
40
|
src = url_for(src) if src.present?
|
29
41
|
|
30
42
|
tag.turbo_frame(**attributes.merge(id: id, src: src, target: target).compact, &block)
|
@@ -1,4 +1,6 @@
|
|
1
1
|
module Turbo::Streams::ActionHelper
|
2
|
+
include ActionView::Helpers::TagHelper
|
3
|
+
|
2
4
|
# Creates a `turbo-stream` tag according to the passed parameters. Examples:
|
3
5
|
#
|
4
6
|
# turbo_stream_action_tag "remove", target: "message_1"
|
@@ -9,22 +11,22 @@ module Turbo::Streams::ActionHelper
|
|
9
11
|
#
|
10
12
|
# turbo_stream_action_tag "replace", targets: "message_1", template: %(<div id="message_1">Hello!</div>)
|
11
13
|
# # => <turbo-stream action="replace" targets="message_1"><template><div id="message_1">Hello!</div></template></turbo-stream>
|
12
|
-
def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil)
|
13
|
-
template = action.to_sym == :remove ? "" :
|
14
|
+
def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **attributes)
|
15
|
+
template = action.to_sym == :remove ? "" : tag.template(template.to_s.html_safe)
|
14
16
|
|
15
17
|
if target = convert_to_turbo_stream_dom_id(target)
|
16
|
-
|
18
|
+
tag.turbo_stream(template, **attributes, action: action, target: target)
|
17
19
|
elsif targets = convert_to_turbo_stream_dom_id(targets, include_selector: true)
|
18
|
-
|
20
|
+
tag.turbo_stream(template, **attributes, action: action, targets: targets)
|
19
21
|
else
|
20
|
-
|
22
|
+
tag.turbo_stream(template, **attributes, action: action)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
private
|
25
27
|
def convert_to_turbo_stream_dom_id(target, include_selector: false)
|
26
28
|
if target.respond_to?(:to_key)
|
27
|
-
|
29
|
+
"#{"#" if include_selector}#{ActionView::RecordIdentifier.dom_id(target)}"
|
28
30
|
else
|
29
31
|
target
|
30
32
|
end
|
@@ -44,6 +44,11 @@ module Turbo::StreamsHelper
|
|
44
44
|
# or a class name):
|
45
45
|
#
|
46
46
|
# <%= turbo_stream_from "room", channel: RoomChannel %>
|
47
|
+
#
|
48
|
+
# It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
|
49
|
+
#
|
50
|
+
# <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
|
51
|
+
#
|
47
52
|
def turbo_stream_from(*streamables, **attributes)
|
48
53
|
attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
|
49
54
|
attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
|
@@ -1,10 +1,15 @@
|
|
1
1
|
import { connectStreamSource, disconnectStreamSource } from "@hotwired/turbo"
|
2
2
|
import { subscribeTo } from "./cable"
|
3
|
+
import snakeize from "./snakeize"
|
3
4
|
|
4
5
|
class TurboCableStreamSourceElement extends HTMLElement {
|
5
6
|
async connectedCallback() {
|
6
7
|
connectStreamSource(this)
|
7
|
-
this.subscription = await subscribeTo(this.channel, {
|
8
|
+
this.subscription = await subscribeTo(this.channel, {
|
9
|
+
received: this.dispatchMessageEvent.bind(this),
|
10
|
+
connected: this.subscriptionConnected.bind(this),
|
11
|
+
disconnected: this.subscriptionDisconnected.bind(this)
|
12
|
+
})
|
8
13
|
}
|
9
14
|
|
10
15
|
disconnectedCallback() {
|
@@ -17,11 +22,22 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
17
22
|
return this.dispatchEvent(event)
|
18
23
|
}
|
19
24
|
|
25
|
+
subscriptionConnected() {
|
26
|
+
this.setAttribute("connected", "")
|
27
|
+
}
|
28
|
+
|
29
|
+
subscriptionDisconnected() {
|
30
|
+
this.removeAttribute("connected")
|
31
|
+
}
|
32
|
+
|
20
33
|
get channel() {
|
21
34
|
const channel = this.getAttribute("channel")
|
22
35
|
const signed_stream_name = this.getAttribute("signed-stream-name")
|
23
|
-
return { channel, signed_stream_name }
|
36
|
+
return { channel, signed_stream_name, ...snakeize({ ...this.dataset }) }
|
24
37
|
}
|
25
38
|
}
|
26
39
|
|
27
|
-
|
40
|
+
|
41
|
+
if (customElements.get("turbo-cable-stream-source") === undefined) {
|
42
|
+
customElements.define("turbo-cable-stream-source", TurboCableStreamSourceElement)
|
43
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
export function encodeMethodIntoRequestBody(event) {
|
2
|
+
if (event.target instanceof HTMLFormElement) {
|
3
|
+
const { target: form, detail: { fetchOptions } } = event
|
4
|
+
|
5
|
+
form.addEventListener("turbo:submit-start", ({ detail: { formSubmission: { submitter } } }) => {
|
6
|
+
const body = isBodyInit(fetchOptions.body) ? fetchOptions.body : new URLSearchParams()
|
7
|
+
const method = determineFetchMethod(submitter, body, form)
|
8
|
+
|
9
|
+
if (!/get/i.test(method)) {
|
10
|
+
if (/post/i.test(method)) {
|
11
|
+
body.delete("_method")
|
12
|
+
} else {
|
13
|
+
body.set("_method", method)
|
14
|
+
}
|
15
|
+
|
16
|
+
fetchOptions.method = "post"
|
17
|
+
}
|
18
|
+
}, { once: true })
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
function determineFetchMethod(submitter, body, form) {
|
23
|
+
const formMethod = determineFormMethod(submitter)
|
24
|
+
const overrideMethod = body.get("_method")
|
25
|
+
const method = form.getAttribute("method") || "get"
|
26
|
+
|
27
|
+
if (typeof formMethod == "string") {
|
28
|
+
return formMethod
|
29
|
+
} else if (typeof overrideMethod == "string") {
|
30
|
+
return overrideMethod
|
31
|
+
} else {
|
32
|
+
return method
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
function determineFormMethod(submitter) {
|
37
|
+
if (submitter instanceof HTMLButtonElement || submitter instanceof HTMLInputElement) {
|
38
|
+
if (submitter.hasAttribute("formmethod")) {
|
39
|
+
return submitter.formMethod
|
40
|
+
} else {
|
41
|
+
return null
|
42
|
+
}
|
43
|
+
} else {
|
44
|
+
return null
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
function isBodyInit(body) {
|
49
|
+
return body instanceof FormData || body instanceof URLSearchParams
|
50
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
// Based on https://github.com/nathan7/snakeize
|
2
|
+
//
|
3
|
+
// This software is released under the MIT license:
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
// this software and associated documentation files (the "Software"), to deal in
|
6
|
+
// the Software without restriction, including without limitation the rights to
|
7
|
+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
// the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
// subject to the following conditions:
|
10
|
+
|
11
|
+
// The above copyright notice and this permission notice shall be included in all
|
12
|
+
// copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
export default function walk (obj) {
|
21
|
+
if (!obj || typeof obj !== 'object') return obj;
|
22
|
+
if (obj instanceof Date || obj instanceof RegExp) return obj;
|
23
|
+
if (Array.isArray(obj)) return obj.map(walk);
|
24
|
+
return Object.keys(obj).reduce(function (acc, key) {
|
25
|
+
var camel = key[0].toLowerCase() + key.slice(1).replace(/([A-Z]+)/g, function (m, x) {
|
26
|
+
return '_' + x.toLowerCase();
|
27
|
+
});
|
28
|
+
acc[camel] = walk(obj[key]);
|
29
|
+
return acc;
|
30
|
+
}, {});
|
31
|
+
};
|
@@ -27,8 +27,8 @@
|
|
27
27
|
# (which is derived by default from the plural model name of the model, but can be overwritten).
|
28
28
|
#
|
29
29
|
# You can also choose to render html instead of a partial inside of a broadcast
|
30
|
-
# you do this by passing the html
|
31
|
-
#
|
30
|
+
# you do this by passing the `html:` option to any broadcast method that accepts the **rendering argument. Example:
|
31
|
+
#
|
32
32
|
# class Message < ApplicationRecord
|
33
33
|
# belongs_to :user
|
34
34
|
#
|
@@ -39,7 +39,21 @@
|
|
39
39
|
# broadcast_update_to(user, :messages, target: "message-count", html: "<p> #{user.messages.count} </p>")
|
40
40
|
# end
|
41
41
|
# end
|
42
|
-
#
|
42
|
+
#
|
43
|
+
# If you want to render a template instead of a partial, e.g. ('messages/index' or 'messages/show'), you can use the `template:` option.
|
44
|
+
# Again, only to any broadcast method that accepts the `**rendering` argument. Example:
|
45
|
+
#
|
46
|
+
# class Message < ApplicationRecord
|
47
|
+
# belongs_to :user
|
48
|
+
#
|
49
|
+
# after_create_commit :update_message
|
50
|
+
#
|
51
|
+
# private
|
52
|
+
# def update_message
|
53
|
+
# broadcast_replace_to(user, :message, target: "message", template: "messages/show", locals: { message: self })
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
43
57
|
# There are four basic actions you can broadcast: <tt>remove</tt>, <tt>replace</tt>, <tt>append</tt>, and
|
44
58
|
# <tt>prepend</tt>. As a rule, you should use the <tt>_later</tt> versions of everything except for remove when broadcasting
|
45
59
|
# within a real-time path, like a controller or model, since all those updates require a rendering step, which can slow down
|
@@ -66,16 +80,22 @@ module Turbo::Broadcastable
|
|
66
80
|
# belongs_to :board
|
67
81
|
# broadcasts_to ->(message) { [ message.board, :messages ] }, inserts_by: :prepend, target: "board_messages"
|
68
82
|
# end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
83
|
+
#
|
84
|
+
# class Message < ApplicationRecord
|
85
|
+
# belongs_to :board
|
86
|
+
# broadcasts_to ->(message) { [ message.board, :messages ] }, partial: "messages/custom_message"
|
87
|
+
# end
|
88
|
+
def broadcasts_to(stream, inserts_by: :append, target: broadcast_target_default, **rendering)
|
89
|
+
after_create_commit -> { broadcast_action_later_to(stream.try(:call, self) || send(stream), action: inserts_by, target: target.try(:call, self) || target, **rendering) }
|
90
|
+
after_update_commit -> { broadcast_replace_later_to(stream.try(:call, self) || send(stream), **rendering) }
|
91
|
+
after_destroy_commit -> { broadcast_remove_to(stream.try(:call, self) || send(stream)) }
|
73
92
|
end
|
74
93
|
|
75
|
-
# Same as <tt>#broadcasts_to</tt>, but the designated stream is automatically set to
|
76
|
-
|
77
|
-
|
78
|
-
|
94
|
+
# Same as <tt>#broadcasts_to</tt>, but the designated stream for updates and destroys is automatically set to
|
95
|
+
# the current model, for creates - to the model plural name, which can be overriden by passing <tt>stream</tt>.
|
96
|
+
def broadcasts(stream = model_name.plural, inserts_by: :append, target: broadcast_target_default, **rendering)
|
97
|
+
after_create_commit -> { broadcast_action_later_to(stream, action: inserts_by, target: target.try(:call, self) || target, **rendering) }
|
98
|
+
after_update_commit -> { broadcast_replace_later(**rendering) }
|
79
99
|
after_destroy_commit -> { broadcast_remove }
|
80
100
|
end
|
81
101
|
|
@@ -329,8 +349,13 @@ module Turbo::Broadcastable
|
|
329
349
|
# Add the current instance into the locals with the element name (which is the un-namespaced name)
|
330
350
|
# as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
|
331
351
|
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self)
|
332
|
-
|
333
|
-
|
352
|
+
|
353
|
+
if o[:html] || o[:partial]
|
354
|
+
return o
|
355
|
+
elsif o[:template]
|
356
|
+
o[:layout] = false
|
357
|
+
else
|
358
|
+
# if none of these options are passed in, it will set a partial from #to_partial_path
|
334
359
|
o[:partial] ||= to_partial_path
|
335
360
|
end
|
336
361
|
end
|
@@ -50,7 +50,7 @@ class Turbo::Streams::TagBuilder
|
|
50
50
|
action_all :remove, targets, allow_inferred_rendering: false
|
51
51
|
end
|
52
52
|
|
53
|
-
# Replace the <tt>target</tt> in the dom with
|
53
|
+
# Replace the <tt>target</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
|
54
54
|
# by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
|
55
55
|
#
|
56
56
|
# <%= turbo_stream.replace "clearance_5", "<div id='clearance_5'>Replace the dom target identified by clearance_5</div>" %>
|
@@ -63,7 +63,7 @@ class Turbo::Streams::TagBuilder
|
|
63
63
|
action :replace, target, content, **rendering, &block
|
64
64
|
end
|
65
65
|
|
66
|
-
# Replace the <tt>targets</tt> in the dom with
|
66
|
+
# Replace the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
|
67
67
|
# by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
|
68
68
|
#
|
69
69
|
# <%= turbo_stream.replace_all ".clearance_item", "<div class='clearance_item'>Replace the dom target identified by the class clearance_item</div>" %>
|
@@ -128,7 +128,7 @@ class Turbo::Streams::TagBuilder
|
|
128
128
|
action_all :after, targets, content, **rendering, &block
|
129
129
|
end
|
130
130
|
|
131
|
-
# Update the <tt>target</tt> in the dom with
|
131
|
+
# Update the <tt>target</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
|
132
132
|
# by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
|
133
133
|
#
|
134
134
|
# <%= turbo_stream.update "clearance_5", "Update the content of the dom target identified by clearance_5" %>
|
@@ -141,7 +141,7 @@ class Turbo::Streams::TagBuilder
|
|
141
141
|
action :update, target, content, **rendering, &block
|
142
142
|
end
|
143
143
|
|
144
|
-
# Update the <tt>targets</tt> in the dom with
|
144
|
+
# Update the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
|
145
145
|
# by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
|
146
146
|
#
|
147
147
|
# <%= turbo_stream.update_all "clearance_item", "Update the content of the dom target identified by the class clearance_item" %>
|
@@ -227,6 +227,8 @@ class Turbo::Streams::TagBuilder
|
|
227
227
|
private
|
228
228
|
def render_template(target, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
229
229
|
case
|
230
|
+
when content.respond_to?(:render_in)
|
231
|
+
content.render_in(@view_context, &block)
|
230
232
|
when content
|
231
233
|
allow_inferred_rendering ? (render_record(content) || content) : content
|
232
234
|
when block_given?
|
data/lib/tasks/turbo_tasks.rake
CHANGED
@@ -3,7 +3,9 @@ def run_turbo_install_template(path)
|
|
3
3
|
end
|
4
4
|
|
5
5
|
def redis_installed?
|
6
|
-
|
6
|
+
Gem.win_platform? ?
|
7
|
+
system('where redis-server > NUL 2>&1') :
|
8
|
+
system('which redis-server > /dev/null')
|
7
9
|
end
|
8
10
|
|
9
11
|
def switch_on_redis_if_available
|
data/lib/turbo/engine.rb
CHANGED
@@ -16,8 +16,8 @@ module Turbo
|
|
16
16
|
#{root}/app/jobs
|
17
17
|
)
|
18
18
|
|
19
|
-
initializer "turbo.no_action_cable" do
|
20
|
-
|
19
|
+
initializer "turbo.no_action_cable", before: :set_eager_load_paths do
|
20
|
+
config.eager_load_paths.delete("#{root}/app/channels") unless defined?(ActionCable)
|
21
21
|
end
|
22
22
|
|
23
23
|
# If you don't want to precompile Turbo's assets (eg. because you're using webpack),
|
@@ -61,8 +61,10 @@ module Turbo
|
|
61
61
|
end
|
62
62
|
|
63
63
|
initializer "turbo.signed_stream_verifier_key" do
|
64
|
-
|
65
|
-
|
64
|
+
config.after_initialize do
|
65
|
+
Turbo.signed_stream_verifier_key = config.turbo.signed_stream_verifier_key ||
|
66
|
+
Rails.application.key_generator.generate_key("turbo/signed_stream_verifier_key")
|
67
|
+
end
|
66
68
|
end
|
67
69
|
|
68
70
|
initializer "turbo.test_assertions" do
|
@@ -7,15 +7,21 @@ module Turbo
|
|
7
7
|
delegate :dom_id, :dom_class, to: ActionView::RecordIdentifier
|
8
8
|
end
|
9
9
|
|
10
|
-
def assert_turbo_stream(action:, target: nil, status: :ok, &block)
|
10
|
+
def assert_turbo_stream(action:, target: nil, targets: nil, status: :ok, &block)
|
11
11
|
assert_response status
|
12
12
|
assert_equal Mime[:turbo_stream], response.media_type
|
13
|
-
|
13
|
+
selector = %(turbo-stream[action="#{action}"])
|
14
|
+
selector << %([target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]) if target
|
15
|
+
selector << %([targets="#{targets}"]) if targets
|
16
|
+
assert_select selector, count: 1, &block
|
14
17
|
end
|
15
18
|
|
16
|
-
def assert_no_turbo_stream(action:, target: nil)
|
19
|
+
def assert_no_turbo_stream(action:, target: nil, targets: nil)
|
17
20
|
assert_equal Mime[:turbo_stream], response.media_type
|
18
|
-
|
21
|
+
selector = %(turbo-stream[action="#{action}"])
|
22
|
+
selector << %([target="#{target.respond_to?(:to_key) ? dom_id(target) : target}"]) if target
|
23
|
+
selector << %([targets="#{targets}"]) if targets
|
24
|
+
assert_select selector, count: 0
|
19
25
|
end
|
20
26
|
end
|
21
27
|
end
|
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: 1.0
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Stephenson
|
@@ -10,8 +10,22 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2023-03-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activejob
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 6.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 6.0.0
|
15
29
|
- !ruby/object:Gem::Dependency
|
16
30
|
name: actionpack
|
17
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,11 +80,14 @@ files:
|
|
66
80
|
- app/helpers/turbo/streams_helper.rb
|
67
81
|
- app/javascript/turbo/cable.js
|
68
82
|
- app/javascript/turbo/cable_stream_source_element.js
|
83
|
+
- app/javascript/turbo/fetch_requests.js
|
69
84
|
- app/javascript/turbo/index.js
|
85
|
+
- app/javascript/turbo/snakeize.js
|
70
86
|
- app/jobs/turbo/streams/action_broadcast_job.rb
|
71
87
|
- app/jobs/turbo/streams/broadcast_job.rb
|
72
88
|
- app/models/concerns/turbo/broadcastable.rb
|
73
89
|
- app/models/turbo/streams/tag_builder.rb
|
90
|
+
- app/views/layouts/turbo_rails/frame.html.erb
|
74
91
|
- config/routes.rb
|
75
92
|
- lib/install/turbo_needs_redis.rb
|
76
93
|
- lib/install/turbo_with_importmap.rb
|
@@ -99,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
116
|
- !ruby/object:Gem::Version
|
100
117
|
version: '0'
|
101
118
|
requirements: []
|
102
|
-
rubygems_version: 3.
|
119
|
+
rubygems_version: 3.4.6
|
103
120
|
signing_key:
|
104
121
|
specification_version: 4
|
105
122
|
summary: The speed of a single-page web application without having to write any JavaScript.
|