Categories


Archives


Recent Posts


Categories


Magento 2: Adding Frontend Assets via Layout XML

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!

As promised, today we’re going to show you how to add javascript and CSS files to your Magento module without needing to worry about how their paths are rendered or how Magento serves them. While you may find this article useful on its own, we’re going to assume you’ve worked through the previous two.

Before we get to the main course, we’re going to provide an engineering critique of Magento 2’s XML based layout rendering language. This language is similar to the XML based language in Magento 1, but has some differences that might trip up an experienced Magento 1 developer.

The next few sections are optional, and not recommended unless you’re interested in the implementation details of Magento 2. You can skip ahead to the Adding CSS and Javascript to a Page section if all you’re interested in is getting your javascript and CSS files included on a page.

Magento 2’s Domain Specific Language for Rendering HTML

With Magento 2, the core team has added a number of features to their XML based layout language. They’ve also changed some of the language’s semantics. For example, in Magento 1, each node under the root node of a layout update XML file was always a layout handle. For example, in the Magento 1 Layout Update XML Node below, the handle is catalog_category_view.

<catalog_category_view>
    <block ...>
        <!-- ... -->
    </block>
</catalog_category_view>

Without getting too deeply into it, handles control which layout nodes are applied to a page during which request. Magento 2 still has handles, but they no longer appear in layout files. Instead, the handle is the file name. For example, the main catalog_category_view layout handle XML file is at

./vendor/magento/module-catalog/view/frontend/layout/catalog_category_view.xml

Other modules that listen for the catalog_category_view handle include

./vendor/magento/module-checkout/view/frontend/layout/catalog_category_view.xml
./vendor/magento/module-directory/view/frontend/layout/catalog_category_view.xml
./vendor/magento/module-google-optimizer/view/frontend/layout/catalog_category_view.xml
./vendor/magento/module-msrp/view/frontend/layout/catalog_category_view.xml
./vendor/magento/module-paypal/view/frontend/layout/catalog_category_view.xml
./vendor/magento/module-swatches/view/frontend/layout/catalog_category_view.xml
./vendor/magento/module-wishlist/view/frontend/layout/catalog_category_view.xml

In all the examples, the file name is the handle name. While the mechanism has changed, handles still serve the same purpose in Magento 2. On every Magento 2 HTML page render, certain handles “fire”, similar to events. The handles that fire control which layout files are loaded (in Magento 1 they controlled which XML nodes were read from all the files), and then Magento processes the combined XML tree to know which blocks it should add to a page.

Put another way, in Magento 1 you would

  1. Configure your module to load a layout update XML file (namespace_module.xml, catalog.xml etc.)
  2. In the layout update XML file you’d add a node for your handle (catalog_category_view)
  3. Under the catalog_category_view node you’d add your blocks

In Magento 2, you

  1. Add a layout handle XML file (catalog_category_view.xml)
  2. Under the root node of your layout handle XML file, add your blocks

We covered this change briefly in our Introduction to Magento 2 — No More MVC article. However, we glossed over a number of substantial changes to Magento’s layout language that we’ll need to touch briefly on before we continue.

New Nodes

In our Introduction to Magento 2 — No More MVC article, we created the following node in the layout handle XML file.

<!-- File: app/code/Pulsestorm/HelloWorldMVVM/view/frontend/layout/hello_mvvm_hello_world.xml -->
<referenceBlock name="content">
    <block
        template="content.phtml"
        class="Pulsestorm\HelloWorldMVVM\Block\Main"
        name="pulsestorm_helloworld_mvvm"/>
</referenceBlock>

As a reminder, this XML is roughly equivalent to the following pseudo code.

//pseudo code -- does not work
$our_view_block = 
    $layout->createNewBlockWithClass('Pulsestorm\HelloWorldMVVM\Block\Main')
$our_view_block->setName('pulsestorm_helloworld_mvvm');
$out_view_block->setTemplate('content.phtml');
$layout->addBlockToContentContainer($our_view_block);

The <block/> tag from Magento 1 remains relatively unchanged. It means Create a block object. The main difference is the type attribute has been replaced with a class attribute. Since Magento did away with class aliases (core/template, namespace_module/my_block, etc.) it made sense to do away with the type attribute, and more accurately label it as class.

The first small change above is the referenceBlock node. Magento 1 had the concept of a block reference. However, the node was named <reference/>. In Magento 1, the above might look like

<reference name="content">
    <block
        template="content.phtml"
        class="Pulsestorm\HelloWorldMVVM\Block\Main"
        name="pulsestorm_helloworld_mvvm"/>
</block>

The referenceBlock node makes things more explicit, and is another welcome change. This might seem superficial, until you realize that Magento 2’s layout language controls more than blocks. The layout language also controls something called containers, and has a corresponding referenceContainer block. You can see an example of the referenceContainer block here

<!-- vendor/magento/module-checkout/view/frontend/layout/checkout_shipping_price_renderer.xml -->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <referenceContainer name="root">
        <block class="Magento\Checkout\Block\Shipping\Price" name="checkout.shipping.price" as="shipping.price" template="shipping/price.phtml"/>
    </referenceContainer>
</layout>

In Magento 2, a container is a special sort of block that only contains other blocks. Containers are conceptually similar to the text/list blocks in Magento 1, although their implementation is very different.

The concept of containers is a good one, but it’s here that the implementation starts to get a little wobbly. Magento’s layout language is a little loosey–goosey with the difference between a container and a block. For example, the above XML?

<referenceContainer name="root">

This could also be written as

<referenceBlock name="root">

That is — even though root is a container, referenceBlock will still return a reference to it, and allow you to add blocks to it. For a change meant to make things more explicit and clear, it’s a little strange that the layout language would let something like that happen.

White Lies

Remember this XML from the introduction article?

<!-- File: app/code/Pulsestorm/HelloWorldMVVM/view/frontend/layout/hello_mvvm_hello_world.xml -->
<referenceBlock name="content">
    <block
        template="content.phtml"
        class="Pulsestorm\HelloWorldMVVM\Block\Main"
        name="pulsestorm_helloworld_mvvm"/>
</referenceBlock>

Well, it turns out that the content block is actually a container. The above should have been written as

<!-- File: app/code/Pulsestorm/HelloWorldMVVM/view/frontend/layout/hello_mvvm_hello_world.xml -->
<referenceContainer name="content">
    <block
        template="content.phtml"
        class="Pulsestorm\HelloWorldMVVM\Block\Main"
        name="pulsestorm_helloworld_mvvm"/>
</referenceContainer>

We used referenceBlock in our introduction tutorial because we weren’t ready to discuss containers and other changes to the layout system. While this was useful for a transitional tutorial, generally speaking this is the sort of looseness that can make a domain specific language seem extra confusing.

Without getting too deeply into the details, you can tell if a “block” is a container or a regular block by how the original programmer created it. If you see a <block/> tag

<block name="foo" />

then the named block (“foo” above) is a regular block. If you see a <container/> tag

<container name="foo"/>

then the named entity is a container. If you’re curious, Magento’s core code adds the content container in the following file

<!-- vendor/magento/module-theme/view/frontend/layout/default.xml -->
<container name="content" label="Main Content Area"/>

Also, notice the default.xml file name? That’s equivalent to Magento 1’s <default/> handle node.

Context Sensitive Nodes

In Magento 1, the layout language was a system designed to render arbitrary HTML via a nested collection of block objects. The layout system itself didn’t care which part of an HTML document it was rendering. It just rendered blocks. Specific blocks, like page/html_head, could introduce that context, but it happened at the block level. The layout system itself was unaware that it was rendering the <head/> portion of a document.

In Magento 2, the core team attempted to change this, and add that context in at the language level. They added two new top level tags named <body> and <head> to the vocabulary of the language. While it was an interesting experiment, the implementation feels half done, and further complicates an already complicated layout system. Consider the following

<!-- File: vendor/magento/module-backend/view/adminhtml/layout/default.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <title>Magento Admin</title>
        <meta name="viewport" content="width=1024, initial-scale=1"/>
        <link src="requirejs/require.js"/>
        <css src="extjs/resources/css/ext-all.css"/>
        <css src="extjs/resources/css/ytheme-magento.css"/>
    </head>
    <body>
        <attribute name="id" value="html-body"/>
        <block name="require.js" class="Magento\Backend\Block\Page\RequireJs" template="Magento_Backend::page/js/require_js.phtml"/>
        <referenceContainer name="global.notices">
            <block class="Magento\Backend\Block\Page\Notices" name="global_notices" as="global_notices" template="page/notices.phtml"/>
        </referenceContainer>
        <!-- ... -->
    </body>
</page>

Here you can see an example of a core module layout handle XML file that uses the new head and body sections. The first bit of confusion this introduces is top level tags under the root tag now mean different things. In some files, these top level tags will be context tags like <head/> and <body/> above. In other files, the top level tags will be actual commands/directives (referenceBlock, container, etc) for the layout engine

<!-- File: vendor/magento/module-bundle/view/base/layout/catalog_product_prices.xml -->
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <referenceBlock name="render.product.prices">
    <!-- ... -->
</layout>

If you enjoy implementing domain specific languages, this may seem like a minor thing. However, the intent of a domain specific language is to simplify and constrain the options for developers and programmers unfamiliar with the entire system. The lack of consistency here will make these files harder for designers and front end developers to understand.

The next bit of confusion is in what that context change means. Nodes placed inside the <body/> tag

<!-- File: vendor/magento/module-backend/view/adminhtml/layout/default.xml -->    
<body>
    <attribute name="id" value="html-body"/>
    <block name="require.js" class="Magento\Backend\Block\Page\RequireJs" template="Magento_Backend::page/js/require_js.phtml"/>
    <referenceContainer name="global.notices">
        <block class="Magento\Backend\Block\Page\Notices" name="global_notices" as="global_notices" template="page/notices.phtml"/>
     </referenceContainer>
     <!-- ... -->
</body>

behave very similar to plain old layout XML nodes. You’re still getting references to existing blocks and containers, and adding new blocks to them for rendering. The only difference is the <attribute/> tag you see above. With this you can change the ID element of the underlying <body/> tag.

When you shift into <head/> context, you’re in a difference world.

<!-- File: vendor/magento/module-backend/view/adminhtml/layout/default.xml -->    
<head>
    <title>Magento Admin</title>
    <meta name="viewport" content="width=1024, initial-scale=1"/>
    <link src="requirejs/require.js"/>
    <css src="extjs/resources/css/ext-all.css"/>
    <css src="extjs/resources/css/ytheme-magento.css"/>
</head>

Here, you’ve completely lost the ability to modify the layout with commands like referenceBlock, etc. Instead, you have a narrow set of tags (<attribute/>, <css/>, <link/>, <meta/>, <remove/>, <script/>, <title/>) for doing things specifically in the <head/> of a document.

The other bit of cognitive dissonance a Magento 1 developer will feel here is the <head/> section of the HTML page is no longer rendered like a normal block. If you take a look at the root phtml template you can see a Magento HTML page is no longer a series of nested blocks.

<!-- File: vendor/magento/module-theme/view/base/templates/root.phtml -->
<!doctype html>
<html <?php /* @escapeNotVerified */ echo $htmlAttributes ?>>
    <head <?php /* @escapeNotVerified */ echo $headAttributes ?>>
        <?php /* @escapeNotVerified */ echo $requireJs ?>            <?php /* @escapeNotVerified */ echo $headContent ?>            <?php /* @escapeNotVerified */ echo $headAdditional ?>        </head>
    <body data-container="body" data-mage-init='{"loaderAjax": {}, "loader": { "icon": "<?php /* @escapeNotVerified */ echo $loaderIcon; ?>"}}' <?php /* @escapeNotVerified */ echo $bodyAttributes ?>>
        <?php /* @escapeNotVerified */ echo $layoutContent ?>        </body>
</html>

In Magento 2, an HTML page is a phtml template populated by simple variables. These simple variables are populated by different means in the render method of the Magento\Framework\View\Result\Page object. Magento creates the <body/> tag of the page by echoing out the $layoutContent variable. Magento gets the string for $layoutContent by doing the traditional kickoff of rendering a series of nested blocks.

#File: vendor//magento/framework/View/Result/Page.php
$output = $this->getLayout()->getOutput();
$this->assign('layoutContent', $output);
//...
<?php /* @escapeNotVerified */ echo $layoutContent ?>

Magento renders the <head/> section of an HTML page by echoing several variables.

<head <?php /* @escapeNotVerified */ echo $headAttributes ?>>
    <?php /* @escapeNotVerified */ echo $requireJs ?>        <?php /* @escapeNotVerified */ echo $headContent ?>        <?php /* @escapeNotVerified */ echo $headAdditional ?>    </head>

How Magento populates of contents of these variable is beyond the scope of this article. The main change you’ll want to be aware of is that <head/> is no longer simply controlled by standard layout blocks.

Summary of Magento 2 Layout Changes

Magento 1’s layout system, while cryptic, was ultimately understandable by a single developer. Its why I wrote No Frills Magento Layout — the system wasn’t well documented, but once explained developers could understand and reason about it from top to bottom. It had a complex looking surface, but a simple elegant implementation.

As you can see from the above critique, Magento 2 has taken an already cryptic system, and added layers of complexity on top of it. These top levels of complexity are equally complex under the hood. Without getting into the details of it, Magento takes the layout handle XML files for a single request, merges them into a a single document, processes that document to transform it into a Magento 1 style page layout document, and then processes that document is way that similar, but not identical, to Magento 1.

Unlike Magento 1’s layout system, which an average developer could ultimately translate in their head into PHP code and reason about, the new rendering is too complex for most human beings to keep in their head at once. The new system is less understandable to the average developer. Perhaps this was necessary to implement the RequireJS and Less CSS systems the core team wanted to, but from the outside looking in it seems like a classic case of what people complain about when they complain about architect driven development.

Adding CSS and Javascript to a Page

Layout system critiqued, let’s get back to the practical business of adding a front end file to our Magento module. Before we begin, per pervious articles in this series, the following assumes

  1. You’re working with developer mode enabled (SetEnv MAGE_MODE developer in your apache config)
  2. That you’ve disabled the full page caching in System -> Cache Managment

To start, we need to create a new Magento module. We’re going to use the Magento code generating tool pestle for this, but if you want to create your own module manually you can follow the instructions in our Introduction to Magento 2 — No More MVC article.

To create the new module using pestle, run the following commands

$ pestle.phar generate_module Pulsestorm JavascriptCssExample 0.0.1
$ pestle.phar generate_route Pulsestorm_JavascriptCssExample frontend pulsestorm_javascriptcssexample
$ pestle.phar generate_view Pulsestorm_JavascriptCssExample frontend pulsestorm_javascriptcssexample_index_index Main content.phtml

Then enable your new module

$ php bin/magento module:enable Pulsestorm_JavascriptCssExample

and let the module setup system know about your module.

$ php bin/magento setup:upgrade

After running the above, you should be able to load the pulsestorm_javascriptcssexample frontname in your system.

http://magento.example.com/pulsestorm_javascriptcssexample

With the above completed, let’s get started!

The Layout Head Section

One of the new features Magento 2 introduces is context aware layout update XML files. By context aware we mean that end-programmer-users can add commands/directives to their layout XML files that only effect a particular section of the document. In plain english — layout update XML files now have a <head/> section where you can add head specific information about a file.

Sample code is often worth 1,000 words. Open up your module’s layout handle XML file

<!-- File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/layout/pulsestorm_javascriptcssexample_index_index.xml -->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <referenceBlock name="content">
        <block template="content.phtml" class="Pulsestorm\JavascriptCssExample\Block\Main" name="pulsestorm_javascriptcssexample_block_main" />
    </referenceBlock>
</page>

and add the following <head/> node

<!-- File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/layout/ -->pulsestorm_javascriptcssexample_index_index.xml 
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Pulsestorm_JavascriptCssExample::test.css"/>
        <link src="Pulsestorm_JavascriptCssExample::test.js"/>
    </head>
    <referenceBlock name="content">
        <block template="content.phtml" class="Pulsestorm\JavascriptCssExample\Block\Main" name="pulsestorm_javascriptcssexample_block_main" />
    </referenceBlock>
</page>

After making the above changes, clear your cache,

$ php bin/magento cache:clean

and then reload the Magento page

http://magento.example.com/pulsestorm_javascriptcssexample

If you view the source to this page, you should see the following HTML

<link  rel="stylesheet" type="text/css"  media="all" href="http://magento-2-with-keys.dev/static/frontend/Magento/blank/en_US/Pulsestorm_JavascriptCssExample/test.css" />
<!-- ... -->
<script  type="text/javascript"  src="http://magento-2-with-keys.dev/static/frontend/Magento/blank/en_US/Pulsestorm_JavascriptCssExample/test.js"></script>

That is, Magento has automatically created the http (or https) paths we’ve been generating manually so far.

http://magento-2-with-keys.dev/static/frontend/Magento/blank/en_US/Pulsestorm_JavascriptCssExample/test.css
http://magento-2-with-keys.dev/static/frontend/Magento/blank/en_US/Pulsestorm_JavascriptCssExample/test.js

When you use a string identifier like this

Pulsestorm_JavascriptCssExample::test.css

you’re telling Magento

use the test.css file found in the Pulsestorm_JavascriptCssExample module.

Without these Vendor_Module::... identifiers Magento would try loading these files from the theme hierarchy.

With the URLs generated, if you add corresponding files to your module at

#File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/web/test.js
alert("hello");

#File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/web/test.css
body{
    background-color:#f00;
}

and reload the page, you’ll see that Magento has loaded them correctly into the system.

Adding Files Via PHP

In addition to using Magento 2’s layout XML system to automatically add front end asset URLs to your project, you can also create these URLs via PHP using a Magento\Framework\View\Asset\Repository object. We’ll show you how to do this below, as well as how to add arbitrary HTML to the <head/> of a Magento HTML page.

Starting with the later item, add the following node to our layout handle XML file

<!-- File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/layout/ pulsestorm_javascriptcssexample_index_index.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <!-- ... -->
    <referenceBlock name="head.additional">
        <block  template="head.phtml" 
                class="Pulsestorm\JavascriptCssExample\Block\Head" 
                name="pulsestorm_javascriptcssexample_block_head" />
    </referenceBlock>
    <!-- ... -->
</page>

The above code

  1. Gets a reference to the head.additional block
  2. Creates a new Pulsestorm\JavascriptCssExample\Block\Head block named pulsestorm_javascriptcssexample_block_head that uses the head.phtml template.
  3. Adds that new block to the head.additional block using the reference from #1

The head.additional block is a special block. Any block added to head.additional will automatically be output into the <head/> area of a Magento page. If you read our critique above, this is another bit of confusion added by the <head/> context. Even though our ultimate goal is to add something to <head/>, we need to operate inside the layout handle XML file’s <body/> tag.

Regardless, once we’ve got the layout XML in place, we’ll want to create our new Head block class

#File: app/code/Pulsestorm/JavascriptCssExample/Block/Head.php
<?php
namespace Pulsestorm\JavascriptCssExample\Block;
class Head extends \Magento\Framework\View\Element\Template
{
}

As well as a template

#File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/templates/head.phtml
<!-- Hello There -->

With the above in place, clear your Magento cache and reload your page. You should see the <!-- Hello There --> comment in your page’s <head/> node.

With a new template rendered in <head/>, we’re ready to render an asset URL using the asset repository.

The Asset Repository

The Magento\Framework\View\Asset\Repository object will allow us to create asset objects. Asset objects can convert a file identifier like foo/test.js or Pulsestorm_JavascriptCssExample::test.js into a full URL.

Like any object in Magento 2, when we want an instance of an object we don’t directly instantiate it — we inject it in another object’s constructor. Change your Head.php file so it matches the following

#File: app/code/Pulsestorm/JavascriptCssExample/Block/Head.php
<?php
namespace Pulsestorm\JavascriptCssExample\Block;
class Head extends \Magento\Framework\View\Element\Template
{
    public $assetRepository;
    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context, 
        array $data = [],    
        \Magento\Framework\View\Asset\Repository $assetRepository
    )
    {
        $this->assetRepository = $assetRepository;
        return parent::__construct($context, $data);
    }
}

What we’ve done above is use Magento 2’s automatic constructor dependency injection to create a \Magento\Framework\View\Asset\Repository object, and assign it to the assetRepository property of our block object. The other parameters in __construct and the call to parent::__construct are there for compatibility with the base template block class. Also, notice we made assetRepository a public property. This means we’ll be able to access it in our phtml template.

Edit your head.phtml file so it matches the following.

#File: app/code/Pulsestorm/JavascriptCssExample/view/frontend/templates/head.phtml
@highlightsyntax@php
<?php
    $asset_repository = $this->assetRepository;
    $asset  = $asset_repository->createAsset('Pulsestorm_JavascriptCssExample::test.js');
    $url    = $asset->getUrl();
?>
<!-- Hello There -->
<script src="<?php echo $url; ?>"></script>

With the above in place, clear your cache, delete the files in var/generate/* (because you changed an automatic constructor dependency injection constructor), and reload the page. If you view the raw HTML source, you should see a new <script/> tag rendered with a full asset URL.

What we’ve done above is use the createAsset method of the asset repository object to create an asset object. Then, we use the getUrl method of the asset object to fetch the HTTP url of the asset. All we need to know is the file identifier — Magento handles the grunt work of pulling together the correct URL path parameters.

Incorrect dependency in class

Update: Readers have reported that, in more modern versions of Magento 2, they end up getting the following error

Incorrect dependency in class Pulsestorm\JavascriptCssExample\Block\Head in app/code/Pulsestorm/JavascriptCssExample/Block/Head.php , \Magento\Framework\View\Asset\Repository already exists in context object

when running setup:di:compile. You can fix this by

  1. Removing Magento\Framework\View\Asset\Repository injection from the constructor
  2. Replace $asset_repository = $this->assetRepository; with $this->getAssetRepository()
  3. If you’re using a version of Magento that doesn’t set an asset repository object on blocks, try $asset_repository = $this->_assetRepo; instead.

The reason this error occurs is the dependency injection compiler checks to make sure objects don’t have “double injections”. The Magento\Framework\View\Asset\Repository class is already injected in a base template class, and therefore our injection of it is redundant.

We regret the error.

Wrap Up

Today, after a long winded critique of Magento 2’s layout language, we demonstrated how to use that language to add front end CSS and Javascript assets to a Magento page. We also investigated directly using the underlying PHP asset repository that makes this possible.

In the past few articles, we’ve been entirely focused on getting “raw” front end asset files into our system. Next time we’ll start investigating how Magento has integrated with the new higher level front end abstractions like RequireJS and Less CSS.

Originally published January 13, 2016
Series Navigation<< Magento 2: Code Generation with PestleMagento 2 and RequireJS >>