Magento 2 container custom attribute
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);
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.