0

Having the following structure:

<div class="the-parent">
  <div>
    <a onClick="doParentStuff()">
      <div>
        <i onClick="doChildStuff()"></i>
      </div>
    </a>
  </div>
</div>

Now, when the child element (icon) is clicked it logs the content of doChildStuff() but afterwards it also logs the content of doParentStuff().

Is there a way to call doChildStuff only when the icon is clicked and call doParentStuff when everything else inside the-parent div is clicked?

Leo Messi
  • 5,157
  • 14
  • 63
  • 125
  • Inline event handlers like `onclick` are [bad practice](/q/11737873/4642212). They’re an [obsolete, cumbersome, and unintuitive](/a/43459991/4642212) way to listen for events. Always [use `addEventListener`](//developer.mozilla.org/en/docs/Learn/JavaScript/Building_blocks/Events#inline_event_handlers_%E2%80%94_dont_use_these) instead. Then, utilize [event delegation](/a/34896387/4642212) with [`closest`](//developer.mozilla.org/en/docs/Web/API/Element/closest) and [`contains`](//developer.mozilla.org/en/docs/Web/API/Node/contains). – Sebastian Simon Dec 15 '22 at 13:40
  • Related: [Javascript event delegation, handling parents of clicked elements?](/q/9914587/4642212). [`composedPath`](//developer.mozilla.org/en/docs/Web/API/Event/composedPath) can also be used. Make sure you’re aware of [potential caveats of `stopPropagation`](/q/54815439/4642212) and read the [documentation](//developer.mozilla.org/en/docs/Web/API/Event/stopPropagation). – Sebastian Simon Dec 15 '22 at 13:47
  • I would suggest removing all `onclick` attributes, then adding this JS: `addEventListener("click", ({ target }) => { const iElement = target.closest(".the-parent a i"), anchorElement = target.closest(".the-parent a"); if(iElement){ doChildStuff(); } else if(anchorElement){ doParentStuff(); } });`. I’d also suggest reconsidering the usage of ``. See [Accessibility concerns of the `` element](//developer.mozilla.org/en/docs/Web/HTML/Element/a#onclick_events). – Sebastian Simon Dec 15 '22 at 13:52

2 Answers2

1

When the child is clicked, you must stopPropagation of the event:

function doChildStuff(e) {
  e.stopPropagation();
  console.log('child clicked');
}

function doParentStuff() {
  console.log('parent clicked');
}
<div class="the-parent">
  <div>
    <a onClick="doParentStuff()">
      <div>
        Test
        <button onClick="doChildStuff(event)">Child</button>
      </div>
    </a>
  </div>
</div>
Mihai Matei
  • 24,166
  • 5
  • 32
  • 50
1

Avoid the use of Event.stopPropagation() (unless you really, really know what you're doing).
An application, or third party code, should never stop or prevent an event to propagate throughout the application layers / components.
Instead, change your logic to implement a third function (like doStuff) that will trigger a desired function depending on the Event.target.closest() match

const doChildStuff = () => {
  console.log("child stuff");
};

const doParentStuff = () => {
  console.log("parent stuff");
};

const doStuff = (ev) => {
 if (!ev.target.closest(".icon")) {
    doParentStuff();
 }
 doChildStuff();
};

document.querySelectorAll(".anchor").forEach(elAnchor => {
  elAnchor.addEventListener("click", doStuff);
});
<div class="the-parent">
  <div>
    <a class="anchor">
      <div>
        Link
        <i class="icon">icon</i>
      </div>
    </a>
  </div>
</div>

Also, stop using HTML inline on* attribute handlers. Such code is hard to maintain and debug. JavaScript should be in one place only, and that's the respective tag or file. Use addEventListener instead.

Even if not asked, if you want to also separate the handler for the parent, simply put it into an else block:

const doChildStuff = () => {
  console.log("child stuff");
};

const doParentStuff = () => {
  console.log("parent stuff");
};

const doStuff = (ev) => {
  if (!ev.target.closest(".icon")) {
    doParentStuff();
  } else {
    doChildStuff();
  }
};

document.querySelectorAll(".anchor").forEach(elAnchor => {
  elAnchor.addEventListener("click", doStuff);
});
<div class="the-parent">
  <div>
    <a class="anchor">
      <div>
        Link
        <i class="icon">icon</i>
      </div>
    </a>
  </div>
</div>
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • your code snippet doesn't work fine, it logs both messages when it should log only one – Leo Messi Dec 15 '22 at 19:48
  • @LeoMessi but you asked that behavior only for the icon - which indeed respects the asked question requirement. If you click on the icon - only one log will be present instead of two. – Roko C. Buljan Dec 15 '22 at 19:50
  • @LeoMessi otherwise the fix is simple as putting `doChildStuff();` inside and else block: like: `else { doChildStuff(); } `. That's it. – Roko C. Buljan Dec 15 '22 at 19:51
  • 1
    @LeoMessi sad to see another stopPropagation answer getting into account - teaching other people getting to this pages really bad coding habits :( – Roko C. Buljan Dec 15 '22 at 19:52
  • Edited the answer to add that other example as well. @LeoMessi – Roko C. Buljan Dec 15 '22 at 19:54