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.