Skip to content

Instantly share code, notes, and snippets.

@john-hamnavoe
Created January 6, 2023 10:04
Show Gist options
  • Save john-hamnavoe/fb74244cea6eadc8f89c0826c24c97ee to your computer and use it in GitHub Desktop.
Save john-hamnavoe/fb74244cea6eadc8f89c0826c24c97ee to your computer and use it in GitHub Desktop.
Autosaving a form rails

Introduction

This approach based on drifting ruby attaches stimulus controller automatically to the form that adds change events that allow the form to be save. Controller approach is slightly different in we add line for turbo stream check the video for difference. This is really for updates rather than creates.

Form Builder

Create a new folder apps\builder then added AutoFormBuilder.rb example can be seen in baccus

Stimulus Controller

Add stimulus controller autosave_controller.js register in index.js for stimulus components.

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="autosave"
export default class extends Controller {
  connect() {
    if (this.element.hasAttribute("data-action")) {
      console.log("data-action already set", this.element.getAttribute("data-action"))
      this.element.setAttribute("data-action",`${this.element.getAttribute("data-action")} change->autosave#submit`)
    }
    else {
      this.element.setAttribute("data-action", "change->autosave#submit")
    }
  }

  submit() {
    let data = new FormData()
    data.append(this.element.name, this.value())

    // regular expression here to find the last value in [brackets] e.g. competition_entry[predictions_attributes][0][home_score] gives home_score
    // we replace that with "id" to see if have nested attributes autosaving as we need to send the id of the nested object
    let field_name_id  = this.element.name.replace(/[^\[\]]+(?!.*\[)/, 'id');
    if (document.getElementsByName(field_name_id).length == 1) {
      console.log("appending-id for " + field_name_id)
      data.append(field_name_id, document.getElementsByName(field_name_id)[0].value)
    }

    fetch(this.url(), {
      method: "PUT",
      headers: {
        Accept: "text/vnd.turbo-stream.html",
        "X-CSRF-Token": this.csrf_token()
      },
      //redirect: "manual",
      body: data
    })
  }

  value() {
   // console.log(this.element.type)
    switch (this.element.type) {
      case "checkbox":
        return this.element.checked == true ? 1 : 0
      default: 
        return this.element.value
    }
  }

  url() {
    return this.element.form.action
  }

  csrf_token() {
    return document.head.querySelector('meta[name="csrf-token"]').getAttribute("content")
  }
}

Form and Controller

Tell form to use the AutoSaveBuilder and give unique dom_id for example:

<%= form_with(model: time_log, builder: AutoSaveBuilder, id: "#{dom_id(time_log)}_form") do |form| %>

In the controller responde to turbostream for example:

    if @time_log.update(time_log_params)
      respond_to do |format|
        format.html { redirect_to time_logs_path, notice: "Time log was successfully updated." }
        format.turbo_stream { head :ok }
      end
    else
      respond_to do |format|
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @time_log.errors, status: :unprocessable_entity }
        format.turbo_stream { head :unprocessable_entity }
      end
    end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment