This implementation is based on the Two Level Navigation In Plone. In the original how-to, the author customized global_sections template which has been deprecated for Plone 3 onwards. This how-to shows how to do it by modifying the viewlet for the navigation based on a theme product. For more details about modifying the viewlet, please refer to the Overriding Template View how-to.
Step By Step
These are the steps to implement it:
1. First you have to have your theme product ready. If you do not know how to do this then please refer to How To Create a Plone 3 Theme Product on the Filesystem. For this example let's assume the theme product is inigo.theme.
2. Then inside your theme product you should have a directory called viewlet. I used this method with the idea that any modifications for the viewlets which you would like to do for your theme should be put in here.
3. In the viewlet folder you should have 4 file:
* __init__.py
* common.py
* configure.zcml
* sections.pt
4. Contents of those files should be:
* __init__.py : empty file. Should be there just so that you could package the folder as part of your egg
* common.py :
from zope.interface import implements, alsoProvides
from zope.component import getMultiAdapter
from zope.viewlet.interfaces import IViewlet
from zope.deprecation.deprecation import deprecate
from plone.app.layout.globals.interfaces import IViewView
from AccessControl import getSecurityManager
from Acquisition import aq_base, aq_inner
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone import utils
from Products.CMFPlone.browser.navigation import get_url,get_id,get_view_url
from cgi import escape
from urllib import quote_plus
from plone.app.layout.navigation.root import getNavigationRoot
class ViewletBase(BrowserView):
""" Base class with common functions for link viewlets.
"""
implements(IViewlet)
def __init__(self, context, request, view, manager):
super(ViewletBase, self).__init__(context, request)
self.__parent__ = view
self.context = context
self.request = request
self.view = view
self.manager = manager
@property
@deprecate("Use site_url instead. ViewletBase.portal_url will be removed in Plone 4")
def portal_url(self):
return self.site_url
def update(self):
self.portal_state = getMultiAdapter((self.context, self.request),
name=u'plone_portal_state')
self.site_url = self.portal_state.portal_url()
def render(self):
# defer to index method, because that's what gets overridden by the template ZCML attribute
return self.index()
def index(self):
raise NotImplementedError(
'`index` method must be implemented by subclass.')
class GlobalSectionsViewlet(ViewletBase):
index = ViewPageTemplateFile('sections.pt')
def update(self):
context_state = getMultiAdapter((self.context, self.request),
name=u'plone_context_state')
actions = context_state.actions()
portal_tabs_view = getMultiAdapter((self.context, self.request),
name='portal_tabs_view')
self.portal_tabs = portal_tabs_view.topLevelTabs(actions=actions)
selectedTabs = self.context.restrictedTraverse('selectedTabs')
self.selected_tabs = selectedTabs('index_html',
self.context,
self.portal_tabs)
self.selected_portal_tab = self.selected_tabs['portal']
for tab in self.portal_tabs:
if tab['id']!='Members':
tab['subtab']=self.getsubtab(self.context,tab)
else:
tab['subtab']=[]
def getsubtab(self,context,tab):
query={}
result=[]
portal_properties = getToolByName(context, 'portal_properties')
portal_catalog = getToolByName(context, 'portal_catalog')
navtree_properties = getattr(portal_properties, 'navtree_properties')
site_properties = getattr(portal_properties, 'site_properties')
rootPath = getNavigationRoot(context)
dpath='/'.join([rootPath,tab['id']])
query['path'] = {'query' : dpath, 'depth' : 1}
query['portal_type'] = utils.typesToList(context)
sortAttribute = navtree_properties.getProperty('sortAttribute', None)
if sortAttribute is not None:
query['sort_on'] = sortAttribute
sortOrder = navtree_properties.getProperty('sortOrder', None)
if sortOrder is not None:
query['sort_order'] = sortOrder
if navtree_properties.getProperty('enable_wf_state_filtering', False):
query['review_state'] = navtree_properties.getProperty('wf_states_to_show', [])
query['is_default_page'] = False
if site_properties.getProperty('disable_nonfolderish_sections', False):
query['is_folderish'] = True
# Get ids not to list and make a dict to make the search fast
idsNotToList = navtree_properties.getProperty('idsNotToList', ())
excludedIds = {}
for id in idsNotToList:
excludedIds[id]=1
rawresult = portal_catalog.searchResults(**query)
# now add the content to results
for item in rawresult:
if not (excludedIds.has_key(item.getId) or item.exclude_from_nav):
id, item_url = get_view_url(item)
data = {'name' : utils.pretty_title_or_id(context, item),
'id' : item.getId,
'url' : item_url,
'description': item.Description}
result.append(data)
return result
* configure.zcml:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:viewlet
name="plone.global_sections"
manager="plone.app.layout.viewlets.interfaces.IPortalHeader"
class=".common.GlobalSectionsViewlet"
permission="zope2.View"
layer="inigo.theme.browser.interfaces.IThemeSpecific"
/>
</configure>
* sections.pt:
<tal:tabs tal:condition="view/portal_tabs"
i18n:domain="plone">
<h5 class="hiddenStructure" i18n:translate="heading_sections">Sections</h5>
<ul id="portal-globalnav">
<tal:tabs tal:repeat="tab view/portal_tabs"><li tal:attributes="id string:portaltab-${tab/id};
class python:view.selected_portal_tab==tab['id'] and 'selected' or 'plain'"
><a href=""
tal:content="tab/name"
tal:attributes="href tab/url;
title tab/description|nothing">
Tab Name
</a>
<tal:block omit-tag=""
tal:define="subnav tab/subtab;">
<tal:block omit-tag="" tal:condition="subnav">
<ul class="nn-twolevel-subnav">
<tal:tabs tal:repeat="subtab subnav">
<li tal:attributes="id string:portaltab-${subtab/id};">
<a href="" class="" tal:attributes="href subtab/url;" accesskey="accesskeys-tabs" i18n:attributes="accesskey">
<tal:block omit-tag="" i18n:translate="" >
<span tal:replace="subtab/name">Tab Name</span>
</tal:block>
</a>
</li>
</tal:tabs>
</ul>
</tal:block>
</tal:block>
</li></tal:tabs>
</ul>
</tal:tabs>
5. Once the viewlet folder is complete, you need to add it to your theme product configure.zcml by adding:
<include package=".viewlet" />
6. Then you should modify your theme main css file (usually public.css.dtml) so that it would hide the submenu and reveal it only on hover of the main tab:
#portal-globalnav {
position:relative;
display: inline;
}
#portal-globalnav li ul {
display: none;
visibility: hidden;
position: absolute;
z-index: 200;
}
#portal-globalnav li ul li {
display: block;
margin: 0;
padding: 0;
width: 100%;
clear: left;
}
#portal-globalnav li ul li a {
display: block;
}
#portal-globalnav li:hover ul {
visibility: visible;
display: block;
}
#portal-globalnav li {
display: inline;
position: relative;
float: left;
}
Additional Information
The code for the getsubtab function was mostly copied from the CMFPlone/browser/navigation.py topLevelTabs function. This is so that the same behaviour can be expected for our sub menu as our normal tabs (eg. hide from navigation etc). The CSS given above is mostly just to hide and show the sub-menu. It would require additional mark-up to make it compeletely compatible and completely blend with your theme.