Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialilovecode
8,071 PointsIs my code an example of callback hell?
Is my code using callback hell? Or is it normal to start feeling overwhelmed and confused when it is this long? I decided to try see if I can't use promises or shorten it and realised I've hit a wall and have no idea how to approach this, and suddenly my code seemed long and overwhelming.
I remember I struggled a bit with the Promises lessons and would like to know if I need to implement this. It will be a good chance to learn, but I am not even sure if a promise is the right thing to implement here! Maybe I'm just overthinking this right now and overcomplicating things. Or maybe I have been coding for too many hours.
const products = '../products.json';
let row = document.querySelector('.shop-body .row');
let cartImg = document.querySelector('.cart-detail-img');
let id = '';
let prices = [];
let stockAm = 0;
let cartProdName = document.querySelector('.cart-product-name');
let cartPrice = document.querySelector('.cart-price');
let prodQuantity = document.querySelector('.cart-count');
let cartNumItems = document.querySelector('.cart-num-items');
let cartTotal = document.querySelector('.cart-total');
let totalSection = document.querySelector('.total-header-section');
let prodShow = [];
fetch(products)
.then(response => response.json())
.then(data => generateProduct(data))
function generateProduct(data) {
let prod = data.map(dat => {
return row.innerHTML = `<div id="product-${dat.id}" class="col-md-4 col-sm-12">
<div class="shop-list">
<a class="product-image">
<img src="../images/shops/${dat.image}" class="img-fluid" alt="${dat.product}">
</a>
<h4 class="product-name">${dat.product}</h4>
<button class="details">Click for more details</button>
<button class="addToTrunk">Add to trunk</button>
</div>
</div>`;
});
row.innerHTML = prod.join('');
generateProductDetails(data);
addToCart(data);
}
function generateProductDetails(data) {
let detailBtn = document.querySelectorAll('.details');
for (let i = 0; i < detailBtn.length; i++) {
detailBtn[i].addEventListener('click', (e) => {
e.preventDefault();
id = data[i].id
if (data[i].id === id) {
if (data[i].stock === true) {
data[i].stock = 'Yes';
} else {
data[i].stock = 'No';
}
row.innerHTML = `<div class="col-md-12 col-sm-12">
<div class="shop-list">
<button class="back">Back</button>
<h5 class="product-name">${data[i].product}</h4>
<p class="product-description">${data[i].description}</p>
<p class="product-price">${priceSeparated(data[i].price)}</p>
<p class="stock">In stock? ${data[i].stock}, ${data[i].stockLeft} left</p>
<button class="addToTrunk">Add to trunk</button>
</div>
</div>`;
addToTrunk(data);
}
//set price to float, then to string and then split
goBackBtn(data);
});
}
}
function goBackBtn (data) {
let backBtn = document.querySelector('.back');
backBtn.addEventListener('click', (e) => {
generateProduct(data);
});
}
function addToCart(data) {
let addToCartBtn = document.querySelectorAll('.addToTrunk');
for (let i = 0; i < addToCartBtn.length; i++) {
stockAm = data[i].stockLeft;
if(stockAm === 0) {
addToCartBtn[i].innerHTML = 'No Stock';
addToCartBtn[i].disabled = true;
}
checkAdd(data)
addToCartBtn[i].addEventListener('click', (e) => {
id = data[i].id;
if (data[i].id === id) {
totalSection.insertAdjacentHTML('afterend', `
<div class="row cart-detail">
<div><a class="remove-product">Remove</a></div>
<div class="col-lg-4 col-sm-4 col-4 cart-detail-img">
<img src="../images/shops/${data[i].image}" alt="${data[i].product}">
</div>
<div class="col-lg-8 col-sm-8 col-8 cart-detail-product">
<p class="cart-product-name">${data[i].product}</p>
<span class="cart-price price text-info">${priceSeparated(data[i].price)}</span>
<button class="counter decrement">-</button><span class="cart-count count"></span><button class="counter increment">+</button>
</div>
</div>
`);
total(data[i].price);
calcQuantity();
removeProduct();
}
});
}
}
function addToTrunk (data) {
let detailBtn = document.querySelectorAll('.addToTrunk');
let index = id - 1;
console.log('id1 ' + id);
stockAm = data[index].stockLeft;
if(stockAm === 0) {
detailBtn[0].innerHTML = 'No Stock';
detailBtn[0].disabled = true;
}
checkAdd(data);
detailBtn[0].addEventListener('click', (e) => {
totalSection.insertAdjacentHTML('afterend', `
<div class="row cart-detail">
<div><a class="remove-product">Remove</a></div>
<div class="col-lg-4 col-sm-4 col-4 cart-detail-img">
<img src="../images/shops/${data[index].image}" alt="${data[index].product}">
</div>
<div class="col-lg-8 col-sm-8 col-8 cart-detail-product">
<p class="cart-product-name">${data[index].product}</p>
<span class="cart-price price text-info">${priceSeparated(data[index].price)}</span>
<button class="counter decrement">-</button><span class="cart-count count"></span><button class="counter increment">+</button>
</div>
</div>
`);
total(data[index].price);
calcQuantity();
removeProduct();
});
}
function checkAdd(data) {
let addToCartBtn = document.querySelectorAll('.addToTrunk');
for (let i = 0; i < addToCartBtn.length; i++) {
addToCartBtn[i].addEventListener('click', (e) => {
id = data[i].id;
if (data[i].id === id) {
addToCartBtn[i].classList.add('added');
addToCartBtn[i].innerHTML = "Added";
addToCartBtn[i].disabled = true;
}
});
}
}
function priceSeparated (price) {
let parts = price.toFixed(2).split('.');
let galleons = Math.floor(parts[0]/17);
let sickles = parts[0] % 17 + Math.floor(parts[1]/29);
let knuts = parts[1] % 29;
if (galleons === 0 && knuts === 0) {
return `${sickles} sickles`;
}
else if (galleons === 0) {
return `${sickles} sickles and ${knuts} knuts`;
}
else if (sickles === 0) {
return `${galleons} galleon/s and ${knuts} knuts`;
}
else if (knuts === 0) {
return `${galleons} galleon/s and ${sickles} sickles`;
}
else {
return `${galleons} galleon/s, ${sickles} sickles and ${knuts} knuts`;
}
}
function total(price) {
let cartP = document.querySelectorAll('.cart-price');
let totalP = document.querySelector('.cart-total');
prices.push(price);
let sum = prices.reduce( (total, amount) => total + amount);
let total = sum;
totalP.innerHTML = priceSeparated(total);
}
function cart () {
//edit products and quantities and update totals accordingly inside the cart section
}
function calcQuantity() {
const increment = document.querySelector('.increment');
const decrement = document.querySelector('.decrement');
const quantity = document.querySelector('.cart-count');
quantity.innerHTML = `Quantity: 1`;
let x = 1;
increment.addEventListener('click', () => {
x++;
quantity.innerHTML = `Quantity: ${x}`;
console.log(x);
});
decrement.addEventListener('click', (e) => {
if (x > 1) {
x--;
quantity.innerHTML = `Quantity: ${x}`;
console.log(x);
}
});
}
function removeProduct() {
const remove = document.querySelector('.remove-product');
const productToRemove = document.querySelector('.cart-detail');
remove.addEventListener('click', () => {
productToRemove.remove();
});
}
document.querySelector('.dropdown-menu').addEventListener('click', (e) => {
e.stopPropagation();
});
1 Answer
Steven Parker
231,141 PointsThe term "callback hell" usually refers to mostly asynchronous code using promises. With only one "fetch" call, I wouldn't consider that term to apply to this code.
But you can often make code compact. I pointed out one way in a previous question, but an even more important concept is called the "DRY principle" which stands for "Don't Repeat Yourself".
For example, "cartPrice" and "cartTotal" are created as globals at the beginning, but in the "total" function they are not used and "cartP" and "totalP" are created with the same values. Eliminating duplication will help reduce the size of the code without affecting (or perhaps even enhancing) performance.
ilovecode
8,071 Pointsilovecode
8,071 PointsThanks Steven, I didn't notice those.
Do you think I should work on the addToCart and addToTrunk functions too to apply DRY? They do the same thing, except the way to execute them had to be different due to the change of code - the addToCart targets all add to cart buttons on the listing section (multiple buttons of the same class on the page so I needed to loop), and then the addToTrunk targets the one single add to cart button.
Steven Parker
231,141 PointsSteven Parker
231,141 PointsIf an entire function can't be shared, perhaps two smaller functions that do only the parts that are different could share a function that did all the parts that are similar.
Another possible enhancement might be to use a single delegated handler instead of individual handlers for each button.