Event Delegation - Parent Element Target instead of Child Element - javascript

I am trying to use & learn Simple Javascript (no JQuery/other libraries/frameworks).
HTML:
<div id="name">text</div>
<ul id="namelist"><li>A </li><li>B </li><li>C </li></ul>
and I wrote the below code for event delegation for click and it works fine for A,B,C child elements click handling.
JS:
<!--
var mainlist = document.getElementById("namelist");
var namefield=document.getElementById("name");
mainlist.addEventListener('click',function(event) {
console.log(event.currentTarget.id);
namefield.innerHTML=event.target.innerHTML;
console.log( event.target.innerHTML);});-->
However, when I click the "ul" element instead of child li elements, I see all ABC are printed to div. I don't want any action to take place for ul.
Any suggestions ?

Short Answer
var mainlist = document.getElementById("namelist");
var namefield = document.getElementById("name");
mainlist.addEventListener('click', function(event) {
if(event.target && event.target.nodeName == "LI") {
console.log(event.currentTarget.id);
namefield.innerHTML=event.target.innerHTML;
console.log( event.target.innerHTML);
}
});
Long Answer
Your question I feel should be, why the event listener of ul gets fired when I am clicking li.
When you are clicking on li, ul also acts as if it is clicked.
It is called event bubbling in javascript and it is by default when you register addEventListener whith no third parameter (defaults to false).
Read more here
I will suggest adding event listener on both the elements and then understand the event delegation model.

Just add a check in your click handler
mainlist.addEventListener('click',function(event) {
if (event.currentTarget != event.target) {
console.log(event.currentTarget.id);
namefield.innerHTML=event.target.innerHTML;
console.log( event.target.innerHTML);
}
});

Related

Jquery only click once

I'm trying to disable a li click event after it has clicked the first time. Essentially to stop the array data being doubled. The click is working fine for each time. My current method doesn't appear to be working. I also need to disable the other li's from being clicked once the first one has :)
Thanks
JS code is:
$('#eventType ul li').click(function(e) {
e.preventDefault();
var value = $(this).attr('value');
answers.push(value);
// Below isn't working
$(this).click(function() {
return false;
});
console.log(answers);
});
you need to use one:
$('#eventType ul li').one('click',function(){
//your code here
});
this event will be fired only once
UPDATE
you can do that using $.off()
$('#eventType ul li').one('click',function(){
//your code here
$('#eventType ul li').off('click');
});
jQuery is just JavaScript so you can easily add behaviors that you want
// basic jQuery plugin boilerplate
$.fn.once = function once(eventType, f) {
// this = the selected elements
return this.each(idx, elem) {
// create reference to jQuery-wrapped elem
var $elem = $(elem);
// add event listener for eventType
$elem.on(eventType, function(event) {
// call the event handler
return f(event);
// remove the event handler
$elem.off(eventType, f);
});
});
};
Usage would look like this
$('#eventType ul li').once('click', function(event) {
console.log("you will only see this once");
});
However, this is obviously a common need so it exists in jQuery already. It's called $.one. As APIs grow, you may not know about the existence of such procedures. This answer exists to show you that you can use your brain to program the things that you want or that might be missing from a particular library. This lessens your dependence on the creator's of the lib to introduce the functionality you need.
EDIT
In a comment, you ask if the event handler can be disabled for all other LI elements after the first LI is clicked. The trouble here is that jQuery uses implicit iteration, which means that when you call $('li').on('click', ...), jQuery will bind an onclick event handler for each LI.
A better solution to this problem would be to use jQuery's event delegation
// only fire event handler for the first LI clicked
$('ul').one('click', 'li', function(event) {
console.log($(this).text());
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
This will delegate the event listener to the children LI, but once one of the LI is clicked, the event handler will be removed (because we delegated using the $.one procedure).
Try clicking one LI, you will see a message in the console. When you click the second LI, nothing will happen because the event handler was removed.
var used = false;
$('#eventType ul li').click(function(e) {
if (used == false) {
used = true;
e.preventDefault();
var value = $(this).attr('value');
answers.push(value);
console.log(answers);
}
});
the way you did it was just adding another on click handler, not removing or overriding the old ond.
You can use CSS classes; add the class 'disabled' to elements you don't need, and avoid adding elements that have the classe 'disabled'.
https://plnkr.co/edit/6aloNPETHGxfiP5oYZ9f?p=preview
$('ul li').click(function(e) {
if(!$(this).hasClass('disabled')) {
var value = $(this).text();
answers.push(value);
$('li').addClass('disabled');
}
console.log(answers);
});

How to make click handler handle when ANY "a" tag is clicked, regardless of what it's wrapped around?

Let's say I have this in my HTML page:
bar
<a href="/">
<div></div>
</a>
And I want to write a handler that handles when ANY "a" tag is clicked (with jQuery):
$(document).click((e) => {
const element = e.target;
if (element && element.nodeName === 'A') {
// Do something
e.preventDefault();
}
});
The above code only works for the top "a" tag, but not the bottom one. For the top "a" tag, element.nodeName equals A. For the bottom "a" tag, element.nodeName equals DIV.
How do I write a click handler that handles whenever ANY "a" tag is clicked, regardless of what it is wrapped around?
Capture delegated events in any parent element. Document will get all events if you don't call preventDefault before it arrives during the bubbling phase. Read about Event phases, it really pays off!
$(document).on("click", "a", function(event) {
// You have clicked an anchor
window.console.log ("You have clicked this anchor:", this," but you clicked maybe inside a div or a something inside the <a>", event.target);
});
https://jsfiddle.net/7ttm4y8k/4/
Note: As you are delegating the events to the document, this will work even for elements that were added after the handler's been set. This way you can setup your delegated anchor handler then maybe at a later time add anchors from an Ajax query without the need of setting handlers to every created anchor.
https://jsfiddle.net/82fpvwrL/
$("a").click(function(){
alert('WOO HOO! I was clicked.');
});
Apply it to every anchor.
document.getElementsByTagName('a').map(tag => tag.onClick = yourHandler);
Use something like this:
$(document).on("click","a", (e) => {
e.preventDefault();
const element = e.target;
// Do something
});
This is how I do it (w/o jQuery):
document.body.addEventListener('click', function(ev) {
var targetElement = ev.target;
while(targetElement !== null && targetElement.nodeName.toUpperCase() !== "A") {
targetElement = targetElement.parentElement;
}
// Proceed only if an A element was found in the ev.target's ancestry
if (targetElement !== null) {
ev.preventDefault();
// Your code here
}
}
This will catch the event at the body tag and is oblivious to a tags being added/removed in the document. It will work as long as the click event's propagation was not stopped by any elements in the DOM tree from the element to the body.

Fire event only when clicking on an element, and not on its children

If I bind a click handler to the body element, when I click anything on the page the event is triggered. I can check the event.target on every click:
$("body").on("click", function(event) {
if (event.target.tagName == "BODY") {
...
}
});
but that seem a bit overkill. Is there a way to trigger the event only when clicking the blank area of the body itself?
You can use the eventPhase property of event. A value of 2 means that the event is currently triggering the target element:
$("body").on("click", function(event) {
//Cancel if not at target
if (event.eventPhase != 2) return;
//Other Code Here
});
Fiddle Here: http://jsfiddle.net/o0yptmmp/
You should put an
event.stopPropagation()
on all children you want to not bubble up.
jQuery Docs: http://api.jquery.com/event.stoppropagation/.
You can do it if you check on every click if the element clicked has a body parent. If the condition is false, you are clicking the body:
$("body").on("click", function(event) {
if ($(this).parents('body').length == 0) {
//Do something
}
});
In my opinion it's not a good practice, but it depends on your code and project.

check if one of the the elements in propagation has a specific class, on jquery.click() method

In jQuery.click() method it's possible to get the class of element which has fired the event using even.target. I want to check if the element which has been clicked or one of its parent has some specific class.
<div class="c1">Click</div>
<script>
$(document).click(function(e){
if($(e.target).hasClass("c1"))
alert("It's C1");
});
</script>
But it always failes because c2 has been clicked. Despite using .parent(), I'm wondering if there is a way to check the propagation on on-click event.
From what you said is C1 was clicked and you want to know if
You need to see if the element is inside that was clicked.
var target = $(e.target);
if (target.hasClass("c2") || target.find(".c2").length) {
alert("C2 is a child");
}
EDIT, now what you orginally asked is not what you really wanted.
Now if it is a parent it is as simple as
var target = $(e.target);
if (target.hasClass("c1") || target.parents(".c1").length) {
alert("C1 child was clicked");
}
or as Ian pointed out
if (target.closest(".c1").length) {
alert("C1 child was clicked");
}
You can use $(this).hasClass instead of $(e.target). Also $(this).parent() will lead to parent of a target

jQuery: Any way to "refresh" event handlers?

I have two divs, one that holds some stuff and the other with all possible stuff. Clicking on one of the divs will transfer items to the other div. The code I came up with is:
$("#holder > *").each(function() {
$(this).click(function(e) {
$(this).remove();
$("#bucket").append(this);
});
});
$("#bucket > *").each(function() {
$(this).click(function(e) {
$(this).remove();
$("#holder").append(this);
});
});
This one works perfectly, except that the event handlers need to be refreshed once I append or remove elements. What I mean is, if I first click on an element, it gets added to the other div, but if I click on this element again, nothing happens. I can do this manually but is there a better way to achieve this?
Try jquery live events .. the $.live(eventname, function) will bind to any current elements that match as well as elements added to the Dom in the future by javascript manipulation.
example:
$("#holder > *").live("click", function(e) {
$(this).remove();
$("#bucket").append(this);
});
$("#bucket > *").live("click", function(e) {
$(this).remove();
$("#holder").append(this);
});
Important:
Note that $.live has since been stripped from jQuery (1.9 onwards) and that you should instead use $.on.
I suggest that you refer to this answer for an updated example.
First, live is deprecated. Second, refreshing isn't what you want. You just need to attach the click handler to the right source, in this case: the document.
When you do
$(document).on('click', <id or class of element>, <function>);
the click handler is attached to the document. When the page is loaded, the click handler is attached to a specific instance of an element. When the page is reloaded, that specific instance is gone so the handler isn't going to register any clicks. But the page remains so attach the click handler to the document. Simple and easy.
Here you go, using the more intuitive delegate API:
var holder = $('#holder'),
bucket = $('#bucket');
holder.delegate('*', 'click', function(e) {
$(this).remove();
bucket.append(this);
});
bucket.delegate('*', 'click', function(e) {
$(this).remove();
holder.append(this);
});
EDIT: don't use live, it be deprecated!
Take advantage of the fact that events bubble. Using .on():
var = function( el1, el2 ) {
var things = $('#holder, #bucket');
things.each(function( index ) {
// for every click on or in this element
things.eq(index).on('click', '> *', function() {
// append will remove the element
// Number( !0 ) => 1, Number( !1 ) => 0
things.eq( Number(!index) ).append( this );
});
});
any click on any element (existing at the time of bind or not) will bubble up (assuming you haven't manually captured the event and stopped propagation). Thus, you can use that event delegation to bind only two events, one on each container. Every click that passed the selector test of the 2nd argument (in this case, > *, will remove that element and then append it to the alternate container as accesesed by things.eq( Number(!index) )
Have you looked at jQuery's live function?
The most Efficient way (dont load all event for all elements) it:
//NORMAL FUNCTION
function myfunction_click(){
//custom action
}
$('id_or_class_of_element').on('click', myfunction_click);
//LOAD OR REFRESH EVENT
$(document).on('click', 'id_or_class_of_element', myfunction_click);

Categories