Addeventlistener dynamically to newly added classes - javascript

Okay, I have a todo app that i'm creating with JS. I have an addtodo function which adds the items when clicked and also runs a for loop for each of these list elements, which have a classname of 'list', to style them. The thing is, the for loop runs everytime I add the todo list which I think add's multiple event listeners to the already existing items with the same function. I'm trying to toggle a class for priority function when clicking on a paragraph with classname of 'thevalue'. My problem is the event listener runs multiple times and cancels the toggle. The first item runs once, which is correct, and the second item runs, twice, and the third runs three times as follow. I will attach the code below. It would be of great help if you could solve my issue.
var theListPara = document.getElementsByClassName('thevalue');
if (theListPara[0]) {
for (var i = 0; i < theListPara.length; i++) {
theListPara[i].addEventListener("click", changePriority);
}
function changePriority() {
var theClassName = this.parentElement.classList.contains('normal');
if (theClassName) {
this.parentElement.classList.toggle('high');
}
}
}
This whole line of code runs whenever the add todo is clicked.

Event Delegation is the way forward. Its philosophy is very simple, event listener is attached to static-parent-element then it analyses the bubbled event.target. if match is found then the desired operation can be performed.
document.querySelector('.static-parent-element').addEventListener("click", function(e) {
if(e.target && e.target.classList.contains('thevalue')) {
// thevalue item found
if (this.parentElement.classList.contains('normal')) {
this.parentElement.classList.toggle('high');
}
}
});
Element.matches() API can also used to validate element matches a given selector.
The Element.matches() method returns true if the element would be selected by the specified selector string; otherwise, returns false.
if(e.target.matches('.thevalue')){
}

Related

OffcanvasMenu plugin - click event, Shopware 6

I have a problem with adding a click event for list of OffcanvasMenu. I need add this event for every li element. I'm working with Shopware 6.
But when I'm trying to add click event for li element with jQuery, nothing happens.
I've extended OffcanvasMenu. Here is my code:
import OffcanvasMenuPlugin from 'src/plugin/main-menu/offcanvas-menu.plugin.js';
export default class ExtendOffcanvasMenuPlugin extends OffcanvasMenuPlugin {
init() {
super.init();
this._registerEvents();
console.log("inner");
}
_registerEvents() {
super._registerEvents();
let list = document.querySelector('.navigation-offcanvas-list-item');
console.log(list);
list.addEventListener('click', (e) => {
console.log("inner event");
e.preventDefault();
});
}
}
In main.js file I overriding this plugin:
PluginManager.override('OffcanvasMenu', ExtendOffcanvasMenuPlugin, '[data-offcanvas-menu]');
It's any possibility to add event on every li element in OffcanvasMenu plugin?
the general problem seems to be that the current code is using querySelector to collect all elements. However querySelector does only yield the first element which matches the selector, even if there are multiple ones, see: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
You can solve this by using querySelectorAll which does essentially the same but returns an iterable list of all found elements. Then you can iterate over the list and attach the listener to each element:
import OffcanvasMenuPlugin from 'src/plugin/main-menu/offcanvas-menu.plugin.js';
export default class ExtendOffcanvasMenuPlugin extends OffcanvasMenuPlugin {
_registerEvents() {
super._registerEvents();
let list = document.querySelectorAll('.navigation-offcanvas-list-item');
console.log(list);
list.forEach((listItem) => {
listItem.addEventListener('click', (e) => {
console.log("inner event");
e.preventDefault();
});
});
}
}
This would work in theory but in practice the a.navigation-offcanvas-link is inside the <li> where you want to attach the listener. The a.navigation-offcanvas-link element has already an event listener (_getLinkEventHandler) and also takes up all the space inside the <li> which is why the <li> itself is not actually clickable.
A workaround could be to make remove the listener first from a.navigation-offcanvas-link and make it non-clickable but it depends on what you want to achieve. Should something additional be executed everytime a menu item is being clicked? Then you could consider extending method _getLinkEventHandler directly even though it is marked as private and might change in future versions.

Classes on tab key press not being added and removed correctly

I am trying to add a CSS class to all objects that have been tab-key navigated to.
I have the following code, it's running a function on keydown checking if the tab key has been pressed. And tries to remove the tabbed CSS class if it already exists. But only one element on my page (top element) removes the CSS for all other elements. Instead of the class being removed if it exists when someone tab navigates there.
I use this code snipped in a Gatsby site
function checkTabPress(e) {
if (e.keyCode === 9) {
if(e.target.classList.contains('tabbed')) {
e.target.classList.remove('tabbed')
}else {
e.target.classList.add('tabbed');
}
}
}
document.addEventListener('keydown', checkTabPress);
Instead of adding event listener on the document, you should add one for each element you want to have this functionality (you should have ids for the elements). Otherwise the event listener will be called each time you tab, even if you do not tab the elements you want, will check the whole document as it is now doing it.
Maybe you can do something like this.
function checkTabPress(e) {
if (e.keyCode === 9) {
if(e.target.classList.contains('tabbed')) {
e.target.classList.remove('tabbed')
}else {
e.target.classList.add('tabbed');
}
}
}
var myElement = document.getElementById("my_cool_id");
myElement.addEventListener('keydown', checkTabPress);
//element
<div id="my_cool_id"/>
Another option, which i forgot about is this. You can loop through the elements including the class tabbed. However, this will work if all elements by default have this class.
document.querySelectorAll('.tabbed').forEach(item => {
item.addEventListener('keydown', checkTabPress)
})
Or you can just create a new class, for example called tabbable, which you will put on every element you want to have this event listener.
document.querySelectorAll('.tabbable').forEach(item => {
item.addEventListener('keydown', checkTabPress)
})

onClick function used on dynamic content not working properly

I hit a problem with the onclick function when i add divs with ids like "n_block"+(1-~). When I use the jquery zoom function on the objects to make them smaller or bigger onClick doesn't work anymore. I'm not really good at programming so the code might be kind of confusing.
Heres the code i use for the onClick of items:
$(document).on("click",function (e, ui){
//When the document gets clicked, check if one of the items was clicked.
if($(e.target).is($("#n_block" + cloneCount1)) || $(e.target).is($("#n_block" + cloneCount1+ " span"))){
//Set current item.
var item = $("#n_block" + cloneCount1);
//Set style to current item.
item.css("border-color", "Black");
item.css("border-width","2px");
item.css("background-color", "floralwhite");
jsPlumb.repaintEverything();
//Check if key Delete was pressed while item selected & delete that item with his children.
$('html').keydown(function(e){
if(item.css("border-width")=="2px"){
if(e.keyCode == 46) {
/* Prevents line bugging*/
jsPlumb.detachEveryConnection();
jsPlumb.deleteEveryEndpoint();
var razred = getClass(item, "b_"),
id = item.prop("id");
item.remove();
if(razred == "b_2"){
$(".ovoj."+id).remove();
}
else if (razred == "b_4"){
$(".ovojLoop."+id).remove();
$(".empty_block_c."+id).remove();
}
if ( $('.objects').find('div').length == 2) {
$(".objects").empty();
$(".objects").append('<div class="b_s" id="start_block">START</div><p id="start_text">Insert symbols here!</p><div class="b_s" id="end_block">STOP</div> ');
}else{
/* Connects objects together with line. ****/
povezi(cloneCount, tip_crte, ".objects");
}
}
jsPlumb.repaintEverything();
}
});
}
// If item is not clicked set this css to the current item.
else{
$("#n_block" + cloneCount1).css("border-width","1px");
jsPlumb.repaintEverything();
}
});
And heres the zoom code for zooming in when button is clicked:
var currentZoom = 1.0;
$(".zoomin").click(function (){
//Detaches the connections from item to item.
jsPlumb.detachEveryConnection();
jsPlumb.deleteEveryEndpoint();
//Prevents spamming of button, animates the objects
$(".project").stop().animate({ "zoom": currentZoom += .1}, "slow", function() {
if(!$(".objects").children().is($("p"))){
povezi(cloneCount, tip_crte, ".objects");
}
});
});
Use event delegation for binding events to dynamically added elements.
$(document).on('click', ".zoomin", function (){
//Your code.
});
When you use normal .click() to bind event to an element, then that even gets bound to only those elements which exist in the DOM at the moment of the execution of code. Using event delegation, you can tell jQuery that we need to add the handler to every '.zoomin' element which comes inside a particular element no matter when it is added.
The solution depends when exactly is the script which tries to bind the events are executed.
For Eg: Lets assume this script is in document ready function of jquery
$(document).ready(function(){
$(".zoomin").click(function (){
//your logic here
});
});
Here this script is executed when the page HTML is completed loading into the browser. Now when the script executes it tries to find a element with the class zoomin and if found it will add a event to that element and move on. If the element is not found the script just moves on. So we should actually take care of when the script is executed and is the intended element available at that particular instant of time. If the element is not yet available in the HTML (element might come in later dynamically using jquery) we have 2 options to bind event to the element.
1) Execute the script when the element is being added into the HTML: Lets say I have a event which brings up a pop up with some image. Now I want to zoomin and zoomout the image. Since the image in the popup is added dynamically and I have control of when its being added, I can do this.
$(document).ready(function(){
$('.ViewImage').on('click',function(){
// some code is executed which brings up the popup
// now we know that the image is added into html we can now run the script
$(".zoomin").click(function (){
//your logic here
});
});
});
2) We have no Clue/ Control when the element is added into HTML but still want to bind a event to it: This is scenario where we have no control on when the element is being added or not sure where it is being added from (might be from some external plugin used etc) or not having control at all on the element which is added. Thats when we use this syntax as suggested by #Rejith R Krishnan
$(document).on('click', ".zoomin", function (){
//Your code.
});
This will work on all the elements which are in the HTML at the time of execution of the script and on the elements which will be added in the future with the class name zoomin. So this script can be placed inside/ outside of jquery document ready event

why doesn't my jquery click function work after changing element values?

So I'm making a small quiz app with object oriented JS using Object.create cloning method. I have an ol, and a function called showVals() that populates it with lis. That seems to be working fine. What I'm having trouble with is: my li click function to give the attr of ".selected' class seems to work intitially, but after I click to proceed and qn2.showVals() is called it is no longer giving the lis a class of selected when clicked.
The data for qn2 is there. Everything looks normal, except for the click function no longer working (giving the lis the class).
$(document).ready(function(){
qn1.showVals();
qn1.setAns(1); // calling question1 answer for now
$('li').click(function(){
$('li').removeAttr("class");
$(this).attr({"class": "selected"});
});
$('.proceed').click(function(e){
e.preventDefault();
if ($('.selected').html() == qn1.ctAns) {
if (confirm("You are correct")){
qn2.showVals();
qn2.setAns(3);
};
};
});
});
var qn1 = {
title:"The Mouth of Sauron",
qn: "How did 'The mouth of Sauron' meet his demise?",
img: "images/mouth-sauron.gif",
ans: ["Shot in the back", "Beheaded", "Drowned in a swamp", "Sacrificed"],
setAns: function(x) {
this.ctAns = this.ans[x]; //setting correct answer
},
showVals: function(){
$('#slide-title').text(this.title);
$('.question-box > p').text(this.qn);
$('#obj-img').attr("src", this.img);
$('ol').html('<li>'+this.ans[0]+'</li>'+'<li>'+this.ans[1]+'</li>'+
'<li>'+this.ans[2]+'</li>'+'<li>'+this.ans[3]+'</li>')
}
}
var qn2 = Object.create(qn1);
qn2.title = "Golemn";
qn2.qn = "What this dude's name?";
qn2.ans= ["Golemn", "Gimli", "Goober", "Poop"]
qn2.img = "images/golemn.gif";
This is likely because your li elements are dynamically added.
You should try using jQuery on(), which allows you to bind an event handler to the parent element which must already exists in your DOM, and then you can specify the child/descendant selector that will call the event handler. This child element may still be non-existent at the time you do the event binding. In such a case, you call on() like:
$('ol').on('click', 'li', function () {...});
where ol already exists.
Alternatively, you could always bind your click handler to your dynamically generated li elements after you have added them to your DOM. Although I think that is more processor-time consuming as I assume you have to do this for all quiz questions you ask your user.

.class selector not working

I'm working in a card game system that the player can select the card by clicking on it and then select the place to put it on. My problem is that when the player click on the target place, nothing happens.
This is my try: http://jsfiddle.net/5qMHz/
And this is my code:
function target() {
$(".target").on("click", function() {
$("#"+x).appendTo(this);
console.log(x);
});
};
What's wrong?
Try binding with document, since you change the class during document ready and there was no element with the class target initially
$(document).on("click",".target", function() {
$("#" + x).appendTo(this);
console.log(x);
}
WORKING FIDDLE
Firstly, your practice of putting function references in to jQuery objects is rather odd. The problem however is that because the .target class is applied after DOM load you need to use a delegate selector. Try this:
var $card
$(".card").on("click", function () {
$card = $(this);
if ($(".myslot").length) {
if ($(".myslot").is(':empty')) {
$(".myslot:empty").addClass("target");
} else {
alert('No empty slots');
}
}
});
$('.field').on('click', ".target", function () {
$card.appendTo(this);
$card = $();
});
Example fiddle
At the moment you are trying to bind the event handler, the elements don't have a class target yet. From the documentation:
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().
(Technically the elements exist, but they are not (yet) addressable by the class target)
You have three options to solve this:
Add the class to your HTML markup.
Bind the handler after you added the class to the elements.
Use event delegation.
The first two don't really fit to your use case, since your are adding the class target in response to an other event and the number of elements with the class target changes over time. This is a good use case for event delegation though:
$('.field').on('click', '.target', function() {
// ...
});

Categories

Resources