dimanche 19 décembre 2010

Zend framework: imaging the menu

Hmm

This is more of a reminder to ::self than a post. Because it took me nearly a full day to figure out how to be able to put just a logo image in the menu with Zend_View_Helper_Navigation_Menu. So here goes.
Note 1: I used ZF 1.11
Note 2: For simplicity, I used "fullmoon" where you should use your application name. Take notice of case within the code, it's important and it changes depending on the part of code you're in.

Step 1: Extend Zend_View_Helper_Navigation_Menu

In the library, create your own menu view helper:
library/Fullmoon/View/Helper/Navigation/FullmoonMenu.php
with the following code:
class Fullmoon_View_Helper_Navigation_FullmoonMenu extends Zend_View_Helper_Navigation_Menu
{
    public function fullmoonMenu(Zend_Navigation_Container $container = null)
    {
        return $this->menu($container);
    }
}


Step 2: Modify the layout view script

This will allow you to make the following call in your layout view script (application/layouts/scripts/layout.phtml):
echo $this->navigation()->fullmoonMenu();


Step 3: Modify the bootstrap

In application/bootstrap.php, add the following:
    /**
     * Menu
     */
    protected function _initNavigation()
    {
        // Load menu data from XML file. First argument is the XML file,
        // second is the XML node containing the menu XML data
        $config = new Zend_Config_Xml(APPLICATION_PATH . '/configs/navigation.xml', 'nav');
        // Retrieve the view object
        $view = $this->getResource('view');
        // Add our own Navigation Menu helper to the view plugins path
        $view->addHelperPath(
            'Fullmoon/View/Helper/Navigation',
         'Fullmoon_View_Helper_Navigation_'
        );
        // Load the menu into the view
        $view->navigation(new Zend_Navigation($config));
    }
Note: You can find examples of XML menu files here.
At this point, your menu should be loading normally, albeit still without an image.

Step 4: Adding the image (aahh, finally!)

Adding the image to the XML file:
<img>
  <class>title</class>
  <src>/img/logo-small.png</src>
  <module>default</module>
  <controller>index</controller>
  <action>index</action>
</img>


Step 5: Amending your menu helper

As a final step, we need to render the image (in library/Fullmoon/View/Helper/Navigation/FullmoonMenu.php) by overriding the protected _renderMenu() method from library/Zend/View/Helper/Navigation/Menu.php:
    /**
     * 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;
                }
            }

            // 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;
            }
            // Manage menu image
            if ($page->src != '') {
                $html .= $myIndent . '    <li class="title">' . self::EOL
                      . '        <img src="' . $page->src . '" />' . self::EOL;
            } else {
                // render li tag and page
                $liClass = $isActive ? ' class="active"' : '';
                $html .= $myIndent . '    <li' . $liClass . '>' . 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;
    }
This part is what does the trick:
            // Manage menu image
            if ($page->src != '') {
                $html .= $myIndent . '    <li class="title">' . self::EOL
                      . '        <img src="' . $page->src . '" />' . self::EOL;
            } else {
                // render li tag and page
                $liClass = $isActive ? ' class="active"' : '';
                $html .= $myIndent . '    <li' . $liClass . '>' . self::EOL
                       . $myIndent . '        ' . $this->htmlify($page) . self::EOL;
            }


Conclusion

I had a hard time gathering the necessary information over the Internet, hopefully this may help out those wanting to add a logo to the menu with Zend Framework.
Note that the logo does not have link attached to it, but with a little tweaking of the last code snippet that shouldn't be too difficult ;)

There may be a far simpler solution to this but I don't know of it nor have I found it on the Internet.

Aucun commentaire: