Introduction to Knockout.js in Magento 2
Knockout was one of the biggest changes introduced to the world of Magento 2 frontend and while in the beginning it can be really hard to grasp, at the end of the day it comes out as a really useful tool that's fun to work with.
The library in question really shines in cases where you have to update something according to the user's input or some other action. It brings 'observables' to the table - variables that can be automatically updated and which inform all interested parties that they have been modified. Besides that, we have a 'computed' type of functions that are being automatically fired on an update of any observable inside it. Add the option of binding your html elements to these variables and the number of easily obtained possibilities should be clear now.
In this article, we will create a simple module responsible for playing table tennis with our customer. I will show you how to:
- create a simple frontend module,
- declare and assign a value to observable variables,
- pass a variable from template/block/php to JavaScript,
- use knockout observables in templates,
- fire functions automatically on an observable change.
Let's begin with creating a module Magently_TableTennis that will provide a JavaScript, a template and a layout update for home page.
//registration.php
1
2
3
4
5
6
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magently_TableTennis',
__DIR__
);
//etc/module.xml
1
2
3
4
5
6
7
8
9
<?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="Magently_TableTennis" setup_version="0.1.0">
<sequence>
<!-- add sequence since we will update cms_index_index layout -->
<module name="Magento_Cms"/>
</sequence>
</module>
</config>
And this is where the fun part begins:
//view/frontend/web/js/view/tableTennis.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//first we have to define modules we want to use in our script
define([
'uiComponent',
'ko'
], function (Component, ko) {
//we'll make our game a Component to allow someone to easily modify it in the future
return Component.extend({
initialize: function () {
//next line is basically equivalent of parent::__construct() in php
this._super();
//let's divide our component to 2 separate elements
//one responsible for populating our game's interface
this.populateUi();
//and one responsible for the gameplay itself
this.gameplay();
},
populateUi: function () {
//here we are creating an observable array and set its items to ping and pong
this.userActions = ko.observableArray(['ping', 'pong']);
//this observable will be responsible for storing information about currently selected action
//initialMove will be passed from template
this.selectedAction = ko.observable(this.initialMove);
//and here we will create an observable without initial value
//that will be responsible for storing computer's action
this.aiMove = ko.observable();
},
gameplay: function () {
//assign this to self so we can use it inside a function
var self = this;
//handleMove will be fired every time an observable inside it changes
ko.computed(function handleMove()
{
if (self.selectedAction() == self.userActions()[0]) {
self.aiMove(self.userActions()[1]);
}
else {
self.aiMove(self.userActions()[0]);
}
});
}
});
});
Now, let’s create a user interface for our game:
//view/frontend/layout/cms_index_index.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="top.container">
<block class="\Magento\Framework\View\Element\Template"
name="tabletennis"
template="Magently_TableTennis::tabletennis.phtml"
before="-" />
</referenceContainer>
</body>
</page>
//view/frontend/templates/tabletennis.phtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- data-bind attribute lets us bind knockout entities to our elemets -->
<div data-bind="scope: 'tableTennis'">
<!-- here we create select input with options fetched from userActions observable array -->
<!-- and bind the selected option to selectedAction observable -->
<select data-bind="options: userActions, value: selectedAction"></select>
<!-- analogically we bind the text of a paragraph element to aiMove -->
<p data-bind="text: aiMove"></p>
</div>
<!-- here we initialize our script -->
<script type="text/x-magento-init">
{
<?php // we can specify site elements on which we want to trigger our script ?>
<?php // * means we launch it only once - for the whole page ?>
"*": {
<?php // this is javascript we are launching ?>
"Magento_Ui/js/core/app": {
<?php // and here we declare components of Magento_Ui/js/core/app ?>
"components": {
"tableTennis": {
"component": "Magently_TableTennis/js/view/tableTennis",
<?php // here we set the initial move of the player ?>
"initialMove": "ping"
}
}
}
}
}
</script>
The example provided may seem trivial, but it should introduce you to the topic of Knockout in Magento 2 and give you an easy starting point that I would love to have when writing my first script in this library. Now to close this article I would like to recommend the interactive tutorial available on Knockout's page http://learn.knockoutjs.com/ - it also covers some additional topics, so even if you feel somehow familiar with knockout it won't hurt to do some exercises.