turbo-rails 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +9 -0
- data/app/assets/javascripts/turbo.js +30 -17
- data/app/helpers/turbo/streams/action_helper.rb +1 -1
- data/app/helpers/turbo/streams_helper.rb +2 -2
- data/lib/turbo/version.rb +1 -1
- data/package.json +2 -2
- data/test/streams/streams_channel_test.rb +6 -0
- data/yarn.lock +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d7e1db921a74088b68618665ff1df3ae7e3216fff065c2236c4bc8fd3c94812
|
4
|
+
data.tar.gz: e3b2ab5358a0c5b333c32401650f3c78325342e9e0e3eac47c1da4ab05621b13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c28d47c9a443718f9737b040b30d91964b1a02fb6f68fd3c9913b13ba7467ecbdb8e9a4bef4df711ad7aa99b8eb1d5a1005cc805b535a0fd21c41ff2f6ac279b
|
7
|
+
data.tar.gz: a20b70fa8892752160064a79ede46b553a1d04f84e91200d3f62092e973aa49c2627f6cc2e866b43c2452c5f8143ca5bc103c1807c3cf0f80499d2ae12be14b0
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
turbo-rails (0.5.
|
4
|
+
turbo-rails (0.5.2)
|
5
5
|
rails (>= 6.0.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -73,7 +73,7 @@ GEM
|
|
73
73
|
erubi (1.10.0)
|
74
74
|
globalid (0.4.2)
|
75
75
|
activesupport (>= 4.2.0)
|
76
|
-
i18n (1.8.
|
76
|
+
i18n (1.8.6)
|
77
77
|
concurrent-ruby (~> 1.0)
|
78
78
|
loofah (2.8.0)
|
79
79
|
crass (~> 1.0.2)
|
@@ -144,4 +144,4 @@ DEPENDENCIES
|
|
144
144
|
turbo-rails!
|
145
145
|
|
146
146
|
BUNDLED WITH
|
147
|
-
2.
|
147
|
+
2.2.3
|
data/README.md
CHANGED
@@ -48,6 +48,15 @@ If you're using Webpack and need to use either the cable consumer or the Turbo i
|
|
48
48
|
You can watch [the video introduction to Hotwire](https://hotwire.dev/#screencast), which focuses extensively on demonstration Turbo in a Rails demo. Then you should familiarize yourself with [Turbo handbook](https://turbo.hotwire.dev/handbook/introduction) to understand Drive, Frames, and Streams in-depth. Finally, dive into the code documentation by starting with [`Turbo::FramesHelper`](https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/frames_helper.rb), [`Turbo::StreamsHelper`](https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/streams_helper.rb), [`Turbo::Streams::TagBuilder`](https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb), and [`Turbo::Broadcastable`](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb).
|
49
49
|
|
50
50
|
|
51
|
+
## Compatibility with Rails UJS
|
52
|
+
|
53
|
+
Rails UJS includes helpers for sending links and forms over XMLHttpRequest, so you can respond with Ajax. Turbo supersedes this functionality, so you should ensure that you're either running Rails 6.1 with the defaults that turn this off for forms, or that you add `config.action_view.form_with_generates_remote_forms = false` to your `config/application.rb`.
|
54
|
+
|
55
|
+
Note that the helpers that turn `link_to` into remote invocations will _not_ currently work with Turbo. Links that have been made remote will not stick within frames nor will they allow you to respond with turbo stream actions. The recommendation is to replace these links with styled `button_to`, so you'll flow through a regular form, and you'll be better off with a11y compliance.
|
56
|
+
|
57
|
+
You can still use the `data-confirm` and `data-disable-with`.
|
58
|
+
|
59
|
+
|
51
60
|
## Development
|
52
61
|
|
53
62
|
* To run the Rails tests: `bundle exec rake`.
|
@@ -19,7 +19,7 @@ const submittersByForm = new WeakMap;
|
|
19
19
|
function findSubmitterFromClickTarget(target) {
|
20
20
|
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
21
21
|
const candidate = element ? element.closest("input, button") : null;
|
22
|
-
return (candidate === null || candidate === void 0 ? void 0 : candidate.
|
22
|
+
return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null;
|
23
23
|
}
|
24
24
|
|
25
25
|
function clickCaptured(event) {
|
@@ -129,6 +129,12 @@ class FetchResponse {
|
|
129
129
|
get failed() {
|
130
130
|
return !this.succeeded;
|
131
131
|
}
|
132
|
+
get clientError() {
|
133
|
+
return this.statusCode >= 400 && this.statusCode <= 499;
|
134
|
+
}
|
135
|
+
get serverError() {
|
136
|
+
return this.statusCode >= 500 && this.statusCode <= 599;
|
137
|
+
}
|
132
138
|
get redirected() {
|
133
139
|
return this.response.redirected;
|
134
140
|
}
|
@@ -377,7 +383,7 @@ class FormSubmission {
|
|
377
383
|
}
|
378
384
|
get method() {
|
379
385
|
var _a;
|
380
|
-
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.method;
|
386
|
+
const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
|
381
387
|
return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
|
382
388
|
}
|
383
389
|
get action() {
|
@@ -429,7 +435,9 @@ class FormSubmission {
|
|
429
435
|
};
|
430
436
|
}
|
431
437
|
requestSucceededWithResponse(request, response) {
|
432
|
-
if (
|
438
|
+
if (response.clientError || response.serverError) {
|
439
|
+
this.delegate.formSubmissionFailedWithResponse(this, response);
|
440
|
+
} else if (this.requestMustRedirect(request) && !response.redirected) {
|
433
441
|
const error = new Error("Form responses must redirect to another location");
|
434
442
|
this.delegate.formSubmissionErrored(this, error);
|
435
443
|
} else {
|
@@ -614,7 +622,9 @@ class FrameController {
|
|
614
622
|
const frame = this.findFrameElement(formSubmission.formElement);
|
615
623
|
frame.controller.loadResponse(response);
|
616
624
|
}
|
617
|
-
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
625
|
+
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
626
|
+
this.element.controller.loadResponse(fetchResponse);
|
627
|
+
}
|
618
628
|
formSubmissionErrored(formSubmission, error) {}
|
619
629
|
formSubmissionFinished(formSubmission) {}
|
620
630
|
navigateFrame(element, url) {
|
@@ -1570,7 +1580,8 @@ class FormSubmitObserver {
|
|
1570
1580
|
const form = event.target instanceof HTMLFormElement ? event.target : undefined;
|
1571
1581
|
const submitter = event.submitter || undefined;
|
1572
1582
|
if (form) {
|
1573
|
-
|
1583
|
+
const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
|
1584
|
+
if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
|
1574
1585
|
event.preventDefault();
|
1575
1586
|
this.delegate.formSubmitted(form, submitter);
|
1576
1587
|
}
|
@@ -1803,12 +1814,10 @@ class Navigator {
|
|
1803
1814
|
}
|
1804
1815
|
formSubmissionStarted(formSubmission) {}
|
1805
1816
|
async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
|
1806
|
-
console.log("Form submission succeeded", formSubmission);
|
1807
1817
|
if (formSubmission == this.formSubmission) {
|
1808
1818
|
const responseHTML = await fetchResponse.responseHTML;
|
1809
1819
|
if (responseHTML) {
|
1810
1820
|
if (formSubmission.method != FetchMethod.get) {
|
1811
|
-
console.log("Clearing snapshot cache after successful form submission");
|
1812
1821
|
this.view.clearSnapshotCache();
|
1813
1822
|
}
|
1814
1823
|
const {statusCode: statusCode} = fetchResponse;
|
@@ -1818,17 +1827,21 @@ class Navigator {
|
|
1818
1827
|
responseHTML: responseHTML
|
1819
1828
|
}
|
1820
1829
|
};
|
1821
|
-
console.log("Visiting", fetchResponse.location, visitOptions);
|
1822
1830
|
this.proposeVisit(fetchResponse.location, visitOptions);
|
1823
1831
|
}
|
1824
1832
|
}
|
1825
1833
|
}
|
1826
|
-
formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1834
|
+
async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
|
1835
|
+
const responseHTML = await fetchResponse.responseHTML;
|
1836
|
+
if (responseHTML) {
|
1837
|
+
const snapshot = Snapshot.fromHTMLString(responseHTML);
|
1838
|
+
this.view.render({
|
1839
|
+
snapshot: snapshot
|
1840
|
+
}, (() => {}));
|
1841
|
+
this.view.clearSnapshotCache();
|
1842
|
+
}
|
1831
1843
|
}
|
1844
|
+
formSubmissionErrored(formSubmission, error) {}
|
1832
1845
|
formSubmissionFinished(formSubmission) {}
|
1833
1846
|
visitStarted(visit) {
|
1834
1847
|
this.delegate.visitStarted(visit);
|
@@ -2475,7 +2488,7 @@ class Session {
|
|
2475
2488
|
});
|
2476
2489
|
}
|
2477
2490
|
willFollowLinkToLocation(link, location) {
|
2478
|
-
return this.
|
2491
|
+
return this.elementIsNavigable(link) && this.locationIsVisitable(location) && this.applicationAllowsFollowingLinkToLocation(link, location);
|
2479
2492
|
}
|
2480
2493
|
followedLinkToLocation(link, location) {
|
2481
2494
|
const action = this.getActionForLink(link);
|
@@ -2496,7 +2509,7 @@ class Session {
|
|
2496
2509
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
2497
2510
|
}
|
2498
2511
|
willSubmitForm(form, submitter) {
|
2499
|
-
return
|
2512
|
+
return this.elementIsNavigable(form) && this.elementIsNavigable(submitter);
|
2500
2513
|
}
|
2501
2514
|
formSubmitted(form, submitter) {
|
2502
2515
|
this.navigator.submitForm(form, submitter);
|
@@ -2582,8 +2595,8 @@ class Session {
|
|
2582
2595
|
const action = link.getAttribute("data-turbo-action");
|
2583
2596
|
return isAction(action) ? action : "advance";
|
2584
2597
|
}
|
2585
|
-
|
2586
|
-
const container =
|
2598
|
+
elementIsNavigable(element) {
|
2599
|
+
const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
|
2587
2600
|
if (container) {
|
2588
2601
|
return container.getAttribute("data-turbo") != "false";
|
2589
2602
|
} else {
|
@@ -8,7 +8,7 @@ module Turbo::Streams::ActionHelper
|
|
8
8
|
# # => <turbo-stream action="replace" target="message_1"><template><div id="message_1">Hello!</div></template></turbo-stream>
|
9
9
|
def turbo_stream_action_tag(action, target:, template: nil)
|
10
10
|
target = convert_to_turbo_stream_dom_id(target)
|
11
|
-
template =
|
11
|
+
template = action.to_sym == :remove ? "" : "<template>#{template}</template>"
|
12
12
|
|
13
13
|
%(<turbo-stream action="#{action}" target="#{target}">#{template}</turbo-stream>).html_safe
|
14
14
|
end
|
@@ -34,11 +34,11 @@ module Turbo::StreamsHelper
|
|
34
34
|
#
|
35
35
|
# # app/views/entries/index.html.erb
|
36
36
|
# <%= turbo_stream_from Current.account, :entries %>
|
37
|
-
# <div id="entries">New entries will be appended to this
|
37
|
+
# <div id="entries">New entries will be appended to this target</div>
|
38
38
|
#
|
39
39
|
# The example above will process all turbo streams sent to a stream name like <tt>account:5:entries</tt>
|
40
40
|
# (when Current.account.id = 5). Updates to this stream can be sent like
|
41
|
-
# <tt>entry.broadcast_append_to entry.account, :entries,
|
41
|
+
# <tt>entry.broadcast_append_to entry.account, :entries, target: "entries"</tt>.
|
42
42
|
def turbo_stream_from(*streamables)
|
43
43
|
tag.turbo_cable_stream_source channel: "Turbo::StreamsChannel", "signed-stream-name": Turbo::StreamsChannel.signed_stream_name(streamables)
|
44
44
|
end
|
data/lib/turbo/version.rb
CHANGED
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@hotwired/turbo-rails",
|
3
|
-
"version": "7.0.0-beta.
|
3
|
+
"version": "7.0.0-beta.3",
|
4
4
|
"description": "The speed of a single-page web application without having to write any JavaScript",
|
5
5
|
"module": "app/javascript/turbo/index.js",
|
6
6
|
"main": "app/assets/javascripts/turbo.js",
|
@@ -13,7 +13,7 @@
|
|
13
13
|
"release": "npm publish && git commit -am \"$npm_package_name v$npm_package_version\" && git push"
|
14
14
|
},
|
15
15
|
"dependencies": {
|
16
|
-
"@hotwired/turbo": "^7.0.0-beta.
|
16
|
+
"@hotwired/turbo": "^7.0.0-beta.2",
|
17
17
|
"@rails/actioncable": "^6.1.0"
|
18
18
|
},
|
19
19
|
"devDependencies": {
|
@@ -33,6 +33,12 @@ class Turbo::StreamsChannelTest < ActionCable::Channel::TestCase
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
test "broadcasting append now with empty template" do
|
37
|
+
assert_broadcast_on "stream", %(<turbo-stream action="append" target="message_1"><template></template></turbo-stream>) do
|
38
|
+
Turbo::StreamsChannel.broadcast_append_to "stream", target: "message_1", content: ""
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
36
42
|
test "broadcasting prepend now" do
|
37
43
|
assert_broadcast_on "stream", turbo_stream_action_tag("prepend", target: "messages", template: "<p>hello!</p>") do
|
38
44
|
Turbo::StreamsChannel.broadcast_prepend_to "stream", target: "messages", partial: "messages/message", locals: { message: "hello!" }
|
data/yarn.lock
CHANGED
@@ -23,10 +23,10 @@
|
|
23
23
|
chalk "^2.0.0"
|
24
24
|
js-tokens "^4.0.0"
|
25
25
|
|
26
|
-
"@hotwired/turbo@^7.0.0-beta.
|
27
|
-
version "7.0.0-beta.
|
28
|
-
resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.0.0-beta.
|
29
|
-
integrity sha512-
|
26
|
+
"@hotwired/turbo@^7.0.0-beta.2":
|
27
|
+
version "7.0.0-beta.2"
|
28
|
+
resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.0.0-beta.2.tgz#0f20c1f5b67fe07472a5f1eda2e3f79146324512"
|
29
|
+
integrity sha512-3YyWDaImtkU8MJ+//qV8nmV8KeFInN/nptqwJo1Bl5UjROmixPr3w9tPHyA499Mo1mBJOur8jiXVowbX6VWLUg==
|
30
30
|
|
31
31
|
"@rails/actioncable@^6.1.0":
|
32
32
|
version "6.1.0"
|
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: 0.5.
|
4
|
+
version: 0.5.3
|
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:
|
13
|
+
date: 2021-01-05 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|