I want to get a specific element when the button clicked. I select the clicked data with DOM Traversal method, Is there any method I can use to get an element faster?
<div class="item" id="dessert">
<div class="item-image-div">
<img src="mcd/<?= $row["image"]; ?>" alt="" class="item-image" onclick="getSrc(this.src)">
</div>
<div class="item-detail">
<div class="item-name" onclick="getSrc(this.name)"><?= $row["item"]; ?></div>
<div class="item-order">
<div class="item-price"><?= $row["price"]; ?></div>
<button class="order">
<img src="icon/order.png" alt="" width="40px">
</button>
</div>
</div>
</div>
let orderBtn = document.querySelectorAll('.order');
orderBtn.forEach(function(btn){
btn.addEventListener('click', (e)=>{
if (e.target.parentElement.classList.contains('order')) {
let fullPath = e.target.parentElement.parentElement.parentElement.previousElementSibling.children[0].src;
let pos = fullPath.indexOf('mcd') + 3;
let partPath = fullPath.slice(pos);
let itemId = e.target.parentElement.parentElement.parentElement.parentElement.id;
const item = {};
item.img = `${partPath}`;
console.log(item);
let itemName = e.target.parentElement.parentElement.previousElementSibling.textContent;
let itemPrice = e.target.parentElement.previousElementSibling.textContent;
console.log(itemName);
console.log(itemPrice);
}
});
});
Instead of adding a listener to the button and checking inside the handler that the clicked parent is the .order, add a listener to the <img> instead. (Or, is that even needed? Could you permit clicks on both the <img> and the outer <button>, maybe? That'd make more sense to me, if possible)
Utilize .closest to avoid having to use lots of difficult-to-read .parentElement accesses.
Use querySelector to readably navigate down to descendants of the whole .item.
document.querySelectorAll('.order > img').forEach((img) => {
img.addEventListener('click', () => {
const item = img.closest('.item');
const fullPath = item.querySelector('img').src;
const pos = fullPath.indexOf('mcd') + 3;
const partPath = fullPath.slice(pos);
const itemId = item.id;
const itemName = item.querySelector('.item-name').textContent;
const itemPrice = item.querySelector('.item-price').textContent;
});
});
Related
So, I am trying to pull the volume info from the JSON array from the URL provided: https://www.googleapis.com/books/v1/volumes?q=HTML5
Trying to pull author, title, images, page numbers and description.
This specific class of my HTML code I want to put the JSON data that I have mentioned above in is the 'b-card' class:
<div class="booklist">
<div class="booklist-cards">
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
<div class="b-card">
</div>
</div>
</div>
<script src="https://www.googleapis.com/books/v1/volumes?q=HTML5"></script>
<script src="assets/js/script.js"></script>
The script.js file I have tried is below:
function handleResponse(obj) {
const book = Objects.keys(obj).map(item => obj['items']).reduce(
(acc, rec, id, array) => {
let singleBookCover = rec[id].volumeInfo.imageLinks.thumbnail;
let singleBookTitle = rec[id].volumeInfo.title;
let singleBookAuthor = rec[id].volumeInfo.authors[0];
return [...acc, {singleBookCover, singleBookTitle, singleBookAuthor}]
},
[]
).forEach( item => {
let title = document.createElement('h1');
title.textContent = `${item.singleBookTitle}`;
let author = document.createElement('strong');
author.textContent = `${item.singleBookAuthor}`;
let img = document.createElement('img');
img.src = item.singleBookCover;
img.alt = `${item.singleTitle} by ${item.singleBookAuthor}`;
let container = document.getElementsByClassName('b-card');
container.appendChild(title).appendChild(author).appendChild(img);
})
return book
}
The above code only adds the title image and author, but I cant get them to load into my HTML.
What are ways to resolve this? Am i calling the URL correctly in the HTML script tag?
Forgot to mention - would like to achieve this without using JQuery & AJAX. I have also tried inputting the callback to handleResponse in the script tag url but it doesnt work.
you can't append to the HTML because container is array so it need index of the element
container[index].appendChild(title).appendChild(author).appendChild(img);
but here simple version, and don't forget to add &callback=handleRespons to the API URL
function handleResponse(obj) {
obj.items.forEach((item, index) => {
if(index > 7) return; // limit 8 result
let div = document.createElement('div');
div.className = 'b-card';
div.innerHTML = `<h1>${item.volumeInfo.title}</h1>
<p><strong>${item.volumeInfo.authors[0]}</strong></p>
<img src="${item.volumeInfo.imageLinks.thumbnail}" alt="${item.singleTitle} by ${item.volumeInfo.authors[0]}" />`
let container = document.querySelector('.booklist-cards');
container.append(div);
})
}
<div class="booklist">
<div class="booklist-cards">
</div>
</div>
<script src="//www.googleapis.com/books/v1/volumes?q=HTML5&callback=handleResponse" async></script>
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 am a beginner in JavaScript and I can't figure out the following problem: I am trying to create a simple JavaScript Movie List. I have 10 lists on the Movie List. I tried to show all of the lists with for loop, but it doesn't work.
Here's the code:
function renderModal() {
for (let i = 0; i < listMovies.length; i++) {
let movieData = listMovies[i];
document.getElementById("poster").src = movieData.img;
document.getElementById("title").innerHTML = movieData.name;
document.getElementById("genre").innerHTML = movieData.genre;
document.getElementById("rating-num").innerHTML = "Rating: "+ movieData.rating + "/10";
document.getElementById("movie-desc").innerHTML = movieData.desc;
document.getElementById("imdb-page").href = movieData.link;
return movieData;
}
}
What do I have to do?
Help me to fix it!.
You can use template tag for list and render it into target element.I am showing an example.
Movie list
<div id="movieList"></div>
template for list
<template id="movieListTemplate">
<div class="movie">
<img src="" class="poster" alt="">
<div class="title"></div>
<div class="genre"></div>
<div class="rating-num"></div>
<div class="movie-desc"></div>
<div class="imdb-page"></div>
</div>
</template>
Javascript code:
if (listMovies.length > 0) {
const movileListTemplate = document.getElementById('movieListTemplate')
const movieRenederElement = document.getElementById('movieList')
for(const movie of listMovies) {
const movieEl = document.importNode(movileListTemplate.content, true)
movieEl.querySelector('.poster').src = movie.img
movieEl.querySelector('.title').textContent = movie.name
//use all queryselector like above
}
}
Your return movieData; will stop the loop dead. Not that running it more than once will change anything since you change the same elements over and over. IDs must be unique.
Here is a useful way to render an array
document.getElementById("container").innerHTML = listMovies.map(movieData => `<img src="${movieData.img}" />
<h3>${movieData.name}</h3>
<p>${movieData.genre}</p>
<p>Rating: ${movieData.rating}/10</p>
<p>${movieData.desc}
IMDB
</p>`).join("<hr/>");
With return movieData, the for loop will ends in advance.You should put it outside the for loop.
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>
So, I have 3 variables as seen below, assume they are assigned values.
What I want to do is to insert those variables into img class "myimg2", p class "myname2" and p class "myprof2" when the user clicks the div class="info2".
It has to be in that div because I want the user to click anywhere on that div to change all 3 values.
Is this possible in Javascript?
Javscript:
var storeOnClick,
name,
prof;
HTML
<table class="table2" rules="rows">
<tr>
<td>
<div class="info2" onclick="changeStats(this)" >
<div style="float:left">
<img class="myimg2"style="max-height:80px;" src="Pictures/QuestionMark.png">
</div>
<div style="float:left;">
<p class="myname2">Name: Jane Doe</p>
<p class="myprof2">Profession: Something</p>
</div>
</div>
</td>
<td>
<div class="info2" onclick="changeStats(this)" >
<div style="float:left">
<img class="myimg2"style="max-height:80px;" src="Pictures/QuestionMark.png">
</div>
<div style="float:left;">
<p class="myname2">Name: Jane Doe</p>
<p class="myprof2">Profession: Something</p>
</div>
</div>
</td>
</tr>
</table>
Ok, I think I've got the code. The way I'm doing it is attaching an event handler to the body of the page. Then in that function, I detect if you clicked on a .info element (or one of it's children). If you did, then I change out the values of that particular .info div
Super important parts:
// add an event listener to the entire body. I could have iterated through each div with the '.info2' class instead
if (document.body.addEventListener) {
document.body.addEventListener('click', updateCard, false);
} else {
document.body.attachEvent('onclick',updateCard); // stupid IE
}
// this is the callback function for the click event handler
function updateCard(e) {
e = e || window.event;
var target = e.target || e.srcElement; // more IE stuff
// does target have an ancestor of .info2 ?
var el = findAncestor(target, 'info2');
if (el) {
// which elements do we want to update? Only the currently clicked on .info2
var iImg = el.querySelector('.myimg2');
var iName = el.querySelector('.myname2');
var iProf = el.querySelector('.myprof2');
// assign the values a random element from the arrays
storeOnClick = imgArray[Math.floor(Math.random()*imgArray.length)];
name = nameArray[Math.floor(Math.random()*nameArray.length)];
prof = profArray[Math.floor(Math.random()*profArray.length)];
iImg.src = storeOnClick;
iName.innerHTML = lblName + name;
iProf.innerHTML = lblProf + prof;
}
}
// this is similar to jQuery's $.parents('.class')
function findAncestor (el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
}
demo
For fun, here's what it might look like in jQuery:
$(function() {
$('.info2').on("click",function() {
var t = $(this);
t.find('.myimg2').attr('src',storeOnClick);
t.find('.myname2').html(name);
t.find('.prof2').html(prof);
});
});
Try this:
Insert the new onClick in info2:
<div class="info2" onclick="change_text()">
Also change class to ID for your fields. For example: class="myname2" to id="myname2"
Javascript:
function change_text() {
var text1 = document.getElementById('myname2');
var text2 = document.getElementById('myprof2');
var img1 = document.getElementById("myimg2");
if (text1.innerHTML === "Name: Jane Doe") {
text1.innerHTML = "test";
}
if (text2.innerHTML === "Profession: Something") {
text2.innerHTML = "test";
}
if (img1.src == "link here") {
img1.src = "link here";
}
}
Kinda messy method but it will be easier for you to understand from this method :)
DEMO