<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"
 	xmlns:x2="http://www.w3.org/TR/xhtml2"
	xmlns:role="http://www.w3.org/2005/01/wai-rdf/GUIRoleTaxonomy#"
	xmlns:state="http://www.w3.org/2005/07/aaa">
<head>

<title>Reference Implementation</title>

<script type="text/javascript" >
<![CDATA[

// returns true if node has role=group, otherwise false
function isGroup(element) {
	// Attempt to match last part of string because of unrecognized-namespace-in-value issues
	if( element.getAttributeNS("http://www.w3.org/TR/xhtml2", "role").search(/group$/) > -1 ) {
		return true;
	}
	return false;
}

// returns true if node has role=tree, else false
function isTree(element) {
	// Attempt to match last part of string because of unrecognized-namespace-in-value issues
	if( element.getAttributeNS("http://www.w3.org/TR/xhtml2", "role").search(/tree$/) > -1 ) {
		return true;
	}
	return false;
}

// returns true if node has role=group, otherwise false
function isTreeitem(element) {
	// Attempt to match last part of string because of unrecognized-namespace-in-value issues
	if( element.getAttributeNS("http://www.w3.org/TR/xhtml2", "role").search(/treeitem$/) > -1 ) {
		return true;
	}
	return false;
}

// returns true if node has role=group, otherwise false
function isTextNode(node) {
	// renamed version of isText() from the mozilla.org tree widget example
	return node && node.nodeType == node.TEXT_NODE;
}
// Returns true if the given node is a DOM element, otherwise false
function isElement(node) {
	if( node.nodeType == node.ELEMENT_NODE ) {
		return true;
	}
	return false;
}

// Determines if the combobox is in an open state
function isExpanded(node) {
	if( isElement(node) ) {
		if( node.getAttributeNS("http://www.w3.org/2005/07/aaa", "expanded") == "true" ) {
			return true;
		}
	}
	return false;
}

// returns the tree element that contains the given node, or null if no tree container is found
// From mozilla.org's dhtml widget's examples
function getTree(treeItem) {
	var tree = treeItem;
	try {
		while (tree.getAttributeNS("http://www.w3.org/TR/xhtml2", "role") != "role:tree") {
			tree = tree.parentNode;
		}
	} catch(ex) {
		return null; // No tree container for tree item
	}
	return tree;
}
// Changes the focus to the given element
// From mozilla.org's dhtml widgets examples
function setFocus(newFocus) {
	if(newFocus) {
		newFocus.focus();
	}
}

// Adds the given className string on given DOM node
function addStyleClass(node, className) {
	var classStr = node.getAttribute("class");
	
	classStr = classStr + " " + className;
	node.setAttribute("class",classStr);
}

// Removes the given className string on given DOM node
function removeStyleClass(node, className) {
	var classStr = node.getAttribute("class");
	
	if(classStr == "" || classStr == null) {
		return;
	}
	
	classNameRegExp = new RegExp("^" + className + "\\s*", "g");
	classStr = classStr.replace(classNameRegExp,"");
	classNameRegExp = new RegExp("\\s+" + className, "g");
	classStr = classStr.replace(classNameRegExp,"");

	node.setAttribute("class",classStr);
}

/* Several functions related to a specific implementation of the tree 
 * Largely adapted from the mozilla.org dhtml examples. 
 * */

var TREE_NAVIGATION_WRAP = false;  // set true to allow wrap in tree navigation, set false to disallow
var TREE_INCREMENTAL_TIMEOUT = 1500;  // the time during which the incremental find function appends characters to the search string

// Determines if a given group element is to be considered open or closed
function isGroupClosed(groupItem) {
	// a role:treeitem preceeding a role:group determines the open/closed state of the group
	var treeItemForGroup = getRelativeTreeItem(groupItem, -1, false);
	return treeItemForGroup.getAttributeNS("http://www.w3.org/2005/07/aaa", "expanded") == 'false';
}

// Returns first treeitem given tree
function getFirstTreeItem(tree) {
	return getRelativeTreeItem(tree.firstChild, 1, false);
}

// Returns the last tree item if the given tree were fully expanded
function getLastTreeItem(tree) {
	var lastTreeItem = tree;
	do {
	 	lastTreeItem = lastTreeItem.lastChild;
		if (isTextNode(lastTreeItem)) {
			lastTreeItem = lastTreeItem.previousSibling;
		}
	} while ( isGroup(lastTreeItem) );
	return lastTreeItem;
}

// Returns the next sibling group role in the DOM tree or null if none found
function getNextSiblingGroup(element) {
	while( element.nextSibling != null ) {
		if( isElement(element.nextSibling) ) {
			if( isGroup(element.nextSibling) ) {
				return element.nextSibling;
			} 
		}
		element = element.nextSibling;
	}
	return null;
}

// Returns the last tree item that is visible to the user (ignores tree items in unexpanded groups)
function getLastVisibleTreeItem(tree) {
	if(tree == null) {
		return null;
	}

	// Start from the last node contained in the tree
	for(var i=tree.childNodes.length-1; i>=0; i--) {
		//  Try an find an unexpanded tree item
		if( isElement(tree.childNodes[i]) ) {
			if( isTreeitem(tree.childNodes[i]) ) {
				if( isExpanded(tree.childNodes[i]) ) {
					return getLastVisibleTreeItem(getNextSiblingGroup(tree.childNodes[i]));  // descend into subtree/group
				} else {
					return tree.childNodes[i]; // the last visible treeitem
				}
			}
		}
	}
	return null;
}

// returns the next treeitem as one would expect by using the up/down arrow keys 
// ... on any kind of mixed expanded/collapsed tree.  Next will skip all treeitems 
// ... in a subtree if closed and go to the next sibling treeitem, or will decend into the 
// ... subtree and return the first subtree item if open.
// delta indicates the change in tree position, -1 for previous, 1 for next
// wrap is boolean and denotes behavior when the search runs into either "end" of the tree
function getRelativeTreeItem(treeItem, delta, wrap) {
	if (delta == 1) {  // Next
		var newTreeItem = treeItem.nextSibling;
		if (isTextNode(newTreeItem)) {
			newTreeItem = newTreeItem.nextSibling;
		}
		if (newTreeItem) {
			if (isGroup(newTreeItem)) {
				if (isGroupClosed(newTreeItem)) {
					return getRelativeTreeItem(newTreeItem, 1, wrap);
				}
				newTreeItem = newTreeItem.firstChild;
				if (isTextNode(newTreeItem)) {
					newTreeItem = newTreeItem.nextSibling;
				}
				return newTreeItem;
			}	
			return (isTreeitem(newTreeItem) ? newTreeItem : null);
		}
	} else {    // Prev
		var newTreeItem = treeItem.previousSibling;
		if (isTextNode(newTreeItem)) {
			newTreeItem = newTreeItem.previousSibling;
		}
		if (newTreeItem) {
			while (isGroup(newTreeItem)) {
				if (isGroupClosed(newTreeItem)) {
					return getRelativeTreeItem(newTreeItem, -1, wrap);
				}
				newTreeItem = newTreeItem.lastChild;
				if (isTextNode(newTreeItem)) {
					newTreeItem = newTreeItem.previousSibling;
				}
			}
		return (isTreeitem(newTreeItem) ? newTreeItem : null);
		}
	}
	
	// If the next/previousSibling's have not existed, wrap the navigation:
	if (isTree(treeItem.parentNode)) {
		if (!wrap) {
			return null;
		}
		var tree = getTree(treeItem)
		return (delta == 1)? getFirstTreeItem(tree) : getLastVisibleTreeItem(tree);
	}
	return getRelativeTreeItem(treeItem.parentNode, delta, wrap); // Recursive
}

// adds the "incremental" styling class to the currently focused tree element
function styleIncrementalActive(treeId) {
	var tree = document.getElementById(treeId);
	if (typeof tree.lastFocus == "undefined") {
		// Do nothing
	} else {
		addStyleClass(tree, "incremental");
	}	
}

// removes the "incremental" styling class to the currently focused tree element
function styleIncrementalNotActive(treeId) {
	var tree = document.getElementById(treeId);
	if (typeof tree.lastFocus == "undefined") {
		// Do nothing
	} else {
		removeStyleClass(tree, "incremental");
	}	
}

function treeItemEvent(event) {
	var treeItem = event.target;
	var hasChildren = treeItem.hasAttributeNS("http://www.w3.org/2005/07/aaa", "expanded");
	var isItemOpen = hasChildren 
		&& treeItem.getAttributeNS("http://www.w3.org/2005/07/aaa", "expanded") == 'true';
	var focusDelta = 0;  // (-1 = up, 0 = no change, 1 = down)
	
	var toggleitem = false;
	if (event.type == "dblclick" && event.button == 0) {
		toggleitem = hasChildren;
		getTree(treeItem)._incrementalString = "";
	} else if (event.type == "keydown") {
		if (event.altKey) {
			return true;  // Browser should use this, the tree view doesn't need alt-modified keys
		}
		// XXX Implement multiple selection (ctrl+arrow, shift+arrow, ctrl+space)
		if (event.keyCode == event.DOM_VK_HOME) {
			try {
				setFocus(getFirstTreeItem(getTree(treeItem)));
			} catch(ex) {
			}
		return false;
		}
		if (event.keyCode == event.DOM_VK_RETURN) {
			toggleitem = hasChildren;
		} else if (event.keyCode == event.DOM_VK_DOWN) {
			focusDelta = 1;
		} else if (event.keyCode == event.DOM_VK_UP) {
			focusDelta = -1;
		} else if (event.keyCode == event.DOM_VK_LEFT) {
			if (isItemOpen) {
				toggleitem = true;
			} else {
				var groupItem = treeItem.parentNode;
				if (groupItem.getAttributeNS("http://www.w3.org/TR/xhtml2", "role") == "role:group") {
					setFocus(getRelativeTreeItem(groupItem, -1, false));
				}
			}
		} else if (event.keyCode == event.DOM_VK_RIGHT) {
			if (isItemOpen) {
				focusDelta = 1;
			} else {
				toggleitem = hasChildren;
			}
		} else if (event.keyCode == event.DOM_VK_END) {
			try {
				var newTreeItemFocus = getLastVisibleTreeItem(getTree(treeItem));
				setFocus(newTreeItemFocus);
			} catch(ex) { 
			}
			return false;   // Consume the event
		} else {
			return true;  // We didn't need key, don't consume event
		}
	} else if (event.type == "keypress") {
		// Implement incremental find here, instead of keydown, because we
		// need to capture printable characters in a keypress handler
		var tree = getTree(treeItem);
		var key;
		var keyAdded = false;
		
		// romanows, 6/19/06: commented out shift test to allow capitalized incremental search
		if (event.charCode > ' ' && !event.altKey && !event.ctrlKey /* && !event.shiftKey */ && !event.metaKey) {
			key = String.fromCharCode(event.charCode);
		} else {
			tree._incrementalString = "";
			styleIncrementalNotActive(tree.id);
			return true;
		}
		key = key.toLowerCase();
		if (event.timeStamp - tree._lastKeyTime > TREE_INCREMENTAL_TIMEOUT) {
			tree._incrementalString = key;
			keyAdded = true;
		} else {
			tree._incrementalString += key;
			keyAdded = true;			
		}
		tree._lastKeyTime = event.timeStamp;
		var length = tree._incrementalString.length;
		var incrementalString = tree._incrementalString;
		var charIndex = 1;
		while (charIndex < length && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
			charIndex++;
		}
		// If all letters in incremental string are same, just try to match the first one
		if (charIndex == length) {
			length = 1;
			incrementalString = incrementalString.substring(0, length);
		}
		var origTreeItem = treeItem;
		if (length == 1) {
			treeItem = getRelativeTreeItem(treeItem, 1, true);
		}

		if(keyAdded) {
			// romanows, 6/19/06: added new styling indicator timeout
			var treeItem = event.target;
			var tree = getTree(treeItem);
	
			styleIncrementalActive(tree.id);
			if( typeof currentTimeout != "undefined" ) {
				clearTimeout(currentTimeout);
			}
			currentTimeout = setTimeout("styleIncrementalNotActive(\"" + tree.id + "\")",TREE_INCREMENTAL_TIMEOUT);
		}
		
		do {
			var text = treeItem.firstChild.data;
			if (treeItem.firstChild.data.substring(0, length).toLowerCase() == incrementalString) {
				setFocus(treeItem);
				return false;
			}
			treeItem = getRelativeTreeItem(treeItem, 1, true);
		} while (treeItem != origTreeItem);
		return false;
	} else if (event.type == "click") {
		// XXX The twisties are :before content
		// Can we use event.clientX to determine if we are on the twisty so we know when to toggle expansion?
		// Can we compare that with window.getComputedStyle(event.target, "").getPropertyCSSValue("left");
		getTree(treeItem)._incrementalString = '';
	} else {
		return true; // continue propagating event;
	}
	if (toggleitem) {
		treeItem.setAttributeNS("http://www.w3.org/2005/07/aaa", "expanded", isItemOpen? "false" : "true");
		return false; // consume event
	}
	if (focusDelta) {
		try {
			setFocus(getRelativeTreeItem(treeItem, focusDelta, TREE_NAVIGATION_WRAP)); 
		} catch(ex) {
		}
		return false;
	}
	return true;  // Browser can still use event
}

// Cache last focused tree item on the tree (for performance reasons)
function treeItemFocus(event) {
	// The last focused item is always the only item in the tab order for this tree,
	// so we need to set its tabindex to 0, and the others to -1
	var treeItem = event.target;
	var tree = getTree(treeItem);
	if (typeof tree.lastFocus == "undefined") {
		tree.lastFocus = getFirstTreeItem(tree);
	}
	tree.lastFocus.tabIndex = "-1";
	
	tree.lastFocus = treeItem;
	treeItem.tabIndex = "0";
}

]]>
</script>

<style type="text/css">
@namespace x2    url("http://www.w3.org/TR/xhtml2");
@namespace role  url("http://www.w3.org/2005/01/wai-rdf/GUIRoleTaxonomy#");
@namespace state url("http://www.w3.org/2005/07/aaa");
ul.hlist {
	list-style: none;
	margin: 1em;
	padding: 0;
	display: inline;
}

ul.hlist li	{
	display: inline;
	margin: 1em;
	padding: 0;	
}

p.inline {
	display: inline;
	margin: 1em;	
}

div#content {
	position: absolute;
	top: 0;
	left: 0;
}

.hide {
/*
	visibility: hidden;
	height: 0;
	width: 0;
	padding: 0;
	margin: 0;
	display: inline;
	*/
	display: none;
}

li.testItem {
	margin: 0;
}

iframe.short {
	margin-top: 1em;
	height: 3em;
}

iframe.textfieldShort {
	margin-top: 1em;
	height: 8em;
}

iframe.comboboxShort {
	margin-top: 1em;
	height: 11em;
}

iframe.rdfShort {
	margin-top: 1em;
	height: 14em;	
}

iframe.treeShort {
	margin-top: 1em;
	height: 20em;	
}

iframe.sliderShort {
	margin-top: 1em;
	height: 5em;	
}

h3.info {
	display: inline;
	margin: 0;
	padding: 0;
}

div[x2|role="role:tree"] { 
	overflow: auto; 
	width: 20em; 
	height: 20em;
	cursor: default; 
	border: 1px solid gray;
	padding-left: .5em;
}

div[x2|role="role:treeitem"]:before { 
	font-size: .8em; 
	font-family: monospace; 	
	content: ' '; 
	margin-right: .6em;
}

div[x2|role="role:treeitem"][state|expanded='false']:before { 
	font-size: .8em; 
	font-family: monospace; 
	content: '+'; 
	outline: 1px solid black; 
	margin-left: .1em; 
	margin-right: .5em;
}

div[x2|role="role:treeitem"][state|expanded='true']:before { 
	font-size: .8em; 
	font-family: monospace; 
	content: '-'; 
	outline: 1px solid black; 
	margin-left: .1em; 
	margin-right: .5em;
}

/* FIXME: not quite, but do we need the visual branching?  Perhaps shading would be just as good.
div[x2|role="role:group"] div[x2|role="role:treeitem"]:before { 
	font-family: monospace; 
	content: '|-'; 
	margin-top: 0;
	margin-bottom: 0;
	margin-left: .1em; 
	margin-right: .5em;
}
*/

div[x2|role="role:treeitem"][state|expanded='false'] + div[x2|role="role:group"] { 
	display : none; 
}

div[x2|role="role:group"] { 
	margin-left: 1.5em; 
}

div[x2|role="role:treeitem"]:focus { 
	outline: 0px; 
	background-color: #dddddd; 
	color: black;
}

div[x2|role="role:tree"].incremental { 
	border: 1px solid red;
	background-color: #ffefef; 	
}

</style>
</head>

<body>

<div id="content">

<h1 class="hide">Reference Implementation</h1>


<h2 class="hide">Test</h2>


<div id="test">

<!-- Initially selected item needs tabindex="0" -->
<div id="tree1" x2:role="role:tree" tabindex="-1" onfocus="return treeItemFocus(event);" onclick="return treeItemEvent(event);" ondblclick="return treeItemEvent(event);" onkeydown="return treeItemEvent(event);" onkeypress="return treeItemEvent(event);">
	<div tabindex="0" x2:role="role:treeitem" state:expanded="true">Veggies</div>
	<div x2:role="role:group">
		<div tabindex="-1" x2:role="role:treeitem" state:expanded="true">Green</div>
		<div x2:role="role:group">
			<div tabindex="-1" x2:role="role:treeitem">Asparagus</div>
			<div tabindex="-1" x2:role="role:treeitem">Kale</div>
			<div tabindex="-1" x2:role="role:treeitem" state:expanded="true">Leafy</div>
			<div x2:role="role:group">
				<div tabindex="-1" x2:role="role:treeitem">Lettuce</div>
				<div tabindex="-1" x2:role="role:treeitem">Kale</div>
				<div tabindex="-1" x2:role="role:treeitem">Spinach</div>
				<div tabindex="-1" x2:role="role:treeitem">Chard</div>
			</div>
			<div tabindex="-1" x2:role="role:treeitem">Green beans</div>
		</div>
		<div tabindex="-1" x2:role="role:treeitem">Legumes</div>
		<div tabindex="-1" x2:role="role:treeitem" state:expanded="true">Yellow</div>
		<div x2:role="role:group">
			<div tabindex="-1" x2:role="role:treeitem">Bell peppers</div>
			<div tabindex="-1" x2:role="role:treeitem">Squash</div>
		</div>
	</div>
	<div tabindex="-1" x2:role="role:treeitem">Junk food</div>
	<div tabindex="-1" x2:role="role:treeitem" state:expanded="true">Fruit</div>
	<div x2:role="role:group">
		<div tabindex="-1" x2:role="role:treeitem" state:expanded="true">Typical</div>
			<div x2:role="role:group">
				<div tabindex="-1" x2:role="role:treeitem">Oranges</div>
				<div tabindex="-1" x2:role="role:treeitem">Apples</div>
				<div tabindex="-1" x2:role="role:treeitem">Bananas</div>
				<div tabindex="-1" x2:role="role:treeitem">Pineapple</div>
				<div tabindex="-1" x2:role="role:treeitem">Grapes</div>
			</div>
		<div tabindex="-1" x2:role="role:treeitem" state:expanded="true">Exotic</div>
		<div x2:role="role:group">
			<div tabindex="-1" x2:role="role:treeitem">Mangos</div>
			<div tabindex="-1" x2:role="role:treeitem">Guava</div>
			<div tabindex="-1" x2:role="role:treeitem">Star fruit</div>
			<div tabindex="-1" x2:role="role:treeitem">Coconut</div>
			<div tabindex="-1" x2:role="role:treeitem">Bread fruit</div>
			<div tabindex="-1" x2:role="role:treeitem">Custard apples</div>
			<div tabindex="-1" x2:role="role:treeitem">Papaya</div>
		</div>
	</div>
</div>

</div>

</div>

</body>
</html>


