Magento 2 Certified Professional JavaScript Developer Guide
Section 1: Technology Stack
1.1 Demonstrate understanding of RequireJS
What is the main purpose of the RequireJS framework?
RequireJS library is a file and module loader that implements AMD API (Asynchronous Module Definition). AMD API is used as a specification? with an aim to define modules such that the module and its dependencies can be asynchronously loaded.
Which capabilities does RequireJS provide to create and customize JavaScript modules?
Below is the example of RequireJS library usage:
require(['vendor/library'], function(library) { let text = library.getText(); alert(text); });
Dependencies array, necessary for JS function (in our example, the only vendor/library element) is the first argument, passed into the require function. After that, RequireJS passes dependencies objects into the function, which was passed in the second parameter.
This is the example of defining RequireJS module:
define([], function(){ var library = {}; library.getText = function() { return 'Hello World'; } return library; });
Define method is very similar to require method. As the first argument, we pass the dependencies array that our module will need (for now, it is empty), and as the second argument – the function that will return the module object. RequireJS does not impose any limitations on the type of the returned value.
By default, RequireJS turns the module name into the path to the file; therefore, if the module name is vendor/library, then RequireJS will add the .js extension to the final segment and try to get /vendor/library.js file.
To use your own path, specify it in the RequireJS configuration:
require.config({ baseUrl: '/scripts', });
After that, RequireJS will search the library at the /scripts/vendor/library.js path.
What are the pros and cons of the AMD approach to JavaScript file organization?
The main advantage of RequireJS is that it allows not to worry about how and from where the needed files will be loaded. Instead, the developer will specify the module X as a dependency, and RequireJS will do all the work.
RequireJS also allows to separate JavaScript code into independent modules and load them only when necessary.
The major disadvantage of RequireJS is the necessity to wrap JS code into requirejs methods (define and require). This may particularly come as a hurdle during legacy modules adaptation.
Another con of the AMD approach to JavaScript file organization is a large number of HTTP requests, because each module is loaded separately.
RequireJS in Magento 2
Magento 2 uses RequireJS for loading all the JavaScript code. By default, it contains the RequireJS call and a launch configuration, as well as the mechanism for expanding this configuration.
Out-of-the-box Magento 2 loads RequireJS modules into /static/AREA/THEME_VENDOR/THEME_NAME/LOCALE. This path is also used to publish frontend static assets from Magento modules. Which means that if our module contains, for instance, app/code/Vendor/Module/view/adminhtml/web/library.js file, it will be automatically published as /static/adminhtml/Magento/backend/en_US/Vendor_Module/library.js and then can be used as RequireJS module:
<script type="text/javascript"> require('Vendor_Module/library', function(library){ // custom code }); </script>
What is a requirejs-config.js file?
The key function of requirejs-config.js files is to allow adding changes into RequireJS library configuration.
During setup:static-content:deploy
, Magento merges all the require-config.js module files for corresponding areas and creates a combined file that contains all of the requirejs config.
In which cases it is necessary to add configurations to it?
To make an example, we will create a new requirejs-config.js file:
app/code/Vendor/Module/view/frontend/requirejs-config.js
var config = { paths:{ "customLibrary":"Vendor_Module/library" } }; This file will be merged into the combined requirejs-config.js file in the following format: (function() { var config = { paths:{ "customLibrary":"Vendor_Module/library" } }; require.config(config); })();
This code is created for every requirejs-config.js file, which allows to pass the values, defined in module file, into the combined file, and apply such RequireJS features as aliases or shim.
What tools does it provide?
Aliases
Alias can be created for any requirejs module:
require.config({ paths: { "customLibrary": "vendor/library" }, });
Then, specifying customLibrary in module dependencies will be enough:
require(['customLibrary'], function(library) { Let text = library.getText(); alert(text); });
Shim
Many libraries like jQuery, that were developed earlier than RequireJS or AMD standard, have a custom plugin system. Such a system is based on the modification of a single global library object, while additional modules are connected with separate JS files that would interact with this global object.
In the case with RequireJS, the global jQuery object will not be created until the respective RequireJS module is called; otherwise, the jQuery may turn out to be not defined during the load process.
Apply RequireJS configuration in order to add jQuery plugin into Magento 2:
app/code/Vendor/Module/view/frontend/requirejs-config.js
var config = { paths:{ "jquery.plugin":"Vendor_Module/jquery.plugin.min" } };
This record will create RequireJS module with jquery.plugin name that will refer to the initial file of the plugin itself. Afterward, it can be used in the following way:
var config = { paths:{ "jquery.plugin":"Vendor_Module/jquery.plugin.min" } };
This record will create RequireJS module with jquery.plugin name that will refer to the initial file of the plugin itself. Afterward, it can be used in the following way:
require(['jquery','jquery.plugin], function(jQuery, jQueryPlugin){ //customCode });
Although this code will indeed initiate the loading of both files, it will work partially. RequireJS module loading is asynchronous, meaning that a certain file loading order is not specified. If, in certain cases, our plugin is loaded after the core library and will work correctly, then in other cases it may be loaded and called before jQuery is loaded, leading to errors.
Use shim directive in RequireJS configuration to prevent such errors from happening.
var config = { paths:{ "jquery.plugin":"Vendor_Module/jquery.plugin.min" }, shim:{ 'jquery.plugin:{ 'deps':['jquery'] } } };
Shim directive allows to specify for RequireJS that jquery.library module depends on the jquery module and must be loaded only after the jquery loading is complete.
What are global callbacks?
Global callbacks allow to specify the method one must call after all the dependencies are loaded.
How can mappings be used?
Map directive allows to selectively change modules and their locations.
What are RequireJS plugins?
RequireJS loader plugins allow to load various dependencies, as well as optimize them if necessary.
Such plugins are marked with ! sign before the module name
require(['loaderPlugin!moduleToBeLoaded'], function(module) { });
RequireJs will load the loaderPlugin module first and then pass it to the module after the ! sign as an argument into the load() method.
What are the text and domReady plugins used for?
Text plugin allows to load text files, for instance, html templates.
require(["module", "text!template.html"], function(module, html) { //html variable will contain text from template.html file } );
domReady plugin allows to execute the module code only after DOMContentLoaded browser event.
require(['domReady!'], function () { //code will be executed only when DOM is ready });
1.2 Demonstrate understanding of UnderscoreJS
Demonstrate understanding of utility functions
UnderscoreJS is a cross-browser library that provides additional functions for working with arrays, objects and functions, templates and more.
UnderscoreJS connects to RequireJS as ‘underscore’ and is usually used as ‘_’.
Example:
define ([ 'underscore' ], function (_) { var max = _.max ([1, 2, 3]); return max; });
What are the benefits of using the underscore library versus native javascript?
UnderscoreJS library provides functions for certain common tasks, which greatly simplifies the developers’ work, especially in older browsers. However, currently more and more browsers supports native JavaScript functions, which raises the question – which is more superior?
Therefore, if some function requires the support of older browsers, then it is better to rely on UnderscoreJS library. If not, then use native JavaScript; this way, script execution speed will be higher.
Use underscore templates in customizations
The Underscore template compiles JavaScript templates into functions that can be used for rendering.
The example of usage:
_.template (templateString, [settings])
Describe how underscore templates are used
<% = …%> substitutes the result of expression calculation
<% – …%> substitutes the HTML escaped calculation result
<% …%> calculates an expression and does not substitute anything
Magento uses underscore templates in the mage/template script. However, it is used less frequently then KnockoutJS
What are the pros and cons of underscore templates?
Advantages of underscore templates:
- support in older browsers,
- the ability to include logical expressions in templates.
Disadvantages of underscore templates:
- does not support internationalization (i18n),
- does not support observables.
Describe how the underscore templates are used in Magento together with the text RequireJS plugin
The use of underscore templates we will describe using the Magento_Ui/js/lib/knockout/bindings/tooltip.js file as an example.
define ([ ... 'mage / template', 'text! ui / template / tooltip / tooltip.html', ... ], function (..., template, tooltipTmpl, ...) { ... template (tooltipTmpl, { data: config } ... }
- RequireJS text plugin connects text resources and is used by the mask ‘text!path-to-text-resource’
- RequireJS text plugin substitutes the contents of a text resource into a tooltipTmpl parameter
- mage/template is called with template text and data.
- mage/template calls the underscore template
Consequently, to use the underscore template with RequireJS in Magento 2, you need to connect the ‘mage/template’, ‘text!path-to-text-resource’ and call the mage/template function
1.3 Demonstrate understanding of jQuery UI widgets
A large part of Magento 2 frontend is built with jQuery Widgets. Using either standard or custom jQuery UI widgets, one can create menus, modal windows, confirmation dialogues, and many other elements.
What is a jQuery library?
A standard jQuery widget call looks the following way:
$('#tabs-id').tabs({configObject});
Widget system simplifies the process of jQuery plugins development, providing additional ability to control the state of the code. At its core, this is another JavaScript objects system.
jQuery UI widget factory is applied to add new widgets:
jQuery.widget("widgetNamespace.widgetName", { _create:function(){ }, doSomething:function(){ console.log("Hello World"); } });
Afterward, the widget can be created in the following way:
$(‘#element’).widgetName({configObject})
Namespaces are not used directly from the client code, but allow to avoid collisions with other jQuery plugins. Namespaces are used by jQuery as an array keys and can be viewed by looking up the global jQuery object in browser:
console.log(jQuery.widgetNamespace.widgetName)
What different components does it have (jQuery UI, jQuery events and so on)?
- jQuery UI
- jQuery event system
- Global AJAX event listeners
How does Magento use it?
Magento 2 strongly depends on the RequireJS library and its modules. Also, nearly all the Magento 2 JavaScript code is initialized with RequireJS modules. This is also applied to jQuery widgets, which can be created in the following way:
lib/web/mage/list.js
define([ "jquery", 'mage/template', "jquery/ui" ], function($, mageTemplate){ "use strict"; $.widget('mage.list', {/*...*/}); /*...*/ return $.mage.list; })
This file determines RequireJS module, which, in its turn, creates a jQuery widget in a conventional way.
It is possible to apply such a widget with the following code:
require([ 'jquery', 'mage/list' ], function($, list){ $('#element).list({configObject}); })
The list variable will not be directly applied in this code. Therefore, we need RequireJS to load the module together with the widget, so that we can later use it directly from the jQuery object.
In addition to this, there are two other ways to create jQuery widgets in Magento 2: data-mage-init attribute and x-magento-init script tag.
Therefore, this call
<div id="element" data-mage-init='{"mage/list":{configObject}}'></div>
will be fully equivalent to the one we mentioned above.
This works because RequireJS module, accountable for our widget creation, returns the widget instance after it was created, allowing to use data-mage-init and x-magento-init constructions.
On the other hand, this method of widgets calling complicates their modification. In another system, this would not be a complicated task:
jQuery.widget('widgetNamespace.widgetName', {widgetDefinition}); /* ... */ jQuery.widget('widgetNamespace.widgetName', jquery.widgetNamespace.widgetName, newDefinition});
Until the widget is not called, this action can be performed as much as you need. JQuery even allows to call parent widget methods with _super and _superApply methods.
However, this will not work in Magento 2 if the widget is called using the data-mage-init and x-magento-init constructions, due to the fact that they use the widget instance that returns immediately after it was declared in the original RequireJS module. This means that all the widget extensions will not be used in other RequireJS modules.
It is possible to sidestep this limitation by expanding the widget with RequireJS mixins:
app/code/Vendor/Module/view/base/requirejs-config.js
var config = { "config": { "mixins": { "mage/dropdown": { 'Vendor_Module/js/dropdownMixin':true } } } };
app/code/Vendor/Module/view/frontend/web/js/dropdownMixin.js
define(['jquery'], function(jQuery){ return function(originalWidget){ jQuery.widget( 'mage.dropdownDialog', jQuery['mage']['dropdownDialog'], { open:function(){ return this._super(); } }); return jQuery['mage']['dropdownDialog']; }; });
Therefore, we intercept the original widget call, expand it with the features we need and return the altered version of the widget object to be used by data-mage-init and x-magento-init constructions.
1.4 Demonstrate understanding of KnockoutJS
Describe key KnockoutJS concepts
Knockout is a client-side rendering library that implements the Model-View-ViewModel pattern. The main feature of the library is an automatic UI refresh when the data changes.
Describe the architecture of the Knockout library: MVVC concept, observables, bindings
Model-View-ViewModel (MVVC for short) is a software architectural pattern that serves to separate the graphical user interface development from the backend development. MVVC contains 3 components:
- Model contains data,
- View displays data on the screen,
- ViewModel is an intermediary between Model and View. It allows to receive data for the View and influence the Model.
Bindings
The Knockout library also has a number of bindings that facilitate various interactions between data and elements. Bindings are specified as an attribute of the data-bind html element or as an html comment:
<select data-bind=" options: availableCountries, optionsText: 'countryName', value: selectedCountry, optionsCaption: 'Choose...'"></select> <!-- ko if: isVisible--> <span>Visible</span> <!-- /ko -->
List of bindings:
Appearance:
- visible – show / hide element depending on the condition
- text – display text and html tags as text
- html – display html and render html tags
- css – add css class depending on the condition
- style – add css style depending on the condition
- attr – set attribute values
Flow:
- foreach – duplicate the element content for each item in the specified array
- if – add the condition block contents to the Document Object Model (later DOM) if they are true
- ifnot – add the condition block contents to the DOM if they are false
- with – create a new bundling property context in which you can directly address the sub-properties of a certain property.
- component – embed an external component in the DOM
Working with form fields:
- click – function call when clicked
- event – function call when a certain event occurs
- submit – function call when submit
- enable – enable the element when the condition is true
- disable – disable the element when the condition is true
- value – change the value of the form field
- textInput – similar to value, it works with text fields only and provides for all types of user input, including autocomplete, drag-and-drop, and clipboard events
- hasFocus – change the value of the property when it is received (into true) and when the focus is lost (into false)
- checked – change property value when selecting checkbox or radio elements
- options – provide options for dropdown
- selectedOptions – provide selected options from the dropdown
- uniqueName – set a unique name for form fields with an empty name
Template rendering:
- template – render another template
Magento bindings:
- i18n – perform a string translation into the current language of the site
Observables
Observables are JavaScript objects that serve to notify subscribers about changes and automatically detect dependencies. Knockout Observables allow to change the values of variables and automatically notify all the subscribers by sending an event.
Example:
html:
The name is <span data-bind=”text: name”></span>
<button data-bind=”click: change”>Change</button>
script:
var myViewModel = { name: ko.observable('Bob'), change: function() { this.name('Alice'); } }; ko.applyBindings(myViewModel);
output before pressing the Change button:
The name is bob
output after clicking on the Change button:
The name is alice
Demonstrate understanding of knockout templates
Demonstrate understanding of knockout templates
Knockout uses the data-bind attribute and HTML comments to calculate expressions. Additionally, Magento provides an alternative syntax for Knockout templates that can be found in the official Magento documentation: https://devdocs.magento.com/guides/v2.3/ui_comp_guide/concepts/magento-bindings.html
What are the pros and cons of knockout templates?
Benefits:
- Support for older browsers up to IE 6
- Simple library
- Separating HTML and JavaScript
- Dynamic data binding
Disadvantages:
- Poor performance when the objects are numerous
- Since Magento switches to PWA and uses React, there is a probability that Knockout will sooner or later become deprecated.
Compare knockout templates with underscore JavaScript templates
Underscore template is rendered only when calling the _.template(template)(params) method, while the Knockout template is automatically re-rendered if data changes.
Underscore uses <%= … %>, <% … %>, <%- … %> blocks to execute scripts; Knockout, at the same time, uses the data-bind attribute in html elements and html comments.
Demonstrate understanding of the knockout-es5 library
Knockout observables are modified the same way as functions and their values are obtained similarly:
var value = this.knockoutProperty(); // get this.knockoutProperty(value); // set
The knockout-es5 library simplifies the process of obtaining value or altering Knockout observables. The library uses ES5 getter/setter.
var value = this.knockoutProperty; // get this.knockoutProperty = value; // set
It is also possible to use the + =, – =, * =, / = operators
this.knockoutProperty += value; // set
which is identical to the following expression:
this.knockoutProperty(this.knockoutProperty() + value)
In order to apply a plugin to the model, address the ko.track(someModel).
Additionally, as the second parameter, it is possible to specify an array of model fields to limit the fields this plugin is applied to:
ko.track(someModel, ['firstName', 'lastName', 'email']);
Now to get the original observable, you need to call
ko.getObservable(someModel, 'email')
The plugin also allows you to simplify the value of the calculated functions.
For example, in order to write
<span data-bind="text: subtotal"></span>
Instead of
<span data-bind="text: getSubtotal()"></span>
one needs to call the following code:
ko.defineProperty(someModel, 'subtotal', function() {return this.price * this.quantity; });
Tell us about your project
Get in touch with our team. Send us an email at [email protected] or call us 1 650 353 2301