<?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[

/*
Cursor behavior:
	This is a simple cursor.  It's a span element that is a child of the textbox.  It 
can be at the first, last, or intermediate positions (splitting text nodes).  Pressing 
a character key will cause a new child text node to be inserted before the cursor.
*/

// Constructor for textfield objects, sets up an object for the corresponding XHTML element with a textfield role, 
// ... that will accept a maximum of maxChars from user input.
function Textfield(node, maxChars) {
	this.node = node;
	this.maxChars = maxChars;
	this.setCursor(0);
	this.setNotDisabled();
}

// Creates a new object for the textfield "id" defined in the document, with a maximum character length of maxCharLen
Textfield.setup = function(id, maxCharLen) {
	var node = document.getElementById(id);
	var newTextField = new Textfield(node, maxCharLen)
	node.addEventListener("keypress", function(event) {return (newTextField.textfieldEvent(event))}, false);
	node.addEventListener("keydown", function(event) {Textfield.preventBrowserBehavior(event);}, false); // Discard keydown events to prevent browser from reacting
	node.addEventListener("keyup", function(event) {Textfield.preventBrowserBehavior(event);}, false); // Discard keydown events to prevent browser from reacting
}

// Prevents default browser behavior on most input keys, allows default behavior on tab
Textfield.preventBrowserBehavior = function(event) {
	switch(event.keyCode) {
		case event.DOM_VK_TAB: // fall through
		return; // don't affect the default browser behavior
	}
	event.preventDefault();
}

Textfield.prototype = {
	node : null, // the DOM node that is acting as the textfield
	cursorPos : 0, // Lowest cursor position is 0
	maxChars : 256, // maximum number of characters allowed

	// Returns true if the disabled state is true in the DOM, else false 
	isDisabled : function() {
		if (this.node.getAttributeNS("http://www.w3.org/2005/07/aaa", "disabled") == "true") {
			return true;
		}
		return false;
	},

	// Sets the disabled state in the DOM
	setDisabled : function() {
		this.node.setAttributeNS("http://www.w3.org/2005/07/aaa", "disabled", "true");
	},

	// Sets the accessible disabled state in the DOM
	setNotDisabled : function() {
		this.node.setAttributeNS("http://www.w3.org/2005/07/aaa", "disabled", "false");
	},

	// Inserts character c at current cursor location
	insertChar : function(c) {
		// Check if at maximum character length
		if( (this.getText(this.node).length + 1) > this.maxChars ) {
			return;  
		}

		var cursorIdx = this.cursorIndex();
		if( cursorIdx == 0 ) {
			// at beginning, must create new text node
			this.node.insertBefore(document.createTextNode(c), this.node.childNodes[cursorIdx]);
		} else {
			// can append to text node before us
			this.node.childNodes[cursorIdx - 1].appendData(c);
		}
	},

	// Deletes character at current cursor location
	deleteChar : function() {
		var cursorIdx = this.cursorIndex();

		// delete the first character from the next text node string
		if( (this.node.childNodes.length - 1) > cursorIdx ) {
			this.node.childNodes[cursorIdx + 1].deleteData(0,1);
		}
	},
	
	// Deletes character at previous cursor position
	backspace : function() {
		var cursorIdx = this.cursorIndex();

		// delete the last character from the previous text node string
		if( cursorIdx > 0 ) { 
			if( this.node.childNodes[cursorIdx - 1].length > 0 ) {
				this.node.childNodes[cursorIdx - 1].deleteData((this.node.childNodes[cursorIdx-1].length - 1), 1);
			}
		}
	},

	// Moves the cursor "back" (abstracted for different text entry directions)
	cursorPrevious : function() {
		var cursorIdx = this.cursorIndex();

		if( cursorIdx > 0 ) {  // If there is a previous node 
			if( this.node.childNodes[cursorIdx - 1].length > 1 ) { 
				this.node.childNodes[cursorIdx - 1].splitText(this.node.childNodes[cursorIdx - 1].length - 1);
				this.node.insertBefore(this.node.childNodes[cursorIdx+1],this.node.childNodes[cursorIdx]); // Move cursor before the newly created split
			} else {
				// The previous node is only one character, set cursor at beginning
				this.node.insertBefore(this.node.childNodes[cursorIdx],this.node.childNodes[0]);  // Move cursor to beginning
			}
		}

		this.node.normalize(); // rejoin split textnodes
	},

	// Moves cursor "forward" (abstracted for different text entry directions)
	cursorNext : function() {
		var cursorIdx = this.cursorIndex();
		
		if( (this.node.childNodes.length - 1) > cursorIdx ) {  // If there is a next node
			if( this.node.childNodes[cursorIdx + 1].length > 1 ) { 
				this.node.childNodes[cursorIdx + 1].splitText(1);
				this.node.insertBefore(this.node.childNodes[cursorIdx],this.node.childNodes[cursorIdx+2]);
			} else {
				// If the next node is only one character, append the cursor onto the end of the textfield
				this.node.appendChild(this.node.childNodes[cursorIdx]);
			}
		}

		this.node.normalize();
	},

	// Returns the index into node.childNodes[] where the cursor node lies
	cursorIndex : function() {
		for(var i=0; i<this.node.childNodes.length; i++) {
			if( this.node.childNodes[i].nodeType == this.node.ELEMENT_NODE ) {
				if( this.node.childNodes[i].getAttribute("class") == "cursor" ) {
					return i;
				}
			}
		}
		return -1; // No cursor found
	},

	// Moves cursor to position 0
	home: function() {
		this.node.insertBefore(this.node.childNodes[this.cursorIndex()],this.node.childNodes[0]);
		this.node.normalize();
	},

	// Moves cursor to end of the textfield data
	end: function() {
		this.node.appendChild(this.node.childNodes[this.cursorIndex()]);
		this.node.normalize();
	},

	// Moves the cursor to a position in the textfield contents
	setCursor : function(pos) {
		if( this.cursorIndex() == -1 ) {
			// Create cursor element
			cursor = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
			cursor.setAttribute("class","cursor");
		} else {
			cursor = this.node.removeChild(this.node.childNodes[this.cursorIndex()]);
		}

		this.node.normalize();

		if( pos == 0 ) {
			// Cursor goes at the beginning
			this.node.insertBefore(cursor, this.node.firstChild);
		} else {
			for(var i=0; i<this.node.childNodes.length; i++) {
				if( this.node.childNodes[i].nodeType == this.node.TEXT_NODE ) {
					if( pos > this.node.childNodes[i].length ) {
						// Position is longer than number of characters, cursor goes to the end
						this.node.appendChild(cursor);
						break;
					} else {
						// Split text at cursor location 
						var newTextNode = this.node.childNodes[i].splitText(pos);
						this.node.insertBefore(cursor, newTextNode);
						break;
					}
				}
			}
		}

		this.node.normalize();
	},

	// Returns string of textfield contents or the empty string if no contents
	// Note, this will return the concatenation of all text nodes (nested, sibling, etc.) beneath the given node 
	getText : function(node) { 
		if(node == undefined) {
			node = this.node;
		}

		var text = "";
		if(node.hasChildNodes()) {
			for(var i=0; i<node.childNodes.length; i++) {
				if(node.childNodes[i].nodeType == node.TEXT_NODE) {
					text += node.childNodes[i].nodeValue;
				} else {
					text += this.getText(node.childNodes[i]); // Recurse
				}
			}
		}
		return text;
	},

	// event handler for user-triggered events
	textfieldEvent : function(event) {
		// FIXME: All the keyboard-dependant stuff is in DOM3?
		// ... Using Firefox specific stuff, now.

		if( !(event.charCode == 0) ) {
			this.insertChar(String.fromCharCode(event.charCode));
		} else {
			switch(event.keyCode) {
				case event.DOM_VK_BACK_SPACE: this.backspace(); break;
				case event.DOM_VK_DELETE: this.deleteChar(); break;
				case event.DOM_VK_RIGHT: this.cursorNext(); break;
				case event.DOM_VK_LEFT: this.cursorPrevious(); break;
				case event.DOM_VK_HOME: this.home(); break;
				case event.DOM_VK_END: this.end(); break;
			}
			Textfield.preventBrowserBehavior(event);
		}
	},
}

// Called onload to create Textfield (controller) objects for the textfield (model/view) in the DOM
function setupTextfields() {
	Textfield.setup("textfield1", 20);
	Textfield.setup("textfield2", 20);
}

function onloadHandler(event) {
	setupTextfields();
}

]]>
</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;
}

.textfield {
	border: thin solid black;
	background-color: #ffffff;
	padding: .25em;
	margin: .25em;
	min-height: 1em;
	max-height: 1em;
	overflow: hidden;
	font-weight: bold;

	white-space: pre; /* CSS2, which doesn't work, currently */
	white-space: -moz-pre-wrap; /* Mozilla */
}

.textfield:focus {
	background-color: #ffeeee;
}

.textfield[state|disabled="true"] {
	background-color: #eeeeee;
	color: #777777;
}

.cursor {
	border-left: thin solid black;
	padding: 0;
	margin: 0;
}


.length_lastName {
	min-width: 20em;
	max-width: 20em;
}

</style>
</head>

<body onload="onloadHandler(event)">

<div id="content">

<h1 class="hide">Reference Implementation</h1>


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


<div id="test">

<div id="textfieldset1">
	<span id="dtextfield1">A sample textfield widget with 20 characters max.</span>
	<span id="ltextfield1" class="textfield_label">Textfield Widget 1</span>
	<div id="textfield1" class="textfield length_lastName" 
		x2:role="role:textfield" 
		state:disabled = "true"
		state:describedby="dtextfield1" state:labeledby="ltextfield1" 
		tabindex="0" />
</div>

<div id="textfieldset2">
	<span id="dtextfield2">A sample textfield widget with initial text.</span>
	<span id="ltextfield2" class="textfield_label">Textfield Widget 2</span>
	<div id="textfield2" class="textfield length_lastName" 
		x2:role="role:textfield" 
		state:disabled = "true"
		state:describedby="dtextfield2" state:labeledby="ltextfield2" 
		tabindex="0" >Initial text</div>
</div>

</div>

</div>

</body>
</html>


