Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Navigation Component Proposal

Proposed Component Name Zend_Navigation
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Navigation
Proposers Robin Skoglund
Zend Liaison Alexander Veremyev (Zend Liaison)
Revision 1.0 - 5 February 2009: Initial Draft.
1.1 - 21 March 2009 Updated based on community feedback. (wiki revision: 14)

Table of Contents

1. Overview

Zend_Navigation and its set of view helpers intend to simplify the creation and rendering of navigational structures, such as menus, breadcrumbs, navigational head links, and XML sitemaps.

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • This component will be able to generate URLs for pages
    • This component will allow assembling of page URLs based on module/contoller/action
    • This component will allow assembling of page URLs based on routes
    • This component will allow assembling of page URLs with user params
    • This component will allow custom URLs (i.e. not based on any of the above)
  • This component will be able to automatically detect the currently active page in a navigation container
  • This component will be able to render menus
  • This component will be able to render breadcrumbs
  • This component will be able to render navigational head links
  • This component will be able to render sitemaps (Sitemaps XML)
  • This component will have optional integration with ACL
  • This component will have optional implicit localization
  • This component will not contain any references to the Cthulhu Mythos

4. Dependencies on Other Framework Components

  • Zend_Exception
  • Zend_Controller_Action_Helper_Url
  • Zend_Validate_Abstract
  • Zend_View_Helper_HtmlElement

5. Theory of Operation

Zend_Navigation is a component for managing trees of pointers to web pages. Simply put: It can be used for creating menus, breadcrumbs, links, and sitemaps, or serve as a model for other navigation related purposes.

Pages and containers

There are two concepts in Zend_Navigation:

  1. Pages
    A page Zend_Navigation_Page in Zend_Navigation — in its most basic form — is an object that holds a pointer to a web page. In addition to the pointer itself, the page object contains a number of other properties that are typically relevant for navigation, such as label, title, etc.

  2. Containers
    A navigation container (Zend_Navigation_Container) is a container class for pages. It contains methods for adding, retrieving, deleting and iterating pages. It implementes the SPL interaces RecursiveIterator and Countable, and can thus be iterated recursively, either by using the SPL class RecursiveIteratorIterator class, or by implementing recursive iteration yourself using foreach loops or other iterators.

    Both Zend_Navigation and Zend_Navigation_Page extend Zend_Navigation_Container, so both can contain any number of hierarchic levels of pages.

By design, it is not possible to create arbitrary graphs of pages. That is, a page X cannot have a parent Y that is also a descendant of page X. It is only possible to create trees.

Finder methods

Containers have finder methods for retrieving pages. They are findOneBy($property, $value), findAllBy($property, $value), and findBy($property, $value, $all = false). Those methods will recursively search the container for pages matching the given $page->$property == $value. The first method, findOneBy(), will return a single page matching the property with the given value, or null if it cannot be found. The second method will return all pages with a property matching the given value. The third method will call one of the two former methods depending on the $all flag.

The finder methods can also be used magically by appending the property name to findBy, findOneBy, or findAllBy, e.g. findOneByLabel('Home') to return the first matching page with label Home. Other combinations are findByLabel(...), findOnyByTitle(...), findAllByController(...), etc. Finder methods also work on custom properties, such as findByFoo('bar').

Rendering

Classes in the Zend_Navigation namespace do not deal with rendering of navigational elements. Rendering is done with navigational view helpers. However, pages contain information that is used by view helpers when rendering, such as; label, CSS class, title, lastmod and priority properties for sitemaps, etc.

I18n, L13n

The navigational helpers support translating of page labels and titles. You can set a translator of type Zend_Translate or Zend_Translate_Adapter in the helper using $helper->setTranslator($translator), or like with other I18n-enabled components; adding the translator to Zend_Registry using the key Zend_Translate, in which case it will be found by the helpers.

ACL

All navigation view helpers support ACL inherently from the class Zend_View_Helper_NavigationBase. A Zend_Acl object can be assigned to a helper instance with $helper->setAcl($acl), where $helper refers to an instance of a helper, and $acl is an ACL instance containing roles and possibly resources. The helpers can be assigned a role to use when iterating pages, by doing $helper->setRole('member') to set a role id, or $helper->setRole(new Zend_Acl_Role('member')) to set an instance. If ACL is used in the helper, the role in the helper must have rights for the page's resource and/or privilege to be included in a menu/breadcrumb/sitemap.

6. Milestones / Tasks

  • Milestone 1: [DONE] Proposal created
  • Milestone 2: [DONE] Working prototype checked into public repository
  • Milestone 3: [DONE] Community review of proposal
  • Milestone 4: [DONE] Proposal acceptance
  • Milestone 5: [DONE] Passing unit tests and initial documentation committed to incubator
  • Milestone 6: Review for inclusion in trunk

7. Class Index

  • Zend_Navigation
  • Zend_Navigation_Container
  • Zend_Navigation_Exception
  • Zend_Navigation_Page
  • Zend_Navigation_Page_Mvc
  • Zend_Navigation_Page_Uri
  • Zend_Validate_Sitemap_Changefreq
  • Zend_Validate_Sitemap_Lastmod
  • Zend_Validate_Sitemap_Loc
  • Zend_Validate_Sitemap_Priority
  • Zend_View_Helper_Navigation
  • Zend_View_Helper_Navigation_Abstract
  • Zend_View_Helper_Navigation_Breadcrumbs
  • Zend_View_Helper_Navigation_Interface
  • Zend_View_Helper_Navigation_Links
  • Zend_View_Helper_Navigation_Menu
  • Zend_View_Helper_Navigation_Sitemap

8. Use Cases

9. Class Skeletons

]]></ac:plain-text-body></ac:macro>

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Feb 13, 2009

    <p>Great to have this, I also wanted to do exactly the same thing, just havin no time at all.<br />
    What I really would like to see is the handling of navigational <link/> elements (copyright, content, chapter, start, prev, next), since it is really tedious to keep track with. Could we create an appropriate view helper for this too?</p>

    1. Feb 13, 2009

      <p>Yes, we could indeed. I thought of it a while ago, but I got caught up in other things and sort of forgot about it. Thanks for the reminder! I'll try to add it later today or in the weekend. You can join #zym on Freenode if you want to talk more about implementation details and a demo.</p>

      <p>Do you have a suggestion for a name?</p>

      1. Feb 14, 2009

        <p>Splendid. For the name: Zend_View_Helper_HeadLinkNavigation / Zend_View_Helper_HeadLinkNav?</p>

        <p>I will try to join on IRC, if I have the time.</p>

      2. Feb 15, 2009

        <p>Reference: <a class="external-link" href="http://www.w3.org/TR/html4/types.html#type-links">http://www.w3.org/TR/html4/types.html#type-links</a></p>

  2. Feb 13, 2009

    <p>Maybe I missed something, but how to you know which page is active automatically? Do you parse the URI?</p>

    <p>Also I would like to have a method to find an item in a specific context, because it might have the same property as another. I.e. I have this menu:</p>

    <p>Home<br />
    ..Lists<br />
    ....add new<br />
    ..Items<br />
    ....add new</p>

    <p>I can't call findByLabel('add new'), because there's more than one match. Therefor I'd like to be able to say findByLabel(array('Home', 'Items', 'add new')) or something similar.</p>

    <p>Of course it would also be great to have an ID that doesn't have to be the same as what's used for the HTML element (because you don't and up with home_items_add, just because it has to be unique in the document). You aren't using the array keys currently, so that could be used. Instead of checking for your labels, which could change you'd than have something like findByKey(array('home', 'items', 'add')), which could even match modules, controller and action.</p>

    1. Feb 13, 2009

      <blockquote>
      <p>Maybe I missed something, but how to you know which page is active automatically? Do you parse the URI?</p></blockquote>

      <p>The algorithm for determining if a page is active:</p>
      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      public function isActive($recursive = false)
      {
      if (!$this->_active) {
      $front = Zend_Controller_Front::getInstance();
      $reqParams = $front->getRequest()->getParams();

      if (!array_key_exists('module', $reqParams))

      Unknown macro: { $reqParams['module'] = $front->getDefaultModule(); }

      $myParams = $this->_params;

      if (null !== $this->_module)

      Unknown macro: { $myParams['module'] = $this->_module; }

      else

      Unknown macro: { $myParams['module'] = $front->getDefaultModule(); }

      if (null !== $this->_controller)

      Unknown macro: { $myParams['controller'] = $this->_controller; }

      else

      Unknown macro: { $myParams['controller'] = $front->getDefaultControllerName(); }

      if (null !== $this->_action)

      Unknown macro: { $myParams['action'] = $this->_action; }

      else

      Unknown macro: { $myParams['action'] = $front->getDefaultAction(); }

      if (count(array_intersect_assoc($reqParams, $myParams)) ==
      count($myParams))

      Unknown macro: { return $this->_active = true; }

      }

      return parent::isActive($recursive);
      }
      ]]></ac:plain-text-body></ac:macro>

      <blockquote>
      <p>I can't call findByLabel('add new'), because there's more than one match. Therefor I'd like to be able to say findByLabel(array('Home', 'Items', 'add new')) or something similar.</p></blockquote>

      <p>You can do this:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      findByLabel('Items')->findByLabel('add new');
      ]]></ac:plain-text-body></ac:macro>

      <p>In this case I'd recommend finding by something other than label. Such as finding by 'route', 'class', 'id', or just a custom property for distinguising the pages.</p>

      <blockquote>
      <p>Of course it would also be great to have an ID that doesn't have to be the same as what's used for the HTML element (because you don't and up with home_items_add, just because it has to be unique in the document). You aren't using the array keys currently, so that could be used. Instead of checking for your labels, which could change you'd than have something like findByKey(array('home', 'items', 'add')), which could even match modules, controller and action.</p></blockquote>

      <p>The purpose of the 'id' property is to be a placeholder for the HTML id attribute. It's currently up to the developer to make sure those are unique (i.e. there is no mechanism in place for making sure id's are unique), and it's a thing that's hard to implement in a fool-proof manner. The 'id' property has no other use by itself througout Zend_Navigation or its view helpers. It should be fairly easy to implement yourself, though. Normally there aren't <em>that</em> many items in a navigation, and you can automate the id generation when building the nav container. This way you're totally in control of what you set as id, and you're free to use that when retrieving pages using the finder methods.</p>

      <p>Let it also be said that the finder methods are just for convenience. I haven't had any real use cases for those yet. That is to say; you're likely to get what you want without ever bothering with finder methods.</p>

  3. Feb 15, 2009

    <p>We'll be able to use this with a Zend_Config object, won't we? If so, a use case showing it would be nice.</p>

    1. Feb 15, 2009

      <p>Added to UC-01 and UC-02.</p>

  4. Feb 26, 2009

    <p>IMO Zend_View_Helper_Breadcrumbs and Zend_View_Helper_Menu should have the ability to add decorators. Otherwise I think they are a bit to restrictive.</p>

    <p>For instance, eventhough I use UL's for menu's all the time, perhaps there may come occasions where I want to use OL's or even other containers. Also, some designs 'force' you to use an extra SPAN and the likes. This general idea is valid for breadcrumbs too IMO, although the separator methods could be enough already.</p>

    <p>Maybe some type of adapter or plugin mechanism would be nice too. One which would allow to retrieve whole navigation structures from a storage mechanism such as a database. Although passing through the constructor, or other methods might be enough already. I'm not sure. What do you think?</p>

    1. Mar 14, 2009

      <blockquote>
      <p>IMO Zend_View_Helper_Breadcrumbs and Zend_View_Helper_Menu should have the ability to add decorators. Otherwise I think they are a bit to restrictive.</p></blockquote>

      <p>While the idea behind decorators is great, I find that it is a technique that over-complicates the task at hand. It certainly gives the developer a lot of flexibility, but at a cost. It complicates the API and slows down execution a fair amount. An argument I often see for decorators - or in general when adding a layer of flexibility - is that it "sure, it slows down execution, but it speeds up development time!". In most cases this simply isn't true. The complex decorator system of Zend_Form takes a lot of time to a) learn, b) understand, c) use. You end spending tens of minutes to "get it right".</p>

      <p>Regarding customizing the output of the navigational view helpers, the thought was that developers extend the view helper they want to change, and override the render() method <ac:link><ri:page ri:content-title="i refactored the component a bit, which is not yet reflected in this porposal" ri:space-key="note" /></ac:link>. I've done this once or twice, and it took me about a minute to write a custom render method. You can't beat that with decorators.</p>

      <p>If many enough feel the absolute need for decorators, I will add it (some help would be much appreciated), but please consider the possbility of simply extending the view helpers. It is very convenient.</p>

      <blockquote>
      <p>Maybe some type of adapter or plugin mechanism would be nice too. One which would allow to retrieve whole navigation structures from a storage mechanism such as a database. Although passing through the constructor, or other methods might be enough already. I'm not sure. What do you think?</p></blockquote>

      <p>This is something I have wanted to generalize, but I'm afraid such a component will be too hard to make general enough, since it would involve forcing a schema upon the developer. Another issue is that people tend to make menus they behave completely different from each other Example; In one project I wanted to make a menu of the users of a particular feature, and mix it with a somewhat static structure of other pages. Each user link would point to that user's page, and it involved fetching elements for the menu from more than one table. Such a use case is not at all uncommon, and it is only one of many ways a developer could build a menu.</p>

      <p>That being said, the current implementation with the page factory, and passing options to the page constructor, works rather well. The menu I mentioned in the previous paragraph took about 5 minutes to implement in a controller plugin. Also, the construction of pages is built with Zend_Config in mind, so it suits an XML or an Ini config well.</p>

      1. Feb 28, 2009

        <p>I agree, Zend_Form's decorator system can be a bit tricky to grasp/use. Therefor I second Joó Ádám's suggestion about being able to use partials.</p>

        <p>And I hear what you say about the difficulty of generalizing an implementation to allow an adapter or plugin of some sort.</p>

    2. Feb 28, 2009

      <p>I agree: customizing the output is crucial, but I would rather see partials to do the job.</p>

      1. Feb 28, 2009

        <p>Hmm yes, I must agree, much better. Especially in light of Robin Skoglund's response to me about Zend_Form's decorators.</p>

  5. Feb 28, 2009

    <p>I'll try to implement a system for partials today. I will also be adding the HeadLink helper, and update the proposal to reflect all refactoring and changes.</p>

    <p>EDIT:<br />
    ...though I'm not fond of partials. It is not a "smooth" system from a library perspective. Some considerations that need to be dealt with:</p>
    <ul>
    <li>Should the library ship with partials? In that case, where would they be located?</li>
    <li>Should the library ship with several partials for breadcrumbs and menus, to customize the partial output?</li>
    <li>What should be done in the view helper, and what should be done in a the partial?
    <ul>
    <li>Should the view helper filter out (ACL, visibility) pages before sending a container to the partial?</li>
    <li>Should the partial deal with <em>all</em> rendering and logic?</li>
    </ul>
    </li>
    <li>Would there even be a need for the menu and breadcrumbs helper if a partial system is implemented?</li>
    </ul>

    <p>Another issue with partials is that they are dead slow. The <a href="http://framework.zend.com/manual/en/performance.view.html#performance.view.partial">performance guide in ZF</a> says it is because the view is cloned. In the light of this, it is not really a road I'm eager to go down, but we'll see how it goes. The solution I'm leaning against is to leave the current view helpers as-is, and provide opt-in partial rendering, meaning developers can specify a partial script using setPartial() which would override the internal rendering.</p>

    <p>Any comments on the bulleted considerations is appreciated.</p>

    1. Feb 28, 2009

      <p>Robin,</p>

      <p>You might want to look at Zend_Pagination in combination with Zend_View_Helper_PaginationControl. I think these illustrate a flexible way of optionally chosing a partial or just use the raw Zend_Pagination output. Maybe something similar is implementable with your Zend_Navigation component?</p>

      <p>I don't have much time now to get into details too much. Nor do I have the time to ponder on whether your compnonent is actually comparable with Zend_Pagination. But I will try to come up with a better illustration of how this could be implemented with your component, if you so desire. That is, IF the two components are comparable enough to begin with. <ac:emoticon ac:name="wink" /></p>

      <p>In light of my first paragraph, and in response to your bulleted list, I would say:</p>

      <ul class="alternate">
      <li>No it should not ship with partials, since it is up to the developer to make partials. Merely the possibility to use them should suffice.</li>
      <li>Again, no I don't think so. For above mentioned reason also.</li>
      <li>This is where the Zend_View_Helper_PaginationControl could be a good example I think. It illustrates a possible implementation for your compnonent. But once again, if they are comparable to begin with. Intuitively I would say yes. But I have to think about it some more first.
      <ul class="alternate">
      <li>I think your component should output iterable properties that can be probed in the partial. And let the developers implementation of the partial decide what to do with ACL/visibilty options.</li>
      <li>Only the rendering and iteration logic, meaning, the partial decides what to do with each iteration. But your compnonent would of course keep control over the implementation of the possibility to iterate through the navigation items. (But perhaps I've misunderstood your question, and this isn't what you were after?)</li>
      </ul>
      </li>
      <li>Not sure yet</li>
      </ul>

      <p>Since the partials are considered slow, I think using them should be optional.</p>

  6. Mar 01, 2009

    <p>IMHO, it needs solution for one use case:</p>

    <p>1. Messages<br />
    1.1 Inbox (10)<br />
    1.2 Outbox (210)</p>

    <p>Some sort of plugin for label preparation before translation or output. In this case adding message count in each category. Sample template: Inbox (%inboxMessageCount%)</p>

    1. Mar 22, 2009

      <p>I would like that, but I don't see it happening. A feature like the one you're requesting should be implemented in a separate ZF component so it would be applicable for other components as well. If this were to be implemented in Zend_Navigation alone, the API would be too complex to use efficiently.</p>

      <p>Let's not forget that we are programmers. You can do the things you're asking for when creating the navigation container, or implement it in a partial view script used for rendering the menu.</p>

      1. Mar 15, 2009

        <p>I think, there is no need for bloated special API. It would work like two step view: 1st pass - language translation, 2nd pass - runtime constant translation. The only api needed is to add other type translator (for runtime constants).</p>

        <p>Something like:<br />
        inbox -> (standart translator::translate) -> Inbox (%inboxMessageCount%) -> (runtime constant translator::translate) -> Inbox (10)</p>

        <p>Both translators are optional (as it is now).</p>

        <p>API change needed for this kind of stuff is only runtime constant translator setter/getter. All the other stuff (such as separator setter/getter, patterns, etc) can be encapsulated into RuntimeConstantTranslator (maybe there is a need of separate component?, but for now i see it usage only here).</p>

        <p>Or maybe it can be done by altering translator in a way i mentioned. IMHO, it needs discussion.
        <br class="atl-forced-newline" /> <br class="atl-forced-newline" /> <br class="atl-forced-newline" /> <br class="atl-forced-newline" /></p>

        1. Mar 15, 2009

          <p>If Robin is still considering implementing an optional partial renderer, I think all these type of extra's could be used with a breeze. What do you think?</p>

          <p>@Robin: Are you still considering implementing some kind of partials rendering system?</p>

          1. Mar 16, 2009

            <p>Yes. I implemented it this weekend, but I haven't had the time to update the proposal yet. I am currently rewriting the documentation and updating the proposal text, so I hope to have it in here today. The Links helper is also implemented.</p>

            <p>If you want a sneak preview, you can check out the code on GitHub:<br />
            <a href="http://github.com/robinsk/zym/tree/94935c1d8b6ec8b01cc9cb656e0d22288dd27ed6/library/Zym/View/Helper/Navigation">View helpers</a><br />
            <a href="http://github.com/robinsk/zym/tree/94935c1d8b6ec8b01cc9cb656e0d22288dd27ed6/tests/Zym/View/Helper/Navigation">View helper unit tests</a></p>

  7. Mar 13, 2009

    <p>Shouldn't be the proposed Zend_Tagcloud merged into this proposal? Anyhow, a tagcloud is quite a navigational element, isn't it?</p>

    1. Mar 14, 2009

      <p>Logically a tag cloud may be considered as a navigational element, but it doesn't make much sense to store tags in a hierarchical navigation container (such as this component provides). Sure it can be done, but that would require the tag cloud component to operate on a navigation container rather than an array/Zend_Tag_Item. I will talk with Ben (tag author) about this.</p>

    2. Mar 14, 2009

      <p>I talked to Ben about this. Zend_Tag will be a separate namespace that generalizes all things related to tags.</p>

      <p>Quote from Ben:</p>
      <blockquote>
      <p>Zend_Tag will get a huge bunch of components later for all possible tagging stuff, Ralph Schindler and Matthew Weier O'Phinney had lots of ideas <ac:emoticon ac:name="wink" /></p></blockquote>

      <p>The current namespace policy in ZF is component based (not based on logical grouping), so although Zend_Tag_Cloud can be considered a navigational element, it will have its own namespace.</p>

      1. Mar 14, 2009

        <p>Fair enough. And that Tag component sounds great <ac:emoticon ac:name="smile" /></p>

  8. Mar 20, 2009

    <p>Update:</p>
    <ul>
    <li>Minor modifications to Zend_Navigation_Container and Zend_Navigation_Page*</li>
    <li>Added Zend_View_Helper_Navigation_Interface</li>
    <li>Renamed Zend_View_Helper_NavigationBase to Zend_View_Helper_Navigation_Abstract</li>
    <li>Added a proxy helper; Zend_View_Helper_Navigation</li>
    <li>Added a helper for head links: Zend_View_Helper_Navigation_Links</li>
    <li>Zend_View_Helper_Breadcrumbs is now Zend_View_Helper_Navigation_Breadcrumbs</li>
    <li>Zend_View_Helper_Menu is now Zend_View_Helper_Navigation_Menu</li>
    <li>Zend_View_Helper_Sitemap is now Zend_View_Helper_Navigation_Sitemap</li>
    <li>Added partial support to breadcrumbs helper</li>
    <li>Added partial support to menu helper</li>
    <li>Updated tests</li>
    <li>Updated documentation</li>
    <li>Updated proposal to reflect changes</li>
    </ul>

    <p>Proposal is now ready for final review and recommendation from the Zend team.</p>

    1. Mar 20, 2009

      <p>I recommend reading the updated <a href="http://robinsk.net/misc/zymdocs-2009-03-21/zym.navigation.html">Zym_Navigation documentation</a> to learn more about this component.</p>

  9. Mar 28, 2009

    <p>Zend_Navigation_Page_Mvc should make use of the getCurrentRoute() and getCurrentRouteName() of the Rewrite router. I alreday wrote a patch for this:</p>

    <p><a class="external-link" href="http://paste2.org/p/172425">http://paste2.org/p/172425</a></p>

    <p>I know, that this methods are not within the router interface, but there could be a workaround to just use this method when the router is an instance of Router_Rewrite or extends it. As most people are either using the Rewrite router directly or extending it, and very very few people are writing their completly own router (and then using Zend_Navigation) parallely, this would make the usage of this component much easier (and also make sure, that really just the active page/route combination gets marked as active, and not a route which supplies similar parameters.</p>

    <p>For ZF 2.0, this workaround could then be removed, as we can then make the required methods member of the Zend_Controller_Router_Interface.</p>

    1. Mar 28, 2009

      <p>Disclaimer for people jumping into the discussion:<br />
      The issue is regarding the <code>isActive()</code> method of <code>Zend_Navigation_Page_Mvc</code>. Currently, the page will not check its route against the currently active route. It will only intersect its params with the params found in the request object. Here's an example illustrating the situation:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      // let's say you have this route in an ini file:
      /*
      routes.archive.route = "archive/:year/*"
      routes.archive.defaults.module = news
      routes.archive.defaults.controller = archive
      routes.archive.defaults.action = show
      routes.archive.defaults.year = 2000
      routes.archive.reqs.year = "\d+"
      */

      /*

      • A page that links to 2008 news
        */
        $page = new Zend_Navigation_Page_Mvc(array(
        'label' => 'News from 2008',
        'route' => 'archive',
        'module' => 'news',
        'controller' => 'archive',
        'action' => 'show',
        'params' => array('year' => 2008)
        ));

      /*

      • The user requests http://www.example.com/archive/2008
        *
      • For $page->isActive() to return true, the all default params from the route must
      • be specified in the page (module, controller, route, year). If those are not
      • present in the page, it will have less params than the request object, thus
      • resulting in "not active".
        */

      /*

      • Ideally, this would be sufficient for determining activeness:
        */
        $page = new Zend_Navigation_Page_Mvc(array(
        'label' => 'News from 2008',
        'route' => 'archive',
        'params' => array('year' => 2008)
        ));
        ]]></ac:plain-text-body></ac:macro>

      <p>The latter approach is of course more succinct, but there is a problem: <code>Zend_Controller_Front::getInstance()<span style="text-decoration: line-through;">>getRouter()</span></code> returns an instance of <code>Zend_Controller_Router_Interface</code>. This interface does not define methods for retrieving the current route or retrieving a specific route (e.g. <code>$router>getRoute('archive')</code>). Further down the lane, <code>Zend_Controller_Router_Route_Interface</code> does not define methods for retrieving default params, meaning it will "only" work for routes of type Hostname, Module, Regex, or Static.</p>

      <p>To solve this, there are 3 options, listed from dirtiest to cleanest:</p>
      <ol>
      <li>Use a slightly modified version of Ben's patch and hope that the router and route are of supported types.</li>
      <li>Have the isActive() method verify that the router and route are of supported types (a gazillion calls to instanceof).</li>
      <li>Fix the router – i.e. add getDefaults() to the route interface, and getCurrentRouteName()/getCurrentRoute()/getRoute($route) to the router interface.</li>
      </ol>

      <p>Solution 3 has the obvious drawback of not maintaining BC for custom routers and routes that implement the aforementioned interfaces, but this is what we should aim for in the long run.</p>

      <p>Solution 2 adds a lot more work to the frequently used isActive() method, and does not scale well (e.g. new or custom routers/routes that support the desired methods).</p>

      <p>Solution 1 makes me feel dirty, because it's not OOP-friendly. We should be programming against well-defined interfaces, as opposed to hoping that an object is of a type that we support.</p>

      <p>I would much rather see the router system being fixed than implement any of those solutions. Why? The <code>isActive()</code> method works as-is. The only drawback is that you have to define a route's default params both in the route and in the page.</p>

  10. Mar 31, 2009

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comments</ac:parameter><ac:rich-text-body>
    <p>This proposal is approved for development in the standard incubator.</p>

    <p>Additional notes:</p>
    <ul>
    <li>Proposal should describe explicitly if it supports graphs (cyclic references within pages structure).</li>
    <li>That would be nice to have an ability to generate HTML for a sitemap.</li>
    </ul>
    </ac:rich-text-body></ac:macro>

    1. Mar 31, 2009

      <p>Clarified that it is not possible to create arbitrary graphs – only trees.</p>

      <p>HTML sitemaps can be generated using the Menu helper, like at <a class="external-link" href="http://www.zftalk.com/sitemap">http://www.zftalk.com/sitemap</a> (which by the way also uses a route to context switch to an XML sitemap available at <a class="external-link" href="http://www.zftalk.com/sitemap.xml">http://www.zftalk.com/sitemap.xml</a>).</p>

      <p>If you're looking for sectioned HTML sitemaps, you can render the Menu helper using a partial view script.</p>

  11. Apr 02, 2009

    <p>Is it possible, to display menu level "1" and level "2 to n" separately? I know many sites with links (level 1) on the top of the page ("tabs") and sub links (level 2 to n) on the left.</p>

    1. Apr 02, 2009

      <p>Agreed, I need that behaviour most of the time.</p>

    2. Apr 02, 2009

      <p>Yes.</p>

      <p>If you don't mind also rendering deeper than 1 level in the tabs, you can do <a href="http://eit.kameleon.ws/">something like this</a>:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      <div class="header">
      <?= $this->navigation()->menu(); ?>
      </div>
      ...
      <div class="sidebar">
      <?= $this->navigation()->menu()renderSubMenu(); ?>
      </div>
      ]]></ac:plain-text-body></ac:macro>

      <p>The sub pages can be hidden with CSS. This has the positive side-effect that search engine crawlers see your entire menu even though it's invisible for the user. The <code>renderSubMenu()</code> method will a) find the deepest active page in the menu, b) if it has children; render those, or c) render the level of the active page.</p>

      <p>If you want to not render deeper than 1 for the tabs, you can use a partial:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      <div class="header">
      <?= $this->navigation()->renderPartial('nav_tabs.phtml', 'default'); ?>
      </div>
      ]]></ac:plain-text-body></ac:macro>

      <p>Contents of application/modules/default/views/nav_tabs.phtml:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      <ul class="navigation">
      <? foreach ($this->container as $page): ?>
      <li class="<?= $page->active ? 'active' : ''">
      <?= $this->navigation()->htmlify($page); ?>
      </li>
      <? endforeach;
      </ul>
      ]]></ac:plain-text-body></ac:macro>

      <p>If you want the sidebar menu to always render the 2nd level of pages (instead of the deepest), you can use another partial:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      <div class="sidebar">
      <?= $this->navigation()->renderPartial('nav_sidebar.phtml', 'default'); ?>
      </div>
      ]]></ac:plain-text-body></ac:macro>

      <p>Contents of application/modules/default/nav_sidebar.phtml:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      <?php
      foreach ($this->container as $page) {
      if ($page->active)

      Unknown macro: { echo $this->navigation()->menu()->setUlClass('sidebar-nav')->renderMenu($page); }

      }
      ]]></ac:plain-text-body></ac:macro>

      1. Apr 02, 2009

        <p>The CSS-thing is more a hack than a solution (it'll look very odd in text browsers when a sub-menu appears two times and the user will loose the overview).</p>

        <p>The second approach works, yes, tho it would be nice to have a simple option like:</p>

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        // Render the main menu
        <?php $this->navigation()>menu()>render(array('toLevel' => 1)); ?>

        // Render the sidebar (only the one from the active main menu item)
        <?php $this->navigation()>menu()>render(array('fromLevel' => 2, 'onlyWithActiveParent' => true)); ?>
        ]]></ac:plain-text-body></ac:macro>

        <p>That would solve all common use-cases and also reduce the usage of partial views again.</p>

        1. Apr 02, 2009

          <p>100% Agree. Your solution looks very nice! I think, it should be implemented.</p>

        2. Apr 02, 2009

          <p>Seconded. Nice one.</p>

        3. Apr 02, 2009

          <p>This will be implemented by modifying the renderMenu() method.</p>

          <p>New method signature:<br />
          public function renderMenu(Zend_Navigation_Container $container, array $options = array()).</p>

          <p>Options:</p>
          <ul>
          <li>indent; int|string; default = $this->getIndent(); initial indentation</li>
          <li>ulClass; string; default = $this->getUlClass(); css class for UL element (empty string or null permitted)</li>
          <li>minLevel; int|null; default = null; if specified, do not traverse/render pages above minLevel</li>
          <li>maxLevel; int|null; default = null; if specified, do not traverse/render pages below maxLevel</li>
          <li>onlyActiveBranch; boolean; default = false; only render pages in the branch of the active page</li>
          <li>renderParents; boolean; default = true; render parents of the active page from minLevel to maxLavel (or depth of the active page)</li>
          </ul>

          <p>Check out <a href="http://pastie.org/435249">this pastie</a> for examples of how to use the new options.</p>

          1. Apr 03, 2009

            <p>Thank you. Makes this component very powerful!</p>

          2. Apr 04, 2009

            <p>This is now implemented and tested in the incubator.</p>

            <p>Notable differences from previous comment:</p>
            <ul>
            <li>minLevel and maxLevel are now minDepth and maxDepth, and are implemented in Zend_View_Helper_Navigation_Abstract, which means that all navigational helpers now support minDepth and maxDepth.</li>
            <li>renderParents is only used when onlyActiveBranch is true (because it doesn't make sense otherwise).</li>
            </ul>

            <p>I recommend checking out <a href="http://pastie.org/435249">http://pastie.org/435249</a> to get an overview of how those options affect the rendered output of the menu helper. The pastie also includes runnable code.</p>

            <p>PS: Code freeze is imminent, so any changed would have to be done <em>immediately</em>.</p>

  12. Apr 05, 2009

    <p>There are some things I would like to note:</p>

    <ul>
    <li>Zend_Navigation_Page is an abstract class, so it should be named Zend_Navigation_Page_Abstract (see Zend_Db_Table/Zend_Db_Table_Abstract).</li>
    </ul>

    <ul>
    <li>You try to cover the most important attributes with some accessor methods (e.g. relationship, reverse link), but you actually do not cover all possible attributes. That is some kind of inconsistent. Maybe you should handle all these extra features as properties. A link/page/whatever is basically just the hypertext reference and its label.</li>
    </ul>

    <ul>
    <li>Pages should not order themselves. They are ordered in a container which should handle their order (see plugin priorities/"stack index" of front controller plugins).</li>
    </ul>

    <ul>
    <li>Pages should not handle their parents. Have you ever thought about reusability of pages? Think about what happens if I want to use the same page in two separate containers. Removing itself from the old parent is not a good solution.</li>
    </ul>

    <ul>
    <li>Active routes may not always require matching a module, controller, action and all parameters. Have you ever thought about a page (navigation) that should match multiple actions (e.g. module: user; controller: login; action: form, submit)? You should only check given parameters and treat module, controller and action as parameters (no extra accessors).</li>
    </ul>

    <ul>
    <li>Zend_Navigation_Page_Uri actually uses a hypertext reference. I would prefer something like Zend_Navigation_Page_Href implementing just a setter method (setHref).</li>
    </ul>

    <ul>
    <li>Personally, I do not like the way things are injected by the current view helper. You actually handle the same data at different places. Have you thought about retrieving a navigation helper (e.g. menu) and later changing the acl in the view helper? The navigation helper will still use the old acl. Navigation helpers should only do rendering. When there is something to render, you can pass the view helper and container.</li>
    </ul>

    <p>Besides these notes, I really like your ideas (especially the filtering (minDepth, maxDepth, ...)) and think this component is very useful. One thing I would like to see in the future is filtering before passing navigations to partials, so you can use the brilliant filtering options within your own view scripts without having to develop them yourself.</p>

    1. Apr 05, 2009

      <p>I have to correct you in some points.</p>

      <ul class="alternate">
      <li>Naming abstract classes (for new components) "_Abstract" should be avoided, since when we move on to namespaces, a class with the name "Abstract" will give you an syntax error. Currently, "_Base" is the prefered suffix.</li>
      <li>Having pages parent-aware is a fine thing, and other components are doing the same.</li>
      <li>A page always represents a single specific action (with additional parameters if required).</li>
      <li>Well, and I'm unsure about your _Href thing.</li>
      </ul>

    2. Apr 05, 2009

      <blockquote>
      <ul>
      <li>Zend_Navigation_Page is an abstract class, so it should be named Zend_Navigation_Page_Abstract (see Zend_Db_Table/Zend_Db_Table_Abstract).</li>
      </ul>
      </blockquote>

      <p>No it shouldn't. As Ben states, the _Abstract suffix should be avoided. Currently, Zend_View_Helper_Navigation_Interface and Zend_View_Helper_Navigation_Abstract are using the "old" class naming style so it will be easy to refactor for ZF 2.0. However, those are subject to change (e.g. INavigation and Base) before release.</p>

      <blockquote>
      <ul>
      <li>You try to cover the most important attributes with some accessor methods (e.g. relationship, reverse link), but you actually do not cover all possible attributes. That is some kind of inconsistent. Maybe you should handle all these extra features as properties. A link/page/whatever is basically just the hypertext reference and its label.</li>
      </ul>
      </blockquote>

      <p>In Zend_Navigation_Page you mean? The attributes that are most common (label, css, titile) and attributes that need extra logic (rel, rev, params) have accessors. Uncommon attributes like lastmod and changefreq that are only used in the Sitemap helper are not covered by explicit accessors.</p>

      <blockquote>
      <ul>
      <li>Pages should not order themselves. They are ordered in a container which should handle their order (see plugin priorities/"stack index" of front controller plugins).</li>
      </ul>
      </blockquote>

      <p>Order is stored in the page to make persistence easier. The container does the actual ordering, based on either a) the explicit order a page has, or b) when the page was added to the container.</p>

      <blockquote>
      <ul>
      <li>Pages should not handle their parents. Have you ever thought about reusability of pages? Think about what happens if I want to use the same page in two separate containers. Removing itself from the old parent is not a good solution.</li>
      </ul>
      </blockquote>

      <p>This is done to prevent circular references, and for making it easier to implement rendering of breadcrumbs, head links, and menus. Do you have a use case where the same page should be used in two containers? If you have a good one I'm eager to hear it, but I'm afraid such a use case would fall outside the 80/20 rule.</p>

      <blockquote>
      <ul>
      <li>Active routes may not always require matching a module, controller, action and all parameters. Have you ever thought about a page (navigation) that should match multiple actions (e.g. module: user; controller: login; action: form, submit)? You should only check given parameters and treat module, controller and action as parameters (no extra accessors).</li>
      </ul>
      </blockquote>

      <p>That is indeed how it works.</p>

      <blockquote>
      <ul>
      <li>Zend_Navigation_Page_Uri actually uses a hypertext reference. I would prefer something like Zend_Navigation_Page_Href implementing just a setter method (setHref).</li>
      </ul>
      </blockquote>

      <p>What do you mean by hypertext reference? But yes, that's actually not a bad idea, except that time is short and it requires refactoring the entire code base and documentation.</p>

      <blockquote>
      <ul>
      <li>Personally, I do not like the way things are injected by the current view helper. You actually handle the same data at different places. Have you thought about retrieving a navigation helper (e.g. menu) and later changing the acl in the view helper? The navigation helper will still use the old acl. Navigation helpers should only do rendering. When there is something to render, you can pass the view helper and container</li>
      </ul>
      </blockquote>

      <p>What do you mean by "you actually handle the same data at different places"? Yes, I have thought about retrieving the menu helper and later changing the ACL. If the proxied helper already has an ACL/role object set, the Navigation helper will not inject anything. The same goes for the translator and the container itself. If the proxied already has those things, they will not be injected. Also, injection can be disabled (setInjectContainer(), setInjectTranslator(), setInjectAcl()). The main reason for injection is performance and ease of use when setting up (i.e. only setting those things in <em>one</em> helper).</p>

      <blockquote>
      <p>Besides these notes, I really like your ideas (especially the filtering (minDepth, maxDepth, ...)) and think this component is very useful. One thing I would like to see in the future is filtering before passing navigations to partials, so you can use the brilliant filtering options within your own view scripts without having to develop them yourself.</p></blockquote>

      <p>Glad you like it, and yes; that would be a cool feature. I've tinkered with the idea of using filter iterators (optionally with user-defined callbacks) to "trim" a container. The only drawback I can think of is that it would require cloning the container, or creating a new container instance and copying all accepted pages to the new instance. Or; the filter methods could toggle the 'visible' flag of a page, though this would not leave the container intact.</p>

      1. Apr 06, 2009

        <blockquote><p>No it shouldn't. As Ben states, the _Abstract suffix should be avoided.</p></blockquote>

        <p>So you may use Zend_Navigation_Page_Base and Zend_Navigation_Page?</p>

        <blockquote><p>In Zend_Navigation_Page you mean? The attributes that are most common (label, css, titile) and attributes that need extra logic (rel, rev, params) have accessors. Uncommon attributes like lastmod and changefreq that are only used in the Sitemap helper are not covered by explicit accessors.</p></blockquote>

        <p>Ok, I agree on the attributes that need extra logic, but - personally - don't like the "most common" thing <ac:emoticon ac:name="cheeky" /> For the majority it will be ok/easier <ac:emoticon ac:name="smile" /></p>

        <blockquote><p>Order is stored in the page to make persistence easier. The container does the actual ordering, based on either a) the explicit order a page has, or b) when the page was added to the container.</p></blockquote>

        <p>Why do you want to split the work? If the container does the ordering, it should also manage the ordering.</p>

        <blockquote><p>This is done to prevent circular references, and for making it easier to implement rendering of breadcrumbs, head links, and menus. Do you have a use case where the same page should be used in two containers? If you have a good one I'm eager to hear it, but I'm afraid such a use case would fall outside the 80/20 rule.</p></blockquote>

        <p>You may want to link to some special sites directly on the bottom of your website (e.g. <a class="external-link" href="http://framework.zend.com">http://framework.zend.com</a>, <a class="external-link" href="http://www.zend.com">http://www.zend.com</a>) which requires setting up a new navigation as you only use some pages of the complete navigation.</p>

        <blockquote><p>That is indeed how it works.</p></blockquote>
        <blockquote><p>A page always represents a single specific action (with additional parameters if required). (Ben Scholzen)</p></blockquote>

        <p>Are you sure? Just another example.</p>

        <ul>
        <li>Module: user</li>
        <li>Controller: list</li>
        <li>Action: standard</li>
        </ul>

        <ul>
        <li>Module: user</li>
        <li>Controller: list</li>
        <li>Action: coverflow</li>
        </ul>

        <p>Although both pages use different actions and may be named separately, I may want to manage both under the label "User list". So I would like to create a navigation page that matches the module 'user' and controller 'list' but doesn't care about the action.</p>

        <p>The current isActive implementation of Zend_Navigation_Page_Mvc will always try to match a module, controller and action (as far as I know).</p>

        <blockquote><p>What do you mean by hypertext reference? But yes, that's actually not a bad idea, except that time is short and it requires refactoring the entire code base and documentation.</p></blockquote>

        <p>HREF = Hypertext REFerence</p>

        <blockquote><p>What do you mean by "you actually handle the same data at different places"?</p></blockquote>

        <p>When setting an acl in the view helper and later working with a navigation helper, you have references to this acl in the view and navigation helper.</p>

        <p>The problem I tried to show is not that you may overwrite already injected acls/... but the fact that you do not overwrite them in some cases.</p>

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        $this->navigation()->setAcl($aclOne);

        $menu = $this->navigation()->findHelper('menu');

        $this->navigation()->setAcl($aclTwo);

        $menu->render();
        ]]></ac:plain-text-body></ac:macro>

        <p>The navigation helper menu will still work with the first acl.</p>

        <blockquote><p>Glad you like it, and yes; that would be a cool feature. I've tinkered with the idea of using filter iterators (optionally with user-defined callbacks) to "trim" a container. The only drawback I can think of is that it would require cloning the container, or creating a new container instance and copying all accepted pages to the new instance. Or; the filter methods could toggle the 'visible' flag of a page, though this would not leave the container intact.</p></blockquote>

        <p>Introduce a new flag: filtered. Makes it even more flexible.</p>

  13. Apr 08, 2009

    <p>There is any plan for a persistance for navigation items (as for Zend_Db or maybe Doctrine) ?</p>

    1. Apr 08, 2009

      <p>The reason it isn't already implemented is because it's hard to make such a feature generic enough, at least until you know how developers/users are actually using the component. When are people setting up their navigation containers? Where are the pages stored? In a single 'navigation' table? Or are pages created by querying several tables, e.g. 'users' and 'mvc'? How should it be integrated with ACL?</p>

      <p>There are several open questions like these that need answers before we can tell how users would want the component to behave.</p>

      <p>That being said, Benjamin Eberlei contacted me a while ago, saying that he could probably make a loader that's generic enough to load a container from a regular nested set table. He's very busy, though, so I wouldn't expect it any time too soon.</p>

      <p>Another approach that might be interesting is to make a component that can persist standard Zend_Config objects to a database/table. Such a component would be useful in other scenarios too, and could be applied to Zend_Navigation.</p>

      1. Apr 20, 2009

        <p>First, my thanks for the work to date.</p>

        <p>When considering DB integration, please consider large dynamic nav sets - in my workplace, sites with 5,000+ pages are not uncommon. At this level components that internally store data in arrays do not work, so a loader is not a great idea.</p>

        <p>I'm working on a DB-aware container at the moment, which is a better idea IMHO. The idea is that static and dynamic containers could be mixed in the Navigation tree, limiting the internal size of the container graph but allowing for huge result-sets.</p>

        <p>Once I have a working prototype I'll share it.</p>

        <p>Cheers, Scott</p>

  14. Apr 08, 2009

    <p>Can I ask, what is probably a simple question, (i've read the manual and understand the practice), what is the idea behind the tool?</p>

    <p>Do I load my websites structure on every visit into a navigation item and call the bits I need? All x pages, sub pages and the like? Do I cache this and load it?</p>

    <p>Do I load the navigation bars I need with the required elements? i.e. home page loads 6 pages from the database and creates the appropriate navigation elements?</p>

    <p>Or do I just create navigation containers as I do at the moment but load the data into Zend_Navigation, currently I check the database for related actions to the current controller, load them into an array and simply loop through them. I imagine this is where Zend_Navigation takes over. I am just struggling understanding the depth of structure to store and use.</p>

    <p>I like the concept I just can't see how to realisiticly work it, surely there could be a lot of data loaded on every page (understandable for a site map, but not a simple about us page).</p>

    <p>As I've said this is a pretty simple question but that's the type of answer I'm after!<br />
    Thanks</p>

    1. Apr 08, 2009

      <p>How and when you load the container is up to you, and depends on what you prefer and/or what your application's requirements are.</p>

      <p>There will be a Zend_Application_Resource_Navigation that handles container setup from a config file. This is probably a good starting point. If you want your application to be super modular, you can then add pages to the container per module, via the module's bootstrap.</p>

      <p>Another approach is to set up the container in a controller plugin. This way you can handle "dynamic" containers the way you want, e.g. aggregate from several tables in a database, or use other forms of persistence.</p>

      <p>And yes, if you want to use menus/breadcrumbs/links for a page, the container necessarily has to be loaded for every request (just like other objects/classes). Populating a container does in fact not require that much work, and is likely to be I/O bound (or IPC in the case of persistence in DB). Which brings me to another part of your question; caching. Yes, cache it if you experience that populating the container takes too much time/resources. Zend_Navigation does not handle caching for you, but it should be simple enough to put it in a cache yourself.</p>

      <p>Summary:<br />
      Sounds like you'd want to populate the container in a plugin and cache it manually.</p>

      1. Apr 10, 2009

        <p>Excellent, I've been playing with it now and I'm impressed, one thing how do you handle dynamic content. i.e. news articles, would ideally be included in a sitemap as content. However I don't think it makes sense to load them all in a config file every time one is added. Or is this indeed the vision? - The actual controller and action would be the same, but the page label and variables wouldn't be.</p>

        <p>Would you suggest these are added as needed to the stack inside the controller handling the action? Then have the sitemap created in a separate controller pulling in all the dynamic sections? Or simply don't include them in the site map?</p>

        <p>Thanks</p>

      2. Apr 10, 2009

        <p>Double post, sorry!</p>