Inspired by this implementation of menu element for CakePHP I found that I require multilevel menus. It’s nice and can highlight current page. Furthermore it should fit drop down menu css layout. Now I need to show or hide element is there is authenticated user, but later I will need to hide menu items that are disabled by standard acl rules.

To achieve required results we should define:

  1. the component that will hold array of menu items;
  2. the helper class that will render menu in ul, li html tags;
  3. integrate existing css design of drop down menu.

The Dynamic Menu Component

We will crete menu.php file in the /app/controllers/components directory:

<?php 
class MenuComponent extends Object {
	var $components = array('Session');
 
	function startup() {
 
		$userMenu = array();
		$generalMenu = array();
 
		$generalMenu[__('Home', true)] = '/';
 
		if(!$this->Session->check('Auth.User')) {
			$userMenu[__('Register', true)] = '/users/register';
			$userMenu[__('Login', true)] = '/users/login';
		}
		else {
			$userMenu[__('Logout', true)] = '/users/logout';
			$userMenu[__('Change', true)] = '/users/change';
		}
 
		//sample child item
		$parent = array();
		$parent[__('Child', true)] = '/';
		$generalMenu[__('Parent', true)] = $parent;
 
		$user = $this->Session->read('Auth.User');
 
 
		//menus arra
		$menus = array();
		$menus[__('General', true)] = $generalMenu;
		$menus[__('User ', true)] = $userMenu;
 
		$this->Session->write('Menu.main', $menus);
 
	}
}
?>

The component places menu array in session. We are going to show our menu on each page of our application. Therefore we will add the component to list of components in the AppController:

var $components = array('Menu');

Now we ready to start with the helper.

The Dynamic Menu Helper

To render our menu in format required by css we will create a helper. The render function will receive array of menus and return html of menus. Create /app/views/helpers/menu.php:

<?php
/**
 * Dynamic menu  helper library.
 * Can be used with CSS from http://www.tanfa.co.uk/css/examples/menu/
 *
 * Methods to render dynamic menu .
 */
class MenuHelper extends AppHelper {
 
	var $helpers = array('Html');
 
	/**
	 * Render  menu from session variable array. 
	 * Top item of the array is menu caption and div id
	 * Sub items are  menu items caption and link to the location, or array of subitems.
	 *
	 * @param array $menu Name of the session variable that stores menu array.
	 * @static
	 */
	function render($menu = null) {
		$out = '';
		foreach($menu as $caption => $config) {
			$out = $out.'<li><h2>'.$caption.'</h2>'.
				$this->Html->nestedList($this->parse($config)).'</li>';
		}
		$out = '<ul>'.$out.'</ul>';
		$out = $this->Html->div('menu', $out);
		return $out;
	}
 
	/** 
	 * Transforms configuration array in to array of hyperlinks recursively.
	  * Returns arraly of list items.
	 */
	function parse($config) {
		$out = array();
		$here = Router::url(substr($this->here, strlen($this->webroot)-1));
 
		foreach($config as $caption => $link) {
			if (is_array($link)) {
				$out[$this->Html->div('parent', $caption)] = $this->parse($link);
			}
			else {
				if (Router::url($link) != $here) {
					$out[$caption] = $this->Html->link($caption, $link);
				}
				else {
					$out[$caption] = $this->Html->div('current', $caption);
				}
			}
		}
		return $out;
	}
}
?>

As we are going to draw our menu in the default layout to display in on each page we will add the helper to the list of helpers in the AppController:

var $helpers = array('Menu');

Adding CSS of dynamic dropdown menu

You almost ready to view your menu, but first we have to add a css to our layout. In the /app/views/layouts/default.ctp add line o the head section of html:

echo $html-->css('menu');

Then create file /app/webroot/css/menu.css:

/* SVN FILE: $Id: misc.css 6311 2008-01-02 06:33:52Z phpnut $ */
/**
 * Menu App CSS
 */
 
.menu {
	width:12em;
}
.menu ul {
	list-style-image:none;
	list-style-position:outside;
	list-style-type:none;
	margin:0pt;
	padding:0pt;
}
.menu a, .menu h2, .menu  .current, .menu  .parent  {
	border-color:#CCCCCC rgb(136, 136, 136) rgb(85, 85, 85) rgb(187, 187, 187);
	border-style:solid;
	border-width:1px;
	display:block;
	font-family:arial,helvetica,sans-serif;
	font-size:11px;
	font-size-adjust:none;
	font-stretch:normal;
	font-style:normal;
	font-variant:normal;
	font-weight:bold;
	line-height:16px;
	margin:0pt;
	padding:2px 3px;
}
.menu h2 {
	background:#000000 none repeat scroll 0%;
	color:#FFFFFF;
	text-transform:uppercase;
}
.menu a, .menu  .current, .menu  .parent   {
	background:#EFEFEF none repeat scroll 0%;
	color:#000000;
	text-decoration:none;
}
.menu a:hover, .menu  .current, .menu .parent:hover  {
	background:#FFFFFF none repeat scroll 0%;
	color:#AA0000;
}
.menu li {
	position:relative;
	padding:0; margin: 0;
}
.menu ul ul ul {
	left:100%;
	position:absolute;
	top:0pt;
	width:100%;
}
div.menu ul ul ul, div.menu ul ul li:hover ul ul {
	display:none;
}
div.menu ul ul li:hover ul, div.menu ul ul ul li:hover ul {
	display:block;
}

Display dynamic menu on the default layout

The last step is to invoke our helper to render the menu. Add to the body section of layout this line:

echo $menu->render($session->read('Menu.main'));

That’s all! Now you should see the menu on each page of your application.

Menu Example

Later I will add acl checks to menu items.

Posted by Rostislav Palivoda, filed under PHP. Date: April 1, 2008, 2:40 pm |

18 Responses

  1. Eric Brunet Says:

    Nice!
    Instructions are great!
    I got it running in 5 minutes.

    Do you know if I can get it to display horizontaly… fairly quickly?

    Thanks

  2. Rostislav Palivoda Says:

    Yes, have a look at refference url for horizontal css and other css tricks.
    Here is direct link:
    http://www.seoconsultants.com/css/menus/horizontal/

  3. h454 Says:

    in my web page still error

    like this

    Notice: Undefined variable: menu in /var/www/projects/alphasys/app/views/layouts/default.thtml on line 145

    Fatal error: Call to a member function render() on a non-object in /var/www/projects/alphasys/app/views/layouts/default.thtml on line 145

  4. Rostislav Palivoda Says:

    In the AppController helpers list you should add the ‘Menu’ helper.

  5. Wendy Says:

    Sweet! So easy to implement.

    I had been struggling on whether to use a helper, element, etc. for my menu. I create websites where the owners can control their content themselves - a basic CMS app. (who wants to update a typo or add in a new page every other day?), So in order to accommodate the users, this is what I did…

    In the afterSave function of my CMS management:

    Menu information is cached when user updates the webpages (changes the title, adds a new page, etc.)

    In startup function of menu component:

    1. Read the menu information from cached menu. Split information into two arrays: main ($generalMenu) and side ($sideMenu)
    2. If logged in, add the admin menu to the $userMenu (I use the $permittedControllers variable from this tutorial http://www.studiocanaria.com/articles/cakephp_auth_component_users_groups_permissions_revisited)

    Added the appropriate css styling (the links above were great for this).

    And voila! A beautiful site with functionality!

    Thanks a million!

  6. Wendy Says:

    A small change I made to the parse function in the helper to indicate if the parent is the selected page:

    function parse($config) {
    $out = array();
    $here = Router::url(substr($this->here, strlen($this->webroot)-1));

    foreach($config as $caption => $link) {
    if (is_array($link)) {
    if (Router::url($link) != $here) {
    $out[$this->Html->div(’current’, $caption)] = $this->parse($link);
    } else {
    $out[$this->Html->div(’parent’, $caption)] = $this->parse($link);
    }
    }
    else {
    if (Router::url($link) != $here) {
    $out[$caption] = $this->Html->link($caption, $link);
    }
    else {
    $out[$caption] = $this->Html->div(’current’, $caption);
    }
    }
    }
    return $out;
    }

  7. Anil Konsal Says:

    Its a nice tut. I am new to cakePHP, wanted to know what if my parent menu items are coming from ‘Modules’ table and Child menu items are coming from ‘Forms’ table. The rights can me managed upto submenu level.

    How can i Do this in cakePHP ?

    Thanks

  8. Rostislav Palivoda Says:

    Well, in my point of view good solution is to link each menu item to the ACL item of Auth behaviour and check user permissions when building menu as well as checking user permission when he is requesting functionality (double check - one to list available functionality in menu and another to prevent user from accessing restricted functionality).

    About Auth behaviour teke a look at http://lemoncake.wordpress.com/2007/07/15/using-aclbehavior-in-cakephp-12/

  9. João Batista Ladchuk Says:

    Congratulations for the initiative,
    Test the code and run in firefox but usually in Internet Explorer is not working, someone help me?
    Thanks

  10. Rostislav Palivoda Says:

    Look for IE fix section at this page
    http://www.seoconsultants.com/css/menus/tutorial/

  11. João Batista Ladchuk Says:

    Thank you for help and sorry my bad English because living in Brazil
    and not dominate the English.
    Thanks

  12. Rostislav Palivoda Says:

    No problem. Hope this helps.

  13. Miguel Akira Says:

    Hello!
    Thanks for your code! It’s working, except that my old menu was built using data from a table (and not by writing code into a menu.ctp file), and I don’t know how to populate this new menu that I created using your tutorial.

    I was passing an array called $categories to be the parent and an array called $pages to be the children. That was from the app_controller’s beforeFilter to the /views/elements/menu.ctp.

    The problem is, the variables are not visible in the controllers/components/menu.php file.

    Any help on that matter would be appreciated! Thanks! =)

  14. Rostislav Palivoda Says:

    Hi!

    You can access database from the component by instantiating model class ad invoking find and etc. like this:
    $menu = ClassRegistry::init(’Menu’);
    $items = $menu->findAll(…);

    Hope this helps.

  15. abhishek Says:

    thanks..man

    congratulations for great work…many other peoples confused me this is what i needed

  16. babila saronni Says:

    Thanks a loot for your solution and for your service, it helped me to improve my software solution based on cake.
    In order to validate (w3c) my pages I have encountered a small problem with ampersant (&) and in particoular the code in the case ‘current’ daesn’t escape, so in my page souce I had ‘&’ instead of ‘&’, in ‘no current’ case the escape was right, so I wasn’t able to have a unique validated result.

    I resolved this problem adding htmlentities PHP function in this way:

    $out[$caption] = $this->Html->div(’current’, htmlentities($caption));

    instead of the original:

    $out[$caption] = $this->Html->div(’current’, $caption);

    Thank again Polivoda you are great.

    Babila Saronni

  17. Alig Says:

    Thank you very much for this nice component! Was really useful for me to build my menu!

  18. sonu goyal Says:

    this menu code,much help me .

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.