Magento 2 container custom attribute

The question:

Have hunted round for this but can’t find an answer.
As the title suggests I want to add a custom attribute to a container.

I currently have a container in layout.xml as such;

<container name="my.custom.container" as="my_custom_container" htmlTag="div" htmlClass="myCustomContainer" htmlId="customContainer">

which generates

<div class="myCustomContainer" id="customContainer"></div>

as expected.

How do I add a custom attribute to this?
E.g. (As to to end up with)

<div class="myCustomContainer" id="customContainer" customAttribute></div>

Thanks

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

Unfortunately, there’s no way you can do this via XML out of the box.

Magento 2 handles the htmlTag, htmlId and htmlClass via the Magento/Framework/View/Layout.php file under the _renderContainer method:

protected function _renderContainer($name)
{
    $html = '';
    $children = $this->getChildNames($name);
    foreach ($children as $child) {
        $html .= $this->renderElement($child);
    }
    if ($html == '' || !$this->structure->getAttribute($name, Element::CONTAINER_OPT_HTML_TAG)) {
        return $html;
    }

    $htmlId = $this->structure->getAttribute($name, Element::CONTAINER_OPT_HTML_ID);
    if ($htmlId) {
        $htmlId = ' id="' . $htmlId . '"';
    }

    $htmlClass = $this->structure->getAttribute($name, Element::CONTAINER_OPT_HTML_CLASS);
    if ($htmlClass) {
        $htmlClass = ' class="' . $htmlClass . '"';
    }

    $htmlTag = $this->structure->getAttribute($name, Element::CONTAINER_OPT_HTML_TAG);

    $html = sprintf('<%1$s%2$s%3$s>%4$s</%1$s>', $htmlTag, $htmlId, $htmlClass, $html);

    return $html;
}

As you can see, this method does not render any extra attributes than the ones I mentionned.

A alternative would be to use JavaScript to add the attribute based on the id you’ve given to your tag, for example:

jQuery('#customContainer').attr('customAttribute','customValue);

Method 2

Raphael is right, there is no way to do this out of the box. There is a way if you add some custom functionality though.

It takes a custom module and an event observer to make this happen. You were right that there seems to be nothing out there at all to achieve this, I came to the same conclusion! No more.

Create a module, let’s call it Stackoverflow_ContainerAttribute. Add all the standard module declaration. Now the next two files and code are the important parts:

etc/frontend/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="core_layout_render_element">
        <observer name="core_layout_render_container_attribute" instance="StackoverflowContainerAttributeObserverLayoutSchemaAttributesObserver" />
    </event>
</config>

Observer/LayoutSchemaAttributesObserver.php

namespace StackoverflowContainerAttributeObserver;

use MagentoFrameworkEventObserverInterface;
use MagentoFrameworkViewLayoutElement;

class LayoutSchemaAttributesObserver implements ObserverInterface
{

    public function execute(MagentoFrameworkEventObserver $observer)
    {
        /** @var MagentoFrameworkViewLayout $layout */
        $layout = $observer->getEvent()->getLayout();
        $elementName = $observer->getEvent()->getElementName();

        if($layout->isContainer($elementName) && ($containerTag = $layout->getElementProperty($elementName, Element::CONTAINER_OPT_HTML_TAG))) {
            $nodes = $layout->getXpath(sprintf('//*[@name="%s"]/attribute[@name]', $elementName));
            if ($nodes) {
                /** @var SimpleXMLElement $_node */
                foreach ($nodes as $_node) {
                    $name = $_node->attributes()->name;
                    $value = $_node->attributes()->value;
                    $output = $observer->getEvent()->getTransport()->getOutput();
                    $output = preg_replace(
                        "/^(<$containerTag.*?)(>)/",
                        sprintf("$1 %s$2", ($name && $value) ? sprintf("%s="%s"", $name, $value) : $name),
                        $output
                    );
                    $observer->getEvent()->getTransport()->setOutput($output);
                }
            }
        }
    }

}

We’re essentially adding custom behaviour to layout XML here. The approach of finding our custom attribute nodes using XPath may look a bit weird, but I personally feel it’s perfectly normal and not hacky at all. Magento 2 uses XPaths to gather information about layout XML all over the place in similar ways.

With that in place, in layout XML you can now add nodes that attributes in layout XML or reference a container using referenceContainer and add nodes there. Use the syntax below to add attributes (valued or non-valued):

<container name="my.custom.container" as="my_custom_container" htmlTag="div" htmlClass="myCustomContainer" htmlId="customContainer">
    <attribute name="customAttribute" />
    <attribute name="customAttributeTwo" value="42" />
</container>

or if it were another container you wanted to add values to:

<referenceContainer name="product.info.main">
    <attribute name="customAttribute" />
    <attribute name="customAttributeTwo" value="42" />
</referenceContainer>

Note: Don’t confuse this new <attribute> node with the <attribute> node that can be placed directly inside of the <body> node in layout XML. That only works out of the box on the body tag. This adds support for containers using the same syntax, the implementation is completely separate.

Method 3

Josh, I think that is a great solution.
However, i get an error that shows a bunch of white text on the page starting with

namespace Qode1ContainerAttributeObserver; use 
MagentoFrameworkEventObserverInterface; use 
MagentoFrameworkViewLayoutElement; class 
LayoutSchemaAttributesObserver implements ObserverInterface { public 
function execute(MagentoFrameworkEventObserver $observer) {

and continuing for quite a while like this.

This happens after I enable the module and viewing the front page. I must be missing something. I thought the only thing I changed was the namespace.

I believe this is the secret to getting UIkit to work on Magento 2.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment