debuggable

 
Contact Us
 

MacGyver menu for CakePHP - What's the active menu item?

Posted on 8/7/07 by Felix Geisendörfer

Deprecated post

The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.

Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.

Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).

Hey folks,

I usually don't talk much about view coding around here but I think there are some interesting things to say about it. One of the challenges many of us have faced before and will face in future is building a menu. Now this of course is very simple assuming that we're just shooting for a static <ul> with some <li>'s containing the links. However the tricky part is to figure out what is the active menu item. I had some code doing this in the past but tried out a new flavor of it today which I call the MacGyver menu for CakePHP as it's a little dirty yet powerful and easy to maintain.

So without much further introduction here is the code I suggest:

<ul>
<?php
$navigation = array(
  'Home' => '/'
  , 'Posts' => '/posts'
  , 'New Post' => '/posts/new'
  , 'Login' => '/users/login'
);

$matchingLinks = array();
foreach ($navigation as $link) {
  if (preg_match('/^'.preg_quote($link, '/').'/', substr($this->here, strlen($this->base)))) {
    $matchingLinks[strlen($link)] = $link;
  }
}
krsort($matchingLinks);
$activeLink = ife(!empty($matchingLinks), array_shift($matchingLinks));

$out = array();
foreach ($navigation as $title => $link) {
  $out[] = '<li>'.$html->link($title, $link, ife($link == $activeLink, array('class' => 'active'))).'</li>';
}

echo join("\n", $out);
?>
</ul>

The advantages of this code are the following:

  • It's easy to implement, just copy your code and modify the $navigation array
  • It will add a class 'active' to highlight the menu item one just clicked on
  • If there is no direct match for the current url then the most specific (longest) link url that is matching will be assumed to be active. E.g. in the example above /posts/edit will make 'Posts' the active menu item since it's link '/posts' is the most specific one matching the current url.

I release this in hope somebody might find it useful, but I'd also be interested in hearing your approaches to this problem - use the CakeBin to paste some code if you like.

If your menu logic is more complex then the one shown above I suggest you to create either an element or an helper for it.

-- Felix Geisendörfer aka the_undefined

 
&nsbp;

You can skip to the end and add a comment.

Daniel Hofstetter said on Jul 09, 2007:

It is imho bad style to have such logic in your view, put it in a helper. Then you can even test your logic with some unit tests ;-)

MP:Schorsch said on Jul 09, 2007:

I haven´t found the ultimate solution to navigations yet.

I started using a menu controller which holds its item inside a modified preeorder tree.

this way i can easily access all parent items (important in deeper nested structures) but i did not tackle the dynamic url mapping (like static in your array) yet.

PHPDeveloper.org said on Jul 09, 2007:

Felix Geisendorfer's Blog: MacGyver menu for CakePHP - What’s the active menu item?...

...

[...] In a new post to his blog today, Felix Geisendorfer talks about view coding - specifically working with a menu and determining which element is active. One of the challenges many of us have faced before and will face in future is building a menu. Now this of course is very simple assuming that we’re just shooting for a static <ul> with some <li>’s containing the links. However the tricky part is to figure out what is the active menu item. I had some code doing this in the past but tried out a new flavor of it today which I call the MacGyver menu for CakePHP as it’s a little dirty yet powerful and easy to maintain. [...]

McFadly said on Jul 10, 2007:

Felix -
Cool post, glad to see how others are approaching this problem. Here's my jQuery solution: http://bin.cakephp.org/saved/21747

jon bennett  said on Jul 10, 2007:

hey Felix,

Have created a helper in cakeforge based on your code, hope you don't mind, it was too useful to leave here alone!

http://cakeforge.org/snippet/detail.php?type=snippet&id=194

Thanks,

Jon

AD7six said on Jul 10, 2007:

Hi Felix,

It's probably worth mentioning that in 1.2, if you use arrays for your urls this gets a lot simpler

(ref: http://www.ad7six.com/MiBlog/MakingTheMostOfTheRouter)

Using this logic you can loop on a menu and in brief:

if $url['plugin'] == $this->plugin
Then you are in the same plugin*

if $url['controller'] == $this->name
Then you are in the same controller

if $url['action'] == $this->action
Then you are in the same function

This doesn't work without a minor change to the router, but I'll mention it anyway
if $url == Router::url(array())

The link is to the page you are currently on

Even if you don't use arrays for your urls you can still make use of this by passing the url to the router to get the url back as an array to then test it.

hth,

AD
*Plugin routes don't work atm unless you add a patch (see tickets)

Felix Geisendörfer said on Jul 12, 2007:

Daniel: I don't think it's bad style. Adding a class to the active link is very much presentation logic and I find myself much more happy using elements for that then helpers as the overhead is much smaller. Besides: There is no reason one couldn't unit test a view, it's probably quite simple actually.
Jon: Everything I post on here is MIT unless stated otherwise. Hm I should put up a sign saying this somewhere - will come with the new blog launch ; ). So feel free to use the code and thanks for doing so.

Ad7Six: You're righton as far as Cake 1.2 goes.

Sergey Egorov  said on Jul 12, 2007:

I have menu definition in database
AppController::beforeRender detects which menu items should be showed and which is active, base on $this->params['controller'] and $this->params['action'] (actually model just returns array of such items :D)

Then layout handles menu drawings :D

So I don't have navigation stuff in real controllers not its views

cakebaker » Element or helper? said on Jul 12, 2007:

[...] In a recent post Felix Geissendörfer presented the “MacGyver Menu”. He put the menu logic directly in the view and wrote: If your menu logic is more complex […] I suggest you to create either an element or an helper for it. [...]

jon bennett said on Jul 13, 2007:

Hey Felix, I seem to have hit a snag with the URL matching, it's not matching URLs without a trailing / - any ideas what the route might be?

http://cakeforge.org/snippet/detail.php?type=snippet&id=194

Thanks,

Jon

jon bennett said on Jul 13, 2007:

I meant 'route cause' above, not just 'route' - doh :)

burzum  said on Aug 28, 2007:

I've modified the above code, is simpler now and faster i think, because preg_match is no longer used, and wrote a helper-method.

You have to use the HtmlHelper in your helper:
var $helpers = array('Html');

And add the method:
http://bin.cakephp.org/view/915506280

But i hope gwoo is going to add it to the HtmlHelper itself :)

Steven  said on Sep 30, 2007:

May seem like a dumb question, but I am still wet behind the ears with CakePHP and am still getting used to views, controllers and basic inner workings of the framework, but how would I go about installing this and accessing/referencing it?

Everything I have been trying/reading leads to me adjusting the tags.ini.php file, which I cannot seem to locate in my install of Cake. I am sure this is very fundamental, but some guidance in this area would be appreciated and might help get the ball rolling for another community member.

Thanks!

Ignatius  said on Jan 04, 2008:

I think its better/easier to use something like this:

- In your controller methods (index, view...):

$this->set('pageId', 'page_sample1');

- In your layout (or where your menu resides):

">

Sample 1
Sample 2

- And finally in your CSS:

#page_sample1 #mnuSample1 { /* styles to apply to active menu (equivalent to .active) }
#page_sample2 #mnuSample2 { /* styles to apply to active menu (equivalent to .active) }

Please, correct me if I'm wrong.

Thanks!

Ignatius  said on Jan 04, 2008:

D'oh! fuc**ng html :)

I think its better/easier to use something like this:

- In your controller methods (index, view...):

$this->set('pageId', 'page_sample1');

- In your layout (or where your menu resides):

<body id="<? echo $pageId ?>">

<ul>
<li><a href="#" id="mnuSample1">Sample 1</a></li>

<li><a href="#" id="mnuSample2">Sample 2</a></li>

</ul>

- And finally in your CSS:

#page_sample1 #mnuSample1 { /* styles to apply to active menu (equivalent to .active) }
#page_sample2 #mnuSample2 { /* styles to apply to active menu (equivalent to .active) }

Please, correct me if I'm wrong.

Thanks!

Jason  said on Aug 18, 2008:

I've been struggling to find the new solution to this problem. I've spend hours searching on google. Could you post a link to the new solution?

redzone said on Sep 21, 2008:

nice post, it's very help me :)

rexford   said on Feb 16, 2009:

Thats great dude. But im new to cake and im trying to incorporate a reset password feature into my application which will send an email to the users, could you pls help me out with a sample code to look on??

Fahd said on Apr 15, 2009:

Thanks, your code was really helpful!

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.