<?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 the value of the state of element, assuming default state namespace
function getState(element, state) {
	return element.getAttributeNS("http://www.w3.org/2005/07/aaa", state);
}

// Sets state to value on an element, assuming default state namespace
function setState(element, state, value) {
	element.setAttributeNS("http://www.w3.org/2005/07/aaa", state, value);
}

// returns true if given element is a slider
function isSlider(element) {
	// Attempt to match last part of string because of unrecognized-namespace-in-value issues
	if( node.getAttributeNS("http://www.w3.org/TR/xhtml2", "role").search(/slider$/) > -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;
}

// 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);
}

// returns an array of DOM elements that match the given role string (via recursing the DOM tree)
// returns an empty array if no matching elements are found
function getAllRoleElements(roleString) {
	var recurseDOM = function(roleStringRegExp, nextElement, matchingArray) {
		for(var i=0; i<nextElement.childNodes.length; i++) {
			if( isElement(nextElement.childNodes[i]) ) {
				if(roleStringRegExp.test(nextElement.childNodes[i].getAttributeNS("http://www.w3.org/TR/xhtml2", "role")) ) {
					matchingArray.push(nextElement.childNodes[i]);
				}
				matchingArray = recurseDOM(roleStringRegExp, nextElement.childNodes[i], matchingArray);				
			}
		}
		return matchingArray;
	}
	
	// recurse and return the results
	return recurseDOM(new RegExp(roleString+"$"), document.documentElement, new Array());
}

/* Slider specific implementation functionality */

var SLIDE_DATA = "||";
var SLIDE_FILLER = "-";

/* Used to track drag states */
var slider_isDragging = false;  // If the drag metaphor is currently active (by a mousedown on a slide)

// returns the element that is being used as a slide, given a slider
function getSlide(sliderElement) {
	// For simplicity, a slide is the only element whose first child contains SLIDE_DATA
	for(var i=0; i<sliderElement.childNodes.length; i++) {
		if( isElement(sliderElement.childNodes[i]) ) {
			if( isTextNode(sliderElement.childNodes[i].firstChild)) {
				if( sliderElement.childNodes[i].firstChild.data == SLIDE_DATA ) {
					return(sliderElement.childNodes[i]);
				}
			}
		}
	}
	return null;
}

// returns the element that is being used as a slider, given a slide
function getSlider(slideElement) {
	// For simplicity, a slider is the parent of a slide
	return slideElement.parentNode;
}

// Increments both the visual slider position and the slider value state given a 
// ... slider element and an integer increment inc.  Returns true on success and 
// ... false if increment moves slide out of allowed range
function incrementSlider(slider, inc) {
	var oldValue = getSliderValue(slider);
	var newValue = oldValue + inc;
	if( newValue <= getSliderMax(slider) && newValue >= getSliderMin(slider) ) {
		setSliderValue(slider, oldValue + inc);
		return true;
	} else {
		return false;
	}
}

// returns current value of slider in units of position from slider start
function getPosition(slider, value) {
	// In this example, each integer between the min and max is a valid position
	// So the integer distance from the min is the same as the position
	// ... if it's not over the max; we'll not check for that.
	return Math.floor(Math.abs(value-getSliderMin(slider)));
}

// Move the slide in the given slider to the given position 
function moveSlideTo(slider, value) {
	var slide = getSlide(slider);
	slider.removeChild(slide);
	insertSlideIn(slider, value);
}

// Generates and inserts a slide in the given slider at given value
function insertSlideIn(slider, value) {
	var position = getPosition(slider, value);
	slider.insertBefore(getNewSlideElement(), slider.childNodes[position]);
}

// Returns the maximum value state of the slider widget
function getSliderMax(slider) {
	return(parseFloat(getState(slider, "valuemax")));
}

// Returns the minimum value state of the slider widget
function getSliderMin(slider) {
	return(parseFloat(getState(slider, "valuemin")));
}

// Returns the value of the slider widget
function getSliderValue(slider) {
	return(parseFloat(getState(slider, "valuenow")));
}

// Returns the value of the slider widget
function setSliderValue(slider, value) {
	setState(slider, "valuenow", value)
	moveSlideTo(slider, value);
}

function getNewSlideElement() {
	var slide = document.createElement("span");
	addStyleClass(slide, "slide"); 
	slide.appendChild(document.createTextNode(SLIDE_DATA));
	slide.addEventListener("mousedown", sliderStartDragEvent, false);			
	slide.addEventListener("mouseup", sliderChildStopDragEvent, false);		// FIXME: can move this to slider
	slide.addEventListener("mouseout", sliderMaskMouseoutEvent, false);	
	return slide;
}

function getNewSliderFillerElement() {
	var slide = document.createElement("span");
	addStyleClass(slide, "sliderfiller"); 
	slide.appendChild(document.createTextNode(SLIDE_FILLER));
	slide.addEventListener("click", sliderMouseIncrementEvent, false);
	slide.addEventListener("mouseover", sliderDragEvent, false);	
	slide.addEventListener("mouseup", sliderChildStopDragEvent, false);	
	slide.addEventListener("mouseout", sliderMaskMouseoutEvent, false);			
	return slide;
}

// For all things with slider roles, inserts appropriate markup.
function insertSliders() {
	var sliders = getAllRoleElements("slider");
	for(var i=0; i<sliders.length; i++) {
		// Get number of slide places (range in this case)
		var max = getSliderMax(sliders[i]);
		var min = getSliderMin(sliders[i]);
		var range = Math.floor(Math.abs(max - min));

		// generate the appropriate number of slideelements and insert them
		for(var j=0; j<range; j++) {
			sliders[i].appendChild(getNewSliderFillerElement());
		}
		insertSlideIn(sliders[i], getSliderValue(sliders[i]));
	}
}

function onloadHandler(event) {
	insertSliders();
}

// Handles keyboard interaction
function sliderKeyEvent(event) {
	// If left, decrement if possible
	// If right, increment if possible
	// If home, go to position 0
	// If end, go to position valuemax
	
	switch(event.keyCode) {
		case event.DOM_VK_RIGHT: incrementSlider(event.target,1); break;
		case event.DOM_VK_LEFT: incrementSlider(event.target,-1); break;
		case event.DOM_VK_HOME: setSliderValue(event.target, getSliderMin(event.target)); break;
		case event.DOM_VK_END: setSliderValue(event.target, getSliderMax(event.target)); break;
	}
}

// Ends the Drag state if we go outside the slider
function sliderStartDragEvent(event) {
	// Only activate drag on left click
	if(event.button != 0) {
		return;
	}
	
	addStyleClass(event.target.parentNode, "slideractive");
	
	event.target.parentNode.drag = true;
	event.preventDefault();	
}

// Ends the Drag state when event happens inside slider
function sliderChildStopDragEvent(event) {
	// Only stop drag for mouseup when it was the left button
	if(event.type == "mouseup" && event.button != 0) {
		return;
	}

	removeStyleClass(event.target.parentNode, "slideractive");

	// end drag
	event.target.parentNode.drag = false;	
}

// Ends the drag state when event happens on slider
function sliderStopDragEvent(event) {
	removeStyleClass(event.target, "slideractive");

	// end drag
	event.target.drag = false;	
}

// returns true if given element filler (in a slider) is before the slide, false if otherwise
function isBeforeSlide(filler) {
	var slider = filler.parentNode;
	var slide = getSlide(slider);
	
	for(var i=0; i<slider.childNodes.length; i++) {
		if(slider.childNodes[i] == slide) {
			return false;
		} else if(slider.childNodes[i] == filler) {
			return true;
		}
	}
	return false;	
}

function sliderMouseIncrementEvent(event) {
	// if this is before the slider, decrement
	// if this is after the slider, increment
	
	if(isBeforeSlide(event.target)) {
		incrementSlider(event.target.parentNode,-1);	
	} else {
		incrementSlider(event.target.parentNode,1);	
	}
	event.preventDefault();
}

// returns the value of the given element in a slider
function sliderValueOf(element) {
	var slider = element.parentNode;
	for(var i=0; i<slider.childNodes.length; i++) {
		if(slider.childNodes[i] == element) {
			return getSliderMin(slider) + i;
		}
	}
}

// Handles mouse clicks and drag movements 
function sliderDragEvent(event) {
	// if mouseclick, move the slider in this direction
	// if in drag mode and
	// ... mouseup: end drag mode
	// ... mouseover: 

	if(typeof event.target.parentNode.drag == "undefined") {
		return;
	} else if(event.target.parentNode.drag == false) {
		return;
	}
	
	// we are dragging, so move the slider to the targeted event
	setSliderValue(event.target.parentNode, sliderValueOf(event.target));
}

// Prevents mouseout events on sliderfiller elements from stopping the drag
// ... but doesn't prevent those on the slider itself from doing so
function sliderMaskMouseoutEvent(event) {
	if(event.target == event.currentTarget) {
		event.stopPropagation();
		event.preventDefault();
	}
}

]]>
</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;
}

.slider {
	padding: .1em; /* slider needs some area to register the mouseout event to stop drag */
	border: thin solid black;
}

.slideractive {
	background-color: #ffdddd;
}

.slider:focus {
	background-color: #ffdddd;
}

.sliderfiller {
}

li {
	margin: .5em;
}
</style>
</head>

<body onload="onloadHandler(event)">

<div id="content">

<h1 class="hide">Reference Implementation</h1>


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


<div id="test">

<ul title="two slider test examples">
	<li>
		<span id="slider1" class="slider" 
			x2:role="role:slider" 
			state:valuenow="0" state:valuemin="-25" state:valuemax="25"	
			state:describedby="dslider1" state:labeledby="lslider1" 
			tabindex="0" 
			onkeydown="sliderKeyEvent(event)" onmouseout="sliderStopDragEvent(event)" onblur="sliderStopDragEvent(event)" 
			onmousemove="event.stopPropagation(); event.preventDefault()"/>
		<span id="lslider1"></span>
	</li>
	<li>
		<span id="slider2" class="slider" 
			x2:role="role:slider" 
			state:valuenow="0" state:valuemin="0" state:valuemax="5"	
			state:describedby="dslider2" state:labeledby="lslider2" 
			tabindex="0" 
			onkeydown="sliderKeyEvent(event)" onmouseout="sliderStopDragEvent(event)" onblur="sliderStopDragEvent(event)" />
		<span id="lslider2"></span>
	</li>
</ul>

</div>

</div>

</body>
</html>


