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 onempty
.view/frontend/page_layout/2columns-left.xml
builds on1column
.view/frontend/page_layout/3columns.xml
builds on2columns-left
.view/frontend/layout/default.xml
applies the3columns
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 that3columns.xml
doesn't actually add or change any components as compared with2columns-left.xml
. Both 2-column layouts (2columns-left
and2columns-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 indefault.xml
(which already applied3columns
).
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 aclass
. When this is omitted, the default block class instantiated isMagento\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 anas
attribute.as
defines an "alias" that can be used instead of the name within templates.name
must be globally unique, whileas
can be unique only within the parent context. - The
before
andafter
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." (Sobefore="-"
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 invendor/magento/module-catalog/view/frontend/layout/catalog_product_view.xml
. - Without a custom
class
, our block is of the defaultTemplate
type, and we have declared thetemplate
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.