/**
 * DET JavaScript Library
 * Version: 1.0.1
 * Author: Chris Kasprowicz
 *
 * Requires: 
 * 		- jQuery > 1.2.6
 * 		- YUI Calendar 2.6.0
 *
 * History:
 *		1.0.0 - Initial Release.
 *		1.0.1 - Added DET.tale.ui.Lookup.
 *			  - Added DET.tale.ui.AjaxLookup
 *			  - Added DET.tale.ui.EmailLookup
 */

/**
 * Creates the specified namespace.
 * 
 * @param name - the namespace to create.
 * @return The created namespace object.
 */
function namespace(name)
{
	var ns = name.split(".");
	var p = window;
	for (var i = 0; i < ns.length; i++)
	{
		p = p[ns[i]] = p[ns[i]] || {};
	}
	return p;
}

namespace("DET");
namespace("DET.tale");

/**
 * Extends standard functionality of the JavaScript Array object.
 * Used to find the index of a certain value in an array.
 *
 * @param value - the value to find in the array.
 * @return The index of the object in the array or -1 if the object
 * does not exist in the array.
 */ 
Array.prototype.indexOf = function(value)
{
	var result = -1;
	for (var i = 0; i < this.length; i = i+1)
	{
		if (this[i] == value)
		{
			result = i;
			break;
		}
	}
	return result;
};

/**
 * 
 * @param obj
 * @param index
 * @return
 */
Array.prototype.insert = function(obj, index)
{
	this.splice(index, 0, obj);
};

/**
 * 
 * @param index
 * @return
 */
Array.prototype.remove = function(index)
{
	this.splice(index, 1);
};


/**
 * Extends the standard functionality of the JavaScript String object.
 * Used to find whether a string starts with a certain prefix.
 *
 * @param str - the prefix to check against the string.
 * @return true if the string starts with the prefix, false otherwise.
 */
String.prototype.startsWith = function(str)
{
	return this.substring(0, str.length) == str;
};

/**
 * 
 * @param str
 * @return
 */
String.prototype.startsWithIgnoreCase = function(str)
{
	return this.toLowerCase().startsWith(str.toLowerCase());
};


/**
 * DET Utils Package.
 */
namespace("DET.tale.util");

/**
 * 
 */
DET.tale.util.BrowserDetails = function()
{      
	var userAgent = navigator.userAgent;

	if (userAgent.indexOf("Opera") != -1) 
	{
		this.parseOpera(userAgent);
	} 
	else if (userAgent.indexOf("MSIE") != -1) 
	{
		this.parseIE(userAgent);
	} 
	else 
	{
		this.parseOther(userAgent);
	}
};

/**
 * 
 * @param uA - the User-Agent string.
 * @return void.
 */
DET.tale.util.BrowserDetails.prototype.parseOpera = function(uA)
{
	this.browser = "Opera";
	this.browserVersion = uA.substring(uA.indexOf("/") + 1, uA.indexOf(" "));
	 
	var lb = uA.indexOf(" (");
	var rb = uA.indexOf(") ");
	var os = jQuery.trim(uA.substring(lb + 2, rb));
	var osT = os.split("; ");
	 
	this.platform = "Unknown";
	this.operatingSystem = jQuery.trim(this.getOS(osT[0]));
	this.security = this.getSecurity(osT[1]);
	this.language = osT[2];
	 
	var suffix = uA.substring(rb + 2);
	this.engine = suffix.substring(0, suffix.indexOf("/"));
	this.engineVersion = suffix.substring(suffix.indexOf("/") + 1);
};

/**
 * 
 * @param uA - the User-Agent string.
 * @return void.
 */
DET.tale.util.BrowserDetails.prototype.parseIE = function(uA)
{
	this.browser = "Internet Explorer";
	var lb = uA.indexOf(" (");
	var rb = uA.indexOf(")");
	var os = jQuery.trim(uA.substring(lb + 2, rb));
	var osT = os.split("; ");
	
	this.browserVersion = osT[1].replace("MSIE ", "");
	this.operatingSystem = jQuery.trim(this.getOS(osT[2]));
	
	if (osT.length > 5)
	{
		var eng = osT[5].split("/");
		this.engine = eng[0];
		this.engineVersion = eng[1];
	}
};

/**
 * 
 * @param uA - the User-Agent string.
 * @return void.
 */
DET.tale.util.BrowserDetails.prototype.parseOther = function(uA)
{
 if (!uA)
 {
   uA = navigator.userAgent;
 }

 var lb = uA.indexOf(" (");
 var rb = uA.indexOf(") ");
 var os = jQuery.trim(uA.substring(lb + 2, rb));
 var osT = os.split("; ");
 
 this.platform = osT[0];
 this.security = this.getSecurity(osT[1]);
 this.operatingSystem = jQuery.trim(this.getOS(osT[2]));
 this.language = osT[3]; // TODO Add interpretation
 
 var suffix = uA.substring(rb + 2);
 var index = suffix.indexOf(" ");
 var eng = suffix.substring(0, index).split("/");
 var bro = suffix.substring(index + 1);
 
 this.engine = eng[0];
 this.engineVersion = eng[1];
 
 this.browser = this.getBrowser(bro);
 this.browserVersion = this.getBrowserVersion(bro, this.browser);
};

/**
 * 
 * @param str
 * @return
 */
DET.tale.util.BrowserDetails.prototype.getSecurity = function(str)
{
 var _s = new DET.tale.util.Map();
 _s.put("N", "No Security");
 _s.put("U", "Strong Security");
 _s.put("I", "Weak Security"); 
 var _str = jQuery.trim(str);

 var result = "Unknown";
 if (_s.containsKey(_str))
 {
   result = _s.get(_str);
 }
 return result;
};

/**
 * 
 * @param str
 * @return
 */
DET.tale.util.BrowserDetails.prototype.getOS = function(str)
{
 var _os = new DET.tale.util.Map();
 _os.put("Windows NT 6.1", "Windows 7");
 _os.put("Windows NT 6.0", "Windows Vista");
 _os.put("Windows NT 5.1", "Windows XP");
 _os.put("Windows NT 5.0", "Windows 2000");
 // TODO Add others here.
 var _str = jQuery.trim(str);

 var result = "Unknown";
 if (_os.containsKey(_str))
 {
   result = _os.get(_str);
 }
 return result;
};

/**
 * 
 * @param str
 * @return
 */
DET.tale.util.BrowserDetails.prototype.getBrowser = function(str)
{
 var _b = new Array();
 _b[0] = "Chrome";
 _b[1] = "Safari";
 _b[2] = "Opera";
 _b[3] = "Firefox";
 _b[4] = "Internet Explorer";
 
 var result = "Unknown";
 for (var i = 0; i < _b.length; i++)
 {
   if (str.indexOf(_b[i]) != -1)
   {
     result = _b[i];
     break;
   }
 }
 return result;
};

/**
 * 
 * @param str
 * @param browser
 * @return
 */
DET.tale.util.BrowserDetails.prototype.getBrowserVersion = function(str, browser)
{   
	var result = "Unknown";
	var substr;
	var b = (browser) ? browser : this.getBrowser(str);
	if (b == "Chrome")
	{
		substr = str.substring(str.indexOf("Chrome"));
		result = substr.substring(substr.indexOf("/") + 1, ((substr.indexOf(" ") != -1) ? 
		substr.indexOf(" ") : substr.length));
	}
	else if (b == "Safari")
	{
		substr = str.substring(str.indexOf("Version"));
		result = substr.substring(substr.indexOf("/") + 1, ((substr.indexOf(" ") != -1) ? 
				substr.indexOf(" ") : substr.length));
	}
	else if (b == "Opera")
	{
	}
	else if (b == "Firefox")
	{
		substr = str.substring(str.indexOf("Firefox"));
		result = str.substring(substr.indexOf("/") + 1, ((substr.indexOf(" ") != -1) ? 
				substr.indexOf(" ") : substr.length));
	}
	return result;
};

/**
 * 
 */
DET.tale.util.Dimension = DET.tale.util.Size = function(x, y)
{
	this.x = x;
	this.y = y;
	this.width = x;
	this.height = y;
};

/**
 * Map object allows storing a value against a key.
 */
DET.tale.util.Map = function()
{
  this.keys = new Array();
  this.values = new Array();
};

DET.tale.util.Map.prototype.containsKey = function(key)
{
  return this.keys.indexOf(key) != -1;
};

DET.tale.util.Map.prototype.get = function(key)
{
  return this.values[this.keys.indexOf(key)];
};

DET.tale.util.Map.prototype.indexOf = function(key)
{
  return this.keys.indexOf(key);
};

DET.tale.util.Map.prototype.put = function(key, value)
{
  this.keys.push(key);
  this.values.push(value);
};

DET.tale.util.Map.prototype.remove = function(key)
{
  var index = this.indexOf(key);
  var result = this.values[index];
  this.keys.splice(index, 1);
  this.values.splice(index, 1);
  return result;
};

DET.tale.util.Map.prototype.size = function()
{
  return this.keys.length;
};


/**
 * Creates a new YUI calendar to represent a text input field.
 * 
 * The following files will need to be included for the calendar to function correctly:
 *  
 * &lt;link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.6.0/build/calendar/assets/skins/sam/calendar.css"/&gt; 
 * &lt;script type="text/javascript" src="http://yui.yahooapis.com/2.6.0/build/yahoo-dom-event/yahoo-dom-event.js"&gt;&lt;/script&gt; 
 * &lt;script type="text/javascript" src="http://yui.yahooapis.com/2.6.0/build/calendar/calendar-min.js"&gt;&lt;/script&gt; 
 * 
 * @param inputId the id of the input field that the calendar will input values into.
 * @param divId the id of the div that the calendar will be displayed in. This will
 * allow the calendar to be shown/hidden by showing/hiding the div.
 * @param imgId the id of the calendar img. The calendar div will be toggled between
 * shown/hidden each time the calendar image is clicked.
 * @return void
 */
DET.tale.util.createCalendar = function(inputId, divId, imgId)
{
  var d = DET.tale.util.parseDate(jQuery("#" + inputId).val());

  var calendar = new YAHOO.widget.Calendar("calendar", divId, 
		    { selected: DET.tale.util.formatDate(d, "mm/dd/yyyy") });
  calendar.selectEvent.subscribe(
		    function(type, args, obj)
		    {
		      var newDate = this.toDate(args[0][0]);
		      jQuery("#" + inputId).val(newDate.getDate() + "/" + 
		          (newDate.getMonth() + 1) + "/" + newDate.getFullYear());
		      jQuery("#" + divId).hide();
		    },
		    calendar, true);
		calendar.render();
		// TODO: Add a parameter to allow this to be specified.
		//jQuery("#" + inputId).attr("readonly", "readonly");
  jQuery("#" + imgId).bind("click", function(e) { jQuery("#" + divId).toggle(); } )
      .css("cursor", "pointer");
};

/**
 * Formats a Date object into a String based on a format string.
 * 	- dd represents a 2-digit date
 * 	- mm represents a 2-digit month
 * 	- yyyy represents a 4-digit year
 * TODO: This may need to work for other formats.
 * 
 * @param d the input Date object.
 * @param fmt the date format to be used.
 * @return the formatted String.
 */
DET.tale.util.formatDate = function(d, fmt)
{
  var result = fmt;
  var d1 = new String(d.getDate());
  var d2 = (d1.length < 2) ? ("0" + d1) : d1;
  var m1 = new String(d.getMonth() + 1);
  var m2 = (m1.length < 2) ? ("0" + m1) : m1;
  
  result = result.replace("dd", d2);
  result = result.replace("d", d1);
  result = result.replace("mm", m2);
  result = result.replace("m", m1);
  result = result.replace("yyyy", d.getFullYear());
  result = result.replace("yy", d.getYear());
  return result;
};

/**
 * Obtains the correct bottom position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute bottom position of the element in pixels.
 */
DET.tale.util.getAbsoluteBottom = function(jQ)
{
	var result = DET.tale.util.getAbsoluteTop(jQ) + jQ[0].offsetHeight;
	return result;
};

/**
 * Obtains the correct left position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute left position of the element in pixels.
 */
DET.tale.util.getAbsoluteLeft = function(jQ)
{
	var result = jQ.offset().left;
	var parents = jQ.parent();
	for (var i = 0; i < parents.length; i++)
	{
		var parent = parents.get(i);
		try
		{
			if (parent.css("position") == "relative" || 
					parent.css("position") == "absolute")
			{
				result += parent.offset().left;
			}
		}
		catch (e)
		{
			break;
		}
	}
	return result;
};

/**
 * Obtains the correct right position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute right position of the element in pixels.
 */
DET.tale.util.getAbsoluteRight = function(jQ)
{
	var result = DET.tale.util.getAbsoluteLeft(jQ) + jQ[0].offsetWidth;
	return result;
};

/**
 * Obtains the correct top position of the element in the document.
 * 
 * @param jQ - a jQuery object containing the element.
 * @return The absolute top position of the element in pixels.
 */
DET.tale.util.getAbsoluteTop = function(jQ)
{
	var result = jQ.offset().top;
	var parents = jQ.parent();
	for (var i = 0; i < parents.length; i++)
	{
		var parent = parents.get(i);
		try
		{
			if (parent.css("position") == "relative" || 
					parent.css("position") == "absolute")
			{
				result += parent.offset().top;
			}
		}
		catch (e)
		{
			break;
		}
	}
	return result;
};

/**
 * Returns the window size (not document size).
 * @return DET.tale.util.Size object.
 */
DET.tale.util.getWindowSize = function()
{
	var result, w, h;
	if (window.innerWidth && window.innerHeight)
	{
		w = window.innerWidth;
		h = window.innerHeight;
	}
	else if (document.documentElement)
	{
		w = document.documentElement.offsetWidth;
		h = document.documentElement.offsetHeight;
	}
	result = new DET.tale.util.Size(w, h);
	return result;
};

/**
 * Parses a String into a Date object.
 * This currently assumes a dd/mm/yyyy date format.
 * 
 * TODO: Add an input format string
 * 
 * @param str the String to be parsed.
 * @return the Date object.
 */
DET.tale.util.parseDate = function(str)
{
  var result = new Date();
  result.setDate(parseInt(str.substr(0, 2)));
  result.setMonth(parseInt(str.substr(3,2)) - 1);
  result.setFullYear(parseInt(str.substr(6,4)));
  return result;
};

/**
 * Specifies the option in the select element (identified by id) as selected. This method can be
 * used to apply multiple selections to a select element.
 *  
 * @param id - the ID of the select element.
 * @param value - the value of the option to be selected.
 */
DET.tale.util.selectOption = function(id, value)
{
	var options = jQuery("#" + id).attr("options");
	for (var i = 0; i < options.length; i++)
	{
		if (options[i].value == value)
		{
			options[i].selected = true;
		}
	}
};

/**
 * UI Package
 */
namespace("DET.tale.ui");

/**
 * Creates a new div element with the class 'popup' and adds it to the dom. If the option url and
 * postdata parameters are included, the div is populated from the url using jQuery's load function.
 */
DET.tale.ui.createPopup = function(id, url, postdata)
{
	var $result = jQuery(document.createElement("div"));
	$result.attr("id", id);
	$result.addClass("popup");
	jQuery("body").append($result);
	if (url != null)
	{
		$result.load(url, postdata);
	}
	return $result;
};

/**
 * 
 * @param id
 */
DET.tale.ui.Lookup = function(id)
{		
	var self = this;
	
	self.id = id;
	self.options = [];
	self.cache = [];
	self.displayed = [];
	
	self.oldDisplayed = []; // Testing
	
	self.curText = "";
	self.selIndex = -1;
	self.visible = false;
	
	self.$element = $("#" + id);
	self.$lookup = $(document.createElement("div"));
	self.$lookup.attr("className", "lookup");
	//self.$lookup.css("width", self.$element.width() + 2);
	self.$lookup.hide();
	self.$element.parent().append(self.$lookup);
	
	/**
	 * Add an option to the list of availables. 
	 * This will be used to retrieve a list of displayable options.
	 * Each option must be unique (any duplicates are ignored).
	 */
	self.addOption = function(str)
	{
		if (self.options.indexOf(str) == -1)
		{
			self.options.push(str);
		}
	};
	
	/**
	 * Adds an option to the cache.
	 * Each option must be unique (any duplicates will be ignored).
	 */
	self.addToCache = function(str)
	{
		if (self.cache.indexOf(str) == -1)
		{
			self.cache.push(str);
		}
	};

	/**
	 * Adds an option to the list of displayed options.
	 * Each option must be unique (any duplicates will be ignored).
	 */
	self.addToDisplayed = function(str)
	{
		if (self.displayed.indexOf(str) == -1)
		{
			self.displayed.push(str);
		}
	};
	
	/**
	 * Creates the option div to display in the lookup.
	 */
	self.createOptions = function()
	{
		// Remove dead options
		for (var i = self.oldDisplayed.length - 1; i >= 0; i--)
		{
			if (self.displayed.indexOf(self.oldDisplayed[i]) == -1)
			{
				$(self.$lookup.find("div")[i]).remove();
				self.oldDisplayed.remove(i);
			}
		}
	
		// Add new options
		for (var i = 0; i < self.displayed.length; i++)
		{
			if (self.oldDisplayed.indexOf(self.displayed[i]) == -1)
			{
				var $div = $(document.createElement("div"));
				$div.attr("innerHTML", self.displayed[i]);
				$div.addClass((i % 2 == 0) ? "a" : "b");
				$div.addClass("lookup_option_ll");
				$div.bind("mouseover", function () { self.highlightOption(jQuery(this)); });
				$div.bind("mouseout", function () { self.lowlightOption(jQuery(this)); });
				$div.bind("click", function() { self.selectOption(jQuery(this)); });
				$(document).bind("click", function() { self.hide(); });
				
				if (i != 0)
				{
					$(self.$lookup.find("div")[i - 1]).after($div);
				}
				else
				{
					self.$lookup.prepend($div);
				}
				self.oldDisplayed.insert(self.displayed[i], i);
			}
			else
			{
				var $div = jQuery(self.$lookup.find("div")[i]);
				$div.removeClass("a");
				$div.removeClass("b");
				$div.addClass((i % 2 == 0) ? "a" : "b");
			}
		}
	};
	
	self.doTab = function()
	{
	};
	
	/**
	 * Hide the lookup.
	 */
	self.hide = function()
	{
		if (self.visible)
		{	
			if (self.selIndex != -1)
			{
				self.lowlightOption(jQuery(self.$lookup.find("div")[self.selIndex]));
			}
			self.$lookup.hide();
			self.visible = false;
		}
		
		self.selIndex = -1;
	};
	
	/**
	 * Displays the option in a highlighted state.
	 */
	self.highlightOption = function($option)
	{
		$option.removeClass("lookup_option_ll");
		$option.addClass("lookup_option_hl");
	};
	
	/**
	 * Displays the option in a non-highlighted state.
	 */
	self.lowlightOption = function($option)
	{
		$option.removeClass("lookup_option_hl");
		$option.addClass("lookup_option_ll");
	};
	
	/**
	 * Retrieve a list of displayable options from those available.
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed.length = 0;
		
		// Retrieve data.
		for (var i = 0; i < self.options.length; i++)
		{
			if (new String(self.options[i]).startsWithIgnoreCase(self.$element.val()))
			{
				self.cache.push(self.options[i]);
				self.displayed.push(self.options[i]);
			}
		}

		// Create options.
		self.createOptions();
	};
	
	/**
	 * Select the option and populate the field with that value.
	 */
	self.selectOption = function($option)
	{
		var str = $option.attr("innerHTML");
		self.selIndex = self.displayed.indexOf(str);
		self.$element.val(str);
		self.hide();
	};
	
	/**
	 * Displays the lookup.
	 */
	self.show = function()
	{
		self.selIndex = -1;
		
		if (!self.visible)
		{
			// Reposition Lookup.
			var x = DET.tale.util.getAbsoluteLeft(self.$element);
			var y = DET.tale.util.getAbsoluteBottom(self.$element);
		
			self.$lookup.css("width", self.$element.width() + 2);
			self.$lookup.css("left", x).css("top", y).show();
			self.visible = true;
		}
	};
	
	/**
	 * Key up event for all non-escape characters.
	 */
	self.doKeyUp = function()
	{
		var str = $.trim(new String(self.$element.val()));
						
		if (str != "" && str.length > 1)
		{							
			self.populateOptions();
			
			// Display the popup.
			if (self.displayed.length > 0)
			{
				self.show();
			}
			else
			{
				self.hide();
			}
		}
		else
		{
			self.hide();
		}
		
		self.curText = str;
	};
	
	/**
	 * Handle the keydown event. This is used to capture all the special characters
	 * before the response is sent.
	 */
	self.keyDown = function(event)
	{
		var key = event.keyCode;
		var curIndex = (self.selIndex != -1) ? self.selIndex : 0;
		
		if (key == 9 || key == 13)
		{
			if (self.displayed.length != 0)
			{
				if (key == 9 && self.selIndex == -1)
				{
					self.hide();
				}
				else
				{
					self.selectOption(jQuery(self.$lookup.find("div")[curIndex]));
				}
			}
		}
		else if (key == 27)
		{
			self.hide();
		}
		else if (key == 38 && curIndex > 0)
		{				
			self.lowlightOption(jQuery(self.$lookup.find("div")[curIndex]));
			self.highlightOption(jQuery(self.$lookup.find("div")[--curIndex]));
			self.selIndex = curIndex;
		}
		else if (key == 40 && self.selIndex < (self.displayed.length - 1))
		{					
			if (self.selIndex != -1)
			{
				self.lowlightOption(jQuery(self.$lookup.find("div")[self.selIndex]));
			}
			self.highlightOption(jQuery(self.$lookup.find("div")[++self.selIndex]));
		}
	};
	
	/**
	 * Control the hiding/displaying of the lookup on key up.
	 */
	self.keyUp = function(event)
	{				
		var key = event.keyCode;
	
		if (key != 9 && key != 13 && key != 27 && key != 38 && key != 40)
		{
			self.doKeyUp();
		}
	};
	
	// Bind events
	self.$element.bind("keydown", self.keyDown);
	self.$element.bind("keyup", self.keyUp);
	
	return self;
};

/**
 * Ajax Lookup Class
 */
DET.tale.ui.AjaxLookup = function(id, url)
{
	var self = new DET.tale.ui.Lookup(id);
	
	self.url = url;
	
	/**
	 * Retrieve a list of displayable options from those available.
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed.length = 0;
		//self.$lookup.attr("innerHTML", "");
		
		// Retrieve Data					
		var data = { searchText: self.$element.val() };
		$.getJSON(self.url, data,
				function(data)
				{						
					for (var i = 0; i < data.length; i++)
					{
						//self.cache.push(data[i].value);
						self.addToCache(data[i].value);
						//self.displayed.push(data[i].value);
						self.addToDisplayed(data[i].value);
					}
					
					// Create options.
					self.createOptions(self.displayed[i]);
					
					// Force a display call.
					
					self.show();
				});
	};

	/**
	 * Overriden to allow displaying/hiding to be handled by asynchronous callback.
	 * @override
	 */
	self.doKeyUp = function()
	{				
		var str = new String(self.$element.val());
					
		if (jQuery.trim(str) != "")
		{
			self.populateOptions();
		}
		else
		{
			self.hide();
		}
		
		self.curText = str;
	}
	return self;
};

/**
 * EmailLookup Class
 */
DET.tale.ui.EmailLookup = function(id)
{
	var self = new DET.tale.ui.Lookup(id);
	
	/**
	 * Retrieve a list of displayable options from those available.
	 * @override
	 */
	self.populateOptions = function()
	{
		// Clear the data.
		self.displayed.length = 0;
		
		// Retrieve data.
		for (var i = 0; i < self.options.length; i++)
		{
			if (new String(self.options[i]).startsWithIgnoreCase(self.getCurrentEmail()))
			{
				self.cache.push(self.options[i]);
				self.displayed.push(self.options[i]);
			}
		}

		// Create options.
		self.createOptions();
	};
	
	/**
	 * Select the option and populate the field with that value.
	 * @override
	 */
	self.selectOption = function($option)
	{
		var str = $option.attr("innerHTML");
		//self.selIndex = self.displayed.indexOf(str);
		self.setCurrentEmail(str);	
		self.hide();
	}
	
	/**
	 * Retrieves the current email (based on the cursor position).
	 */
	self.getCurrentEmail = function()
	{
		var value = self.$element.val();
		var caret = DET.tale.form.doGetCaretPosition(self.$element[0]);
		var start = value.substring(0, caret).lastIndexOf(";") + 1;
		var end = value.indexOf(";", caret);
		end = (end != -1) ? end : value.length;
		return jQuery.trim(value.substring(start, end));
	};
	
	/**
	 * Sets the current email (based on the cursor position).
	 */
	self.setCurrentEmail = function(email)
	{
		var value = self.$element.val();
		var caret = DET.tale.form.doGetCaretPosition(self.$element[0]);
		var start = value.substring(0, caret).lastIndexOf(";") + 1;
		var end = value.indexOf(";", caret);
		end = (end != -1) ? end : value.length;
		self.$element.val(value.substring(0, start) + email + "; " + 
				value.substring(end, value.length));
	};
	
	return self;
};

/**
 * Form Package.
 */
namespace("DET.tale.form");
 
DET.tale.form.Option = function(text, value)
{
	var self = this;
	self.value = value;
	self.text = text;
};

/**
 * Limits a textarea to a specified maxlength. 
 * 
 * This function should be specified in the 'onkeypress' event.
 */
DET.tale.form.checkMaxLength = function(textarea, maxlength)
{
	return (textarea.value.length <= maxlength);
};

/**
 * Disables the default behaviour of submitting a form when the enter key is 
 * pressed in a text input.
 */
DET.tale.form.disableSubmitOnEnter = function()
{
	jQuery(document).bind("keypress", DET.tale.form.doDisableSubmitOnEnterKeyPress);
};

/**
 * The keypress event for disableSubmitOnEnter.
 */
DET.tale.form.doDisableSubmitOnEnterKeyPress = function(evt)
{
	var result = true;
	var e = (evt) ? evt : ((event) ? event : null);
	var node = (e.target) ? e.target : ((e.srcElement) ? e.srcElement : null);
	if ((e.keyCode == 13) && (node.type == "text"))  
	{
		result = false;
	}
	return result; 
};

/**
 * Get the caret position within an input control.
 */
DET.tale.form.doGetCaretPosition = function(ctrl) 
{
	var result = 0;
	// IE Support
	if (document.selection) 
	{
		ctrl.focus ();
		var sel = document.selection.createRange();
		var selLength = sel.text.length;
		sel.moveStart('character', -ctrl.value.length);
		result = sel.text.length - selLength;
	}
	// Firefox support
	else if (ctrl.selectionStart || ctrl.selectionStart == '0')
	{
		result = ctrl.selectionStart;
	}
	return result;
};


/**
 * Validation Package.
 */
namespace("DET.tale.validation");

/**
 * Validates an email String against a regular expression.
 * 
 * @param email The email String to be validated.
 * @return true if the String contains a valid email address, false otherwise.
 */
DET.tale.validation.validateEmail = function(email)
{
	var emailRegEx = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	var result = email.match(emailRegEx);
	return result;
};

/**
 * Validates a String to be not empty.
 * 
 * @param str The String to validate.
 * @return true if the String is not empty, false otherwise.
 */
DET.tale.validation.validateNotEmpty = function(str)
{
	var result = jQuery.trim(str).length != 0;
	return result;
};
