2.2.2: Layout XML Files

Layout XML Files

Learning to anticipate and navigate the use of XML structure to compose the layout of a page can be one of the more challenging aspects of Magento development. It sometimes requires some detective work to track down the particular layout statements that correspond with a page area, particularly since relevant XML can be spread across different files and since the HTML itself resides separately in template files. Let's waste no time in getting better acquainted with the layout system.

Page Layouts

Let's start our layout journey in the module defining the lowest level foundation: Magento_Theme. Take a look at the directory vendor/magento/module-theme/view. Note the subdirectories base, frontend and adminhtml. We have previously discussed the concept of site areas and config files that are specific to an area. Layout directory structure works the same way, but instead of globally applicable files residing directly in view, they reside in the subdirectory base. (This is an "area" directory you'll rarely find yourself dealing with; we mention it here just so the purpose of the files we're examining is clear.)

Within the core module, each "area" directory (e.g., view/base and view/frontend) contains a subdirectory called page_layout, and here we find files named e.g., empty.xml, 1column.xml and 3columns.xml. The XML structure in these "page layout" files doesn't differ in any way from files in the more common layout directory; the only difference is the purpose they serve. The files define "page layouts" that are applied to all other layout files via an attribute in their root node. Within the same core module, take a peek at view/frontend/layout/default.xml:

<page layout="3columns" ...>

By virtue of this declaration, all layout structure from the 3columns.xml "page layout" file is applied as the foundation that default.xml builds on. And as we've previously noted, default.xml contains layout that applies to all pages of the site.

So by examining these files in the Magento_Theme modules, we can form a picture of how the initial "building blocks" that all other layout files expand on are formed:

  • view/base/page_layout/empty.xml establishes the very first building blocks.
  • view/frontend/page_layout/1column.xml builds on empty.
  • view/frontend/page_layout/2columns-left.xml builds on 1column.
  • view/frontend/page_layout/3columns.xml builds on 2columns-left.
  • view/frontend/layout/default.xml applies the 3columns layout and establishes building blocks for all pages.

All other layout files build on this foundation.

Core Concept

If you examine the page_layout files particularly closely, you might be confused by the fact that 3columns.xml doesn't actually add or change any components as compared with 2columns-left.xml. Both 2-column layouts (2columns-left and 2columns-right) and the 3-column layout actually contain the same structural components; CSS is simply applied to manipulate whether they appear in 2 or 3 columns visually. This is also why any page can declare, e.g., 2columns-right as its layout without disturbing any of the components declared in default.xml (which already applied 3columns).

We'll emphasize again that you're unlikely to need to deal with these lowest level files frequently. But this should help give you a full picture of where layout structures start, which can be particularly helpful when you're following layout references "up the tree" to understand a certain page's structure.

The Anatomy of Layout XML

With those foundational aspects out of the way, let's dive into what a layout file really contains. We're going to continue to use vendor/magento/module-theme/view/frontend/layout/default.xml as our reference; here's a very truncated example from that file that represents the major highlights:

<body>
    ...
    <referenceContainer name="after.body.start">
        ...
        <block class="Magento\Theme\Block\Html\Notices" 
            name="global_notices" 
            template="Magento_Theme::html/notices.phtml"/>
    </referenceContainer>
    <referenceBlock name="top.links">
        <block class="Magento\Theme\Block\Html\Header" 
            name="header" as="header" before="-">
            <arguments>
                <argument name="show_part" xsi:type="string">
                  welcome
                </argument>
            </arguments>
        </block>
    </referenceBlock>
    ...
    <referenceContainer name="header.container">
        <container name="header.panel.wrapper" 
            htmlClass="panel wrapper" 
            htmlTag="div" before="-">
            <container name="header.panel" label="Page Header Panel" 
                htmlTag="div" htmlClass="panel header">
                ...

In one fell swoop, we have examples of most of the key layout declarations! The <body> element is the root node for all layout applying to the HTML <body>. Then we have two main building blocks that define a layout:

  • A <block> node is the primary component that injects content. During layout processing, a PHP object is instantiated to handle the final output of rendering. Block classes can contain various logic for what content to render and how. You can see in the above example that some blocks explicitly declare a class. When this is omitted, the default block class instantiated is Magento\Framework\View\Element\Template; the rendering logic of this class simply involves locating and outputting a declared template.
  • A <container> node is similar to a block, but it's essentially an empty parent for child containers and blocks. No PHP object is instantiated for a container, and they render no output other than their children.

Both types of nodes have a name attribute uniquely identifying them. You can see in the example that containers and blocks can be nested within each other to form a hierarchy. It's important to note that in the case of a parent/child relationship between blocks, it's up to the parent block (likely within a template) to render its children. Containers, by contrast, will automatically render all of their children.

The above example contains an example of a template declaration: template="Magento_Theme::html/notices.phtml. The portion of the string before :: declares a module, while the portion after declares the file path of the template relative to the module's view/*/templates directory. (So this applies the template found at vendor/magento/module-theme/view/frontend/templates/html/notices.phtml.)

The <referenceBlock> and <referenceContainer> nodes allow you to reference (by name) blocks or containers that were defined in other layout files, nesting further layout structure within them as if such updates were nested within the original declaration.

A few other notable pieces of syntax:

  • Some blocks have both a name and an as attribute. as defines an "alias" that can be used instead of the name within templates. name must be globally unique, while as can be unique only within the parent context.
  • The before and after attributes accept the name of another block at the same hierarchical level and can be used to manipulate the order in which elements are rendered. The "-" value means "all elements." (So before="-" means the block should be rendered before all other blocks within the parent.)
  • <arguments> relates to setting arbitrary properties on the PHP block class. We'll delve deeper into this later.

The Product Questions Layout File

Let's now return to our own layout file, view/frontend/layout/catalog_product_view.xml within our module.

<body>
    <referenceContainer name="content">
        <block name="questions.tab" as="questions"
               after="product.info.details" 
               template="SwiftOtter_ProductQuestions::product/view/questions.phtml"
               ifconfig="catalog/questions/enabled">
            <arguments>
                <argument name="view_model" xsi:type="object">
                  SwiftOtter\ProductQuestions\ViewModel\ProductView\Questions
                </argument>
            </arguments>
        </block>
    </referenceContainer>
</body>

Armed with our new knowledge of layout syntax, observe:

  • <referenceContainer> is used to apply our content within the container named "content", which is a common container close to the root of the entire page.
  • Our <block> declaration inserts a new block in that container after the "product.info.details" block. The existing block we're referencing is one you can see declared in vendor/magento/module-catalog/view/frontend/layout/catalog_product_view.xml.
  • Without a custom class, our block is of the default Template type, and we have declared the template to output.
  • Again, we'll revisit the function of <arguments> in a later lesson.

Adding a Child Block

Our custom block isn't yet outputting much. Eventually, it will render a list of the product questions and answers associated with the product. We'll also need a form for submitting a new question, and since we'll need an easy way to insert question records before we have any to display, let's build that out first. We'll do so by adding a child block to "questions.tab" to render the form.

In the existing layout file, add the child <block> node, referencing a new template:

<block name="questions.tab" ...>
    <arguments>
        ...
    </arguments>
    <block name="questions.post_form" as="post_form"
        template="SwiftOtter_ProductQuestions::post_form.phtml">
    </block>
</block>

Go ahead and bootstrap the template file by creating view/frontend/templates/post_form.phtml with the simple content <p>This is a question form.</p>.

Note

If you have a mind to clear the cache and refresh the product detail page to see the output of the new child block, you won't see anything just yet. Remember that it's up to a parent block to render its children. We'll make this happen when we discuss templates.

SEE THE CODE

Resources

Complete and Continue