Menu items that are not links

Last modified: August 26, 2009 - 23:41

Problem

The Menu system in Drupal 5 lets admins define an arbitrary number of menu items in a hierarchy, each of which is a link to somewhere in the site (or on another site). That's great, unless you want to have a menu tree that has items in it which are not, in fact, links. For that, the following code lets us designate "menu links" that will not actually link anywhere.

Step 1

We will define the magic value <none> to mean "a path to nowhere", just like <front> means "the home page". The crucial part here is the theme_menu_item_link() function, which we will override. Place the following function in your template.php file.

<?php
function phptemplate_menu_item_link($item, $link_item) {
  if (
$item['path'] == '<none>') {
   
$attributes['title'] = $link['description'];
    return
'<span'. drupal_attributes($attributes) .'>'. $item['title'] .'</span>';
  }
  else {
    return
l($item['title'], $link_item['path'], !empty($item['description']) ? array('title' => $item['description']) : array(), isset($item['query']) ? $item['query'] : NULL);
  }
}
?>

Any menu item whose path is <none> will now become a span rather than a link. Note that because it's not a link you can't click on it, so you can't access sub-menu items unless the no-link menu item has the "Expand" checkbox checked so that its children are always visible. If you need to have access to the sub-menu items for things like dropdown/flyout menus, try using the JavaScript version of this snippet.

This method can also be used as a separator. Simply have a menu item with text "------" or similar, and set its path to <none>. It will then show as text with no link, providing a nice separation of the menu.

Step 2

As a nice extra, we can also add additional help text to the menu item edit page. For that you will need to implement a very small module that implements form_alter. The module itself doesn't need anything more than the following code snippet:

<?php
function example_form_alter($form_id, &$form) {
  if (
'menu_edit_item_form' == $form_id) {
   
$form['path']['#description'] .= ' ' . t('Enter %none to have a menu item that generates no link.', array('%none' => '<none>'));
  }
}
?>

That will modify just the menu item edit form and add additional text to the description (the text that appears below the textfield) describing how to use the <none>.

Look here for a Drupal 6 solution.

how can i do this on drupal

anibalardid - August 27, 2008 - 14:20

how can i do this on drupal 6 ? because the path dont accept ""

The problem is the new

drinkdecaf - September 3, 2008 - 21:39

The problem is the new function in 6.x called menu_valid_path that validates whether a link actually works. I can't see a way around it as is. For my own site, I implemented this (ugly) fix:

I created a node with alias 'blank' and took note of its nid - 15 and then added this to my template.php:

<?php
function SITE NAME_menu_item_link($link) {
 
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }
 
if (
$link['href'] == 'node/15') {
    return
'<span class="submenunolink">'. $link['title'] .'</span>';
  }

  return
l($link['title'], $link['href'], $link['localized_options']);
}
?>

This effectively gives me a span with class "submenunolink" for any menu item pointing to "blank."

This is a sloppy, ugly fix, and I'd love to hear if someone has a better way of doing it!

What do you mean by SITE

dirkson - September 10, 2008 - 19:22

What do you mean by SITE NAME here? Where would I find what my SITE NAME is?

It's the theme's name

grendzy - September 16, 2008 - 22:11

You actually want to use the name of your theme there. I think the poster wrote SITE_NAME because a lot of people name the theme after the site when writing custom themes.

So if you're using garland... it would be

<?php
function garland_menu_item_link($link) {
?>
...

Hi gredzy , I am using inove

dvkd - June 24, 2009 - 06:48

Hi gredzy ,
I am using inove theme so i used :

<?php
function inove_menu_item_link($link) {
?>

but it show error when i gave as path

The path '' is either invalid or you do not have access to it.

please help me

Another way to 'fix'

rsmithers - November 17, 2008 - 18:41

I came up with this way to make certain menu items non-links. It turns the menu item into a link to the current page with an empty anchor at the end. The link at the bottom of the page doesn't look nice, but at least clicking on the menu item will not cause a page load.

With this change, you can put in the link path as , like you would , to have an non-link menu item.

Note: This does change two core include files, so it very likely would have to be reapplied after any official fixes.

Module: menu.inc
Function: menu_link_save
Was:

  $item['_external'] = menu_path_is_external($item['link_path'])  || $item['link_path'] == '<front>';

New:
  $item['_external'] = menu_path_is_external($item['link_path'])  || $item['link_path'] == '<front>' || $item['link_path'] == '<none>';

Module: common.inc
Function: url
Add new code after:

  $base = $options['absolute'] ? $options['base_url'] .'/' : base_path();
  $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
  $path = drupal_urlencode($prefix . $path);

New code:

/********** MY CUSTOM CODE - If url is <none>, generate empty link - mainly for use in menu system ********/
if ($original_path == '<none>')
{
    $base = '#';
    $prefix = '';
    $path = '';
    $options['query']='';
    $options['fragment']='';
}
/********END MY CUSTOM CODE*******/

Another way to 'fix' part 2

Chaos_Hades - December 1, 2008 - 04:02

Maybe I'm wrong, but we need to do this change too because the function valid_menu_path will block the <none> value.

Module: menu.inc
Function: valid_menu_path
Was:

  if ($path == '<front>' || menu_path_is_external($path)) {
    $item = array('access' => TRUE);
  }

New:

  if ($path == '<front>' || $path == '<none>' || menu_path_is_external($path)) {
    $item = array('access' => TRUE);
  }

Simple D6 approach

kees@qrios - December 22, 2008 - 11:12

This is how it could be done in D6:

add node or node alias as a dummy to identify that this link isn't a link. I personally add a "nolink" alias to systempath "node", since I never use the "node" path.

Add this to your template.php

<?php
function yourtheme_menu_item_link($link) {
  if (
$link['type'] && $link['href'] == 'node') {
    return
'<span class="nolink">'.check_plain($link['title']).'</span>';
  }
}
?>

Replace the 'node' part with the valid systempath you use as a dummy for this.

Now add a menulink with "nolink" as path.

Now style the .nolink class to your needs, there you go!
Please feedback if you have issues with this.

Luck,

Kees

Drupal website, webdevelopment

Slightly Confused

mkeith - January 19, 2009 - 21:58

I'm somewhat foggy on how to implement this...

I went to "URL Aliases" and added the alias "nolink" for the system path of "node."

I then dropped the code snippet into my theme. I'm using a customized sub-theme of Zen. I've have never played around in the template.php file, so I am unaware of where to put this snippet. From the looks of the file, I think I need to drop the opening and closing PHP tags. That leaves me with...

function yourtheme_menu_item_link($link) {
if ($link['type'] && $link['href'] == 'node') {
return ''.check_plain($link['title']).'';
}
}

I changed "yourtheme" to my customized theme name.

I then went to my "Menus" admin area and edited the menu item that I wanted to apply this effect to. I made the path "nolink"

Upon saving, I received this message. "The menu system stores system paths only, but will use the URL alias for display. nolink has been stored as node"

I cleared the cache under the "Performance" menu. When doing this all of my menu items disappeared except for the item I applied the "nolink" to.

Any ideas on what I may be doing wrong?

Also, I have multiple menu items that I want to apply this to. How do I accomplish that?

Thanks!

Confused too

franzinenstein - January 23, 2009 - 12:00

Hi, thank you for your tutorial, but I have also some problems to implement it.
It makes disappear every menu of my site.

Not quite right, and here's my variant

origo - January 23, 2009 - 12:02

I guess the solution above will work, but the function is not complete. You need an else clause that returns the default menu item, otherwise all menu items except the nolink items will be blank:

<?php
function yourtheme_menu_item_link($link) {
  if (
$link['type'] && $link['href'] == 'node') {
    return
'<span class="nolink">'.check_plain($link['title']).'</span>';
  }
  else {
    return
theme_menu_item_link($link);
  }
}
?>

I decided to do it a little bit differently. Here's an example using the garland theme.
I added the following to the theme's template.php file:

<?php
/**                                                                                                                                          
* Create special "nolink" menu items. These are modified versions of
* (includes/menu.inc)   function theme_menu_item_link() and
* (includes/common.inc) function l().
*/
function garland_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  return
nl($link['title'], $link['href'], $link['localized_options']);
}

function
nl($text, $path, $options = array()) {
 
// Merge in defaults.
 
$options += array(
     
'attributes' => array(),
     
'html' => FALSE,
    );

 
// Append active class.
 
if ($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) {
    if (isset(
$options['attributes']['class'])) {
     
$options['attributes']['class'] .= ' active';
    }
    else {
     
$options['attributes']['class'] = 'active';
    }
  }

 
// Remove all HTML and PHP tags from a tooltip. For best performance, we act only
  // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
 
if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
   
$options['attributes']['title'] = strip_tags($options['attributes']['title']);
  }

 
$p = check_url(url($path, $options));
  if (
$p == "/nolink") {
    return
'<span class="nolink"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</span>'
;
  }
  else {
    return
'<a href="'. $p .'"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</a>';
  }
}
?>

Basically I just modified the end of the nl (l) function so that I can check for the URL alias "/nolink" instead of whatever system path you decided to use. Also, the advantage is that tooltips etc work.

I added the following to the theme's style.css file to make the nolink menu items look like the real links:

span.nolink {
  color: #027AC6;
  text-decoration: none;
}

span.nolink:hover {
  color: #0062A0;
  text-decoration: underline;
}

span.nolink:active {
  color: #5895be;
}

As a nice touch, if you're using the DHTML Menu module, you need to modify it a little so that it can expand and collapse the nolink menu items. Here's a little patch:

--- dhtml_menu.js.nolink        2009-01-12 11:13:30.000000000 +0100
+++ dhtml_menu.js       2009-01-23 02:38:52.000000000 +0100
@@ -60,6 +60,11 @@
       Drupal.dhtmlMenu.toggleMenu($(li));
       return false;
     });
+
+    $(li).find('span:first').click(function(e) {
+      Drupal.dhtmlMenu.toggleMenu($(li));
+      return false;
+    });
   });
}

Enjoy!

Note that you're using a

sjancich - February 11, 2009 - 01:29

Note that you're using a subtheme of Zen, the first snippet will break your tabs. The else clause should pull Zen's override (zen_menu_item_link) instead of theme_menu_item_link.

For Zen subthemes

doubledoh - October 13, 2009 - 01:54

To spell it out...this is what I put in my theme's template.php file (which is a Zen subtheme I had called "wmp"). Notice in the "else" conditional, I spelled out "zen" instead of my theme name.

<?php
function wmp_menu_item_link($link) { // <<< Make sure you alter this line for YOUR theme name (mine was "wmp")
 
if (!$link['page_callback'] && strpos( $link['href'], 'no.link')) {
    return
'<a href="javascript:void(0)" class="nolink">'. $link['title'] .'</a>';
  }
  else {
    return
zen_menu_item_link($link); // <<< Make sure this says "zen_"
 
}
}
?>

So, if I make a menu item path "http://no.link", this function will turn that link into a "javascript:void(0)" WITHOUT screwing up the tab formatting of zen themes.

It's a bit more complicated than that

random_ - February 21, 2009 - 21:59

I tried to get this to work for ages, and eventually I had to hard-code the function into includes/menu.inc to make it work.
Note that the examples above require you to enable the 'path' module in Drupal 6.

This is what I did:
1. create a dummy page, which had the path 'node/15'
2. created a menu item with the path 'node/15'
3. replaced the 'theme_menu_item_link' function in menu.inc with this:

function theme_menu_item_link($link) {
  if (empty($link['localized_options'])) {
    $link['localized_options'] = array();
  }

  if ($link['type'] && $link['href'] == 'node/15') {
    return '<span class="nolink">'.check_plain($link['title']).'</span>';
  }
  else {
        return l($link['title'], $link['href'], $link['localized_options']);
  }
}

I am using the Garland theme, but code in the garland/template.php file does not seem to get executed.
Does anyone have any idea why?

I've thought of a simpler way

joachim - March 5, 2009 - 19:42

I've thought of a simpler way than creating a fake node.

The menu system lets you create links to external sites, and has no way of checking those.
So add a menu item linking to, say, 'http://label.fake' and test for it in the theming function as above:

  if (!$link['page_callback'] && strpos( $link['href'], 'label.fake')) {

This is the solution that worked for me

agerson - May 14, 2009 - 03:00

This gave me everything I wanted. No clickable link, no hand cursor, no page items jumping around.

node/120 is a node I am using to point all unclickable links to.

Added to template.php

<?php
function yourthemename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }
  if (
$link['type'] && $link['href'] == 'node/120') {
    return
'<a href="javascript:void(0)" class="nolink">'. $link['title'] .'</a>';
  }
  else {
    return
l($link['title'], $link['href'], $link['localized_options']);
  }
}
?>

Added to style.css:

.nolink:hover {
  cursor: default;
}

Drupal 6 + Nice Menus

tekket - March 15, 2009 - 17:12

This is the only version that works for me with Nice Menus module... (I also had strange problems when I tried the dummy node approach, so I used the fake url one - in this case I use http://fake.link for every item I do not want to be linkable)

<?php
function themename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  if (!
$link['page_callback'] && strpos( $link['href'], 'fake.link')) {
    return
'<a href="#" class="nolink">'. $link['title'] .'</a>';
  }
  else {
        return
l($link['title'], $link['href'], $link['localized_options']);
  }
}
?>

Note: class nolink is purely optional, but it's good for adding some useful styling like changing the cursor etc.

Hi, I got confused. Where am

simeon.mattes - May 7, 2009 - 16:14

Hi,

I got confused. Where am I going to put this?

There was a terribly simple

joachim - May 7, 2009 - 16:36

There was a terribly simple page about this in the D5 guide.
The D6 guide is somewhat more detailed: http://drupal.org/node/341628

Should I read this in order

simeon.mattes - May 7, 2009 - 17:41

Should I read this in order to use the function which tekket wrote? Sorry, but I am a newbie in drupal and it seems difficult

well, bits of it. It's a bit

joachim - May 7, 2009 - 18:21

well, bits of it.
It's a bit hardcore.
this for D5 is what you need, should be ok for 6: http://drupal.org/node/55126

I have read what you 've told

simeon.mattes - May 8, 2009 - 12:54

I have read what you 've told me and I have concluded that I should put the function of tekket in template.php. I'm using as a theme this of zen's starterkit. The steps I followed were:

1. I renamed themename_menu_item_link to my_theme_menu_item_link, where my_theme is the renamed name of the zen's starterkit.
2. I replaced 'front' path of my menu with http://fake.link.

Though it doesn't work. Every time I select the menu button it's trying to connect me to http://fake.link. What am I missing?

Never mind...It worked. Thanks!

This version worked for me

pjabode - May 13, 2009 - 16:32

This version worked for me (Great work!)

to avoid having the page scroll to the top when clicking on the fake link,
use
javascript:void(0)

instead of the

#
sign
inside of return '

Taxonomy Menu

soulston - May 19, 2009 - 18:13

Hi,

How would you go about doing this for a menu created using taxonomy terms. I have a product catalog and have created taxonomy terms for items so that when I add an item I can tag it with one or more terms and then when a user clicks on the term in the menu they are shown any product tagged as such.

However, I don't want the parent menu item to display a page when there are sub menu items:

Outdoor:
-->Furniture
-->Accessories
-->Misc

Some Menu items don't have children such as Mirrors so I need to check this in the code.

Any pointers, I tried creating a template.php but the above post doesn't seem to work with a taxonomy menu.

Looking into this I found this in the D6 api:

_menu_update_parental_status

which I suppose I could use to check if the item has any children, maybe:

if ($item['plid']){ // item has children
replace link with javascriptvoid(0) // and just list the submenu items
}else{
return normal link // and load any items with this taxonomy term
}

This works & additional improvement

digitallica - July 14, 2009 - 12:41

This solution works for nice menus (and other menu's occasions where dummy link needed [?])

A quick additional improvement: insert


onclick="return false"

to the return code for dummy link to become as follows:

<?php
function themename_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  if (!
$link['page_callback'] && strpos( $link['href'], 'fake.link')) {
    return
'<a href="#" onclick="return false" class="nolink">'. $link['title'] .'</a>';
  }
  else {
        return
l($link['title'], $link['href'], $link['localized_options']);
  }
}
?>

so dummy link (#) not causing web browser to search whole page for link anchor #

Another improvement

segx - August 11, 2009 - 01:57

Thanks digitallica - there was a couple things I wanted to add/share to this improvement. When using nice menus, "menu-path-fake.link" is created as a class in each parent li. I don't believe having a period in the class is good form. I also wanted to utilize javascript:void(0) instead of # so that the current URL isn't read into the href (removing an incorrect URL may be better for SEO?), so I made the below modifications and added a new function from the nice menus module to my sub-theme's template.php file.

<?php
/**
* Creates empty menu item when <a href="http://menu.nolink" title="http://menu.nolink" rel="nofollow">http://menu.nolink</a> is added to the menu path.
*/
function THEMENAME_menu_item_link($link) {
  if (empty(
$link['localized_options'])) {
   
$link['localized_options'] = array();
  }

  if (!
$link['page_callback'] && strpos( $link['href'], 'menu.nolink')) {
    return
'<a href="javascript:void(0)" onclick="return false" class="nolink">'. $link['title'] .'</a>';
  }
  else {
        return
l($link['title'], $link['href'], $link['localized_options']);
  }
}

/**
* Helper function that builds the nested lists of a nice menu.
*
* @param $menu
*   Menu array from which to build the nested lists.
*/
function THEMENAME_nice_menu_build($menu) {
 
$output = '';

  foreach (
$menu as $menu_item) {
   
$mlid = $menu_item['link']['mlid'];
   
// Check to see if it is a visible menu item.
   
if ($menu_item['link']['hidden'] == 0) {
     
// Build class name based on menu path
      // e.g. to give each menu item individual style.
      // Strip funny symbols.
     
$clean_path = str_replace(array('http://', '<', '>', '&', '=', '?', ':', 'menu.'), '', $menu_item['link']['href']);
     
// Convert slashes to dashes.
     
$clean_path = str_replace('/', '-', $clean_path);
     
$path_class = 'menu-path-'. $clean_path;
     
// If it has children build a nice little tree under it.
     
if ((!empty($menu_item['link']['has_children'])) && (!empty($menu_item['below']))) {
       
// Keep passing children into the function 'til we get them all.
       
$children = theme('nice_menu_build', $menu_item['below']);
       
// Set the class to parent only of children are displayed.
       
$parent_class = $children ? 'menuparent ' : '';
       
$output .= '<li id="menu-'. $mlid .'" class="'. $parent_class . $path_class .'">'. theme('menu_item_link', $menu_item['link']);
       
// Build the child UL only if children are displayed for the user.
       
if ($children) {
         
$output .= '<ul>';
         
$output .= $children;
         
$output .= "</ul>\n";
        }
       
$output .= "</li>\n";
      }
      else {
       
$output .= '<li id="menu-'. $mlid .'" class="'. $path_class .'">'. theme('menu_item_link', $menu_item['link']) .'</li>'."\n";
      }
    }
  }
  return
$output;
}
?>

Obviously THEMENAME would be the name of your theme. The second function will remove "http://menu." creating the class "menu-path-nolink".

CSS to the rescue

BassPlaya - June 1, 2009 - 15:11

I know it's not the best but in the worst case scenario:
you could use css by tricking the user to feel that certain links aren't links..
.certain-links a,
.certain-links a:hover {
cursor: default;
text-decoration: none;
}

 
 

Drupal is a registered trademark of Dries Buytaert.