How to use Knockout JS in Magento 2 checkout

Magento 2 uses the Knockout JS framework to dynamically build the checkout page. Knockout JS makes use of the Model-View-ViewModel (MVVM) pattern to build the dynamic javascript user interfaces.

In this article, we will start off by looking into all aspects of the rendering process of Magento 2 checkout page. Then we will study an example of using Knockout JS in UB One Step Checkout -- a one step checkout extension for Magento 2. We hope this gives you a foundation for further looking into Knockout JS.

Magento 2 checkout rendering flow

As you may know, the core checkout module in Magento 2 comprises a range of Knockout JS components. Magento 2 gathers all configurations and loading methods of these components in a single XML file loaded in /vendor/magento/module-checkout/view/frontend/layout/checkout_index_index.xml.

Magento 2 parses the XML file checkout_index_index.xml, and runs it through the layout processor. Since the checkout_index_index.xml file declares the structure of UI Components in a tree way manner, layout processors help to modify the initial list of declared UI Components before these components are rendered on the checkout page.

The layout processor processes each XML node and its configurations, then inserts it into a large multidimensional associative array, where each key represents a component or a group of a component. This array is then converted to a JSON object and submitted to the main app checkout component (Magento_Ui/js/core/app) on the main checkout template file (onepage.phtml) and initialized.

As you can see in the main checkout template file at vendor/magento/module-checkout/view/frontend/templates/onepage.phtml, it contains a declarative notation <script type=“text/x-magento-init” /> to initialise a JS component app.js which is responsible for defining rendering Knockout JS components:

#File: vendor/magento/module-checkout/view/frontend/templates/onepage.phtml

<script type="text/x-magento-init">
 {
         "#checkout": {
                  "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>
         }
 }
 </script> 

The main JS component app.js looks as follows:

#File: vendor/magento/module-ui/view/base/web/js/core/app.js

define([
'./renderer/types',
'./renderer/layout',
'../lib/knockout/bootstrap'
], function (types, layout) {
'use strict';

return function (data, merge) {
types.set(data.types);
layout(data.components, undefined, true, merge);
};
});

This component has a dependency named Magento_Ui/js/core/renderer/layout. The code in the Magento_Ui/js/core/renderer/types and Magento_Ui/js/core/renderer/layout JS component is responsible for creating and registering any and all Knockout JS view model constructor objects. That means you simply include the component into XML file and then build the component logic. The renderer automatically loops through each of the components that need to be rendered. If a node with children is detected, it loops them in the same manner and renders each component in the list it receives. For a node without any children, the render returns and processes the current node with no further rendering of children.

An example of custom Knockout JS component in UB One Step Checkout

We will use our Magento 2 UB One Step Checkout extension to demonstrate the example of displaying product(s) within the estimation component -- a core Knockout JS component (This component is visible on mobile view only) -- after adding items to the shopping cart.

Magento 2 Knockout JS - Sample Knockout JS component in Magento 2 UB One Step Checkout

Sample Knockout JS component in Magento 2 UB One Step Checkout

Please note checkout is one of the most complex element of Magento 2, we could not cover in details on every single steps to build a complete feature set. In this article, we just walk you through the key nodes that we implement to achieve a custom Knockout JS component in UB One Step Checkout.

1 -- Define a custom JS Component ‘mobile_cart_items’ in the XML file

According to Magento 2 rules and for the purpose of our one step checkout solution, we need to extend or override each of components building the checkout and their parent/child relationship in the core Magento 2 XML file /vendor/magento/module-checkout/view/frontend/layout/checkout_index_index.xml. When building UB One Step Checkout extension, we already extended and overrode this file which can be found at: app/code/Ubertheme/Checkout/view/frontend/layout/onestepcheckout_index_index.xml.

NOTE: How the extended XML file works.
 
In the extended XML file we created above, it contains a line: <update handle=”checkout_index_index” /> that defines that all definitions in the core Magento 2 file will be extended in the extended XML of UB One Step Checkout module.

The most important item in the XML file is the ‘components’ item: <item name=”components” xsi:type=”array”>. This section refers to the parent Array of all the checkout components defined in the XML file checkout_index_index.xml. It comprises a series of embedded component definitions.

As you go deeper into the tree, you will see the next important item node named ‘checkout’: <item name=”checkout” xsi:type=”array”>

The next item down in the tree is the declaration of the Knockout JS component uiComponent for the node ‘checkout’. The uiComponent alias refers to the actual file path vendor/magento/module-ui/view/base/web/js/lib/core/collection.js. As a required rule of Magento 2, UB One Step Checkout must extend from this base component or another component which has already extended from this base component. Here’s an example of a custom Knockout JS component:

<item name="component" xsi:type="string">Ubertheme_Checkout/js/view/authentication</item>

It is possible to pass an alias to the JS file you want to use as your component (as with uiComponent or the module path to the JS file you want to use). For examples:

<item name="component" xsi:type="string">Magento_Ui/js/view/messages</item>

Or,

<item name="component" xsi:type="string">Magento_Checkout/js/view/authentication-messages</item>

Or,

<item name="component" xsi:type="string">Ubertheme_Checkout/js/view/authentication</item>

The value passed into the component attribute can also be any configuration through the component from the XML file. This is a sample template setting in the UB One Step Checkout extension:

<item name="config" xsi:type="array">
<item name="template" xsi:type="helper" helper="Ubertheme\Checkout\Helper\Data::getCheckoutPageTemplate" />
</item>

Besides, since UB One Step Checkout supports to switch multiple templates, the template value will be dynamically defined via a PHP helper of the UB One Step Checkout extension with the following configuration:

helper="Ubertheme\Checkout\Helper\Data::getCheckoutPageTemplate"

So, in order to add a custom Knockout JS component mentioned above, the first thing we need to do is to declare a custom JS component named mobile_cart_items in the extended XML file (app/code/Ubertheme/Checkout/view/frontend/layout/onestepcheckout_index_index.xml).

We add this configuration and custom component definition as an entry point:

<item name="mobile_cart_items" xsi:type="array">

Next, we define our custom component mobile_cart_items as a child component of the ‘estimation’ component by simply nesting our custom component as a child for the parent node within the ‘children’ attribute as follows:

<item name="estimation" xsi:type="array">
...
<item name="children" xsi:type="array">
<item name="mobile_cart_items" xsi:type="array">
…
</item>
...
</item>
...
</item>
NOTE: The way the layout renderer works in Knockout JS is to loop through each element it finds within the ‘children’ attribute, render it and any of its children. Thus, in order to define a child component in Knockout JS, you simply add such component as a child for the parent node within the ‘children’ attribute. You can define nested children as many as you want.

Then we declare a Knockout JS component:

Ubertheme_Checkout/js/view/summary/cart-items

That’s it. The onestepcheckout_index_index.xml may seem complicated, however the most important node is the one which includes the configuration of all elements that we explained above.

2 -- Define a custom Knockout JS Component ‘cart-items.js’

In aforementioned step 1, we’ve called the new custom JS component cart-items.js. Now, we need to create this file at app/code/Ubertheme/Checkout/view/frontend/web/js/view/summary/cart-items.js. Within this file, we specify that the JS component cart-items.js is extended from an existing Magento 2 core component at vendor/magento/module-checkout/view/frontend/web/js/view/summary/cart-items.js:

define([
'Magento_Checkout/js/view/summary/cart-items',
...

Normally, setting the template is done in the component itself, so we use this statement to define the template of the component:

template: 'Ubertheme_Checkout/summary/cart-items'

This is an alias for the full template file path at: app/code/Ubertheme/Checkout/view/frontend/web/template/summary/cart-items.html

NOTE: In case of using the base component uiComponent -- which does not define a template -- we need to set a template for our component like in the example of the core Magento 2 ‘checkout’ component:

...
<item name="checkout" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
<item name="config" xsi:type="array">
<item name="template" xsi:type="string">Magento_Checkout/onepage</item>
</item>
…

3 -- Define the position of the JS component

Back to the mobile_cart_items component definition in the XML file, we add this configuration: <item name=”displayArea” xsi:type=”string”>mobileCartItems</item> to define where the mobile_cart_items component is rendered on the checkout page. Actually, Magento 2 uses the displayArea attribute which refers to the getRegion function in the template file to tell the Knockout JS renderer where to place the component in HTML.

In our example, the default template file of UB One Step Checkout is located at: app/code/Ubertheme/Checkout/view/frontend/web/template/layout/default.html.

Within this template file, we add this section:

<!-- ko foreach: getRegion('estimation') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->

This indicates that the custom component mobile_cart_items will be displayed as a child element of the estimation component. For each component in the region estimation, it will render content of that component inside the foreach loop.

The position of where children components are rendered can be specified either in the parent component’s template file or using the sortOrder attribute. Unless you use the sortOrder option, you can simply move a component up or down in the same children attribute array to reposition JS components within the checkout page.

In the example of our UB One Step Checkout, we define the position of the mobile_cart_items using the sortOrder attribute via the XML:

<item name="sortOrder" xsi:type="string">1</item>

You can set the value 0 to position the component at the very top, or a high number 8888 for the bottom position.

To reiterate on the preceding, the flow of execution as we understand it comes down to the following configuration for the custom JS component mobile_cart_items:

Magento 2 Knockout JS - Sample Knockout JS component in Magento 2 UB One Step Checkout

Sample Knockout JS component in Magento 2 UB One Step Checkout

Once you get a copy of our UB One Step Checkout extension, you can check out the
app/code/Ubertheme/Checkout/view/frontend/layout/onestepcheckout_index_index.xml file to get into more details about the structure of this main XML file.

Conclusion

Knowing how to work with Knockout JS is an essential tool for you to gain full insight into how the core checkout works in Magento 2. Despites of its complexity, we hope the example of our UB One Step checkout extension helps you get familiar with the checkout rendering in Magento 2, HTML template processing and how Magento 2 initialises Knockout JS components.

Meanwhile, you can check out UB One Step Checkout demo here (it gives you both storefront and admin demo access).

Got questions about Magento 2 extensions or themes? Talk to Ubertheme, we’re happy to help.

Written By

Comments