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 trial

JavaScript

ilovecode
ilovecode
8,071 Points

Is 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
Steven Parker
231,141 Points

The 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
ilovecode
8,071 Points

Thanks 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
Steven Parker
231,141 Points

If 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.