I have a list of 10 items with a particular class. when the page scrolls to the end, the following 10 items are loaded.
I want to bind an event only for items that are loaded dynamically rather than for 10 indoor items.
My problem is that the items look exactly the same, I can not change any class or anything else.
any solution?
Create a Set containing first ten objects at the start of the page. Later bind event only to elements that are not in the set.
var s = new Set();
document.querySelectorAll('.aaa').forEach(el=>s.add(el));
setTimeout(function() {
ex.innerHTML += '<span class="aaa">aaa</span>';
ex.innerHTML += '<span class="aaa">aaa</span>';
ex.innerHTML += '<span class="aaa">aaa</span>';
ex.innerHTML += '<span class="aaa">aaa</span>';
document.querySelectorAll('.aaa').forEach(el=> {
if (!s.has(el)) {
el.addEventListener('click', function() {
alert('You clicked me');
});
}
});
}, 1000);
.aaa {
display: block;
}
<span class="aaa">aaa</span>
<span class="aaa">aaa</span>
<span class="aaa">aaa</span>
<span class="aaa">aaa</span>
<span class="aaa">aaa</span>
<span class="aaa">aaa</span>
<div id = "ex"></div>
Only last 4 elements have added click event listener.
Event delegation is how you deal with adding events to dynamic content. Since you did not show actual code, you get a generic answer.
$(document).on("click", ".yourClass", function () {
console.log(this);
});
This way you do not have to bind events every time the content is loaded. It will just listen on a parent element for the click.
It would be better to change document to a parent element that contains all of the elements you are listening to the click event on.
Related
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.");
});
}
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.
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.
I have a Foundation 5 dropdown on my page, which works fine. However when I submit a form in the dropdown area a new element is added to the top of the page so all the elements are moved down.
After that, all my dropdowns stop to work. (If I skip the adding of the element to the DOM, everything is working fine)
My dropdown:
<div id="file-tab">
<i data-dropdown="shareForm16" aria-controls="shareForm16" aria-expanded="false" class="iconTrigger"></i>
<form data-dropdown-content class="share-form f-dropdown content" aria-hidden="true" tabindex="-1" action="" id="shareForm16">
...
</form>
</div>
I thought I need to rebind the foundation event listeners, but it doesn't work. Maybe I am just doing it wrong.
$('#file-tab').on("submit", 'form.share-form',function(e){
e.preventDefault();
var groupName = $(form.target).find('input[type="text"]').val();
var id = $(e.target).parent().children('input[type="hidden"]').val();
if (groupName) {
$(e.target).trigger('click');
window.currentFTT.share(id ,groupName); // adds the element to the DOM
// my attempt to rebind:
$('#'+e.target.id).foundation({bindings: 'events'});
$('i[aria-controls="'+e.target.id+'"]').foundation({bindings: 'events'});
}
});
Instead of calling foundation for individual elements, make a general call to foundation, passing 'reflow' as a parameter:
$(document).foundation('reflow');
E.g:
$('#file-tab').on("submit", 'form.share-form',function(e){
e.preventDefault();
var groupName = $(form.target).find('input[type="text"]').val();
var id = $(e.target).parent().children('input[type="hidden"]').val();
if (groupName) {
$(e.target).trigger('click');
window.currentFTT.share(id ,groupName); // adds the element to the DOM
// my attempt to rebind:
$(document).foundation('reflow');
}
});
I am trying to code it such that when you hover and click one word(eg. chocolate, sugar or cocoa", it gives a sentence, click another word, another sentence. and then the rotating words continue rotating.
Any suggestions is deeply appreciated. Thanks, guys:)
checkout my jsfiddle: http://jsfiddle.net/hE2pk/
HTML:
<div id="steps">
<div id="receipe"></div>
<div id="text"></div>
</div>
JS:
var words = [
'<span id="pasta">Penne</span>',
'<span id="meat">Italian Sausage</span>',
'<span id="oil">Olive Oil</span>',
'<span id="onion">Onion</span>'
];
var index = 0;
$(document).ready(function rotate() {
document.getElementById('text').innerHTML =words[(index++)%(words.length)];
if($("#text").is(":hover")){
setTimeout(rotate, 500);
}
else{
setTimeout(rotate, 100);
}
})()
$(function(){
$('#pasta').on('click', function () {
$("<p>Cook 8 ounces of pasta, according to its package directions.</p>").appendTo('#receipe');
$("#steps").html('<div id="text"></div>');
});
});
Your code:
Appends <span id="pasta">…</span> to the DOM
Attaches a click handler to it
Replaces <span id="pasta">…</span> with <span id="meat">…</span> 100ms later
As soon as step #3 finishes, the #pasta span gets obliterated, along with the click handler!
The correct approach is to use event delegation. This is a technique where you attach an event handler higher up in the DOM (on something that you can reasonably expect not to be destroyed, like document), then listen for events that bubble up toward it. If the event originated from an element of interest, you fire your handler!
$(document).on('click', '#pasta', function() { ... });
Here's your example, fixed: http://jsfiddle.net/steveluscher/hE2pk/1/