Nav items in bootstrap follow a specific format;  All items in the bar are instances of <li class='dropdown' />. It could be one of the other bootstrap classes than dropdown, but here we use the dropdown class.  Inside the list item, we find an anchor.  That anchor will contain a caret if there is a dropdown menu list, otherwise not.

<!-- A dropdown with no list -->
<li class='dropdown'>
  <a href='#'>My Menu item</a>
</li>

<!-- A dropdown with a list -->
<li class='dropdown'>
  <a href='#'>My Menu item<span class='caret'></span></a>
  <ul class='dropdown-menu'>
     <li><a href="#">Item 1</a></li>
     <li><a href="#">Item 2</a></li>
     <li><a href="#">Item 3</a></li>
  </ul>
</li>

If there is a dropdown menu list, then the block element is a <ul class='dropdown-menu' />. Of course, our menu bar can consist of multiple instances of the above menu entry.

Defining a menu item element.

Starting with the above code, we can model a typical page template to produce the needed HTML.  Remembering that the requirements for a single menu item may be quite onerous and will differ dependent on whether the item has a sublist or not.

<li class="dropdown">
    <a tal:attributes="href view/link"
       tal:condition="not:context/items"
       tal:content="context/title" />
    <a class="dropdown-toggle" data-toggle="dropdown"
         tal:attributes="id context/id; href string:#"
         tal:condition="context/items">
        <span tal:replace="context/title" />
        <span class="caret" />
    </a>
    <ul class="dropdown-menu"
      tal:condition="context/items"
      tal:attributes="aria-labelledby context/id">
      <li>
        <a tal:attributes="href view/link">Section</a>
      </li>
      <li class="divider" />
      <tal:loop tal:repeat="item context/items">
            <li tal:attributes="class item/itemClass | nothing">
                <a tal:attributes="href python:view.link(item['link'])"
                   tal:content="item/title">
                </a>
            </li>
      </tal:loop>
    </ul>
</li>

We can see how the page template refers to context/title, context/items and context/id.  Ignoring for the moment the references to view/link, we know that at the least, the context for this view must contain an id, a title and an items attribute.  We can formalise those needs by defining an interface for the nav item context:

class INavItem(Interface):
    '''  A navigation item
    '''
    id    = Attribute(u'id')     # The label for the menu item
    title = Attribute(u'title')  # The label for the menu item
    items = Attribute(u'items')  # May have sub items in the dropdown

Now, based on the above interface, we can define a view for the navigation item corresponding to the above page template.  So, when provided with a context which implements INavItem, the following view will render:

class NavBar_Item(grok.View):
    ''' Represents a single item on the navigation bar
    '''
    grok.context(INavItem)
    grok.layer(Bootstrap)

    def link(self, sub=False):  # Return a url for the article
        if sub: return self.url(self.context.context, name=sub)
        return self.url(self.context.context)

All that remains, is to produce a bunch of INavItem items, and populate the navigation bar with them.

We can produce all of the fields for an INavItem from an instance of an article.  To do this, we need an adapter which implements an INavItem given a IArticle.  The following implements this adapter:

class ArticleNavItem(grok.Adapter):
    ''' Adapts an IArticle to provide an INavItem
    '''
    grok.context(IArticle)
    grok.implements(INavItem)

    def __init__(self, context):
        ''' Gets id, title and items from the IArticle
        '''
        super(ArticleNavItem, self).__init__(context)
        self.id = quote_plus(context.navTitle)
        self.title = context.navTitle
        self.items = []
        sorter = IArticleSorter(self.context)
        for a in sorter.sortedItems():
            self.items.append({'link':  quote_plus(a.navTitle),
                               'title': a.navTitle})

The Navigation Bar

Now, the first article we will see when we navigate to the site, will be the ISiteRoot.  This article has several child elements, and we can visit each of these to produce INavItem instances by using our new shiny adapter.  We can then render the NavBar_Item view for each of these to produce our navigation bar item.  We do this in another view, called our NavBar:

class NavBar(grok.View):
    '''  This view renders as the fixed navigation bar
    '''
    grok.context(IArticle)
    grok.layer(Bootstrap)

    def navItems(self):
        ''' Returns navigation bar items for current IArticle
        '''
        try:
            sorter = IArticleSorter(self.context.__parent__)
            for a in sorter.sortedItems():
                item = NavBar_Item(INavItem(a), self.request)
                yield item()
        except:
            item = NavBar_Item(INavItem(self.context), self.request)
            yield item()

You can see how the navItems() method visits each article in the IArticle container which is the current context.  For each of these articles, an INavItem instance is created, which is then used as a context for a NavBar_Item() view.  Each such view is then rendered in turn, returning the HTML for each navigation item.  These items are all added to the navbar in the NavBar page template (navbar.pt):

<div class="navbar-header" tal:content="structure context/@@navbar_header" />
<div class="navbar-collapse collapse" id="navbar-main">
    <ul class="nav navbar-nav">
        <tal:loop tal:repeat="item view/navItems">
            <li tal:replace="structure item" />
        </tal:loop>
    </ul>
</div>

The first div rendered by this view is the NavBar header, and refers to the NavBar_Header view template (navbar_header.pt):

<a tal:attributes="href view/hRef | string:#" class="navbar-brand">Grok 4 Noobs</a>
  <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbar-main">
    <span class="icon-bar" />
    <span class="icon-bar" />
    <span class="icon-bar" />
  </button>

This header is used by the bootstrap Javascript to roll the menu up into a hamburger bar if the screen gets too small.  This is the 'responsive' part of the site.  The header anchor refers to a view/hRef which is calculated to be the site root by the correponding Python view class:

class NavBar_Header(grok.View):
    '''  This view renders as the site header item
    '''
    grok.context(IArticle)
    grok.layer(Bootstrap)

    def hRef(self):  # Returns site root URL
        return self.url(grok.getApplication())

...and that wraps it up for the Navigation Bar.

Breadcrumbs

Where a navigation bar provides for navigating through a site in a forwards direction, a simple but effective way to navigate back to where you came from is by using breadcrumbs.  Bootstrap uses a specific HTML template to do breadcrumbs, which are extraordinarily easy to produce.

<ul class="breadcrumb">
  <li tal:repeat="crumb view/crumbs">
      <a tal:condition="not:crumb/isLast"
         tal:attributes="href crumb/hRef"
         tal:content="crumb/title">Crumb</a>
      <div tal:omit-tag="" tal:condition="crumb/isLast">
          <span class="active" tal:replace="crumb/title">Crumb</span>
          <span tal:condition="crumb/isLast" class="divider">/</span>
      </div>
  </li>
</ul>

Thie template iterates view/crumbs to retrieve the links in the URL.  Each crumb other than the last is rendered as an anchor link, and the href is expected to be found in the crumb/hRef field.

 The crumb also has a cumb/title and a crumb/isLast flag. These are all set in the update() method for the view class:

class BreadCrumbs(grok.View):
    '''  Renders the breadcrumbs
    '''
    grok.context(IArticle)
    grok.layer(Bootstrap)

    def crumbs(self):
        ''' Walk the tree up from current context, build crumbs.
        '''
        app = grok.getApplication()
        cl  = []
        curr = self.context

        while curr != app:
            cl.append(curr)
            curr = curr.__parent__
        cl.append(curr)
        cl.reverse()
        return ({'title': a.navTitle,
                 'hRef':self.url(a),
                 'isLast':a==self.context} for a in cl)

 Breadcrumbs are a great addition for navigation to any site, an have become increasingly popular especially for mobile aware sites.

 

Grok 4 Noobs

Providing a nav bar and breadcrumbs