[Checkins] SVN: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/ -
Implemented menu selector concept. This allows us to register
Roger Ineichen
roger at projekt01.ch
Thu Jan 24 10:59:32 EST 2008
Log message for revision 83170:
- Implemented menu selector concept. This allows us to register
different rules for rendering selected menus. That's the part which makes a
menu concept flexible or not and allows to add new menu items without to change existing code.
- Implemented ZCML directive for menu selector
Changed:
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/README.txt
A z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/checker.py
A z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/configure.zcml
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/interfaces.py
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.pt
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.py
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/manager.py
A z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/meta.zcml
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/testing.py
U z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/tests.py
A z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.py
A z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.txt
-=-
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/README.txt
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/README.txt 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/README.txt 2008-01-24 15:59:31 UTC (rev 83170)
@@ -22,6 +22,9 @@
>>> from z3c.menu.ready2go import IAddMenu
>>> from z3c.menu.ready2go.manager import MenuManager
+And we configure our menu item as viewlet Managers. This is normaly done by the
+``viewletManager`` ZCML directive:
+
>>> GlobalMenu = manager.ViewletManager('left', IGlobalMenu,
... bases=(MenuManager,))
@@ -48,20 +51,51 @@
>>> interfaces.IMenuManager.implementedBy(AddMenu)
True
-Now we have to define a context:
+We also need our checker adapter which can check if a menu item is available
+and/or selected:
+ >>> import zope.component
+ >>> from z3c.menu.ready2go import checker
+ >>> zope.component.provideAdapter(checker.GlobalSelectedChecker)
+ >>> zope.component.provideAdapter(checker.SiteSelectedChecker)
+ >>> zope.component.provideAdapter(checker.ContextSelectedChecker)
+
+Now we have to define a site and a context:
+
>>> import zope.interface
>>> from zope.app.container import contained
+ >>> from zope.app.container import btree
>>> from zope.app.container.interfaces import IContained
+ >>> from zope.location.interfaces import IPossibleSite
+ >>> from zope.app.component.site import SiteManagerContainer
+ >>> from zope.app.component.site import LocalSiteManager
+
+ >>> class Site(btree.BTreeContainer, SiteManagerContainer):
+ ... zope.interface.implements(IPossibleSite)
+ ... def __init__(self):
+ ... super(Site, self).__init__()
+ ... self.setSiteManager(LocalSiteManager(self))
+
>>> class Content(contained.Contained):
... zope.interface.implements(IContained)
- >>> root['content'] = Content()
- >>> content = root['content']
+ >>> root['site'] = Site()
+ >>> site = root['site']
+
+Now we have to set the site object as site. This is normaly done by the
+traverser but we do this here with the hooks helper because we do not really
+traaverse to the site within the publisher/traverser:
+
+ >>> from zope.app.component import hooks
+ >>> hooks.setSite(site)
+
+ >>> site['content'] = Content()
+ >>> content = site['content']
+
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
-And we need a view:
+And we need a view which knows about it's parent:
>>> from zope.publisher.interfaces.browser import IBrowserView
>>> class View(contained.Contained):
@@ -75,7 +109,9 @@
>>> view = View(content, request)
-Our menus can adapt the context, request and view:
+Our menus can adapt the context, request and view. See IViewletManager in
+zope.viewlet for more infos about this pattern. If we render them, there is an
+empty string returned. This means the menus don't find menu items for rendering:
>>> globalMenu = GlobalMenu(content, request, view)
>>> globalMenu.update()
@@ -101,14 +137,12 @@
Global Menu Item
----------------
+Now we register a context menu item for our IGlobalMenu:
-But now we register a context menu item for the IMenu:
-
- >>> import zope.component
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> from z3c.menu.ready2go.item import GlobalMenuItem
- >>> class RootMenuItem(GlobalMenuItem):
+ >>> class MyGlobalMenuItem(GlobalMenuItem):
...
... viewName = 'root.html'
@@ -116,18 +150,197 @@
>>> from zope.security.checker import NamesChecker, defineChecker
>>> viewletChecker = NamesChecker(('update', 'render'))
- >>> defineChecker(RootMenuItem, viewletChecker)
+ >>> defineChecker(MyGlobalMenuItem, viewletChecker)
+And we configure our menu item for IGlobalMenu. This is normaly done by the
+``viewlet`` ZCML directive:
+
>>> zope.component.provideAdapter(
- ... RootMenuItem,
+ ... MyGlobalMenuItem,
... (zope.interface.Interface, IDefaultBrowserLayer,
... IBrowserView, IGlobalMenu),
- ... IViewlet, name='RootMenuItem')
+ ... IViewlet, name='My Global')
-Now let's render the global menu again:
+Now let's render the global menu again. You can see that we ve got a menu item:
>>> globalMenu.update()
>>> print globalMenu.render()
+ <li>
+ <a href="http://127.0.0.1/root.html"><span>My Global</span></a>
+ </li>
+
+
+Site Menu Item
+--------------
+
+Now we register a context menu item for our ISiteMenu:
+
+ >>> import zope.component
+ >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+
+ >>> from z3c.menu.ready2go.item import SiteMenuItem
+ >>> class MySiteMenuItem(SiteMenuItem):
+ ...
+ ... viewName = 'site.html'
+
+Now we need a security checker for our menu item
+
+ >>> from zope.security.checker import NamesChecker, defineChecker
+ >>> viewletChecker = NamesChecker(('update', 'render'))
+ >>> defineChecker(MySiteMenuItem, viewletChecker)
+
+And we configure our menu item for ISiteMenu. This is normaly done by the
+``viewlet`` ZCML directive:
+
+ >>> zope.component.provideAdapter(
+ ... MySiteMenuItem,
+ ... (zope.interface.Interface, IDefaultBrowserLayer,
+ ... IBrowserView, ISiteMenu),
+ ... IViewlet, name='My Site')
+
+Now let's render the site menu again. You can see that we ve got a menu item
+and the url points to our site:
+
+ >>> siteMenu.update()
+ >>> print siteMenu.render()
+ <li>
+ <a href="http://127.0.0.1/site/site.html"><span>My Site</span></a>
+ </li>
+
+
+Context Menu Item
+-----------------
+
+Now we register a context menu item for our IContextMenu:
+
+ >>> import zope.component
+ >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+
+ >>> from z3c.menu.ready2go.item import ContextMenuItem
+ >>> class MyContextMenuItem(ContextMenuItem):
+ ...
+ ... viewName = 'context.html'
+
+Now we need a security checker for our menu item
+
+ >>> from zope.security.checker import NamesChecker, defineChecker
+ >>> viewletChecker = NamesChecker(('update', 'render'))
+ >>> defineChecker(MyContextMenuItem, viewletChecker)
+
+And we configure our menu item for IContextMenu. This is normaly done by the
+``viewlet`` ZCML directive:
+
+ >>> zope.component.provideAdapter(
+ ... MyContextMenuItem,
+ ... (zope.interface.Interface, IDefaultBrowserLayer,
+ ... IBrowserView, IContextMenu),
+ ... IViewlet, name='My Context')
+
+Now let's render the context menu again. You can see that we ve got a menu
+item. Another important point here is, that the url of such ContextMemuItem
+implementations point to the context of the view:
+
+ >>> contextMenu.update()
+ >>> print contextMenu.render()
+ <li>
+ <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
+ </li>
+
+Let's set the view __name__ to ``context.html``. This will reflect that
+the view offers the same name that our context menu needs to get rendered as
+selected:
+
+ >>> view.__name__ = 'context.html'
+
+Now try again and see if the context menu titem get rendered as selected:
+
+ >>> contextMenu.update()
+ >>> print contextMenu.render()
<li class="selected">
- <a href="http://127.0.0.1/root.html"><span>RootMenuItem</span></a>
+ <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
</li>
+
+
+Menu groups
+-----------
+
+The global and the site menu items are grouped menu items. This means such menu
+items should get rendered as selected if a context menu item is selected. This
+reflects the menu hierarchie. Let's show how we can solve this not so simple
+problem. We offer a ISelectedChecker adapter which can decide if a menu get
+rendered as selected or not. This is very usefull because normaly a menu get
+registered and later we add views and can not change the menu item
+implementation. Let's see how such an adapter can handle an existing menu,
+context and view setup and change the selected rendering. We register a
+selected checker for our site menu item:
+
+ >>> zope.component.provideAdapter(checker.TrueSelectedChecker,
+ ... (IContained, IDefaultBrowserLayer, None, ISiteMenu, MySiteMenuItem),
+ ... interfaces.ISelectedChecker)
+
+Now we can render the site menu again. Note that our context is still the
+sample content object.
+
+ >>> siteMenu.update()
+ >>> print siteMenu.render()
+ <li class="selected">
+ <a href="http://127.0.0.1/site/site.html"><span>My Site</span></a>
+ </li>
+
+This reflects that the site menu is a group menu which the context menu item
+of the content object is selected too.
+
+ >>> contextMenu.update()
+ >>> print contextMenu.render()
+ <li class="selected">
+ <a href="http://127.0.0.1/site/content/context.html"><span>My Context</span></a>
+ </li>
+
+
+Special use case
+----------------
+
+We have some special use case because of Zope's internals. One important part
+is that our menu heavy depend on context and it's __parent__ chain to the
+zope application root. This is not allways supported by Zopes default setup.
+One part is the bad integrated application control part which fakes a root
+object which doesn't know about the real childs of the real root from the
+ZODB e.g. application root. Now we will show you that our menu by default
+render no items if we get such a fake root which messes up our menu structure.
+
+Let's define a object which does not know about any __parent__.
+
+ >>> nirvana = Content()
+ >>> nirvanaView = View(nirvana, request)
+
+Now we can check what's happen to the menus if we adapt the parent less nirvana
+context and update and render the menus. You can see that the global menu does
+not contain any menu item. That's because the global menu items tries to find
+the root by traversing from the context to the root by the __parent__ chain
+and we don't support any parent for your nirvana object:
+
+ >>> globalMenu = GlobalMenu(nirvana, request, nirvanaView)
+ >>> globalMenu.update()
+ >>> globalMenu.render()
+ u''
+
+But you can see that the site menu renders the menu item becyuse we lookup the
+site by the hooks and we still point to our site we set with setSite():
+
+ >>> siteMenu = SiteMenu(nirvana, request, nirvanaView)
+ >>> siteMenu.update()
+ >>> print siteMenu.render()
+ <li class="selected">
+ <a href="http://127.0.0.1/site/site.html"><span>My Site</span></a>
+ </li>
+
+
+ >>> contextMenu = ContextMenu(nirvana, request, nirvanaView)
+ >>> contextMenu.update()
+ >>> contextMenu.render()
+ u''
+
+ >>> addMenu = AddMenu(nirvana, request, nirvanaView)
+ >>> addMenu.update()
+ >>> addMenu.render()
+ u''
Added: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/checker.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/checker.py (rev 0)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/checker.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -0,0 +1,105 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id: layer.py 197 2007-04-13 05:03:32Z rineichen $
+"""
+
+import zope.interface
+import zope.component
+from zope.publisher.interfaces.browser import IBrowserRequest
+
+from z3c.menu.ready2go import interfaces
+
+
+class CheckerBase(object):
+ """Generic checker base class."""
+
+ def __init__(self, context, request, view, menu, item):
+ self.context = context
+ self.request = request
+ self.view = view
+ self.menu = menu
+ self.item = item
+
+
+# ISelectedChecker
+class FalseSelectedChecker(CheckerBase):
+ """False selected checker can avoid selected menu item rendering."""
+
+ zope.interface.implements(interfaces.ISelectedChecker)
+
+ @property
+ def selected(self):
+ return False
+
+
+class TrueSelectedChecker(CheckerBase):
+ """True selected checker can force selected menu item rendering."""
+
+ zope.interface.implements(interfaces.ISelectedChecker)
+
+ @property
+ def selected(self):
+ return True
+
+
+class ViewNameSelectedChecker(CheckerBase):
+ """Selected by view name offers a generic checker for IContextMenuItem."""
+
+ zope.interface.implements(interfaces.ISelectedChecker)
+
+ @property
+ def selected(self):
+ """Selected if also view name compares."""
+ if self.view.__name__ == self.item.viewName:
+ return True
+ return False
+
+
+# default selected checkers
+class GlobalSelectedChecker(FalseSelectedChecker):
+ """Global menu item selected checker.
+
+ Note, this is a menu group which is selected on different menu items.
+ You need to register for each view a TrueSelectedChecker if the site menu
+ item should get rendered as selected.
+ """
+
+ zope.component.adapts(zope.interface.Interface, IBrowserRequest,
+ zope.interface.Interface, interfaces.IMenuManager,
+ interfaces.IGlobalMenuItem)
+
+
+class SiteSelectedChecker(FalseSelectedChecker):
+ """Site menu item selected checker.
+
+ Note, this is a menu group which is selected on different menu items.
+ You need to register for each view a TrueSelectedChecker if the site menu
+ item should get rendered as selected.
+ """
+
+ zope.component.adapts(zope.interface.Interface, IBrowserRequest,
+ zope.interface.Interface, interfaces.IMenuManager,
+ interfaces.ISiteMenuItem)
+
+
+class ContextSelectedChecker(ViewNameSelectedChecker):
+ """Context menu item selected checker."""
+
+ zope.component.adapts(zope.interface.Interface, IBrowserRequest,
+ zope.interface.Interface, interfaces.IMenuManager,
+ interfaces.IContextMenuItem)
+
+
+
Property changes on: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/checker.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/configure.zcml
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/configure.zcml (rev 0)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/configure.zcml 2008-01-24 15:59:31 UTC (rev 83170)
@@ -0,0 +1,16 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope">
+
+ <adapter
+ factory=".checker.GlobalSelectedChecker"
+ />
+
+ <adapter
+ factory=".checker.SiteSelectedChecker"
+ />
+
+ <adapter
+ factory=".checker.ContextSelectedChecker"
+ />
+
+</configure>
Property changes on: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/interfaces.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/interfaces.py 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/interfaces.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -15,6 +15,7 @@
$Id: layer.py 197 2007-04-13 05:03:32Z rineichen $
"""
+import zope.interface
import zope.schema
from zope.viewlet import interfaces
@@ -93,7 +94,7 @@
default=u''
)
- subProviderName = zope.schema.TextLine(
+ subMenuProviderName = zope.schema.TextLine(
title=_('Sub menu provider name'),
description=_('Name of the sub menu provider.'),
default=u''
@@ -106,6 +107,16 @@
"""Return the template with the option 'menus'"""
+class ISelectedChecker(zope.interface.Interface):
+ """Selected checker."""
+
+ selected = zope.schema.Bool(
+ title=_('Selected'),
+ description=_('Marker for selected menu item'),
+ default=False
+ )
+
+
class IGlobalMenuItem(IMenuItem):
"""Menu item with ZODB application root as url base."""
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.pt
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.pt 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.pt 2008-01-24 15:59:31 UTC (rev 83170)
@@ -1,8 +1,8 @@
<li class="current"
tal:attributes="class view/css"
- tal:define="subProviderName view/subProviderName">
+ tal:define="subProviderName view/subMenuProviderName">
<a href="#"
- tal:attributes="href view/url"><span i18n:translate="" tal:content="view/title">Title</span></a>
+ tal:attributes="href view/approvedURL"><span i18n:translate="" tal:content="view/title">Title</span></a>
<tal:block condition="subProviderName"
replace="structure provider:${subProviderName}">sub menu items</tal:block>
</li>
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.py 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/item.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -33,15 +33,48 @@
template = ViewPageTemplateFile('item.pt')
- # set this attrs directly in zcml or override it in a sub class
+ # internal approved values
+ approved = False
+ approvedURL = None
+
+ # url view name if different then ``selected`` viewName
+ viewName = u'index.html'
+
+ # ``selected`` discriminator values
contextInterface = zope.interface.Interface
viewInterface = zope.interface.Interface
- viewName = u'index.html'
+ selectedViewName = viewName
+
+ # css classes
cssActive = u'selected'
cssInActive = u''
+
+ # menu order weight
weight = 0
+
+ # sub menu provider name
subMenuProviderName = None
+ def __init__(self, context, request, view, manager):
+ super(MenuItem, self).__init__(context, request, view, manager)
+ self.view = view
+ self.setupFilter()
+
+ def setupFilter(self):
+ """Catch location error and set approved attributes.
+
+ Note, this get called before update because the filter method in menu
+ manager needs to know that before the menu items update method get
+ called.
+ """
+ try:
+ if self.available:
+ self.approvedURL = self.url
+ self.approved = True
+ except TypeError:
+ self.approvedURL = None
+ self.approved = False
+
# override it and use i18n msg ids
@property
def title(self):
@@ -49,33 +82,34 @@
@property
def css(self):
- if self.selected:
+ """Return cssActive, cssInActive or None.
+
+ None will not render a HTML attribute in TAL.
+ """
+ if self.selected and self.cssActive:
return self.cssActive
+ elif self.selected and self.cssInActive:
+ return self.cssInActive
else:
- return self.cssInActive
+ return None
@property
def available(self):
+ """Available checker call"""
return True
@property
def selected(self):
- """Selected if context and view interfaces compares."""
- if self.viewInterface.providedBy(self.__parent__) and \
- self.contextInterface.providedBy(self.__parent__.context):
- return True
- return False
+ """Selected checker call"""
+ checker = zope.component.getMultiAdapter((self.context, self.request,
+ self.view, self.manager, self), interfaces.ISelectedChecker)
+ return checker.selected
@property
def url(self):
- context = self.getURLContext()
- return absoluteURL(context, self.request) + '/' + self.viewName
+ return '%s/%s' % (absoluteURL(self.getURLContext(), self.request),
+ self.viewName)
- @property
- def subProviderName(self):
- """Name of the sub item menu provider."""
- return self.subMenuProviderName
-
def getURLContext(self):
return getRoot(self.context)
@@ -92,14 +126,7 @@
zope.interface.implements(interfaces.IGlobalMenuItem)
- @property
- def selected(self):
- if self.viewInterface.providedBy(self.__parent__) and \
- self.contextInterface.providedBy(self.__parent__.context):
- return True
- return False
-
class SiteMenuItem(MenuItem):
"""Site menu item."""
@@ -114,15 +141,6 @@
zope.interface.implements(interfaces.IContextMenuItem)
- @property
- def selected(self):
- """Selected if also view name compares."""
- if self.viewInterface.providedBy(self.__parent__) and \
- self.contextInterface.providedBy(self.__parent__.context) and \
- self.__parent__.__name__ == self.viewName:
- return True
- return False
-
def getURLContext(self):
return self.context
@@ -132,13 +150,11 @@
zope.interface.implements(interfaces.IAddMenuItem)
+ subMenuProviderName = None
+
@property
def selected(self):
return False
- @property
- def subProviderName(self):
- return None
-
def getURLContext(self):
return self.context
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/manager.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/manager.py 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/manager.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -17,17 +17,33 @@
__docformat__ = "reStructuredText"
import zope.interface
+import zope.security
from zope.viewlet import manager
from z3c.menu.ready2go import interfaces
+def isAvailable(viewlet):
+ try:
+ return zope.security.canAccess(viewlet, 'render') and viewlet.approved
+ except AttributeError:
+ return True
+
+
class MenuManager(manager.ConditionalViewletManager):
"""Menu manager for all kind of menu items"""
zope.interface.implements(interfaces.IMenuManager)
+ def filter(self, viewlets):
+ """Sort out all viewlets which are explicit not available
+ ``viewlets`` is a list of tuples of the form (name, viewlet).
+ """
+ return [(name, viewlet) for name, viewlet in viewlets
+ if isAvailable(viewlet)]
+
+
class EmptyMenuManager(object):
"""Empty menu manager."""
Added: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/meta.zcml
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/meta.zcml (rev 0)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/meta.zcml 2008-01-24 15:59:31 UTC (rev 83170)
@@ -0,0 +1,16 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:meta="http://namespaces.zope.org/meta">
+
+ <meta:directives namespace="http://namespaces.zope.org/z3c">
+
+ <meta:directive
+ name="menuSelector"
+ schema=".zcml.IMenuSelectorDirective"
+ handler=".zcml.menuSelectorDirective"
+ />
+
+ </meta:directives>
+
+</configure>
+
Property changes on: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/meta.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/testing.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/testing.py 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/testing.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -17,25 +17,77 @@
__docformat__ = 'restructuredtext'
import zope.security
-from zope.app.testing import setup, ztapi
+from zope.publisher.interfaces.browser import IBrowserView
+from zope.app.testing import setup
+from zope.app.testing import ztapi
+from zope.app.container import contained
+from z3c.menu.ready2go import interfaces
+from z3c.menu.ready2go import item
+
class TestParticipation(object):
principal = 'foobar'
interaction = None
+class ISample(zope.interface.Interface):
+ """Sample context interface."""
+
+
+class Sample(object):
+ """Sample context object."""
+
+ zope.interface.implements(ISample)
+
+ def __init__(self, title):
+ self.title = title
+
+
+class LocatableView(contained.Contained):
+
+ zope.interface.implements(IBrowserView)
+
+ def __init__(self, context, request):
+ self.__parent__ = context
+ self.context = context
+ self.request = request
+
+class IFirstView(IBrowserView):
+ """First sample view interface."""
+
+class ISecondView(IBrowserView):
+ """Second sample view interface."""
+
+class FirstView(LocatableView):
+ """First view."""
+
+ zope.interface.implements(IFirstView)
+
+class SecondView(LocatableView):
+ """Second view."""
+
+ zope.interface.implements(ISecondView)
+
+
+class IFirstMenu(interfaces.IMenuManager):
+ """First menu manager."""
+
+class ISecondMenu(interfaces.IMenuManager):
+ """Second menu manager."""
+
+
+class FirstMenuItem(item.ContextMenuItem):
+ viewName = 'first.html'
+
+class SecondMenuItem(item.ContextMenuItem):
+ viewName = 'second.html'
+
+
def setUp(test):
root = setup.placefulSetUp(site=True)
test.globs['root'] = root
-
- # resource namespace setup
- from zope.traversing.interfaces import ITraversable
- from zope.traversing.namespace import resource
- ztapi.provideAdapter(None, ITraversable, resource, name="resource")
- ztapi.provideView(None, None, ITraversable, "resource", resource)
-
from zope.app.pagetemplate import metaconfigure
from zope.contentprovider import tales
metaconfigure.registerType('provider', tales.TALESProviderExpression)
Modified: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/tests.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/tests.py 2008-01-24 15:12:19 UTC (rev 83169)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/tests.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -32,6 +32,25 @@
from z3c.menu.ready2go import testing
+class CheckerStub(object):
+ """Just a checker stub."""
+
+ def __init__(self, context, request, view, menu, item):
+ self.context = context
+ self.request = request
+ self.view = view
+ self.menu = menu
+ self.item = item
+
+ @property
+ def available(self):
+ return True
+
+ @property
+ def selected(self):
+ return True
+
+
class ParentStub(object):
"""Just an object supporting a context attribtute."""
@@ -80,6 +99,8 @@
hooks.setSite(site)
zope.component.provideAdapter(AbsoulteURLStub, (None, None),
IAbsoluteURL)
+ zope.component.provideAdapter(CheckerStub, (None, None, None, None,
+ None), interfaces.ISelectedChecker)
super(GlobalMenuItemTest, self).setUp()
def getTestInterface(self):
@@ -99,6 +120,8 @@
hooks.setSite(site)
zope.component.provideAdapter(AbsoulteURLStub, (None, None),
IAbsoluteURL)
+ zope.component.provideAdapter(CheckerStub, (None, None, None, None,
+ None), interfaces.ISelectedChecker)
super(SiteMenuItemTest, self).setUp()
def getTestInterface(self):
@@ -113,6 +136,11 @@
class ContextMenuItemTest(z3c.testing.InterfaceBaseTest):
+ def setUp(self):
+ zope.component.provideAdapter(CheckerStub, (None, None, None, None,
+ None), interfaces.ISelectedChecker)
+ super(ContextMenuItemTest, self).setUp()
+
def getTestInterface(self):
return interfaces.IContextMenuItem
@@ -129,6 +157,10 @@
setUp=testing.setUp, tearDown=testing.tearDown,
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
),
+ DocFileSuite('zcml.txt',
+ setUp=testing.setUp, tearDown=testing.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
unittest.makeSuite(MenuManagerTest),
unittest.makeSuite(GlobalMenuItemTest),
unittest.makeSuite(SiteMenuItemTest),
Added: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.py
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.py (rev 0)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.py 2008-01-24 15:59:31 UTC (rev 83170)
@@ -0,0 +1,90 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+import zope.configuration.fields
+import zope.security.zcml
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IBrowserView
+
+from zope.component import zcml
+
+from z3c.i18n import MessageFactory as _
+from z3c.menu.ready2go import interfaces
+from z3c.menu.ready2go import checker
+
+
+class IMenuSelectorDirective(zope.interface.Interface):
+ """A directive to register a menu selector."""
+
+ factory = zope.configuration.fields.GlobalObject(
+ title=_("Selector factory"),
+ description=_("Python name of a factory which can create the"
+ " selector object. This must identify an"
+ " object in a module using the full dotted name."),
+ required=False,
+ default=checker.TrueSelectedChecker)
+
+ for_ = zope.configuration.fields.GlobalObject(
+ title=u"Context",
+ description=u"The content interface or class this selector is for.",
+ required=False)
+
+ view = zope.configuration.fields.GlobalObject(
+ title=_("The view the selector is registered for."),
+ description=_("The view can either be an interface or a class. By "
+ "default the provider is registered for all views, "
+ "the most common case."),
+ required=False,
+ default=IBrowserView)
+
+ layer = zope.configuration.fields.GlobalObject(
+ title=_("The layer the view is in."),
+ description=_("""
+ A skin is composed of layers. It is common to put skin
+ specific views in a layer named after the skin. If the 'layer'
+ attribute is not supplied, it defaults to 'default'."""),
+ required=False,
+ default=IBrowserRequest)
+
+ manager = zope.configuration.fields.GlobalObject(
+ title=u"Menu Manager",
+ description=u"The menu manager interface or class this selector is for.",
+ required=False,
+ default=interfaces.IMenuManager)
+
+ menu = zope.configuration.fields.GlobalObject(
+ title=u"Menu Item",
+ description=u"The menu item interface or class this selector is for.",
+ required=False,
+ default=interfaces.IMenuItem)
+
+
+# menu selector directive
+def menuSelectorDirective(
+ _context, factory=checker.TrueSelectedChecker,
+ for_=zope.interface.Interface, layer=IBrowserRequest, view=IBrowserView,
+ manager=interfaces.IMenuManager, menu=interfaces.IMenuItem):
+
+ # Security map dictionary
+ objs = (for_, layer, view, manager ,menu)
+ factory = (factory,)
+
+ zcml.adapter(_context, factory, provides=interfaces.ISelectedChecker,
+ for_=objs, permission=None, name='', trusted=False, locate=False)
Property changes on: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.txt
===================================================================
--- z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.txt (rev 0)
+++ z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.txt 2008-01-24 15:59:31 UTC (rev 83170)
@@ -0,0 +1,160 @@
+===================
+Z3C Menu directives
+===================
+
+Show how we can use the menu directive. Register the meta configuration for
+the directive.
+
+ >>> import sys
+ >>> from zope.configuration import xmlconfig
+ >>> import z3c.menu.ready2go
+ >>> context = xmlconfig.file('meta.zcml', z3c.menu.ready2go)
+
+We need to register our checker adapter which can check if a menu item is
+selected or not:
+
+ >>> import zope.component
+ >>> from z3c.menu.ready2go import checker
+ >>> zope.component.provideAdapter(checker.ContextSelectedChecker)
+
+Let's define a content object:
+
+ >>> from z3c.menu.ready2go import testing
+ >>> sampleContent = testing.Sample('Sample Content')
+
+Now add the content object to our site root:
+
+ >>> root['sample'] = sampleContent
+
+Now we can define our test menu manager:
+
+ >>> from zope.viewlet.manager import ViewletManager
+ >>> from z3c.menu.ready2go import manager
+ >>> FirstMenu = ViewletManager('left', testing.IFirstMenu,
+ ... bases=(manager.MenuManager,))
+
+ >>> SecondMenu = ViewletManager('left', testing.ISecondMenu,
+ ... bases=(manager.MenuManager,))
+
+And we need a view which knows about it's parent:
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> firstView = testing.FirstView(sampleContent, request)
+ >>> testing.IFirstView.providedBy(firstView)
+ True
+
+ >>> secondView = testing.SecondView(sampleContent, request)
+ >>> testing.ISecondView.providedBy(secondView)
+ True
+
+As you can see the menu is not selected if we access the page:
+
+ >>> firstMenu = FirstMenu(sampleContent, request, firstView)
+ >>> testing.IFirstMenu.providedBy(firstMenu)
+ True
+
+ >>> firstMenu.update()
+ >>> firstMenu.render()
+ u''
+
+ >>> secondMenu = SecondMenu(sampleContent, request, secondView)
+ >>> testing.ISecondMenu.providedBy(secondMenu)
+ True
+
+ >>> secondMenu.update()
+ >>> secondMenu.render()
+ u''
+
+Now we need some menu items for the first menu:
+
+ >>> from zope.publisher.interfaces.browser import IBrowserView
+ >>> from zope.publisher.interfaces.browser import IBrowserRequest
+ >>> from zope.viewlet.interfaces import IViewlet
+ >>> zope.component.provideAdapter(
+ ... testing.FirstMenuItem,
+ ... (zope.interface.Interface, IBrowserRequest,
+ ... IBrowserView, testing.IFirstMenu),
+ ... IViewlet, name='First Menu')
+
+ >>> zope.component.provideAdapter(
+ ... testing.SecondMenuItem,
+ ... (zope.interface.Interface, IBrowserRequest,
+ ... IBrowserView, testing.IFirstMenu),
+ ... IViewlet, name='Second Menu')
+
+And we need some menu items for the second menu:
+
+ >>> zope.component.provideAdapter(
+ ... testing.FirstMenuItem,
+ ... (zope.interface.Interface, IBrowserRequest,
+ ... IBrowserView, testing.ISecondMenu),
+ ... IViewlet, name='First Menu')
+
+ >>> zope.component.provideAdapter(
+ ... testing.SecondMenuItem,
+ ... (zope.interface.Interface, IBrowserRequest,
+ ... IBrowserView, testing.ISecondMenu),
+ ... IViewlet, name='Second Menu')
+
+Now render the menu manager again and you can see that we've got some menu
+items. but you can see that this menu items are not selected:
+
+ >>> firstMenu = FirstMenu(sampleContent, request, firstView)
+ >>> firstMenu.update()
+ >>> print firstMenu.render()
+ <li>
+ <a><span>Second Menu</span></a>
+ </li>
+ <li>
+ <a><span>First Menu</span></a>
+ </li>
+
+ >>> secondMenu = SecondMenu(sampleContent, request, firstView)
+ >>> secondMenu.update()
+ >>> print secondMenu.render()
+ <li>
+ <a><span>Second Menu</span></a>
+ </li>
+ <li>
+ <a><span>First Menu</span></a>
+ </li>
+
+Now we can register a menu selector for our page whihc renders the menu
+as selected if we access the page:
+
+ >>> context = xmlconfig.string("""
+ ... <configure
+ ... xmlns:z3c="http://namespaces.zope.org/z3c">
+ ... <z3c:menuSelector
+ ... view=".testing.IFirstView"
+ ... manager=".testing.IFirstMenu"
+ ... menu=".testing.FirstMenuItem"
+ ... />
+ ... </configure>
+ ... """, context)
+
+After we registered a menu selector for the first view and first menu, we will
+see that the first menu get rendered as selected on the first menu:
+
+ >>> firstMenu = FirstMenu(sampleContent, request, firstView)
+ >>> firstMenu.update()
+ >>> print firstMenu.render()
+ <li>
+ <a><span>Second Menu</span></a>
+ </li>
+ <li class="selected">
+ <a><span>First Menu</span></a>
+ </li>
+
+But not on the second menu:
+
+ >>> secondMenu = SecondMenu(sampleContent, request, firstView)
+ >>> secondMenu.update()
+ >>> print secondMenu.render()
+ <li>
+ <a><span>Second Menu</span></a>
+ </li>
+ <li>
+ <a><span>First Menu</span></a>
+ </li>
Property changes on: z3c.menu.ready2go/trunk/src/z3c/menu/ready2go/zcml.txt
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Checkins
mailing list