Why compare element to this keyword? - javascript

I am looking at the following link which is a part of event delegation documentation.
In the link, we have the followig
let table = document.getElementById('bagua-table');
let selectedTd;
table.onclick = function(event) {
let target = event.target;
while (target != this) {
if (target.tagName == 'TD') {
highlight(target);
return;
}
target = target.parentNode;
}
}
I am just unable to understand why the following while (target != this). I understand that the this keyword here refers to the entire table since that is the object in play but why write a loop in this manner?
Maybe my understanding of this is not as deep. If someone could please clarify.
Thanks

What this does is, the target starts at the event's target - the innermost element the event was dispatched to. Then it searches the target, then its ancestor, then its ancestor, and so on. The first one that matches the condition target.tagname == 'TD', if any, will result in highlight being called and the loop ending. If no elements match, then the loop will continue until the target is this - the table - and will end at that point.
But this is pretty confusing code. Using .closest would be much easier, since it will find the first ancestor matching a selector.
table.onclick = (event) => {
const td = event.target.closest('td');
if (td) highlight(td);
};
If nested tables are a possibility, and you don't want the event to go outward past the table in question, include the table in the selector string:
table.onclick = (event) => {
const td = event.target.closest('#bagua-table td');
if (td) highlight(td);
};

In an event listener, this is equivalent to event.currentTarget, which is the element that the listener was added to (the table).
event.target is the nested element that was actually clicked on.
So the loop is walking up the DOM hierarchy (using target.parentNode) from the clicked element until it reaches the table that the listener was added to.
If it reaches a TD element along the way, it highlights that element and returns, which breaks out of the loop.
It's roughly equivalent to:
highlight(event.target.closest("TD"));

Related

Click on table cell targets child element

I have a simple HTML table with ids in some cells:
<td id="x-11"><b>Is it a cell?</b> What a cell!</td>
Now I'd like to pass the ID to a JavaScript function triggered onclick:
const $tds = document.querySelectorAll('td[id]')
for (let i = 0; i < $tds.length; i++)
$tds[i].addEventListener('click', ev => console.log(ev.target.id))
It works as expected if I click an empty area or normal text within the cell. But if I hit the text inside the <b> element something strange happens: The browser says that the <b> element is the ev.target - although it has no listener whatsoever.
Can someone explain this behavior or offer a solution?
Update: As stated in the comments, Difference between e.target and e.currentTarget provides the answer (for ActionScript, but that doesn't make a difference here) but the question is different.
You need to use this or ev.currentTarget that always refers to the DOM element the listener was attached to, instead, the ev.target will refer to the element that fired the event, in this case, the DOM element that was clicked :
$tds[i].addEventListener('click', function(){
console.log(this.id)
});
//Or
$tds[i].addEventListener('click', ev => console.log(ev.currentTarget.id))

jQuery selector precedence when using pattern matching

I am creating a form that implements a bunch of similar elements. They are custom select boxes, created out of <ul>s.
Some of these elements are slightly different in the way I want the mousedown event to be handled though.
The way I have it set up currently is that, by appending _custom_select to the end of an elements class name, it will be treated as one of these special elements as far as CSS is concerned.
However, when the string selections is found inside a class name (that will coincidentally also end with _custom_select in order to apply the proper styling) I want to use a different mousedown event handler.
This is the relevant section of my event listener set up:
$('[class$="_custom_select"] li').mousedown(function(event){
var opt= event.target;
if(opt.className!='li_disabled' && event.which==1)
{
if(opt.className=='li_unselected'){
opt.className= 'li_selected';
}
else{
opt.className= 'li_unselected';
}
update_selections(opt.parentElement);
}
});
$('[class*="selections"]').mousedown(function(event){
var opt=event.target;
if(event.which==1){
if(opt.className=='li_unselected'){
opt.className= 'li_selected_2';
}
else{
opt.className= 'li_unselected';
}
}
});
This code works, but notice how, in the second binding, I had to bind the event listener to the ul that holds the li that is actually being clicked.(The ul is the element whose class name matches the pattern) In the first one however, I can bind the event listener directly to the li elements contained within the ul.
If I change the second jQuery selector to $('[class*="selections"] li') the event listener is never bound to the corresponding lis.
What is causing this behavior?
I am aware that I can just check event.target.tagName to ensure the event is bubbling up from an <li>, but that is not what the question is about.
I originally thought it had something to do with precedence and that the listeners weren't being bound because the lis that would have matched the second selector already matched against the first selector.
However, after implementing logging and looking at the DOM I have determined that when I change the second selector to: $('[class*="selections"] li') neither event listener is bound to the lis that match the second selector.
Here is a link to a JS fiddle of the 'working version'. If you add ' li' to the second selector and then try to click the <li>s in the box to the right, you will see that they no longer become green.
jsFiddle
https://jsfiddle.net/6sg6z33u/4/
Okay, thanks for posting the jsFiddle. This is an easy fix!
The elements in your second li are being added dynamically. When you bind to elements using the shortcut methods like .click() it only binds to the elements on the page when it initially bound
The fix: use the .on() method, which is the preferred method per jQuery foundation. This method allows for live binding meaning it will pick up on dynamic elements.
$('[class*="selections"]').on( 'mousedown', 'li', function(event) {
var opt = event.target;
if (event.which == 1) {
if (opt.className == 'li_unselected') {
opt.className = 'li_selected_2';
} else {
opt.className = 'li_unselected';
}
}
});

Get any element tagName on click

I need to take $('this') information from any element i click on the document.
I tried the following code:
$('body').click(function(){
var element = this.tagName; // or var element = $(this).prop('tagName');
alert(element);
});
The problem is that wherever i click i get only BODY element. If i click on a button or a div i want to get that element. How can i create something general to take every element i click ?
Because you are attaching your event handler to the body element, this will always be the body. Instead, interrogate the event.target property:
$('body').click(function(e){
var element = e.target.tagName;
alert(element);
});
Example fiddle
nodeName
$('body').click(function(e){
alert(e.target.nodeName);
});
http://quirksmode.org/dom/core/#t23
My advice is not to use tagName at all. nodeName contains all
functionalities of tagName, plus a few more. Therefore nodeName is
always the better choice.
it also looks like the performance is slightly better on some versions of chrome and firefox.
http://jsperf.com/tagname-vs-nodename/2
this always refers to the element where the event handler is assigned, not where the event originated (well, you can change it, but it's pretty unusual to do so). For that, you need Event.target:
$('body').click(function(event){
var element = event.target.tagName; // or var element = $(this).prop('tagName');
alert(element);
});

How to get the parent node of an element when the parent has siblings?

Please take a look at the snippet below:
<div>
<div></div>
<div><!-- my target node -->
<div><!-- not my target node -->
<img /><!-- my source node -->
</div>
</div>
</div>
As you can see the img-elment has two enclosing divs. I want the first of those two enclosing divs to be considered the "real" parent (the one I need to find) of the img-elment because it has a brother div before so the search ends and the brother div and the outer enclosing div are ignored.
In the case there are no siblings at all, the outer div has to be yielded; in the case the element is not enclosed, the element itself has to be yielded.
I just would like to know how to target the element as I explained via JavaScript.
So it sounds like you want the first ancestor that has siblings elements. If so, you can do it like this:
var parent = img.parentNode;
while (parent && !parent.previousElementSibling && !parent.nextElementSibling) {
parent = parent.parentNode;
}
Or perhaps more appropriately written as a do-while loop:
do {
var parent = img.parentNode;
} while (parent && !parent.previousElementSibling && !parent.nextElementSibling);
So the loop will end when it finds one with at least one sibling element, or when it runs out of ancestors.
If you know if the sibling comes before or after the parent, you can just test for one or the other.
Also note that you'll need a shim for the ***ElementSibling properties if you're supporting legacy browsers.
You can make a function that will do this:
function prevElement(el) {
while ((el = el.previousSibling) && el.nodeType !== 1) {
// nothing needed here
}
return el;
}
function nextElement(el) {
while ((el = el.nextSibling) && el.nodeType !== 1) {
// nothing needed here
}
return el;
}
Then use the functions like this:
do {
var parent = img.parentNode;
} while (parent && !prevElement(parent) && !nextElement(parent));
If you don't know how many levels up the parent element is, it will be difficult to select it using methods like element.getParent alone. However, you CAN iterate through parent nodes until the node you're looking at has siblings and is the child of a body element. Let's assume that your img tag is referred to by imgNode.
function getParentWithSiblings(imgNode) {
for( ; n; n = imgNode.parentNode) {
if (n.nextSibling && n.parentNode.tagName == 'body') {
return n;
}
}
}
In the code above, we progressively iterate through the parents of the image node. At each iteration, we check whether the current node (some parent of the img node) has a sibling and is the child of a body tag.
Just in case you're curious, here's how you might implement user1689607's answer using jQuery.
function getAncestorWithSiblings(element) {
var ancestor = element.parent();
while (ancestor && ancestor.siblings().length === 0) {
ancestor = ancestor.parent();
}
return ancestor;
}
Whether it makes sense to use this library for your purposes depends on a great deal of context we don't have. As others have rightfully pointed out, you don't need jQuery to solve this problem, and it may be an unnecessarily heavyweight solution. That said, it can be a very useful library and is certainly worth your consideration if you weren't aware of it or hadn't already looked into it.

Javascript Sections

I have a website I want to take that always has the same section with the same id with all the content I want to display. I'm not very amazing at javascript and I'm wondering how I could remove everything but a specific section.
Would the best approach be to just do a loop that goes through all the elements in the DOM and remove everything but the section with the id I want to keep? If I go that approach how do I keep it from removing all the elements inside that section?
Perhaps another way to do this more efficiently would be:
document.body.innerHTML = document.getElementById( 'saveContentId' ).innerHTML
Removing one node includes all its children, so you won't need to loop over all elements in the whole document. I see two possibilities:
get the section, remove all its siblings in the current parent, and then walk up the DOM tree until document.body, while removing all siblings.
get the section and detach it from the document. Then clear document.body and re-attach the section there
The first solution seems cleaner to me, so here some sample code:
function removeEverythingBut(el) {
while (el != document.body) {
var par = el.parentNode;
for (var i=par.childNodes.length-1; i>=0; i--)
if (par.childNodes[i] != el)
par.removeChild(par.childNodes[i]);
el = par;
}
}
// usage:
removeEverythingBut(document.getElementById("my-section"));
you can save only the element you want and delete all other elements. Also I recommend using Jquery

Categories