Initialising a Component
Magento starts JavaScript components from markup with data-mage-init or x-magento-init, and both hand the component a JSON config.
<div data-mage-init='{"Vendor_Notice/js/stock-notice": {"threshold": 5}}'></div>An AMD module does nothing on its own. Something has to name it and hand it configuration. Magento does this from markup, in one of two forms, and both end up in the same bootstrap code.
data-mage-init
An attribute on the element the component should act on. Its value is a JSON object whose keys are component names and whose values are that component's configuration. On DOM ready Magento parses the attribute, removes it from the element, and calls each component.
x-magento-init
A script tag with type="text/x-magento-init". Its JSON maps a CSS selector to components. If the selector matches several elements the component runs once per element. The selector * means no element at all: the component runs once, and receives false where the element would be. Use this form when you cannot add an attribute to the element, or when there is no element.
What the component receives
If the module returns a plain function, Magento calls it as fn(config, element). If the component name resolves to a jQuery plugin on that element, it is called as $(element).name(config) instead. Either way config is the decoded JSON object.
Getting values out of PHP
Build a PHP array, pass it through json_encode, and print it. For an attribute use $escaper->escapeHtmlAttr() around the encoded string. Templates are compiled into var/view_preprocessed and cached, so after editing one run bin/magento cache:flush.
Example
<?php
// app/code/Vendor/Notice/view/frontend/templates/notice.phtml
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Vendor\Notice\ViewModel\NoticeConfig $viewModel */
$viewModel = $block->getData('view_model');
$initConfig = [
'Vendor_Notice/js/stock-notice' => [
'threshold' => $viewModel->getThreshold(),
'qty' => $viewModel->getSalableQty(),
],
];
<div class="stock-notice"
data-role="stock-message"
data-mage-init=' $escaper->escapeHtmlAttr(json_encode($initConfig)) '>
</div>
<script type="text/x-magento-init">
{
"*": {
"Vendor_Notice/js/page-ready": {
"locale": " $escaper->escapeJs($viewModel->getLocale()) "
}
}
}
</script>
// What the reader sees: on the page that renders this template, the div
// appears with its data-mage-init attribute already gone, because Magento
// removes it after parsing. The network panel shows stock-notice.js and
// page-ready.js fetched, and the div's text is replaced.
// Apply a template edit with: bin/magento cache:flush
When to use it
- Pass a store configuration value or a URL from a view model into a storefront script.
- Start a core widget such as collapsible on markup produced by another module's template.
- Run a script once per page, with no element to attach it to, using the * selector.
- Diagnose a component that never runs by checking the console for a JSON parse error.
More examples
Two components on one selector
One selector can start several components, and they are independent. This is the form to use when the element is rendered by a core template you do not want to override, since the script tag can sit anywhere on the page.
<?php
// app/code/Vendor/Notice/view/frontend/templates/panel.phtml
/** @var \Magento\Framework\Escaper $escaper */
?>
<div id="notice-panel" data-role="title">...</div>
<script type="text/x-magento-init">
{
"#notice-panel": {
"collapsible": {
"active": false,
"content": "[data-role=content]"
},
"Vendor_Notice/js/panel-tracker": {
"eventName": "<?= $escaper->escapeJs('notice_panel_open') ?>"
}
}
}
</script>A component that handles both the element and the * case
Worth writing defensively when a component may be started both ways. With the '*' selector the second argument is the boolean false, not a node, so $(element) would silently produce an empty jQuery object and the component would appear to do nothing.
// app/code/Vendor/Notice/view/frontend/web/js/page-ready.js
define(['jquery'], function ($) {
'use strict';
/**
* @param {Object} config - the decoded JSON from the init markup
* @param {HTMLElement|Boolean} element - false when the selector was '*'
*/
return function (config, element) {
var scope = element ? $(element) : $(document.body);
scope.attr('data-locale', config.locale);
};
});Shorthand and longhand for the same initialisation
Recognising both forms matters when reading core templates, which use each in different places. The script tag form is also how you add configuration to an element whose template belongs to another module.
<?php
// app/code/Vendor/Notice/view/frontend/templates/equivalent.phtml
?>
<!-- Shorthand: the attribute sits on the element itself. -->
<div id="faq" data-mage-init='{"collapsible": {"active": false}}'>...</div>
<!-- Longhand: the same component and config, addressed by selector.
x-magento-init merges its config into the element's data-mage-init
attribute first, then the same bootstrap code runs. The two are the
same thing; the second works when you cannot touch the element. -->
<div id="faq">...</div>
<script type="text/x-magento-init">
{
"#faq": {
"collapsible": {"active": false}
}
}
</script>
Discussion