tsvallender
tsvallender

Reputation: 2974

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

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?

Upvotes: 37

Views: 20104

Answers (2)

wnm
wnm

Reputation: 1759

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()"

Upvotes: 80

aidan
aidan

Reputation: 1795

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 -->

Upvotes: 5

Related Questions