Chat with us, powered by LiveChat

This website uses cookies

Our website, platform and/or any sub domains use cookies to understand how you use our services, and to improve both your experience and our marketing relevance.

The Basic Concepts of Magento 2 Knockout JS

June 18, 2018

5 Min Read
Reading Time: 5 minutes

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 Cloudways_Mymodule module. Location of our module is MAGENTO2_ROOT > app > code > Cloudways > Mymodule. Now, create a registration.php in app > code > Cloudways > Mymodule

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

and module.xml in app > code > Cloudways > 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="Cloudways_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.

Magento > Catalog > view > frontend > templates > catalog > product > view > addtocart.phtml

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

// @codingStandardsIgnoreFile

/** @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><?php /* @escapeNotVerified */ echo __('Qty') ?></span></label>
            <div class="control">
                <input type="number"
                       name="qty"
                       id="qty"
                       maxlength="12"
                       value="<?php /* @escapeNotVerified */ echo $block->getProductDefaultQty() * 1 ?>"
                       title="<?php /* @escapeNotVerified */ echo __('Qty') ?>" class="input-text qty"
                       data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
                       />
            </div>
        </div>
        <?php endif; ?>
        <div class="actions">
            <button type="submit"
                    title="<?php /* @escapeNotVerified */ echo $buttonTitle ?>"
                    class="action primary tocart"
                    id="product-addtocart-button">
                <span><?php /* @escapeNotVerified */ echo $buttonTitle ?></span>
            </button>
            <?php echo $block->getChildHtml('', true) ?>
        </div>
    </div>
</div>
<?php endif; ?>
<?php if ($block->isRedirectToCartEnabled()) : ?>
<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            "Magento_Catalog/product/view/validation": {
                "radioCheckboxClosest": ".nested"
            }
        }
    }
</script>
<?php else : ?>
<script>
    require([
        'jquery',
        'mage/mage',
        'Magento_Catalog/product/view/validation',
        'Magento_Catalog/js/catalog-add-to-cart'
    ], function ($) {
        'use strict';

        $('#product_addtocart_form').mage('validation', {
            radioCheckboxClosest: '.nested',
            submitHandler: function (form) {
                var widget = $(form).catalogAddToCart({
                    bindSubmit: false
                });

                widget.catalogAddToCart('submitForm', $(form));

                return false;
            }
        });
    });
</script>
<?php endif; ?>

Copy the addtocart.phtml file to your own module:

app > code > Cloudways > Mymodule > view > frontend > template > catalog > 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 > Cloudways > Mymodule > view > frontend > web > js > view > 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": "Cloudways_Mymodule/js/view/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 © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

// @codingStandardsIgnoreFile

/** @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><?php /* @escapeNotVerified */ echo __('Qty') ?></span></label>
            <script type="text/x-magento-init">
            {
                "*": {
                        "Magento_Ui/js/core/app": {
                            "components": {
                                "qty_change": {
                                    "component": "Cloudways_Mymodule/js/view/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 /* @escapeNotVerified */ echo __('Qty') ?>" class="input-text qty"
                    data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
                />
                <button data-bind="click: increaseQty">+</button>
            </div>
        </div>
        <div>
            <input
                type="text"
                name="remarks"
                id="remarks"
                maxlength="255"
                placeholder="Remarks"
            />
        </div>
        <br>
        <?php endif; ?>
        <div class="actions">
            <button type="submit"
                    title="<?php /* @escapeNotVerified */ echo $buttonTitle ?>"
                    class="action primary tocart"
                    id="product-addtocart-button">
                <span><?php /* @escapeNotVerified */ echo $buttonTitle ?></span>
            </button>
            <?php echo $block->getChildHtml('', true) ?>
        </div>
    </div>
</div>
<?php endif; ?>
<?php if ($block->isRedirectToCartEnabled()) : ?>
<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            "Magento_Catalog/product/view/validation": {
                "radioCheckboxClosest": ".nested"
            }
        }
    }
</script>
<?php else : ?>
<script>
    require([
        'jquery',
        'mage/mage',
        'Magento_Catalog/product/view/validation',
        'Magento_Catalog/js/catalog-add-to-cart'
    ], function ($) {
        'use strict';

        $('#product_addtocart_form').mage('validation', {
            radioCheckboxClosest: '.nested',
            submitHandler: function (form) {
                var widget = $(form).catalogAddToCart({
                    bindSubmit: false
                });

                widget.catalogAddToCart('submitForm', $(form));

                return false;
            }
        });
    });
</script>
<?php endif; ?>

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

Create a new file qty_change.js in app > code > Cloudways > Mymodule > view > frontend > web > js > view > 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 > Cloudways > 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">Cloudways_Mymodule::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="product.info.addtocart.additional">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Cloudways_Mymodule::catalog/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 Cloudways_Mymodule
php bin/magento setup:upgrade
php bin/magento setup:di:compile
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.

Share your opinion in the comment section. COMMENT NOW

Share This Article

Boost Your Magento Store Performance by 5x Times & Maximize Your Sales

Our fastest Magento hosting can help you in growing your business revenue by 500%

Fayyaz Khattak

Fayyaz is a Magento Community Manager at Cloudways - A Managed Magento Hosting Platform. His objective is to learn & share about PHP & Magento Development in Community. Fayyaz is a food lover and enjoys driving. You can email him at m.fayyaz@cloudways.com

Get Our Newsletter
Be the first to get the latest updates and tutorials.

Do you like what you read?

Get the Latest Updates

Share Your Feedback

Please insert Content

Thank you for your feedback!