So far our site has defined pluggable areas for masthead, navigation, footer, sitematter and content. These areas are filled by rendering their respective viewlet managers. The masthead and footer areas already contain content as defined by viewlets in the layout.pt source code.
Navigation and content have yet to be defined, and this brings us to the page template menu.pt.
We begin by defining a Menu(grok.Viewlet), and tell it to render into the Navigation slot for instances of ILayout:
The corresponding template lives in menu.pt:
The result may be seen on the left, the template renders two div's and an unordered list; The first contains the navTitle for the current context. Although.it is conditional on the context actually having a navTitle, we expect this to always be the case. The second div contains three anchors, clearly containing links to edit, add or cancel operations (only shown if you are an authenticated user). The unorderd list element renders the content of a new provider called 'menuitems'.
So let us take a look at the MenuItems viewlet manager:
We can see that it renders for contexts which implement the ILayout interface.
In order to greatly reduce the amount of boilerplate code we would otherwise have, we can define a generic MenuItem which we then may use as a base class for ad-hoc menu items we may wish to later add. A grok.View or grok.Viewlet may use the grok.baseclass() directive to ensure that the class is treated as a base class and does not register itself with the ZCA.
For the first time we have an instance of a view or viewlet which does not have an associated page template. The MenuItem class defines a render() method, which renders the whole of the viewlet <li /> tag content. You cannot have both a render method and a page template for the same view or viewlet (a view may have only one way of rendering).
One or two things should be explained about the above code;
First, menu items are selectively rendered depending on the condition() method. A subclass may override this method, for example use information available in the context to decide whether or not to render.
Second, the grok.view.url(argument) call may take several kinds of argument. Text is interpreted as relative to the current view url, if a grok.Model or grok.Container is the argument, the absolute url to the object is returned.
Third, the selected() method returns True if the menu title matches the navTitle of the context. That is to say, if the current URL has identified a model which has the same navTitle as the currently rendered menu item's title, we give the <li /> tag a selected class. This is not immediately useful, but in conjunction with CSS, we can style the menu item differently depending on the current state of navigation.
mclass is an extra css class for the menu item, and mtitle is a title for the <li /> tag which is displayed when hovering over the item. title is displayed as a menu title, and link is interpreted by the href() method as discussed. If an image is specified, it will be rendered in a 20x20 area to the left of the menu title.
In order to move ahead with our discussion here, we will first need to introduce the IArticle interface. We don't need to know an awful lot about it yet, other than that there is one, and that it represents an article in our wiki. The reason we introduce IArticle here, is that IArticle is a container which contains other IArticle's, and the navigation menu is generated automatically from the content. An IArticle also has a title, a navTitle, and text:
In the above class definition, the TextLine and Text classes are defined in zope.schema, and such schema classes are the basis for server-side validation and automatic form generation in Grok. This will be discussed in more detail later.
We define a ContainerMenu viewlet which renders into the MenuItems slot whenever the context is an IArticle:
The corresponding containermenu.pt page template loops through the containermenu.sorteditems() and renders a <li /> item tag for each one:
Which brings us to IArticleSorter. Grok defines two kinds of containers; the general grok.Container looks like and quacks like a dict. The grok.OrderedContainer sort of looks like and quacks like a dict which always appends to the end, and has an updateOrder() method which can be used to change the order of items maintained. Performance of grok.OrderedContainer is not as good as the grok.Container, due to the need to maintain the sorted order.
Containers in a hierarchical structure may contain other containers, and this forms the basis for site hierarchies in the ZODB, Zope, and Grok, and the ability to locate resources through traversal in that hierarchy.
We decided to use a normal grok.Container and implement our own sorting extension, mostly in order to demonstrate extensibility but also because we wanted a bit more flexibility than a grok.OrderedContainer would have given us out of the box. The IArticleSorter interface is defined as follows:
The line...
sorter = IArticleSorter(self.context)
in the listing for the ContainerMenu viewlet finds an adapter which can turn a self.context, which we know to be an instance of IArticle, into an IArticleSorter instance. This adapter implementation may be found in sortedarticles.py, and this will lead us kicking and screaming to the next part of our narrative discussion.