Automatic unique IDs for Drupal menu items

Tom
A man walks along the bank of a frozen river. Snow is falling. There is already a thick carpet of flakes crunching beneath his feet with every step. The fall is heavy and the sky is white. Reaching the bridge he begins to cross the wide, high structure keeping one eye on the usually-flowing, unusually still body sitting motionless tens of feet beneath. 'I wonder,' he thinks 'how I can give unique IDs to my Drupal menu items.'

Well I'll tell him, and you. And yes, it is a thinly veiled attempt to inject a hint of interest into what could otherwise have been a stale opening paragraph. More such thrilling fiction in future posts. To the point!

There are doubtless several ways to do this of varying degrees of flexibility or complexity, as there often is with Drupal, but I find the method shown below to a be pretty simple one.

We'll be using a theme hook, a function Drupal will call automatically which can affect the markup that is output in the end. The hook in question is theme_menu_item_link() which, as the API docs state, generates the HTML output for a single menu link. What we'll be doing is creating our own version of that function in our theme which is identical, except that it checks that this link is part of the menu we want to control.

The code below is what we end up with in our theme's template.php file:

function yourthemename_menu_item_link($link) {
  if (empty($link['localized_options'])) {
    $link['localized_options'] = array();
  }
 
  // Our custom code starts here. >>>
 
  // Check for the menu we need
  if ($link['menu_name'] == 'primary-links') {
    /*
     * If this item points to the site front page, remove the < > symbols
     * or this keyword will result in the link target path being just '/'
     * and so your id will just be 'nav_', as opposed to 'nav_front'. Change
     * to suit your preference.
     */
    if ($link['link_path'] == '') {
      $link['link_path'] = 'front';
    }
   
    /*
     * Generate URL from the link path, replace slashes with hyphens
     */
    $link['localized_options']['attributes']['id'] = 'nav' . strtr(url($link['link_path']), '/', '-');
  }
 
  // <<< Our custom code ends here.
 
  return l($link['title'], $link['href'], $link['localized_options']);
}

You can see what we have added by comparing this function to the one at the hook's link above. Ignoring the stuff we copied from the original function, we start by checking this link is part of the correct menu—I'll just use the Primary links. If so then we check that the link_path, what Drupal calls the portion of the URL after the domain, to ensure that it is not "<front>". This is used within Drupal to denote a link to the site's front page. Because we will be running this path through the url() function, "<front>" would return a URL path of just "/" and so the link's ID would end up as simply "nav-" which isn't very descriptive.

That just leaves us with setting the link's id, which we will add to an attributes array, inside the link's localized_options array. We pass that link_path to the url() function which returns a URL path. I'm then using string translate, to convert the slashes to hyphens (yes you could use str_replace or whatever your preferred method of replacing is—I like strtr for small, quick replacements, since it's both of those things itself). Concatenate that onto the end of "nav" or whatever prefix you like and we're done. This menu's links will have a unique ID based on the path of that link. If a path was for example "about-us/meet-the-team", your link's ID will be "nav-about-us-meet-the-team".

Obvious problems are that IDs must be unique in the DOM so you need to be aware of where you are using this, and use it appropriately. Perhaps you'd rather set a class this way instead of an ID to avoid any validation errors. That's fine—change $link['localized_options']['attributes']['id'] to $link['localized_options']['attributes']['class'].

Enjoy... theme hooking your links this Christmas!

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
7 + 1 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.