[Stimulus] Get original element of `data-action`

Is there a way to get the original HTML element that contains the data-action attribute when a Stimulus action is fired? I got this setup:

<div data-controller="copy">
  <button data-action="copy#copy">
    Click here to copy! <i class="fas fa-copy"></i>
  </button>
  <span data-copy-target="copy">This content here will be copied</span>
</div>
import { Controller } from "@hotwired/stimulus";
import showToastAttachedTo from "toaster";

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

  copy(event) {
    if (window.navigator?.clipboard) {
      window.navigator.clipboard.writeText(this.copyTarget.innerText);
      showToastAttachedTo(event.target, "Copied successfully!");
    } else {
      showToastAttachedTo(event.target, "Copy failed, copy manually!");
    }
  }
}

When the text on the button is clicked, the Toast gets attached to the button and everything looks good. However, if the user clicks on the <i>-Icon inside of the button, the Toast gets attached to the Icon, which is not intended. Inspecting the event argument in copy, I found no property that returns the original <button> element, i.e. the “source” of the event. Neither target nor originalTarget nor currentTarget contain the original <button>.

I could use some heuristic like event.target.closest("[data-action=\"copy#copy\"]"), however this breaks apart as soon as there are multiple Stimulus controllers nested inside of each other or as the data-action becomes more complex, i.e. in some places data-action="click->copy#copy" is used instead.

I don’t think the element is exposed.

You could use a target to make toast element parents explicit, then find the targeted toast:

  • const toastTarget = toastTargets.find(target => target.contains(event.target)) 
    

You could allow/block list elements (closest button/div or closest not i/img etc).

There’s also the option of a more permissive CSS selector: [data-action*=copy#copy]

Are you looking for event.currentTarget?

1 Like

Sorry for the late reply. You are absolutely right, event.currentTarget does contain the correct element.

For debugging, I saved the event as global var for the browser console and it turned out to be null there, so I assumed it would be null all the time, which is not the case.