Event delegation does not capture all inner elements - javascript

I am using event delegation on my project but for some reason it does not works as expected.
It may seem like a duplicated question but searching for days I have not found the solution so it is not as clear, even in a course I am taking at UDEMY this is not addressed.
The html structure is like this:
<div class="users-list">
<a class="useruid" id='. $row['unique_id'] .'>
<div class="content">
<img src="php/images/'. $row['img'] .'" alt="">
<div class="details">
<span>'. $row['fname']. " " . $row['lname'] .'</span>
<p>'. $you . $msg .'</p> </div>
</div>
<div class="status-dot '. $offline .'"><i class="fas fa-circle"></i>
</div>
</a>
</div>
The vars with the dollar sign are php variables and everything inside "users-list" <div> is added dynamically to the DOM (that part works well).
The problem comes when handling the event listener in javascript as follows:
const ulist = document.querySelector(".users-list");
ulist.addEventListener("click", (e) => {
e.preventDefault();
console.log(e.target);
if (e.target.classList.contains("useruid")) {
console.log(e.target.id);
}
});
I need to get the id number inside the element to use it in another part of the program but it will only be captured if I click on the outer boundaries of the box and most of the time only the <span> and the <p> elements are the ones that will capture the click.
What do I'm missing here, isn't the click suppose to bubble up all the way up passing through the <a> element not matter where I click in that box?
I've searched on other questions here and everywhere online for days but can't find a clear solution. Maybe my approach is incorrect, I don't know really.
Adding additional clarification
using this has the same problem:
if (e.target.matches("a.useruid")) {
console.log(e.target.id);
}

What do I'm missing here, isn't the click suppose to bubble up all the way to the <a> element not matter where I click in that box?
No. It bubbles up to <div class="users-list"> because that is where the event listener is bound.
The target is the element that triggered the event.
You need to change your logic from does the clicked element have the class to does the clicked element or one of its ancestors have the class.
You can do that with closest.
const ulist = document.querySelector(".users-list");
ulist.addEventListener("click", (e) => {
e.preventDefault();
const anchor = e.target.closest(".useruid");
console.log(e.target);
console.log(anchor)
if (anchor) {
console.log(anchor.id);
}
});
<div class="users-list">
<a class="useruid" id='one'>
<div class="content">
<img src="//placekitten.com/100/100" alt="">
<div class="details">
<span>foo</span>
<p>bar</p>
</div>
</div>
</a>
</div>

The target will be the most nested element that has been clicked, So, it maybe a child element to .useruid element.
I think you need to use closest
const ulist = document.querySelector(".users-list");
ulist.addEventListener("click", (e) => {
e.preventDefault();
console.log(e.target);
if (e.target.closest(".useruid")) {
console.log(e.target.id);
}
});

e.target represents the element event was called upon. If you click on span, your e.target is that span.
To solve the problem, you first have to check if the event was called on anchor tag.
const myTarget = e.target.matches("a")? e.target : e.target.closest("a")

Related

Changing image with onclick for 100's of different image links

I am fairly new to javascript and just trying to figure out how to get 100's of image links to change to a specific image once they've been clicked.
I know you can add ID's, but having to make 100's or 1000's of ID's will be a pain. Maybe someone will be able to help me with this issue, or direct me in the correct direction. Thank you!
<a id="click"><img id="change" src="http://i.imgur.com/zS0lOud.jpg" onClick="window.open('http://yahoo.com','_blank');"></a>
<a id="click"><img id="change" src="http://i.imgur.com/zS0lOud.jpg" onClick="window.open('http://google.com','_blank');"></a>
<a id="click"><img id="change" src="http://i.imgur.com/zS0lOud.jpg" onClick="window.open('http://monster.com','_blank');"></a>
document.getElementById('click').onclick = function(){
document.getElementById('change').src = "http://i.imgur.com/0eaWvgw.jpg";
}
Using jQuery,
$('a').on('click', function() {
$('img').each(function() {
$(this).attr('src', 'http://i.imgur.com/0eaWvgw.jpg')
});
});
First of all, There must not be multiple elements in a document that have the same id value.
Use common-class over all the elements on which click event is to be bound
Use jQuery .on method to register events over all the elements having specified common-class
Also note that you have attached inline-click event over img elements, which action is suppose to take place once click is dispatched ?
$('.click').on('click', function(e) {
e.preventDefault(); //To prevent behavior of `a` elements
$(this).find('img.change').prop('src', 'http://i.imgur.com/0eaWvgw.jpg');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<a class="click">
<img class="change" src="http://i.imgur.com/zS0lOud.jpg" onClick="window.open('http://yahoo.com','_blank');">
</a>
<a class="click">
<img class="change" src="http://i.imgur.com/zS0lOud.jpg" onClick="window.open('http://google.com','_blank');">
</a>
<a class="click">
<img class="change" src="http://i.imgur.com/zS0lOud.jpg" onClick="window.open('http://monster.com','_blank');">
</a>
As others stated, an id is something that must be unique. A class is what you want to use. The class attribute is a space-separated list of words. In this case, we only need one class on all links. Now you put some JS on all these links, but you should instead write it like this:
<a class="change" href="http://www.example.com" target="_blank">
<img src="image.png" />
</a>
The links will open the sites in a new window or tab even on browsers with JavaScript disabled in that way.
Then we need to retrieve a list of all elements with the "change" class, and add an event listener for the click event on all of them.
var changeLinks = document.querySelectorAll('.change');
var clickListener = function(e) {
var imgElement = e.currentTarget.querySelector('img');
imgElement.src = 'newimage.png';
// technically, the image is only changed once so we can remove the
// event listener once it has run on a link
e.target.removeEventListener('click', clickListener);
};
for(var i=0, element; element = changeLinks[i]; ++i) {
element.addEventListener('click', clickListener);
}

How to get the sibling element of a parent element using jQuery

Today I was trying to trigger the tooltip of a particular element when I hovered over a different element. I'm not that experienced in jQuery, but I eventually got this to work reading through the docs. Afterwards, I saw how I referenced the target element and it seemed really convoluted. I was wondering if there were better methods that could produce the same results.
The HTML I'm working with is as follows:
<div class="item">
<h3 class="level-title">ITEM 1
<span class="level-label" data-toggle="tooltip" data-placement="top" data-animation="true" title="Tooltip text that pops up">Title Text
</span>
</h3>
<div class="level-bar">
<div class="level-bar-inner" data-level="70%">
</div>
</div>
</div>
<div class="item">
<h3 class="level-title">ITEM 2
<span class="level-label" data-toggle="tooltip" data-placement="top" data-animation="true" title="Tooltip text that pops up">Title Text
</span>
</h3>
<div class="level-bar">
<div class="level-bar-inner" data-level="70%">
</div>
</div>
</div>
In my HTML file I have 5 or so of those item sections. What I wanted was for the tooltip to not only trigger when a viewer moused over the "Title Text", but also when they hovered over the "level-bar-inner" div. To I achieve this I came up with the following piece of JavaScript:
jQuery(document).ready(function($) {
...
$('.level-bar-inner').hover(
function(e) {
$($($(e.target).parent().siblings()[0]).children()[0]).tooltip('show');
},
function(e) {
$($($(e.target).parent().siblings()[0]).children()[0]).tooltip('hide');
}
);
...
});
It seemed like a sloppy solution but a reasonable one. This method hinges on there being only one sibling and one child, which fortunately in my case was true for all the "item" sections in my HTML file. Which leads me to asking if there are better/more efficient methods of retrieving this element or even just toggling it's tooltip in general.
You can reference the element that raised the event using the this keyword. You can also use jQuery's DOM traversal methods, instead of repeatedly getting a DOMElement from a jQuery object, and turning it back in to a jQuery object again. Try this:
$('.level-bar-inner').hover(function(e) {
$(this).parent().prev().find('span').tooltip('show');
}, function(e) {
$(this).parent().prev().find('span').tooltip('hide');
});
You better go all the way up to the parent .item then it would be much easier to find the corresponding tooltip element:
$('.level-bar-inner').hover(
function() {
$(this).parents('.item').find('.level-label').tooltip('show');
},
function() {
$(this).parents('.item').find('.level-label').tooltip('hide');
}
);
You can use this other than e.target. Along with that the prev element is probably what you are looking for, then get the child by it's class:
$('.level-bar-inner').hover(
function(e) {
$(this).parent().prev().child(".level-label").tooltip('show');
},
function(e) {
$(this).parent().prev().child(".level-label").tooltip('hide');
}
);
Another way of doing it if you are following the same structure of adding data attributes to the elements, would be
$('.level-bar-inner').hover(function(e) {
$(this).closest('[data-toggle=tooltip]').tooltip('show');
}, function(e) {
$(this).closest('[data-toggle=tooltip]').tooltip('hide');
});

onClick does not function inside a dynamically generated div

I have a jquery code which generate a div dynamically
the problem is that the onclick function for the a tag does not calls the required function
Here is the code
$("#new").append('
<ul class="#...#">
<li>
<a href="./d.html?n1='+item[0]+'&n2='+item[2]+'&n3='+item[3]+'">
<input type="hidden" value='+item[0]+'>
<img style="height: 64px; width: 64px;" class="#...#"
src="image.png" width="40" height="40" />
<span class="#...#">
<b>'+item[0]+'</b>
'+item[1]+'......
</span>
</a>
<br />
<div>
<a onClick="insert();" href="#">
<i class="icon1"></i>
</a>
<a href="2.php?q='+q+'&n='+item[0]+'" id="icon2" name="icon2">
<i class="icon2"></i>
</a>
</div>
</li>
</ul>
');
I am using the above code as an ajax success function
the a tag is not calling the insert() function
I searched for the error but could not find the
What am doing it wrong?
Thsnks in advance
Add a custom class, such as "catchClick" to the element that you need to catch the click event of.
var linkToAdd = '<a class="catchClick" href="#">My link</a>';
var $linkToAdd = $(linkToAdd);
$('#new').append($linkToAdd);
Then the following code will setup handlers for clicks on that element.
It works because the click event is bubbled up the dom hierarchy to the document element. So you can attach a listener to the document element instead.
$(document).on('click', '.catchClick', function(e) {
// do your stuff here
// you probably need to get a jquery reference to the element that was clicked:
var $this = $(this);
// you probably want to stop the event's default actions so we'll add this:
e.stopPropagation();
e.preventDefault();
});
When you work with dynamic content I suggest you use DOM event delegation. The ideea is to add the listener on a parent of your dynamically added content
This explains how to do it and how it works
http://davidwalsh.name/event-delegate
Use JavaScript event delegation to separate concerns:
$(function() {
$(document.body).on('click', 'ul.someClass div a', function() {
// insert() method logic goes here
});
});
Second argument specifies the event target. This event is bind to the document's body so it's always present. Target DOM objects can be generated per AJAX at some point later or removed from the document. The actual target check happens when a user actually clicks something.
Also adding a class attribute to <div> or <a> in your code might help with readability. E.g.:
<div class="buttons">
<a class="btn-insert" onClick="insert();" href="#">
<i class="icon1"></i>
</a>
<a href="2.php?q='+q+'&n='+item[0]+'" id="icon2" name="icon2">
<i class="icon2"></i>
</a>
</div>
Target attribute for jQuery on() method could then be simplified to just '.btn-insert'
Set the click event handler AFTER having appended new clickable items to the document, and subsequently after each time you append new clickable items to the document.

Separate touch events of parent and child

I use jquery for my web application. I want it to be correct for desktop browsers and mobile brousers for touchscreen devices.
I have a div, and some elements inside it:
<div class="well listItem element-div alert-error" data-state="removing">
<strong>Item title</strong> <small>Items count</small>
<div class="pull-right" style="margin-top: -5px;">
<a class="btn btn-success approve-button"><i class="icon-ok icon-white"></i></a>
<a class="btn btn-danger cancel-button"><i class="icon-remove icon-white"></i></a>
</div>
</div>
I catch click and touchend event for .listItem class (top-level div) and same events for every a element (for .approve-button and .cancel-button), but when I'm click on desktop browser on 'a' element, it works correct, and when I am pressing on 'a' element in iOS Safari browser, or WindowsPhone InternetExplorer, works only event for parent div, but not for 'a'. If I remove event listener for parent div, events for 'a' elements works correct in mobile browsers. I want parent-div event works when I touch a free space of it, and when I touch 'a' element - I want only 'a' event listener to go on. Can you advise me how to separate them?
Have you tried to check event target?
$(".listItem").on("click", function(event){
if (event.target === this) {
// clicked exactly on this element
}
});
I've had a similar problem, only my content was more nested. You want to exclude the areas (divs, classes or otherwise) where you expect to handle other events, using :not selector, like so:
<article>
<div class="title">
<span class="title"></span>
<div class="buttons">
<div class="minimize">+</div>
<div class="remove">x</div>
</div>
</div>
<div class="post">
...
</div>
</article>
With jQuery:
$("article :not(.buttons, .minimize, .remove)").click(function (event) {
// toggle display value of div.post
});
This triggers a click event anywhere inside article, except for the .buttons, .minimize and .remove divs.

onclick alert jquery html javascript

I have a page so far with:
<div id="x1">Text paragraph 1<link here></div>
<div id="x2">Text paragraph 2<link here></div>
<div id="x3">Text paragraph 3<link here></div>
Where link here is like
google
What I am trying to do is add a link to the bottom of each paragraph of text so that when it is clicked it displays an alert with the div id of that text block.
So for example, if someone clicks on the link at the bottom of text paragraph 2, then they will get an alert saying "x2".
So far, I have only been able to think of a way involving an onclick event for each link in each div. But with 100 paragraphs this could become quite a lot and is messy code.
like
$('#x1').onclick(function(){
alert('x1');
});
How can I do this better?
The page is generated with php so I could put the div id's anywhere in that text block area (even make a new div around the link if required)...
EDIT - Many good answers, I don't know which to pick as best. I actually ended up using Loongawas for my purpose as its easy to make for my beginner level in php.
<div id='a1'>This text <a href="" onclick=tomato(1)>test</a>
</div>
<div id='a2'>This text <a href="" onclick=tomato(2)>test</a>
</div>
<div id='a3'>This text <a href="" onclick=tomato(3)>test</a>
</div>
and
function tomato(test){
alert(test);
};
Some of the others are incredibly interesting as they use higher functions. I'm going to spend the rest of the day looking into them. Thanks to all.
use jQuery's live or delegate functions:
$('div a').live('click', function(ev){
alert($(this).closest('div').attr('id'));
});
The benefit to the live/delegate functions is that there's actually only a single event on the entire page for this (as opposed to one event per link). If you add more links dynamically, this still works without having to attach more events.
The difference between live and delegate is that delegate is specific to a part of the page. If, for instance, you wrapped all of these DIVs in another div, the call would look like:
$('#wrapperDiv').delegate('a', 'click', function(ev){ ...
The advantage to this is that the internal jQuery code that checks to see if the click matches the selector only runs on clicks inside of #wrapperDiv instead of clicks anywhere on the page.
You could make a javascript function that takes a variable and then pass the paragraph number to the function. If the paragraph was number two you could call
myfunction(2);
or is the number not the problem?
$('#x1, #x2, #x3').click(function(){
alert($(this).parents().attr("id"));
});
EDIT:
Better version:
HTML:
<div class="x">Text paragraph 1<link here></div>
<div class="x">Text paragraph 2<link here></div>
<div class="x">Text paragraph 3<link here></div>
$('.x a').click(function(){
alert($(this).parents().attr("id"));
});
Have you considered using a class to name them all as opposed to explicit ids?
<div class="x">Text paragraph 1<link here></div>
<div class="x">Text paragraph 2<link here></div>
<div class="x">Text paragraph 3<link here></div>
so then you would be able to use a single click event for all of them?
$(".x a").click()
{
//Use $(this) to refer to the clicked item.
alert($(this).parents().attr("id"));
});
$('.myDivs').click(function(){
alert($(this).parent().attr("id"));
});
Or select the divs in some other way:
$('#x1').parent().children('div').click(...);
Something along these lines should work:
<div id="x1">Text paragraph 1 <a href='google.com'>google.com</a></div>
<div id="x2">Text paragraph 2 <a href='google.com'>google.com</a></div>
<div id="x3">Text paragraph 3 <a href='google.com'>google.com</a></div>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js'></script>
<script>
$('a').click(function() {
alert($(this).parent().attr('id'))
return false
})
</script>
Add a class to each div, so you can select all of 'em at once.
<div id="x1" class="x">Text paragraph 1 <a>Click</a></div>
<div id="x2" class="x">Text paragraph 2 <a>Click</a></div>
<div id="x3" class="x">Text paragraph 3 <a>Click</a></div>
Then you can do:
$('div.x a').live('click', function(e){
e.preventDefault();
alert($(this).closest('div.x').attr('id'));
});
http://jsfiddle.net/VGh3X/1/
A better approach to this is to make all of the clickable areas share something in common that you can use as a selector. For example, if all of the clickable divs had class='click', you'd be able to select them all using $('.click') and bind to that.
$('.click a').bind('click', function() {
var div = this.closest('.click');
alert(div.attr('id'));
return false;
});
$(document).ready(function() {
var links = $("div[id^='x'] a"); //get the a tags
$.each(links, function(i,v) {
$(v).click(function() { //bind on click
alert(v.parentNode.id); //alert div id
return false; // stop
});
});
});

Categories