Drupal 8 Architecture: How to Add Custom HTML Data Attributes to Menus
In a new Drupal 8 build that I am working on, there is a use case for some menu items to have an abbreviation underneath. I wanted to create a content editor friendly way of achieving this in the Drupal menu admin UI. Since I have already implemented the Drupal 8 Link Attributes widget module, I got to thinking that perhaps I could extend the module to handle these abbreviations.
Extending the Link Attributes widget
I was happy to discover that Link Attributes can be extended to any kind of additional HTML attribute you might need for your menu. I came up with the idea that if I could add a new field for the abbreviation, I could turn it into an HTML data attribute for a menu item and then output it with a little jQuery.
There are two pieces to extending the widget within a custom module, my_module
.
- Leverage
function my_module_entity_base_field_info_alter
to add the new attribute desired. - Create a custom
my_module.link_attributes.yml
file and add your attribute in there.
Function
Our function looks like this:
function my_module_entity_base_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type) {
if ($entity_type->id() === 'menu_link_content') {
$fields['link']->setDisplayOptions('form', [
'type' => 'link_attributes',
'settings' => [
'enabled_attributes' => [
'id' => FALSE,
'name' => FALSE,
'target' => TRUE,
'rel' => FALSE,
'class' => TRUE,
'accesskey' => FALSE,
'data-menu-abbreviation' => TRUE,
],
],
]);
}
}
YAML
The key from the above code is 'data-menu-abbreviation' => TRUE,
to let Drupal know, we want a field with that name. Once that is done, we can write a little code in our my_module.link_attributes.yml
file as to how this will look in the menu UI. Note that we match the attribute name from entity_base_field_info_alter
.
data-menu-abbreviation:
title: Menu Abbreviation
Once you clear cache, the menu UI will now show the new field we created.
Because we use data-
here, this attribute will render as an HTML5 data attribute which is ideal to leverage with jQuery. Inspecting on the front end, we see the menu item render as:
Rendered HTML
<a href="" class="custom-menu__link" data-menu-abbreviation="DRPL">
A menu link with an abbreviation
</a>
This is a great, we now have our abbreviation within the menu link. The next step is to read the data in jQuery and render that as a span tag within the menu item.
Pull the data in via jQuery
Now in our custom theme JS file, we can loop through the menu items and output each data attribute like so:
// Get the menu data attribute and render as a span tag.
$(context)
.find(".custom-menu__item--level-1 > a")
.once("menu-abbreviation")
.each(function () {
// If there is a data attribute, "menu-abbreviation".
if ($(this).data("menu-abbreviation")) {
// Set a variable.
var menu_abbreviation = $(this).data("menu-abbreviation");
// Output the tag from the data.
$(this).append("<span>" + menu_abbreviation + "</span>");
}
});
After this code, we now see the output look like this:
<a href="" class="custom-menu__link" data-menu-abbreviation="DRPL">
A menu link with an abbreviation
<span>DRPL</span>
</a>
This works great and solves a very specific use case but as you can imagine, this type of customization could come in handy for a lot of menu needs.
Gotchas
There are a few gotchas here, it sounds like hook_entity_base_field_info_alter
may be changed or deprecated in Drupal 9 so be aware of that when upgrading. (see the issue listed below in resources). You also might need to use hook_module_implements_alter
to adjust the weight of your custom code to be sure it fires after link_attributes
if your module is named with a letter starting before the letter "L".
Resources
- Module: Link Attributes widget
- Doc: Link Attributes - Adding custom attributes
- Issue:
hook_entity_base_field_info_alter()
andhook_entity_bundle_field_info_alter()
are documented to get a parameter that doesn't implement an interface with setter methods - API: function
hook_entity_base_field_info_alter