ColumNav
Overview
ColumNav is a hierarchical menu implementation utilizing Bill Scott's Yahoo UI Carousel component. Content is loaded from an unordered list and displayed in a scrollable viewport, similar to Column View in the Mac OS X Finder. Features include:
- infinite extensibility using Ajax to build sub-menus
- instantiation from DOM or JSON data sources
- a highly customizable look and feel using CSS
- support for multiple columns
- support for keyboard-only navigation (CTRL + arrow keys)
- cross-browser compatibility
Examples
The following examples demonstrate the various ways content can be loaded when initializing a ColumNav object:
- from a DOM object using
document.getElementById - from a DOM object using
document.createElement - from a DOM object via Ajax
- from a JSON object literal
- from a JSON object via Ajax
Additional examples, demonstrating some customizations:
- Infinite depth: unlimited sub-menus using Ajax
- Custom icons: using CSS to fine tune images
- Multiple columns
- Arbitrary HTML: loading non-menu content
- onNext handler: preventing default link behavior using a custom event
- Event handlers: the gamut of custom events that occur
- "Loading" graphic: (running this example requires a PHP-enabled web server)
- Breadcrumbs: providing a simple navigation history for the user
Download
- columnav-1.0.2.zip (4 Aug 2007): includes source code, examples, and this documentation
- the latest version can always be found here
Usage
Setting up ColumNav is a straightforward process of providing the proper HTML, JavaScript, and CSS configuration, along with creating the data source. Understanding how a data source is formed and processed is key to using ColumNav, so it is presented first below.
Data sources
You have two types of objects to choose from when building a data source: DOM or JSON. In either case, the object represents a normal HTML unordered list. Each list item contains a link and possibly a nested sub-list (or some arbitrary HTML). For example, a DOM representation of basketball teams might look like this:
<ul id="basketball-list" title="Basketball Teams">
<li>
<a href="http://sports.yahoo.com/nba/">NBA</a>
<ul>
<li>
<a href="">Atlantic</a>
<ul>
<li><a href="http://sports.yahoo.com/nba/teams/bos">Boston Celtics</a></li>
<li><a href="http://sports.yahoo.com/nba/teams/njn">New Jersey Nets</a></li>
<li><a href="http://sports.yahoo.com/nba/teams/nyk">New York Knicks</a></li>
<li><a href="http://sports.yahoo.com/nba/teams/phi">Philadelphia 76ers</a></li>
<li><a href="http://sports.yahoo.com/nba/teams/tor">Toronto Raptors</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="examples/ajax/ncaa/index.xml" rel="ajax">NCAA</a></li>
</ul>
The equivalent JSON object would look like this:
{ "ul": {
"@id": "basketball-list",
"@title": "Basketball Teams",
"li": [
{ "a": { "@href": "http://sports.yahoo.com/nba/", "#text": "NBA" },
"ul": {
"li": [
{ "a": { "@href": "", "#text": "Atlantic" },
"ul": {
"li": [
{ "a": { "@href": "http://sports.yahoo.com/nba/teams/bos", "#text": "Boston Celtics" } },
{ "a": { "@href": "http://sports.yahoo.com/nba/teams/njn", "#text": "New Jersey Nets" } },
{ "a": { "@href": "http://sports.yahoo.com/nba/teams/nyk", "#text": "New York Knicks" } },
{ "a": { "@href": "http://sports.yahoo.com/nba/teams/phi", "#text": "Philadelphia 76ers" } },
{ "a": { "@href": "http://sports.yahoo.com/nba/teams/tor", "#text": "Toronto Raptors" } }
]
} },
]
} },
{ "a": { "@href": "ajax/json/ncaa/index.json", "@rel": "ajax", "#text": "NCAA" } }
]
} }
When imported into the ColumNav component, the data source is divided into "panes" based on each item's position in the list. Top-level items are viewable at first ("NBA" and "NCAA" above), while nested items occupy subsequent panes. Bottom-level items act as normal links unless their rel attribute contains the flag ajax. In this case, the URL specified in the href attribute is used to retrieve the next pane in the series via Ajax.
ColumNav instances can be initialized using a pre-built object (DOM or JSON) or from an object acquired via Ajax. See the datasource configuration property below.
IMPORTANT: Data sources loaded though Ajax should be served with a proper mime type. XML sources should have an appropriate mime type like text/xml or application/xml, while JSON sources must be sent as application/json.
All link attributes in the data source are propagated to the ColumNav. You can, for example, specify a class attribute for a particular link, and use that value for fine-grained CSS control. See the CSS section below. Additionally, each list node (<ul>) can specify a title attribute.
HTML
The HTML structure of each ColumNav should conform to that expected by the Carousel component script:
<div id="mycolumnav" class="carousel-component">
<div class="carousel-clip-region">
<ul class="carousel-list"></ul>
</div>
</div>
The specific class names are required by the Carousel. See the Carousel documentation for more information. The id value is arbitrary, and will be passed as the first argument to the ColumNav constructor (see below).
Additionally, you can provide a "back" button for retracing your steps:
<div class="prevButton">
<a href="javascript:void(0)" id="mycolumnav-prev">< back</a>
</div>
The HTML can be anything, provided the clickable element has an id attribute. The id will be passed to the ColumNav constructor to enable the button. A ColumNav instance can have multiple back buttons. See the configuration properties below.
JavaScript
ColumNav relies on Carousel, which in turn relies on the Yahoo UI Library (version 0.12 or above). Include the required files in the <head> of your document. Note that you can include YUI files hosted on Yahoo servers. If you are going to be processing JSON objects, include the JSON parsing script as well.
<script type="text/javascript" src="http://yui.yahooapis.com/2.2.2/build/utilities/utilities.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.2.2/build/container/container_core-min.js"></script>
<script type="text/javascript" src="carousel.js"></script>
<script type="text/javascript" src="columnav.js"></script>
<!-- if JSON data sources: -->
<script type="text/javascript" src="json.js"></script>
Note that the distribution contains minimized versions of both Carousel and ColumNav JavaScript files.
Since a ColumNav instance depends on the existence of a specific HTML element, it is best to instantiate it after page load. For instance:
<script type="text/javascript">
function init() {
// ... do other stuff first, perhaps ...
var cn_cfg = { prevElement: 'mycolumnav-prev'', datasource: some_url_or_object };
var cn = new YAHOO.extension.ColumNav('mycolumnav', cn_cfg);
}
YAHOO.util.Event.addListener(window, 'load', init);
</script>
The ColumNav constructor accepts two arguments: the id of the outermost <div> (the one with class="carousel-component"), and an object containing configuration properties. These properties are name/value pairs governing the ColumNav look and behavior:
datasource: (required) a string representing the URL to call via Ajax in order to load the initial items, OR a reference to an object (DOM or JSON) containing the necessary list structure.
prevElement: (optional) The
idor DOM object reference of the HTML element that will provide the previous navigation control. Can be an array ofids or DOM objects.linkAction: (optional) a reference to a function to be called whenever a leaf node link is clicked. This function should return
trueorfalsedepending on whether or not the linkhrefshould be followed in normal fashion after the function has completed. It should accept a single argument: the event that triggered the link. DEPRECATED usenextHandlerinstead (see below).numVisible: (optional, default: 1) an integer representing the number of visible columns.
animationSpeed: (optional, default: 0.25) a number representing the time (in seconds) it takes to complete the scroll animation. If set to 0, animated transitions are turned off and the new pane is moved immediately into place.
animationMethod: (optional, default: YAHOO.util.Easing.easeOut) a YAHOO.util.Easing method for scrolling the viewport.
requestTimeout: (optional, default: 5000) an integer representing the number of milliseconds to elapse before an Ajax request fails.
Event handlers
prevButtonStateHandler: (optional) a function to handle the
onPrevButtonStateChangeevent. This event occurs whenever the previous button state changes due to the advance of the ColumNav. The responsibility of this method is to enable or disable the "previous" control. When called, the handler function receives three arguments: the custom event type (always the string "onPrevButtonStateChange"), a two-item array containing a boolean indicating whether the "previous" control is enabled or disabled and a reference to the "previous" control, and a reference to the ColumNav instance.nextHandler: (optional) a function to handle the
onNextevent. This event occurs when a menu link is clicked, but before the request is made. When called, the handler function receives three arguments: the custom event type (always the string "onNext"), an two-item array containing the target on which the event was fired and the native MouseEvent object itself, and a reference to the ColumNav instance. Unlike the other event handlers, the return value ofnextHandleris significant: returningfalsewill prevent the link's action from occurring.prevHandler: (optional) a function to handle the
onPrevevent. This event occurs when the previous pane is requested using theprevElementor the keyboard (CTRL + left arrow). When called, the handler function receives three arguments: the custom event type (always the string "onPrev"), a two-item array containing the target on which the event was fired and the native MouseEvent object itself, and a reference to the ColumNav instance.requestHandler: (optional) a function to handle the
onRequestevent. This event occurs just before an Ajax request is made to retrieve the next pane. When called, the handler function receives three arguments: the custom event type (always the string "onRequest"), a two-item array containing the URL that was requested and the target on which the event was fired, and a reference to the ColumNav instance.responseHandler: (optional) a function to handle the
onResponseevent. This event occurs just after a response is received from an Ajax request, but before the new pane is added to the ColumNav. When called, the handler function receives three arguments: the custom event type (always the string "onResponse"), a single-item array containing the string "success" or "failure", and a reference to the ColumNav instance.paneHandler: (optional) a function to handle the
onPaneevent. This event occurs just after a new pane is added to the ColumNav, but before it is scrolled into view. When called, the handler function receives three arguments: the custom event type (always the string "onPane"), a single-item array containing the pane's position in the series (an integer), and a reference to the ColumNav instance.animationCompleteHandler: (optional) a function to handle the
onAnimationCompleteevent. This event occurs just after the scrolling animation stops for both the next and previous panes. When called, the handler function receives three arguments: the custom event type (always the string "onAnimationComplete"), a single-item array containing the string "next" or "previous", and a reference to the ColumNav instance.
A ColumNav instance exports two public methods: reset() which can be called at any time to revert the component back to its original state, and toString() which returns a string representation of the ColumNav instance.
CSS
Two stylesheets are required:
<link href="carousel.css" rel="stylesheet" type="text/css"/>
<link href="columnav.css" rel="stylesheet" type="text/css"/>
The carousel.css file governs the layout and behavior of the underlying Carousel component, and should not be edited. The columnav.css file can be edited to customize the look and feel of the ColumNav component. Apart from altering fonts, colors, margins, etc., rules can be added to display different icons for each list item type. When the script runs, it introduces up to six additional CSS class names which you can use to refine the display:
columnav-has-next: (applies to
<a>) designates an item that, when clicked, will produce a new panecolumnav-active: (applies to
<a>) designates an item that has been clickedcolumnav-error: (applies to
<span>) designates an error message (occurs when adding a new pane fails)columnav-multiple: (applies to topmost
<ul>) designates a ColumNav that has more than one pane visiblecolumnav-menu: (applies to
<li>) designates a pane that contains a ColumNav menucolumnav-nonmenu: (applies to
<li>) designates a pane that contains arbitrary HTML, i.e. not a ColumNav menu
In addition to these class names, others can be added to an item by including them in the data source, providing fine-grained CSS control. For example:
<ul title="Basketball Teams">
<li><a href="examples/ajax/ncaa/index.xml" class="ncaa" rel="ajax">NCAA</a><li>
</ul>
When loaded into the ColumNav, the link above will contain two class names: "ncaa" (propagated from the data source), and "columnav-has-next" (due to the presence of rel="ajax"). This allows you to display a different icon based on the given class name. For example:
.carousel-component ul.carousel-list li a.ncaa span {
background-image: url(ncaa-logo.png);
}
Note the background image belongs to the nested span.
It is important to remember that the data source markup is not copied verbatim into the ColumNav (except for non-menu content). Instead, it is transformed into a format more suitable for containment within the Carousel component. For instance, when the markup above is translated into the Carousel (e.g. #mycolumnav ul.carousel-list) it looks like this:
<li id="columnav-item-1" class="columnav-menu">
<div title="Basketball Teams">
<a href="examples/ajax/ncaa/index.xml" class="ncaa columnav-has-next" rel="ajax">
<span>NCAA</span>
</a>
<div>
</li>
Keep this in mind when styling the ColumNav.
Tips
When the ColumNav has focus, you can use the keyboard to navigate the menus. Hold down the CTRL key while pressing the arrow keys (the CTRL key prevents the browser from performing the default action for the arrow keys: scrolling the main browser window).
When viewing the Ajax examples above or developing your own, be sure to view the pages from a web server instead of locally, or else the connections will fail. Also, be sure that your Ajax responses are sent with the proper mime type:
text/xmlorapplication/xmlfor XML data, andapplication/jsonfor JSON data.If having an empty
<ul>element in your ColumNav HTML offends your standard-compliant sensibilities, you can insert an empty<li>if you'd like. But make sure you remove it before instantiating the ColumNav, or it might not render properly.In Internet Explorer, you might notice the images flickering slightly when you mouse over them. To remedy this, add the following code at the top of your script:
try { document.execCommand('BackgroundImageCache', false, true); } catch(e) {}See http://evil.che.lu/2006/9/25/no-more-ie6-background-flicker for more information.
You can load arbitrary HTML into a pane by ensuring the root node of the content is not a
<ul>OR by loading in conetnt with a non-XML mime type (text/htmlfor example). When this occurs, the carousel pane receives the class name "columnav-nonmenu". See the example above.
Credits
My name is David Lindquist, and I wrote the ColumNav JavaScript, CSS, examples, and this documentation. Many thanks to Bill Scott for his wonderful Carousel component upon which ColumNav is based, and to the Yahoo UI developers for my favorite JavaScript framework. Special thanks go to Nate Steiner for suggesting the ColumNav name, and for convincing me to allow for non-Ajax content loading.
Questions? Comments? Send me email at david.lindquist@gmail.com.