7

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

3 Answers 3

4

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);
2
  • @Matt feel free to upvote and/or mark your question as answered so it will help other people with the same issue Commented Apr 25, 2016 at 10:28
  • Is there a way to extend theses classes to add the functionality?
    – Black
    Commented Jan 17, 2020 at 14:05
3

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="Stackoverflow\ContainerAttribute\Observer\LayoutSchemaAttributesObserver" />
    </event>
</config>

Observer/LayoutSchemaAttributesObserver.php

namespace Stackoverflow\ContainerAttribute\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\View\Layout\Element;

class LayoutSchemaAttributesObserver implements ObserverInterface
{

    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        /** @var \Magento\Framework\View\Layout $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.

4
  • Does not work anymore in 2.2 ... XSD verification make this impossible.. Commented Feb 5, 2018 at 12:56
  • Oh really @PolRavalitera, well that sucks! Sounds like a double edged sword I guess. Better validation is a good thing but it stops stuff like this working. Probably then, the only way to allow this to work would be by implementing something like this: magento.stackexchange.com/questions/102498/… Commented Feb 5, 2018 at 17:54
  • In fact, i was already using the @Raphael method, to create "new attributes", like "role", that is not in the XSDs... So i had an horrible idea: putting a string in htmlId, that i parse. To add attributes, to completely remove tag (very pratical to handle containers. It was possible in 2.1, but did not knew it was a bug...) As you say, double edged ;) Commented Feb 6, 2018 at 9:44
  • 2
    Can we get this added as a PR? I mean, given my experience with core PR's it'll get rejected, but man, that's so useful! Commented Aug 21, 2019 at 6:29
0

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 Qode1\ContainerAttribute\Observer; use 
Magento\Framework\Event\ObserverInterface; use 
Magento\Framework\View\Layout\Element; class 
LayoutSchemaAttributesObserver implements ObserverInterface { public 
function execute(\Magento\Framework\Event\Observer $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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.