<?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[

// 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);
}

// 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);
}

// 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 true if the given node is a DOM element, otherwise false
function isElement(node) {
	if( node.nodeType == node.ELEMENT_NODE ) {
		return true;
	}
	return false;
}

// True if the given node has a combobox role
function isCombobox(node) {
	if( isElement(node) ) {
		if( node.getAttributeNS("http://www.w3.org/TR/xhtml2", "role").search(/combobox$/) != -1 ) {
			return true;
		}
	}
	return false;
}

// True if the given node has an option role
function isOption(node) {
	if( isElement(node) ) {
		if( node.getAttributeNS("http://www.w3.org/TR/xhtml2", "role").search(/option$/) != -1 ) {
			return true;
		}
	}
	return false;
}

// Given a node, returns the first ancestor option node or null if no option parent exists
function optionGetParent(node) {
	// test if current node is a option node 
	if( isOption(node) ) {
		return(node);
	}

	if(node.parentNode == null) {
		return null; // Hit the top of the DOM
	}

	return (optionGetParent(node.parentNode));
}

// Opens the combobox, using the drop-down metaphor
function comboboxOpenDropDown(node) {
	removeStyleClass(node,"comboboxClosed");
	addStyleClass(node,"comboboxOpen");

	node.setAttributeNS("http://www.w3.org/2005/07/aaa", "expanded", "true");
}

// Closes the combobox
function comboboxCloseDropDown(node) {
	removeStyleClass(node,"comboboxOpen");
	addStyleClass(node,"comboboxClosed");
	node.setAttributeNS("http://www.w3.org/2005/07/aaa", "expanded", "false");
}

// Given a node, returns the first ancestor combobox node or null if no combobox parent exists
function comboboxGetParent(node) {
	// test if current node is a combobox
	if( isCombobox(node) ) {
		return(node);
	}

	if(node.parentNode == null) {
		return null; // Hit the top of the DOM
	}

	return (comboboxGetParent(node.parentNode));
}

// Recursively removes all selected attributes from combobox "node" options (not descending into nested comboboxes)
function comboboxSetNoneSelected(node) {
	optionNode = comboboxGetFirstOption(node);

	while(optionNode != null) {
		optionNode.setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "false");
		optionNode = comboboxGetNextOption(optionNode);
	}
}

// Sets the option at index "index" to be selected from combobox "node"
function comboboxSetSelectedOption(node, index) {
	optionNode = comboboxGetFirstOption(node);

	for(var i=0; i<index; i++) {
		optionNode = comboboxGetNextOption(optionNode);
	}

	if(optionNode == null) {
		return;
	}

	optionNode.setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
}

// Returns all options of a combobox in the order they appear in the DOM
function comboboxGetOptionArray(node) {
	var nodes = new Array();

	for(var i=0; i<node.childNodes.length; i++) {
		if( !isCombobox(node.childNodes[i]) ) { // Don't decend into nested checkboxes
			if( isOption(node.childNodes[i]) ) {
				nodes[nodes.length] = node.childNodes[i];
			}
			newNodes = comboboxGetOptionArray(node.childNodes[i]);
			if( newNodes.length != 0 ) {
				nodes.concat(newNodes);
			}
		}
	}
	return nodes;
}

// returns the first option node of the given combobox
function comboboxGetFirstOption(node) {
	for(var i=0; i<node.childNodes.length; i++) {
		if( !isCombobox(node.childNodes[i]) ) { // Don't decend into nested checkboxes
			if( isOption(node.childNodes[i]) ) {
				return(node.childNodes[i]);
			}
			option = comboboxGetFirstOption(node.childNodes[i]);
			if(option != null) {
				return option;
			}
		}
	}

	return null;
}

// returns the next option node given an option node or null if none exists
function comboboxGetNextOption(node) {
	var nodes = comboboxGetOptionArray(comboboxGetParent(node));

	for(var i=0; i<nodes.length; i++) {
		if(node == nodes[i]) {
			if( (i+1) < nodes.length ) {
				return nodes[i+1];
			} else {
				return null;
			}
		}
	}

	return null;
}

// returns the previous option node given an option node or null if none exists
function comboboxGetPreviousOption(node) {
	var nodes = comboboxGetOptionArray(comboboxGetParent(node));

	for(var i=0; i<nodes.length; i++) {
		if(node == nodes[i]) {
			if( (i-1) >= 0 ) {
				return nodes[i-1];
			} else {
				return null;
			}
		}
	}

	return null;
}

// FIXME: Implement a nextSiblingOption() to ease complexity when we're not doing anything too crazy with nested options

// Changes the current selected option in combobox "node" to the next option node
function comboboxSelectNextOption(node) {
	var nodes = comboboxGetOptionArray(node);

	for(var i=0; i<nodes.length; i++) {
		if( nodes[i].getAttributeNS("http://www.w3.org/2005/07/aaa", "selected") == "true" ) {
			if( (i+1) < nodes.length ) {
		 		nodes[i].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "false");
				nodes[i+1].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
			} else {
		 		nodes[i].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "false");
				nodes[0].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
			}
			return;
		}
	}
}

// Changes the selected option node to the previous option node 
function comboboxSelectPreviousOption(node) {
	var nodes = comboboxGetOptionArray(node);

	for(var i=0; i<nodes.length; i++) {
		if( nodes[i].getAttributeNS("http://www.w3.org/2005/07/aaa", "selected") == "true" ) {
			if( (i-1) >= 0 ) {
		 		nodes[i].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "false");
				nodes[i-1].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
			} else {
		 		nodes[i].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "false");
				nodes[nodes.length-1].setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
			}
			return;
		}
	}
}

function comboboxEvent(event) {
	// Consider combobox drop-down
	if( !isExpanded(event.currentTarget) ) {
		if( event.type == "click" || (event.type == "keydown" && (event.keyCode == event.DOM_VK_DOWN || event.keyCode == event.DOM_VK_UP)) ) {
			comboboxOpenDropDown(event.currentTarget);
		} 
		event.stopPropagation();
		return;
	}

	// Consider combobox close drop-down
	if( event.type == "blur" ) {
		// Don't care about blurring elements "in" the combobox
		if( event.target == event.currentTarget ) {
			if( isCombobox(event.target) ) {
				if(isExpanded(event.currentTarget)) { 
					comboboxCloseDropDown(event.currentTarget);
				}
			}
		}
		event.stopPropagation();
		return;
	}

	if( event.type == "click" ) {
		var option = optionGetParent(event.target);
		if( option != null ) {
			comboboxSetNoneSelected(event.currentTarget);
			option.setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
			comboboxCloseDropDown(event.currentTarget);
		}
		event.stopPropagation();
		return;
	}

	if( event.type == "mouseover" ) {
		var option = optionGetParent(event.target);
		if( option != null ) {
			comboboxSetNoneSelected(event.currentTarget);
			option.setAttributeNS("http://www.w3.org/2005/07/aaa", "selected", "true");
		}
		event.stopPropagation();
		return;
	}

	// Note: not checking for keyboard event handlers can get us into trouble here if this function is set as an event listener for something unconsidered above

	if( event.keyCode == event.DOM_VK_DOWN ) {
		comboboxSelectNextOption(event.currentTarget);
		event.stopPropagation();
		return;
	}

	if( event.keyCode == event.DOM_VK_UP ) {
		comboboxSelectPreviousOption(event.currentTarget);
		event.stopPropagation();
		return;
	}

	if( event.keyCode == event.DOM_VK_ENTER || event.keyCode == event.DOM_VK_RETURN ) {
		comboboxCloseDropDown(event.currentTarget);
		event.stopPropagation();
		return;
	}

}
]]>
</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;
}

ul.combobox {
	border: thin solid black;
	background-color: #ffffff;
	padding: .25em;
	margin: .25em;
	overflow: hidden;
}

ul.combobox[state|disabled="true"] {
	background-color: #eeeeee;
	color: #777777;
}

ul.comboboxClosed {
	min-height: 1em;
	max-height: 1em;
}

ul.comboboxOpen {
	min-height: 100%;
	max-height: 100%;
}

ul.comboboxOpen li.option {
	display: list-item;
}

ul.combobox li.option, ul.combobox li.separator {
	list-style: none;
	padding: 0;
	margin: 0;
}

ul.comboboxOpen li.option[state|selected="true"] {
	background-color: black;
	color: white;
}

/* default hide the options when closed */
ul.comboboxClosed li.option, ul.comboboxClosed li.separator {
	display: none;
}

/* Unhide selected option when closed */
ul.comboboxClosed li.option[state|selected="true"] {
	display: list-item;
}

li.separator:after {
	content: "--------";
}

</style>
</head>

<body>

<div id="content">

<h1 class="hide">Reference Implementation</h1>


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


<div id="test">

<div id="comboboxgroup1">
	<span id="lcombobox1" class="comboboxlabel">Combobox Widget</span>
	<ul id="combobox1" class="combobox comboboxClosed"
		x2:role="role:combobox" 
		state:describedby="dcombobox1" state:labeledby="lcombobox1" 
		tabindex="0"
		onkeydown="comboboxEvent(event)" onclick="comboboxEvent(event)" onmouseover="comboboxEvent(event)" onblur="comboboxEvent(event)">
	
		<li class="option" x2:role="role:option">Red</li>
		<li class="option" x2:role="role:option" state:selected="true">Green</li>
		<li class="separator"></li>
		<li class="option" x2:role="role:option">Blue</li>
	</ul>
	<p id="dcombobox1">A sample combobox widget.  Up/Down or a mouse click to open.</p>
</div>

</div>

</div>

</body>
</html>


