@BKSpurgeon have you tried hooking a callback up to the turbo:submit-end
event?
@dan thank you for your response.
The issue with turbo:submit-end is that I wish to act on the results after they are added to the dom. I was unable to get turbo:submit-end to do this?
<div data-controller="slideshow" data-slideshow-index-value="1" />
turbo_frame_tag "results"
<form />
<li> results etc. </li>
end
- Form is submitted via javascript (i.e. within turbo_frame_tag)
- Controller is hit and results are returned.
- We want to perform some action on the results that are returned. i.e. a callback after the form submission and after everythingās rendered
I was unable to get turbo:submit-end
to work on the rendered response of the form submission. Off the top of your head: is this the expected behaviour?
highlightCurrentSlide(event){
this.slideTargets.forEach((element) => {
// the slideTargets get updated on the form submission
// the highlightCurrentSlide method seems to run before the
// the results of the form submission are rendered.
}, this)
}
Any advice would be much appreciated. chrs!
Hmmā¦turbo:submit-end
only fires after your controller has responded. So I guess the issue here is the gap between getting that response and the HTML actually being put in the DOM. I wouldnāt expect that gap to be significant, though.
A couple of ideas (I havenāt tested any of them, mind):
The event exposes the FetchResponse
via event.detail.fetchResponse
(I think). FetchResponse
has a responseHTML
method that returns a promise. You could make your highlightCurrentSlide
method async and on the first line await event.detail.fetchResponse.responseHTML()
.
Or (more of a long shot) Iād maybe try wrapping highlightCurrentSlide
in a window.requestAnimationFrame
callback.
Or, failing any of that, thereās probably a solution using MutationObserver
and/or maybe giving those slideTargets
their own controller (so you could just use connect
).
I think FrameElement.loaded could help you
In Stimulus controller:
// get frame const frame = document.getElementById("frame_id"); // submit form this.formTarget.dispatchEvent(new CustomEvent('submit', { bubbles: true })); // listen for frame rendering frame.loaded.then(function (success) { // some actions after frame rendered }, function (error) {});
Thatās pretty cool!
I was getting Fetch API cannot load [url here] due to access control checks.
errors in Safari with the polyfill.
But this works in Safari and Brave.
This actually seemed to do the trick for me in Safari / Firefox / Chrome. Curious if this is the best approach?
After moving to using the new importmap-rails gem and failing to get rails-ujs working I submit my forms using -
Turbo.navigator.submitForm(this.formTarget)
Ok, I couldnāt underand, why all the replies were so positive, as ānothingā didnāt work in my stimulus controller, after I upgraded turbo (previously I just āclickedā the submit button, what worked ⦠until now).
But Iām really glad, that I read until the end of the topic, as those two solutions (actually itās the same) are the only ones, which work for my controller, where I want to submit and register to the turbo:submit-end event:
from @defsdoor
Turbo.navigator.submitForm(this.formTarget)
and
import { navigator } from "@hotwired/turbo"
...
navigator.submitForm(this.element.closest("form"))
from @mrhead
much thanks to you both!
As it seems to be a bit of a hidden api, I hope that will least
Update: With this commit as of 2021-11-11 requestSubmit polyfill is included by default so it should just work out of the box, obviating much of the headache. Hotwire now relies on it.
This is fantastic news!
Another good experience here with the Turbo.navigator approach. I had a checkbox form element that would trigger a response but it wouldnāt work with Safari desktop, iOS or Chrome iOS.
Tried to troubleshoot it here but couldnāt get it working: https://discuss.hotwired.dev/t/checkbox-form-does-not-submit-on-safari-and-mobile-chrome-using-requestsubmit/4011
Then tried the Navigator route and all is well with Safari and Chrome mobile. Thanks all!
The requestSubmit
method did not work in my case, I think because the search form was already nested in higher level form (autocomplete field in a form).
What I like to do now for simple actions that trigger a Turbo Stream is to use request.js to send the HTTP request.
It has a responseKind option that you can set to turbo-stream
. It will set the Accept Header to text/vnd.turbo-stream.html
so the backend will respond with a Turbo Stream.
I think this trick deserves to be better known from Hotwire devs!
// autocomplete_controller.js
import { get } from "@rails/request.js"
export default class extends Controller {
search({ target: { value } }) {
get(`/search?query=${value}`, {
responseKind: "turbo-stream"
})
}
}
<div data-controller="autocomplete">
<input
type="search"
autocomplete="off"
data-action="keyup->autocomplete#search"
>
<turbo-frame id="autocomplete_results"></turbo-frame>
</div>
# search_controller.rb
class SearchController < ApplicationController
def index
@results = ...
respond_to do |format|
format.turbo_stream
end
end
end
# app/views/search/index.turbo_stream.erb
<%= turbo_stream.update("autocomplete_results") do %>
<ul>
<% @results.each do |result| %>
<li><%= result %></li>
<% end %>
<ul>
<% end %>
It does work well. Iāve noticed one exception, and that is the new page refresh functionality. To do this properly (and not refresh the browser that initiated the broadcast) an ID needs to be sent with the request. Turbo sends the ID normally bit request.js
doesnāt know about it yet. Iāve raised an Issue with request.js
: Adding X-Turbo-Request-Id support Ā· Issue #73 Ā· rails/request.js Ā· GitHub
For now it can be achieved by temporarily overwriting window.fetch
with the adjusted fetch
from Turbo so it might be as simple as request.js
allowing us to sub in that fetch method manually, or using it if it imports successfully.