Magento2 Ajax Cart Validation on Quantity Change

ajaxcartmagento2

I'm new to Ajax implementation and data-mage-init implementations in Magento 2.

So what I'm trying to do :

Situation now

When you are in your cart -> URL/checkout/cart/ you need to press the update button in order to update the QTY's in your cart. But since the cart does not remember the quantity's when you have 1 that exceeded current stock and it resets all fields i want to do something to fix that

Requested scenario

So i want to have the Qty in the cart updated when you change the qty itself and not have it updated in bulk with 1 button.

Code right now

So i have my JQuery (to perform the ajax) and pthml fille loaded like this :
(module name = Designit/LiveUpdate)

app/code/Designit/LiveUpdate/view/frontend/layout/checkout_cart_index.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <link src="Designit_LiveUpdate::js/LiveUpdate.js"/>
    </head>

    <referenceBlock name="checkout.cart.form">
        <action method="setTemplate">
            <argument name="template" xsi:type="string">Designit_LiveUpdate::cart/form.phtml</argument>
        </action>
    </referenceBlock>

</page>

I made some minor changes to the .pthml like giving the button that does the update right now an ID submitbutton. I thought is was handy to have control over the .phtml fille since that is where i want to get the data. Nothing special done there so i'm not posting that long code block unless requested.

Now i want to have Ajaxto validate this on each change in the input.qty field.

Js:

require(['jquery'], function ($) {
    $("#submitbutton").hide();
    var form = $('form#form-validate');
    var qtyfields = $('input.qty');

    form.find(qtyfields).each(function () {
        console.log("test find fields");
        $(this).change(function () {
            console.log("test");
            $.ajax({
                type: "POST",
                url: form.attr('action'),
                data: form.serialize(),
                success: function (response) {
                //     Here goes the validation ??;
                }
            });
        });
    })
});

You can see i have 2 'test' console logs there. One to see if it finds all the QTY fields and one to see if it can detect the change.

What i noticed so far :

  • It does give me 6 console logs on page load for each qty field
  • It does not give me console logs on change for some reason
  • Not sure if my url: form.attr('action') is a good choice here

Question

  • How can i display the error when the qty field has changed to see live if it is in stock or not? And maybe even output the max stock available.

I thank everyone in advance who wants to put time into this question really really much!

Update

I have managed to have the quantity updated in the cart (not the error message) on change by doing this (do not mind the console.log because those are just for testing):

require(['jquery', 'Magento_Customer/js/customer-data',
    'jquery/jquery-storageapi'], function ($) {
    // $("#submitbutton").hide();
    var form = $('form#form-validate');
    var qtyfields = $('input.qty');
    $('.page.messages').each(function () {
        var thismessage = $(this);
        thismessage.attr('id', 'messages');
    });

    form.find(qtyfields).each(function (e) {

        var thisfield = $(this);

        $(this).change(function () {

            console.log('change detected');
            form.submit();
        });

    });

    form.on('submit', function (e) {
        e.preventDefault();
        $.ajax({
            url: form.attr('action'),
            data: form.serialize(),
            type: 'post',
            success: function (res) {
            },
            error: function () {
                console.log('error');
            }
        });
        console.log('form submitted');

    });
});

I still need a way to update the error messages.

Update

This is a better way. Still finding a way to update the Shipment calculation

define([
    'jquery',
    'Magento_Checkout/js/action/get-totals',
    'jquery/ui'
], function ($, getTotalsAction) {
    "use strict";

    $.widget('mage.liveupdate', {
        options: {
            enabled: true,
            qtyfield: 'input.qty',
            form : 'form#form-validate'
        },
        _create: function () {
            if (this.options.enabled) {
                this._bindqtycart();
            }
        },
        _bindqtycart: function () {

            $("#submitbutton").hide();
            var form = $(this.options.form);
            var qtyfields = $(this.options.qtyfield);


            form.find(qtyfields).each(function (e) {

                $(this).change(function () {
                    form.submit();
                });

            });

            form.on('submit', function (e) {

                // var thisfield = $(this);
                // var thisiteminfo = thisfield.closest(".item-info");
                // var thissubtotal = thisiteminfo.children(".subtotal");
                // var formsubtotals = $(this).find('.subtotal');

                e.preventDefault();
                $.ajax({
                    url: form.attr('action'),
                    data: form.serialize(),
                    type: 'post',
                    success: function (response) {
                        var deferred = $.Deferred();
                    getTotalsAction([], deferred);

                    $('.cart.item').each(function () {
                        var thiscartitem = $(this);

                        var subtotal = thiscartitem.find('.subtotal');
                        var subtotalval = subtotal.find('span.price');

                        var qty = thiscartitem.find('.qty');
                        var qtyval = qty.find('input.qty').val();

                        var price = thiscartitem.find('td.price');
                        var priceval = price.find('span.price').html();
                        var priceval = priceval.replace('€&nbsp;' , '');
                        var priceval = priceval.replace(',' , '.');

                        var subtotalafter = qtyval*priceval;

                        subtotalval.html('€ ' + (subtotalafter.toFixed(2)));
                        })

                    }
                });

            });
        }
    });

    return $.mage.quantity;
});

Best Answer

Just a quick update. I finished this by using regular expression.I still think if we only get the JSON formatted data for the quote data from the controller than that should be better for performance. Thank you.

Solution:

require(['jquery', 'Magento_Customer/js/customer-data',
    'jquery/jquery-storageapi'], function ($) {
    // $("#submitbutton").hide();

    var spinner = $('.ajax-checkout-cart-loader');
    var form = $('form#form-validate');
    var qtyfields = $('#cart-<?php echo $_item->getId() ?>-qty');
    var changeDected = 0;

       $('.page.messages').each(function () {
           var thismessage = $(this);
           thismessage.attr('id', 'messages');
       });

       /*
       form.find(qtyfields).each(function (e) {
           var thisfield = $(this);
           //console.log(thisfield);
           $(this).change(function () {
               spinner.css("display", "block");
               console.log('change detected');
               form.submit();
           });
       });
        */

    form.on('submit', function (e) {

        e.preventDefault();
        $.ajax({
            url: form.attr('action'),
            data: form.serialize(),
            type: 'post',
            success: function (response) {
                var parsedResponse = $.parseHTML(response);
                var itemSubtotal = $(parsedResponse).find(".ajax-subtotal").text();

                var itemSubtotalArray = itemSubtotal.split(" ");


                if(itemSubtotalArray.length !== 1) {
                    itemSubtotalArray = itemSubtotalArray.filter(s => s.replace(/\s+/g, '').length !== 0);
                    //console.log(itemSubtotalArray);

                } else {
                    location.reload();
                }

                //console.log(itemSubtotalArray.length);
                var ajaxSubtototal = $('.ajax-subtotal');

                for (i=0; i <= itemSubtotalArray.length - 1; i++) {

                        ajaxSubtototal.eq(i).find('span.price').text(itemSubtotalArray[i]);

                }

                var quoteDataRegexp = /\"quoteData\":(.*)/;
                var quoteDataMatch = quoteDataRegexp.exec(response);

                var grandTotalRegexp = /\"grand_total\":\"(.*?)\",/;
                var subTotalRegexp = /\"subtotal\":\"(.*?)\",/;
                var taxRegexp = /\"tax_amount\":\"(.*?)\",/;
                var cadRegexp = /\"Discount \(CAD Discount\)\",\"value\":\"(.*?)\",/;


                var grandTotalMatch = grandTotalRegexp.exec(quoteDataMatch[1]);
                var subTotalMatch = subTotalRegexp.exec(quoteDataMatch[1]);
                var taxMatch = taxRegexp.exec(quoteDataMatch[1]);
                var cadDiscountMatch = cadRegexp.exec(quoteDataMatch[1]);

                /* subtotal for each item in the table */

                /* data set for the Knockout data bar */
                //var total =  checkoutConfig2.match(/\"row_total_incl_tax\"/).text();
                var grandTotal = parseFloat(Math.round(grandTotalMatch[1] * 100) / 100).toFixed(2);
                var subTotalbar = parseFloat(Math.round(subTotalMatch[1] * 100) / 100).toFixed(2);
                var taxTotal = parseFloat(Math.round(taxMatch[1] * 100) / 100).toFixed(2);
                var discountTotal = parseFloat(Math.round(cadDiscountMatch[1] * 100) / -100).toFixed(2);
                //console.log(grandTotal);

                $('.totals.sub').find('span.price').text("$" + subTotalbar);
                $('.totals-tax').find('span.price').text("$" + taxTotal);
                $('.grand.totals').find('span.price').text("$" + grandTotal);
                $('.totals:nth-child(2)').find('span.price').text("-$" + discountTotal);

                spinner.css("display", "none");

                //location.reload();

            },
            error: function () {
                console.log('error');
            }
        });
        //console.log('form submitted');

    });

});