0

Using mongodb and ejs in a NodeJS application, I have created a function that loops through products in the shopping cart and dynamically shows each one of them on the page, inside a table.

I am trying to create a quantity update function that uses an input field to take the quantity and a button to update the database.

My HTML:

<tbody class="product-container">
    <!-- loop through each product -->
    <%  products.forEach (function(element) { %>
        <tr valign="top" class="cart-p-list">
            <!-- get individual unique ID for each product -->
            <input type="hidden" class="id" value="<%= element.item._id %>">

            <td class="col-qty cart-p-qty nowrap" align="right">
                <div class="proopc-input-append">
                    <!-- input for quantity and update button -->
                    <input type="number" class="input-ultra-mini proopc-qty-input qty" size="1" maxlength="4" name="quantity" value="<%= element.qty %>" data-quantity="<%= element.qty %>" step="1" min="1" max="50">
                    <button class="proopc-btn proopc-task-updateqty updateproduct" name="updatecart.0" title="Update Quantity In Cart"><i class="proopc-icon-refresh"></i></button>
                </div>
            </td>
        </tr>
        <% }); %>

For test purposes, the javascript is in a <script> tag at the bottom of the page.

My JavaScript code:

window.addEventListener('load', function() {
    {
        // Update Quantity of product in shopping cart
        const block = document.querySelector('.product-container');


        block.addEventListener('click', function(e) {
            if (e.target.classList.contains('updateproduct')) {
                console.log(e);

                let id = e.target.parentNode.parentNode.parentNode.parentNode.querySelector('.id').value;
                let qty = +e.target.parentNode.querySelector('.qty').value;
                console.log(id);

                fetch(`/update/${id}/${qty}`, {
                    method: 'GET'
                }).then((res) => res.text());
            }
        });
    }
});

The code fetches the following GET request from my cart.js:

router.get('/update/:id/:qty', function (req, res, next) {
    let productId = req.params.id;
    let quantity = +req.params.qty;

    let cart = new Cart(req.session.cart ? req.session.cart : {});
    cart.update(productId, quantity);
    req.session.cart = cart;
    res.redirect('back');
});

And my cart model:

module.exports = function Cart(oldCart) {
    this.items = oldCart.items || {};
    this.totalQty = oldCart.totalQty || 0;
    this.totalPrice = oldCart.totalPrice || 0;

    this.update = function (id, quantity) {
        let currentQuantity = this.items[id].qty;
        let newQuantity = this.items[id].qty = quantity;
        let currentPrice = this.items[id].price;
        let newPrice = this.items[id].item.price * quantity;;
        this.items[id].price = this.items[id].item.price * quantity;
        this.totalQty -= currentQuantity;
        this.totalQty += newQuantity;
        this.totalPrice -= currentPrice;
        this.totalPrice += newPrice;

    };

    this.generateArray = function () {
        let arr = [];
        for (let id in this.items) {
            arr.push(this.items[id]);
        }
        return arr;
    };
};

The logic is working fine. The product is being updated, the price and quantity are correct. The total price and quantity are also correct.

However, if I have more than one product in the cart (two different products), if I try to update the quantity of the second product (or any product that's not the first one), on refresh, the quantity of the first product is updated instead.

This is caused because the eventlistener that updates the quantity, always takes the id of the first dynamically generated item on the page instead of the one that I am trying to update the quantity of.

This must be caused because of looping through the products in the ejs file, so I suspect I need to do some sort of looping in the js function to get the correct id, but I am unsure of this.

Loretta
  • 1,781
  • 9
  • 17
  • Thank you for the suggested answer. The class 'id' exists on every product that is dynamically generated. Every input with class 'id' holds the correct unique id of the product. I don't think this is the problem. – Loretta Nov 05 '19 at 17:50
  • 1
    Checkout the answer to this question. https://stackoverflow.com/questions/34896106/attach-event-to-dynamic-elements-in-javascript Should help you out. Also is that console.log hitting? What is the output of that? – yoshinator Nov 05 '19 at 17:57
  • I tried the first three answers in what you suggested. The result is the same. The console.log(e) outputs the event as it should. `MouseEvent {isTrusted: true, screenX: 866, screenY: 639, clientX: 866, clientY: 536, …}` – Loretta Nov 05 '19 at 18:49
  • The solution for what you suggested is if the class is not found due to the code executing before the dynamic elements being created which is not the problem. It just always selects the id of the first generated element, instead of the id of the selected element for update. – Loretta Nov 05 '19 at 18:51
  • You probably did this already but did you console log `e.target.parentNode.parentNode.parentNode.parentNode.querySelector('.id').value` To see if its what you expect? – yoshinator Nov 05 '19 at 19:04
  • I have both event delegation and selected nearest content to bypass the issues with the function loading before the dynamic content. – Loretta Nov 05 '19 at 19:04
  • Yes, I did. I mentioned this in the question. It outputs the id of the first element no matter which element I choose. So now I have two products. In the developer tools the first one has the id of: `5dbda01a8dd4570774b20a38` and the second one `5dbda01a8dd4570774b20a39`. When I click on the update quantity button for the second product, I should get id `5dbda01a8dd4570774b20a39`, instead I get id `5dbda01a8dd4570774b20a38` in the console. This results in the first item's quantity being updated instead of the second one. – Loretta Nov 05 '19 at 19:07
  • Is your ejs rendering the products with the right id's in the dom? – yoshinator Nov 05 '19 at 19:17
  • Yes. My last answer suggests that. I've been thinking of selecting all ids/products with something like `let ids = document.querySelectorAll('.id');` and then finding a way to check which one was clicked through a loop. – Loretta Nov 05 '19 at 19:24
  • 1
    Found the solution. Thank you for the help anyway, @yoshinator – Loretta Nov 05 '19 at 20:01

1 Answers1

2

I figured out a solution.

I created a function that checks the position of the child (tr in this case) of a parent (tbody with the class product-container in my case) of which the update request was declared.

This is the loop that finds the index:

for (let i = 0, len = block.children.length; i < len; i++) {

    (function(index) {
        block.children[i].onclick = function() {
            console.log(index);
        }
    })(i);
}

This is how I implemented it in my code:

document.addEventListener('DOMContentLoaded', function() {
    {
        // Update Quantity of product in shopping cart
        const block = document.querySelector('.product-container');
        // Fetch an array of all ids
        let ids = document.querySelectorAll('.id');
        // Create a function that shows the index of the child of the parent block
        for (let i = 0, len = block.children.length; i < len; i++) {
            (function(index) {
                block.children[i].onclick = function(e) {
                    if (e.target && e.target.classList.contains('updateproduct')) {
                        // ID now equals the id of the clicked child of the container
                        let id = ids[index].value;
                        let qty = +e.target.parentNode.querySelector('.qty').value;

                        fetch(`/update/${id}/${qty}`, {
                            method: 'GET'
                        }).then((res) => res.text()).then(() => window.history.go());
                    }
                }
            })(i);
        }
    }
});
Loretta
  • 1,781
  • 9
  • 17