How can I submit a form on input change with Turbo Streams?
Asked Answered
S

2

31

I have a form I want to submit automatically whenever any input field is changed. I am using Turbo Streams, and if I use onchange: "this.form.submit()" it isn't captured by Turbo Streams and Rails uses a standard HTML response. It works fine when clicking the submit button. How can I work around this?

Sharenshargel answered 2/8, 2021 at 16:0 Comment(0)
A
73

There is a discussion on the hotwire forum, where Mark Godwin figured out why form.submit() isn't working with turbo:

Turbo intercepts form submission events, but weirdly, the JS formElement.submit() method does not trigger the submit event.

And Jacob Daddario figures out that you can use form.requestSubmit() instead:

It turns out that the turbo-stream mechanism listens for form submission events, and for some reason the submit() function does not emit a form submission event. That means that it’ll bring back a normal HTML response. That said, it looks like there’s another method, requestSubmit() which does issue a submit event.

So you can change your code slightly, and use requestSubmit() if a browser supports it, and use submit() if not:

onchange: "this.form.requestSubmit ? this.form.requestSubmit() : this.form.submit()"


Update:

As BenKoshy pointed out, in Turbo 7.1.0, a polyfill was added so you can use form.requestSubmit() without checking for browser support, so you can add this to your input field:

onchange: "this.form.requestSubmit()"
Augite answered 12/10, 2021 at 8:59 Comment(4)
I confirm this works.Thompkins
There's a polyfill which handles the browser compatibility issue out of the box, so long as you install the latest version of turbo, you'll be fine.Angilaangina
this works if you posting form. but if you have form_with( ... method: :get ) it will be submitted in html format. to fix this behavior you can use form_with(... data: { turbo_stream: true }). This is for turbo-rails 1.4.0 which is latest a.t.m.Fayfayal
There's nothing weird about form.submit() not emitting the event. A standard pattern is the "submit" event is listened for, something done, then the form submitted from within the event handler. While it's possible to mitigate, the standard functionality would be an infinite loop.Politicize
P
3

I need to implement this for an app with lots of forms. I wound up using Stimulus. Below is the whole controller:

import { Controller } from "stimulus"
const _ = require("lodash")
export default class extends Controller {
  connect() {
    let that = this;
    that.element.addEventListener('change', _.debounce(that.handleChange, 500))
  }
  handleChange(event) {
    event.preventDefault()
    // event.target.name // => "user[answer]"
    // event.target.value // => <user input string>
    event.target.form.requestSubmit()
  }
}

and here it's used in a form with a single text input. NOTE the controller is attached to the form, not to the inputs.

<%= turbo_frame_tag dom_id(form_model) do %>

      <%= form_with model: form_model,
        format: :turbo_stream,
        html: { data: { controller: "buttonless-form" } } do |f| %>

        <%= f.hidden_field :question_id, value: question.id %>

        <%= f.text_field :answer_value, class: "input shadow wide", placeholder: "Enter your answer here" %>
        
        
      <% end %> 

  <div id=<%= "question_#{question.id}_output" %>>
   <p> <!-- feedback to the user shows up here via Turbo -->
  </div>

<% end %> <!-- end turbo frame -->
Polka answered 5/8, 2021 at 18:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.