﻿/************************************************************************************

DRAIG TO BACH

Originally written by Chris Wood <chris@draig.co.uk>

0.1	CPW	11/2003		First version. Handles the Welsh alphabet and its accents.

0.2	CPW	12/2003		- Stopped passing "o" around everywhere! It's now a global
				variable used by all functions.

				- Slight rewrite of getPos and setPos to stop IE's
				moving the cursor to seemingly random positions in textareas.
				
				- Monitoring of a textarea's scroll position added for 
				Mozilla / Netscape.
				
				- Browser detection has been improved; now the script really
				will only run in IE6, Netscape 7.0.2 or Mozilla 1.3
				(onwards). Handled by the determineBrowser function.
				
				- handleEvents no longer requires the target object to be
				passed into the function. The target object is obtained
				from the event object.
				
				- handleEvents now explicity checks for Gecko rather than
				just relying on a simple "else".
				
				- processDiacritics now handles three letter character
				sequences better. Also now passes charSequence.length to
				the insertChar function, rather than a hardcoded length
				as before.
				
				- If you tried to replace some text (i.e. the user highlights
				one or more characters, then presses a key to replace those
				characters) then diacritics were not being processed. This
				has been fixed.
				
0.3 CPW 09/2007		Now has better compatibility with Firefox and Safari.
				

This script is Copyright ©2007 Draig Technology Ltd.

The script may be used for any purpose, whether it be personal, not-for-profit or
commercial, on the condition that this original comment header is included in the code.

Users are free to customise the script, however any customisations of benefit to the
wider user community should be submitted to chris@draig.co.uk.

If you do use the script on your web site, please drop us a line to let us know
your thoughts! We welcome your feedback. Thanks! / Diolch!

Draig Technology
Intec
Ffordd Y Parc
Parc Menai
Bangor
LL57 4FG
WALES

Tel: +44 (0)870 2200512
Fax: +44 (0)870 2200513


************************************************************************************/

// Detect the user's browser. This script is only compatible with Microsoft Internet
// Explorer 6, Netscape 7 and Mozilla browsers (using the Gecko engine from 2003 onwards).

var o;
var isIE = false;
var isGecko = false;
var pos=-1;
var currCode=-1;

var lastKey=-1;


determineBrowser();

/*------------------------------------------------------------------------------
Function:	determineBrowser
Purpose:	Checks for Internet Explorer 6 (or later) or browsers with a
		Gecko engine from 2003 onwards.
Parameter(s):	n/a
Returns:	n/a
-------------------------------------------------------------------------------*/
function determineBrowser()
	{
	var userAgent = navigator.userAgent.toLowerCase();
	var index;
	var version;
	
	index = userAgent.indexOf("msie");
	
	if (index > -1)
		{
		// This is a version of Internet Explorer
		version = userAgent.substr(index + 5, 1);
		
		if (version >= 6)
			{
			// It is version 6 or later.
			isIE = true;
			}
		}
	
	index = userAgent.indexOf("gecko/");
	
	if (index > -1)
		{
		// This is a Gecko-based browser
		if (userAgent.indexOf("safari") == -1)
			{
			version = userAgent.substr(index + 6, 4);

			if (version >= 2003)
				{
				// The engine is dated 2003 or later.
				isGecko = true;
				}
			}
		else
			{
			isGecko = true;
			}
		}
	}

/*------------------------------------------------------------------------------
Function:	processDiacritics
Purpose:	Does the hard work - replacing the character sequences with
		the Unicode characters.
Parameter(s):	n/a
Returns:	True - process the key event as normal.
		False - don't process the key event (because we've altered the
			content of the textbox by adding the unicode character).
-------------------------------------------------------------------------------*/
function processDiacritics()
	{
	var charSequence = "";

	// Get the current position of the cursor.
	pos = getPos();

	// Check the length of the form element value. It needs to be greater than one
	// if we're going to do any replacements.
	if (o.value.length > 0)
		{
		// Get the preceeding two characters already in the textbox and add on the key
		// that was just pressed. This gives us a three character sequence.
		charSequence = o.value.substring(pos - 2, pos) + String.fromCharCode(currCode);
		}
	
	// If the length of the form element value is zero (i.e. it's empty), then the key
	// just pressed is all we have to go on. No sequence = no processing to do!
	else
		{
		return true;
		}

	// Now we've got the character sequence, it's time to do something with it.
	// The insertChar function is called passing the form element, the Unicode character
	// value (in decimal) and the length of the character sequence.
	//
	// This could be built dynamically from a database for efficiency, only including the
	// character sequences requested by the user in his/her profile.
	
	
	// Loop using the charSequence, taking off the sequence's first character on each
	// iteration. Do this until the charSequence length is 1.
	while (charSequence.length > 1)
		{
		switch(charSequence)
			{
			case "w^":
				insertChar(373, charSequence.length);
				return false;
				break;

			case "W^":
				insertChar(372, charSequence.length);
				return false;
				break;

			case "o^":
				insertChar(244, charSequence.length);
				return false;
				break;

			case "O^":
				insertChar(212, charSequence.length);
				return false;
				break;

			case "Y^":
				insertChar(374, charSequence.length);
				return false;
				break;

			case "y^":
				insertChar(375, charSequence.length);
				return false;
				break;

			case "i..":
				insertChar(239, charSequence.length);
				return false;
				break;

			case "I..":
				insertChar(207, charSequence.length);
				return false;
				break;

			case "a^":
				insertChar(226, charSequence.length);
				return false;
				break;

			case "A^":
				insertChar(194, charSequence.length);
				return false;
				break;

			case "e^":
				insertChar(234, charSequence.length);
				return false;
				break;

			case "E^":
				insertChar(202, charSequence.length);
				return false;
				break;

			case "e..":
				insertChar(235, charSequence.length);
				return false;
				break;

			case "E..":
				insertChar(203, charSequence.length);
				return false;
				break;

			case "a..":
				insertChar(228, charSequence.length);
				return false;
				break;

			case "A..":
				insertChar(196, charSequence.length);
				return false;
				break;

			case "a/":
				insertChar(225, charSequence.length);
				return false;
				break;

			case "A/":
				insertChar(193, charSequence.length);
				return false;
				break;

			case "e/":
				insertChar(233, charSequence.length);
				return false;
				break;

			case "E/":
				insertChar(201, charSequence.length);
				return false;
				break;

			case "i^":
				insertChar(238, charSequence.length);
				return false;
				break;

			case "I^":
				insertChar(206, charSequence.length);
				return false;
				break;

			case "o..":
				insertChar(246, charSequence.length);
				return false;
				break;

			case "O..":
				insertChar(214, charSequence.length);
				return false;
				break;

			case "o/":
				insertChar(243, charSequence.length);
				return false;
				break;

			case "O/":
				insertChar(211, charSequence.length);
				return false;
				break;

			case "o\\": // Need to escape sequences ending in "\"
				insertChar(242, charSequence.length);
				return false;
				break;

			case "O\\":
				insertChar(210, charSequence.length);
				return false;
				break;

			case "a\\":
				insertChar(224, charSequence.length);
				return false;
				break;

			case "A\\":
				insertChar(192, charSequence.length);
				return false;
				break;

			case "i/":
				insertChar(237, charSequence.length);
				return false;
				break;

			case "I/":
				insertChar(205, charSequence.length);
				return false;
				break;

			case "u..":
				insertChar(252, charSequence.length);
				return false;
				break;

			case "U..":
				insertChar(220, charSequence.length);
				return false;
				break;

			case "i\\":
				insertChar(236, charSequence.length);
				return false;
				break;

			case "I\\":
				insertChar(204, charSequence.length);
				return false;
				break;

			case "e\\":
				insertChar(232, charSequence.length);
				return false;
				break;

			case "E\\":
				insertChar(200, charSequence.length);
				return false;
				break;

			case "u/":
				insertChar(250, charSequence.length);
				return false;
				break;
				
			case "U/":
				insertChar(218, charSequence.length);
				return false;
				break;

			case "u^":
				insertChar(251, charSequence.length);
				return false;
				break;

			case "U^":
				insertChar(219, charSequence.length);
				return false;
				break;

			case "y..":
				insertChar(255, charSequence.length);
				return false;
				break;

			case "u\\":
				insertChar(249, charSequence.length);
				return false;
				break;

			case "U\\":
				insertChar(217, charSequence.length);
				return false;
				break;

			case "y\\":
				insertChar(7923, charSequence.length);
				return false;
				break;

			case "Y\\":
				insertChar(7922, charSequence.length);
				return false;
				break;
				
			case "w/":
				insertChar(7811, charSequence.length);
				return false;
				break;

			case "W/":
				insertChar(7810, charSequence.length);
				return false;
				break;
				
			case "y/":
				insertChar(253, charSequence.length);
				return false;
				break;

			case "Y/":
				insertChar(221, charSequence.length);
				return false;
				break;
				
			case "w\\":
				insertChar(7809, charSequence.length);
				return false;
				break;

			case "W\\":
				insertChar(7808, charSequence.length);
				return false;
				break;
			}

		charSequence = charSequence.substring(1);
		}

	return true;
}

/*------------------------------------------------------------------------------
Function:	getPos
Purpose:	Gets the current cursor position. Uses a bit of trickery for
		Internet Explorer to work this out, but Netscape/Mozilla can
		use properties from the DOM2 Range Object.
Parameter(s):	n/a
Returns:	The position of the cursor.
-------------------------------------------------------------------------------*/
function getPos()
	{
	if (isIE) {
		 var saveText = o.value;
		 o.focus();
		 var range = document.selection.createRange();
		 
		 // Create a marker and insert it into the form element.
		 var weirdStr = String.fromCharCode(1);
		 range.text = weirdStr;
		 
		 // Now find the index of that marker. This is the current
		 // cursor position
		 var pos = o.value.indexOf(weirdStr);

		 // Get rid of the marker and return the position.
		 o.value = saveText;
		 setPos(pos);
		 
		 return pos;
		}
	 else
		{
		o.setSelectionRange;
		return o.selectionStart;
		}
	}

/*------------------------------------------------------------------------------
Function:	getLength
Purpose:	Gets the length of the selected text range.
Parameter(s):	n/a
Returns:	The selection's length
-------------------------------------------------------------------------------*/
function getLength()
	{
	if (isIE) {
		 var saveText = o.value;
		 o.focus();
		 var range = document.selection.createRange();
		 return range.text.length;
		}
	 else
		{
		o.setSelectionRange;
		return (o.selectionEnd - o.selectionStart);
		}
	}


/*------------------------------------------------------------------------------
Function:	setPos
Purpose:	Sets the cursor's position. Again, has to do this with a lot of
		trickery for Internet Explorer. Mozilla uses the standard DOM2
		Range object.
Parameter(s):	The position to set the cursor to.
Returns:	n/a
-------------------------------------------------------------------------------*/
function setPos(pos)
	{
	 if (isIE)
	 	{
		// IE counts CR & LF has characters in javascript indexOfs
		// but not in range positions. Easiest way to remedy this is to
		// find the line number minus 1 and subtract this from pos. The result should
		// be used in setting the cursor position number.

		// Select from the beginning to the passed cursor position
		var contentString = o.value.substring(0, pos);
		
		// Create an array by splitting the string on linefeeds (char code 13)
		var contentArray = contentString.split(String.fromCharCode(13));
		
		// The new position is..
		pos = pos - (contentArray.length - 1);
		
		// Set the position
		var rng=o.createTextRange();
		rng.moveStart("character",pos);
		rng.collapse();
		rng.select();			
		}
	else
		{
		o.setSelectionRange;
		o.selectionStart = pos;
		o.selectionEnd = pos;
		}
	}

/*------------------------------------------------------------------------------
Function:	insertChar
Purpose:	Gets rid of the character sequence and replaces it with the
		proper Unicode character. Could use the range object, however,
		they are implemented so differently between browsers that some
		good old "standard" string code is used instead.
Parameter(s):	The form element object, the Unicode character number (in decimal),
		the length of the character sequence.
Returns:	n/a
-------------------------------------------------------------------------------*/
function insertChar(mychar, length)
	{
	var firstHalf="";
	var secondHalf="";
	var scrollPos;

	// Get everything before the character sequence
	firstHalf = o.value.substring(0, (pos - (length - 1)));
	
	// Get everything after the character sequence
	secondHalf = o.value.substring(pos);

	// Get the position of the textarea's vertical scroll bar.
	// Not required for IE, but Netscape / Mozilla makes the
	// textarea jump to the top when you change its content
	// programmatically.
	scrollPos = o.scrollTop;

	// Alter the value of the form element
	o.value = firstHalf + String.fromCharCode(mychar) + secondHalf;

	// Set the position of the cursor accordingly (remember, the string just got shorter
	// so the cursor position will probably have to step back one or two places).
	setPos(pos + ((length - 2) * -1));

	// Reset the scroll position.
	o.scrollTop = scrollPos;
	
	}

/*------------------------------------------------------------------------------
Function:	processBackspace
Purpose:	This manages all the backspace key hits.
Parameter(s):	n/a
Returns:	True - process the key event as normal.
		False - don't process the key event (because we've altered the
			content of the textbox.
-------------------------------------------------------------------------------*/
function processBackspace()
	{
	var bsCode;

	// Is the user deleting a section of text rather than just the preceeding
	// character? If yes, then just go ahead and do it. Then finish.
	if (getLength() > 0) return true;
	
	// If not, let's get to work..
	
	// Get the cursor position.
	pos = getPos();
	
	// Get the Unicode character number (in decimal) of the character we're
	// deleting.
	bsCode = o.value.charCodeAt(pos - 1);

	// If it's one of our recognised Unicode characters, replace it with its
	// character sequence. (Maybe the user really wanted that sequence of
	// characters rather than the replacement character?)
	//
	// deleteChar takes care of the replacement. It takes the character sequence
	// as a parameter.
	switch(bsCode)
		{
		case 373:
			deleteChar("w^");
			return false;
			break;

		case 372:
			deleteChar("W^");
			return false;
			break;

		case 244:
			deleteChar("o^");
			return false;
			break;

		case 212:
			deleteChar("O^");
			return false;
			break;

		case 374:
			deleteChar("Y^");
			return false;
			break;

		case 375:
			deleteChar("y^");
			return false;
			break;

		case 239:
			deleteChar("i..");
			return false;
			break;

		case 207:
			deleteChar("I..");
			return false;
			break;

		case 226:
			deleteChar("a^");
			return false;
			break;

		case 194:
			deleteChar("A^");
			return false;
			break;

		case 234:
			deleteChar("e^");
			return false;
			break;

		case 202:
			deleteChar("E^");
			return false;
			break;

		case 235:
			deleteChar("e..");
			return false;
			break;

		case 203:
			deleteChar("E..");
			return false;
			break;

		case 228:
			deleteChar("a..");
			return false;
			break;

		case 196:
			deleteChar("A..");
			return false;
			break;

		case 225:
			deleteChar("a/");
			return false;
			break;

		case 193:
			deleteChar("A/");
			return false;
			break;

		case 233:
			deleteChar("e/");
			return false;
			break;

		case 201:
			deleteChar("E/");
			return false;
			break;

		case 238:
			deleteChar("i^");
			return false;
			break;

		case 206:
			deleteChar("I^");
			return false;
			break;

		case 246:
			deleteChar("o..");
			return false;
			break;

		case 214:
			deleteChar("O..");
			return false;
			break;

		case 243:
			deleteChar("o/");
			return false;
			break;

		case 211:
			deleteChar("O/");
			return false;
			break;

		case 242:
			deleteChar("o\\");
			return false;
			break;

		case 210:
			deleteChar("O\\");
			return false;
			break;

		case 224:
			deleteChar("a\\");
			return false;
			break;

		case 192:
			deleteChar("A\\");
			return false;
			break;

		case 237:
			deleteChar("i/");
			return false;
			break;

		case 205:
			deleteChar("I/");
			return false;
			break;

		case 252:
			deleteChar("u..");
			return false;
			break;

		case 220:
			deleteChar("U..");
			return false;
			break;

		case 236:
			deleteChar("i\\");
			return false;
			break;

		case 204:
			deleteChar("I\\");
			return false;
			break;

		case 232:
			deleteChar("e\\");
			return false;
			break;

		case 200:
			deleteChar("E\\");
			return false;
			break;

		case 251:
			deleteChar("u^");
			return false;
			break;

		case 219:
			deleteChar("U^");
			return false;
			break;

		case 255:
			deleteChar("y..");
			return false;
			break;

		case 249:
			deleteChar("u\\");
			return false;
			break;

		case 217:
			deleteChar("U\\");
			return false;
			break;

		case 7923:
			deleteChar("y\\");
			return false;
			break;

		case 7922:
			deleteChar("Y\\");
			return false;
			break;

		case 218:
			deleteChar("U/");
			return false;
			break;

		case 7810:
			deleteChar("W/");
			return false;
			break;

		case 7811:
			deleteChar("w/");
			return false;
			break;

		case 221:
			deleteChar("Y/");
			return false;
			break;

		case 250:
			deleteChar("u/");
			return false;
			break;

		case 253:
			deleteChar("y/");
			return false;
			break;

		case 7808:
			deleteChar("W\\");
			return false;
			break;

		case 7809:
			deleteChar("w\\");
			return false;
			break;

		}

	return true;
	}

/*------------------------------------------------------------------------------
Function:	deleteChar
Purpose:	Deletes a Unicode character and replaces it with the character
		sequence
Parameter(s):	The replacement character sequence.
Returns:	n/a
-------------------------------------------------------------------------------*/
function deleteChar(repString)
	{
	var firstHalf="";
	var secondHalf="";
	var scrollPos;

	// Get everything before the deleted character
	firstHalf = o.value.substring(0, (pos - 1));
	
	// Get everything after the selected character
	secondHalf = o.value.substring(pos);
	
	// Get the position of the textarea's vertical scroll bar.
	// Not required for IE, but Netscape / Mozilla makes the
	// textarea jump to the top when you change its content
	// programmatically.
	scrollPos = o.scrollTop;
	
	// Re-do the form element's value using the character sequence
	o.value = firstHalf + repString + secondHalf;

	// Set the cursor position. (Remember, the string probably just got longer so the
	// cursor may need to be a couple of places to the right.)
	setPos(pos + (repString.length -1));
	
	o.scrollTop = scrollPos;
	}

/*------------------------------------------------------------------------------
Function:	handleEvents
Purpose:	Handles the events fired by the browsers. Unfortunately, the
		incompatibilities between web browsers have made this function
		necessary. Mozilla/Netscape handles backspaces on the keypress
		event fine, but unreliably on the keydown event. Internet
		Explorer, however, doesn't handle backspaces on the keypress
		event, but does on the keydown event. This function calls the
		appropriate functions depending on the browser and the event
		fired.
Parameter(s):	The event.
Returns:	True - process the key event as normal.
		False - don't process the key event (because we've altered the
			content of the textbox.
-------------------------------------------------------------------------------*/
function handleEvents(event)
	{
	var selectionLength;
	
	// Internet Explorer
	if (isIE)
		{
		o = event.srcElement;
		currCode = event.keyCode;

		// Check to see if this is a backspace and a keydown event.
		if ((currCode == 8) && (event.type == "keydown"))
			{
			return processBackspace();
			}
		
		// Otherwise it's a keypress event.
		else if (event.type == "keypress")
			{
			selectionLength = getLength();
			
			// If you are trying to replace some text (i.e. the user has highlighted one
			// or more characters, then pressed a key to replace those characters with) then
			// this removes the highlighted text first, before handling diacritics.
			if (selectionLength > 0)
				{
				pos = getPos();
				o.value = o.value.substring(0, pos) + o.value.substring(pos + selectionLength);
				setPos(pos);
				}

			return processDiacritics();
			}

		}
	
	// Netscape & Mozilla
	else if (isGecko)
		{
		var retVal;
		o = event.target;
		currCode = event.which;

		// Check to see if this is a backspace and a keypress event.
		if ((currCode == 8) && (event.type == "keypress"))
			{
			//return processBackspace();
			if (!processBackspace()) {
				event.preventDefault();
			}
				
			return;
			}
		
		// Otherwise, it's an ordinary keypress event.
		else if (event.type == "keypress")
			{
			selectionLength = getLength();

			// If you are trying to replace some text (i.e. the user has highlighted one
			// or more characters, then pressed a key to replace those characters with) then
			// this removes the highlighted text first, before handling diacritics.

			if (selectionLength > 0 && currCode != 0 && !event.altKey && !event.ctrlKey && !event.metaKey)
				{
				pos = getPos();
				o.value = o.value.substring(0, pos) + o.value.substring(pos + selectionLength);
				setPos(pos);
				}
			
			if (!processDiacritics()) {
				event.preventDefault();
			}
			
			return;
			}
		}
	}

	function attachToFields() {
	    var inputs = document.body.getElementsByTagName("input");

	    for (var i = 0; i < inputs.length; i++) {
	        if (inputs[i].type == "text") {
	            if (inputs[i].addEventListener) {
	                inputs[i].addEventListener("keydown", handleEvents, false);
	                inputs[i].addEventListener("keypress", handleEvents, false);
	            }
	            else if (inputs[i].attachEvent) {
	                inputs[i].attachEvent("onkeydown", handleEvents);
	                inputs[i].attachEvent("onkeypress", handleEvents);
	            }
	        }
	    }

	    var textareas = document.body.getElementsByTagName("textarea");

	    for (var i = 0; i < textareas.length; i++) {
	        if (inputs[i].addEventListener) {
	            inputs[i].addEventListener("keydown", handleEvents, false);
	            inputs[i].addEventListener("keypress", handleEvents, false);
	        }
	        else if (inputs[i].attachEvent) {
	            inputs[i].attachEvent("onkeydown", handleEvents);
	            inputs[i].attachEvent("onkeypress", handleEvents);
	        }
	    }
}