I am trying to dynamically load a bunch of posts from a API and then implement a like button for each of them.
function load_allposts(){
fetch("/posts")
.then(response => response.json())
.then(posts => {
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=${element[0].author_id}> ${element[0].author_name} </button>
</div>
<div class="post-body">
${element[0].body}
</div>
<div class="p1">
<span class="like-status">${element[0].likes}</span> people like this
<button class="like-btn">${element[1]}</button>
</div>
<div class="post-time">
${element[0].timestamp}
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
});
}
I would to like to modify the <span class="like-status"> element when I click the <button class="like-btn">. The only way that I can think of to get a reference to <span class="like-status"> is by adding a ID to it by implementing some kind of counter, which I feel is more like a hack rather than real solution.
I tried googling but almost all solutions involved JQuery, which I am not familiar with. Any help would be appreciated.
You can use delegate event binding document.addEventListener('click', function(event) { to trigger click event for dynamically added button.
It will raise click on every element inside document you need to find if it is one which you expect with event.target.matches('button.like-btn').
Then you can find your span with getting parent and then finding span.like-status using querySelector.
Try it below. For demo modified load_allposts. You do not need to do any change in it.
load_allposts();
document.addEventListener('click', function(event) {
if (event.target.matches('button.like-btn')) {
let span = event.target.parentElement.querySelector('span.like-status');
span.innerText = 'Modified';
}
});
function load_allposts() {
let posts = [1]
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=element[0].author_id> element[0].author_name </button>
</div>
<div class="post-body">
element[0].body
</div>
<div class="p1">
<span class="like-status">element[0].likes</span> people like this
<button class="like-btn">element[1]</button>
</div>
<div class="post-time">
element[0].timestamp
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
}
<div id='all-posts'>
</div>
Note event delegation have extra overhead so alternatively you can use below code.
Here added two functions added as below and added one line bindClickEvent(enc); at end of load_allposts function.
likeClick - perform custom logic to update span.like-status
bindClickEvent - bind click event to all button.like-btn inside div
Call bindClickEvent(enc); at end of load_allposts function.
Try it below.
load_allposts();
// perform custom logic to update span.like-status
function likeClick(event) {
// querySelector will return first matching element
let span = event.target.parentElement.querySelector('span.like-status');
span.innerText = 'Modified';
}
// bind click event to all button.like-btn inside div
function bindClickEvent(enc) {
// querySelectorAll will return array of all matching elements
let buttons = enc.querySelectorAll('button.like-btn');
// loop over each button and assign click function
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = likeClick;
}
}
function load_allposts() {
let posts = [1]
var enc = document.createElement('div');
enc.className = "post-enc";
let s = ``;
posts.forEach(element => {
s += `<div class="p-container">
<div>
<button type="button" class="btn btn-link" class="profile-btn" data-id=element[0].author_id> element[0].author_name </button>
</div>
<div class="post-body">
element[0].body
</div>
<div class="p1">
<span class="like-status">element[0].likes</span> people like this
<button class="like-btn">element[1]</button>
</div>
<div class="post-time">
element[0].timestamp
</div>
</div>`;
});
enc.innerHTML = s;
document.querySelector('#all-posts').appendChild(enc);
// assign click event to buttons inside enc div.
bindClickEvent(enc);
}
<div id='all-posts'>
</div>
Related
I'm making a task board project.
Must say I'm using only HTML, CSS, JS, and nothing else right now.
I'm making a fade-in effect to the newly added note (ul element), and I would like to delete the fade-in class from the previously added note.
this is a chunk of my code that displays the note inside the div.
function displayAllTasks(allTasks){
taskNotesDiv.innerHTML = "";
for(const task of allTasks){
const index = allTasks.indexOf(task);
const note = `
<div class"noteDiv">
<ul class="fadeInNote">
<button type="button" onclick="deleteTask(${index})">
<i class="fa-solid fa-trash deleteButton"></i>
</button>
<li>Task: ${task.task}</li>
<li>Description: ${task.textArea}</li>
<li>Date: ${task.date}</li>
<li>Time: ${task.time}</li>
</ul>
</div>
`
taskNotesDiv.innerHTML += note;
}
}
I tried already another function to delete it but with no success.
any help would be appreciated!
There can be multiple approaches, but my approach is to create element using document.createElement . The modified JS will become:
function displayAllTasks(allTasks) {
last_ul = null; // store the last ul element added
taskNotesDiv.innerHTML = "";
for (const task of allTasks) {
const index = allTasks.indexOf(task);
let noteDiv = document.createElement('div');
noteDiv.classList.add('noteDiv');
note_ul = document.createElement('ul');
note_ul.classList.add('fadeInNote');
note_ul.innerHTML = `
<button type="button" onclick="deleteTask(${index})">
<i class="fa-solid fa-trash deleteButton"></i>
</button>
<li>Task: ${task.task}</li>
<li>Description: ${task.textArea}</li>
<li>Date: ${task.date}</li>
<li>Time: ${task.time}</li>`
noteDiv.appendChild(note_ul);
// if it is not the first element, then remove the class from previous
if (last_ul != null) {
last_ul.classList.remove('fadeInNote');
}
last_ul = note_ul; // this becomes previous for next iteration
taskNotesDiv.appendChild(noteDiv);
}
// remove class of the last element
if (last_ul != null) {
last_ul.classList.remove('fadeInNote');
}
}
I have to build an event listener that listens for a click to delete the product it's linked to; for this I was asked to use element.closest().
Here is the HTML that is being generated for each product:
<article class="cart__item" data-id="${product._id}" data-color="${chosenProduct.color}">
<div class="cart__item__img">
<img src="${product.imageUrl}" alt="${product.altTxt}">
</div>
<div class="cart__item__content">
<div class="cart__item__content__description">
<h2>${product.name}</h2>
<p>${chosenProduct.color}</p>
<p>${product.price}€</p>
</div>
<div class="cart__item__content__settings">
<div class="cart__item__content__settings__quantity">
<p>Qté : ${quantity}</p>
<input type="number" class="itemQuantity" name="itemQuantity" min="1" max="100" value="${quantity}">
</div>
<div class="cart__item__content__settings__delete">
<p class="deleteItem">Supprimer</p>
</div>
</div>
</div>
</article>
As you can see, the identity of the product is inside the article tag.
I launch my function with the event listener with a delay, to make sure the HTML is created, so that I can collect the button(s).
window.setTimeout(function deleteButton() {
const buttons = document.querySelectorAll(".deleteItem");
buttons.forEach((button) => {
button.addEventListener("click", deleteProduct);
});
}, 800);
With this code, each button responds to the function, but they only delete the first product.
Here is what the delete function looks like:
function deleteProduct() {
const itemToDelete = document.querySelector(".cart__item__content");
const idProductToDelete = itemToDelete.closest("article").getAttribute("data-id");
const colorProductToDelete = itemToDelete.closest("article").getAttribute("data-color");
const productToDelete = "product-" + idProductToDelete + "-" + colorProductToDelete;
//remove the item from local storage
localStorage.removeItem(productToDelete);
//remove from the html instantly
deleteHtml();
}
What I understand is that: element.closest() only works with querySelector() (I can't get it to work with getElements etc...), but querySelector() returns only the first element it finds.
How can I make this work?
function deleteProduct() {
const itemToDelete = event.target.querySelector(".cart__item__content");
const idProductToDelete = itemToDelete.closest("article").getAttribute("data-id");
const colorProductToDelete = itemToDelete.closest("article").getAttribute("data-color");
const productToDelete = "product-" + idProductToDelete + "-" + colorProductToDelete;
// //remove the item from local storage
localStorage.removeItem(productToDelete);
// //remove from the html instantly
deleteHtml();
}
Instead of using document.querySelector, you can use the click event's target element. closest would work for it.
or you can directly use
const idProductToDelete = event.target.closest("article").getAttribute("data-id");
I have a list of user cards. That card contains add and remove button.
I want to remove that card from list of card when I click at remove button.
Code is similar to following:
// function to generate card
function generateUserCard(id) {
return `
<div class="user-card" id="${id}">
<button data-id="${id}" class="add" >Add</button>
<button data-id="${id}" class="remove" >Remove</button>
</div>
`;
}
// function to generate list of user
function generateUsers(users) {
const userGrid = $("#user-grid");
for(let user of users) {
const userCard = generateUserCard(user.id);
userGrid.append(userCard);
// adding event listeners
$(`[data-id=${user.id}]`).on("click", function() {
// I did something like this
(`#${user.id}`).remove(); // But this didn't work
})
}
}
Please help!
There are several issues in the logic used in your click event callback:
The variable id is not accessible in the callback. A quick fix will be to fix the reference so that you are using user.id in the selector instead. Also, you can simply remove it by ID without needing to search for it inside its parent element, since it is unique.
Your selector [data-id]=${user.id} is syntacically incorrect. I suppose you meant [data-id=${user.id}]
You should be using .remove() to remove a node
A quick fix will look like this:
$(`button[data-id=${user.id}].remove`).on("click", function() {
$(`#${user.id}`).remove();
});
See proof-of-concept below:
function generateUserCard(id) {
return `
<div class="user-card" id="${id}">
User ID: ${id}
<button data-id="${id}" class="add" >Add</button>
<button data-id="${id}" class="remove" >Remove</button>
</div>
`;
}
function generateUsers(users) {
const userGrid = $("#user-grid");
for (let user of users) {
const userCard = generateUserCard(user.id);
userGrid.append(userCard);
$(`button[data-id=${user.id}].remove`).on("click", function() {
$(`#${user.id}`).remove();
})
}
}
// For demo only
let i = 0;
$('#btn').on('click', function() {
const userArray = [];
for (let j = 0; j < 3; j++) {
i++;
userArray.push({ id: i });
}
generateUsers(userArray);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn">Generate users</button>
<div id="user-grid"></div>
However, an improvement to your code will be to avoid adding new click event listeners to all your newly appended elements. You can simply listen to the click event bubbling up to a parent that is already present at runtime (e.g. #user-grid), and you can bind it outside of your generateUsers function:
$('#user-grid').on('click', 'button.add, button.remove', function() {
const id = $(this).attr('data-id');
$(`#${id}`).remove();
});
See proof-of-concept below:
function generateUserCard(id) {
return `
<div class="user-card" id="${id}">
User ID: ${id}
<button data-id="${id}" class="add" >Add</button>
<button data-id="${id}" class="remove" >Remove</button>
</div>
`;
}
function generateUsers(users) {
const userGrid = $("#user-grid");
for (let user of users) {
const userCard = generateUserCard(user.id);
userGrid.append(userCard);
}
}
// Listen to event bubbling instead!
$('#user-grid').on('click', 'button.remove', function() {
const id = $(this).attr('data-id');
$(`#${id}`).remove();
});
// For demo only
let i = 0;
$('#btn').on('click', function() {
const userArray = [];
for (let j = 0; j < 3; j++) {
i++;
userArray.push({
id: i
});
}
generateUsers(userArray);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn">Generate users</button>
<div id="user-grid"></div>
const cartContent = document.querySelector(".cart_content");
.
.
.
addCartItem(item) {
const div = document.createElement('div');
div.classList.add('cart_item');
div.innerHTML = `
<div class="shop_cart_items_wrapper">
<span class="remove_item" data-id=${item.id}><i class="fas fa-times"></i></span>
<div class="shop_cart_items">
<p class="item_amount">${item.amount}</p>
<span class="cart_title_text_wrapper"><h4 class="cart_title_text">${item.title}</h4></span>
<span class="cart_price_amount_wrapper"><h5 class="cart_price_amount">$${item.price}</h5></span>
</div>
<div>
<div class="inc_dec_amounts_cart">
<i class="fas fa-chevron-up" data-id=${item.id}></i>
<i class="fas fa-chevron-down" data-id=${item.id}></i>
</div>
</div>
</div>`;
cartContent.appendChild(div);
}
.
.
.
cartContent.addEventListener("click", event => {
if (event.target.classList.contains("remove_item")) {
let removeItem = event.target;
let id = removeItem.dataset.id;
cartContent.removeChild.firstElementChild(removeItem.parentElement.parentElement);
this.removeItem(id);
}
So I have everything that's needed for the code to work. I want the entire product item removed from the cart as I press the 'times' icon. I think the removeItem.parentElement.parentElement is not targetting the times icon. What do I have to do to make it target the times icon and close the cart_item div?
It sounds like you're trying to remove the item relative to where the class remove_item is defined (which I assume is maybe the X icon or something. A better way of going about is just defining an id on the parent item itself, finding that element via document.querySelector, and then removing it.
So instead of having something like this
const div = document.createElement('div');
div.classList.add('cart_item');
div.setAttribute('data-item-id', item.id); // <-- now the cart_item has an id associated to it
div.innerHTML = `
<div class="shop_cart_items_wrapper">
<span class="remove_item" data-id=${item.id}><i class="fas fa-times"></i></span>
<div class="shop_cart_items">
And then your remove function becomes
cartContent.addEventListener("click", event => {
if (event.target.classList.contains("remove_item")) {
let removeItem = event.target;
let id = removeItem.dataset.id;
cartContent.removeChild(document.querySelector(`[data-item-id=${id}`));
this.removeItem(id);
}
Or something of the sort. If you're looking for a specific answer on how to get this relatively via your method, that's possible but just giving you another way of going about the solution.
im trying to add an eventlistner to this html tag that i am creating with a api call
handleProducts()
function handleProducts() {
var display = document.getElementById("display")
var url = "http://127.0.0.1:8000/api/product/"
fetch(url)
.then((resp) => resp.json())
.then(function (data) {
console.log(data)
var products = data
for (var i in products) {
var product = `
<div class="col-lg-4">
<img class="thumbnail" src="${products[i].img}" alt="">
<div class="box-element product">
<h6><strong>${products[i].title}</strong></h6>
<hr>
<button data-product=${products[i].id} data-action = "add" class="btn btn-outline-secondary add-btn update-cart">Add to Cart</button>
<a class="btn btn-outline-success" href="">View</a>
<h4 class="price">${products[i].price}</h4>
</div>
</div>
`
display.insertAdjacentHTML('beforeend', product)
}
})
}
function handleAddToCart(){
var updateBtns = document.getElementsByClassName("update-cart")
console.log(updateBtns)
for (var y = 0; y < updateBtns.length; y++) {
updateBtns[y].addEventListener("click", function () {
console.log("Clicked")
})
}
}
handleAddToCart()
Ive included all the code because mabye there is something else i need to add when adding an eventlistner to this type of html code. The problem is that this does not console log clicked when i click the button. Any ideas?
You are calling both functions at the same time, and the fetch is not complete yet while you try to add the event listener. You can move the function call handleAddToCart() inside the first function after you create that element.