CSS Menu Helper
This is a helper and CSS file for displaying a dropdown menu on your site.
This can be used to display a dropdown menu with sub menus. There are currently 3 kinds of menu: menus where elements are listed down, and sub menus pop right, menus where submenus pop left, and menus where the top level elements are listed horizontally and sub menus pop right (except for the first, which pops down).
Demo site: http://vectorjohn.com/cake/
The way the CSS works now, you can only have a maximum of 5 levels deep in your menu structure. If you have more, they are all displayed when you hover over the 5th level menu item. The limit can be increased by adding 2 lines to the CSS file.
To use it, just put the file css_menu.php in your helpers directory, and put the file css_menu.css in your webroot/css directory. You also need the 3 arrow image files, which are referenced by the CSS file. You could make your own, but here they are. They also belong in webroot/css:
http://vectorjohn.com/cake/files/arrow-left.png http://vectorjohn.com/cake/files/arrow-right.png http://vectorjohn.com/cake/files/arrow-down.png
Then in a view, you can do:
Download code
If you want to use it in any view, you can add 'CssMenu' to your $helpers array.
It would be easy to make a menu model to go along with this, and store a menu structure in the database. I might just do that.
Here is the code for the helper, css_menu.php:
Here is the CSS file, css_menu.css, to go in your webroot/css folder:
Download code
Demo site: http://vectorjohn.com/cake/
The way the CSS works now, you can only have a maximum of 5 levels deep in your menu structure. If you have more, they are all displayed when you hover over the 5th level menu item. The limit can be increased by adding 2 lines to the CSS file.
To use it, just put the file css_menu.php in your helpers directory, and put the file css_menu.css in your webroot/css directory. You also need the 3 arrow image files, which are referenced by the CSS file. You could make your own, but here they are. They also belong in webroot/css:
http://vectorjohn.com/cake/files/arrow-left.png http://vectorjohn.com/cake/files/arrow-right.png http://vectorjohn.com/cake/files/arrow-down.png
Then in a view, you can do:
Download code
echo $cssMenu->menu($menu,'down');
To make a horizontal menu, for example. The other options are 'left' and 'right'. The $menu argument is explained in the comments of the code.If you want to use it in any view, you can add 'CssMenu' to your $helpers array.
It would be easy to make a menu model to go along with this, and store a menu structure in the database. I might just do that.
Here is the code for the helper, css_menu.php:
Helper Class:
Download code
<?php
/*
* CSS menu helper.
* Author: John Reeves.
*/
class CssMenuHelper extends Helper{
var $helpers = array('Html');
/*
* display a menu list.
* @arg $data: a nested associative array. The keys are the text that
* is displayed for that menu item. If the value is an array, it is
* treated as a sub menu, with the same format. Otherwise it is
* interpreted as a URL to be used for a link.
* @arg $type: the type of array. Can be right, left, or down.
*/
function menu($data=array(), $type='right'){
global $cm_css_inc;
$out ='';
if($cm_css_inc != true){
$cm_css_inc = true;
$out .= $this->Html->css('css_menu');
}
return $this->output($out . $this->_cm_render($data, $type));
}
/* render a menu.
* This is a helper for recursion. The arguments are the
* same as for $this->menu().
*/
function _cm_render($data=array(), $type='right'){
$out='';
if($data == array()) return;
if(is_array($data)){
$out .= "<ul class=\"css_menu cm_$type\">\n";
foreach($data as $key => $item){
if(is_array($item)){
$out .= '<li class="parent">'. $key. "\n";
$out .= $this->_cm_render($item, $type);
$out .= "</li>\n";
}else{
$out .= '<li><a href="'. $item. '">'. $key. '</a></li>'. "\n";
}
}
$out .= "</ul>\n";
}
return $out;
}
}
?>
Here is the CSS file, css_menu.css, to go in your webroot/css folder:
Download code
/*
* CSS for css menu helper.
* Author: John Reeves
* Credit given to Jake Gordon, author of Nice Menus module
* for Drupal, for much of the idea.
*/
ul.css_menu,
ul.css_menu ul{
list-style: none;
margin: 0;
padding: 1px;
}
ul.css_menu ul{
display: none;
position: absolute;
margin-right: 0;
z-index: 5;
}
ul.css_menu li{
margin: 0;
padding: .1em;
}
ul.css_menu li{
float: left;
border: 1px solid black;
background-color: #99fefd;
width: 6em;
position: relative;
left: 2px;
top: 0;
}
ul.css_menu ul li{
display: block;
}
ul.css_menu:after{
clear: both;
display: block;
height: 0;
visibility: hidden;
}
ul.css_menu li:hover{
background-color: #66cbca;
}
/*
* Hide sub menus that are not hovered over.
* It only works for 5 levels deep. If for some reason you need
* more, it should be easy to see how to copy the last selector and
* add one more li:hover. Same goes for the display: block; part below.
*/
ul.css_menu ul,
ul.css_menu li:hover ul ul,
ul.css_menu li:hover li:hover ul ul,
ul.css_menu li:hover li:hover li:hover ul ul{
display: none;
}
/* show hovered submenus */
ul.css_menu li:hover ul,
ul.css_menu li:hover li:hover ul,
ul.css_menu li:hover li:hover li:hover ul,
ul.css_menu li:hover li:hover li:hover li:hover ul{
display: block;
}
/* RIGHT type menus */
ul.cm_right li{
float: none;
}
ul.cm_right li.parent:hover,
ul.cm_right li li.parent:hover{
background: #66cbca url(arrow-right.png) right center no-repeat;
}
ul.cm_right li.parent,
ul.cm_right li li.parent{
background-image: url(arrow-right.png);
background-position: right center;
background-repeat: no-repeat;
}
ul.cm_right li ul,
ul.cm_right li ul li.parent ul{
left: 5.9em;
top: -2px;
}
/* LEFT type menus */
ul.cm_left li{
float: none;
padding-left: 15px;
}
ul.cm_left li.parent:hover,
ul.cm_left li li.parent:hover{
background: #66cbca url(arrow-left.png) left center no-repeat;
}
ul.cm_left li.parent,
ul.cm_left li li.parent{
background-image: url(arrow-left.png);
background-position: left center;
background-repeat: no-repeat;
}
ul.cm_left li ul,
ul.cm_left li ul li.parent ul{
left: -7.8em;
top: -2px;
}
/* DOWN type menus */
ul.cm_down li ul{
top: 1.5em;
left: -4px;
}
ul.cm_down li ul li.parent ul{
left: 5.9em;
top: -0.1em;
}
ul.cm_down li.parent:hover{
background: #66cbca url(arrow-down.png) right center no-repeat;
}
ul.cm_down li.parent{
background-image: url(arrow-down.png);
background-position: right center;
background-repeat: no-repeat;
}
ul.cm_down li li.parent:hover{
background: #66cbca url(arrow-right.png) right center no-repeat;
}
ul.cm_down li li.parent{
background-image: url(arrow-right.png);
background-position: right center;
background-repeat: no-repeat;
}
Comments
Comment
1 Problem in IE 6.0
Comment
2 IE6 CSS Thingy
IE6 doesn't support the :hover pseudo-class on anything except links (). You can simulate it like this: (best put in a CC so it will validate)
For each rule that has a ":hover" clause, do this: (example for
ul.cm_down li.parent:hoverul.cm_down li.parent {
/* Add class 'hover' when hovered */
stupid-ie-be-more-funny: expression(this.onmouseover = new Function("this.className += ' hover';"));
}
ul.cm_down li.parent.hover {
/* Duplicate bit from the :hover rule */
background: #66cbca url(arrow-right.png) right center no-repeat;
/* Delete 'hover' class when unhovered */
stupid-ie-be-more-funny: expression(this.onmouseout = new Function("this.className=(' '+this.className+' ').replace(' hover ','');"));
}
Question
3 not working right
am i passing wrong data?
Bug
4 gap between parent and child breaks menu
echo $cssMenu->menu(array('Activities'=> array('Add'=> $this->base.'/activities/add','List'=> $this->base.'/activities/index')),'down');Comment
5 Re:not working right
ian vill, I had the same problem, so I've made a couple of changes in the code. My array of Links looks like:
Array
(
[0] => Array
(
[Link] => Array
(
[id] => 6
url => http://www.google.com
[category_id] => 1
[display] => Y
[parent_id] => 0
[title] => Home
[description] => Back to main
)
)
)
In case of child links, it will show the the parent_id (which is not null, of non-zero).
So, every time, doing(in controller):
Set menu method in the Link model grabs the results and create a nested associative array fitting the helper of John above:// $type stands for category name and related links in it
function get_menu($type){
$navbar_links = $this->Category->Link->findAll(
array('Category.name_eng'=>$type,
'Link.display' =>'Y'
));
$navbar = $this->Link->set_menu($navbar_links);
$this->set('navbar',$navbar);
}
(in models/link.php)
The code is a bit dirty, but workingfunction set_menu($data){
$parents = array();
$children = array();
$menu = array();
foreach($data as $ln){
if(empty($ln['Link']['parent_id'])){
$parents[$ln['Link']['id']] = $ln['Link'];
} else {
$children[$ln['Link']['id']] = $ln['Link'];
}
}
foreach($data as $k=>$ln){
if(empty($ln['Link']['parent_id'])){
$chld = $this->get_children($ln['Link'],$children);
if(!empty($chld)){
$menu[$ln['Link']['title']] = $chld;
} else {
$menu[$ln['Link']['title']] = $ln['Link']['url'];
}
}
}
unset($parents);
unset($children);
return $menu;
}
/*
* @array $ln - array of Link table entity
*/
function get_children($parent,$children){
$arr = array();
$parent_id = $parent['id'];
foreach($children as $k=>$ch){
if($parent_id == $ch['parent_id']){
$arr[$ch['title']] = $ch['url'];
}
}
if(!empty($arr)){
return $arr;
}
return;
}