Posts for the month of May 2010

A simple tabbed navigation with Nagare

Tabs are a widely used mean to navigate between categories or sections of a website. There are lots of howtos and tutorials to create clean tab based navigation with HTML and CSS.

Let's see how to handle such a navigation with Nagare. We are going to create a tabbed navigation between a TicTacToe and a Counter.

Requirements

We use TicTacToe and Counter examples from our Demonstration Portal. Hence nagare examples have to be installed with following command:

easy_install nagare.examples

To run our small example we use serve-module:

nagare-admin serve-module ./menu_example.py:TabNav menu_example

Create Menu component

The Menu has to meet the following needs:

  1. store the selected entry
  2. display the list of clickable entries
  3. notify when an entry is clicked

A fairly simple implementation in Nagare could be:

from nagare import presentation, var
class Menu:
    def __init__(self, menu):
        self.menu = menu
        self.selected = var.Var(0)
    def display(self, i, comp):
        comp.answer(i)

@presentation.render_for(Menu)
def render(self, h, comp, *args):
    with h.ul(class_='menu'):
        for index, entry in enumerate(self.menu):
            h << h.li(h.a(entry).action(lambda index=index: self.display(index, comp)),
                      class_='selected-entry' if self.selected() == index else '')
    return h.root

Notification of the clicked entry is done through the use of answer() method of the Menu component.

Put it together in TabNav component

TabNav is composed of two parts, menu and content. TabNav is expected to work the following way:

  1. When an entry is clicked in menu, select_tab is called
  2. select_tab change the displayed content and then sets the selected entry in menu
from nagare.examples import counter, tictactoe
from nagare import presentation, component
class TabNav:
    tab_factories = [('Counter', counter.Counter1),
                     ('TicTacToe', tictactoe.Task)]
    def __init__(self):
        self.menu = component.Component(Menu([label for label, factory in self.tab_factories]))
        self.menu.on_answer(self.select_tab)

        self.content = component.Component(None)
        self.select_tab(0)
    def select_tab(self, index):
        self.content.becomes(self.tab_factories[index][1]())
        self.menu().selected(index)
@presentation.render_for(TabNav)
def render(self, h, *args):
    h << self.menu
    with h.div(class_='content'):
        h << self.content
    return h.root

on_answer() method of the Menu component let us bind answer() method of the Menu component with select_tab() method of TabNav component. This way select_tab is called with the index of the clicked entry in the menu as parameter.

Some styling

Finally a bit of CSS can be added to get the tabbed rendering effect.

@presentation.render_for(TabNav)
def render(self, h, *args):
    css = """
body {
    font-family: verdana,'Bitstream Vera Sans',helvetica,arial,sans-serif;
}
ul.menu {
    height: 2em;
    list-style: none;
    margin: 0;
    padding: 0;
}
ul.menu li {
    float: left;
    margin: 0 1px 0 0;
    padding-left: 10px;
    border: 1px solid #555555;
    border-bottom: none;
    color: #555555;
}
ul.menu a {
    color: #555555;
    display: block;
    float: left;
    height: 2em;
    line-height: 2em;
    padding-right: 10px;
    text-decoration: none;
}
ul.menu li.selected-entry {
    background-color: #555555;
}
ul.menu li.selected-entry a {
    color: #FFFFFF;
    font-weight: bold;
}
div.content {
    border: #555555 solid 2px;
    clear: left;
    padding: 1em;
}
"""
    h << h.style(css, type="text/css")
    h << self.menu
    with h.div(class_='content'):
        h << self.content
    return h.root

This renders as following:

/trac/raw-attachment/blog/tabbed-navigation-with-nagare/tabbed_navigation.2.png
Rq:
As we use serve-module, CSS has to be embedded into HTML but it is usually put in the static folder of the application skeleton.