How To Customize CMenu Output

CMenu comes with a lot of great customization options built in but sometimes you need it to do more.

Here is the Problem

I need to alter the way CMenu works and this time built in parameter settings won't help.

Here is the code I received from a designer

[html]
 <ul id="search-type">
            <li><a class="active" href="#s01"><span>Internet</span></a></li>
            <li><a href="#s02"><span>Firms</span></a></li>
            <li><a href="#s03"><span>Articles</span></a></li>
            <li><a href="#s04"><span>Images</span></a></li>
            <li><a href="#s05"><span>News</span></a></li>
 </ul>

As you can see the active class is on the link tag and not on the list item tag.

The solution is to extend the CMenu class and Override the renderMenuRecursive on line 171 of CMenu

The first thing I want to do is stop the active class from being added to the list item tag. On line 180 of CMenu.php you will find this if statement

if($item['active'] && $this->activeCssClass!='')
   $class[]=$this->activeCssClass;

I could just comment out that line and that would stop the active css class from being put into the class array and eventually $options['class']. But I want to be able to have something what will allow me to turn this feature on and off from the the CMenu widget call.

What I am going to do is start by creating a class that is going to extend CMenu. I am going to copy over the run (we need this or our extension won't run) and renderMenuRecursive functions (this is going to be where we make some code changes) and lastly I will add a public variable activateItemsOuter. The activateItemsOuter will be set to true by default so we will only have to add an activateItemsOuter parameter and set it to false when we want to use the modified way.

At this point you should have something that looks like this

<?php

Yii::import('zii.widgets.CMenu');
class MyMenu extends CMenu {


    public $activateItemsOuter = true;

    //need to include this for our function to run
    public function run()
        {
               $this->renderMenu($this->items);
        }

 
    protected function renderMenuRecursive($items)
        {
              //... code from function
                
        }

}


?> 

Now back to line 180. We now have a parameter that we can set to true or false and we can check if we want to do it the original(default) way or the modified way.

The second thing we need to do is handle the actions for the modified way. If we flagged MyMenu to work the modified way then we need to put the active class into $item['linkOptions']['class'] and we need to make sure that if there is already a class set we need to append to that class and not overwrite it. So lets replace the whole if statement on line 180 with this new piece of code

if($item['active'] && $this->activeCssClass!='') {
   // if we are on a default/true setting do it the old way
   if($this->activateItemsOuter) {$class[]=$this->activeCssClass;}
   // we are on the new way setting so put it in linkoptions
   else {
        if(isset($item['linkOptions'])) {
            $item['linkOptions']= array(
                'class'=>$item['linkOptions']['class'].' '.$this->activeCssClass,
            );
        }
        else {
            $item['linkOptions']= array('class'=>$this->activeCssClass);
        }
   }
}

Now we call our menu widget we are going to use our MyMenu class instead of CMenu and if we want the active class on the li tag then we add no extra parameters and if we want the active class on the ahref tag then we add an activateItemsOuter parameter and set it to false.

Here is an example call

<?php $this->widget('application.components.MyMenu', array(
   'activateItemsOuter'=>false,
    'linkLabelWrapper' => 'span',
    'activateItems' => true,
    'id' => 'search-type',
    'items' => array(
       array('label' => 'Home', 'url' => array('/site/index')),
       array('label' => 'Add Your Business', 'url' => array('bdlisting/create')),
    ),
));
?>

Now you should be armed with the power to Adjust CMenu to suit any menu you type out there.

Here is the full class

<?php
        Yii :: import('zii.widgets.CMenu');
        class MyMenu extends CMenu {
        // must set this to allow  parameter changes in CMenu widget call
            public $activateItemsOuter = true;

            public function run() {
                $this->renderMenu($this->items);
            }
   
            protected function renderMenuRecursive($items) {
                $count = 0;
                $n = count($items);
                foreach ($items as $item) {
                    $count++;
                    $options = isset ($item['itemOptions']) ? $item['itemOptions'] : array();
                    $class = array();
                    if ($item['active'] && $this->activeCssClass != '') {
                        if ($this->activateItemsOuter) {
                            $class [] = $this->activeCssClass;
                        }
                        else {
                            if (isset ($item['linkOptions'])) {
                                $item['linkOptions'] = array('class' => $item['linkOptions']['class'] . ' ' . $this->activeCssClass);

                            }
                            else {
                                $item['linkOptions'] = array('class' => $this->activeCssClass);
                            }
                        }
                    }
                    if ($count === 1 && $this->firstItemCssClass != '')
                        $class [] = $this->firstItemCssClass;
                    if ($count === $n && $this->lastItemCssClass != '')
                        $class [] = $this->lastItemCssClass;
                    if ($class !== array()) {
                        if (empty ($options['class']))
                            $options['class'] = implode(' ', $class);
                        else
                            $options['class'] .= ' ' . implode(' ', $class);
                    }
                    echo CHtml :: openTag('li', $options);
                    if (isset ($item['url'])) {
                        $label = $this->linkLabelWrapper === null ? $item['label'] : '<' . $this->linkLabelWrapper . '>' . $item['label'] . '</' . $this->linkLabelWrapper . '>';
                        $menu = CHtml :: link($label, $item['url'], isset ($item['linkOptions']) ? $item['linkOptions'] : array());
                    }
                    else
                        $menu = CHtml :: tag('span', isset ($item['linkOptions']) ? $item['linkOptions'] : array(), $item['label']);
                    if (isset ($this->itemTemplate) || isset ($item['template'])) {
                        $template = isset ($item['template']) ? $item['template'] : $this->itemTemplate;
                        echo strtr($template, array('{menu}' => $menu));
                    }
                    else
                        echo $menu;
                    if (isset ($item['items']) && count($item['items'])) {
                        echo "\n" . CHtml :: openTag('ul', $this->submenuHtmlOptions) . "\n";
                        $this->renderMenuRecursive($item['items']);
                        echo CHtml :: closeTag('ul') . "\n";
                    }
                    echo CHtml :: closeTag('li') . "\n";
                }
            }
        }
    ?>