Table of content

Magento 2 Certified Professional JavaScript Developer Guide


Section 2: Magento JavaScript Basics

2.1 Demonstrate understanding of the modular structure of Magento

What file structure is used to organize JavaScript modules? Where does Magento locate a module’s JavaScript file? Where does Magento store the JavaScript library?

Javascript files in Magento 2 can be located at the following places:

  1. Library level (lib/web) – this directory is used solely for core Magento resources, available for all modules and themes
  2. Module level (app/code/Vendor/Module/view/AREA/web) – also available for all modules and themes
  3. Theme level for a particular module (app/design/AREA/Vendor/Theme/Vendor_Module/web) – available for current and inheriting themes
  4. Theme level (app/design/AREA/Vendor/Theme/web) – available for current and inheriting themes

Describe how static content is organized in Magento

Static content allows to redefine resources by simply creating a file with the same name, but on a level higher according to the fallback scheme. Magento 2 fallback system, in its turn, will search for the requested file following the directories in a certain order. This order depends on the file type and module context availability.

For JavaScript files, as well as style files, images and fonts, the order is the following:

  1. If the module context is unknown
    1. Static files of the current theme for a certain locale (THEME_DIR/web/i18n/LOCALE/)
    2. Static files of a current theme (THEME_DIR/web/)
    3. Parent themes until an unparented theme is not found (PARENT_THEME_DIR/web/i18n/LOCALE/, PARENT_THEME_DIR/web/)
    4. Library files (lib/web/)

b. If the module context is known

    1. Current theme files for locale and module (THEME_DIR/web/i18n/LOCALE/Vendor_Module/)
    2. Current theme files for the module (THEME_DIR/Vendor_Module/web/)
    3. Parent themes, until an unparented theme is not found (PARENT_THEME_DIR/web/i18n/LOCALE/Vendor_Module/, PARENT_THEME_DIR/Vendor_Module/web/)
    4. Module files for the current AREA (app/code/Vendor/Module/view/AREA/web/)
    5. Module files for the base area (app/code/Vendor/Module/view/base/web/)

How does Magento expose a module’s static content to the web requests?

Due to the fact that static resources are used for creating a web page directly and therefore must be available at the user’s browser, Magento utilizes static content publishing mechanism.

To make static files available externally, Magento publishes them in pub/static/frontend/VENDOR/THEME/LOCALE/ directory.

Afterward, file app/code/Vendor/Module/view/frontend/web/js/customModule.js will be available at the path pub/static/frontend/VENDOR/THEME/MODULE/Vendor_Module/js/customModule.js.

What are the different ways to deploy static content?

In developer and default modes static content directory is filled on-demand. In production mode, these files are not created automatically and must be deployed with CLI command setup:static-content:deploy.

bin/magento setup:static-content:deploy [<languages>]

bin/magento setup:static-content:deploy en_US en_GB

This command takes the input LOCALES list, for which one must publish static files, and allows to optionally exclude from the publication certain themes, locales, areas and file types.

Additionally, there are several strategies for static resources publication:

  1. Standard strategy – in this case, all files from all modules are published.
  2. Quick strategy – in this case, only one locale is published for each theme. In other locales, only altered files are published and the rest of them are copied. This strategy decreases deploy time and increases the number of files copied.
  3. Compact strategy – allows to avoid file duplicates by copying similar files in the directory base.

2.2 Describe How to use JavaScript modules in Magento

Use requirejs-config.js files to create JavaScript customizations

Requirejs-config.js holds an important place in Magento JavaScript ecosystem.

The requirejs-config.js files are collected from the current theme, its parent themes, from all included modules into one file and the link to them is sent to the browser.

Here is an example of requirejs-config.js file Magento (vendor / magento / module-catalog / view / frontend / requirejs-config.js):

var config = {
    map: {
        '*': {
            compareList:            'Magento_Catalog/js/list',
            relatedProducts:        'Magento_Catalog/js/related-products',
            upsellProducts:         'Magento_Catalog/js/upsell-products',
            productListToolbarForm: 'Magento_Catalog/js/product/list/toolbar',
            catalogGallery:         'Magento_Catalog/js/gallery',
            priceBox:               'Magento_Catalog/js/price-box',
            priceOptionDate:        'Magento_Catalog/js/price-option-date',
            priceOptionFile:        'Magento_Catalog/js/price-option-file',
            priceOptions:           'Magento_Catalog/js/price-options',
            priceUtils:             'Magento_Catalog/js/price-utils',
            catalogAddToCart:       'Magento_Catalog/js/catalog-add-to-cart'
        }
    },
    config: {
        mixins: {
            'Magento_Theme/js/view/breadcrumbs': {
                'Magento_Catalog/js/product/breadcrumbs': true
            }
        }
    }
};

To get to know all the available options, follow the link https://requirejs.org/docs/api.html#config

How do you ensure that a module will be executed before other modules?

Bear in mind that the order of dependency listing does not correlate with the load order. To define the order of execution, one needs to apply Shim option in requirejs-config.js.

For instance, jquery will be loaded before the jquery/jquery-migrate.

'shim': {
    'jquery/jquery-migrate': ['jquery'],
},

Using define or require, enforce loading of the jquery module before the other module:

require(['jquery'], function ($) {
    ...
});

define(['jquery'], function ($) 
    return function(...){
        ...
    };
});
How can an alias for a module be declared?

The best way to describe this course of action is with the following example:

vendor/magento/module-ui/view/base/requirejs-config.js:

var config = {
    map: {
        '*': {
            uiElement:      'Magento_Ui/js/lib/core/element/element',
            uiCollection:   'Magento_Ui/js/lib/core/collection',
            uiComponent:    'Magento_Ui/js/lib/core/collection',
            uiClass:        'Magento_Ui/js/lib/core/class',
            uiEvents:       'Magento_Ui/js/lib/core/events',
            uiRegistry:     'Magento_Ui/js/lib/registry/registry',
            consoleLogger:  'Magento_Ui/js/lib/logger/console-logger',
            uiLayout:       'Magento_Ui/js/core/renderer/layout',
            buttonAdapter:  'Magento_Ui/js/form/button-adapter'
        }
    }
};

Now it is possible to execute require([‘uiElement’], function(uiElement){ … }) instead of require([‘Magento_Ui/js/lib/core/element/element’], function(uiElement){ … }).

What is the purpose of requirejs-config.js callbacks?

A callback is a function to execute after the deps loading. Callbacks are helpful in the situation when require is defined as a config object before require.js is loaded and one must specify a function to require after the loading of the configuration’s deps array.

Describe different types of Magento JavaScript modules

A module is a separate *.js file that can import other require.js modules. It looks the following way:

// File (app/code/Vendor/Module/view/frontend/web/js/script.js)
define(['jquery'], function ($) 
    return function(...){
        ...
    };
});
// 
Another module must look the following way to be applied by the first module.

define([
    'jquery',
    'Vendor_Module/js/script'
], function ($, myModule) 
    return function(...){
        ...
        myModule(...);
        ...
    };
});
Plain modules

A plain module is a regular one that is not inherited from another module. You can find an example of a plain module above (Vendor_Module/js/script). In the examples below, we will use modules that are inherited using the jQuery.widget, uiElement.extend functions.

jQuery UI widgets

jQuery widget allows to create a UI widget with a custom handler. Let us consider the following example:

vendor/magento/module-multishipping/view/frontend/web/js/payment.js:

define([
    'jquery',
    'mage/template',
    'Magento_Ui/js/modal/alert',
    'jquery/ui',
    'mage/translate'
], function ($, mageTemplate, alert) {
    'use strict';

    $.widget('mage.payment', {
        options: {
            ...
        },

        _create: function () {
            ...
        },
    });

    return $.mage.payment;
});
UiComponents

UiComponents enables to create a widget with a custom handler. UiComponents are inherited from uiElement.

Let us examine it using the following file as an example vendor/magento/module-catalog/view/frontend/web/js/storage-manager.js:

define([
    'underscore',
    'uiElement',
    'mageUtils',
    'Magento_Catalog/js/product/storage/storage-service',
    'Magento_Customer/js/section-config',
    'jquery'
], function (_, Element, utils, storage, sectionConfig, $) {
    'use strict';

    ...

    return Element.extend({
        defaults: {
            ...
        },

        initialize: function () {
            ...

            return this;
        },
    });
});

2.3 Demonstrate ability to execute JavaScript modules

Magento javascript init methods is a default interface for launching RequireJS modules. It allows to avoid direct JavaScript introduction with using standard <script> tags.

Magento 2 has two basic means for this purpose:

  1. <script type=”text/x-magento-init” /> html tag
  2. data-mage-init html attribute

Both methods allow to initialize RequireJS module, pass JSON values to this module and specify DOM element this module must use.

What is the purpose and syntax of the text/x-magento-init script tag?

To consider in detail how this method works, we can take any module, in which we need to create RequireJS module and phtml template that will utilize this module.

app/code/Vendor/Module/view/frontend/web/test.js

define([], function () {
    let jsComponent = function(config, node)
    {       
        console.log(config);
        console.log(node);
    };

    return jsComponent;
});

app/code/Vendor/Module/view/frontend/templates/test.phtml

<div id="example_div">Contents</div>

<script type="text/x-magento-init">
    {
        "#example_div": {
            "Vendor_Module/test":{"config":"value"}          
        }
    }        
</script>

<script type="text/x-magento-init">
…
</script>

Such script tag is commonly ignored by the browser because browsers do not recognize the type of text/x-magento-init. Magento makes use of this behavior peculiarity and, after the page is loaded, uses JSON-content of such tags for initialization and launch of Magento JavaScript Components. These components are different from common RequireJS modules, for they return the function that Magento can later execute.

The syntax of x-magento-init tags’ JSON-content looks the following way:

   {
        "DOM_ELEMENT_SELECTOR": {
            "REQUIREJS_MODULE":CONFIG_OBJECT          
        }
    }

Where DOM_ELEMENT_SELECTOR is a CSS selector of DOM-node that will be passed to the component, CONFIG_OBJECT is the component’s RequireJS name and CONFIG_OBJECT is a JSON object generated with PHP.

This initialization method allows to avoid hardcoding of the necessary selectors and configuration parameters in JavaScript or JavaScript generation directly from PHP.

CONFIG_OBJECT and DOM_ELEMENT_SELECTOR are passed as parameters into the returned by a RequireJS function component. After the page is loaded in the browser console, the following content will appear:

> Object {config: “value”}

> <div id=”example_div”>Contents</div>

Also, if our component does not require DOM element to function, we can call it the following way:

<script type="text/x-magento-init">
    {
        "*": {
            "Vendor_Module/test":{"config":"value"}          
        }
    }        
</script>

What is the purpose and syntax of the data-mage-init attribute?

Data-mage-init html attribute is an alternative method for initializing Magento JavaScript Component for a certain DOM element.

How is it used to execute JavaScript modules?

We can modify our phtml file the following way:

app/code/Vendor/Module/view/frontend/templates/test.phtml

<div data-mage-init='{"Vendor_Module/test": {"config":"value"}}'>Content</div>

The result will be the same. Div with the same attribute and embedded config JSON object will be passed to the function, returned by Vendor_Module/test RequireJS module.

It is necessary to use single quotation marks (data-mage-init=’…’) in this case, because the embedded JSON will be processed in a strict mode that requires JSON to have only double quotation marks.

Also, in case JS component does not return the function, Magento tries to find REQUIREJS_MODULE in jQuery prototype, and if such does not exist, it will be called in the following way:

$.fn.REQUIREJS_MODULE  = function() { ... };
  return;

What is the difference between the text/x-magento-init and the data-mage-init methods of JavaScript module execution?

Tag text/x-magento-init can be used to call modules without the reference to a certain DOM element.

How do you execute a JavaScript module in an AJAX response and in dynamic code stored in a string?

To execute this, use imperative call of a JS module:

<script>
require([
    'jquery',
    'module'
], function ($) {
    $(function () {
        $('[data-role=example]') 
           .accordion({
                header:  '[data-role=header]',
                content: '[data-role=content]',
                trigger: '[data-role=trigger]',
                ajaxUrlElement: "a"
            });
    });
});
</script>

2.4 Describe jQuery UI widgets in Magento

Describe how Magento uses jQuery widgets, and demonstrate an understanding of the $.mage object

$.mage is a namespace for Magento widgets. It uses a set of widgets and functions; below is a list of the most common ones:

  1. Accordion widget
  2. Alert widget
  3. Calendar widget
  4. Collapsible widget
  5. Confirm widget
  6. DropdownDialog widget
  7. Gallery widget
  8. List widget
  9. Loader widget
  10. Menu widget
  11. Modal widget
  12. Navigation widget
  13. Prompt widget
  14. QuickSearch widget
  15. Tabs widget

There is also a number of less popular functions and widgets:

  • __ – look translate
  • cookies.get(name) – get cookie
  • cookies.set(name, value, options) – change cookie
  • cookies.clear(name) – clear cookie
  • dataPost – a widget that creates post form and sends it to the server
  • dateRange – date range widget that creates 2 calendar widget
  • escapeHTML(str) – execute escape HTML
  • formKey – widget that generates formKey and saves into cookie, in case formKey is absent from the cookie
  • init – initiates data-mage-init scripts
  • isBetween(value, from, to) – checks whether there is a number between two other
  • isEmpty(value) – checks whether the line is empty using trim
  • isEmptyNoTrim(value) – checks whether the line is empty not using trim
  • isValidSelector(selector) – checks whether css selector is valid
  • pageCache – a widget for full page cache that loads private content via an additional ajax query
  • parseNumber(value) – transforms a line into a number
  • redirect(url, type, timeout, forced) – executes redirect to the specified url
  • sidebar – a sidebar widget
  • stripHtml(value) – deletes html tags from the line
  • translate – translates the line into the language of the current website locale
  • translateInline – inline translation widget
  • validation – form validation widget
What is the role of jQuery widgets in Magento?

jQuery JavaScript library serves to implement client functionality, widely using standard, customized and custom jQuery widgets for this purpose.

A developer has a choice – either to create a new widget from scratch, using the $.Widget objects as a base to inherit from, or directly inherit from the existing jQuery UI or third-party widgets. If you name your custom widget the same as the widget you inherited it from, it is possible to extend the initial widget.

How are Magento jQuery widget modules structured?

A widget is created using the $.widget method and contains options and ways to work with the widget (_addClass, _create, _delay, _destroy, _focusable, _getCreateEventData, _getCreateOptions, _hide, _hoverable, _init, _off, _on, _removeClass, _setOption, _setOptions, _show, _super, _superApply, _toggleClass, _trigger, destroy, disable, enable, instance, option, widget).

The following example demonstrates how to create a widget using vendor/magento/module-multishipping/view/frontend/web/js/payment.js:

define([
    'jquery',
    'mage/template',
    'Magento_Ui/js/modal/alert',
    'jquery/ui',
    'mage/translate'
], function ($, mageTemplate, alert) {
    'use strict';

    $.widget('mage.payment', {
        options: {
            ...
        },

        _create: function () {
            ...
        },
    });

    return $.mage.payment;
});

Describe how Magento executes jQuery widgets

Magento executes Magento jQueries two ways:

  • Connection via RequireJS
  • data-mage-init and text/x-magento-init

In order to connect via RequireJS, add a jQuery and the widget into dependencies. Then call the widget using the jQuery object:

require(
    [
        'jquery',
        'jquery/validate'
    ], function ($) {
        $.validator.addMethod("my-email", function(value, element) {
          return this.optional(element) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@(?:\S{1,63})$/.test(value);
        }, 'Please enter a valid email address.');
    }
);
How are Magento jQuery widgets executed with data-mage-init and text/x-magento-init?

To simplify the process of working with widgets in Magento, it is possible to initiate them. There are two alternative ways of initializing widgets:

  1. data-mage-init = ”…” – attribute, on the basis of which the widget will be initialized

In the example, the tabs widget is initialized.

<div class = "product data items" data-mage-init = '{"tabs": {"openedState": "active"}}'>
...
</ div>
  1. script [type = “text/x-magento-init”] – a script tag that contains the widgets initialization data.

In the example, the Magento_SendFriend/js/back-event widget is initialized for all tags “a” with the role = back attribute.

<script type="text/x-magento-init">
    {
        "a[role='back']": {
            "Magento_SendFriend/js/back-event": {}
        }
    }
</script>

2.5 Demonstrate ability to customize JavaScript modules

Describe advantages and limitations of using mixins

Magento 2 provides the functionality that allows to listen to any RequireJS module after it was initialized and alter or moderate it immediately after initialization and before it is returned. This allows to redefine methods and add new parameters to JavaScript objects, not altering its original code.

app/code/Vendor/Module/view/frontend/requirejs-config.js

var config = {
    'config':{
        'mixins': {
            'Magento_Customer/js/view/customer': {
                Vendor_Module/js/customer-mixin:true
            }
        }
    }
};   

app/code/Vendor/Module/view/frontend/web/js/customer-mixin.js

define([], function(){
    'use strict';    
    alert('Mixin is working');
    return function(targetModule){
        targetModule.newCustomProperty = value;
        return targetModule;
    };
});

Afterward, when we load any page that uses Magento_Customer/js/view/customer RequireJS module, we will see our alers, which means the code was called. Also, when we look at the Magento_Customer/js/view/customer module code, there will be a new parameter newCustomProperty.

mixins configuration key in requirejs-config.js file is a new parameter that Magento added into RequireJS system. Syntaxis:

   'mixins': {
            'REQUIREJS_MODULE': {
                MIXIN_MODULE:true
            }
        }

MIXIN_MODULE is a common RequireJS module that will return the function via a single parameter (targetModule), and this function is called after the original REQUIREJS_MODULE, which is then passed to mixin function. What is returned MIXIN_MODULE will be considered as REQUIREJS_MODULE.

This way, we can add methods and parameters to RequireJS modules without altering the code directly.

Method override:

app/code/Vendor/Module/view/frontend/web/js/customer-mixin.js

define([], function(){
    'use strict';    
    alert('Mixin is working');
    return function(targetModule){
        targetModule.defaultMethod = function(){
            //custom replacement code
        }

        return targetModule;
    };
});

In case the module returns uiClass-based module, we can use the extend method:

app/code/Vendor/Module/view/frontend/web/js/customer-mixin.js

define([], function(){
    'use strict';    
    alert('Mixin is working');
    return function(targetModule){
       return targetModule.extend({
            defaultMethod:function()
            {
                var result = this._super(); //call parent method
                //custom code
                return result;
            }
        });
    };
});

The disadvantages of this approach manifest themselves only when mixins are used by several developers. In case several mixins are available for one and the same method, one of them will overwrite the results of the rest. Use mage/utils/wrapper module to sidestep these constraints. The functionality of this module is very close to Magento 2 around plugins and is used in the following way:

app/code/Vendor/Module/view/frontend/web/js/customer-mixin.js

define(['mage/utils/wrapper'], function(){
    'use strict';    
    alert('Mixin is working');
    return function(targetModule){
        var customFunction = targetModule.defaultFunction;
        var customFunction = wrapper.wrap(customFunction, function(original){
            //custom code
            var result = original(); //original method call
            //custom code            
            return result;
        });

        targetModule.defaultFunction = customFunction ;
        return targetModule;
    };
});

The mage/utils/wrapper.wrap method receives two arguments – the original method we must change and the function we will change it for. The original parameter is the reference of the method, needed to be changed. Wrap method returns the function that will, in its turn, call our method. The original method can be called optionally.

What are cases where mixins cannot be used?

Mixins can not be used in case JS code was called without RequireJS.

How can a new method be added to a jQuery widget?

To add a new method to a jQuery widget, use mixin:

app/code/Vendor/Module/view/frontend/requirejs-config.js

var config = {
    "config": {
        "mixins": {
            "mage/dropdown": {
                Vendor_Module/js/dropdown-mixin':true
            }
        }
    }
};

app/code/Vendor/Module/view/frontend/web/js/dropdown-mixin.js

define(['jquery'], function($){
    return function(originalWidget){
        $.widget('mage.dropdownDialog', $['mage']['dropdownDialog'], {
	    //custom methods
                customMedhot:function(){                    
                    //custom code           
                }
            });                                

        return $['mage']['dropdownDialog'];
    };
});

How can an existing method of a jQuery widget be overridden?

You can also apply mixins to override an existing method of a jQuery widget.

app/code/Vendor/Module/view/frontend/requirejs-config.js

var config = {
    "config": {
        "mixins": {
            "mage/dropdown": {
                Vendor_Module/js/dropdown-mixin':true
            }
        }
    }
};

app/code/Vendor/Module/view/frontend/web/js/dropdown-mixin.js

define(['jquery'], function($){
    return function(originalWidget){
        $.widget('mage.dropdownDialog', $['mage']['dropdownDialog'], {
	    //overriden methods
                open:function(){                    
                    //custom code
                    //parent method call
                    return this._super();              
                }
            });                                

        return $['mage']['dropdownDialog'];
    };
});

What is the difference in approach to customizing jQuery widgets compared to other Magento JavaScript module types?

jQuery widgets by default contain the features that allow to override and modify the widget. All we need to do is to override the widget:

//original definition
jQuery.widget('widgetNamespace.methodName', {/* ... initial method definitions ... */});

/extending widget
jQuery.widget('widgetNamespace.methodName', jquery.widgetNamespace.methodName, 
    {/*... new method definitions here ...*/});

This will work until the widget is not called. The problem is that Magento allows to use widgets with the help of data-mage-init and x-magento-init constructions. The widgets allow to initialize inline widget, which leads to:

  1. Call of the corresponding RequireJS module
  2. RequireJS module defines the widget (jQuery.define)
  3. RequireJS module returns the newly created widget
  4. Magento core applies the newly created widget

The widget is defined and immediately used in the corresponding selector, which makes it hard to redefine or modify.

When extending our widgets, one must bear in mind that our mixin must not only extend the widget with jQuery.widget, but also return the widget instance so that it could be immediately used by x-magento-init and data-mage-init constructions.

pm-tatiana
Tatiana-Buchkova

Partner With Us

Let us know more about your project. Contact us and let’s grow your business.

Get started