Blog

The Basic Concepts of Magento 2 Knockout JS – Qty Increment/Decrement

Knockout is a Javascript library which helps in the frontend of Magento 2. It implements MVVM (Model-View-View-Model) design pattern. You can find Knockout JS in Magento 2 on almost every page, but mostly on the checkout page. The implementation of Magento 2 Knockout JS is a bit tricky.

The goal of this post is to explain the basic concepts of Magento 2 Knockout JS which are valid to use in Magento 2 and we will implement very simple logic as well. If you are not familiar with Knockout Javascript library, I would like you to read the documentation of Knockout JS.

Magento 2 is using a text field to handle quantity on the product page. But if you want quantity increment buttons, you can easily add this kind of behavior by using Knockout JS in Magento 2.

 

First of all, create a Magento 2 module. In our example, all files will be located in Thecoachsmb_Mymodule module. Location of our module is MAGENTO2_ROOT > app > code > Thecoachsmb> Mymodule. Now, create a registration.php in app > code > Thecoachsmb> Mymodule

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Thecoachsmb_Mymodule',
    __DIR__
);

and module.xml in app > code > Thecoachsmb> Mymodule > etc

<?xml version="1.0"?>
	<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
	<module name="Thecoachsmb_Mymodule" setup_version="1.0.0"></module>
</config>

so Magento 2 can see our module. As you know, we are going to make changes in the behavior of quantity, so we have to find the place from where Magento 2 is rendering the default quantity field on the product page. After some findings, we have got the following template which can help us.

vendor > magento > module_catalog > view > frontend > templates >  product > view > addtocart.phtml

Copy the addtocart.phtml file to your own module:

app > code > Thecoachsmb > Mymodule > view > frontend > template > product > view > addtocart.phtml

Magento 2 Knockout JS has a dependency of UI Component which further inherits classes and methods from UI Element.

In our addtocart.phtml, we will be creating a UI component and initialize it. We will be telling Magento 2 to create a component that will be located in:

app > code > Thecoachsmb> Mymodule > view > frontend > web > js > product > view > qty_change.js

Add the below script somewhere above the input field of quantity:

<script type="text/x-magento-init">
{
    "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "qty_change": {
                        "component": "Thecoachsmb_Mymodule/js/product/view/qty_change",
                        "defaultQty": <?php echo $block->getProductDefaultQty() * 1 ?>
                    }
                }
            }
    }
}
</script>

Since we have created our component named as qty_change, we need to connect/bind it with the front end HTML, like this:

<div class="control" data-bind="scope: 'qty_change'">
    <button data-bind="click: decreaseQty">-</button>
    <input  data-bind="value: qty()"
    type="number"
    name="qty"
    id="qty"
    maxlength="12"
    title="<?php echo __('Qty') ?>"
    class="input-text qty"
    data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
    />
    <button data-bind="click: increaseQty">+</button>
</div>

In the above code, we have a data-bind attribute to the div, as well as in the input field. The data-bind attribute is used as a medium to connect HTML with a Javascript function of our component qty_change. It means according to the Knockout way; every function call invoked there will be searched in our qty_change component.

This leads you to the understanding that the value of the input field is linked to a result of invoking qty() function located in the component. Also, there are two buttons as well, connected to the component via Javascript click event. They will help us decrease/increase in quantity value. So, the final view of our addtocart.phtml will be:

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

/** @var $block \Magento\Catalog\Block\Product\View */
?>
<?php $_product = $block->getProduct(); ?>
<?php $buttonTitle = __('Add to Cart'); ?>
<?php if ($_product->isSaleable()) :?>
<div class="box-tocart">
<div class="fieldset">
<?php if ($block->shouldRenderQuantity()) :?>
<div class="field qty">
<label class="label" for="qty"><span><?= $block->escapeHtml(__('Qty')) ?></span></label>
<?php /* <div class="control">
<input type="number"
name="qty"
id="qty"
min="0"
value="<?= $block->getProductDefaultQty() * 1 ?>"
title="<?= $block->escapeHtmlAttr(__('Qty')) ?>"
class="input-text qty"
data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
/>
</div> */?>
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"qty_change": {
"component": "Thecoachsmb_Mymodule/js/product/view/qty_change",
"defaultQty": <?php echo $block->getProductDefaultQty() * 1 ?>
}
}
}
}
}
</script>
<div class="control" data-bind="scope: 'qty_change'">
<button data-bind="click: decreaseQty">-</button>
<input data-bind="value: qty()"
type="number"
name="qty"
id="qty"
maxlength="12"
title="<?php echo __('Qty') ?>"
class="input-text qty"
data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
/>
<button data-bind="click: increaseQty">+</button>
</div>
</div>
<?php endif; ?>
<div class="actions">
<button type="submit"
title="<?= $block->escapeHtmlAttr($buttonTitle) ?>"
class="action primary tocart"
id="product-addtocart-button" disabled>
<span><?= $block->escapeHtml($buttonTitle) ?></span>
</button>
<?= $block->getChildHtml('', true) ?>
</div>
</div>
</div>
<?php endif; ?>
<script type="text/x-magento-init">
{
"#product_addtocart_form": {
"Magento_Catalog/js/validate-product": {}
}
}
</script>

Now, let’s talk about the last stage: qty_change component.

Create a new file qty_change.js in app > code > Thecoachsmb > Mymodule > view > frontend > web > js > product > view and add the following content in it.

define([
    'ko',
    'uiComponent'
], function (ko, Component) {
    'use strict';
 
    return Component.extend({
        initialize: function () {
            //initialize parent Component
            this._super();
            this.qty = ko.observable(this.defaultQty);
        },
 
        decreaseQty: function() {
            var newQty = this.qty() - 1;
            if (newQty < 1) {
                newQty = 1;
            }
            this.qty(newQty);
        },
 
        increaseQty: function() {
            var newQty = this.qty() + 1;
            this.qty(newQty);
        }
 
    });
});

In the above code, everything is clear enough now. Look at the initialize: function (). We have initialized a qty observable, a Magento 2 Knockout JS thing, that returns its value when invoked by data-bind attribute from HTML. There are also two more functions decreaseQty and increaseQty. They help to modify the value when the button from HTML is clicked.

And that’s all. Now, the last thing is to change the default addtocart.phtml template. Magento 2 doesn’t know that we want to use our addtocart.phtml file, so we have to modify the template path using the layout. Let’s do it now. Create a new file catalog_product_view.xml in app > code > Thecoachsmb> Mymodule > view > frontend > layout and add the following code:

<?xml version="1.0"?>
<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="product.info.addtocart">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Thecoachsmb_Mymodule::product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="product.info.addtocart.additional">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Thecoachsmb_Mymodule::product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Finally, we had prepared our module using Knockout JS in Magento 2. Please enable and activate your module using below Magento 2 CLI commands:

rm -rf var/di var/generation var/cache/* var/log/* var/page_cache/*
php bin/magento module:enable Thecoachsmb_Mymodule
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:s:d -f
php bin/magento indexer:reindex
php bin/magento cache:clean
php bin/magento cache:flush

Conclusion

Our module is ready to go, and we can also use it on a regular basis. Magento 2 Knockout JS helps to build some parts of Magento 2 frontend dynamically. I hope you have understood this simple example of Magento 2 Knockout JS, and it will be helpful in your Magento 2 development.

If you have anything to discuss related to it, feel free to share your thoughts in the comments section.

Leave a Reply

Your email address will not be published. Required fields are marked *