
//****************************************************************************************
//
//	prototype.js
//
//	Functions, objects, variables and constants that have a global use. (To be included 
//	at the start of every javascript page.)
//
//	Depends On:
//				
//
//	Copyright: 	David Horne, Tuross Technologies Australia P/L, 2006.
//				dkhorne@bigpond.net.au
//
//****************************************************************************************
	
//****************************************************************************************
//
//	Globals that are used in other scripts
//
//****************************************************************************************

	document.PrototypeIsSet = true;

//***************************************************************************************
//
//	Prototypes for various types
//
//****************************************************************************************


		var StringDelim1 = ",";
		var StringDelim2 = "=";

//****************************************************************************************
//	String prototypes
//****************************************************************************************

		var StringPrototype = true;

//	Strips leading spaces
		function Strip() {
			var iStart = 0;
			while ((iStart < this.length) && (this.charAt(iStart) == " ")) iStart++; 
			var iEnd = this.length - 1;
			while ((iEnd < this.length) && (this.charAt(iEnd) == " ")) iEnd--; 
			return this.substr(iStart, (iEnd - iStart + 1));
		}
		

		String.prototype.strip = Strip;
		

//	Formats string type
		function FormatString(Type, Option) {
			return FormatValue(this, Type, Option);
		}
		
		String.prototype.format = FormatString;

//	Returns the true if the value is a valid type
		function ValidValue(Type) {
			var Value = this;

			switch (Type.toLowerCase()) {
				case "number": 
					if (isNaN(Value)) {
						alert("Invalid numeric value - " + Value);
						return false
					}
					return true;
					break;
		
				case "money": 
					if (Value != "TBA") {
						if (isNaN(new String(Value).replace(/[\$,]/g, ""))) {
							alert("Invalid Money value - " + Value)
							return false
						}
					}
					return true;
					break;
		
				case "date": 
				case "time": 
				case "datetime": 
					return ((Value == "") || (new Date().parseDate(Value) != ""));
					break;
		
				default: 
					return true;
					break;
			}
		}

		String.prototype.valid = ValidValue;

//	Returns the raw value of a string
		function RawValue(Type) {
			var Value = this;
			if (!this.valid(Type)) return "";

			switch (Type.toLowerCase()) {
				case "number":
					return Number(Value);
					break;
		
				case "money":
					if (Value == "TBA")
						return Value
					else
						return parseFloat(String(Value).replace(/[\$,]/g, ""));
					break;
		
				case "time":
				case "date":
				case "datetime":
					if (Value == "") return "";
					return new Date().parseDate(Value);
					break;
		
				case "boolean":
					return (Value == "on");
					break;
		
				default:
					return Value.toString();
					break;
			}
		}

		String.prototype.raw = RawValue;
		

		function SpanHTML(Type, strOptions, strDelim1, strDelim2) {
			var Delim1 = (strDelim1 != null ? strDelim1 : StringDelim1);
			var Delim2 = (strDelim2 != null ? strDelim2 : StringDelim2);
		
			var strHTML = "<span ";

			var Level1 = strOptions.split(Delim1);
	
			for (Element in Level1) {
				var Level2 = Level1[Element].split(Delim2);
				if ((Level2[0] != null) && (Level2[1] != null)) 
					strHTML += " " + Level2[0].strip() + "=\"" + Level2[1].strip() + "\"";
				else
					alert("span error: Could not parse " + Level1[Element]);
			}
			strHTML += ">" + this.format(Type) + "</span>\n";
			
			return strHTML;
		}

		String.prototype.span = SpanHTML;
		
		function IsEmpty() {
			return ((this == null) || (this == ""));
		}
		
		String.prototype.isEmpty = IsEmpty;

		//	parses KeyWord='Value' into pairs
		function ParseStringOptions() {
			var Options = new Array();
			var Match;
			var regOption = /(\w*)\s*=\s*(['"!]?)(.*)\2/g;

			if (this != "") {
				var strOptions = this.replace(/\\'/g, "~");
			
				while ((Match = regOption.exec(strOptions)) != null) {
					if (Match.length >= 4)
						Options.push({'Key': Match[1], 'Value': Match[3].replace(/~/g, "'")});
				}
			}
			return Options;
		}

		String.prototype.parseOptions = ParseStringOptions;
		
		function MergeString(Conjunction) {
			var ReturnString = this;
			
			for (var i = 1; i < arguments.length; i++)
				ReturnString += (!ReturnString.isEmpty() ? Conjunction : "") + (!arguments[i].isEmpty() ? arguments[i] : "");
				
			return ReturnString;
		}
		
		String.prototype.merge = MergeString;
		

//****************************************************************************************
//	Number prototypes
//****************************************************************************************
//	Formats the number
		function FormatNumber(Option) {
			return FormatValue(this, "Number", Option);
		}
		
		Number.prototype.format = FormatNumber;


//	Rounds the number
		function NumberRound(Places) {

			var TheValue = new Number(this);
			if (TheValue.NaN) return "";
			TheValue = new String(Math.round(TheValue * Math.pow(10, Places)));
			if (Places != 0)
				TheValue = TheValue.substr(0, TheValue.length - Places) + "." + TheValue.substr(TheValue.length - Places);
			return TheValue;
		}
		
		Number.prototype.round = NumberRound;

//****************************************************************************************
//	Boolean prototypes
//****************************************************************************************
//	Formats the Boolean
		function FormatBoolean() {
			return FormatValue(this, "Boolean");
		}
		
		Boolean.prototype.format = FormatBoolean;

//****************************************************************************************
//	Array prototype
//****************************************************************************************
//	Filters an array of objects based on fields and value pairs
		function ArrayFilter(Fields, Values) {

			if (Fields.constructor != Array) Fields = [Fields];
			var TheTest = "";
			for (var i = 0; i < Fields.length; i++)
				TheTest += (TheTest != "" ? "+" : "") + "this[i]." + Fields[i] + ".toString()";
				
			if (Values.constructor != Array) Values = [Values];
			TheTest += " == '" + Values.join("") + "'";
			
			var NewArray = new Array();
			for (var i = this.length - 1; i >= 0; i--)
				if (eval(TheTest))
					NewArray.push(this[i]);
					
			return NewArray;
		}

		Array.prototype.filter = ArrayFilter;
		
//	return array of unique values
		function ArrayUniqueValues(Field) {
			var UniqueValues = new Array();
			var IsUnique = new Object();

			for (var i = this.length - 1; i >= 0; i--)
				if (IsUnique[this[i][Field]] == null) {
					IsUnique[this[i][Field]] = true;
					UniqueValues.push(this[i][Field]);
				}
					
			return UniqueValues;
		}

		Array.prototype.unique = ArrayUniqueValues;
		
//****************************************************************************************
//	Date prototype
//****************************************************************************************

		var DatePrototype = true;

		function DateDiff(Ident, Diff) {

			var iYear = this.getFullYear();
			var iMonth = this.getMonth();
			var iDay = this.getDate();
			var iHour = this.getHours();
			var iMin = this.getMinutes();

			switch (Ident) {
				case "M":
					iMin += Diff;
					break;
					
				case "H":
					iHour += Diff;
					break;
					
				case "d":
					iDay += Diff;
					break;
					
				case "m":
					iMonth += Diff;
					break;

				case "y":
					iYear += Diff;
					break;
			}
			
			return new Date(iYear, iMonth, iDay, iHour, iMin);
		}

		Date.prototype.dateDiff = DateDiff;

		function DateError(ErrorType) {
			var strValid = "Valid formats: dd/mm/yy hh:mm or ddmmyy hhmm or dd mmm yyyy hh:mm";

			switch (ErrorType) {
				case 1:
					alert("Invalid Date: Cannot resolve year.\n" + strValid);
					break;
				
				case 2:
					alert("Invalid Date: Cannot resolve month.\n" + strValid);
					break;
				
				case 3:
					alert("Invalid Date: Cannot resolve day.\n" + strValid);
					break;
				
				case 4:
					alert("Invalid Time.\n" + strValid);
					break;
				
				default:	
					alert("Invalid Date.\n" + strValid);
					break;
			}
			return Number.NaN
		}

		function ParseDate(strDate) {
			var strMonths = new Array("january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december");
			var MonthDays = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
			var re = /[\/\-:.t ]*([^t\W]*)[\/\-:.t ]*([^t\W]*)[\/\-:.t ]*([^t\W]*)[\/\-:.t ]*([^t\W]*)[\/\-:.t ]*([^t\W]*)[\/\-:.t ]*([^t\W]*)/;
            var Days = /mon\w*|tue\w*|wed\w*|thu\w*|fri\w*|sat\w*|sun\w*/g;

			var iYear, iMonth, iDay, iHour, iMin, iSec, imSec;

			var iPos = 0;
			
			if ((strDate == "") || (strDate == null)) return "";
        	strDate = strDate.toString().toLowerCase().replace(Days, "");
            
			var DateArray = strDate.match(re);
			var YMD = (DateArray[1] > 31)
			var IsAM = (strDate.toString().indexOf("am") >= 0);
			var IsPM = (strDate.toString().indexOf("pm") >= 0);

			if (DateArray.length >= 4) {
				iYear = parseFloat(YMD ? DateArray[1] : DateArray[3]);

				if (isNaN(parseFloat(DateArray[2]))) {
					if (iMonth == null)
						for (var j = 0; j < strMonths.length; j++)
							if (strMonths[j].indexOf(DateArray[2]) >= 0) iMonth = j;
				} else {
					iMonth = parseFloat(DateArray[2]) - 1;
				}

				iDay = parseFloat(YMD ? DateArray[3] : DateArray[1]);
				iHour = (DateArray[4] != "" ? parseFloat(DateArray[4]) : 0);
				iMin = (DateArray[5] != "" ? parseFloat(DateArray[5]) : 0);
				iSec = (DateArray[6] != "" ? parseFloat(DateArray[6]) : 0);
			}
			
// Do Some checks
			if ((iMonth >= 12) && (iDay <= 12)) {
				var tmp = iMonth;
				iMonth = iDay - 1;
				iDay = tmp + 1;
			}
			
			iHour += (IsPM ? 12 : 0) + (IsPM && (iHour == 12) ? -12 : 0) + (IsAM && (iHour == 12) ? -12 : 0);

			var TheDate = new Date(iYear, iMonth, iDay, (iHour != null ? iHour : 0), (iMin != null ? iMin : 0), (iSec != null ? iSec : 0), (imSec != null ? imSec : 0));
			if (isNaN(TheDate)) {
				TheDate = new Date(strDate);
				if (isNaN(TheDate)) {
					alert("Invalid date: " + strDate + "\nValid formats include dd mmm yyyy hh:mm:ss or dd/mm/yy.")
					return "";
				}
			}
			return TheDate;
		}
		
		Date.prototype.parseDate = ParseDate;



		function FormatDate(strFormat) {
		
			var ShowAMPM = ((strFormat.match(/AMPM/gi) != null) || (strFormat.match(/AP/gi) != null));
			var IsPM = (this.getHours() >= 12)

			var Months = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
			var iYear = this.getFullYear();
			var iMonth = this.getMonth();
			var iDay = this.getDate();
			var iHour = this.getHours();
			if (ShowAMPM && (iHour > 12)) iHour -= 12;
			if (ShowAMPM && (iHour <= 0)) iHour = 12;
			var iMin = this.getMinutes();
			var iSec = this.getSeconds();
			var imSec = this.getMilliseconds();
			
			var strDate = strFormat;
			
			strFormat = strFormat.replace(/mmm/g, Months[iMonth]);
			strFormat = strFormat.replace(/mm/g, (iMonth > 9 ? "" : "0") + (iMonth + 1));
			strFormat = strFormat.replace(/yyyy/g, iYear);
			strFormat = strFormat.replace(/yy/g, iYear.toString().substr(iYear.toString().length - 2));
			strFormat = strFormat.replace(/dd/g, (iDay > 9 ? "" : "0") + iDay);
			strFormat = strFormat.replace(/HH/g, (iHour > 9 ? "" : "0") + iHour);
			strFormat = strFormat.replace(/MM/g, (iMin > 9 ? "" : "0") + iMin);
			strFormat = strFormat.replace(/SSS/g, (imSec > 99 ? "" : "0") + (imSec > 9 ? "" : "0") + imSec);
			strFormat = strFormat.replace(/SS/g, (iSec > 9 ? "" : "0") + iSec);
			strFormat = strFormat.replace(/AMPM/gi, (IsPM ? "pm" : "am"));
			
			return strFormat;
		}

		Date.prototype.format = FormatDate;


		
//****************************************************************************************
//	Object prototype
//****************************************************************************************

		var Delimit = ", ";
		var ArrayTemplate = "<indent>[\t<item>\t]";
		var ObjectTemplate = "<indent>{\t<item>\t}";
		var ArrayItemTemplate = "<value>" + Delimit;
		var ObjectItemTemplate = "'<name>': <value>" + Delimit;
		var ItemNewline = "\n<indent>\t";

//	Converts an object to JSON string
		function JSONFormat(Item, IncludeFunctions, Indent) {
			switch (typeof(Item)) {
				case "object":
					return Item.ToJSON(IncludeFunctions, (Indent + 1));
					break;
					
				case "function":
					return "'" + encodeURI(Item) + "'";
					break;
					
				case "number":
					return Item;
					break;

				case "boolean":
					return (Item ? "'true'" : "'false'");
					break;

				case "undefined":
					return "";
					break;

				case "date":
					return "'" + Date(Item).toString("dd/MMM/yyyy hh:mm:ss") + "'";
					break;

				case "string":
				default:
					return "'" + Item + "'";
					break;
			}
		}

		function ObjectToJSON(IncludeFunctions, Indent) {
			IncludeFunctions = (IncludeFunctions == null ? false : IncludeFunctions);
			Indent = (Indent != null ? Indent : 0)
			
			var TheTemplate;
			var WasObject = false;
			var strIndent = "";
			for (var i = 0; i < Indent; i++)
				strIndent += "\t";
			
			var strItems = "";
			if (this.constructor == Array) {
				for (var i = 0; i < this.length; i++) {
					if ((typeof(this[i]) != "function") || IncludeFunctions) {
						if (typeof(this[i]) == "object") {
							strItems += ItemNewline + ArrayItemTemplate.replace(/<value>/g, JSONFormat(this[i], IncludeFunctions, Indent));
							WasObject = true;
						} else {
							strItems += (WasObject ? ItemNewline : "") + ArrayItemTemplate.replace(/<value>/g, JSONFormat(this[i], IncludeFunctions, Indent));
							WasObject = false;
						}
					}
				}

				TheTemplate = ArrayTemplate;
			} else {
				for (var item in this) {
					if ((typeof(this[item]) != "function") || IncludeFunctions) {
						if (typeof(this[item]) == "object") {
							strItems += ItemNewline + ObjectItemTemplate.replace(/<name>/g, item).replace(/<value>/g, JSONFormat(this[item], IncludeFunctions, Indent));
							WasObject = true;
						} else {
							strItems += (WasObject ? ItemNewline : "") + ObjectItemTemplate.replace(/<name>/g, item).replace(/<value>/g, JSONFormat(this[item], IncludeFunctions, Indent));
							WasObject = false;
						}
					}
				}
				TheTemplate = ObjectTemplate;
			}
			strItems = strItems.substr(0, strItems.lastIndexOf(Delimit, strItems));
			return TheTemplate.replace(/<item>/g, strItems).replace(/<indent>/g, strIndent);
		}
		
/*
*/

		Object.prototype.ToJSON = ObjectToJSON;




//	Converts Object data to a string
		function ObjectToString(Prefix) {
			var strData = "";
			for (var item in this) {
				switch (typeof(this[item])) {
					case "object":
						if (this[item].constructor == Date) {
							strData += (strData != "" ? "&" : "") + (Prefix != null ? Prefix + "." : "") + item + "=" + this[item] + "::date";
						} else {
							strData += (strData != "" ? "&" : "") + Object(this[item]).ToString(item);
						}
						break;
						
					case "function":
						strData += (strData != "" ? "&" : "") + (Prefix != null ? Prefix + "." : "") + item + "=" + escape(this[item]) + "::function";
						break;
						
					default:
						strData += (strData != "" ? "&" : "") + (Prefix != null ? Prefix + "." : "") + item + "=" + this[item] + "::" + typeof(this[item]);
						break;
				}
			}
			return strData
		}
		
//		Object.prototype.ToString = ObjectToString;


//	Converts a string to Object data
		function SetObjectData(TheObject, strFields, strData) {
			var TheFields = "['" + strFields.replace(/\./g, "']['") + "']";
			var TheData = strData.split("::");
			switch (TheData[1]) {
				case "date":
					eval("TheObject" + TheFields + " = new Date('" + TheData[0] + "')");
					break;
					
				case "number":
					eval("TheObject" + TheFields + " = " + TheData[0]);
					break;
					
				case "function":
					eval("TheObject" + TheFields + " = " + unescape(TheData[0]));
					break;
					
				default:
					eval("TheObject" + TheFields + " = '" + TheData[0] + "'");
					break;
			}
		}
	
		function ObjectFromString(strData) {
			var strElements = strData.split("&");
			var strKey, strValue;
	
			for (var i = 0; i < strElements.length; i++) {
				var TheKeyValue = strElements[i].split("=");
				SetObjectData(this, TheKeyValue[0], TheKeyValue[1]);
			}
		}
		
//		Object.prototype.FromString = ObjectFromString;
		
		function CloneObject(NoData) {
			NoData = (NoData ? NoData : false);
 			var NewObj = new this.constructor;
			for (var Item in this) {
				if ((typeof(this[Item]) == "object") && (this[Item] != null)) {
					switch (this[Item].constructor) {
						case Date:
							NewObj[Item] = (NoData ? new Date() : this[Item]);
							break;
							
						default:
							NewObj[Item] = this[Item].Clone(NoData);
							break;
					}
				} else {
					NewObj[Item] = (NoData ? "" : this[Item]);
				}
			}
			return NewObj;
		}

		Object.prototype.Clone = CloneObject;
		
//		Object.prototype.FromString = ObjectFromString;
		
		function CopyObject(SourceObj, NoData) {
			NoData = (NoData ? NoData : false);
			for (var Item in SourceObj) {
				if ((SourceObj[Item] != null) && (typeof(SourceObj[Item]) == "object")) {
					if (this[Item] == null) this[Item] = new SourceObj[Item].constructor
					this[Item].copy(SourceObj[Item], NoData);
				} else {
					this[Item] = (NoData ? "" : SourceObj[Item]);
				}
			}
		}

		Object.prototype.Copy = CopyObject;
		
		function ObjectAsOptions(Exceptions) {
			var strOptions = "";
			Exceptions = "[" + Exceptions.replace(/\s*,\s*/g, "][");
			for (var Item in this) {
				if (this.hasOwnProperty(Item) && (typeof(this[Item]) != "object") && (Exceptions.indexOf(Item) < 0))
					strOptions += (strOptions != "" ? " " : "") + Item + " = '" + this[Item] + "'";
			}
			
			return strOptions;
		}

		Object.prototype.asOptions = ObjectAsOptions;
		
		function TestEmpty(TheObject) {
			return ((TheObject == null) || (String(TheObject).isEmpty()));
		}
		
		function HasValidData(TheField) {
			return TestEmpty(this[TheField]);
		}
		
		Object.prototype.hasValidData = HasValidData;
		


//****************************************************************************************
//
//	SUPPORT ROUTINES
//
//****************************************************************************************

//****************************************************************************************
//	Format a Value
//****************************************************************************************
		function FormatValue(Value, Type, Option) {

			switch (Type.toLowerCase()) {
				case "number": 
					if (Option != null)
						return String(Number(Value).round(Option));
					else
						return String(Number(Value));

					break;
		
				case "date": 
					if (Option != null)
						return new Date().parseDate(Value).format(Option);
					else
						return new Date().parseDate(Value).format("dd-mmm-yyyy");
					break;
		
				case "time": 
					if (Option != null)
						return new Date().parseDate(Value).format(Option);
					else
						return new Date().parseDate(Value).format("HH:MM");
					break;
		
				case "datetime": 
					if (Option != null)
						return new Date().parseDate(Value).format(Option);
					else
						return new Date().parseDate(Value).format("dd-mmm-yyyy HH:MM");
					break;
		
				case "datepicker": 
					if (Option != null)
						return new Date().parseDate(Value).format(Option);
					else
						return new Date().parseDate(Value).format("dd-mmm-yyyy");
					break;
		
				case "money": 
					return FormatMoney(Value);
					break;
		
				case "bit": 
					if ((Value.toLowerCase() == "true") || (Value.toLowerCase() == "1"))
						return "1";
					else
						return "0";
					break;
	
				case "boolean": 
					return (Number(Value) != 0 ? "&times;" : "&nbsp;");
					break;
	
				case "percent": 
					return FormatPercent(Value);
					break;
	
				default: 
					return Value;
					break;
			}
		}

		function FormatPercent(valPercent) {
			if ((valPercent == null) || (isNaN(valPercent))) return "";
			var strPercent = new String(Math.round(valPercent * 1000) / 10);
			return strPercent + "%";
		}
	
		function FormatMoney(strSource) {
			if (strSource == null) return "";
			strSource = new String(strSource).replace(/[\$,]/g, "");
			
			var TheValue = Number(strSource).round(2);

			var Negative = (TheValue < 0)
			if (Negative) TheValue = TheValue * -1;
			
			if (TheValue < 1)
				return (Negative ? "($0" + TheValue + ")" : "$0" + TheValue);
			else
				return (Negative ? "($" + TheValue + ")" : "$" + TheValue);
		}

		
//***************************************************************************************
//	END prototype.js
//***************************************************************************************
