CakePHP: Smarter links

Richard's picture

The Problem

There's a couple of things I wish were present in CakePHP's excellent HtmlHelper.

  1. Links should automatically add a class to themselves when they are a) pointing to a resource in the same controller and b) pointing to themselves.
  2. Able to show/hide themselves.

Why is #1 important? Imagine a primary navigation that runs across the top of a page:

[Home] [Articles] [About]

If the links knew what section they pointed to you could add CSS rules to highlight the homepage link when the user was on the homepage, the Article link when the user was on *any* page handled by the ArticleController, etc.

And #2? If you could easily toggle their visibility you could for example, only show edit links to users with edit privileges.

The Solution

Enter the LinkHelper. This helper has only one method (link) which does all the work we need:

(note, minor update to handle urls passed as an array)

app/views/helpers/link.php

class LinkHelper extends AppHelper {
 
	var $helpers = array(
		"Html"
	);
 
	function link($title, $url = null, $display = true, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
 
		if (!$display) {
 
			// do not display this link
 
			return "";
 
		}
		else {
 
			// display the link
 
			// parse the current url into its components
			$here = Router::parse($this->params['url']['url']);
 
			// parse the destination url into its components
			if (is_array($url)) {
 
				$destination = $url;
 
			}
			else {
 
				$destination = Router::parse($url);
 
			}
 
			if (!isset($destination['controller'])) {
 
				$destination['controller'] = $this->params['controller'];
 
			}
 
			$class = "";
 
			if ($here['controller'] == $destination['controller']) {
 
				// link is to another action within this controller
				$class .= " current_controller";
 
				if ($here['action'] == $destination['action']) {
 
					// link is to the current action in this controller
					$class .= " current_action";
 
				}
 
				if (isset($htmlAttributes['class'])) {
 
					// we already have a class attribute, append our classes
					$htmlAttributes['class'] .= $class;
 
				}
				else {
 
					// class not set, add ours
					$htmlAttributes['class'] = trim($class);
 
				}
 
			}
 
			// build the link
			$link = $this->Html->link($title, $url, $htmlAttributes, $confirmMessage, $escapeTitle);
 
			// return the link
			return $this->output($link);
 
		}
 
	}
 
}

What Is It Doing?

Firstly, its checks the $display parameter. If it's false, then it returns nothing.

Why is this useful? It means you can attach a rule to your links to determine if they should be rendered or not:

<?php echo $link-link("Edit this article", "/articles/edit/1", $is_admin) ?>

The link will only be visible when $is_admin is true. $is_admin is a view variable I set in my app's as part of the authentication process to indicate whether or not the current user has admin privileges.

You can embed other logic to show/hide the link too:

<?php echo $link-link("Edit this article", "/articles/edit/1", ($article['Article']['user_id'] == $auth_user['User']['id'])) ?>

The link will only be visible when the current user is the author of the article.

Secondly, the method checks if the link points to a page handled by the current controller. If it does, it appends "current_controller" to the class of the link.

This is useful for those primary nav links. The current link will have a class of "current_controller" and can be styled differently to the others (very handy for tabs!)

<ul>
	<li><?echo $link->link("Home", "/") ?></li>
	<li><?echo $link->link("Articles", "/articles/" ?></li>
	<li><?echo $link->link("About", "/about" ?></li>
</ul>

Thirdly, the method checks if the link points to the current action of the current controller. If it does, it appends "current_action" to the class of the link.

Comments

Richard's picture

A minor tweak...

Updated the helper to handle urls passed as array("controller"=>"foo", "action"=>"bar")

Anonymous's picture

Hey thanks,   This is an

Hey thanks,
 
This is an great helper.......
 
Thanks a lot..................

Anonymous's picture

home page

seems it does not work when link is '/'

Anonymous's picture

you

you mean:

app/views/helpers/link.php ??

Richard's picture

Thanks for the correction

Thanks for the correction Chris :-)

Anonymous's picture

some modifications

here is a small fix to make it identify the view pages for my cms ( like /contents/view/1 etc )

<?php class LinkHelper extends AppHelper {
 
	var $helpers = array(
		"Html"
	);
 
	function link($title, $url = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
 
 
			// parse the current url into its components
			$here = Router::parse($this->params['url']['url']);
 
			// parse the destination url into its components
			if (is_array($url)) {
 
				$destination = $url;
 
			}
			else {
 
				$destination = Router::parse($url);
 
			}
 
			if (!isset($destination['controller'])) {
 
				$destination['controller'] = $this->params['controller'];
 
			}
 
			$class = "";
 
			if ($here['controller'] == $destination['controller']) {
 
				// link is to another action within this controller
				$class .= " current_controller";
 
				if ($here['action'] == $destination['action']) {
 
					// link is to the current action in this controller
					$class .= " current_action";
 
					if(isset($here['pass']) && !empty($here['pass'])){
 
						$match = false;
 
						foreach($here['pass'] as $key => $val){
 
							if(isset($destination[$key]) && $destination[$key] == $val){
 
								$match = true;
 
							}else{
 
								$match = false;
								break;
							}
						}
 
						if($match){
 
							$class .= " active withallmatch";
						}
 
					}else{
 
						$class .= " active";
					}					
 
				}
 
				if (isset($htmlAttributes['class'])) {
 
					// we already have a class attribute, append our classes
					$htmlAttributes['class'] .= $class;
 
				}
				else {
 
					// class not set, add ours
					$htmlAttributes['class'] = trim($class);
 
				}
 
			}
 
			// build the link
			$link = $this->Html->link($title, $url, $htmlAttributes, $confirmMessage);
 
			// return the link
			return $this->output($link);
 
		}
 
 
}
 
?>

i added the part where it checks for the passed param matches and then adds an active class to the exiting classes.

Anonymous's picture

Works like a charm on cake 1.3.4

Thanks you so much for this! It works like a charm on CakePHP 1.3.4.
I added some code so that it also recognize a webpage by its slug.
For example I have an articles controller, with a view action that has a slug parameter to know what article to show.
With this little extra a class is also added for the specific article showed on the page :

if ($here['action'] == $destination['action']) {
    // link is to the current action in this controller
    $class .= " current_action";
    if ($here['action'] == 'view') {
        if ($here['slug'] == $destination['slug']) {
            $class .= " current_view"; 
        }
    }
}

Thanks again for this simple but truly efficient helper ;)

Anonymous's picture

I think you're missing some

I think you're missing some parentheses on the second and third PHP lines in your "secondly" example. Thanks for this tip though!

Anonymous's picture

CakePHP 2.0

Hey thanks for the helper. By the way to make it cakePHP compliant you basically have to change line 14
$here = Router::parse($this->params['url']);

to

$here = $this->params->params;

Works for me. Oh and I used Rajesh Sharma's version.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account, used to display your avatar.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. Beside the tag style "<foo>" it is also possible to use "[foo]".

More information about formatting options