so I try to make some simple content of modal window as a shopping cart only using js, and there is a problem with delete of each item by "x" after I add them on the cart, I use event.target, but at that stage it gives me an error as "null", it is in the last row of the code. Dont be strict, its my first code :)
so each time I insert in cart
cartItems.innerHTML += `<div class="modal-items-flex ">
<div>
<h2> Item Price: ${itemPrice} </h2>
<h2 class="underscore">Item Name: ${itemTitle} </h2>
</div>
<div class="modal-item-delete">+</div>
</div>`
let removeButton = document.querySelector(".modal-item-delete");
removeButton.onclick=function(e){
e.target.parentElement.remove();
}
I would not attach a new event-listener to each item, but instead would delegate the event-handling to the parent, for example:
cartItems.addEventListener('click', (e) => {
const btn = e.target.closest('.modal-item-delete');
if (!btn) return;
btn.parentElement.remove();
});
Related
as I said in title I have problem with HTML elements created with Element.insertAdjacentHTML() method, I'm trying about an hour to solve this but can't. I have button that create new HTML elements, couple of that elements is new buttons with same class or id, it's no matter, that I need to catch in some variable and than again use for event listener, for some reason the class or id for these new created button doesn't exist, is there any way to catch it and use it later, I need Vanila Javascript?
There is over 500 lines of code, this is only for mentioned method
btnClaim.addEventListener("click", () => {
rewardCurrent.style.display = "none";
claimedRewards.push(currentReward);
rewardsList.innerHTML = ``;
claimedRewards.forEach(function (rew, i) {
const html = `
<div class="reward" id="${i}">
<div class="img-text-cont">
<img src="${rew.imgUrl}" alt="">
<div class="text-cont">
<p class="claimed-reward-title">${rew.title}</p>
<p class="claimed-reward-price">$${rew.price}</p>
</div>
</div>
<div class="claimed-rewards-action">
<button id="btn-sell2">Sell</button>
<button id="btn-ship">Ship</button>
</div>
</div>
`;
rewardsList.insertAdjacentHTML("afterbegin", html);
I need that btn-sell2 and btn-ship buttons in variables.
your element is going to be created and doesn't exist at the time page loads, so js addeventlistener will throw an error. to solve you have 2 ways.
1- use parent node that element will be created inside.
addevenlistener to parent and use
parent.addeventlistener( event, function (event){
if(event.target.classList.contains("childClass") {}
}
2- give addeventlistener when creating the element :
function createElement () {
const elem = -craete elemnt-
elem.addeventlistener(event, function);
}
I am using Django templating engine and JavaScript. My HTML looks like this
<p class="content-card__address">{{ z.formatted_address|truncatewords:6 }}</p>
<div class="content-card-inner">
<p class="content-card__review">Отзывы ({{ z.post_relate.all.count }})</p>
<p class="content-card__phone">{{ z.international_phone_number }}</p>
<div class="div-shaddow"></div>
<p class="content-card__text">Показать</p>
</div>
Cards with text to be generated on the backend using a template engine. My JavaScript code only works on the first card and I need it to work on all cards. With JavaScript I add a class to the div elements. Here is my JavaScript
let call = document.querySelector('.content-card__text');
let divShadow = document.querySelector('.div-shaddow');
call.addEventListener('click', clickCall)
function clickCall() {
call.classList.add('visually-hidden');
divShadow.classList.add('visually-hidden');
}
This code returns you the first element in the DOM and you add click handlers only for it
document.querySelector('.content-card__text')
It will work for you:
const buttons = document.querySelectorAll('.content-card__text');
buttons.forEach(button => {
button.addEventListener('click', clickCall);
});
But please also note that you need to take this into account when working with .divShadow if this element is not alone on the page
Update: example based on your comment
const buttons = document.querySelectorAll('.content-card__text');
const divShadow = document.querySelectorAll('.div-shaddow');
buttons.forEach((button, index) => {
button.addEventListener('click', () => clickCall(index));
});
function clickCall(index) {
buttons[index].classList.add('visually-hidden');
divShadow[index].classList.add('visually-hidden');
}
I'm trying to make active tabs that show content depending on which tab you click.
I absolutely need the first tab to be active by default so i put the "active" class manually in the HTML so the base content is shown.
The issue is, i do that through an empty variable to hold the state but after you've already clicked once. Here is the code :
function homeLoaded() {
menuLoaded();
function menuLoaded() {
const sideMenuChildren = document.getElementById("sidemenu").childNodes;
let currentTab;
for(let i = 0; i < sideMenuChildren.length; i++) {
const tab = sideMenuChildren[i];
tab.addEventListener(
"click",
function() {
if(currentTab) {
sideMenuChildren[currentTab].classList.remove("liactive");
}
currentTab = i;
sideMenuChildren[currentTab].classList.add("liactive");
showContent(tab.id);
console.log("I clicked " + i);
}
);
}
}
let currentPage;
function showContent(menuName) {
const pageContainer = document.getElementById("content");
const element = pageContainer.querySelector("." + menuName);
if(currentPage) {
currentPage.classList.remove("selected");
}
currentPage = element;
element.classList.add("selected");
}
}
Here is the HTML :
<div id="main" style="background-image: url(./images/bg2.png);">
<div id="box">
<ul id="sidemenu">
<li id="profil" class="liactive">Profil</li>
<li id="parcours">Parcours</li>
<li id="contact">Contact</li>
</ul>
<div id="content">
<div class="profil selected"><p>Blabla</p></div>
<div class="parcours"><p>Blablabla</p></div>
<div class="contact"><p>Blablablabla</p></div>
</div>
<div id="bottomnav">
<div id="bottomnavcontent">
</div>
</div>
</div>
</div>
What this does sadly is that when i click another tab...it does not remove the first active tab, so i have 2 active tabs until i re-click the first tab to store it in the variable. I don't know how to fix this.
demo of working codeYou can get event inside the event listener function. There you can get the target.
So,first Remove active class of current tab. Then, set active class for target element.
i.e
tab.addEventListener(
"click",
function (ev) {
const currentab = document.querySelector(".liactive");
currentab.classList.remove("liactive")
ev.target.classList.add("liactive")
}
)
I want to show a modal when I click on and icon. The modal template is inside a template string in my js.
html
<i id="settings-icon" class="fa fa-cog" onClick='attachModal()'></i>
javascript
const attachModal = () => {
const modal = `
<div class='modal>
Modal
</div>
`;
document.body.insertAdjacentHTML('beforeend', modal);
};
There is not error messages however I don't see the modal code in the dom after I click
I'd recommend using [innerHTML][1] and [node.remove()][1] for this since the modal is only either supposed to be visible or hidden. You can append the modal code to the html by using document.body.innerHTML += modal. Using += will concatenate the new HTML to the existing one without replacing/removing any of the existing HTML.
If isModal is true (meaning the modal is already visible) then use isModal.remove() which is a method that a child node can use to remove itself from it's parent (in this case body).
By adding the logic that both hides or shows the modal conditionally, you can reuse this function in what I assume will be a close or cancel button within the modal.
*Note: I use a p tag instead of an i tag for demonstrative purposes. The p tag is not crucial to the solution.
const attachModal = () => {
var isModal = document.getElementById('modal')
const modal = `<div id="modal">
MODAL
</div>`
if (isModal){
isModal.remove()
} else {
document.body.innerHTML += modal
}
};
<p id="settings-icon" class="fa fa-cog" onClick='attachModal()'>test</p>
I'm trying to add an event listener on some repeating innerHTML. I.E for every lot of HTML added by innerHTML, I'll also need to add a click event onto it.
To complicate things I'm also importing a data set from another JS file imported under the name data. As you can see in the code I need the data inside the event listener to be specific to the for loop iteration of the innerHTML so that when I fire the event listener I can see the correct, corresponding data.
This is my attempt:
JS:
import data from './data.js';
import img from './images.js';
export const lists = () => {
const main = document.getElementById('main');
main.innerHTML = `
<div class="main-container">
<div class="flex-between row border-bottom">
<div class="flex new-list">
<img class="create-img img-radius" src="${img.symbols[0]}" alt="Delete Bin">
<h3>New List</h3>
</div>
<div class="flex-between sections">
<h3 class="text-width flex-c">Items:</h3>
<h3 class="text-width flex-c">Reminders:</h3>
<h3 class="text-width flex-end">Created:</h3>
</div>
</div>
<div id="lists"></div>
</div>
`;
const lists = document.getElementById('lists');
for (let i = 0; i < data.lists.length; i++) {
let obj = eval(data.lists[i]);
let totalReminders = getTotalReminders(obj);
lists.innerHTML += `
<div class="flex-between row list">
<h4>${obj.name}</h4>
<div class="flex-between sections">
<h4 class="number-width flex-c">${obj.items.length}</h4>
<h4 class="number-width flex-c">${totalReminders}</h4>
<div class="text-width flex-end">
<h4 class="date">${obj.created}</h4>
<img class="img-radius" src="${img.symbols[3]}" alt="Delete Bin">
</div>
</div>
</div>
`;
const list = document.querySelector('.list');
list.addEventListener('click', () => { // click event
listNav.listNav(obj.name);
listSidebarL.listSidebarL();
listSidebarR.listSidebarR();
listMain.listMain(obj.items);
});
};
};
const getTotalReminders = passed => { // find total reminders
let total = 0;
for (let i = 0; i < passed.items.length; i++) {
total += passed.items[i].reminders;
};
return total;
};
At the moment ONLY the first iteration of innerHTML += has an event listener attached and when I click on it I see the data that should be corresponding the last iteration.
What am I doing wrong here?
You need to move the code that sets up the event handlers so that it is outside of your for loop and runs after that loop is finished. Then, instead of .querySelector(), which only returns the first matching element, you need .querySelectorAll() to return all matching elements. After that, you'll loop through all those elements and set up the handler.
You'll also need to change how your obj variable is declared so that it will be in scope outside of the for loop. Do this by declaring it just before the loop, but assigning it inside the loop:
let obj = null; // Now, obj is scoped so it can be accessed outside of the loop
for (let i = 0; i < data.lists.length; i++) {
obj = eval(data.lists[i]);
And, put the following just after the for loop finishes:
// Get all the .list elements into an Array
const list = Array.prototype.slice.call(document.querySelectorAll('.list'));
// Loop over the array and assign an event handler to each array item:
list.forEach(function(item){
item.addEventListener('click', () => {
listNav.listNav(obj.name);
listSidebarL.listSidebarL();
listSidebarR.listSidebarR();
listMain.listMain(obj.items);
});
});
With all this said, your approach here is really not very good. There is almost always another option than to use eval() for anything and using .innerHTML is usually something to avoid due to its security and performance implications. Using it in a loop is almost always a bad idea. You really should be using the DOM API to create new elements, configure them and inject them into the DOM. If you must use .innerHTML, then build up a string in your loop and after the loop, inject the string into the DOM via .innerHTML, just once.
One options is to look at event delegation/bubbling. The basic principle here is you add the event handler to a parent object, in this case <div id="lists"></div>. Then when the event is fired you query the target of that event to see if it matches your element.
Using this technique you don't have to re-bind event handlers when new items are added, particularly useful if the items are added by user interaction.
In your case it would look something like:
export const lists = () => {
const main = document.getElementById('main');
main.innerHTML = `
<div class="main-container">
<div class="flex-between row border-bottom">
<div class="flex new-list">
<img class="create-img img-radius" src="${img.symbols[0]}" alt="Delete Bin">
<h3>New List</h3>
</div>
<div class="flex-between sections">
<h3 class="text-width flex-c">Items:</h3>
<h3 class="text-width flex-c">Reminders:</h3>
<h3 class="text-width flex-end">Created:</h3>
</div>
</div>
<div id="lists"></div>
</div>
`;
const lists = document.getElementById('lists');
//Now that the parent element is added to the DOM
//Add the event handler
lists.addEventListener("click",function(e) {
// e.target was the clicked element
if (e.target && e.target.matches(".list")) {
listNav.listNav(obj.name);
listSidebarL.listSidebarL();
listSidebarR.listSidebarR();
listMain.listMain(obj.items);
}
//Add Items etc
});
NOTE Scots comments re eval and innerHTML apply equally to this answer.