Extending Magento 2 default JS components

extending-magento2-nav-featured

Since Magento 2 is out for quite some time now and new projects based on the platform are rapidly approaching, there is no doubt that we, as developers, need to be adequately prepared for the challenge.

In this post I will demonstrate how to deal with platform’s default javascript components (widget instances). More precisely, a javascript component responsible for the site main navigation functioning. If you’re interested, just keep reading.

Introduction

Frontend part of the main navigation in Magento 2 is partly constructed with javascript logic responsible for the numerous actions spanning from transformations for mobile devices to hover delay timing for desktop computers. In most cases, default approach will suffice and will cover basically all requirements, but sometimes, in cases when a project requires specific/custom approach, sooner or later we’ll find ourselves in a situation to modify the default logic.

Let’s put ourselves in a position where our project requires certain modifications to the main navigation javascript part. First things first, we need to know some basic file architecture and technologies involved in the process.

Magento 2 uses Require JS which is AMD or asynchronous module loader and jQuery/jQuery UI library as a base for creating javascript components currently present on the system.

I won’t go further into requireJS but if you need to learn more about it, you can check out our blog post covering this topic in more details. Also, I would strongly suggest visiting requireJS site to get solid fundamentals of AMD and requireJS basic usage prior working with Magento 2.

Another important note is that jQuery UI widget factory is used to deliver easy extendable techniques, quite similar to old prototype class approach in Magento 1. If you’re not familiar with jQuery UI widgets, I strongly suggest visiting learn jQuery site with widget factory examples to get familiar with the concept.

Extending the defaults

Main file responsible for the functioning of the navigation menu is menu.js located under [project_root]/lib/web/mage/ among other default js system components.

Now, if we wan’t to extend or overwrite it, we’ll need to make sure we’re following these steps.

Step 1

To properly extend parts of menu.js with our custom content, first step is to map our js file so the system loads it instead of the default file.
Magento uses requirejs-config.js files to successfully map js components on the system. First thing that matters is to know where to place requirejs-config.js. It can be placed on several levels.

All requireJS configurations will be merged and executed in the following order:

    • module level
    • theme module level (parent theme)
    • theme module level (current theme)
    • theme level (parent theme)
    • theme level (current theme)

Let’s see how it works with one real example. Default file is menu.js and we want to replace it with our custom one. Pay attention that I will still load default menu.js file afterwards as a dependency.

We need to create our file that will replace the menu.js file. Let’s call it menu-custom.js and place it under [current_theme]/web/js/ directory.

Step 2

Next, we need to create requirejs-config.js file and place it under [current_theme]/root directory. That way we can successfully map our file to replace the default one. See the example below:

var config = {
    "map": {
        "*": {
            "menu": "js/menu-custom"
        }
 
    }
};

To be really sure if the procedure was a success, make sure to check in developer tools if the new file is loaded.

Loaded js component

Step 3

Now the fun part! We wan’t to extend default functionality and this is where jQuery widget factory comes into play. We will place the following code to our newly created menu-custom.js file.

Check the example below:

define([
    'jquery',
    'jquery/ui',
    'mage/menu'],
    function($){
        $.widget('inchoo.menu', $.mage.menu, {
            _init: function () {
                alert("I'm Inchoo");
            },
            toggle: function () {
                alert("I'm Inchoo");
            }
    });
    return $.inchoo.menu;
    });

What we can see from the above example is that we’re using require js to define our dependencies. First one is jquery, second is jquery/ui and the last one is mage/menu.
This means that our script will not be loaded until those 3 are fully loaded, because our logic depends on it.

Our custom widget instance called inchoo.menu is extending default $.mage.menu and in this example for the sake of this tutorial I’m extending two methods, _init and toggle. Toggle method is in charge to toggle navigation on smart phones and tablets while _init method is in charge for the component initialization.

Original toggle method example from default menu.js

toggle: function () {
            if ($('html').hasClass('nav-open')) {
                $('html').removeClass('nav-open');
                setTimeout(function () {
                    $('html').removeClass('nav-before-open');
                }, 300);
            } else {
                $('html').addClass('nav-before-open');
                setTimeout(function () {
                    $('html').addClass('nav-open');
                }, 42);
            }
        },

Overriding the file

In some cases (although in relatively rare ones) you will find your self in a position to completely override the menu logic (create custom navigation). In that specific case, we can apply step 1 and step 2 and create custom navigation logic in menu-custom.js file.

And that’s it! We’ve successfully extended default menu.js widget instance. Procedure can be applied to any widget instance currently presented on the system.

Hopefully this article will help someone looking to achieve the same goal.

Happy coding.


About Filip Svetlicic

Project Manager/Frontend Developer

Filip is Project Manager/Frontend Developer who enjoys working on complex and innovative projects where he can combine his energy, motivation and expertise.

Read more posts by Filip / Visit Filip's profile

4 comments

  1. Is there a way to extend a knockout model-view JS component ?
    This exemple work only for widgets because you still can refer to them through jquery ($) after you made you requireJs mapping.

  2. Not working for me, I followed the steps but the menu-custom file is not loaded/not showing in developer tools.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.