2

I need to capture an anchor node with an image inside using event delegation.

document.addEventListener(
  'click',
  function(event) {
    console.log(event.target);
    return true;
  },
  false
);
<a href="#" class="link">
  <img src="http://placehold.it/100x100" alt="">
</a>

event.target is always img.

How can check if the click was made on a node with class .link?

UPDATE: to be clear here is an example with jQuery

When I use jQuery.on() there is a node in this property in callback fucntion, not img node. With pure JS I can determine initial target only.

  • Possible duplicate of [What is event bubbling and capturing?](http://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing) – Christoph May 02 '17 at 18:16
  • @Christoph This question has a much narrower scope than that one, and it's referencing a specific problem, rather than comparing the two methods. – freginold May 02 '17 at 18:31
  • 1
    @freginold I realize that, but I thought the answers are very thorough and might help the OP to better understand the concept. Short answer to OPs question would simply be: "The event target always is the actual element you clicked on." ;) – Christoph May 02 '17 at 18:50
  • _ "The event target always is the actual element you clicked on." _ That's exactly what I have with my solution. Probably I understand the bubbling and capturing wrong but as I read an event go through the DOM tree to the root element (bubbling) and triggers for each node. I need to fire a function when event triggers on `a.link` node even if an actual element is `img` node inside `a`. – Maks Sherstobitow May 02 '17 at 19:20

2 Answers2

1

You can check if an element has a class by calling:

element.classList.contains('link');

What you want right now is to do something when an <img> within an <a class="link"> is clicked. To determine if the clicked <img> has a parent <a class="link"> we must traverse up its parent tree and check.

This is very similar behavior as the jQuery example you have, i.e.

$('body').on('click', '.link', callback)

except the jQuery matches a whole query, not just a class.

Here's how you could do this:

// function to determine if the element has the link class
const hasLinkClass = element => element
  ? element.classList && element.classList.contains('link')
  : false;

// function that accepts an event handler and returns
// a wrapper function arround it.
// The wrapper is called it if the passed in event 
// object contains as its target an <img> with a parent
// with .link class
function filterImagesWithinLinks(handler) {
  return function(event) {
    let elem = event.target;

    // ignore clicks that are not on an image (it might be better to be more specific here)
    if (elem.tagName === 'IMG') {
    
      // filter until we find the parent with .link class
      while (elem && !hasLinkClass(elem)) {
        elem = elem.parentNode;
      }

      // if a parent with .link class was found, 
      // call the original handler and set its `this`
      // to the parent.
      if (elem) {
        handler.call(elem, event);
      }
    }
  };
}

// function handler that fires when 
// an <img> that has a parent with 
// class 'link' was clicked
function handler(event) {
  console.log('target : ', event.target);
  console.log('this   : ', this);
}

document.addEventListener(
  'click',
  filterImagesWithinLinks(handler),
  false
);
a.link {
  display: block;
  padding: 10px;
  background: #018bbc;
}

.middle {
  background: salmon;
  padding: 10px;
  text-align: center;
}
<a href="#" class="link">
  <p class="middle">
    <img src="http://placehold.it/100x100" alt="" />
  </p>
</a>
nem035
  • 34,790
  • 6
  • 87
  • 99
  • Worth noting that arrow functions won't natively work in IE. – freginold May 02 '17 at 18:28
  • 1
    Sure, but the arrow function is only there for simplicity & readability. It's trivial to rewrite into a plain function. – nem035 May 02 '17 at 18:36
  • Yup, no disagreement... just noting it in case others are stuck using IE (like I am). – freginold May 02 '17 at 19:34
  • I don't know exact structure of markup. It can be several `span` nodes inside with unpredictable depth. `parentNode` will be quite useless. – Maks Sherstobitow May 02 '17 at 19:34
  • @Maks then why use a global handler? Why not just attach a handler on the `` element? What you want right now is to say "When an `` within `` is clicked, do something", which can only be done by filtering the parents of the clicked `` until we reach ``. – nem035 May 02 '17 at 19:37
  • @nem035 this doesn't meet an application needs. I develop a JS-based application with loads of ajax communication and `a.link` can be loaded async. WIth your solution an application needs to updated all `a.link` handlers after each async update. In my case it's better to delegate onclick listening to a root element of page. – Maks Sherstobitow May 02 '17 at 19:42
  • @MaksSherstobitow I've edited my answer. Is this what you had in mind? – nem035 May 03 '17 at 02:58
  • 1
    @nem035 basically that's what I think I can use. Passing through parent nodes seems to be the only solution. Thank you! – Maks Sherstobitow May 03 '17 at 07:48
0

If you add some text or other content to the anchor tag, it will be easier to see the difference between the a and the img. See this example on JSFiddle -- it shows whether or not an element of the class link was clicked:

https://jsfiddle.net/Lppt4hyk/4/

Here's the code (only slightly modified from yours):

<a href="#" class="link"> Anchor
  <img src="http://placehold.it/100x100" alt="">
</a>
document.addEventListener(
  'click',
    function( event ) {
            var patt = /(?:^link | link$|^link$| link )/g;
            if (patt.test(event.target.className)) {
        alert('link class was clicked');
      }
      else { alert('link class was not clicked'); }
    return true;
  },
    false
);
div {
  background: red;
  display: block;
  height: 90%;
  width: 90%;
}

.n1 {
    background: yellow;
}

.n2 {
    background: green;
}

UPDATE: Added check for the link class name if it's not the only class assigned to that element.

UPDATE: Added regex checks to weed out link as part of a larger word.

freginold
  • 3,946
  • 3
  • 13
  • 28
  • `event.target.className == "link"` will only work for the special case of the element's class property is **exactly** equal to the string `"link"`. It would break for any whitespace or multiple classes. Also the main problem is that the ``, which is the parent of ``, has the class `"link"`. – nem035 May 02 '17 at 17:46
  • @nem035 Good point about the class name, I'll update that. The OP's question originally had the `a` with the `link` class assigned; why did you remove it? – freginold May 02 '17 at 17:50
  • I removed it by accident when I copied his/hers code from the jsfiddle link, which didn't have the `"link"` class on ``. Added it back. – nem035 May 02 '17 at 17:56
  • Now the problem with your solution is that it will pass for elements that have a class `unlink` or `linking` or anything containing the characters `link`. Also, you should use SO snippets instead of jsfiddle, it's easier to run the code right here then switch to jsfiddle all the time. – nem035 May 02 '17 at 17:57
  • @nem035 Ah, good point. I'll add the regex checks when I have time to edit it later. As for the SO snippets, they are great but I kept this one on JSFiddle to work from the OP's code. – freginold May 02 '17 at 18:01
  • @nem035 Added the regex checks. Thanks for the advice. – freginold May 02 '17 at 18:27
  • 1
    still [missing](https://repl.it/HcQg/0) some cases. I was hoping you'd realize that using `className` is a more difficult and less performant way of establishing class existence and that you should use `classList` instead and that way I could delete my answer :) – nem035 May 02 '17 at 18:36
  • The thing is that I don't need any text in an anchor tag but an image only. It's natural that `e.target` will be a n anchor node if a text content inside is clicked. What I want is to capture click event on `a` node when a click event from nested `img` is trigered. – Maks Sherstobitow May 02 '17 at 19:19
  • @MaksSherstobitow So basically you want to be able to detect if any ancestor of the element that was clicked has the `link` class? – freginold May 02 '17 at 19:35
  • @nem035 I prefer `classList` too, but alas I'm stuck using IE which only partially supports it, so I default to `className` instead. `classList` is definitely better, I agree. – freginold May 02 '17 at 19:36
  • 1
    @freginold I updated the question. I want to implement jQuery's `on` function with pure JS. An idea behind bubbling and capturing looks absolutely perfect for this. The problem is that the event triggers on `img` node only and doesn't reach `a` node. If it did then on one of "bubbling" stage I would get `a` node, could check its class and fire related functionality. – Maks Sherstobitow May 02 '17 at 19:39
  • @nem035 Looks like something weird going on with my regex, works sometimes but not others. [https://jsfiddle.net/Lppt4hyk/6/](https://jsfiddle.net/Lppt4hyk/6/) – freginold May 02 '17 at 19:55
  • @MaksSherstobitow It looks like nem035's answer provides the functionality you're looking for. – freginold May 03 '17 at 17:11
  • @freginold yes, it's true. But I don't know how to make an accepted answer. – Maks Sherstobitow May 04 '17 at 17:23
  • @MaksSherstobitow When you're looking at nem035's answer, you should see a gray check mark to the left of the answer (where the up and down vote arrows are). If you click on the check mark, it should turn green, showing that you accepted that answer. – freginold May 04 '17 at 17:51