Megrok.navigation lets you easily add all sorts of menus to a site.
Menus are implemented as viewletmanagers, and items as viewlets.
You can also override the default templates by registering your own IPageTemplate
Let us now define a menu
We'll first define an Interface, so that later on we won't need to redefine some menus after we redefined
the Navigation menu, so this is not necessary to do, although it can lessen dependencies.
Menu Items can be categorized into groups by adding a group parameter to all menuitem, submenu and the patentmenu
directives:
>>> class IGroupedMenu(navigation.interfaces.IMenu):
... pass
>>> class GroupedMenu(navigation.Menu):
... grok.implements(IGroupedMenu)
>>> grok_component('GroupedMenu', GroupedMenu)
True
>>> class Index(grok.View):
... grok.context(MySite)
... navigation.sitemenuitem(IGroupedMenu, 'Home', order=1, group='Group 1')
... def render(self):
... return 'test'
>>> grok_component('Index', Index)
True
>>> class View2(grok.View):
... grok.context(MySite)
... navigation.sitemenuitem(IGroupedMenu, 'View 2', order=0, group='Group 2')
... def render(self):
... return 'test'
>>> grok_component('View2', View2)
True
>>> class View3(grok.View):
... grok.context(MySite)
... navigation.sitemenuitem(IGroupedMenu, 'View 3', order=2, group='Group 1')
... def render(self):
... return 'test'
>>> grok_component('View3', View3)
True
The default templates render a separate <ul> for each group:
>>> gm = GroupedMenu(site, request, grok.View(site, request))
>>> gm.update()
>>> print gm.render()
<div class="">
<ul>
<li class="">
<a href="http://127.0.0.1/site/view2">View 2</a>
<BLANKLINE>
</li>
</ul>
<ul>
<li class="">
<a href="http://127.0.0.1/site/index">Home</a>
<BLANKLINE>
</li>
<li class="">
<a href="http://127.0.0.1/site/view3">View 3</a>
<BLANKLINE>
</li>
</ul>
</div>
You can change the order of the groups by setting the grouporder attribute on the Menu class:
>>> class GroupedMenu(navigation.Menu):
... grok.implements(IGroupedMenu)
... grouporder=['Group 1', 'Group 2']
>>> grok_component('GroupedMenu', GroupedMenu)
True
>>> gm = GroupedMenu(site, request, grok.View(site, request))
>>> gm.update()
>>> print gm.render()
<div class="">
<ul>
<li class="">
<a href="http://127.0.0.1/site/index">Home</a>
<BLANKLINE>
</li>
<li class="">
<a href="http://127.0.0.1/site/view3">View 3</a>
<BLANKLINE>
</li>
</ul>
<ul>
<li class="">
<a href="http://127.0.0.1/site/view2">View 2</a>
<BLANKLINE>
</li>
</ul>
</div>
megrok.navigation uses zope.pagetemplate (or megrok.pagetemplate) to allow you to override the default templates.
Let's define a template based on divs, instead of ul
>>> mt = """<div tal:attributes='class menu/cssClass'>
... <div tal:repeat='group menu/groups'>
... <tal:repeat tal:repeat="item group/items"
... tal:replace='structure item/render' />
... </div>
... </div>"""
>>> from megrok import pagetemplate
>>> class DivMenu(pagetemplate.PageTemplate):
... template = grok.PageTemplate(mt)
... pagetemplate.view(navigation.interfaces.IMenu)
>>> grok_component('divmenu', DivMenu)
True
>>> it = """<div tal:attributes='class menu/cssItemClass'>
... <a tal:attributes="href item/link;
... title viewlet/description|nothing">
... <img tal:condition="item/icon | nothing"
... tal:attributes="src item/icon"/>
... <span tal:replace="item/title"/></a>
... <tal:replace tal:condition="item/submenu | nothing"
... tal:replace="structure provider:${item/submenu}"/>
... </div>"""
>>> class DivMenuItem(pagetemplate.PageTemplate):
... template = grok.PageTemplate(it)
... pagetemplate.view(navigation.interfaces.IMenuItem)
>>> grok_component('divmenuitem', DivMenuItem)
True
>>> print actions.render()
<div class="">
<div>
<div class="">
<a href="http://127.0.0.1/site/foo/fooindex">
<BLANKLINE>
Details</a>
<BLANKLINE>
</div>
<div class="">
<a href="http://127.0.0.1/site/foo/fooedit">
<BLANKLINE>
Edit</a>
<BLANKLINE>
</div>
<div class="">
<a href="http://127.0.0.1/site/foo/fooprotected">
<BLANKLINE>
Manage</a>
<BLANKLINE>
</div>
</div>
</div>
But what if you want 2 different templates for items to be used in different menus? You never specify any Items
yourself, so you can't tell them to implement a different interface and register the template to that interface.
To allow this, the itemsimplement directive was introduced.
>>> class IIconItem(navigation.interfaces.IMenuItem):
... pass
>>> class IconMenu(navigation.Menu):
... grok.name('icons')
... navigation.itemsimplement(IIconItem)
... navigation.globalmenuitem('http://grok.zope.org', 'Grok!',
... icon='icon.jpg')
>>> grok_component('nav', IconMenu)
True
>>> it = """<img tal:condition="item/icon | nothing"
... tal:attributes="src item/icon"/>"""
>>> class IconMenuItem(pagetemplate.PageTemplate):
... template = grok.PageTemplate(it)
... pagetemplate.view(IIconItem)
>>> grok_component('iconmenuitem', IconMenuItem)
True
>>> icons = IconMenu(site, request, grok.View(site, request))
>>> icons.update()
>>> print icons.render()
<div class="">
<div>
<img src="icon.jpg" />
</div>
</div>
Register plain viewlets and *.items together.
System Message: WARNING/2 (<string>, line 854); backlink
Inline emphasis start-string without end-string.
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> class IGlobalMenu(navigation.interfaces.IMenu):
... pass
>>> class GlobalMenu(navigation.Menu):
... grok.name('globalmenu')
... grok.implements(IGlobalMenu)
>>> grok_component('globalmenu', GlobalMenu)
True
>>> class Contact(grok.View):
... grok.context(MySite)
... grok.layer(IDefaultBrowserLayer)
... navigation.menuitem(IGlobalMenu)
... def render(self):
... return "test"
>>> grok_component('contact', Contact)
True
>>> class Logout(grok.Viewlet):
... grok.viewletmanager(IGlobalMenu)
... grok.context(MySite)
... grok.layer(IDefaultBrowserLayer)
... def render(self):
... return "logout"
>>> grok_component('logout', Logout)
True
>>> globalmenu = getMultiAdapter((site, request, grok.View(site, request)),
... IContentProvider, name="globalmenu")
>>> globalmenu.update()
>>> logout, contact = globalmenu.viewlets
>>> logout
<megrok.navigation.tests.Logout object at ...>
>>> contact
<megrok.navigation.util.MySite_contact object at ...>
>>> grok.layer.bind().get(logout)
<InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>
>>> grok.layer.bind().get(contact)
<InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>
>>> print globalmenu.render()
<div class="">
<div>
logout
<div class="">
<a href="http://127.0.0.1/site/contact">
contact</a>
</div>
</div>
</div>
Let's see if we can get the individual viewlets based on their name via
the Zope-Component Architecture
>>> from zope.viewlet.interfaces import IViewlet
>>> contact = getMultiAdapter(
... (site, request, grok.View(site, request), globalmenu),
... IViewlet, name=u"MySite_contact")
>>> contact
<megrok.navigation.util.MySite_contact object at ...>
>>> class Contact(grok.View):
... grok.context(MySite)
... grok.name('othercontact')
... grok.title('Other Link')
... grok.layer(IDefaultBrowserLayer)
... navigation.menuitem(IGlobalMenu)
... def render(self):
... return "test"
>>> grok_component('contact', Contact)
True
>>> contact1 = getMultiAdapter(
... (site, request, grok.View(site, request), globalmenu),
... IViewlet, name=u"MySite_othercontact")
>>> contact1
<megrok.navigation.util.MySite_othercontact object at ...>