The following code will set the class nav on the first level UL
$mainNav = public_nav_main();
$mainNav->setUlClass('nav')->setUlId('main-menu-left');
However im using bootstrap and so want the second level ul to have the class 'dropdown-menu'
I cant seem to find a reference to get this sorted.
Zend is being used as the base structure in the software im using, Omeka. Unfortunately Omeka doesnt have a way to do this natively so I am having to dive into the underlying Zend FW although I dont want to modify that too much as it might be changed.
You might just want to write a totally new View Helper based off Zend_View_Helper_Navigation_HelperAbstract.
Looking on GitHub for a bootstrap compatible helper based on that abstract I did encounter this: https://github.com/michaelmoussa/zf1-navigation-view-helper-bootstrap/blob/master/library/ZFBootstrap/View/Helper/Navigation/Menu.php which takes an interesting approach, post processing the markup generated from the out-of-the-box helpers.
I took a slightly different approach recently and just hacked the heck out of  Zend_View_Helper_Navigation_Menu.  Here is a unified diff summarizing those changes: http://pastebin.com/mrJG8QCt  Better though to extend the class...
I didn't deal with sub-menus, however the issues I ran into were...
aria-role to <li> elements.This code shows the methods you need to tweak:
class MyMenu extends Zend_View_Helper_Navigation_Menu
{
/**
 * Want a way to set aria role on menu li elements because its 2015 yo
 *
 * @var string
 */
protected $_liRole = '';
/**
 * Workaround so I can render the damn thing twice on the same page and not collide IDs on the <a>'s
 * Issue arose when adopting bootstrap and rendering both full page nav and collapsed nav bar
 *
 * @var string
 */
protected $_idAlias = '';
public function setLiRole($liRole)
{
    if (is_string($liRole)) {
        $this->_liRole = $liRole;
    }
    return $this;
}
public function getLiRole()
{
    return $this->_liRole;
}
public function setIdAlias($alias)
{
    $this->_idAlias = $alias;
    return $this;
}
public function getIdAlias()
{
    return $this->_idAlias;
}
public function renderMenu(Zend_Navigation_Container $container = null, array $options = array())   
{
    $this->setLiRole($options['liRole']);
    $this->setIdAlias($options['idAlias']);
    return parent::renderMenu($container, $options);
}
/**
 * Returns an HTML string containing an 'a' element for the given page if
 * the page's href is not empty, and a 'span' element if it is empty
 *
 * Overrides {@link Zend_View_Helper_Navigation_Abstract::htmlify()}.
 *
 * @param  Zend_Navigation_Page $page  page to generate HTML for
 * @return string                      HTML string for the given page
 */
public function htmlify(Zend_Navigation_Page $page)
{
    // get label and title for translating
    $label = $page->getLabel();
    $title = $page->getTitle();
    // translate label and title?
    if ($this->getUseTranslator() && $t = $this->getTranslator()) {
        if (is_string($label) && !empty($label)) {
            $label = $t->translate($label);
        }
        if (is_string($title) && !empty($title)) {
            $title = $t->translate($title);
        }
    }
    // get attribs for element
    $attribs = array(
        'id'     => $this->getIdAlias() . $page->getId(),
        'title'  => $title,
        'class'  => $page->getClass()
    );
    // does page have a href?
    if ($href = $page->getHref()) {
        $element = 'a';
        $attribs['href'] = $href;
        $attribs['target'] = $page->getTarget();
    } else {
        $element = 'span';
    }
    return '<' . $element . $this->_htmlAttribs($attribs) . '><span class="span-nav-icon"></span><span>'
         . str_replace(chr(32), ' ', $this->view->escape($label))
         . '</span></' . $element . '>';
}
/**
 * Normalizes given render options
 *
 * @param  array $options  [optional] options to normalize
 * @return array           normalized options
 */
protected function _normalizeOptions(array $options = array())
{
    if (isset($options['indent'])) {
        $options['indent'] = $this->_getWhitespace($options['indent']);
    } else {
        $options['indent'] = $this->getIndent();
    }
    if (isset($options['ulClass']) && $options['ulClass'] !== null) {
        $options['ulClass'] = (string) $options['ulClass'];
    } else {
        $options['ulClass'] = $this->getUlClass();
    }
    if (isset($options['liRole']) && $options['liRole'] !== null) {
        $options['liRole'] = (string) $options['liRole'];
    } else {
        $options['liRole'] = $this->getLiRole();
    }
    if (isset($options['idAlias']) && $options['idAlias'] !== null) {
        $options['idAlias'] = (string) $options['idAlias'];
    } else {
        $options['idAlias'] = '';
    }
    if (array_key_exists('minDepth', $options)) {
        if (null !== $options['minDepth']) {
            $options['minDepth'] = (int) $options['minDepth'];
        }
    } else {
        $options['minDepth'] = $this->getMinDepth();
    }
    if ($options['minDepth'] < 0 || $options['minDepth'] === null) {
        $options['minDepth'] = 0;
    }
    if (array_key_exists('maxDepth', $options)) {
        if (null !== $options['maxDepth']) {
            $options['maxDepth'] = (int) $options['maxDepth'];
        }
    } else {
        $options['maxDepth'] = $this->getMaxDepth();
    }
    if (!isset($options['onlyActiveBranch'])) {
        $options['onlyActiveBranch'] = $this->getOnlyActiveBranch();
    }
    if (!isset($options['renderParents'])) {
        $options['renderParents'] = $this->getRenderParents();
    }
    return $options;
}
 /**
 * Renders the deepest active menu within [$minDepth, $maxDeth], (called
 * from {@link renderMenu()})
 *
 * @param  Zend_Navigation_Container $container  container to render
 * @param  array                     $active     active page and depth
 * @param  string                    $ulClass    CSS class for first UL
 * @param  string                    $indent     initial indentation
 * @param  int|null                  $minDepth   minimum depth
 * @param  int|null                  $maxDepth   maximum depth
 * @return string                                rendered menu
 */
protected function _renderDeepestMenu(Zend_Navigation_Container $container,
                                      $ulClass,
                                      $indent,
                                      $minDepth,
                                      $maxDepth)
{
    if (!$active = $this->findActive($container, $minDepth - 1, $maxDepth)) {
        return '';
    }
    // special case if active page is one below minDepth
    if ($active['depth'] < $minDepth) {
        if (!$active['page']->hasPages()) {
            return '';
        }
    } else if (!$active['page']->hasPages()) {
        // found pages has no children; render siblings
        $active['page'] = $active['page']->getParent();
    } else if (is_int($maxDepth) && $active['depth'] +1 > $maxDepth) {
        // children are below max depth; render siblings
        $active['page'] = $active['page']->getParent();
    }
    $ulClass = $ulClass ? ' class="' . $ulClass . '"' : '';
    $html = $indent . '<ul' . $ulClass . '>' . self::EOL;
    $liRole = (! empty($this->getLiRole())) ? "role=\"{$this->getLiRole()}\"" : "";
    foreach ($active['page'] as $subPage) {
        if (!$this->accept($subPage)) {
            continue;
        }
        $liClass = $subPage->isActive(true) ? ' class="active"' : '';
        $html .= $indent . '    <li' . $liClass . ' ' . $liRole . '>' . self::EOL;
        $html .= $indent . '        ' . $this->htmlify($subPage) . self::EOL;
        $html .= $indent . '    </li>' . self::EOL;
    }
    $html .= $indent . '</ul>';
    return $html;
}
/**
 * Renders a normal menu (called from {@link renderMenu()})
 *
 * @param  Zend_Navigation_Container $container   container to render
 * @param  string                    $ulClass     CSS class for first UL
 * @param  string                    $indent      initial indentation
 * @param  int|null                  $minDepth    minimum depth
 * @param  int|null                  $maxDepth    maximum depth
 * @param  bool                      $onlyActive  render only active branch?
 * @return string
 */
protected function _renderMenu(Zend_Navigation_Container $container,
                               $ulClass,
                               $indent,
                               $minDepth,
                               $maxDepth,
                               $onlyActive)
{
    $html = '';
    // find deepest active
    if ($found = $this->findActive($container, $minDepth, $maxDepth)) {
        $foundPage = $found['page'];
        $foundDepth = $found['depth'];
    } else {
        $foundPage = null;
    }
    // create iterator
    $iterator = new RecursiveIteratorIterator($container,
                        RecursiveIteratorIterator::SELF_FIRST);
    if (is_int($maxDepth)) {
        $iterator->setMaxDepth($maxDepth);
    }
    // iterate container
    $prevDepth = -1;
    foreach ($iterator as $page) {
        $depth = $iterator->getDepth();
        $isActive = $page->isActive(true);
        if ($depth < $minDepth || !$this->accept($page)) {
            // page is below minDepth or not accepted by acl/visibilty
            continue;
        } else if ($onlyActive && !$isActive) {
            // page is not active itself, but might be in the active branch
            $accept = false;
            if ($foundPage) {
                if ($foundPage->hasPage($page)) {
                    // accept if page is a direct child of the active page
                    $accept = true;
                } else if ($foundPage->getParent()->hasPage($page)) {
                    // page is a sibling of the active page...
                    if (!$foundPage->hasPages() ||
                        is_int($maxDepth) && $foundDepth + 1 > $maxDepth) {
                        // accept if active page has no children, or the
                        // children are too deep to be rendered
                        $accept = true;
                    }
                }
            }
            if (!$accept) {
                continue;
            }
        }
        $liRole = (! empty($this->getLiRole())) ? "role=\"{$this->getLiRole()}\"" : "";
        // make sure indentation is correct
        $depth -= $minDepth;
        $myIndent = $indent . str_repeat('        ', $depth);
        if ($depth > $prevDepth) {
            // start new ul tag
            if ($ulClass && $depth ==  0) {
                $ulClass = ' class="' . $ulClass . '"';
            } else {
                $ulClass = '';
            }
            $html .= $myIndent . '<ul' . $ulClass . '>' . self::EOL;
        } else if ($prevDepth > $depth) {
            // close li/ul tags until we're at current depth
            for ($i = $prevDepth; $i > $depth; $i--) {
                $ind = $indent . str_repeat('        ', $i);
                $html .= $ind . '    </li>' . self::EOL;
                $html .= $ind . '</ul>' . self::EOL;
            }
            // close previous li tag
            $html .= $myIndent . '    </li>' . self::EOL;
        } else {
            // close previous li tag
            $html .= $myIndent . '    </li>' . self::EOL;
        }
        // render li tag and page
        $liClass = $isActive ? ' class="active"' : '';
        $html .= $myIndent . '    <li' . $liClass . ' ' . $liRole . '>' . self::EOL
               . $myIndent . '        ' . $this->htmlify($page) . self::EOL;
        // store as previous depth for next iteration
        $prevDepth = $depth;
    }
    if ($html) {
        // done iterating container; close open ul/li tags
        for ($i = $prevDepth+1; $i > 0; $i--) {
            $myIndent = $indent . str_repeat('        ', $i-1);
            $html .= $myIndent . '    </li>' . self::EOL
                   . $myIndent . '</ul>' . self::EOL;
        }
        $html = rtrim($html, self::EOL);
    }
    return $html;
}   
}
Admittedly a lot of code. Might be good to diff the class you have now against this http://pastebin.com/qiD2ULsz - and then seeing what the touch points are, creating a new extending class. Really just the new properties and some "tweaks" to the string concatenation it does to render the markup.
I don't specifically address class on "second level ul's" but passing in an additional property would be trivial and follow the same changes I did make.
Hope this helps some. ZF 1.x shows its age a bit and these view helpers were never that great. The underlying Nav code isn't too bad, so again, maybe just start from scratch and write your own code to render a Zend Nav Container. Good luck.
This is admittedly an ugly hack, but you could do it by processing the output of public_nav_main() with a regular expression.  So in the header.php file you would replace:
echo public_nav_main();
with
echo preg_replace( "/(?<!\/)ul(?!.*?nav)/", 'ul class="dropdown-menu"', public_nav_main() );
This will only work if you only have 2 levels in your menu, since the above regular expression will also add the class="dropdown-menu" to all ul elements below the top level ul.
public_nav_main()
class attributes in your 2nd and lower level ul elements in the event that they already have a class attributeIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With