Appended node does not trigger JavaScript event - javascript

I have an inventory with some slots in it. With some actions from the client, new items are added to these slots by the appendChild function.
<div id="letters">
<div class="slot">
<div class="item">
<img src="...">
</div>
</div>
<div class="slot"></div>
<div class="slot">
<div class="item">
<img src="...">
</div>
</div>
</div>
With the following code I try to trigger a mouseover event for each slot.
var items = document.querySelectorAll(".item");
items.forEach(function(element) {
element.addEventListener("mouseover", function () {
//Stuff
});
});
It works perfectly fine, but when I add a new item to a class slot, this one does not trigger the event.
Can anybody guess the problem?
Thanks a lot!
Edit:
I solved the problem by adding a new eventListener too whenever I append a new item. EventListeners must be added before using them (pretty obvious), and in my code I was working with them as if they were onmouseover functions.

You need to add the handler to the newly constructed element after it is created.
var items = document.querySelectorAll(".item");
function attachMouseOverHandler(element) {
element.addEventListener("mouseover", mouseOverHappened);
}
items.forEach(attachMouseOverHandler);
function addNewElement(element) {
attachMouseOverHandler(element);
}

Following the append, you need to add the event listener to the new element, as it did not exist when you originally created the event listeners.

Here's a solution using a single deferred event listener, so you don't have to dynamically add new event listeners:
document.getElementById('letters').addEventListener('mouseover', function (event) {
const element = event.target;
if (!element.matches('.item')) return;
//Stuff
console.log(element.textContent.trim());
})
<div id="letters">
<div class="slot">
<div class="item">
Foo
</div>
</div>
<div class="slot">I do not match .item</div>
<div class="slot">
<div class="item">
Bar
</div>
</div>
</div>

Related

Is there anyway to simplify this Javascript code

I have this code that, when a certain card is clicked, its content is displayed on an overlay card. But the way I have it right now is to repetitive:
HTML:
<div class="card c1">
<img src="max.png" width="65px">
<div class="text">
<h3 class="firstName">Owen</h3>
<h3 class="lastName">Osagiede</h3>
<p>[email]</p>
<p>[city]</p>
</div>
</div>
<div class="card c2">
<img src="max.png" width="60px">
<div class="text">
<h3 class="firstName">Kanye</h3>
<h3 class="lastName">West</h3>
<p>[email]</p>
<p>[city]</p>
</div>
</div>
<div class="card c3">
<img src="max.png" width="65px">
<div class="text">
<h3 class="firstName">Quando</h3>
<h3 class="lastName">Rondo</h3>
<p>[email]</p>
<p>[city]</p>
</div>
</div>
JS:
function overlayUser(){
card[1].addEventListener('click', function(){
first.innerHTML = card[1].getElementsByTagName('h3')[0].innerHTML;
last.innerHTML = card[1].getElementsByTagName('h3')[1].innerHTML;
});
card[2].addEventListener('click', function(){
first.innerHTML = card[2].getElementsByTagName('h3')[0].innerHTML;
last.innerHTML = card[2].getElementsByTagName('h3')[1].innerHTML;
});
card[3].addEventListener('click', function(){
first.innerHTML = card[3].getElementsByTagName('h3')[0].innerHTML;
last.innerHTML = card[3].getElementsByTagName('h3')[1].innerHTML;
});
I have tried to loop over it with a for loop, but keep getting an error:
`function overlayUser(){
for (i = 0; i < card.length; i++){
card[i].addEventListener('click', function(){
first.innerHTML = card[i].getElementsByTagName('h3')[0].innerHTML;
last.innerHTML = card[i].getElementsByTagName('h3')[1].innerHTML;
});
}
}`
In a DOM event handler, the current element is this. Therefore you can write a single function for all of them:
function handleClick () {
first.innerHTML = this.getElementsByTagName('h3')[0].innerHTML;
last.innerHTML = this.getElementsByTagName('h3')[1].innerHTML;
}
function overlayUser(){
for (i = 0; i < card.length; i++){
card[i].addEventListener('click', handleClick);
}
}
The this API is the original API for finding out which element caused the event. Thus it is very compatible with all browsers.
Alternatively, if you feel uncomfortable mixing the usage of this you can also find out the current element from the event object:
function handleClick (event) {
let card = event.target;
first.innerHTML = card.getElementsByTagName('h3')[0].innerHTML;
last.innerHTML = card.getElementsByTagName('h3')[1].innerHTML;
}
The event object is a slightly less ancient API but is compatible with everything from IE8 and above.
Additionaly you can use event bubbling/capturing to even get rid of the for loop. Just install the event on the parent element of all three cards and let event.target sort out which card caused the event:
parentDiv.addEventListener('click', handleClick);
Instead of looping over all the individual elements that you want to have event handlers and hooking each up, set a single handler on an ancestor element and allow the event to bubble up to that element. Then, when handling it, look at the event.target, which refers to the actual element that triggered the event. This is called event delegation.
Also, do not use .getElementsByTagName() in 2020. That is a 25+ year old API that returns a live node list that can dramatically hurt performance, especially since you are only interested in a single element when you use it.
Addtionally, never use .innerHTML if you can avoid it. It has security and performance implications. Since you aren't actually working with a string that needs any HTML parsed, you should use .textContent.
Finally, you should not be using h3 unless it is to create a sub-section of a pre-existing h2. Headings are meant to divide your document into ordered sections and these sections are used by those who rely on assistive technologies to navigate a document. If you are just using the h3 because of the styling the browser applies to the text, you should instead just use a p and then use CSS to style it the way you want.
// Get references to first and last (for this demo)
let first = document.querySelector(".first");
let last = document.querySelector(".last");
// Just handle the click event at the wrapper of all the cards
document.querySelector(".wrapper").addEventListener("click", function (event){
// Then access the content of the card that actaully triggered the event
first.textContent = event.target.closest(".card").querySelector("h3").textContent;
last.textContent = event.target.closest(".card").querySelector("h3:nth-child(2)").textContent;
});
/* Just for demo */
.results {
position:sticky;
left:50%;
top:0;
background-color:#e0e0e0;
border:2px solid red;
}
<div class="results">
<div class="first"></div>
<div class="last"></div>
</div>
<div class="wrapper">
<div class="card c1">
<img src="max.png" width="65px">
<div class="text">
<h3 class="firstName">Owen</h3>
<h3 class="lastName">Osagiede</h3>
<p>[email]</p>
<p>[city]</p>
</div>
</div>
<div class="card c2">
<img src="max.png" width="60px">
<div class="text">
<h3 class="firstName">Kanye</h3>
<h3 class="lastName">West</h3>
<p>[email]</p>
<p>[city]</p>
</div>
</div>
<div class="card c3">
<img src="max.png" width="65px">
<div class="text">
<h3 class="firstName">Quando</h3>
<h3 class="lastName">Rondo</h3>
<p>[email]</p>
<p>[city]</p>
</div>
</div>
</div>

How do I get the value of the first child element in Javascript?

I'm trying to get the value of the first child element from my HTML to show whenever I click on an image aka my 'services-cell" container, but it keeps saying the value is undefined.
<div class="services-cell">
<img class="services-cell_img" src="gallery/img-1.jpg" alt="">
<div class="services-cell_text">Digital Marketing</div>
</div>
Here is my Javascript
let galleryImages = document.querySelectorAll('.services-cell')
if(galleryImages) {
galleryImages.forEach(function(image){
image.onclick = function() {
console.log(galleryImages.firstElementChild.value)
}
})
}
I tried to add the img class as a variable as well, but it also says undefined. I want the console.log to print
<img class="services-cell_img" src="gallery/img-1.jpg" alt="">
I have multiple images with the same html except it just say img-2, img-3 etc. So ideally whenever I click on the other images it would print the same HTML value but just will say the image number that I clicked on
You created the array as galleryImages, but then rather than accessing the firstElementChild of the div, you're trying to access that property on the array. You need to do image.firstElementChild instead. Also, as far as I know, accessing .value of an image has no meaning, I think you meant to just do firstElementChild instead:
let galleryImages = document.querySelectorAll('.services-cell');
if (galleryImages) {
galleryImages.forEach(function (image) {
image.onclick = function() {
console.log(image.firstElementChild);
};
});
}
<div class="services-cell">
<img class="services-cell_img" src="https://s3.amazonaws.com/pix.iemoji.com/images/emoji/apple/ios-12/256/deciduous-tree.png" alt="">
<div class="services-cell_text">Digital Marketing</div>
</div>
<div class="services-cell">
<img class="services-cell_img" src="https://cdn-cloudfront.cfauthx.com/binaries/content/gallery/gg-en-us/icons/gg-tree-icon.png" alt="">
<div class="services-cell_text">Digital Marketing</div>
</div>
What you could do to achieve this is passing the event attribute as a parameter of the onclick function.
The event attribute has a target; which is the item that triggered the event. So for example:
if(galleryImages) {
galleryImages.forEach(function(image){
image.onclick = function(e) {
console.log(e.target.firstElementChild.value)
}
})
}
However instead of adding an eventListener to every element, it might be better to add one event handler on the parent - and check which item is clicked.
E.g.
<div class="parent">
<div class="services-cell">
<img class="services-cell_img" src="gallery/img-1.jpg" alt="" />
<div class="services-cell_text">Digital Marketing</div>
</div>
<div class="services-cell">
<img class="services-cell_img" src="gallery/img-2.jpg" alt="" />
<div class="services-cell_text">Digital Marketing</div>
</div>
</div>
And the javascript:
document.querySelector('.parent').addEventListener('click', function(e){
if(e.target.matches('.services-cell'){
// Do something with the e.target - which is the .services.cell
}
}

How to add Event Listener on Multiple tag at once

I'm new in Javascript. I have this images that I want to replace on the main-img. The code below is working fine but I just want to know how to do this by using less code in Javascript.
<div id="container">
<div id="side-img">
<img id="side1" onclick="side1()" src="img1.jpeg">
<img id="side2" onclick="side2()" src="img2.jpeg">
<img id="side3" onclick="side3()" src="img3.jpeg">
<img id="side4" onclick="side4()" src="img4.jpeg">
</div>
<div id="main-img">
<img id="main" src="img0.jpeg">
</div>
</div>
<script type="text/javascript">
var sideimg = document.querySelectorAll('#side-img img');
var main = document.querySelector('#main');
function side1() {
main.src = sideimg[0].src;
}
function side2() {
main.src = sideimg[1].src;
}
function side3() {
main.src = sideimg[2].src;
}
function side4() {
main.src = sideimg[3].src;
}
</script>
You could simplify it by modifying the image tags like this:
<img id="side1" onclick="side(0)" src="img1.jpeg">
<img id="side2" onclick="side(1)" src="img2.jpeg">
<img id="side3" onclick="side(2)" src="img3.jpeg">
<img id="side4" onclick="side(3)" src="img4.jpeg">
And remove all side() functions in your JS script and add this function:
var sideimg = document.querySelectorAll('#side-img img');
var main = document.querySelector('#main');
function side(index) {
main.src = sideimg[index].src;
}
You can programatically attach an onclick handler for each image.
document.querySelectorAll('img').forEach((img) => {
img.onclick = () => {
console.log(img.src)
document.querySelector('#main').src = img.src
}
})
<div id="container">
<div id="side-img">
<img id="side1" src="img1.jpeg">
<img id="side2" src="img2.jpeg">
<img id="side3" src="img3.jpeg">
<img id="side4" src="img4.jpeg">
</div>
<div id="main-img">
<img id="main" src="img0.jpeg">
</div>
</div>
Delegation included version
JS
const viewer = document.querySelector('#main');
document.addEventListener('click', (event) => {
if (typeof event.target.hasAttribute('data-clickable') && event.target.src) {
viewer.src = event.target.src;
}
})
HTML
<div id="container">
<div id="side-img">
<img id="side1" data-clickable src="img1.jpeg">
<img id="side2" data-clickable src="img2.jpeg">
<img id="side3" data-clickable src="img3.jpeg">
<img id="side4" data-clickable src="img4.jpeg">
</div>
<div id="main-img">
<img id="main" src="img0.jpeg">
</div>
</div>
I'd suggest:
// retrieving the common ancestor element of the <img> elements:
let sideImage = document.querySelector('#side-img'),
// defining a named function, using arrow syntax; 'e' is a reference
// to the event object, passed automatically from the
// EventTarget.addEventListener() method:
imageChange = (e) => {
// we retrieve the element with the 'id' of 'main', and update
// its src property to be that of the clicked element;
// e.target retrieves the element upon which the event was
// originally triggered:
document.getElementById('main').src = e.target.src;
};
sideImage.addEventListener('click', imageChange);
let sideImage = document.querySelector('#side-img'),
imageChange = (e) => {
document.getElementById('main').src = e.target.src;
};
sideImage.addEventListener('click', imageChange);
<div id="container">
<div id="side-img">
<img id="side1" src="https://via.placeholder.com/100.png?text=image1">
<img id="side2" src="https://via.placeholder.com/100.png?text=image2">
<img id="side3" src="https://via.placeholder.com/100.png?text=image3">
<img id="side4" src="https://via.placeholder.com/100.png?text=image4">
</div>
<div id="main-img">
<img id="main" src="img0.jpeg">
</div>
</div>
JS Fiddle demo.
With reference to the question left in the comments, by the OP:
Can you please explain to me how the container #side-img was looping through each image and adding an event listener to them?
Sure, in this case we used event-delegation taking advantage of the way that events bubble through the DOM.
Rather than binding an event-listener to multiple <img> elements we took advantage of the way that events bubble through the DOM; this means that we listened for the click event as it reaches the #side-img, and look at the Event Object's target property to find the element upon which the event was initially triggered.
References:
Arrow functions.
document.getElementById().
document.querySelector().
Event delegation.
EventTarget.addEventListener().

Clone and append on click makes multiple elements and toggle not working

I am trying to clone and append elements. after i append i have added a toggle animation. on first click it works fine. after i am getting mutliple elements appended.
as well the animation not working.
here is the html:
<div class="parent hide">
<div class="header">
<h6>Header</h6>
</div>
<div class="content">
<p>paragraph content </p>
</div>
</div>
<div id="content">
</div>
<button>Add</button>
Js :
$('.parent').on('click', '.header', function () {
$(this).find('h6').toggleClass('name').end()
.siblings('.content').slideToggle();
});
$('button').on('click', function () {
var newParent = $('.parent').clone();
$('#content').append(newParent.removeClass('hide'));
});
JSfiddle
UPDATE:
I updated the cloning passing var newParent = $('.parent').clone(true); - animation works!
you should clone only the first element (or the last for that matter):
var newParent = $('.parent:first').clone(true);
EXAMPLE 1
Using .clone(true) seems to fix the animation. Another solution is targeting the parent on click and delegating .parent .header since the cloned .parent is being added to the DOM after the initial load:
$('#content ').on('click', '.parent .header', function () {
instead of
$('.parent').on('click', '.header', function () {
EXAMPLE 2
Cloning an elements means there will be two identical elements (having the same class aswell) afterwards.
Each time you click the button, all elements having the .parent class are cloned and appended to the #content
Regarding the animation:
The appended elements are not known to the DOM, so the .on('click') is not working.
Try to put a wrapper around your .parent elements and then use the following syntax:
HTML
<div class="wrapper">
<div class="parent hide">
<div class="header">
<h6>Header</h6>
</div>
<div class="content">
<p>paragraph content </p>
</div>
</div>
<div id="content">
</div>
</div>
<button>Add</button>
JS
$('.wrapper').on('click', '.parent .header', function(){ [...] });

Onclick event issue after using the outerHTML

this is the code :
$(".adauga_incasare").click(function(){
var html = $(".incasari")[0].outerHTML;
$(html).insertAfter($(".incasari").last());
});
$(".del_incasare").click(function(){
alert("dsdasdasd");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
<div class="incasari">
<div class="data_incasare_container">
<label><b>Data</b></label>
<input class ="data_incasare" type="text" id="datepicker">
<label class ="data_incasare_hidden">12-06-2014</label>
</div>
<div class="suma_incasare_container" style="">
<label><b>Suma</b></label>
<input class="suma_incasare" type="text" maxlength="8" name="pret_unitar[]" alt="">
<label class ="suma_incasare_hidden">100</label>
</div>
<div class="coscos" style="">
<a class="stergereIncasare" href="javascript:void(0);"><i class="icon-trash"></i></a>
<div style="clear:both;"></div>
<div class ="incasare_action">
<input class="btn btn-success" type="button" style="margin-left:50px;width:80px;height:30px;float:left;" value="Salveaza"></input>
</div>
<div style="clear:both;"></div>
</div>
<div style="clear:both;"></div>
</div>
<div style="clear:both;"></div>
+ Adauga incasare noua
<div class="toram">
<label style = 'cursor:default;'>Total incasat: <b>100 €</b></label>
<label style = 'cursor:default;'>Total ramas: <b>1012 €</b></label>
</div>
</div>
the outerHTML works fine, but when i "clone" the class incasari after that , when the onclick event doesnt work on the clone part. I have a delete button. In the first class "incasari" in works , but in the clone class it does not . Why ?
Use Event Delegation because your element is dynamically created after the click event was assigned. For example, to delegate to the document:
Demo
$(document).on("click", ".del_incasare", function(){
alert("dsdasdasd");
});
This means all clicks will be checked by the .del_incasare selector, and if it matches then the function will run, regardless of when the element was created.
The method you used to fire click event only works with the DOM elements that has been created during page load, DOM elements created dynamically would not be handled by this method.
For the elements created after page load or dynamically you have to use Event Delegation. For this use jQuery on method.
$(document).on("click", ".del_incasare", function(){
// do some cool stuff here.
});
You can use any parent element instead of document here, that was already in DOM during page load.

Categories