yeller.js - auto submit form with turbo stream

Edit
equivalent Web Development
Public
stimulus
Rails
Note to myself true "yeller" should be able to call different path as the form defined. e.g. autosafe to different endpoint.
The scenarios here for yeller that submit the form on event should be called "submitter" 


Click button version



//app/javascript/controllers/yeller_controller.js
import { Controller } from "@hotwired/stimulus"

// <div data-controller="yeller">
//  <form>
//    <input type="submit" data-yeller-target="submit">
//    <input type="checkbox" name="some_field" value="some_value" data-action="yeller#call">
//  </form>

export default class extends Controller {
  static targets = ["submit"]

  call() {
    this.submitTarget.click()
  }
}



JS version without button



Using requestSubmit() which triggers Turbo events



//app/javascript/controllers/yeller_controller.js
import { Controller } from "@hotwired/stimulus"

// <form data-controller="yeller">
//   <input type="checkbox" name="some_field" value="some_value" data-action="yeller#call">

export default class extends Controller {
  call()  {
    this.element.requestSubmit()
  }
}


JS version using turbo navigator


//app/javascript/controllers/yeller_controller.js
import { Controller } from "@hotwired/stimulus"
import { Turbo } from "@hotwired/turbo-rails"

// <form data-controller="yeller">
//   <input type="checkbox" name="some_field" value="some_value" data-action="yeller#call">

export default class extends Controller {
  call()  {
    Turbo.navigator.submitForm(this.element);
  }
}

JS version using request.js


make sure you pin

pin "@rails/request.js"


stimulus

//app/javascript/controllers/yeller_controller.js
import { Controller } from "@hotwired/stimulus"
import { FetchRequest } from "@rails/request.js"

// <div data-controller="yeller"
//   ...or
// <div data-controller="yeller" data-yeller-url-value="/some/different/path/than/form", data-yeller-method-value="post">

export default class extends Controller {
  static values = { url: String, method: String }

  async call() {
    let form = this.element.querySelector('form')

    const formData = new FormData(form);
    formData.delete('_method')

    let url = this.hasUrlValue ? this.urlValue : form.action;
    let httpMethod = this.hasMethodValue ? this.methodValue : form.method;

    const request = new FetchRequest(httpMethod, url, {responseKind: "turbo-stream", body: formData})
    request.perform()
  }
}

use


to autosubmit to same endpoint where <form> points to (with same HTTP method)
div data-controller="yeller"
  = simple_form_for @model, url: some_path do |f|
    = f.input :whatever, as: :radio_buttons, input_html: { data: { action: "change->yeller#call" } }

to autosubmit form to different controller than where form points to (with same HTTP method)
div data-controller="yeller" data-yeller-url-value="#{different_path}" data-yeller-method-value="post"
  = simple_form_for @model, url: some_path do |f|
    = f.input :whatever, as: :radio_buttons, input_html: { data: { action: "change->yeller#call" } }



rails controller

def create
  @cq_form.update(cq_property_params)

  respond_to do |format|
    format.turbo_stream
  end
end

DEbounce JS version


(not tested yet but should work)

import { Controller } from "@hotwired/stimulus"
import { FetchRequest } from "@rails/request.js"

// Connects to data-controller="yeller"
// <div data-controller="yeller" data-yeller-url-value="/draft_save/2/path", data-yeller-method-value="patch">
//   <form data-yeller-target="form" action="/real_save/2" method="post">
//     <input type="text" name="message" data-action="keyup->yeller#debounceCall" />
//     <button type="button" data-action="click->yeller#call">Save Draft</button>
//     <button type="submit"">Real save</button>
//   </form>

export default class extends Controller {
  static values = { url: String, method: { type: String, default: 'post' } }
  static targets = [ "form" ]

  connect() {
    this.debounceCall = this._debounce(this.call.bind(this), 300); // this should create #debounceCall method
  }

  async call(event) {
    const formData = new FormData(this.formTarget);
    formData.delete('_method');
    const request = new FetchRequest(this.methodValue, this.urlValue, {
      responseKind: "turbo-stream",
      body: formData
    });
    request.perform();
  }

  _debounce(fn, delay) {
    let timeout;
    return (...args) => {
      clearTimeout(timeout);
      timeout = setTimeout(() => fn(...args), delay);
    };
  }
}