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.