Categories


Archives


Recent Posts


Categories


Magento 2: ES6 Template Literals

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Today we need to take a small detour into ES6 template literals. Template literals are a newish javascript feature that Magento 2’s UI Component system ends up leaning on for some key pieces of functionality. They’re an important concept to understand by themselves, but it’s even more important to understand the extra abstractions Magento have built up on top of template literals, as well as how Magento’s uiClass object system has these template literals baked into its bones.

Also, before we get started, a big thanks to “bassplayer7” over on the Magento StackExchange. This answer provided a key piece of information that helped me finish up this article.

ES6 Template Literals

For many of you, your first question is likely – what the heck is ES6?!

The ES is short for ECMAScript. The ECMA is short for (Ecma_International)[https://en.wikipedia.org/wiki/Ecma_International], the body that standardized the various javascript/jscript/actionscript programming languages into one unified standard.

ECMAScript 6 is the latest version of this standard. Browser makers (Apple, Google, Firefox) are implementing features from ES6 over time (similar to how HTML5 rolled out).

One of those features is something called template literals. Template literals provide javascript with a simple, built-in template language. We’re going to use the javascript console in Google Chrome to run through some template literal code samples, but you should be able to use these in any javascript enviornment that supports them.

In their simplest form, template literals are almost indistinguishable from strings. Consider the Hello World text below.

> results = `Hello World`
> console.log(results);

Hello World

Notice we’ve placed the text “Hello World” between backticks (the ` character). By placing the text between backticks, we’re telling javascript this string is a template literal.

So far, there’s not much difference between a string and a template literal. Template literals are (from a userland perspective) immediately rendered as strings. Consider the following.

> var results = `Hello World`
> var type    = typeof results;
> console.log(type)

string

To javascript, the results variable looks like a string. Given what we’ve seen so far, template literals seem useless.

Of course, we wouldn’t be telling you about them if they were useless! Template literals support template variables/placeholders. Consider the following

> var salutation = "Goodbye"
> var results = `${salutation} World`
> console.log(results);

Above, ${salutation} is a template literal variable (or placeholder). These variables read from javascript’s current scope. So, javascript will render the template variable ${salutation} as the word Goodbye. Javascript will do this because we assigned the string Goodbye to the global variable salutation.

While covering template literal’s functionality in full is beyond the scope of this article, The Mozilla developer network has more information on template literals, including advanced features like “tag” functions.

Browser Support and Magento 2

Like any new javascript feature, a client side developer needs to be careful before adopting template literals. Template literals are not supported across all browsers currently in use, and trying to use them in an old browser will likely result in javascript errors that crash your application.

Magento 2’s developers worked around this limitation by creating a RequireJS module for rendering template literals. This module’s identifier is mage/utils/template, and you can use it like this.

//requires an enviornment bootstrapped with Magento 2
//javascript.  i.e. open you debugger on a Magento page

> requirejs(['mage/utils/template'], function(templateRenderer){
    window.salutation      = 'Brave New';
    var templateLiteral = '${salutation} World';    
    var results         = templateRenderer.template(templateLiteral);
    console.log(results);
}); 

Brave New World

Behind the scenes, the mage/utils/template module will check for template literal support. If this support is there, the module uses the native browser implementation. If support’s not there, the module will use a (slower) pure userland javascript implementation. I believe the kids call this sort of abstraction a polyfill.

While this module is useful, there are a few caveats. First, you’ll notice we needed to assign the salutation variable to the global namespace (“window” in browser javascript).

window.salutation      = 'Brave New';
var templateLiteral = '${salutation} World';  

Native template literals will read from the current functional scope, but Magento 2’s module (or any userland code) doesn’t have automatic access to that scope. Therefore, the template literals can only read from global scope. The following code

> requirejs(['mage/utils/template'], function(templateRenderer){
        var salutation      = 'Brave New';
        var templateLiteral = '${salutation} World';    
        var results         = templateRenderer.template(templateLiteral);
        console.log(results);
    }); 

results in the following javascript error (unless, of course, you have a global variable named salutation defined)

VM1627:1 Uncaught ReferenceError: salutation is not defined

The second mage/utils/template caveat, and likely a direct result of the above scope problem, is Magento 2’s template literals have extra abilities that go above and beyond those defined in the standard.

Binding View Variables to a Template Literal

Magento 2’s template literals allow you to bind a specific object to a specific template literal, and then reference those variables with a special syntax. The syntax for binding the object looks like this.

> requirejs(['mage/utils/template'], function(templateRenderer){
        var viewVars        = {
            'salutation':'What a Crazy'
        };   

        var templateLiteral = '${salutation} World';
        var results         = templateRenderer.template(templateLiteral, viewVars);
        console.log(results);
    }); 

That is, the template method has a second parameter that accepts an object.

templateRenderer.template(templateLiteral, viewVars);

However, the above program still results in an error. That’s because of the aforementioned special syntax. You need to reference this object with a second $ symbol inside a ${} variable/placeholder. In other words, this

var templateLiteral = '${salutation} World';

needs to be this

var templateLiteral = '${$.placeholder} World';

It’s a slightly awkward syntax, but easy enough to mentally parse once you understand that $. is just a place holder for your passed in object. If you try running this corrected program, you should see the correct What a Crazy World output.

> requirejs(['mage/utils/template'], function(templateRenderer){
        var viewVars        = {
            'salutation':'What a Crazy'
        };   

        var templateLiteral = '${$.salutation} World';
        var results         = templateRenderer.template(templateLiteral, viewVars);
        console.log(results);
    }); 

What a Crazy World

Connection to UI Components

You may be wondering why we’re covering template literals in a series focused on Magento 2’s UI Components. It turns out template literals are baked into the view model constructor object system we’ve discussed in previous articles.

As we know, the Knockout.js view model constructor objects Magento uses with its UI Components are based on the uiElement/Magento_Ui/js/lib/core/element/element RequireJS module. We’re going to take a step away from Knockout and try using this object system via pure javascript code to better understand it. Here’s a simple example that creates a new view model constructor, and uses that constructor to create a view model.

> requirejs(['uiElement'], function(Element){
    viewModelConstructor = Element.extend({});
    viewModel = new viewModelConstructor;
    console.log(viewModel);
});

UiClass {_super: undefined, ignoreTmpls: Object, ...}

One of the features of this object system (implemented in the uiClass/Magento_Ui/js/lib/core/class module that uiElement extends from) is the ability to have your instantiated object include “default” values. If you provide a defaults object for your view model constructor

viewModelConstructor = Element.extend({
    'defaults':{
        'ourDefaultValue':'Look at our value!'
    }
});

then any object instantiated from this constructor will have a default value for its ourDefaultValue property.

> requirejs(['uiElement'], function(Element){
    viewModelConstructor = Element.extend({
        'defaults':{
            'ourDefaultValue':'Look at our value!'
        }
    });
    viewModel = new viewModelConstructor;
    console.log(viewModel.ourDefaultValue);
});

Look at our value!

It turns out Magento’s object system will scan any default string value for a template literal, and automatically render those template literals. Consider the following program.

> requirejs(['uiElement'], function(Element){
    window.salutation = 'Hello';
    viewModelConstructor = Element.extend({
        'defaults':{
            'message':'${salutation} World. ',
            'salutation':'Goodbye'
        }
    });
    viewModel = new viewModelConstructor;
    console.log(viewModel.message);
});

Hello World. 

Here we’ve provided the view model constructor with a default message

'defaults':{
    'message':'${salutation} World. '
}

and Magento 2’s javascript objects automatically expand that literal in the instantiated viewModel object, using the globally set salutation variable.

Hello World.

These defaults template literals can also take advantage of Magento’s “bound object” feature. The $. object accessor will read from any other defaults variable.

requirejs(['uiElement'], function(Element){
    viewModelConstructor = Element.extend({
        'defaults':{
            'message':'${$.salutation} World. ',
            'salutation':'Goodbye'
        }
    });
    viewModel = new viewModelConstructor({
        'salutation':'This is still a crazy'
    });
    console.log(viewModel.message);
});

As well as an object passed to the view model constructor.

requirejs(['uiElement'], function(Element){
    viewModelConstructor = Element.extend({
        'defaults':{
            'message':'${$.salutation} World. ',
            'salutation':'Goodbye'
        }
    });
    viewModel = new viewModelConstructor({
        'salutation':'This is still a crazy'
    });
    console.log(viewModel.message);
});

As you can see from our final program, the values passed to the constructor have precedence over those passed in as defaults. i.e. The This is still a crazy salutation wins out over the Goodbye salutation.

Wrap Up

Today’s tutorial is a good example of the challenges facing a developer who wants to adopt Magento 2 as a platform. Not only are there bleeding edge javascript concepts to pickup, but you also need to understand how Magento has extended these concepts in non-standard ways (binding view variables), and also understand how Magento has composed these objects into their systems (the uiClass object system). Without the concepts above, a developer who encounters code like this

return Element.extend({
    defaults: {
        clientConfig: {
            urls: {
                save: '${ $.submit_url }',
                beforeSave: '${ $.validate_url }'
            }
        }
    },

will be left scratching their head.

Fortunately, we now have Magento’s ES6-like template literals in our tool-belt. We’re one step closer to being able to explore how Magento gets backend data into a view model constructor’s defaults array, as well as how Magento handles the UI Component generated data sources.

Originally published October 1, 2016
Series Navigation<< Magento 2: Simplest XSD Valid UI ComponentMagento 2: uiClass Data Features >>