4

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

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);
  • @Matt feel free to upvote and/or mark your question as answered so it will help other people with the same issue – Raphael at Digital Pianism Apr 25 '16 at 10:28
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.

  • Does not work anymore in 2.2 ... XSD verification make this impossible.. – Pol Ravalitera Feb 5 '18 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/… – Josh Davenport Feb 5 '18 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 ;) – Pol Ravalitera Feb 6 '18 at 9:44
  • 1
    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! – Nathaniel Rogers Aug 21 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, privacy policy and cookie policy

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