An instruction to utilize Knockout JS in Magento 2
Vinh Jacker | 11-11-2024
Knockout is a widely-used JavaScript library in the user interface of Magento 2, employing the MVVM (Model-View-View-Model) design pattern. It is present on various pages of Magento 2, but primarily on the checkout page. However, integrating Knockout JS into Magento 2 can be somewhat complex.
The purpose of this article is to explain the basic principles of Knockout JS in Magento 2, focusing on concepts applicable to Magento 2 and presenting how to implement simple logic.
Introduction about Knockout JS
The JavaScript library known as Knockout JS is essentially used to build specific elements of the Magento 2 user interface in a flexible way. The Model-View-View-Model (MVVM) pattern is used, which in Magento 2 can be somewhat confusing and challenging to apply in practice. However, it significantly improves the smooth operation of an online store built on Magento 2.
Furthermore, the development process facilitated by Knockout JS is incredibly powerful and engaging. In short, Magento primarily relies on the data binding concept of Knockout JS, which is used in many crucial components, including the checkout and mini cart.
Why should we use Data Binding in the first place?
“Can we use basic libraries like JQuery to manually parse the DOM?” - Absolutely!
To be honest, JQuery isn’t really necessary for this purpose. All DOM operations can be performed using simple JavaScript. But in Magento 2, Knockout JS offers benefits such as standardized, clean, and maintainable code. It also improves the speed and security of Magento 2. Therefore, we will guide you on using Knockout JS and building it into a custom module in detail after this article.
How to use Knockout JS in Magento 2
To get started, create a Magento 2 module named Mageplaza_Mymodule. The path MAGENTO2_ROOT > app > code > Mageplaza > Mymodule is where you can find our module. Now, create the register.php file by navigating to app > code > Mageplaza > Mymodule.
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Mageplaza_Mymodule',
__DIR__
);
and in module.xml in app > code > Mageplaza > 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="Mageplaza_Mymodule" setup_version="1.0.0"></module>
</config>
We need to identify where Magento 2 displays the default quantity field on the product page because, as you know, we will be changing the behavior of the quantity. We have the following template that might be helpful after some research.
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 file addtocart.phtml into the module you created: app > code > Mageplaza > Mymodule > view > frontend > templates > catalog > product > view > addtocart.phtml
The UI component, which inherits additional classes and methods from the UI component, is a dependency of Magento 2 Knockout JS. We will build and initialize the UI component in our addtocart.phtml file. We will instruct Magento 2 to create a component in: app > code > Mageplaza > Mymodule > view > frontend > web > js > catalog > product > view > qty_change.js
Insert the following code above the quantity input field:
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"qty_change": {
"component": "Mageplaza_Mymodule/js/view/product/view/qty_change",
"defaultQty": <?php echo $block->getProductDefaultQty() * 1 ?>
}
}
}
}
}
</script>
Now that our qty_change component has been built, we need to link or connect it with the frontend HTML as follows:
<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>
The above code uses the data-bind attribute in both the div and input fields. This attribute acts as a bridge between the HTML and the JavaScript functionality of our qty_change component. Essentially, in the Knockout framework, any function call referenced here will be looked for in our qty_change component.
This helps you realize that the value entered into the input field is related to the result of using the qty() function of the component. Additionally, there are two buttons linked to the component using JavaScript click events. They will help us change the quantity value. Therefore, the final view of 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": "Mageplaza_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 discuss the final step, the qty_change component.
Create a new file named qty_change.js in app > code > Mageplaza > Mymodule > view > frontend > web > js > view > product > view and fill it with the following content.
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);
}
});
});
Now everything is quite clear in the above code. Check the initializer function: (). We have set up an observable quantity, which is a Magento 2 Knockout JS object. When called with the data-bind attribute from HTML, it will return its value. The two other functions are decreaseQty and increaseQty. When the HTML button is clicked, they will help change the value.
That’s the end. To override the default addtocart.phtml template, we need to inform Magento 2 about our custom template file. We will achieve this by modifying the layout using XML.
Let’s create a new file named catalog_product_view.xml in the app > code > Mageplaza > Mymodule > view > frontend > layout directory 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">Mageplaza_Mymodule::catalog/product/view/addtocart.phtml</argument>
</action>
</referenceBlock>
<referenceBlock name="product.info.addtocart.additional">
<action method="setTemplate">
<argument name="template" xsi:type="string">Mageplaza_Mymodule::catalog/product/view/addtocart.phtml</argument>
</action>
</referenceBlock>
</body>
</page>
Finally, we are ready with the Magento 2 module using Knockout JS. Please use the following Magento 2 CLI commands to enable and activate your module:
rm -rf var/di var/generation var/cache/* var/log/* var/page_cache/*
php bin/magento module:enable Mageplaza_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
We can also regularly use our module once it is prepared for use. Part of the Magento 2 interface is built flexibly with the help of Magento 2 Knockout JS. I hope this simple Magento 2 Knockout JS example has been understood and will be helpful for your future Magento 2 development.
Please feel free to contact us anytime if you have any questions or need assistance from our experts with your Magento development agency.