673 lines
18 KiB
HTML
673 lines
18 KiB
HTML
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
ul.tree {
|
|
width: 10em;
|
|
}
|
|
ul.tree, ul.tree ul {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding-left: 20px;
|
|
font-weight: normal;
|
|
background-color: white;
|
|
color: black;
|
|
}
|
|
|
|
ul.tree li {
|
|
margin-left: 17px;
|
|
}
|
|
|
|
ul.tree li.groupHeader {
|
|
font-weight: bold;
|
|
margin-left: 0px;
|
|
}
|
|
|
|
img.headerImg {
|
|
margin-right: 5px;
|
|
}
|
|
|
|
li.focus {
|
|
color: white;
|
|
background: black;
|
|
}
|
|
</style>
|
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
|
<script type="text/javascript">
|
|
var g_focusHandled = false; // set to true if focus is handled
|
|
|
|
//
|
|
// Function resetFocusFlag() is a callback to reset the focusHandled flag. This is
|
|
// called by the timer set in the treeview focus handler
|
|
//
|
|
function resetFocusFlag() {
|
|
g_focusHandled = false;
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
|
|
var treeviewApp = new treeview('tree1');
|
|
|
|
}); // end ready
|
|
|
|
//
|
|
// Function keyCodes() is an object to define keycodes for the application
|
|
//
|
|
function keyCodes() {
|
|
|
|
this.enter = 13;
|
|
this.space = 32;
|
|
|
|
this.pageup = 33;
|
|
this.pagedown = 34;
|
|
this.end = 35;
|
|
this.home = 36;
|
|
this.left = 37;
|
|
this.up = 38;
|
|
this.right = 39;
|
|
this.down = 40;
|
|
this.asterisk = 106;
|
|
|
|
} // end keyCodes()
|
|
|
|
//
|
|
// Function treeview() is a class constructor for a treeview widget. The widget binds to an
|
|
// unordered list. The top-level <ul> must have role='tree'. All list items must have role='treeitem'.
|
|
//
|
|
// Tree groups must be embedded lists within the listitem that heads the group. the top <ul> of a group
|
|
// must have role='group'. aria-expanded is used to indicate whether a group is expanded or collapsed. This
|
|
// property must be set on the listitem the encapsulates the group.
|
|
//
|
|
// @param (treeID string) treeID is the html id of the top-level <ul> of the list to bind the widget to
|
|
//
|
|
// @return N/A
|
|
//
|
|
function treeview(treeID) {
|
|
|
|
// define the object properties
|
|
this.$id = $('#' + treeID);
|
|
this.$listitems = this.$id.find('li'); // an array of list items
|
|
this.$groups = undefined; // an array of the listitems that function as group headers
|
|
this.$visibleItems = undefined; // an array of currently visible listitems (including headers)
|
|
this.focusHandled = false; // Set to true when a focus event is handled and reset after a small delay
|
|
|
|
this.keys = new keyCodes();
|
|
|
|
// initialize the treeview
|
|
this.init();
|
|
|
|
// bind event handlers
|
|
this.bindHandlers();
|
|
|
|
} // end treeview() constructor
|
|
|
|
//
|
|
// Function init() is a member function to initialize the treeview widget. It traverses the tree, identifying
|
|
// which listitems are headers for groups and applying initial collapsed are expanded styling
|
|
//
|
|
// @return N/A
|
|
//
|
|
treeview.prototype.init = function() {
|
|
|
|
var thisObj = this;
|
|
|
|
// iterate through the tree and apply the groupHeader class and styling to the group headers
|
|
this.$id.find('li').each (function(index) {
|
|
|
|
var $group = $(this).children('ul');
|
|
|
|
if ($group.length > 0) {
|
|
// this listitem is a group header
|
|
|
|
// Apply the group header styling
|
|
$(this).addClass('groupHeader');
|
|
|
|
// insert the header image. Note: this method allows the widget to degrade gracefully
|
|
// if javascript is disabled or there is some other error.
|
|
$(this).prepend('<img class="headerImg" src="treeExpanded.gif" />');
|
|
|
|
// If the aria-expanded is false, hide the group and display the collapsed state image
|
|
if ($(this).attr('aria-expanded') == 'false') {
|
|
$group.hide();
|
|
$(this).find('img').attr('src', 'treeContracted.gif');
|
|
}
|
|
}
|
|
});
|
|
|
|
// create the group and initial visible item array
|
|
this.$groups = $('li.groupHeader');
|
|
this.$visibleItems = this.$id.find('li:visible');
|
|
|
|
} // end init()
|
|
|
|
//
|
|
// Function expandGroup() is a member function to expand a collapsed group
|
|
//
|
|
// @param($id object) $id is the jquery id of the group header of the group to expand
|
|
//
|
|
// @param(focus boolean) focus is true if the group header has focus, false otherwise
|
|
//
|
|
// @return N/A
|
|
//
|
|
treeview.prototype.expandGroup = function($id, focus) {
|
|
|
|
var $group = $id.children('ul');
|
|
|
|
// expand the group
|
|
$group.show();
|
|
|
|
$id.attr('aria-expanded', 'true');
|
|
|
|
if (focus == true) {
|
|
$id.children('img').attr('src', 'treeExpandedFocus.gif');
|
|
}
|
|
else {
|
|
$id.children('img').attr('src', 'treeExpanded.gif');
|
|
}
|
|
|
|
// refresh the list of visible items
|
|
this.$visibleItems = this.$id.find('li:visible');
|
|
|
|
} // end expandGroup()
|
|
|
|
//
|
|
// Function collapseGroup() is a member function to collapse an expanded group
|
|
//
|
|
// @param($id object) $id is the jquery id of the group header of the group to collapse
|
|
//
|
|
// @param(focus boolean) focus is true if the group header has focus, false otherwise
|
|
//
|
|
// @return N/A
|
|
//
|
|
treeview.prototype.collapseGroup = function($id, focus) {
|
|
|
|
var $group = $id.children('ul');
|
|
|
|
// collapse the group
|
|
$group.hide();
|
|
|
|
$id.attr('aria-expanded', 'false');
|
|
|
|
if (focus == true) {
|
|
$id.children('img').attr('src', 'treeContractedFocus.gif');
|
|
}
|
|
else {
|
|
$id.children('img').attr('src', 'treeContracted.gif');
|
|
}
|
|
|
|
// refresh the list of visible items
|
|
this.$visibleItems = this.$id.find('li:visible');
|
|
|
|
} // end collapseGroup()
|
|
|
|
//
|
|
// Function toggleGroup() is a member function to toggle the display state of a group
|
|
//
|
|
// @param($id object) $id is the jquery id of the group header of the group to toggle
|
|
//
|
|
// @param(focus boolean) focus is true if the group header has focus, false otherwise
|
|
//
|
|
// @return N/A
|
|
//
|
|
treeview.prototype.toggleGroup = function($id, focus) {
|
|
|
|
var $group = $id.children('ul');
|
|
|
|
if ($id.attr('aria-expanded') == 'true') {
|
|
// collapse the group
|
|
this.collapseGroup($id, focus);
|
|
}
|
|
else {
|
|
// expand the group
|
|
this.expandGroup($id, focus);
|
|
}
|
|
|
|
} // end toggleGroup()
|
|
|
|
//
|
|
// Function bindHandlers() is a member function to bind event handlers to the listitems
|
|
//
|
|
// return N/A
|
|
//
|
|
treeview.prototype.bindHandlers = function() {
|
|
|
|
var thisObj = this;
|
|
|
|
// bind a dblclick handler to the group headers
|
|
this.$groups.dblclick(function(e) {
|
|
return thisObj.handleDblClick($(this), e);
|
|
});
|
|
|
|
// bind a click handler
|
|
this.$listitems.click(function(e) {
|
|
return thisObj.handleClick($(this), e);
|
|
});
|
|
|
|
// bind a keydown handler
|
|
this.$listitems.keydown(function(e) {
|
|
return thisObj.handleKeyDown($(this), e);
|
|
});
|
|
|
|
// bind a keypress handler
|
|
this.$listitems.keypress(function(e) {
|
|
return thisObj.handleKeyPress($(this), e);
|
|
});
|
|
|
|
// bind a focus handler
|
|
this.$listitems.focus(function(e) {
|
|
return thisObj.handleFocus($(this), e);
|
|
});
|
|
|
|
// bind a blur handler
|
|
this.$listitems.blur(function(e) {
|
|
return thisObj.handleBlur($(this), e);
|
|
});
|
|
|
|
} // end bindHandlers()
|
|
|
|
//
|
|
// Function doHighlight() is a member function to remove the highlighting from
|
|
// other treeview items and apply it to the passed element
|
|
//
|
|
// @param ($id object) $id is the jQuery object of the element to highlight
|
|
//
|
|
// @param (isHeader boolean) isHeader is true if $id points to a group header
|
|
//
|
|
// @return N/A
|
|
//
|
|
treeview.prototype.doHighlight = function($id, isHeader) {
|
|
|
|
// remove the focus highlighting from the treeview items
|
|
// and remove them from the tab order.
|
|
this.$listitems.removeClass('focus').attr('tabindex', '-1');
|
|
|
|
// remove the focus image from group headers
|
|
this.$groups.each(function() {
|
|
// add the focus image
|
|
if ($(this).attr('aria-expanded') == 'true') {
|
|
$(this).children('img').attr('src', 'treeExpanded.gif');
|
|
}
|
|
else {
|
|
$(this).children('img').attr('src', 'treeContracted.gif');
|
|
}
|
|
});
|
|
|
|
if (isHeader == true) {
|
|
// add the focus image
|
|
if ($id.attr('aria-expanded') == 'true') {
|
|
$id.children('img').attr('src', 'treeExpandedFocus.gif');
|
|
}
|
|
else {
|
|
$id.children('img').attr('src', 'treeContractedFocus.gif');
|
|
}
|
|
}
|
|
|
|
|
|
// apply the focus highlighting and place the element in the tab order
|
|
$id.addClass('focus').attr('tabindex', '0');
|
|
|
|
} // end doHighlight()
|
|
|
|
//
|
|
// Function handleKeyDown() is a member function to process keydown events for the treeview items
|
|
//
|
|
// @param ($id object) $id is the jQuery id of the group header firing event
|
|
//
|
|
// @param (e object) e is the associated event object
|
|
//
|
|
// @return (boolean) returns false if consuming event; true if not
|
|
//
|
|
treeview.prototype.handleKeyDown = function($id, e) {
|
|
|
|
var curNdx = this.$visibleItems.index($id);
|
|
var isHeader = false;
|
|
|
|
// determine if this is a group header
|
|
if (this.$groups.index($prev) != -1) {
|
|
isHeader = true;
|
|
}
|
|
|
|
if (e.altKey || e.ctrlKey || e.shiftKey) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
switch (e.keyCode) {
|
|
case this.keys.home: {
|
|
this.$groups.first().focus();
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.end: {
|
|
this.$visibleItems.last().focus();
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.enter: {
|
|
|
|
if (isHeader == false) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
this.toggleGroup($id, true);
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.left: {
|
|
|
|
if (isHeader == false) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
if ($id.attr('aria-expanded') == 'true') {
|
|
this.collapseGroup($id, true);
|
|
}
|
|
else {
|
|
// move to previous group header
|
|
var prevNdx = this.$groups.index($id) - 1;
|
|
|
|
if (prevNdx >= 0) {
|
|
var $prev = this.$groups.eq(prevNdx);
|
|
var parentFound = false;
|
|
|
|
while (parentFound == false) {
|
|
if ($prev.find('#' + $id.attr('id')).length > 0) {
|
|
parentFound = true;
|
|
$prev.focus();
|
|
break;
|
|
}
|
|
else {
|
|
// decrement prevNdx to reference the previous
|
|
// group header in the $groups array
|
|
prevNdx--;
|
|
|
|
$prev = this.$groups.eq(prevNdx);
|
|
|
|
if (prevNdx < 0) {
|
|
// no parent group header
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // end while
|
|
}
|
|
}
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.right: {
|
|
|
|
if (isHeader == false) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
if ($id.attr('aria-expanded') == 'false') {
|
|
this.expandGroup($id, true);
|
|
}
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.up: {
|
|
|
|
if (curNdx > 0) {
|
|
var $prev = this.$visibleItems.eq(curNdx - 1);
|
|
|
|
$prev.focus();
|
|
}
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.down: {
|
|
|
|
if (curNdx < this.$visibleItems.length - 1) {
|
|
var $next = this.$visibleItems.eq(curNdx + 1);
|
|
|
|
$next.focus();
|
|
}
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
case this.keys.asterisk: {
|
|
// expand all groups
|
|
|
|
var thisObj = this;
|
|
|
|
this.$groups.each(function() {
|
|
thisObj.expandGroup($(this), false);
|
|
});
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
} // end handleKeyDown
|
|
|
|
//
|
|
// Function handleKeyPress() is a member function to process keypress events for the treeview items
|
|
// This function is needed for browsers, such as Opera, that perform window manipulation on kepress events
|
|
// rather than keydown. The function simply consumes the event.
|
|
//
|
|
// @param ($id object) $id is the jQuery id of the group header firing event
|
|
//
|
|
// @param (e object) e is the associated event object
|
|
//
|
|
// @return (boolean) returns false if consuming event; true if not
|
|
//
|
|
treeview.prototype.handleKeyPress = function($id, e) {
|
|
|
|
if (e.altKey || e.ctrlKey || e.shiftKey) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
switch (e.keyCode) {
|
|
case this.keys.enter:
|
|
case this.keys.home:
|
|
case this.keys.end:
|
|
case this.keys.left:
|
|
case this.keys.right:
|
|
case this.keys.up:
|
|
case this.keys.down: {
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
} // end handleKeyPress
|
|
|
|
//
|
|
// Function handleDblClick() is a member function to process double-click events for group headers.
|
|
// Double-click expands or collapses a group.
|
|
//
|
|
// @param ($id object) $id is the jQuery id of the group header firing event
|
|
//
|
|
// @param (e object) e is the associated event object
|
|
//
|
|
// @return (boolean) returns false if consuming event; true if not
|
|
//
|
|
treeview.prototype.handleDblClick = function($id, e) {
|
|
|
|
if (e.altKey || e.ctrlKey || e.shiftKey) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
// apply the focus highlighting
|
|
this.doHighlight($id, true);
|
|
|
|
// expand or collapse the group
|
|
this.toggleGroup($id, true);
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
|
|
} // end handleDblClick
|
|
|
|
//
|
|
// Function handleClick() is a member function to process click events.
|
|
//
|
|
// @param ($id object) $id is the jQuery id of the group header firing event
|
|
//
|
|
// @param (e object) e is the associated event object
|
|
//
|
|
// @return (boolean) returns false if consuming event; true if not
|
|
//
|
|
treeview.prototype.handleClick = function($id, e) {
|
|
|
|
if (e.altKey || e.ctrlKey || e.shiftKey) {
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
if (this.$groups.index($id) == -1) {
|
|
// this is a list item
|
|
|
|
// apply the focus highlighting
|
|
this.doHighlight($id, false);
|
|
}
|
|
else {
|
|
// this is a group header
|
|
|
|
// apply the focus highlighting
|
|
this.doHighlight($id, true);
|
|
}
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
|
|
} // end handleClick
|
|
|
|
//
|
|
// Function handleFocus() is a member function to process focus events.
|
|
//
|
|
// @param ($id object) $id is the jQuery id of the group header firing event
|
|
//
|
|
// @param (e object) e is the associated event object
|
|
//
|
|
// @return (boolean) returns true
|
|
//
|
|
treeview.prototype.handleFocus = function($id, e) {
|
|
|
|
// only process the event if the focusHandled flag is false
|
|
if (g_focusHandled == false) {
|
|
|
|
// prevent encapsulating group headers from responding to
|
|
// the focus event.
|
|
g_focusHandled = true;
|
|
|
|
if (this.$groups.index($id) == -1) {
|
|
this.doHighlight($id, false);
|
|
}
|
|
else {
|
|
// this is a group header
|
|
this.doHighlight($id, true);
|
|
}
|
|
|
|
window.setTimeout(resetFocusFlag, 10);
|
|
}
|
|
|
|
return true;
|
|
|
|
} // end handleFocus
|
|
|
|
//
|
|
// Function handleBlur() is a member function to process blur events.
|
|
//
|
|
// @param ($id object) $id is the jQuery id of the group header firing event
|
|
//
|
|
// @param (e object) e is the associated event object
|
|
//
|
|
// @return (boolean) returns true
|
|
//
|
|
treeview.prototype.handleBlur = function($id, e) {
|
|
|
|
if (this.$groups.index($id) != -1) {
|
|
// this is a group header
|
|
|
|
// remove the focus image
|
|
if ($id.attr('aria-expanded') == 'true') {
|
|
$id.children('img').attr('src', 'treeExpanded.gif');
|
|
}
|
|
else {
|
|
$id.children('img').attr('src', 'treeContracted.gif');
|
|
}
|
|
}
|
|
|
|
// remove the focus highlighting
|
|
$id.removeClass('focus');
|
|
|
|
return true;
|
|
|
|
} // end handleBlur
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div role="application">
|
|
<h2 id="label_1">Foods</h2>
|
|
<ul id="tree1" class="tree" role="tree" aria-labelledby="label_1">
|
|
<li id="fruits" role="treeitem" tabindex="0" aria-expanded="true">Fruits
|
|
<ul role="group">
|
|
<li id="oranges" role="treeitem" tabindex="-1">Oranges</li>
|
|
<li id="pinapples" role="treeitem" tabindex="-1">Pineapples</li>
|
|
<li id="apples" role="treeitem" tabindex="-1" aria-expanded="false">Apples
|
|
<ul role="group">
|
|
<li id="macintosh" role="treeitem" tabindex="-1">Macintosh</li>
|
|
<li id="granny_smith" role="treeitem" tabindex="-1" aria-expanded="false">Granny Smith
|
|
<ul role="group">
|
|
<li id="Washington" role="treeitem" tabindex="-1">Washington State</li>
|
|
<li id="Michigan" role="treeitem" tabindex="-1">Michigan</li>
|
|
<li id="New_York" role="treeitem" tabindex="-1">New York</li>
|
|
</ul>
|
|
</li>
|
|
<li id="fuji" role="treeitem" tabindex="-1">Fuji</li>
|
|
</ul>
|
|
</li>
|
|
<li id="bananas" role="treeitem" tabindex="-1">Bananas</li>
|
|
<li id="pears" role="treeitem" tabindex="-1">Pears</li>
|
|
</ul>
|
|
</li>
|
|
<li id="vegetables" role="treeitem" tabindex="-1" aria-expanded="true">Vegetables
|
|
<ul role="group">
|
|
<li id="broccoli" role="treeitem" tabindex="-1">Broccoli</li>
|
|
<li id="carrots" role="treeitem" tabindex="-1">Carrots</li>
|
|
<li id="lettuce" role="treeitem" tabindex="-1" aria-expanded="false">Lettuce
|
|
<ul role="group">
|
|
<li id="romaine" role="treeitem" tabindex="-1">Romaine</li>
|
|
<li id="iceberg" role="treeitem" tabindex="-1">Iceberg</li>
|
|
<li id="butterhead" role="treeitem" tabindex="-1">Butterhead</li>
|
|
</ul>
|
|
</li>
|
|
<li id="spinach" role="treeitem" tabindex="-1">Spinach</li>
|
|
<li id="squash" role="treeitem" tabindex="-1" aria-expanded="true">Squash
|
|
<ul role="group" >
|
|
<li id="acorn" role="treeitem" tabindex="-1">Acorn</li>
|
|
<li id="ambercup" role="treeitem" tabindex="-1">Ambercup</li>
|
|
<li id="autumn_cup" role="treeitem" tabindex="-1">Autumn Cup</li>
|
|
<li id="hubbard" role="treeitem" tabindex="-1">Hubbard</li>
|
|
<li id="kobacha" role="treeitem" tabindex="-1">Kabocha</li>
|
|
<li id="butternut" role="treeitem" tabindex="-1">Butternut</li>
|
|
<li id="spaghetti" role="treeitem" tabindex="-1">Spaghetti</li>
|
|
<li id="sweet_dumpling" role="treeitem" tabindex="-1">Sweet Dumpling</li>
|
|
<li id="turban" role="treeitem" tabindex="-1">Turban</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</body>
|
|
</html>
|