JavaScript Click-Event trigger fires several times per click - javascript

I have looked at similiar posts but I cant seem to find a Solution.
So the issue I am facing is that I dynamically add divs with content. If you click on that generated content, sth happens. Problem is that the clicklick Event fires several times. Interesting is, that it actually starts with only 1 trigger, goes up to 2,4,6,10,20,40 etc. triggers per click.
function AddArticle() {
let single_article = document.createElement("div");
single_article.setAttribute("class", "each-article");
single_article = `<div> ANY ARTICEL </div>`;
let prices_summary = document.getElementById('prices-summary');
prices_summary.appendChild(single_article);
//Refresh the Event since we added on DIV to the NodeList
RefreshClickEvent();
}
function RefreshClickEvent() {
let each_article = document.querySelectorAll(".each-article");
for (let article of each_article ) {
article.addEventListener('click', () => {
console.log("Trigger.");
});
}
}
Console Log OutPut:
Trigger.
[2]Trigger.
[4]Trigger.
.
.
.
[90]Trigger.
Appreciate any help.

When you add an element, the loop in RefreshClickEvent works for all elements (including the elements that were added). So, you should define a parameter to add event to an element. Another mistake innerHTML to assign content.
function AddArticle() {
let single_article = document.createElement("div");
single_article.setAttribute("class", "each-article");
single_article.innerHTML = `<div> ANY ARTICEL </div>`;
let prices_summary = document.getElementById('prices-summary');
prices_summary.appendChild(single_article);
//Refresh the Event since we added on DIV to the NodeList
RefreshClickEvent(single_article);
}
function RefreshClickEvent(element) {
element.addEventListener('click', () => {
console.log("Trigger.");
});
}

Related

How to copy an event listener in plain Javascript

So I am making some overrides on a Wordpress plugin. I need to copy the event listener on an element and then replace the element and add it back. The event listener is generated by the plugin.
I thought getEventListeners() would work but I have read that it only works in console. If that is this case I'm really astounded. We're in freaking 2020 and I am not finding an obvious solution to this.
What is the solution here people?
Below is the code I was trying to implement having assumed getEventListeners wasn't just a console function.
// Edit Affirm
(function replaceAffirm() {
if (document.querySelector(".affirm-modal-trigger")) {
const learnMore = document.querySelector("#learn-more");
const modalTrigger = document.querySelector(".affirm-modal-trigger");
const clickHandler = getEventListeners(modalTrigger).click[0].listener;
const substr = learnMore.innerHTML
.toString()
.substring(
learnMore.innerHTML.indexOf("h") + 2,
learnMore.innerHTML.length
);
learnMore.innerHTML = "Easy Financing with " + substr;
modalTrigger.addEventListener("click", clickHandler);
} else {
setTimeout(function () {
replaceAffirm();
}, 250);
}
})();
HTML
<p id="learn-more" class="affirm-as-low-as" data-amount="20000" data-affirm-color="white" data-learnmore-show="true" data-page-type="product">
Starting at
<span class="affirm-ala-price">$68</span>
/mo with
<span class="__affirm-logo __affirm-logo-white __ligature__affirm_full_logo__ __processed">Affirm</span>.
<a class="affirm-modal-trigger" aria-label="Prequalify Now (opens in modal)" href="javascript:void(0)">Prequalify now</a>
</p>
You can't copy event listeners, but it seems because of the structure of your HTML it's more likely that you shouldn't need to re-add it. Instead of editing the HTML and removing the event listener by doing so, the best bet would be to edit around it.
If you want to remove the text nodes you can iterate through childNodes and separate out what should be removed.
Then to rebuild the appropriate text where you want it you can use insertAdjacentText
if (document.querySelector(".affirm-modal-trigger")) {
const learnMore = document.querySelector("#learn-more");
const modalTrigger = document.querySelector(".affirm-modal-trigger");
const children = Array.from(learnMore.childNodes);
children.forEach(child => {
if (child.nodeType === Node.TEXT_NODE || child.matches(".affirm-ala-price")) {
if (learnMore.contains(child)) {
learnMore.removeChild(child);
}
}
});
learnMore.insertAdjacentText("afterBegin", "Easy Financing With ");
modalTrigger.insertAdjacentText("beforeBegin", " ");
} else {
setTimeout(function() {
replaceAffirm();
}, 250);
}
<p id="learn-more" class="affirm-as-low-as" data-amount="20000" data-affirm-color="white" data-learnmore-show="true" data-page-type="product">
Starting at
<span class="affirm-ala-price">$68</span> /mo with
<span class="__affirm-logo __affirm-logo-white __ligature__affirm_full_logo__ __processed">Affirm</span>.
<a class="affirm-modal-trigger" aria-label="Prequalify Now (opens in modal)" href="javascript:void(0)">Prequalify now</a>
</p>
Yes waiting for the Html element to be loaded and checking until it gets loaded is okay and this is one of the correct ways to wait for it.
As per my understanding of your issue, you just have to change the text of the learn-more element.
for that, it is not necessary to copy event listener and then again binding it.
Instead of replacing the whole element just change the text keeping the same element.
So it gets binded with the event listener by default.

Changing values from localstorage on click event isn't always working

I'm doing a to-do list application and I want it show the tasks on the page and also save them in local-storage so it's there when the page is refreshed. I added the option to delete and mark tasks as complete which change the element color and change the value of the 'done' property that is stored in local-storage.
By default the 'done' property is supposed to be false and that changes when you click on the element. One time it works and you can see the value changing on the browser tools, and other times when you click on different elements or delete them it doesn't.
The error that I'm getting is:
TypeError: items[index] is undefined. [line 73 in JS Fiddle]
https://jsfiddle.net/84rkq0yj/1/
This is the function that I used but I couldn't identify the reason items[index] becomes undefined.
function completeList(e) {
const targetLi = e.target.parentElement;
const lisArr = Array.from(list.children);
const index = lisArr.indexOf(targetLi);
const taskLi = document.querySelectorAll('li');
taskLi.forEach(li => {
if(li.contains(e.target)){
li.classList.toggle('done');
items[index].done = !items[index].done;
populateList(items, list);
localStorage.setItem('items', JSON.stringify(items));
}
});
};
list.addEventListener('click', completeList);
The issue may happen when the click event fires on a li element instead of a span.
The following code assumes that event is fired on span element that is a child of li element.
function completeList(e) {
const targetLi = e.target.parentElement;
...
}
When the event is fired for li element, the wrapping div is assigned to targetLi variable and that breaks things.
Adding a check for the type of element in the event handler should help:
function completeList(e) {
const targetLi = e.target.tagName === 'LI' ? e.target : e.target.parentElement;
...
}

getElementsByClassName( of node in template literal not working... - undefined error

I have an object of data retrieved via a GET request, which I'm then looping through and display in the DOM.
I then want to grab the ID of the <i> element, (which is a number from each element in the object), so I can then grab this ID and work with it and pass into another function, in another js file. (Eventually, I'll be using this ID for a DELETE request).
Problem is, I run getElementsByClassName on the i tag, I get the following error:
TypeError: Cannot set property 'onclick' of undefined
When my data is injected into the DOM, I can see all the HTML tags/content there in the DOM through console. However, it's almost as if the JavaScript maybe loading too quickly.
I've tried setTimeOut() function to bypass this.
Window.onload = {} and drop my function in here.
And even addEventListener.
However, the error still appears, regardless of what I try....
Any ideas?
Here is snippets of my code below:
function displayLastTask(res) {
const lastTask = (res[Object.keys(res).length-1]);
return new Promise(resolve => {
setTimeout(() => {
resolve(
individualTasks +=
`<div class="todoStyle">
<div class="text">
<p>Date: ${lastTask.date}</p>
<p>Task: ${lastTask.todo}</p>
</div>
<div class-"icon">
<i class="far fa-calendar-times deleteButton" id=${lastTask.id}></i>
</div>
</div>`
);
tasksContainer.innerHTML = individualTasks;
return tasksContainer;
}, 2000);
});
}
And here is the simple JS, I'm trying to grab the ID by:
var divs = document.getElementsByClassName('deleteButton');
for (var i = 0; i <= divs.length; i += 1) {
divs[i].onclick = function(e) {
e.preventDefault();
alert(this.id);
};
}
After the close the error in console. And copy and paste the code above into console, it works...
I cannot figure out, why any tag I try/className to grab, is returned undefined with the onclick combo, after the GET request is made and my data is posted into the DOM.
This error occurred because of getElementsByClassName('deleteButton') execution before appending deleteButton to the dom, u can use .then method after calling the displayLastTask like this:
displayLastTask(res).then(() => {
var divs = document.getElementsByClassName('deleteButton');
for (var i = 0; i <= divs.length; i += 1) {
divs[i].onclick = function(e) {
e.preventDefault();
alert(this.id);
};
}
});
Your onclick is being attached too soon. Specifically, when you call divs[i].onclick = function(e), your divs need to be in the DOM. However, it looks like they're not present when that loop runs, so the handlers are not attached.
You need to attach the onclick handlers after your GET request completes.

Add event listener to element after DOMContentLoaded

I have some trouble with adding event listener to element after DOM updating.
I have some page, that sort two lists and save the stage.
I can move elements between this lists by d&d and by clicking special button. And it work fine for me.
https://jsfiddle.net/bmj32ma0/2/
But, I have to save stage of this lists, and after reloading I have to extract stage, so I write code below.
function saveFriendsLists(e) {
if(e.target.classList.contains("b--drugofilter--save-button")){
var vkFriends = document.querySelector('.b--friends-from-vk .js--friends-container').innerHTML;
var choosenFriends = document.querySelector('.b--friends-choosen .js--friends-container').innerHTML;
localStorage.setItem('vkFriends', vkFriends);
localStorage.setItem('choosenFriends', choosenFriends);
}
}
function loadFriensListFromStorage() {
if(localStorage&&localStorage.choosenFriends&&localStorage.vkFriends){
document.querySelector('.b--friends-from-vk .js--friends-container').innerHTML = localStorage.vkFriends;
document.querySelector('.b--friends-choosen .js--friends-container').innerHTML = localStorage.choosenFriends;
}
}
document.addEventListener("DOMContentLoaded", loadFriensListFromStorage);
But after adding this, the preview functionality like D&D doesn't work. And I can't provide you valid jsfidle because, as I can understand, localStoradge reason or something.
When I tried to move my addEventListener to loadFriensListFromStorage function, like this:
function loadFriensListFromStorage() {
if(localStorage&&localStorage.choosenFriends&&localStorage.vkFriends){
document.querySelector('.b--friends-from-vk .js--friends-container').innerHTML = localStorage.vkFriends;
document.querySelector('.b--friends-choosen .js--friends-container').innerHTML = localStorage.choosenFriends;
}
[].forEach.call(friends, function(friend) {
friend.addEventListener('dragstart', handleDragStart, false);
});
}
But that doesn't have any effect.
How can I fix this issue? Thx.

jQuery slideDown not working on element with dynamically assigned id

EDIT: I cleaned up the code a bit and narrowed down the problem.
So I'm working on a Wordpress site, and I'm trying to incorporate drop-downs into my menu on mobile, which means I have to use jQuery to assign classes and id's to my already existing elements. I have this code that already works on premade HTML, but fails on dynamically created id's.
Here is the code:
...
var menuCount = 0;
var contentCount = 0;
//find the mobile menu items
var submenus = $('[title="submenu"]');
if (submenus.length && submenus.parent('.fusion-mobile-nav-item')) {
console.log(submenus);
submenus.addClass('dropdown-title').append('<i id="dropdown-angle" class="fa fa-angle-down" aria-hidden="true"></i>');
submenus.each(function() {
$(this).attr("href", "#m" + menuCount++);
})
var content = submenus.parent().find('ul.sub-menu');
content.addClass('dropdown-content');
content.each(function() {
$(this).attr("id", "m" + contentCount++);
})
}
$(document).on('click', '.dropdown-title', function(e) {
var currentAttrValue = $(this).attr('href');
if ($(e.target).is('.d-active') || $(e.target).parent('.dropdown-title').is('.d-active')) {
$(this).removeClass('d-active');
$(currentAttrValue).slideUp(300).removeClass('d-open');
} else {
$('.dropdown-title').removeClass('d-active');
$('.dropdown-content').slideUp(300).removeClass('d-open');
$(this).addClass('d-active');
console.log($(currentAttrValue));
//THIS LINE FAILS
$(currentAttrValue).slideDown(300).addClass('d-open');
}
e.preventDefault();
});
I've registered the elements with the class dropdown-title using $(document).on(...) but I can't figure out what I need to do to register the elements with the custom ID's. I've tried putting the event callback inside the .each functions, I've tried making custom events to trigger, but none of them will get the 2nd to last line of code to trigger. There's no errors in the console, and when I console log the selector I get this:
[ul#m0.sub-menu.dropdown-content, context: document, selector: "#m0"]
0
:
ul#m0.sub-menu.dropdown-content
context
:
document
length
:
1
selector
:
"#m0"
proto
:
Object[0]
So jQuery knows the element is there, I just can't figure out how to register it...or maybe it's something I'm not thinking of, I don't know.
If you are creating your elements dynamically, you should be assigning the .on 'click' after creating those elements. Just declare the 'on click' callback code you posted after adding the ids and classes instead of when the page loads, so it gets attached to the elements with .dropdown-title class.
Check this jsFiddle: https://jsfiddle.net/6zayouxc/
EDIT: Your edited JS code works... There also might be some problem with your HTML or CSS, are you hiding your submenus? Make sure you are not making them transparent.
You're trying to call a function for a attribute, instead of the element. You probably want $(this).slideDown(300).addClass('d-active'); (also then you don't need $(this).addClass('d-active'); before)
Inside submenus.each loop add your callback listener.
As you are adding the class dropdown-title dynamically, it was not available at dom loading time, that is why event listener was not attached with those elemnts.
var menuCount = 0;
var contentCount = 0;
//find the mobile menu items
var submenus = $('[title="submenu"]');
if (submenus.length && submenus.parent('.fusion-mobile-nav-item')) {
console.log(submenus);
submenus.addClass('dropdown-title').append('<i id="dropdown-angle" class="fa fa-angle-down" aria-hidden="true"></i>');
submenus.each(function() {
$(this).attr("href", "#m" + menuCount++);
// add callback here
$(this).click( function(e) {
var currentAttrValue = $(this).attr('href');
if ($(e.target).is('.d-active') || $(e.target).parent('.dropdown-title').is('.d-active')) {
$(this).removeClass('d-active');
$(currentAttrValue).slideUp(300).removeClass('d-open');
} else {
$('.dropdown-title').removeClass('d-active');
$('.dropdown-content').slideUp(300).removeClass('d-open');
$(this).addClass('d-active');
console.log($(currentAttrValue));
$(currentAttrValue).slideDown(300).addClass('d-active');
}
e.preventDefault();
});
})
var content = submenus.parent().find('ul.sub-menu');
content.addClass('dropdown-content');
content.each(function() {
$(this).attr("id", "m" + contentCount++);
})
}
Turns out my problem is that jQuery is adding to both the mobile menu and the desktop menu, where the desktop menu is being loaded first when I search for that ID that's the one that jQuery finds. So it turns out I was completely wrong about my suspicions.

Categories

Resources