ZF-6941: Zend_View_Navigation_Menu expand sibling of active branch

Description

I want an option to display the menu with all sibling of the active branch and not just the parents branch.

For the exemple in the view helpers documentation display the menu like this :

  • Foo Server ** FAQ ** Editions ** System Requirement
  • Foo Studio
  • Investor Relations
  • News
  • My Account
  • Forums

The code changed in the Menu helper, I had a new option parameter : "expandBranch"


    /**
     * Whether only active branch should be rendered
     *
     * @var bool
     */
    protected $_expandBranch = false;

    /**
     * Sets a flag indicating whether the first depth and expand the active branch
     *
     * @param  bool $flag                        [optional] render the first depth and the active branch
     *                                                      . Default is true.
     * @return Zend_View_Helper_Navigation_Menu  fluent interface, returns self
     */
    public function setExpandBranch($flag = true)
    {
        $this->_expandBranch = (bool) $flag;
        return $this;
    }

    /**
     * Returns a flag indicating whether the first depth and the active branch should be rendered
     *
     * By default, this value is false, meaning the entire menu will be
     * be rendered.
     *
     * @return bool  whether only active branch should be rendered
     */
    public function getExpandBranch()
    {
        return $this->_expandBranch;
    }

    // Render methods:

    /**
     * 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?
     * @param  bool                      $expandBranch  render sibling of the active branch
     * @return string
     */
    protected function _renderMenu(Zend_Navigation_Container $container,
                                   $ulClass,
                                   $indent,
                                   $minDepth,
                                   $maxDepth,
                                   $onlyActive,
                                   $expandBranch)
    {
        $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 ($expandBranch && $depth > $minDepth) { // If expand branch
                // 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 ($page->getParent()->isActive(true)) {
                        // page is a sibling of the active branch...
                        $accept = true;
                    }
                }  
                if (!$isActive && !$accept) {
                    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;
                }
            }

            // 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 . '
' . 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 . ' ' . self::EOL; $html .= $ind . '' . self::EOL; } // close previous li tag $html .= $myIndent . ' ' . self::EOL; } else { // close previous li tag $html .= $myIndent . ' ' . self::EOL; } // render li tag and page $liClass = $isActive ? ' class="active"' : ''; $html .= $myIndent . ' ' . 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 . ' ' . self::EOL . $myIndent . '' . self::EOL; } $html = rtrim($html, self::EOL); } return $html; } /** * Renders helper * * Renders a HTML 'ul' for the given $container. If $container is not given, * the container registered in the helper will be used. * * Available $options: * * * @param Zend_Navigation_Container $container [optional] container to * create menu from. Default * is to use the container * retrieved from * {@link getContainer()}. * @param array $options [optional] options for * controlling rendering * @return string rendered menu */ public function renderMenu(Zend_Navigation_Container $container = null, array $options = array()) { if (null === $container) { $container = $this->getContainer(); } $options = $this->_normalizeOptions($options); if ($options['onlyActiveBranch'] && !$options['renderParents']) { $html = $this->_renderDeepestMenu($container, $options['ulClass'], $options['indent'], $options['minDepth'], $options['maxDepth']); } else { $html = $this->_renderMenu($container, $options['ulClass'], $options['indent'], $options['minDepth'], $options['maxDepth'], $options['onlyActiveBranch'], $options['expandBranch']); } return $html; }

I just had this condition before the activeBranch condition :


          if ($expandBranch && $depth > $minDepth) { // If expand branch
                // 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 ($page->getParent()->isActive(true)) {
                        // page is a sibling of the active branch...
                        $accept = true;
                    }
                }  
                if (!$isActive && !$accept) {
                    continue;
                }         
            } 

Comments

Thanks for the detailed report. I see how this feature might be useful.

Have you signed a CLA? Would you care to implement the expandBranch feature and write tests and docs for it? You probably have a better overview of exactly what needs to be tested etc.

Hello,

Yes I have signed a CLA. I can do it, I will send a patch as soon as possible.

Thanks.

Hope this improvement will find his way into the framework asap. ;)

Hello,

This is the patch to add this improvement. There are the modification of the menu helper, tests, and documentation. I hope it's can be added in the next release.

Thanks.

Is there a new patch for the current ZF version (1.9.2) or is there another (new) way to solve what this improvements makes?

Has this patch already found his way to the framework? If not, when does the patch will be included?

I don't found the path in changelog of new framework release. I don't know when the patch will be included.

I don't test the path on the ZF 1.9.2 version, but I think it will work with the version. There aren't many modification in the navigation helpers (has I see in the changelog). I will test soon with the last version.

For my project, I extend the menu helper to add the path code.

Thanks

Good job! Works like a charm on latest Zend Framework 1.9.3. I'm also vote for adding this patch to zf. Thanks!

Cleaned up patch so that it will apply cleanly against 1.11.11. Also renamed option/methods to better describe their purpose.

I've applied and tested the patch, with no ill-side-effects noted. All unit tests in Navigation and View_Helper groups pass after applying patch. The fix does, however, extend the public API of Zend_View_Helper_Navigation_Menu, so we will need to get approval from [~Matthew] before committing to SVN.

Hi Adam, I have tested your patch. Looks good and all unit tests are working.

Thanks for the clean up!

Fixed in trunk (1.12.0): r24839