Tree Navigation Menu
Tree Example 1: Sub-list is Sibling of Sub-list Control
Validate DOM (HTML5)
Foods
- Fruits
-
- Oranges
- Pineapples
- Apples
-
- Macintosh
- Granny Smith
-
- Washington State
- Michigan
- New York
- Fuji
- Bananas
- Pears
- Vegetables
-
- Brocolli
- Carrots
- Lettuce
-
- Romaine
- Iceberg
- Butterhead
- Spinach
- Squash
-
- Acorn
- Ambercup
- Autumn Cup
- Hubbard
- Kabocha
- Butternut
- Spaghetti
- Sweet Dumpling
- Turban
Keyboard Shortcuts
- Up: Select previous tree item
- Down: Select next tree item
- Left: Collapse node if possible
- Right: Expand node if possible
- Tab: Navigate away from tree
ARIA Roles and Properties
-
Roles:
role="application"
role="tree"
role="treeitem"
role="group"
- States and properties:
aria-describedby
aria-labeledby
aria-expanded
HTML Source Code
Show HTML Source Code: tree1.inc
<div role="application">
<script type="text/javascript">
var tree1 = new Tree("tree1");
widgets.add( tree1 );
</script>
<h2 id="label_1">Foods</h2>
<ul id="tree1"
role="tree"
tabindex="0"
aria-labelledby="label_1"
class="tree"
>
<li role="treeitem" tabindex="-1" aria-expanded="true">Fruits</li>
<li role="group">
<ul>
<li role="treeitem" tabindex="-1">Oranges</li>
<li role="treeitem" tabindex="-1">Pineapples</li>
<li role="treeitem" tabindex="-1" aria-expanded="false">Apples</li>
<li role="group">
<ul>
<li role="treeitem" tabindex="-1">Macintosh</li>
<li role="treeitem" tabindex="-1" aria-expanded="false">Granny Smith</li>
<li role="group">
<ul>
<li role="treeitem" tabindex="-1">Washington State</li>
<li role="treeitem" tabindex="-1">Michigan</li>
<li role="treeitem" tabindex="-1">New York</li>
</ul>
</li>
<li role="treeitem" tabindex="-1">Fuji</li>
</ul>
</li>
<li role="treeitem" tabindex="-1">Bananas</li>
<li role="treeitem" tabindex="-1">Pears</li>
</ul>
</li>
<li role="treeitem" tabindex="-1" aria-expanded="true">Vegetables</li>
<li role="group">
<ul>
<li role="treeitem" tabindex="-1">Brocolli</li>
<li role="treeitem" tabindex="-1">Carrots</li>
<li role="treeitem" tabindex="-1" aria-expanded="false">Lettuce</li>
<li role="group">
<ul>
<li role="treeitem" tabindex="-1">Romaine</li>
<li role="treeitem" tabindex="-1">Iceberg</li>
<li role="treeitem" tabindex="-1">Butterhead</li>
</ul>
</li>
<li role="treeitem" tabindex="-1">Spinach</li>
<li role="treeitem" tabindex="-1" aria-expanded="true">Squash</li>
<li role="group" >
<ul>
<li role="treeitem" tabindex="-1">Acorn</li>
<li role="treeitem" tabindex="-1">Ambercup</li>
<li role="treeitem" tabindex="-1">Autumn Cup</li>
<li role="treeitem" tabindex="-1">Hubbard</li>
<li role="treeitem" tabindex="-1">Kabocha</li>
<li role="treeitem" tabindex="-1">Butternut</li>
<li role="treeitem" tabindex="-1">Spaghetti</li>
<li role="treeitem" tabindex="-1">Sweet Dumpling</li>
<li role="treeitem" tabindex="-1">Turban</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
Javascript Source Code
Show Javascript Source Code: globals.js
<script type="text/javascript">
/**
*
* The Globale Variables
*/
if (!window.Node) {
var Node = { // If there is no Node object, define one
ELEMENT_NODE: 1, // with the following properties and values.
ATTRIBUTE_NODE: 2, // Note that these are HTML node types only.
TEXT_NODE: 3, // For XML-specific nodes, you need to add
COMMENT_NODE: 8, // other constants here.
DOCUMENT_NODE: 9,
DOCUMENT_FRAGMENT_NODE: 11
}
}
var KEY_PAGEUP = 33;
var KEY_PAGEDOWN = 34;
var KEY_END = 35;
var KEY_HOME = 36;
var KEY_LEFT = 37;
var KEY_UP = 38;
var KEY_RIGHT = 39;
var KEY_DOWN = 40;
var KEY_SPACE = 32;
var KEY_TAB = 9;
var KEY_BACKSPACE = 8;
var KEY_DELETE = 46;
var KEY_ENTER = 13;
var KEY_INSERT = 45;
var KEY_ESCAPE = 27;
var KEY_F1 = 112;
var KEY_F2 = 113;
var KEY_F3 = 114;
var KEY_F4 = 115;
var KEY_F5 = 116;
var KEY_F6 = 117;
var KEY_F7 = 118;
var KEY_F8 = 119;
var KEY_F9 = 120;
var KEY_F10 = 121;
var KEY_M = 77;
var NS_XHTML = "http://www.w3.org/1999/xhtml"
var NS_STATE = "http://www.w3.org/2005/07/aaa";
// **********************************************
// *
// * Commonly used helper functions
// *
// **********************************************
/**
*
* nextSiblingElement
*
* @contructor
*/
function nextSiblingElement( node ) {
var next_node = node.nextSibling;
while( next_node
&& (next_node.nodeType != Node.ELEMENT_NODE) ) {
next_node = next_node.nextSibling;
} // endwhile
return next_node;
}
/**
*
* previousSiblingElement
*
* @param ( node ) node object for which you are looking for the next sibling element node
*
* @return ( node) next sibling or "null"
*/
function previousSiblingElement( node ) {
var next_node = node.previousSibling;
while( next_node
&& (next_node.nodeType != Node.ELEMENT_NODE) ) {
next_node = next_node.previousSibling;
} // endwhile
return next_node;
}
/**
*
* firstChildElement
*
* @param ( node ) node object for which you are looking for the first child element node
*
* @return ( node) next sibling or "null"
*/
function firstChildElement( node ) {
var next_node = node.firstChild;
while( next_node
&& (next_node.nodeType != Node.ELEMENT_NODE) ) {
next_node = next_node.nextSibling;
} // endwhile
return next_node;
}
/**
*
* getTextContentOfNode
*
* @contructor
*/
function getTextContentOfNode( node ) {
var next_node = node.firstChild;
var str = "";
while( next_node ) {
if( (next_node.nodeType == Node.TEXT_NODE ) &&
(next_node.length > 0 )
)
str += next_node.data;
next_node = next_node.nextSibling;
} // endwhile
return str;
}
/**
*
* setTextContentOfNode
*
* @contructor
*/
function setTextContentOfNode( node, text ) {
// Generate a new text node with the text value
var text_node = document.createTextNode(text);
// Remove child nodes to remove text
while (node.firstChild) {
node.removeChild(node.firstChild);
} // while
// Append new text to the container element
node.appendChild( text_node );
}
</script>
Show Javascript Source Code: widgets_inline.js
<script type="text/javascript">
// JavaScript Document
if (!window.Node) {
var Node = { // If there is no Node object, define one
ELEMENT_NODE: 1, // with the following properties and values.
ATTRIBUTE_NODE: 2, // Note that these are HTML node types only.
TEXT_NODE: 3, // For XML-specific nodes, you need to add
COMMENT_NODE: 8, // other constants here.
DOCUMENT_NODE: 9,
DOCUMENT_FRAGMENT_NODE: 11
}
}
var ARIA_STATE = "aria-";
/**
* Widgets Object is used to initialize a set of controls
* and provide a conveinence fuction to cancel event propagration
* @construtor
*/
function Widgets() {
this.widgets = new Array();
}
/**
* add is member of the Widgets Object
* and used add a widget ot the list of widgets to be intitialized
* as part of the onload event
* The controls array is the list of controls to initialize
* @member Enable
* @return none
*/
Widgets.prototype.add = function(obj) {
this.widgets[this.widgets.length] = obj;
}
/**
* init is member of the Widgets Object
* and is called by the onload event to initialize widgets in the web resource
* The controls array is the list of controls to initialize
* @member Enable
* @return none
*/
Widgets.prototype.init = function() {
for(var i = 0; i < this.widgets.length; i++ )
this.widgets[i].init();
}
//
// convience function for getting the node based on id
function _$( id ) {
return document.getElementById( id );
}
//
// WebBrowser object to abstract accessibility API differences between web standards supporting browsers and Internet Explorer 7.0
//
// The state variable keeps track of current state of checkbox
function WebBrowser() {
}
//
// keyCode is a function to get the keycode from a keypress event
//
// @param ( event object) event is an event object
//
// @return ( keycode )
WebBrowser.prototype.keyCode = function( event ) {
var e = event || window.event;
return e.keyCode;
}
/**
* OnClick Event Simulator
*
* @param ( node ) DOM node object
* @return nothing
*/
if( document.createEvent ) {
// If a web standards based browser implement this function
WebBrowser.prototype.simulateOnClickEvent = function( node ) {
// W3C DOM Events way to trigger a "click" event
var e = document.createEvent('MouseEvents');
e.initEvent( 'click', true, true );
node.dispatchEvent( e );
}
} else {
// If a Microsoft IE based browser implement this function
WebBrowser.prototype.simulateOnClickEvent = function( node ) {
var e = document.createEventObject();
node.fireEvent( "onclick", e );
} // endif
}
if ( document.addEventListener ) {
// If a web standards based browser implement this function
WebBrowser.prototype.setMouseCapture = function( node, clickHandler, downHandler, moveHandler, upHandler ) {
if( clickHandler )
document.addEventListener( "click", clickHandler, true );
if( downHandler )
document.addEventListener( "mousedown", downHandler, true );
if( moveHandler )
document.addEventListener( "mousemove", moveHandler, true );
if( upHandler)
document.addEventListener( "mouseup", upHandler, true );
}
WebBrowser.prototype.releaseMouseCapture = function( node, clickHandler, downHandler, moveHandler, upHandler ) {
if( upHandler)
document.removeEventListener( "mouseup", upHandler, true );
if( moveHandler )
document.removeEventListener( "mousemove", moveHandler, true );
if( downHandler )
document.removeEventListener( "mousedown", downHandler, true );
if( clickHandler )
document.removeEventListener( "click", clickHandler, true );
}
} else {
// If a Microsoft IE based browser implement this function
WebBrowser.prototype.setMouseCapture = function( node, clickHandler, downHandler, moveHandler, upHandler ) {
node.setCapture();
if( clickHandler)
node.attachEvent( "onclick", clickHandler );
if( downHandler)
node.attachEvent( "onmousedown", downHandler );
if( moveHandler )
node.attachEvent( "onmousemove", moveHandler );
if( upHandler )
node.attachEvent( "onmouseup", upHandler );
} // endif
WebBrowser.prototype.releaseMouseCapture = function( node, clickHandler, downHandler, moveHandler, upHandler ) {
if( upHandler )
node.detachEvent( "onmouseup", upHandler );
if( moveHandler )
node.detachEvent( "onmousemove", moveHandler );
if( downHandler)
node.detachEvent( "onmousedown", downHandler );
if( clickHandler)
node.detachEvent( "onclick", clickHandler );
node.releaseCapture();
} // endif
}
if (typeof document.documentElement.setAttributeNS != 'undefined') {
WebBrowser.prototype.stopPropagation = function( event ) {
event.stopPropagation();
event.preventDefault();
return false;
}
WebBrowser.prototype.target = function( event ) {
return event.target;
}
WebBrowser.prototype.attrName = function( event ) {
return event.attrName
}
WebBrowser.prototype.charCode = function(event) {
return event.charCode;
}
WebBrowser.prototype.calculateOffsetLeft = function( node ) {
return node.offsetLeft;
}
WebBrowser.prototype.calculateOffsetTop = function( node ) {
return node.offsetTop;
}
WebBrowser.prototype.pageX = function( e ) {
return e.pageX;
}
WebBrowser.prototype.pageY = function( e ) {
return e.pageY;
}
WebBrowser.prototype.setNodePosition = function(node,left,top) {
node.style.left = left+"px";
node.style.top = top+"px";
}
} else {
WebBrowser.prototype.stopPropagation = function( event ) {
event.cancelBubble = true;
event.returnValue = false;
return false;
}
WebBrowser.prototype.charCode = function(event) {
return window.browser.keyCode( event );
}
WebBrowser.prototype.target = function( event ) {
return event.srcElement;
}
WebBrowser.prototype.attrName = function( event ) {
return event.propertyName;
}
WebBrowser.prototype.calculateOffsetLeft = function(node) {
var offset = 0;
while( node ) {
offset += node.offsetLeft;
node = node.offsetParent;
}
return offset;
}
WebBrowser.prototype.calculateOffsetTop = function(node) {
var offset = 0;
while( node ) {
offset = offset + node.offsetTop;
node = node.offsetParent;
}
return offset;
}
WebBrowser.prototype.pageX = function( e ) {
return e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
}
WebBrowser.prototype.pageY = function( e ) {
return e.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
}
WebBrowser.prototype.setNodePosition = function(node,left,top) {
offsetx = 0;
offsety = 0;
nnode = node.offsetParent
while( nnode ) {
offsetx = offsetx + nnode.offsetLeft;
offsety = offsety + nnode.offsetTop;
nnode = nnode.offsetParent;
}
node.style.left = left-offsetx+"px";
node.style.top = top-offsety+"px";
}
};
if (document.addEventListener) {
// Functions for W3C Standards compliant implementation of adding event handlers
WebBrowser.prototype.addEvent = function(elmTarget, sEventName, fCallback) {
elmTarget.addEventListener(sEventName, fCallback, false);
returnValue = true;
};
WebBrowser.prototype.removeEvent = function(elmTarget, sEventName, fCallback) {
elmTarget.removeEventListener(sEventName, fCallback, false);
returnValue = true;
};
WebBrowser.prototype.addChangeEvent = function(elmTarget, fCallback) {
elmTarget.addEventListener("DOMAttrModified", fCallback, false);
returnValue = true;
};
} else {
if(document.attachEvent) {
// IE Specific Event handler functions
WebBrowser.prototype.addEvent = function(elmTarget, sEventName, fCallback) {
returnValue = elmTarget.attachEvent('on' + sEventName, fCallback);
};
WebBrowser.prototype.removeEvent = function(elmTarget, sEventName, fCallback) {
returnValue = elmTarget.detachEvent('on' + sEventName, fCallback);
};
WebBrowser.prototype.addChangeEvent = function(elmTarget, fCallback) {
returnValue = elmTarget.attachEvent("onpropertychange", fCallback);
};
} else {
// For browsers that do not support W3C or IE event functions
WebBrowser.prototype.addEvent = function(elmTarget, sEventName, fCallback) {
return false;
};
WebBrowser.prototype.removeEvent = function(elmTarget, sEventName, fCallback) {
return false;
};
WebBrowser.prototype.addChangeEvent = function(elmTarget, fCallback) {
return false;
};
}
}
widgets_flag = true;
var widgets = new Widgets();
var browser = new WebBrowser();
function initApp() {
widgets.init();
}
</script>
Show Javascript Source Code: tree1.js
<script type="text/javascript">
// JavaScript Document
var SLIDER_KNOB_BORDER_WIDTH = 2;
var SLIDER_BORDER_WIDTH = 1;
// ******************************************
//
// Tree Class
//
// ******************************************
//
// The state variable keeps track of current state of checkbox
function Tree( id ) {
this.id = id;
this.current_node = null;
}
//
//
Tree.prototype.init = function() {
this.node = document.getElementById( this.id );
var obj = this;
browser.addEvent( this.node, "keydown", function(event) {handleTreeKeyDownEvent(event, obj); }, false);
browser.addEvent( this.node, "focus", function(event) {handleTreeFocusEvent(event, obj); }, false);
// add click events to expandable nodes
var nodes = this.node.getElementsByTagName("li");
for( var i = 0; i < nodes.length; i++ ) {
if( nodes[i].getAttribute("aria-expanded") ) {
browser.addEvent( nodes[i], "click", function(event) {handleTreeItemClickEvent( event, obj); }, false );
} // endif
} // endfor
}
// The state variable keeps track of current state of expansion and contaction of tree group
treeToggleExpand = function( node ) {
if( node.getAttribute("aria-expanded" ) )
if (node.getAttribute("aria-expanded") == "true") {
node.setAttribute("aria-expanded", "false");
// Kick IE 7 to update css styling based on changes in attribute values
node.className += "";
} else {
node.setAttribute("aria-expanded", "true");
// Kick IE 7 to update css styling based on changes in attribute values
node.className += "";
}
}
//
// Get the next sibling that elements
treeParentTreeGroupElement = function ( tree ) {
var next_node = tree.current_node.parentNode;
while( next_node &&
!next_node.getAttribute("role") &&
(next_node.getAttribute("role") != "tree") &&
(next_node.getAttribute("role") != "group")
) {
next_node = next_node.parentNode;
} // endwhile
if( next_node.getAttribute("role") == "group" )
return previousSiblingElement(next_node);
else
return null;
}
//
// When moving to the previous node you want to get to the last item in a branch
treeLastTreeItem = function( node ) {
var new_node = null;
var next_node;
var element_nodes;
if( node.getAttribute("aria-expanded") == "true" ) {
next_node = nextSiblingElement( node );
//
// check to see if node exists
if( next_node ) {
element_nodes = next_node.getElementsByTagName("*");
// start with last element
for( var i = (element_nodes.length-1); i > 0 ; i--) {
if( element_nodes[i].getAttribute("role") == "treeitem") {
new_node = element_nodes[i];
break;
} // endif
} // endfor
} // endif
}
return new_node;
}
//
// Move the current node to the next node in the tree
treeNextTreeItem = function( tree ) {
var new_node = null;
var next_node = null;
var element_nodes = null;
if(tree.current_node == null) {
tree.current_node = tree.node.getElementsByTagName("*")[0];
} // endif
// alert(tree.current_node.getAttribute("role"));
// alert(tree.current_node.getAttribute("role") + " " + tree.current_node.getAttribute("aria-expanded"));
// If on a group node check to see if its children are visible
if( tree.current_node.getAttribute("aria-expanded") == "true" ) {
// get the group node
next_node = nextSiblingElement( tree.current_node );
if( next_node ) {
// alert(next_node.tagName + " " + next_node.getAttribute("role"));
element_nodes = next_node.getElementsByTagName("*");
for( var i = 0; i < element_nodes.length; i++) {
if( element_nodes[i].getAttribute("role") == "treeitem") {
new_node = element_nodes[i];
break;
} // endif
} // endfor
} // endif
} else {
// look for sibling nodes
next_node = nextSiblingElement(tree.current_node);
while( next_node ) {
if( next_node.getAttribute("role") == "treeitem" ) {
new_node = next_node;
break;
} // endif
next_node = nextSiblingElement(next_node);
} // endwhile
// see there is no sibling then check parent nodes
if( !next_node ) {
// look for parent nodes
next_node = tree.current_node.parentNode;
while( next_node &&
(next_node.getAttribute("role") != "tree") &&
!nextSiblingElement(next_node)
) {
next_node = next_node.parentNode;
} // endwhile
if( (next_node.getAttribute("role") != "tree") &&
nextSiblingElement(next_node)
) {
new_node = nextSiblingElement(next_node);
} // endif
} // endif
} // endif
if( new_node != null ) {
tree.current_node = new_node;
tree.current_node.focus();
} // endif
}
// ***************************************
// previousTreeItem
//
treePreviousTreeItem = function( tree ) {
var new_node = null;
var next_node = null;
//
// check to see if their are any previous siblings that are treeitems or groups
new_node = previousSiblingElement( tree.current_node );
while( new_node && (new_node.getAttribute("role") != "treeitem") ) {
new_node = previousSiblingElement( new_node );
} // while
// see if new node is group and if the group is expanded
if( new_node ) {
while (new_node.getAttribute("aria-expanded") == "true") {
new_node = treeLastTreeItem(new_node);
} // endwhile
} else {
// if no previous tree element check for parent tree element
new_node = treeParentTreeGroupElement(tree);
} // endif
if( new_node != null ) {
tree.current_node = new_node;
tree.current_node.focus();
} // endif
}
// ***************************************
// collapseTreeGroup
//
treeCollapseTreeGroup = function( tree ) {
if( tree.current_node.getAttribute("aria-expanded") ) {
tree.current_node.setAttribute("aria-expanded", "false");
// Kick IE 7 to update css styling based on changes in attribute values
tree.current_node.className += "";
} // endif
}
// ***************************************
// expandTreeGroup
//
treeExpandTreeGroup = function( tree ) {
if( tree.current_node.getAttribute("aria-expanded") ) {
tree.current_node.setAttribute("aria-expanded", "true");
// Kick IE 7 to update css styling based on changes in attribute values
tree.current_node.className += "";
} // endif
}
//
// Keyboard Event Handler for Tree Role
function handleTreeFocusEvent( event, tree ) {
if(tree.current_node == null) {
tree.current_node = tree.node.getElementsByTagName("*")[0];
}
tree.current_node.focus();
}
//
// Keyboard Event Handler for Tree Role
function handleTreeKeyDownEvent( event, tree ) {
var e = event || window.event;
switch(e.keyCode) {
case KEY_DOWN:
if( !e.ctrlKey && !e.altKey ) {
treeNextTreeItem( tree );
return browser.stopPropagation(e);
} // endif
break;
case KEY_UP:
if( !e.ctrlKey && !e.altKey ) {
treePreviousTreeItem( tree );
return browser.stopPropagation(e);
} // endif
break;
case KEY_RIGHT:
if( !e.ctrlKey && !e.altKey ) {
treeExpandTreeGroup( tree );
return browser.stopPropagation(e);
} // endif
break;
case KEY_LEFT:
if( !e.ctrlKey && !e.altKey ) {
treeCollapseTreeGroup( tree );
return browser.stopPropagation(e);
} // endif
break;
default:
return true;
break;
} // endswitch
return true;
}
//
//
function handleTreeGroupClickEvent( event, tree ) {
var e = event || window.event;
var node = browser.target( e );
// If node can be expanded, then toggle node state
if( node.getAttribute("aria-expand") ) {
treeToggleExpand( node );
return browser.stopPropagation(e)
}
return true;
}
//
//
function handleTreeItemClickEvent( event, tree ) {
var e = event || window.event;
var node = browser.target( e );
if( node && node.getAttribute("role") == "treeitem" ) {
tree.current_node = node;
tree.current_node.focus();
if( tree.current_node.getAttribute("aria-expanded") )
treeToggleExpand( node );
return browser.stopPropagation(e);
}
return true;
}
</script>
CSS Source Code
Show CSS Source Code: tree1.css
<style type="text/css">
/* CSS Document */
li[role="treeitem"]:focus {
background: black;
color: white;
font-weight: bold;
}
li[aria-expanded="true"]:focus {
background-image:url(images/treeExpandedFocus.gif);
background-repeat:no-repeat;
background-position: 2px center;
}
li[aria-expanded="false"]:focus {
background-image: url(images/treeContractedFocus.gif);
background-repeat:no-repeat;
background-position: 2px center;
}
ul.tree, ul.tree ul {
list-style: none;
margin: 0;
padding-left: 0.25em;
color: black;
background: white;
font-weight: normal;
}
ul.tree li {
padding-left: 18px;
width: 10em;
}
li[aria-expanded="true"] {
background-image:url(images/treeExpanded.gif);
background-repeat: no-repeat;
background-position: 2px center;
font-weight: bold;
}
li[aria-expanded="false"] {
background-image: url(images/treeContracted.gif);
background-repeat: no-repeat;
background-position: 2px center;
font-weight: bold;
}
li[aria-expanded="false"] + li[role="group"]{
display: none;
}
li[aria-expanded="true"] + li[role="group"]{
display: block;
}
</style>