Calling and initializing JS components might seem a bit challenging at first. There are two types of syntax notations used with Magento JS components:
- Declarative:
- Using the data-mage-init attribute
- Using the <script type="text/x-magento-init" /> tag
- Imperative:
- Using the <script> tag, without the type="text/x-magento-init" attribute
To better understand the data-mage-init notation, let's take a look at a partial <PROJECT_DIR>/lib/web/mage/redirect-url.js file extract:
define([
'jquery',
'jquery/ui'
], function ($) {
'use strict';
$.widget('mage.redirectUrl', {
options: {
event: 'click',
url: undefined
},
_bind: function () { /* ... */ },
_create: function () { /* ... */ },
_onEvent: function () { /* ... */ }
});
return $.mage.redirectUrl;
});
This here is a jQuery widget wrapped as an AMD module; more on that later on. data-mage-init knows how to interpret mage.redirectUrl as a redirectUrl component. By studying the redirectUrl widget code, we can see it can be used not only with the button and the link type of elements but with the select type as well. Let's go ahead and append our playground.phtml file with the following:
<a data-mage-init='{"redirectUrl":{"url":"http://test.url"}}'>
<span><?= __('Test') ?></span>
</a>
<button type="button"
data-mage-init='{"redirectUrl":{"url":"http://test.url"}}'>
<span><?= __('Test') ?></span>
</button>
<select data-mage-init='{"redirectUrl": {"event":"change"}}'>
<option value="http://test.url/1">Test#1</option>
<option value="http://test.url/2">Test#2</option>
<option value="http://test.url/3">Test#3</option>
</select>
While the click event works perfectly for link and button elements, the select element relies on a more specific change event. Therefore, our select element exploits the fact that the redirectUrl component accepts the event configuration option. This makes for a nice and clean little example of reusing a single component multiple time.
To better understand the <script type="text/x-magento-init" /> notation, let's take a look at a partial <MAGENTO_DIR>/module-cookie/view/frontend/web/js/notices.js file extract:
define([
'jquery',
'jquery/ui',
'mage/cookies'
], function ($) {
'use strict';
$.widget('mage.cookieNotices', {
_create: function () {
//...
}
});
return $.mage.cookieNotices;
});
Just like in our first example, this is just another jQuery widget essentially. What the cookieNotices widget does is take the given content and display it as cookie notice alert to the user, doing so until the user finally hits the Allow Cookies button. We can easily reuse this widget to inject our own content. While both cookieNotices and redirectUrl are jQuery widgets, the way they are used in Magento differs.
Let's go ahead and append our playground.phtml file with the following HTML bits:
<div id="playgroundCookieBlock" class="message global cookie"
style="display: none;">
<p>
<strong><?= $block->escapeHtml(__('We use cookies to make your experience better.')) ?></strong>
<span><?= $block->escapeHtml(__('To comply with the new e-Privacy directive, we need to ask for your consent to set the cookies.')) ?></span>
<?= $block->escapeHtml(__('<a href="%1">Learn more</a>.', 'http://magelicious.loc/privacy'), ['a']) ?>
</p>
<div class="actions">
<button id="btn-cookie-allow" class="action allow primary">
<span><?= $block->escapeHtml(__('Allow Cookies')) ?></span>
</button>
</div>
</div>
This is to simulate our intent for a custom cookie widget, with special content and a cookie name. Let's further append the playground.phtml file with a declarative call to cookieNotices JS component:
<script type="text/x-magento-init">
{
"#playgroundCookieBlock": {
"cookieNotices": {
"cookieAllowButtonSelector": "#btn-cookie-allow",
"cookieName": "playgroundCookie",
"cookieValue": "playgroundCookieValue",
"cookieLifetime": "300",
"noCookiesUrl": "http://magelicious.loc/no-cookies"
}
}
}
</script>
Unlike the redirectUrl widget, which had a nice list of options defined at the very start of the widget definition, the cookieNotices widget does not have those. It merely references those options throughout the code, via this.options.<optionPushedViaMagentoInit> calls. This is really a default jQuery widget options object. The reason we are bringing it up is merely to understand how, most of the time, one needs to take a more involved approach toward inspecting existing JavaScript components code, instead of just focusing on the set of possible default options.
To better understand the <script> tag notation, let's take a look at a partial <MAGENTO_DIR>/module-ui/view/base/web/js/modal/modal.js file extract:
define([
/* ... */
], function ( /* ... */ ) {
'use strict';
//...
$.widget('mage.modal', {
//...
});
return $.mage.modal;
});
As in the previous two examples, this again is just a jQuery widget. Now let's go ahead and append our playground.phtml file with the following HTML bits:
<div>
<a href="#" id="playgroundModalLink">Show modal!</a>
</div>
<div id="playgroundModal">
<p>Content...</p>
</div>
This is to simulate our intent of creating a modal box, with special content. Now, let's use the modal widget to turn this into an actual modal. We further append our playground.phtml file, as follows:
<script>
require([
'jquery',
'mage/translate',
'Magento_Ui/js/modal/modal'
], function ($, $t, modal) {
var options = {
title: 'Playground Modal',
buttons: [{
text: $t('Continue'),
click: function () {
this.closeModal();
}
}]
};
modal(options, $('#playgroundModal'));
$('#playgroundModalLink').on('click', function () {
$('#playgroundModal').modal('openModal');
});
}
);
</script>
This time we are using the <script> tag approach to utilize the JS component.
To ensure our code evaluates on page load, we can further wrap our modal widget related code into a function, as follows:
<script>
require([
/* libraries ... */
], function ( /* params ... */ ) {
$(function () {
// Raw JS code...
});
}
);
</script>
Likewise, we can use a RequireJS domReady module to execute our JS code on DOM:
<script>
require([
'jquery',
'mage/translate',
'domReady!'
], function ($, $t) {
// Raw JS code...
});
</script>
The ! character used in domReady! is a syntax reserved for plugins. While there is more to it, suffice to say that in a case of domReady! the plugin exists simply as a way of waiting until DOM gets loaded before invoking our function.
The choice of calling and initializing JS components depends on how they are written and how they are intended to be used. We use the declarative notation when our component requires initialization. The configuration is prepared on the backend and simply outputted to the page. We use the imperative notation on the pages that use raw JS code; this allows us to execute particular business logic.