We can generate an index page from the content stored in our wiki.  This provides an alternative navigation, and a summary of the site.

We envisage the index being reachable from the root site menu.  We can add support at a later time for reaching the index regardless of which article is being read.

Source files here are indexpage.py, indexcontent.pt and indexof.pt

The index page should show the site layout, including masthead, navigation and footer, so we start by defining a model IndexPage.

class IndexPage(grok.Model):
    ''' Return an index page for the site
    '''
    grok.implements(ILayout)
    title = u'Site Index'
    navTitle = u'Index'
    text = u''

The Content section for an ILayout is rendered through a viewlet, and we define a viewlet called IndexContent for this purpose:

class IndexContent(grok.Viewlet):
    ''' This populates the 'Content' section with the index
    '''
    grok.context(IndexPage)
    grok.require('zope.Public')
    grok.viewletmanager(Content)

    def namespace(self):
        return dict(site=self.context.__parent__)

The namespace() method makes a template variable called site available within the template, and the definition in indexcontent.pt refers to this variable:

<div class='IndexPage' style='margin:2em'>
   <ul class="sectionItems" tal:content="structure site/@@indexof"></ul>
</div>

This fills the sectionItems container with list items from an indexof view on the site.

The IndexOf view produces a list item for each contained IArticle, and each of those list items may have a <ul> container containing sub items.  These sub items are rendered using a recursive call to IndexOf.

<li class='IndexEntry' style='list-style-type:none'>
    <span tal:content="view/articleNumber" />
    <a tal:content="context/title" tal:attributes="href python:view.url(context)" />
	<ul class="sectionItem" tal:repeat="ctx view/sortedItems">
	    <div tal:replace="structure ctx/@@indexof">
	      section text
	    </div>
	</ul>
</li>

The view/articleNumber method above depends on the fact that to reach a given node in the hierarchy, one would already have had to visit each parent along the way.  So the new article number becomes the parents article number with the current nodes order appended.  That number is then stored temporarily with the node for use by any children.

The sortedItems() method uses the IArticleSorted interface discussed prevously to produce an ordered list of articles.

class IndexOf(grok.View):
    ''' Produces an index entry from the current article
    '''
    grok.context(IArticle)
    grok.require('zope.Public')

    def articleNumber(self):
        order = getattr(self.context, "order", None)
        if order is None:
            self.context.section = ""
        else:
            order = int(order) + 1
            parent = getattr(self.context, "__parent__", None)
            if parent and len(parent.section):
                section = "{}.{}".format(parent.section, order)
            else:
                section = "{}".format(order)
            self.context.section = section
            return section + ": "
        return ""

    def sortedItems(self):
        sorter = IArticleSorter(self.context)
        return sorter.sortedItems()

To produce the index page given the ISiteRoot, we define a view called indexpage.  This creates an IndexPage() instance, and uses view = component.getMultiAdapter((page, request), name='index') to instantiate the ILayout view.  We then simply call the view to produce the site index, and return the HTML.

class PageView(grok.View):
    grok.context(ISiteRoot)
    grok.name('indexpage')
    grok.require('zope.Public')

    def render(self):
        page = IndexPage()
        page = location.located(page, self.context, 'indexpage')
        view = component.getMultiAdapter((page, self.request), name='index')
        return view()

The location.located() method above sets the __parent__ and __name__ attributes for the page, which is necessary for view.url() to produce a valid url.

 To add the index to the menu for all pages other than just the first, we have only a few changes.  The IndexButton definition changes to use a context of IArticle rather than ISiteRoot, and we alter PageView as follows:

class PageView(grok.View):
    grok.context(IArticle)
...
    def render(self):
        ctx = self.context
        while not ISiteRoot.providedBy(ctx):
            ctx = ctx.__parent__
        page = IndexPage()
        page = location.located(page, ctx, 'indexpage')
        view = component.getMultiAdapter((page, self.request), name='index')
        return view()

class IndexButton(UtilItem):
    '''  A menu item to navigate to the index
    '''
    grok.context(IArticle)

Grok 4 Noobs

Adding an Index Page