Hotwire Discussion

Another attempt to add a data-confirm prompt to button_to helper in Rails

I’ve been trying to update a Rails 6 app to Rails 7 and was having fairly good results until I ran into classic scaffold delete links. I sense that JS alert aren’t vogue anymore, but I’ve clicked on the wrong button or link too many times to allow an inadvertent click destroy something by mistake.

An older post had a few suggestions but with way more javascript than I wanted to deal with. I’m just a Stimulus person which I am soooo happy it came along. So I wrote a short Stimulus controller that was similar to one in the older post to deal with the button_to hidden form.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "submit"]
  static values = { cmsg: String}
  connect() {
    // console.log("destroy confirm")
    // set default confirm_msg
    if (this.hasCmsgValue) {
      this.confirm_msg = this.cmsgValue
    }else{
      this.confirm_msg  = "Are you sure?"
    }
  }

  confirm(){
    // console.log(this.submitTarget.closest('form'))
    let ans = confirm(`${this.confirm_msg}`)
    if (ans == true) {
      this.submitTarget.closest('form').submit()
    }
  }
}

The current Rails 7 scaffold button-to helper (with tailwind.css):

    <div class="inline-block ml-2">
      <%= button_to 'Destroy this entry', entry_path(@entry), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
    </div>

Generates the hidden form:

<div class="inline-block ml-2">
  <form class="button_to" method="post" action="/entries/1"><input type="hidden" 
    name="_method" value="delete" autocomplete="off">
    <button class="mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" type="submit">
      Destroy this entry
   </button>
  <input type="hidden" name="authenticity_token" 
value="zsLeEqDkcTJdNc0IPyFaFlEvSaZLQv_HD8riMdLJfwY_vCB6H5rXARAzarIl9MevstETykLn7ne3bNB8yMeyjQ" autocomplete="off">
  </form>
</div>

I then wrapped that div/button in another div along with Stimulus stuff (slim-rails) and some options

div[class="#{btnDanger} inline-block" 
  data-controller="destroyConfirm" 
  data-action="click->destroyConfirm#confirm"
  data-destroyConfirm-cmsg-value="A longer version of Are You Sure?"]
  span Delete Entry
  = button_to '',@entry, method: :delete,
   class:"hidden",
   data:{destroyConfirm_target:"submit"}

Clicking on the outer div button, calls the controller and asks for confirmation. If yes, it submits the closest form (the hidden form) that delete the object.

That a lot of typing, but then I can add a rails helper that has default options:

  def destroyConfirmTag(model,confirm_msg:"",klass:"",prompt:"")
    klass= "#{btnDanger} inline-block" if klass.blank?
    confirm_msg = "Are You Sure?" if confirm_msg.blank?
    prompt = "Delete #{model.class.name}" if prompt.blank?
    node = content_tag(:div, class: klass,
      data:{
        controller:"destroyConfirm", 
        action:"click->destroyConfirm#confirm",
        destroyConfirm_cmsg_value:confirm_msg
      }){
      concat(tag.span(prompt))
      concat(button_to( '',model, method: :delete,class:"hidden",data:{destroyConfirm_target:"submit"}))
    }
    node 
  end

Now I only have to type: = destroyConfirmTag(@entry) into the view for my delete tag.

If your using icons (e.g. fontawsome), rather than a button you can do something like:

            = destroyConfirmTag(deposit,
              klass:"inline-block mr-4", 
              prompt:tag.i(class:"fas fa-trash w3-text-red"))

I’ll mention that btnDanger is just another helper that return my standard class so you can customize it to your theme.