How do we go about designing a web application that does well with both the navigational structure presented to the user, and the URI format used to traverse the application? They really are distinct problems, are they not? If so, I find that I've artificially blended the two concepts together. Maybe that's because, as a user, I've unintentionally joined the two ideas. After all, they both play a role in the same job. They take the user from one place to another. One from presents a graphical layout to the user, often with styled elements using some kind of grid layout. The other, is just a string destined for the browser address bar. An action in the navigational menu often means a change in the URI. The change is triggered from the user interface, and I would think that this is the expected pattern of behavior, as opposed to the user constantly typing in the desired location — unless the menu is really that bad.
Showing posts with label navigation. Show all posts
Showing posts with label navigation. Show all posts
Monday, December 3, 2012
Wednesday, January 11, 2012
Django Navigation States
I've tried several approaches to implementing Django navigation correctly. Each approach presents it's own unique challenges and limitations. The toughest thing to get right is the state. By state, I mean the menu item that's currently active. Active navigational items need to be conveyed visually - this is easy enough from Django's perspective because it often means inserting the correct CSS class in the template.
Furthering the complexity to designing solid navigation in Django user interfaces is the fact that we can have dynamic variables in the URL path. This makes a simple equality test in determining the state arduous.
So with this in mind, let me demonstrate the best Django-friendly approach to implementing state management with navigation. I say Django-friendly because my approach attempts to be of use for any Django application.
Overview
The approach I'm using is similar to this one. I'm creating a template filter that's applied to the HTTP request object inside a given template. The filter takes a parameter - an identifier for the item we want state applied to. So long as the identifier is valid, we can use this filter to emit the appropriate CSS class.
The Template
Here is the navigation template. Intentionally simple, it shows how the link_state filter is applied.
The Filter
Here is the link_state template filter used in the template to apply navigation item state.
Explanation
The link_state filter works by taking the HTTP request path, the navigational item key, and comparing URLs. If a URL in the set associated with the key matches the request path, the state is active. Otherwise, the state is default.
The keys dictionary maps navigational item keys to their active state URLs. Here, there are three keys - home, products, and services. Notice the relation to these names and arguments passed to link_state in the template. Each key stores a tuple of URL names - when you define a URL in Django, you can give it a name.
Next, we resolve the request path. The reason for doing so is that we need any dynamic path values so when we do comparisons, we can match these URLs too. Now we can iterate through each URL in the specified key, checking for a match.
This approach is flexible because it allows for navigational state management even with dynamic path values, without the need for any specialized code. For example, the product_details URL might look like /products/845/. This URL would set the products link as active.
Furthering the complexity to designing solid navigation in Django user interfaces is the fact that we can have dynamic variables in the URL path. This makes a simple equality test in determining the state arduous.
So with this in mind, let me demonstrate the best Django-friendly approach to implementing state management with navigation. I say Django-friendly because my approach attempts to be of use for any Django application.
Overview
The approach I'm using is similar to this one. I'm creating a template filter that's applied to the HTTP request object inside a given template. The filter takes a parameter - an identifier for the item we want state applied to. So long as the identifier is valid, we can use this filter to emit the appropriate CSS class.
The Template
Here is the navigation template. Intentionally simple, it shows how the link_state filter is applied.
<ul>
<li><a class="{{ request|link_state:'home' }}" >Home</a></li>
<li><a class="{{ request|link_state:'products' }}" >Products</a></li>
<li><a class="{{ request|link_state:'services' }}" >Services</a></li>
</ul>
The Filter
Here is the link_state template filter used in the template to apply navigation item state.
from django import template
from django.core.urlresolvers import reverse, resolve, NoReverseMatch
register = template.Library()
@register.filter
def link_state(request, key):
keys = dict(
home = (
'home',
'promotion_details',
),
products = (
'product_list',
'product_details',
),
services = (
'service_list',
'service_details',
)
)
url = resolve(request.path)
for candidate in keys.get(key, []):
try:
candidate = reverse(
candidate,
args=url.args,
kwargs=url.kwargs
)
except NoReverseMatch:
continue
if candidate == request.path:
return 'active'
return 'default'
Explanation
The link_state filter works by taking the HTTP request path, the navigational item key, and comparing URLs. If a URL in the set associated with the key matches the request path, the state is active. Otherwise, the state is default.
The keys dictionary maps navigational item keys to their active state URLs. Here, there are three keys - home, products, and services. Notice the relation to these names and arguments passed to link_state in the template. Each key stores a tuple of URL names - when you define a URL in Django, you can give it a name.
Next, we resolve the request path. The reason for doing so is that we need any dynamic path values so when we do comparisons, we can match these URLs too. Now we can iterate through each URL in the specified key, checking for a match.
This approach is flexible because it allows for navigational state management even with dynamic path values, without the need for any specialized code. For example, the product_details URL might look like /products/845/. This URL would set the products link as active.
Sunday, September 13, 2009
Navigating Trac Tickets
The Trac project management system allows users of the system to perform queries for tickets. In Trac, tickets are entities that help keep track of outstanding tasks, bug fixes, or basically anything the needs to be accomplished for a given software project. Trac default set of useful ticket queries that users can easily execute. The default set of ticket queries allow interested parties to common groups of tickets such as those belonging to a particular developer.
When viewing a ticket, Trac displays a set of links in the top right of the screen that allow the user to view the previous ticket or the next ticket. There is also a link to return to the the query results. The next ticket and previous ticket links are useful because tickets can be navigated without the need to return to the query. However, the ticket order is based on the ticket number. This presents a problem if the user is only interested in navigating back and forth between tickets in the current query result set. The next ticket can always be completely irrelevant to the tickets the user is interested in.
The really useful aspect of the link back to the query results is how the presentation of the query results can change. Tickets that have been edited while you were viewing a specific ticket, change appearance in the query results once the user returns to the query results. You will also notice that the current user is part of the URL back to the query results.
This feature is incredibly useful in a high paced development environment with many developers. It also helps if a small group of developers are collaborating on a small group of related tickets because changes to tickets by other people become immediately apparent.
When viewing a ticket, Trac displays a set of links in the top right of the screen that allow the user to view the previous ticket or the next ticket. There is also a link to return to the the query results. The next ticket and previous ticket links are useful because tickets can be navigated without the need to return to the query. However, the ticket order is based on the ticket number. This presents a problem if the user is only interested in navigating back and forth between tickets in the current query result set. The next ticket can always be completely irrelevant to the tickets the user is interested in.
The really useful aspect of the link back to the query results is how the presentation of the query results can change. Tickets that have been edited while you were viewing a specific ticket, change appearance in the query results once the user returns to the query results. You will also notice that the current user is part of the URL back to the query results.
This feature is incredibly useful in a high paced development environment with many developers. It also helps if a small group of developers are collaborating on a small group of related tickets because changes to tickets by other people become immediately apparent.
Thursday, February 12, 2009
Trouble extending Trac navigation.
I'm in the process of building a new Trac site. I wanted to add new menu items to the main navigation. Hiding or rearranging the default menu items in Trac 0.11 is quite straightforward. It can all be done in the Trac configuration. However, new menu items need to be part of a component. Hence, the need for the NavAdd plugin.
It looks like the plugin has not yet been updated to fit the 0.11 plugin architecture, although it does work with 0.11. The Trac plugin API changes weren't too drastic. I did notice some strange behaviour though. It turns out that any menu items added with the NavAdd plugin can not be considered active.
What? No active menu items? Well, it isn't really the NavAdd plugins' fault. It turns out, that in order to have an active menu item, the current request needs to be handled by the same component that produces the menu item. I discovered this by looking at the timeline Trac component. This component actually implements the IRequestHandler interface. This is necessary because the timeline component handles requests to the /timeline URI. The menu item produced by this component becomes active anytime the /timeline URI is visited.
So, this design works well for components that have URIs. But what if I want to add menu items that point to wiki pages? And what if I want to have my menu item become active when visiting those pages? There is currently no way to do this. My suggestion would be that the INavigationContributor interface adds a new uri field to be returned from get_navigation_items(). When page corresponding to this uri becomes active, so does the custom menu item.
It looks like the plugin has not yet been updated to fit the 0.11 plugin architecture, although it does work with 0.11. The Trac plugin API changes weren't too drastic. I did notice some strange behaviour though. It turns out that any menu items added with the NavAdd plugin can not be considered active.
What? No active menu items? Well, it isn't really the NavAdd plugins' fault. It turns out, that in order to have an active menu item, the current request needs to be handled by the same component that produces the menu item. I discovered this by looking at the timeline Trac component. This component actually implements the IRequestHandler interface. This is necessary because the timeline component handles requests to the /timeline URI. The menu item produced by this component becomes active anytime the /timeline URI is visited.
So, this design works well for components that have URIs. But what if I want to add menu items that point to wiki pages? And what if I want to have my menu item become active when visiting those pages? There is currently no way to do this. My suggestion would be that the INavigationContributor interface adds a new uri field to be returned from get_navigation_items(). When page corresponding to this uri becomes active, so does the custom menu item.
Labels:
api
,
extensibility
,
menu
,
navigation
,
plugin
,
trac
Subscribe to:
Posts
(
Atom
)