I have a dynamically generated page of items in an inventory. I am trying to create an onclick event so that if the user clicks on the displayed item information or image the event listner executes. I can make it work by adding an onclick="doSomething(this.id) in the HTML, but can't make it work using the methods in any of the event bubbling, and event delegation tutorials I have seen.
The item image, and data are presented in individual HTML tables, one for each item. Each is given a unique id, and they are all contained in a single container div element. The addEventListener is assigned to the div instead of having a listener for each table.
What seems to be happening is a click event triggers for the img element, the td element. the tr, or the table, and if I am very careful placing the cursor before clicking the table element. The event does not seem to bubble up no matter what I do. I should be able to get it to fire on each successive element, but something is stopping propagation.
I need the to retrieve the unique id (of the table) when that table, or any descendant element is clicked.
var theParent = document.querySelector('#container');
theParent.addEventListener("click", doSomething);
}
function doSomething(e) {
console.log(e.target.id) + " \r\n";
if (e.target !== e.currentTarget) {
var clickedItem = e.target.nodeName;
console.log(clickedItem);
}
}
<div id="container">
<table class="items" id="1234">
<tr>
<td>
<img src="path/to/item.jpg">
</td>
<td>
this is item 1234
</td>
</tr>
</table>
...
</div>
or
<div id="container">
<div class="items" id="1234">
<table>
<tr>
<td>
<img src="path/to/item.jpg">
</td>
<td>
this is item 1234
</td>
</tr>
</table>
</div>
...
</div>
I would think the if (e.target !== e.currentTarget) should execute console.log(clickedItem) for each descendant object up to, but not including the currentTarget. It doesn't, even if I remove the if() screening. I would like some clarification on that, but more importantly I would like to rework the if statement to be true only for the clicks on the direct descendants of the currentTarget.
I discovered that if I add the same listener to every element in a segment of the DOM tree a single mouse click will trigger a click event for each element starting with the element clicked, traveling up each successive element (bubbling). I can then test each node for className "item" and when found read its id. This produces the result I want, but means hundreds of elements are assigned the same listener. It's probably just hundreds of pointers, but doesn't strike me as very efficient
However if I add the listener to just the top element a click event only fires the listener once no matter which element actually received the click (e.g. the img but not the td it's in, or the tr the td is in, etc.). I see lots of reasons this is a good thing under normal circumstances, but it doesn't do what I need.
Thinking about it I came up with the following solution. I add the listener to the top element. Then the handler tests if e.target has a className of "item", if not it walks up each successive parent until it does find the node with a className "item", then reads the id of that node (which is the inventory item number I am looking for).
function doSomething(e) {
if(e.target !== e.currentTarget){
var el = e.target;
do {
el = el.parentNode;
} while (el.className !="item");
var clickedItem = el.id;
console.log("Clicked item id: " + clickedItem);
}
}
topofTree= document.querySelector('#container') ;
topofTree.addEventListener("click",doSomething) ;
<div id="container">
<table class="item" id="0064">
<tr>
<td><img src="/spiritmasks/images/MSK_spirit_mask_8.jpg" ></td>
<td>
<table>
<tr><td>Item ID:</td><td>0064</td></tr>
<tr><td>Display Name:</td><td>Spirit Mask #8</td></tr>
<tr><td>Description:</td><td></td></tr>
<tr><td>Type:</td><td>SPIRIT MASK</td></tr>
<tr><td>Location:</td><td>GALLERY</td></tr>
<tr><td>In Slide Show:</td><td>NO</td></tr>
<tr><td>Size:</td><td></td></tr>
<tr><td>Price:</td><td></td></tr>
<tr><td>Comments:</td><td></td></tr>
</table>
</td>
</tr>
</table>
</div>