[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