I need to implement a navigation menu on a web page, possibly with several levels of tabs. I've used the CSS way whenever the CSS is under my control, but now I'm facing problems when using an exising CSS library (Twitter's Bootstrap in this case).
First, this is the CSS way I've used. Example HTML:
<html>
<head>
<title>Example page</title>
</head>
<body class="articles">
<div class="navigation">
<ul>
<li class="frontpage"><a href="#">Frontpage</a></li>
<li class="articles"><a href="#">Articles</a></li>
</ul>
</div>
<p>Blah blah.</p>
</body>
</html>
... and the CSS:
body.frontpage div.navigation li.frontpage,
body.articles div.navigation li.articles {
background-color:red;
color:white;
}
Simple enough. Only the body's class needed to be changed when appriopriate.
Now, Bootstrap seems to use the class based style to select navigation items (topbar, tabs, pills). Example:
...
<div class="navigation">
<ul class="tabs">
<li class="active"><a href="#">Frontpage</a></li>
<li><a href="#">Articles</a></li>
</ul>
</div>
...
I initially thought of doing something like
navigation = ('frontpage',)
# or
navigation = ('articles', 'categories', 'foo',)
in the controller and then passing this tuple to the templates, but somehow it has this vibe of "doing it wrong".
So the question is: Since I actually have to print the class selection in the templates, how do I keep track of the current location (as in, where in the navigation tree is this page), given that the page may have multiple levels of tabs or other navigation items? Is there a preferred way to do this?
I'm using Pyramid in route style with Mako templates, but generic answers appreciated too.
I do something similar (Pyramid & Bootstrap as well), but only for one level of navigation.
First thing I did was create a "view" object that passes parameters to every render such as title, header, get_data, etc. Ex.
class View_Controller(object):
def __init__(self, request, **kwargs):
self.request = request
self.nav = kwargs.get('nav', None)
@property
def page(self):
return request.GET.get('page', 1)
@view_config(route_name='home', renderer='templates/homepage.mako')
def Homepage_View(request):
view = View_Controller(request, nav='homepage')
stuff = DBSession.query(Stuff).all()
return {'view': view, 'stuff': stuff}
I set the attribute nav in every page rendering, but have nav = None when no navigation element is activated. I then created a template for navigation elements in Mako:
<%def name="navelements(things)">
% for a in things:
<li${' class="active"' if nav == a[1] else '' | n }><a href="#">a[0]</a></li>
% endfor
</%def>
Where "things" is a tuple of all the elements in the navigation. [0] is the display name, and [1] is the internal name. The "| n " is needed to escape the html characters. Ex:
<%! things = [['Frontpage', 'homepage'], ['Articles', 'arts'], ['Categories', 'cats'], ['Foo', 'foo']] %>
% if things:
<div class="navigation">
<ul class="tabs">
${navelements(things)}
</ul>
</div>
% else:
<hr />
% endif
Now, you can set things either in the template or in the controller via view. I have the "if things" clause in case you want a generic tab template that is called on every page; you can disable tabs just by setting things = None in the controller.
This is only one level of tabs, but you can do the same by making things a more complicated list. I would make things a more complicated so that compared to how it looks currently, it'll look like:
things = [['displayname1', 'elementname1', things1], ['displayname2', 'elementname2', things2], ...]
I would then make navelements recursive (which Mako doesn't support, so I would just make two copies of the navelements function and have them call each other) and implement support for multiple levels of nav:
<%def name="navelements1(things, level=0)">
% for a in things:
<li${' class="active"' if nav[level] == a[1] else '' | n }><a href="#">a[0]</a></li>
% if a[2]:
<ul class="tabs">
${navelements2(a[2], level+=1)}
</ul>
% endif
% endfor
</%def>
Where nav looks something like:
nav = ['frontpage', 'best', None, ...]
Hope that answers your question. Just make sure your lists are of correct length so you don't get out of bound errors.