Magento 2 Containers and Blocks

term-blocks-contentMagento 2 has introduced containers in addition to blocks in layout files. This post is a bit of background of why, with an invitation to share your perspective.

Background

In case you are not familiar with the Magento 2 page layout engine, here is a quick summary. This is very similar to Magento 1, but there have been a few tweaks.

A page that a user sees is built from a tree of “structural elements” (blocks and containers). To generate the HTML for a page, the structural element tree is rendered into HTML. Each structural element is asked to produce a fragment of HTML for the final page. During rendering, structural elements can ask children for their HTML to merge into the final result.

The reason for layouts is to make it easy for a module to add something to a page introduced by another module. For example, when viewing a product maybe you want to add into the side bar a link to any blog posts you have written on the product. This sort of manipulation in Magento is done at the structural element level (blocks and containers), not the HTML level. Every structural element can declare a global name (stored in the ‘name’ attribute in a layout XML file) that is used for such cross references. (To me, this ‘name’ is like an ‘id’ in HTML.)

More precisely, layout files actually contain layout instructions. Normally you declare changes you want to apply to the structure element tree that has been built up so far. For example, you can reference a container somewhere in the tree by name and add another child block to it. You can also move elements in the tree. <referenceContainer> and <referenceBlock> are the most common layout instructions which locate a container or block in the tree by ‘name’, then manipulate that element (frequently by adding or replacing children structural elements). Layout instructions are not the focus of this blog post, so I don’t mention them much further. I also don’t mention here the difference between pages, page layouts, and layout instructions.

Blocks vs Containers

Those familiar with Magento 1 will be familiar with blocks. Typically a block has a PHP class and a PHTML template file used to generate the HTML for the block. Methods of the PHP block class can be called from the PHTML template file to keep the amount of PHP in the PHTML file minimal.

There are two common patterns in Magento – a block can have a set of named children, or it can have an array of unnamed children. In Magento 2, these two patterns have been split into Blocks and Containers. (You can still find some old-style blocks with an array of children, such as Magento\Framework\View\Element\Html\Links. Ideally these would be all converted to the new approach.)

For example, the block Magento\Customer\Block\Account\Forgotpassword allows for a child structural element ‘form_additional_info’. The current terminology is to call this an ‘alias name’ – conceptually I think of it as a parameter name to reference child elements. Blocks can have both ‘arguments’ (configuration settings specified in <argument> elements in the layout file) and children structure elements (blocks/containers). The ‘as’ attribute of the child structural element holds the ‘alias name’ used by the parent block to find a child element by name. The forgot password child element allows other layout files to inject some additional content into the “I forgot my password” page.

Containers do not use the ‘as’ attribute on its children. The children are just an array to be displayed in order – there is no need to name them. Attributes ‘before’ and ‘after’ are used to help place children of containers relative to each other. Containers also do not have a PHTML file. All the cases examined so far had very simple markup, so the HTML markup to be generated by a container is currently held in attributes such as ‘htmlTag’ and ‘htmlClass’ of the <container> element.

<container
     name="customer.form.register.fields.before"
     as="form_fields_before"
     label="Form Fields Before"
     htmlTag="div"
     htmlClass="customer-form-before"/>

So what if you want a block to have a group of zero or more children? The answer is to put a container under a block. The container will have an ‘as’ attribute that the block refers to.

In Magento 1 there were no containers. The implementation of a block just asked for a child element by name or it asked for all children. That is, blocks did the job of a container as well. The reason for the split in Magento 2 was to help introduction of a visual design tool. (This tool is not being delivered as part of Magento 2.0 due to resource constraints.) Having blocks with named children and containers with zero or more unnamed children makes development of such a tool easier.

Possible Changes

The following are some ideas that have floated around, but not considered important enough to make. This is where your voice can be heard – leave a comment if you think any of these changes are worth doing, or have a better recommendation.

  • One could consider changing the ‘name’ attribute of blocks and containers to ‘id’ to better reflect that it is globally unique in the structural element tree. <referenceBlock> and <referenceContainer> elements could then have a ‘ref’ attribute (rather than the current ‘name’ attribute) to reference an ‘id’.
  • I find myself referring to ‘as’ attributes as the child name (rather than ‘alias name’). The term ‘alias’ is not really the right concept to me. It is not an alternative way to reference the child – it is the only way a parent block references a child. The only problem with ‘child name’ is that it’s easy confused with the current ‘name’ attribute (a further reason for the previous bullet point of renaming ‘name’ to ‘id’).
  • Remove the ability for blocks to have an array of children by removing the function to fetch all children. This would help ferret out the few remaining blocks with arrays of children. Each should be changed to a container.
  • Or maybe you think blocks should still be able to have an array of children. In that case, the current APIs to accessing children does not allow access to children as an array without also picking up all named children. Maybe the function to return all children should only pick up those without an ‘as’ attribute set.
  • Rename the <update> layout instruction in layout files (not mentioned above) to say <include> to better explain what the element does. Ok, unrelated to the rest of this blog post but it always annoyed me! 😉  Currently you say <update handle=’xxx’> which does not update the referenced handle – instead it includes the instructions in the referenced layout file into the current file.
  • The XML Schema (XSD file) for layouts could be tightened up so the ‘as’ attribute is only permitted on <block> and <container> elements under <block> and <referenceBlock>. At present the ‘as’ attribute is allowed in many places where it has no meaning. This has led to some Magento layout files specifying the ‘as’ attribute in the wrong place by accident. This should be cleaned up to reduce confusion.
  • Unrelated again to this blog post, the XML Schema also allows some nesting of <referenceBlock> and <referenceContainer> elements that serves no semantic purpose. For example, a <referenceBlock> inside a <referenceBlock> actually has no meaning. They should be siblings, allowing nesting to have some meaning in a future release.
  • Add metadata to each block (a new function?) to return documentation about the block, the ‘alias’ names it supports for child elements, and the <argument> markup the block supports. Then develop a tool to print out this information for all available blocks for online documentation.
  • If blocks did have metadata available as to the legal children alias names, this could be verified in a structure element tree to spot spelling errors in ‘as’ attribute values (in developer mode).
  • For the brave, consider implementing an interactive layout file editor that can manipulate layout files and hence the structural element tree. The challenge here you need to capture the instructions to manipulate a tree, not just capture the final tree itself. But a tool that could show the final tree while editing the layout instructions (using the metadata from the previous point) would be kinda cool.

Conclusion

The purpose of this blog post was to clarify the reason for separating containers from blocks. While Magento 2.0 will not have a visual design editor, it is not ruled out for a future release.

I also snuck in a few personal favorite little changes that I would like to make, but do not have the clear business value to make them worth the cost of implementing. We may do some, but no guarantees. If you think they are worth introducing, or any other related changes, feel free to leave a comment.

15 comments

  1. Matthias Zeis · · Reply

    Hi Alan,

    I like these ideas:

    * changing the ‘name’ attribute to ‘id’
    * introducing a ‘ref’ attribute instead of ‘name’ for and >referenceContainer>
    * “Remove the ability for blocks to have an array of children by removing the function to fetch all children.”
    * Rename to .

    While these wordings shouldn’t be a problem if you worked with Magento for some time, it’s easier for beginners to get started with it. Also, the similarity with the HTML attributes may help.

    Concerning the alias name / “child name”: I always wondered if there isn’t a way to solve this with only one name. If the “name” (or “id”) is unique, this could be possible. I personally didn’t experience name conflicts when using 3rd-party extensions in M1 so this wouldn’t be a big concern for me.

    1. Regarding alias name vs name/id for children, the theory is blocks can be generic so the ‘alias’ is like a function parameter name. The ‘name’ attribute is like a global variable.

      1. Matthias Zeis · ·

        In practice I saw that most devs use pretty much the same string for the name/id and the alias. The alias may be simplified sometimes.

    2. “I personally didn’t experience name conflicts when using 3rd-party extensions in M1 so this wouldn’t be a big concern for me”

      I don’t think there were conflicts either, but there would be if you cross checked all the values of the “as” attribute for uniqueness… even amongst core. The “as” is unique amongst siblings, the “name” globally unique. I like to think of “as” as a nickname that I call that block as it’s familiar “parent”

  2. Jason Neumann · · Reply

    I may be misunderstanding these lines:
    “Containers do not use the ‘as’ attribute on its children. The children are just an array to be displayed in order – there is no need to name them. Attributes ‘before’ and ‘after’ are used to help place children of containers relative to each other.”
    But if i take them as I see it now I disagree. I think child blocks of containers should be required to have the as attribute and the parent container should use that in its ordering.

    A problem I have run into in Magento 1 is while trying to add a block using the ‘before’ or ‘after’ attributes and i can’t use the names of the other blocks because they are random numbers like ‘group_27’. Hardcoding that number into the layout xml will almost guarantee it to break when the extension is added to a different installation.

    I think containers should print the blocks in the order they are received unless a before or after attribute is specified, that attribute should reference the name of the other children so you can guarantee consistency across different installations. if the name isnt found among the current children, then the block is placed at the end like a block without a before or after attribute.

    1. I believe names like ‘group_27’ occurs when no ‘name’ attribute has been set. Using that name in before/after clauses I agree is ‘evil’ and likely to break.

      If you suggest children of containers should use ‘as’, what should they be set to? And then why not just use the ‘name’ attribute instead?

      I think the default behavior is much like what your last paragraph says – there is a consistent default position.

  3. I am all for the following:

    * changing “name” to “id” — although very semantical, it makes a ton of sense, and I see folks assume that “name” should be what a block references a child as. Name (as a concept) is generally not unique, but an id is. Nobody has the same SSN, but many of us have the name David. If it weren’t for possibly confusing former M1 developers, I’d say “name” -> “id” and what’s currently “as” change to “name”

    * Supporting arrays of children in blocks will encourage theme vendors to continue using blocks where containers should otherwise be used. I would like to see them have an exclusive purpose.

    * Renaming “update” to “include” — You don’t know how many times I’ve had to explain this as being semantically incorrect to a less experienced developer…

    * Removing support for nested <reference nodes. It doesn't make sense to nest them and it confuses the intent of the markup

  4. Dmitry Dmitriev · · Reply

    Dear Alan,

    According to the main content e.g:

    ‘Product listing’ block.

    ( https://alankent.files.wordpress.com/2015/01/term-blocks-content.jpg )

    Total magento frontend/cms(not admin grid system) layout columns count: 12
    left bar columns: 2
    right bar columns: 2
    So main content bar columns: 8

    Proof:
    Magento2RootPath/lib/web/css/docs/variables.html
    Paragraph: ‘Variables used for layout grid’.

    Concerning to: frontend/cms pages (not adminhtml grid system):

    How to “Magento2-like-way” dividing the main content of 1column layout without using tag?

    1. Desired result:

    1column magento layout divided to 3 columns (12/4):
    | | | |

    1column magento layout divided to 4 columns (12/3):
    | | | | |

    1column magento layout divided to 6 columns (12/2):
    | | | | | | |

    1column magento layout divided to 12 columns (12/12):
    | | | | | | | | | | | | |

    Thanks for your quick response

    1. Hi Dmitry,

      You might be looking for the `.lib-layout-column()` mixin defined in https://github.com/magento/magento2/blob/2.0/lib/web/css/source/lib/_layout.less#L107-L114 for declaring a grid, and the accompanying .lib-column()` mixin defined in https://github.com/magento/magento2/blob/2.0/lib/web/css/source/lib/_grids.less for defining widths that span that grid. I don’t think it is documented well enough, but documentation of the grid-defining `.lib-layout-column()` does exist at Magento2RootPath/lib/web/css/docs/layout.html.

  5. Dmitry Dmitriev · · Reply

    Hi, Alan,

    Yes, ‘for defining widths that span that grid’, is not well documented yet.

    Looking for close similarity like bootstrap:

    E.g: “Two Unequal Columns”:

    .col-sm-4
    .col-sm-8

    Or “Two Columns With Two Nested Columns”:

    .col-sm-8

    .col-sm-6
    .col-sm-6

    .col-sm-4

    Proof:
    https://www.w3schools.com/bootstrap/bootstrap_grid_examples.asp

    In case of magento 2:

    I want create the 1column layout and split 1column layout to 8 columns:
    12 / 8 = 1.5

    So, finally, each of 8 column should be contain the product item.

    Is the current statement of magento 2 frontend grid (not admin grid)
    system required manual calculation of columns width
    (like 12 / 8 = 1.5 and farther manual
    manipulations like: @screen__l, @screen__xl instructions)?

    Or: Is the situation, regarding to splitting columns, generally obtains
    with more elegant existed magento 2 solution (like bootstrap, e.g. below)?
    E.g: “12 equal bootstrap Columns”:

    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1
    .col-sm-1

    Please provide me with details and father instructions.

    Thanks for your reply
    Your support is highly appreciated

  6. Dmitry Dmitriev · · Reply

    Self-answer:

    columns width should be calculated manually according to the count of desired columns

  7. David Wendt · · Reply

    What was the rationale for removing PHTML templates for containers? I’m trying to add extra markup to the default header.container block and realized that you can’t – you’re stuck with whatever markup the XML allows you to add. I need to add 10 lines of markup to create a mobile-responsive site header – there’s NOWHERE to put this! How the hell are you supposed to customize the markup of a container?

    1. There is a discussion also in the forums going on. But briefly, containers are not designed for this purpose. In general you would add a template block inside the container and it can then have a template file (there is a default block provided for doing this – no code required – Magento\Framework\View\Element\Template). However, you mention site headers by which I assume means the section of the page? There are lots of stackexchange etc examples – look around for “head.additional”. Adding your block inside that container is the norm.

      1. David Wendt · ·

        Fortunately I figured it out a few hours after posting my slightly salty comment. I did notice, however, that the official theming guide (see http://devdocs.magento.com/guides/v2.1/frontend-dev-guide/) doesn’t really explain the fact that there is a generic block class just to hold a template… nor does it really list out what the useful block types would be or what the standard block and container layout names are. These would go a long way to making this structure more understandable. As it stands, I actually just wrote a module that dumps the layout XML data into var/log so I can see what all the blocks are supposed to be named, which is far more useful than, say, path hints.

        For the record, site header means the element that’s normally called header.container in Magento 2 layout – it’s the thing that contains the logo branding as well as all of the site-wide navigation elements.

        (Ironically my new wrapper block makes heavy use of containers as generic mounting points to blocks into. The only problem is that I can’t figure out how to make the phtml only print out certain classes and wrapper elements if a container has children or not…)

      2. I will pass this feedback onto the devdocs team.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: