// CLIENT-SIDE FRAMEWORK EXCEPTION

function newFrameworkException(code, description, number, properties) {
	var err = new Error(number, description);
	err.code = code;
	
	if (typeof(properties) == 'object') {
		err.properties = properties;
	} else {
		err.properties = {};
	}
	return(err);
}


// EVENT HANDLER FRAMEWORK

// A VOID function.
function DxDoEvents(listeners) {
	for (var i = 0; i < listeners.length; i++) {
		listeners[i]();
	}
}

// A non-VOID function.
function DxGetEventResults(listeners) {
	for (var i = 0; i < listeners.length; i++) {
		if (!listeners[i]()) return false;
	}
	
	return true;
}


// UTILITIES

// getAtt()
//
// Utility method to extract an attribute value from a DOM node.
function getAtt(node, attName, ifNotFoundValue) {
	var att = node.attributes.getNamedItem(attName);
	if (att) {
		return att.value;
	} else if (!(ifNotFoundValue == undefined || ifNotFoundValue == null)) {
		return ifNotFoundValue;
	} else {
		return '';
	}
}

// Get text of node by different properties.
function getNodeText(node) {
	if (node.textContent) {
		return node.textContent;
	}
	else if (node.text) {
		return node.text;
	}
	else {
		var child = node.firstChild;
		if (child) {
			return child.nodeValue;
		} else {
			return '';
		}
	}
}

// Given a node, get the first child of a given tag name.
// If not found, return null.
function firstChildByTagName(node, tagName) {
	var currChild = node.firstChild;
	var found = false;
	
	while (currChild != null && !found) {
		if (currChild.nodeName == tagName) {
			found = true;
		} else {
			currChild = currChild.nextSibling;
		}
	}
	
	return currChild;
}

// jump to URL
function linkTo(url, localFrame) {
	if (localFrame) {
		window.location.href = url;
	} else {
		window.top.location.href = url;
	}
}

// Determine if one object is the ancestor of another.
function isAncestor(ancestor, descendant) {
	descendant = descendant.parentNode;
	while (descendant) {
		if (descendant == ancestor) return true;
		descendant = descendant.parentNode;
	}
	return false;
}

// DEBUGGING: list all properties of element.
// Filters = array of substrings; if given, properties are shown only if they contain substrings.
function listElementProperties(elem, filters) {
	var j, showProp, prop;
	var props = [];
	if (!filters) filters = [];
	
	for (var i in elem) {
		prop = i.toLowerCase();
		if (filters.length > 0) {
			showProp = false;
			for (var j = 0; j < filters.length; j++) {
				if (prop.indexOf(filters[j].toLowerCase()) >= 0) showProp = true;
			}
		} else {
			showProp = true;
		}
		if (showProp) props.push(i);
	}
	alert(props.join('\n'));
}

// Clone a javascript object (either superficially, or deep)
// Adapated from http://www.faqts.com/knowledge_base/view.phtml/aid/6231, but deliberately
// does *not* modify Object.Prototype, as otherwise the clone() method shows up in enumerations
function clone (obj, deep) {
	var objClone = new Object();
	for (var property in obj) {
		if (!deep) {
			objClone[property] = obj[property];
		} else if (typeof obj[property] == 'object') {
			objClone[property] = obj[property].clone(deep);
		} else {
			objClone[property] = obj[property];
		}
	}
	return objClone;
}


// Parse XML document from text.
function parseXML(xml) {
	if (window.ActiveXObject) {
		var doc = new ActiveXObject("Microsoft.XMLDOM");
		doc.async = "false";
		doc.loadXML(xml);
	}
	else {
		var parser = new DOMParser();
		var doc = parser.parseFromString(xml, "text/xml");
	}
	
	return doc;
}

//XMLEncode: Escapes a string so that it conforms to valid XML syntax for text
//nodes and attribute values.  To avoid any potential encoding conflicts, all characters
//above ASCII code 127 are escaped, even though it isn't be strictly necessary to do so.
function XMLEncode(strText) {
	var re = /[\&\<\>"'\x0A\x0D]|[\u0080-\uffff]/g;
	if(strText) {
		return(strText.toString().replace(re, 
			function(match) {
				switch(match) {
					case "<":
						return("&lt;");
						break;
					case ">":
						return("&gt;");
						break;
					case "&":
						return("&amp;");
						break;
					case "\"":
						return("&quot;");
						break;
					case "'":
						return("&#39;");
						break;
					case String.fromCharCode(10):
						return("&#10;");
						break;
					case String.fromCharCode(13):
						return("&#13;");
						break;
					default:
						return("&#" + match.charCodeAt(0) + ";");
				}
			})
		);
	}
	else {
		return("");
	}
}

//XMLEncodeAttribute: Creates an XML Attribute string, encoding its contents and ensuring that the name is valid.
//If the value is null or undefined, and if the includeNull parameter is false (default) then the function simply 
//returns an empty string (no attribute).  If includeNull is true, then the function returns an empty attribute.
function XMLEncodeAttribute(name, value, includeNull) {
	name = XMLEncodeName(name);

	// If there is a value, or the includeNull flag is true, then return the attribute string.
	if(value == null && !includeNull) {
		return("");
	}
	else {
		value = XMLEncode(value);
		return(" " + name + "=\"" + value + "\"");
	}
}



//XMLEncodeElement: Creates an XML Element string, encoding its contents and ensuring that the name is valid.
//If the value is null or undefined, and if the includeNull parameter is false (default) then the function simply 
//returns an empty string (no element).  If includeNull is true, then the function returns an empty element.
function XMLEncodeElement(name, value, includeNull) {
	name = XMLEncodeName(name);

	// If there is a value, or the includeNull flag is true, then return the attribute string.
	if(value == null && !includeNull) {
		return("");
	}
	else {
		value = XMLEncode(value);
		if(value != "") {
			return("<" + name + ">" + value + "</" + name + ">");
		}
		else {
			return("<" + name + "/>");
		}
	}
}

//XMLEncodeName: Strips any invalid characters out of a string that will be used as an XML element or attribute name.
function XMLEncodeName(name) {
	return(name.replace(/[^A-Za-z0-9\-\_]/g, ""));
}

/***********************************************************************************************************************

ASYNCHRONOUS EVENT SYSTEM
=========================

The asynchronous event system allows windows to raise events that can be caught by other windows, even across browser 
instances, as long as the pages are in the same domain.

EXAMPLE
-------

Window #1 can register an event handler for an "OnUserChanged" event, as follows:

	registerEventHandler("OnUserChanged", OnUserChangedHandler);
	
	function OnUserChangedHandler(UserOID) {
		alert("User " + UserOID + " has changed!");
	}

Window #2 can then RAISE the event as follows:

	raiseEvent("OnUserChanged", 27770);

Events are raised ASYNCHRONOUSLY.  The window that raises the event has no way of knowing whether any other window has 
handled it.  There are no return values, no output parameters.  Any arguments passed to the raiseEvent method
must be scalar values.  If you attempt to pass an object, it will simply be converted to a string (e.g. "[object]").

These functions rely on the cookie handling library by Paul Stevens, contained in pcpcookielib.js

***********************************************************************************************************************/

// Registers a function as a handler for a specific event.
// EventName: The name of the event to be handled.
// HandlerFunc: The function that will handle the event.
function registerEventHandler(EventName, HandlerFunc) {
	if(!this.EventHandlers) {
		this.EventHandlers = new Object();
	}
	if(!this.EventHandlers[EventName]) {
		this.EventHandlers[EventName] = new Array();
	}
	HandlerFunc.LastHandled = new Date().valueOf() - 10;
	this.EventHandlers[EventName].push(HandlerFunc);
	
	if(!this.eventProcessorTimer) {
		if(!this.processEvents) {
			this.processEvents = processEvents;
		}
		this.eventProcessorTimer = this.setInterval("processEvents()", 500);
	}
}


// Raises an event.
// EventName: The name of the event being raised.
// Arguments: Arbitrary parameters to pass to the event handlers.
function raiseEvent(EventName) {
	var Time = new Date();
	var TimeString = Time.valueOf().toString();
	var EventKey = "Event_" + EventName + "_" + TimeString;

	var EventCookie = new cookieObject(EventKey, new Date(Time.valueOf() + 5000), "/");
	EventCookie.fields[0] = EventName;
	EventCookie.fields[1] = TimeString;
	
	var args = [];
	for(var c = 1; c < arguments.length; c++) {
		args.push(arguments[c]);
	}

	if(is_ie && document.all.fraDataStore) {
		fraDataStore.saveData(EventKey + "_Args", serializeValue(args), 5);
	}
	else {
		EventCookie.fields[2] = serializeValue(args);
	}
	EventCookie.write();
}

// Raises an event, to be handled within the current window (or its frames, parent windows, and subframes)
function raiseLocalEvent(EventName) {
	var topWindow = window;
	
	// Find the top-level parent window.
	while(topWindow != topWindow.parent) {
		topWindow = topWindow.parent;
	}
	
	// Chop off the first argument (EventName).  The rest of these are arguments to the event handlers.
	var args = new Array();
	for(var i = 1; i < arguments.length; i++) {
		args.push(arguments[i]);
	}
	
	// Recursively look for event handlers in the top-level window and all its nested subframes.
	setTimeout(function () { handleLocalEvents(topWindow, EventName, args); }, 100);
}

// Recursively looks for event handlers and calls them with the specified event.
function handleLocalEvents(w, EventName, args) {
	var eventHandlers = w.EventHandlers;
	if(eventHandlers) {
		var listeners = eventHandlers[EventName];
		if(listeners) {
			for(var i = 0; i < listeners.length; i++) {
				try {
					listeners[i].apply(w, args);
				}
				catch(e) {
					if(e.message != "") {
						throw e;
					}
				}
			}
		}
	}

	var frames = [];
	var iframeElements = w.document.getElementsByTagName("iframe");
	var frameElements = w.document.getElementsByTagName("frame");

	// Get iframes.
	for(var f = 0; f < iframeElements.length; f++) {
		try {
			frames.push(iframeElements[f].contentWindow);
		} catch (err) {
		}
	}
	// Get regular frames.
	for(var f = 0; f < frameElements.length; f++) {
		try {
			frames.push(frameElements[f].contentWindow);
		} catch (err) {
		}
	}
	// Call event handlers.
	for(var f = 0; f < frames.length; f++) {
		try {
			handleLocalEvents(frames[f], EventName, args);
		} catch (err) {
		}
	}
}


// Internal function, which gets called on a timer (setInterval) every half second.
// Looks for events that have been raised, and calls the appropriate handlers.
// Events expire after 5 seconds.  If a window with a registered event handler does 
// not process the event within 5 seconds, it misses out.
function processEvents() {
	var list = new cookieList();
	var EventCookies = new Array();
	
	// Get a list of all cookies that start with "Event_".
	for(var cook = 0; cook < list.length; cook++) {
		if(list[cook].name.substring(0, 6) == "Event_") {
			EventCookies.push(list[cook].name);
		}
	}

	for(var c = 0; c < EventCookies.length; c++) {
		var EventKey = EventCookies[c];
		var EventCookie = new cookieObject(EventKey, null, "/");
		var Time = new Date(parseInt(EventCookie.fields[1]));
		if(Time.toString() != "NaN") {
			var EventName = EventCookie.fields[0];
			//if the event is 5 seconds old (or less), process it.
			if(new Date().valueOf() - Time.valueOf() <= 5000) {
				var EventHandlers = this.EventHandlers;
				if(EventHandlers && EventHandlers[EventName]) {
					var sArgList; // Serialized arg list.
					var ArgList;  // Deserialized arg list.
					
					// If in IE and the DataStore is available, the arg list comes from the data store (which allows for more data).
					if(is_ie && document.all.fraDataStore) {
						var sArgList = fraDataStore.loadData(EventCookies[c] + "_Args");
					}
					else {
						// Otherwise, the arg list comes from the event cookie.
						var sArgList = EventCookie.fields[2];
					}
					
					// If an arg list exists, deserialize it.
					if (sArgList) {
						ArgList = eval(sArgList);
					}
					var Handlers = EventHandlers[EventName];
					for(var i = 0; i < Handlers.length; i++) {
						var Handler = Handlers[i];
						if (Handler && ArgList ) {
							if(Handler.LastHandled < Time.valueOf()) {
								Handler.LastHandled = Time;
								Handler.apply(window, ArgList);
							}
						}
					}
				}
			}
		}
	}
	
	//window.isModal gets set in AdminApp.js if the current window is a modal dialog.
	if(this.isModal) {
		try {
			if(this.opener && this.opener.eventProcessorTimer && this.opener.processEvents) {
				this.opener.processEvents();
			}
		}
		catch(e) {}
	}
}

// Serializes a value into a string that can be used with the "eval" function to deserialize it.
function serializeValue(value) {
	var val;
	var aProps;
	var aVals;

	switch(getTypeOf(value)) {
		case "number":
			return value.toString();
			break;
			
		case "string":
			return "\"" + EscapeForJScript(value) + "\"";
			
		case "date":
			return "new Date(" + value.valueOf() + ")";
			
		case "function":
			return "";
			
		case "object":
			aProps = [];
			for(var propName in value) {
				val = serializeValue(value[propName]);
				if(val != "") {
					aProps.push(propName + ": " + val);
				}
			}
			return "({" + aProps.join(",") + "})";
			
		case "array":
			aVals = [];
			for(var i = 0; i < value.length; i++) {
				val = serializeValue(value[i]);
				if(val != "") {
					aVals.push(val);
				}
			}
			return "[" + aVals.join(",") + "]";
			
		case "boolean":
			return (value ? "true" : "false");
		
		case "null":
			return "null";
			
		case "undefined":
			return "undefined";
			
		default:
			return "";
	}
}

// Returns the detailed type of a value.
function getTypeOf(value) {
	var sType = typeof(value);
	
	if(sType == "object") {
		if(value == null) {
			sType = "null";
		}
		else if(typeof(value.length) == "number" && typeof(value.splice) == "function") {
			sType = "array";
		}
		else if(typeof(value.getFullYear) == "function") {
			sType = "date";
		}
	}

	return sType;
}


function EscapeForJScript(value) {
	value = value.replace(/\\/g, "\\\\");
	value = value.replace(/'/g, "\\'");
	value = value.replace(/\"/g, "\\\"");
	value = value.replace(/</g, "\\x3C");
	value = value.replace(/>/g, "\\x3E");
	value = value.replace(/\n/g, "\\n");
	value = value.replace(/\r/g, "\\n");
	
	return value;
}

// Trim function
// Removes leading and trailing blanks 
function trim(value) {
   var temp = value;
   var obj = /^(\s*)([\W\w]*)(\b\s*$)/;
   if (obj.test(temp)) { temp = temp.replace(obj, '$2'); }
   var obj = / +/g;
   temp = temp.replace(obj, " ");
   if (temp == " ") { temp = ""; }
   return temp;
}

function ensureFloat(strFloat) {
	numFloat = parseFloat(strFloat);
	if (isNaN(numFloat))
		numFloat = 0;
	return numFloat;
}

function ensureDivision(strDividend, strDivisor) {
	var result = 0;
	var numDividend = ensureFloat(strDividend);
	var numDivisor = ensureFloat(strDivisor);
	if (numDivisor != 0) {
		result = numDividend/numDivisor;
	}
	return result;
}

//checks the URL for the querystring value, and replaces it if required		
function replaceQueryString(url,param,value) {
    var re = new RegExp("([?|&])" + param + "=.*?(&|$)","i");
    if (url.match(re))
        return url.replace(re,'$1' + param + "=" + value + '$2');
    else
        return url + '&' + param + "=" + value;
}

//checks the URL for the querystring value, and removes it if found		
function removeQueryString(url,param) {
    var re = new RegExp("([?|&])" + param + "=.*?(&|$)","i");
    if (url.match(re)) {
        url = url.replace(re,'');
        // remove any leftover crud
		url = url.replace( /[&;]$/, "" );
	}
    return url;
}
