
var BSDClass = {
	create: function() {
    	return function() {
    		if(this.initialize) {
	      		this.initialize.apply(this, arguments);
	      	} else if(this.className) {
				BSDLogUtils.error("Couldn't find initialize function for class " + this.className);		      	
			} else {
				BSDLogUtils.error("Couldn't find initialize function for class " + arguments);		      	
	      	}
    	}
  	}
}
BSDStringUtils = {
	DEPENDENCIES: new Array(),
	VERSION: 1.1,
	
	toCamelCaseRegex: /-([a-z])/,
	
	toCamelCase: function(value) {
		var regex = BSDStringUtils.toCamelCaseRegex;
		for(; regex.test(value); value = value.replace(regex, RegExp.$1.toUpperCase()) );
		return value;
	},
	
	trimRegex: /^\s+|\s+$/g,
	
	trim: function(value) {
		var regex = BSDStringUtils.trimRegex;
		value = value.replace(regex, '');
		return value;
	},
	
	equalsTrimmed: function(value1, value2) {
		if(!value1 && !value2) {
			return true;
		}
		if(!value1) {
			return false;
		}
		if(!value2) {
			return false;
		}
		value1 = BSDStringUtils.trim(value1);
		value2 = BSDStringUtils.trim(value2);
		return value1 == value2;
	},
	
	equalsIgnoreCase: function(value1, value2) {
		if(!value1 && !value2) {
			return true;
		}
		if(!value1) {
			return false;
		}
		if(!value2) {
			return false;
		}
		return value1.toLowerCase() == value2.toLowerCase();
	},

	startsWith: function(value, starting) {
		if(!value) {
			return false;
		}
		var regex = new RegExp("^" + starting, "g");
		if(regex.exec(value)) {
			return true;
		}
		return false;
	},
		
	endsWith: function(value, ending) {
		if(!value) {
			return false;
		}
		var regex = new RegExp(ending + "$", "g");
		if(regex.exec(value)) {
			return true;
		}
		return false;
	},
	
	stripWhitespace: function(value) {
		return value.replace(/\s/g, '');
	},
	
	stripHtml: function(value) {
		return value.replace(/<[^\s0-9\.\$\=>][^>]*>/g, '');
	},
	
	truncate: function(value, length) {
		if(value.length > length) {
			value = value.substring(0, length);
		}
		return value;
	},
	
	brToLB: function(value) {
		if(!value) {
			return value;
		}
		value = value.replace("<br/>", "\n");
		value = value.replace("<br>", "\n");
		return value;
	},
	
	lbToBR: function(value) {
		if(!value) {
			return value;
		}
		value = value.replace("\r\n", "<br/>");
		value = value.replace("\r", "<br/>");
		value = value.replace("\n", "<br/>");
		return value;
	}
	
	
}

BSDTypeUtils = {
	DEPENDENCIES: new Array(),
	
	isArray: function(value) {
	    return BSDTypeUtils.isObject(value) && value.constructor == Array;		
	},
	
	isBoolean: function(value) {	
		return typeof value == 'boolean';
	},
		
	isEmpty: function(value) {
	    var i, v;
	    if (isObject(value)) {
	        for (i in value) {
	            v = value[i];
	            if (BSDTypeUtils.isUndefined(v) && BSDTypeUtils.isFunction(v)) {
	                return false;
	            }
	        }
	    }
	    return true;
	}, 
	
	isFunction: function(value) {
	    return typeof value == 'function';	
	},
	
	isNull: function(value) {
		return value == nulll;
	},
	
	isNumber: function(value) {
		return typeof value == 'number'; // && BSDTypeUtils.isFinite(value);
	},
	
	isObject: function(value) {
		return (value && typeof value == 'object');
	},

	isString: function(value) {
		return typeof value == 'string';
	},
	
	isUndefined: function(value) {
		return typeof value == 'undefined';
	}
	
}

BSDBrowserUtils = {
	DEPENDENCIES: new Array(),

	getIsSafari: function() {
		if(!navigator.userAgent) {
			return false;
		}
		return navigator.userAgent.indexOf('Safari/') > -1;
	},
	
	getIsSafari3: function() {
		if(!navigator.userAgent) {
			return false;
		}
		return navigator.userAgent.indexOf('Safari/5') > -1;
	},
	
	getIsWebkit: function() {
		if(!navigator.userAgent) {
			return false;
		}
		return navigator.userAgent.indexOf('WebKit') > -1;
	},
	
	getIsMSIE: function() {
		if(!navigator.userAgent) {
			return false;
		}
		return navigator.userAgent.indexOf('MSIE') > -1;	
	},
	
	getIsMSIE8: function() {
		return BSDBrowserUtils.getIsMSIE() && document.documentMode;
	},
	
	getIsOpera: function() {
		return window.opera;
	},
	
	setCookie: function(name, value, days) {
		var expires;
		if(days) {
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			expires = "; expires="+date.toGMTString();
		} else  {
			var expires = "";
		}
		document.cookie = name + "=" + value+expires + "; path=/";
	},

	getCookieValue: function(name) {
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i = 0; i < ca.length; i++) {
			var c = ca[i];
			while(c.charAt(0) == ' ') {
				c = c.substring(1, c.length);
			}
			if(c.indexOf(nameEQ) == 0)  {
				return c.substring(nameEQ.length, c.length);
			}
		}
		return null;
	},

	eraseCookie: function(name) {
		createCookie(name, "", -1);
	}
	
			
	
	
}	
var bsdObjectsByClassHash;

BSDDOMUtils = {
	DEPENDENCIES: new Array("BSDStringUtils", "BSDTypeUtils", "util/BSDBrowserUtils"),
	VERSION: 1.2,

	setElementValue: function(element, value) {
		if(element.innerHTML) {
			element.innerHTML = value;
		} else if(element.nodeName && element.nodeName == 'input') {
			element.value = value;
		} else if(element.nodeType == 1) {
			var children = element.childNodes;
			for(i = 0; children && i < children.length; i++) {
				var currentChild = children[i];
				element.removeChild(currentChild);
			}
			var newTextNode = document.createTextNode(value);
			element.appendChild(newTextNode);
		} else {
			alert("Couldn't set value for node type " + element.nodeType);
		}
	},

	getAttributeValue: function(element, attributeName) {

		if(!element) {
			return;
		}
		if(element.getAttribute) {
			var currentAttribute = element.getAttribute(attributeName);
			if(currentAttribute) {
				return currentAttribute;
			}
		} else if(element.attributes) {
			var currentAttr = element.attributes[attributeName];
			if(currentAttr) {
				return currentAttr.value;
			}
		} else {

		}	



		
	},
	
	setAttributeValue: function(element, attributeName, attributeValue) {
		element.setAttribute(attributeName, attributeValue);
	},

	removeAttribute: function(element, attributeName) {
		element.removeAttribute(attributeName);
	},

	getObjectById: function(id, doc) {
		if(!doc) {
			doc = document;
		}
		
	    if(doc.getElementById) {
	        return doc.getElementById(id);
	    } else if(doc.all) {
	        return doc.all[id];
	    } else if(doc.layers) {
	        return doc.layers[id];
	    }	
	},
	
	getParentObjectByClass: function(element, className) {
		if(BSDDOMUtils.containsClass(element, className)) {
			return element;
		} else if(element.parentNode) {
			return this.getParentObjectByClass(element.parentNode, className);
		} else {

		}		
	},

	getParentObjectById: function(element, objectId) {
		if(element.id == objectId) {
			return element;
		} else if(element.parentNode) {
			return this.getParentObjectById(element.parentNode, objectId);
		} else {

		}		
	},

	getParentObjectByNodeName: function(element, nodeName, includeCurrent) {
		if(includeCurrent && element.nodeName && element.nodeName.toLowerCase() == nodeName.toLowerCase()) {

			return element;
		}
		if(element.parentNode == element || !element.parentNode || !element.parentNode.nodeName) {

			return null;
		}
		if(element.parentNode.nodeName.toLowerCase() == nodeName.toLowerCase()) {

			return element.parentNode;
		} else {

			return BSDDOMUtils.getParentObjectByNodeName(element.parentNode, nodeName);
		}
	},
	
	getObjectByNodeNameFromParent: function(parent, nodeName, includeCurrent) {
		if(includeCurrent && parent.nodeName && parent.nodeName.toLowerCase() == nodeName) {
			return parent;
		}
		for(var i = 0; i < parent.childNodes.length; i++) {
			var currentChild = parent.childNodes[i];
			if(currentChild.nodeName && currentChild.nodeName.toLowerCase() == nodeName) {
				return currentChild;
			}
		}
		for(var i = 0; i < parent.childNodes.length; i++) {
			var result = BSDDOMUtils.getObjectByNodeNameFromParent(parent.childNodes[i], nodeName, true);
			if(result) {
				return result;
			}
		}
		
	},

	getObjectByIdFromParent: function(parent, id, elementClassToIgnore) {
		if(!parent) {
			return;
		}
		var children = parent.childNodes;
		if(!children) {
			return null;
		}
		if(arguments.length > 3) {
			elementClassToIgnore = new Array();
			for(var i = 2; i < arguments.length; i++) {
				BSDArrayUtils.append(elementClassToIgnore, arguments[i]);
			}
		}
		
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(currentChild.id == id && (!elementClassToIgnore || !BSDDOMUtils.containsClass(currentChild, elementClassToIgnore))) {
				return currentChild;
			}
		}
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(!elementClassToIgnore || !BSDDOMUtils.containsClass(currentChild, elementClassToIgnore)) {
				var childValue = BSDDOMUtils.getObjectByIdFromParent(currentChild, id, elementClassToIgnore);
				if(childValue != null) {
					return childValue;
				}
			}
		}
	    return null;
	}, 

	getObjectByIdPrefixFromParent: function(parent, idPrefix, elementClassToIgnore) {
		var children = parent.childNodes;
		if(!children) {
			return null;
		}
		
		if(arguments.length > 3) {
			elementClassToIgnore = new Array();
			for(var i = 2; i < arguments.length; i++) {
				BSDArrayUtils.append(elementClassToIgnore, arguments[i]);
			}
		}
		
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(currentChild.id && currentChild.id.indexOf(idPrefix) == 0 && (!elementClassToIgnore || !BSDDOMUtils.containsClass(currentChild, elementClassToIgnore))) {
				return currentChild;
			}
		}
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(!elementClassToIgnore || !BSDDOMUtils.containsClass(currentChild, elementClassToIgnore)) {
				var childValue = BSDDOMUtils.getObjectByIdPrefixFromParent(currentChild, idPrefix, elementClassToIgnore);
				if(childValue != null) {
					return childValue;
				}
			}
		}
	    return null;
	}, 

	getObjectsByClass: function(className, parentElement, elementArray, elementClassToIgnore) {
		if(parentElement || elementArray || elementClassToIgnore) {
			return BSDDOMUtils.getObjectsByClassInternal(className, parentElement, elementArray, elementClassToIgnore);
		}
		if(document.getElementsByClassName) {
			return document.getElementsByClassName(className);
		}
		if(!bsdObjectsByClassHash) {
			BSDDOMUtils.buildObjectsByClassHash();
		} 
		
		var elementArray = bsdObjectsByClassHash[className];
		if(!elementArray) {

			elementArray = new Array();
		} 
		
		return elementArray;		
	},

	buildObjectsByClassHash: function() {
		bsdObjectsByClassHash = new Array();
		BSDDOMUtils.buildObjectsByClassHashByElement(document, true);
	},

	buildObjectsByClassHashByElement: function(parentElement) {
		if(!parentElement) {
			BSDLogUtils.error("Got null parentElement for buildObjectsByClassHashByElement");
			return;
		}
		if(parentElement.className) {
	        var split = parentElement.className.split(/\s+/);
	        for(var j = 0; j < split.length; j++) {
	        	var currentClassName = split[j];
	        	if(currentClassName.length < 1) {
	        		continue;
	        	}
				var classElements = bsdObjectsByClassHash[currentClassName];
				if(!classElements) {
					classElements = new Array();
					bsdObjectsByClassHash[currentClassName] = classElements;
				}
				classElements[classElements.length] = parentElement;				
			}			
		}
		
		
		var childNodes = parentElement.childNodes;
		for(var i = 0; childNodes && i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			BSDDOMUtils.buildObjectsByClassHashByElement(currentChild);
		} 
		
	},
	
	getObjectsByClassInternal: function(className, parentElement, elementArray, elementClassToIgnore) {
		if(!elementArray) {
			elementArray = new Array();
		}
		if(!className) {
			return elementArray;
		}
		if(!parentElement) {
			parentElement = document;
		}
	    var children = parentElement.childNodes;
	    if(!children) {
	   		return elementArray;
	    }
	    for(var i = 0; i < children.length; i++) {
	        var currentChild = children[i];
		    if(currentChild.nodeType != 1) {
			    continue;
	        }	
	        
	        var split = currentChild.className.split(/\s+/);
	        for(var j = 0; j < split.length; j++) {
	        	var currentClassName = split[j];
	        	if(!currentClassName || currentClassName.length < 1) {
	        		continue;
	        	}

			    if(currentClassName == className) {
			        var index = elementArray.length;
			        elementArray[index] = currentChild;
		        } else if(elementClassToIgnore && currentClassName == elementClassToIgnore) {

		        	continue;
		        }
		    }	        
	        
		    BSDDOMUtils.getObjectsByClass(className, currentChild, 
							elementArray, elementClassToIgnore);
	    }
	    return elementArray;
	},
	
	getObjectsById: function(id, parentElement, elementArray) {
		if(!elementArray) {
			elementArray = new Array();
		}
		if(!id) {
			return elementArray;
		}
		if(!parentElement) {
			parentElement = document;
		}
	    var children = parentElement.childNodes;
	    if(!children) {
	   		return elementArray;
	    }
	    for(var i = 0; i < children.length; i++) {
	        var currentChild = children[i];
		    if(currentChild.nodeType != 1) {
			    continue;
	        }	
		    if(currentChild.id == id) {
		        var index = elementArray.length;
		        elementArray[index] = currentChild;
	        }
		    BSDDOMUtils.getObjectsById(id, currentChild, 
							elementArray);
	    }
	    return elementArray;
	},

	getObjectsByNodeName: function(parentElement, nodeName, elementArray) {
		if(!elementArray) {
			elementArray = new Array();
		}
		if(!nodeName) {
			return elementArray;
		}
		if(!parentElement) {
			BSDLogUtils.error("ERROR: Got null parentElement for getObjectsByNodeName()");
			return;
		}
	    var children = parentElement.childNodes;
	    if(!children) {
	   		return null;
	    }
	    for(var i = 0; i < children.length; i++) {
	        var currentChild = children[i];
		    if(currentChild.nodeType != 1) {
			    continue;
	        }	
		    if(currentChild.nodeName == nodeName) {
		        var index = elementArray.length;
		        elementArray[index] = currentChild;
	        }
		    BSDDOMUtils.getObjectsByNodeName(currentChild, nodeName, 
							elementArray);
	    }
	    return elementArray;
	},

	getRootElement: function() {
		if(document.documentElement) {
			return document.documentElement;
		}
		return null;
	},
	
	getNextElementSibling: function(element) {
		var sibling = element.nextSibling;
		while(sibling && sibling.nodeType != 1) {
			sibling = sibling.nextSibling;
		}
		return sibling;
	},

	getPreviousElementSibling: function(element) {
		var sibling = element.previousSibling;
		while(sibling && sibling.nodeType != 1) {
			sibling = sibling.previousSibling;
		}
		return sibling;
	},
	
	getElementStyle: function(element, styleName) {
		if(!element.style) {

			return;
		}
		var ieStyleName = BSDStringUtils.toCamelCase(styleName);
		var styleValue = element.style[ieStyleName];
	    if(!styleValue) {
			if(document.defaultView && document.defaultView.getComputedStyle) {
	        	var cssStyleValue = document.defaultView.getComputedStyle(element, "");
	        	if(!cssStyleValue) {
	        		return null;
	        	}
	        	styleValue = cssStyleValue.getPropertyValue(styleName);

	      	} else if(element.currentStyle) {
	        	styleValue = element.currentStyle[ieStyleName];
	      	}
	  	}

		if(styleValue == 'auto') {
			return null;
		}
	  	return styleValue;
	},
	
 
	elementContainsStyle: function(element, stylePropertyName, stylePropertyValue) {
	    stylePropertyValue = stylePropertyValue.toLowerCase();
	    if(element.style && element.style[stylePropertyName] &&
						element.style[stylePropertyName].toLowerCase() == stylePropertyValue) {
			return true;
	    }
	    return false;
	},

	setElementStyle: function(element, stylePropertyName, stylePropertyValue) {
		BSDDOMUtils.changeElementStyle(element, stylePropertyName, stylePropertyValue);
	},
		
	changeElementStyle: function(element, stylePropertyName, stylePropertyValue) {
		if(!element) {
			return;
		}
	    var elementStyle = element.style;
	    if(elementStyle) {
	    	try {
			    elementStyle[stylePropertyName] = stylePropertyValue;
			} catch (err) {  


			}
		}
		if(stylePropertyName == 'background-color') {
			element.style.backgroundColor = stylePropertyValue;
		} else if(stylePropertyName == 'font-family') {
			element.style.fontFamily = stylePropertyValue;
		} else if(stylePropertyName == 'text-align') {
			element.style.textAlign = stylePropertyValue;
		}
	},
	
	cloneElementStyle: function(source, target, stylePropertyName) {
		if(stylePropertyName) {
			var value = BSDDOMUtils.getElementStyle(source, stylePropertyName);
			if(value) {

				BSDDOMUtils.changeElementStyle(target, stylePropertyName, value);
			}
			return value;
		}
	},
	
	cloneAllElementStyles: function(source, target) {
		for(var styleName in source.style) {
			if(styleName) {
				BSDDOMUtils.cloneElementStyle(source, target, styleName);
			}
		}
	
	},
	
	getElementMargin: function(source, tryChildren) {
		var margin = BSDDOMUtils.getElementStyle(source, 'margin');
		var iTop = 0;
		var iRight = 0;
		var iLeft = 0;
		var iBottom = 0;
		if(margin) {
			var split = margin.split(/\s*px\s*/i);
			if(split.length < 1 && margin.length > 0) {
				iTop = parseInt(margin);
			}
			if(split.length > 0) {
				iTop = parseInt(split[0]);
			}
			if(split.length > 1) {
				iRight = parseInt(split[1]);
			}
			if(split.length > 2) {
				iBottom = parseInt(split[2]);
			}
			if(split.length > 3) {
				iLeft = parseInt(split[3]);
			}			
		}
		
		var marginTop = BSDDOMUtils.getElementStyle(source, 'margin-top');
		if(marginTop && marginTop.length > 2) {
			iTop = marginTop.replace(/\s*px\s*/i, '');
		}
		var marginRight = BSDDOMUtils.getElementStyle(source, 'margin-right');
		if(marginRight && marginRight.length > 2) {
			iRight = marginRight.replace(/\s*px\s*/i, '');
		}
		var marginBottom = BSDDOMUtils.getElementStyle(source, 'margin-bottom');
		if(marginBottom && marginBottom.length > 2) {
			iBottom = marginBottom.replace(/\s*px\s*/i, '');
		}
		var marginLeft = BSDDOMUtils.getElementStyle(source, 'margin-left');
		if(marginLeft && marginLeft.length > 2) {
			iLeft = marginLeft.replace(/\s*px\s*/i, '');
		}

		if(tryChildren && iTop == 0 && iRight == 0 && iBottom == 0 && iLeft == 0) {

			var marginChild = null;
			for(var i = 0; i < source.childNodes.length; i++) {
				var currentChild = source.childNodes[i];
				if(currentChild.nodeType == 1 && !BSDVisibilityUtils.isObjectHidden(currentChild)) {
					if(!marginChild) {
						marginChild = currentChild;
					} else {
						marginChild = null;
						break;
					}
				}
				if(marginChild) { 
					return BSDDOMUtils.getElementMargin(marginChild, false);
				}
			}		
		}
		
		var margin = new Object();
		
		margin.margin = iTop + 'px ' + iRight + 'px ' + iBottom + 'px ' + iLeft + 'px';
		margin.top = iTop;
		margin.right = iRight;
		margin.bottom = iBottom;
		margin.left = iLeft;


		return margin;
	},

	
	getElementWidth: function(element) {
		var iWidth = element.offsetWidth;
		if(iWidth && iWidth > 0) {
			return iWidth;
		}
		var width = BSDDOMUtils.getElementStyle(element, 'width');
		if(width && width.length > 0) {
			width = width.replace(/\s*px\s*/i, '');
			return parseInt(width);
		}
		
		width = BSDDOMUtils.getAttributeValue(element, 'width');		
		if(width && width.length > 0) {
			return parseInt(width);
		}
		return 0;
	},
	
	setElementWidth: function(element, newWidth) {
		BSDDOMUtils.changeElementStyle(element, 'width', newWidth + 'px');
	},
	
	getElementHeight: function(element) {
		var iHeight = element.offsetHeight;
		if(iHeight && iHeight > 0) {
			return iHeight;
		}
		var height = BSDDOMUtils.getElementStyle(element, 'height');
		if(height && height.length > 0) {
			height = height.replace(/\s*px\s*/i, '');
			return parseInt(height);
		}
		height = BSDDOMUtils.getAttributeValue(element, 'height');
		if(height && height.length > 0) {
			return parseInt(height);
		}
		return 0;
	},
	
	setElementHeight: function(element, newHeight) {
		BSDDOMUtils.changeElementStyle(element, 'height', newHeight + 'px');
	},
	
	getDocumentWidth: function(doc) {
		if(!doc) {
			doc = document;
		}
		if(doc.body) {
			return doc.body.clientWidth;
		} else if(doc.documentElement) {
			return doc.documentElement.clientWidth;
		}
	},
	
	getDocumentHeight: function(doc) {
		if(!doc) {
			doc = document;
		}
		if(doc.body) {
			return doc.body.clientHeight;
		} else if(doc.documentElement) {
			return doc.documentElement.clientHeight;
		}
	},
	
	cloneElement: function(sourceElement, doShallowClone) {
		var deep = true;
		if(doShallowClone) {
			deep = false;
		}
		return sourceElement.cloneNode(deep);
	},    

	cloneElementDimensions: function(source, target, deltaWidth, deltaHeight) {
	    var newWidth = source.offsetWidth;
	    var newHeight = source.offsetHeight;
	    if(deltaWidth) {
			newWidth += deltaWidth;
	    }
	    if(deltaHeight) {
			newHeight += deltaHeight;
	    }
	    BSDDOMUtils.changeElementStyle(target, 'width', newWidth);
	    BSDDOMUtils.changeElementStyle(target, 'height', newHeight);
	},

	cloneDimensions: function(sourceDimensions, target) { 
	    BSDDOMUtils.changeElementStyle(target, 'width', sourceDimensions.width);
	    BSDDOMUtils.changeElementStyle(target, 'height', sourceDimensions.height);	
	},
	
	cloneElementMargins: function(source, target, tryChildren) {
		var margin = BSDDOMUtils.getElementMargin(source, tryChildren);
		BSDDOMUtils.changeElementStyle(target, 'margin', margin.margin);

		return margin.margin != '0px 0px 0px 0px';		

	},

	createElement: function(nodeName, parent, id, className) {
		return BSDDOMUtils.createElementByDoc(document, nodeName, parent, id, className);
	},

	createElementByDoc: function(doc, nodeName, parent, id, className) {

		var element = doc.createElement(nodeName);	
		if(parent) {
			parent.appendChild(element);
		}
		if(id) {
			element.id = id;
		}
		if(className) {
			element.className = className;
		}
		return element;
	},
	
	removeElement: function(element) {
		var parent = element.parentNode;
		if(!parent) {
			return;
		}
		if(element.nodeName == 'TR') {
			while(parent && parent.nodeName != 'TABLE') {
				parent = parent.parentNode;
			}
			if(parent) {
				parent.deleteRow(element.rowIndex);
			} else {
				BSDLogUtils.error("ERROR: Couldn't find table parent for row to remove");
			}
		} else {
			parent.removeChild(element);
		}
	},
	
	getPreviousSiblingElement: function(element) {
		var sibling = element.previousSibling;
		while(sibling && sibling.nodeType != 1) {
			sibling = sibling.previousSibling;
		}
		return sibling;
	},
	
	getNextSiblingElement: function(element) {
		var sibling = element.nextSibling;
		while(sibling && sibling.nodeType != 1) {
			sibling = sibling.nextSibling;
		}
		return sibling;
	},
	
	setCursor: function(element, cursorName) {
		BSDDOMUtils.changeElementStyle(element, 'cursor', cursorName);
	},

	setMoveCursor: function(element) {
		BSDDOMUtils.setCursor(element, 'move');
	},
	
	setDefaultCursor: function(element) {
		BSDDOMUtils.setCursor(element, 'default');
	},
	
	setClass: function(element, className) {
		element.className = className;
	},
	
	addClass: function(element, className, prepend) {
		if(element.className) {
			if(prepend) {
				element.className = className + " " + element.className;
			} else {
				element.className += " " + className;
			}
		} else {
			element.className = className;
		}
	},
	
	removeClass: function(element, className) {
		if(!element.className || element.className.length < 1) {
			return;
		}
		var newClassName = "";
		var split = element.className.split(/\s+/);
	    for(var i = 0; i < split.length; i++) {
	        var currentClassName = split[i];
	        if(!currentClassName || currentClassName.length < 1) {
	        	continue;
	        }

			if(currentClassName != className) {
				newClassName += currentClassName;
				if(i < split.length -1) {
					newClassName += " ";
				}
			}
		}
		element.className = newClassName;
	},
	
	containsClass: function(element, className) {
		if(!element.className || !className) {
			return false;
		}
		var multipleClasses = BSDTypeUtils.isArray(className);
        var split = element.className.split(/\s+/);
        for(var j = 0; j < split.length; j++) {
        	var currentClassName = split[j];
        	if(!currentClassName || currentClassName.length < 1) {
        		continue;
        	}

        	if(multipleClasses && BSDArrayUtils.contains(className, currentClassName)) {
        		return true;
		    } else if(currentClassName == className) {
		    	return true;
	        } 
	    }		
	    return false;
	},
	
	addChild: function(element, child) {
		element.appendChild(child);
	},
	
	moveElement: function(element, newParent) {
		BSDDOMUtils.removeElement(element);
		BSDDOMUtils.addChild(newParent, element);
	},
	
	replaceElement: function(oldElement, newElement) {
		if(oldElement && oldElement.parentNode) {
			oldElement.parentNode.replaceChild(newElement, oldElement);
		}
	},
	
	replaceElementByIdAndHtml: function(oldElementId, newElementHtml) {
		var oldElement = BSDDOMUtils.getObjectById(oldElementId);
		if(!oldElement) {
			BSDLogUtils.warning("Couldn't find element to replace with id: " + oldElementId);
			return;
		}
		newElementHtml = newElementHtml.replace(/scripx/g, 'script');

		oldElement.innerHTML = newElementHtml;
		var newNode;
		if(oldElement.childNodes && oldElement.childNodes.length && oldElement.childNodes.length == 1) {
			newNode = oldElement.childNodes[0];
			BSDDOMUtils.replaceElement(oldElement, newNode);
		}
		return newNode;
	},
	
	replaceElementByParentId: function(parentElementId) {
		var parentElement = BSDDOMUtils.getObjectById(parentElementId);
		if(!parentElement) {
			BSDLogUtils.warning("Couldn't find parent element to replace with id: " + parentElementId);
			return;
		}

		var elementsToMove = new Array();
		for(var i = 0; i < parentElement.childNodes.length; i++) {

			var currentElement = parentElement.childNodes[i];

			if(currentElement.nodeType != 1) {
				continue;
			}
			var targetId = BSDDOMUtils.getAttributeValue(currentElement, 'rid');
			if(!targetId) {
				BSDLogUtils.warning("Couldn't find target id for element to replace: " + currentElement.id);
				continue;
			}
			var target = BSDDOMUtils.getObjectById(targetId);
			if(!target) {
				BSDLogUtils.warning("Couldn't find target element to replace: " + targetId);
				continue;
			}
			
			var currentHolder = new Object();
			currentHolder.source = currentElement;
			currentHolder.target = target;
			elementsToMove[elementsToMove.length] = currentHolder;
		}
		for(var i = 0; i < elementsToMove.length; i++) {
			var currentHolder = elementsToMove[i];
			BSDDOMUtils.replaceElement(currentHolder.target, currentHolder.source);

		}
	

	},
	
	addText: function(element, text) {
		var textNode = document.createTextNode(text);
		element.appendChild(textNode);
	},
	
	setText: function(element, text) {
		if(!element || !element.childNodes) {
			BSDLogUtils.error("Cannot set text on null element");
			return;
		}
		if(element.nodeName && element.nodeName.toLowerCase() == 'input') {
			element.value = text;
		} else if(element.nodeName && element.nodeName.toLowerCase() == 'select') {
			if(!text || text.length < 1) {
				if(element.options && element.options.length > 0) {
					element.options[0].selected = true;
					return;
				}
			}
			for(var i = 0; i < element.childNodes.length; i++) {
				var currentChild = element.childNodes[i];
				var value = currentChild.value;
				if(value && value == text) {
					currentChild.selected = true;
					break;
				} else if(!value && BSDDOMUtils.getText(element) == text) {
					currentChild.selected = true;
					break;
				}
			}
		} else if(element.nodeType == 3) {
			element.nodeValue = text;
		} else {			
			for(var i = 0; i < element.childNodes.length; i++) {
				if(element.childNodes[i].nodeType == 3) {
					element.removeChild(element.childNodes[i]);
					i--;
				} 
			}
			BSDDOMUtils.addText(element, text);
		}
		
	},
	
	setTextById: function(elementId, text, parentElement) {
		var element;
		if(parentElement) {
			element = BSDDOMUtils.getObjectByIdFromParent(parentElement, elementId);
		} else {
			element = BSDDOMUtils.getObjectById(elementId);
		}
		if(!element) {
			return;
		}
		BSDDOMUtils.setText(element, text);
		return element;
	},
	
	setTextByClass: function(elementClass, text, parentElement) {
		var objects = BSDDOMUtils.getObjectsByClass(elementClass, parentElement);
		for(var i = 0; i < objects.length; i++) {
			BSDDOMUtils.setText(objects[i], text);
		}
		return objects;
	},
	
	getText: function(element) {
		var text = "";
		if(!element) {
			return text;
		}
		if(element.nodeName && element.nodeName.toLowerCase() == 'input') {
			return element.value;
		}
		
		if(element.nodeType == 3) {
			return element.nodeValue;
		} else if(!element.childNodes) {
			return text;
		}
		for(var i = 0; i < element.childNodes.length; i++) {
			if(element.childNodes[i].nodeType == 3) {
				text += element.childNodes[i].nodeValue;
			}		
		}
		return text;
	},
	
	getTextById: function(elementId, parentNode) {
		var element;
		if(parentNode) {
			element = BSDDOMUtils.getObjectByIdFromParent(parentNode, elementId);
		} else {
			element = BSDDOMUtils.getObjectById(elementId);
		}
		if(!element) {
			return;
		}
		return BSDDOMUtils.getText(element);	
	},
	
	appendElementToRoot: function(element) {
		if(document.body) {
			document.body.appendChild(element);
		} else {
			for(var i = 0; i < document.childNodes.length; i++) {
				document.childNodes[i].appendChild(element);
			}
		}
	},
	
	clear: function(element) {
		if(element.nodeName.toLowerCase() == 'table' && element.tBodies && element.tBodies.length > 0) {
			for(var i = 0; i < element.tBodies.length; i++) {

				BSDDOMUtils.clear(element.tBodies[i]);
			}
		} else {
		    while(element.childNodes.length > 0) {

				element.removeChild(element.childNodes[0]);
			}
		}	
	},
	
	getContainsChildElements: function(element, exceptionClass) {
		if(!element || !element.childNodes) {
			return false;
		}
		for(var i = 0; i < element.childNodes.length; i++) {
			var currentChild = element.childNodes[i];
			if(exceptionClass && BSDDOMUtils.containsClass(currentChild, exceptionClass)) {
				continue;
			}
			if(currentChild.nodeType == 1) {
				return true;
			}
		}
		return false;
	},
	
	insertAfter: function(existingElement, newElement) {
		var parentNode = existingElement.parentNode;
		if(!parentNode) {
			return false;
		}
		if(existingElement.nextSibling) {
			parentNode.insertBefore(newElement, existingElement.nextSibling);
		} else {
			parentNode.appendChild(newElement);
		}
		
		return true;
	},
	
	insertBefore: function(existingElement, newElement) {
		var parentNode = existingElement.parentNode;
		if(!parentNode) {
			return false;
		}

		if(existingElement.nodeName.toUpperCase() == 'TR' && !newElement.nodeName.toUpperCase() == 'TR') {
			var row = document.createElement('tr');
			if(!newElement.nodeName.toUpperCase() == 'TD') {
				var column = document.createElement('td');
				column.appendChild(newElement);
				row.appendChild(column);
			} else {
				row.appendChild(newElement);
			}
			newElement = row;
		} 

		parentNode.insertBefore(newElement, existingElement);
		
		return true;
	},
	
	insertChild: function(parentElement, newElement, index) {
		if(!parentElement) {
			BSDLogUtils.error("Got null parentElement for insertChild");
			return;
		}
		var childNodes = parentElement.childNodes;
		if(index >= 0 && childNodes.length > index) {
			BSDDOMUtils.insertBefore(childNodes[index], newElement);
		} else {
			BSDDOMUtils.addChild(parentElement, newElement);
		}
	},
	
	getElementParentIndex: function(element) {
		var parentNode = element.parentNode;
		for(var i = 0; i < parentNode.childNodes.length; i++){
			if(parentNode.childNodes[i] == element) {
				return i;
			}
		}
	},
	
	appendAsRow: function(table, rowContents) {
		var row = document.createElement('tr');
		var column = document.createElement('td');
		
		column.innerHTML = rowContents;
		table.tBodies[0].appendChild(row);
		row.appendChild(column);
	},
	
	setInnerHTML: function(element, content) {
  		if(element.nodeName.toUpperCase() == 'TABLE') {
  			BSDDOMUtils.clear(element);
  			if(BSDStringUtils.startsWith(content, '<tr') && element.tBodies && element.tBodies.length > 0) {
  				element.tBodies[0].innerHTML = content;
  			} else {
				BSDDOMUtils.appendAsRow(element, content);
			}
  		} else {
	  		element.innerHTML = content;
	  	}
	
	},
	
	getFrameDocument: function(frame) {
		if(frame.contentDocument) {
			return frame.contentDocument;
		} else if(frame.contentWindow && frame.contentWindow.document) {
			return frame.contentWindow.document;
		} else {
			return frame.document; //ie
		}
	},
	
	getRangeObject: function(selectionObject, doc) {
		if(selectionObject.getRng && selectionObject.getRng()) {
			return selectionObject.getRng();
		} else if(selectionObject.getRangeAt) {
			return selectionObject.getRangeAt(0);
		} else { // Safari!
			if(!doc) {
				doc = document;
			}
			var range = doc.createRange();
			if(selectionObject.anchorNode) {
				range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
			}
			if(selectionObject.focusNode) {
				range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
			}
			return range;
		}
	},
	
	copyTable: function(source, target, removeExisting) {

		var tbody = null;
		for(var i = 0; i < target.childNodes.length; i++) {
			var currentChild = target.childNodes[i];
			if(currentChild.nodeName == 'TBODY') {
				tbody = currentChild;
			} else if(removeExisting) {
				BSDDOMUtils.removeElement(currentChild);
			}
		}

		if(tbody && removeExisting) {

			for(var i = 0; i < tbody.childNodes.length; i++) {
				BSDDOMUtils.removeElement(tbody.childNodes[i]);
			}	
		} else if(!tbody) {
			tbody = target;
		}
		for(var i = 0; i < source.childNodes.length; i++) {
			var currentChild = source.childNodes[i];
			if(currentChild.nodeName == 'TBODY') {
				for(var j = 0; j < currentChild.childNodes.length; j++) {
					tbody.appendChild(currentChild.childNodes[j]);

				}	
			} else {
				tbody.appendChild(currentChild);
			}
		}

	},
	
	wrapSelectionByElement: function(selectionObject, doc, newElement) {
		var range = BSDDOMUtils.getRangeObject(selectionObject, doc);
		if(!range || !range.startContainer || range.collapsed) {

			return null;
		}

		if(range.startContainer.nodeType == 3) { //text
			var text = BSDDOMUtils.getText(range.startContainer);
			if(text.length == range.startOffset - range.endOffset) { //the whole element is selected - return it
				return range.startContainer;
			}
			var startText; 
			var endText;
			var selectText;
			if(range.startOffset > 0) {
				startText = text.substring(0, range.startOffset);
			}
			if(range.endContainer == range.startContainer) {
				selectText = text.substring(range.startOffset, range.endOffset);
				endText = text.substring(range.endOffset);
			} else {
				selectText = text.substring(range.startOffset);
			}

			var startNode;
			if(startText && startText.length > 0) {
				startNode = doc.createTextNode(startText);
			}
			if(range.endContainer == range.startContainer) {
				var endNode;
				if(endText && endText.length > 0) {
					endNode = doc.createTextNode(endText);
				}
				BSDDOMUtils.setText(newElement, selectText);
				if(endNode) {
					BSDDOMUtils.insertAfter(range.startContainer, endNode);			
				}
				if(startNode) {
					BSDDOMUtils.insertAfter(range.startContainer, startNode);
				}
				BSDDOMUtils.insertAfter(range.startContainer, newElement);
			} else {
				if(startNode) {
					BSDDOMUtils.insertBefore(range.startContainer, startNode);
				}
				if(selectText) {
					var selectTextNode = doc.createTextNode(selectText);
					BSDDOMUtils.addChild(newElement, selectTextNode);
				}
				var nextSibling = range.startContainer.nextSibling;
				while(nextSibling) {


					if(nextSibling == range.endContainer && nextSibling.nodeType == 3) {
						var curText = BSDDOMUtils.getText(nextSibling);
						var endText1 = curText.substring(0, range.endOffset);
						var endNode1 = doc.createTextNode(endText1);
						BSDDOMUtils.addChild(newElement, endNode1);
						if(range.endOffset > curText.length) {
							var endText2 = curText.substring(range.endOffset);
							var endNode2 = doc.createTextNode(endText2);
							BSDDOMUtils.insertAfter(range.startContainer, endNode2);
						}
						BSDDOMUtils.removeElement(range.endContainer);						
						nextSibling = null;
					} else {
						var nextNextSibling = nextSibling.nextSibling;
						BSDDOMUtils.addChild(newElement, nextSibling);						
						if(nextSibling != range.endContainer) {
							nextSibling = nextNextSibling;
						} else {
							nextSibling = null;
						}
					}

				}
				BSDDOMUtils.insertBefore(range.startContainer, newElement);
			}
			

			BSDDOMUtils.removeElement(range.startContainer);

		} else if(range.startContainer && range.startContainer.nodeType == 1) {
			var hasInserted = false;
			if(!hasInserted) {
				BSDDOMUtils.insertChild(range.startContainer, newElement, range.startOffset);
			}
			if(range.startContainer.nodeList) {
				for(var i = range.startOffset; i < range.endOffset && i < range.startContainer.nodeList.length; i++) {
					var currentChild = range.startContainer.nodeList[i];
					BSDDOMUtils.removeElement(currentChild);
					newElement.appendChild(currentChild);
				}
			}
		}
		
		return newElement;
	},
	
	clearStyles: function(parent, removeEmptySpans) {
		if(parent.style) {

			parent.style.cssText = '';
		}
		var childNodes = parent.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			if(currentChild.nodeType == 1) {
				BSDDOMUtils.clearStyles(currentChild, removeEmptySpans);
			}
		}
		if(removeEmptySpans && parent.nodeName == 'SPAN' && !parent.id && !parent.className) {
			for(var i = 0; i < childNodes.length; i++) {
				BSDDOMUtils.insertBefore(parent, childNodes[i]);			
			}
			BSDDOMUtils.removeElement(parent);
		}
	},
	
	convertDoubleBrToP: function(parent, doc, currentParent, depth) {




		if(!doc) {
			doc = parent.ownerDocument;
		}
		if(!depth) {
			depth = 0;
		}
		var prevBr;
		if(!currentParent) {
			currentParent = parent;
		}
		var childNodes = currentParent.childNodes;
		var moveArray = new Array();
		var moveParentArray = new Array();
		var deleteArray = new Array();
		var prevElement = parent;



		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			var isBr = currentChild.nodeName == 'BR';

			/*if(isBr) {
				alert("Got br");
			} else {
				alert("Got other: " + currentChild.nodeName + " " + currentChild.id + " " + currentChild.nodeValue);
			}*/

			if(isBr && prevBr) {
				BSDArrayUtils.append(deleteArray, currentChild);
				prevBr = null;
				continue; //skip this one, as we must have already created a p for the previous br
			} else if(isBr) {
				prevBr = currentChild;
				var newP = BSDDOMUtils.createElementByDoc(doc, "p");
				currentParent = newP;
				newP.bsdIndex = i;
				BSDArrayUtils.append(deleteArray, currentChild);

			} else if(currentChild.nodeName == 'DIV' && currentChild.id == '_mcePaste') {
				var hadParent = false;
				BSDArrayUtils.append(deleteArray, currentChild);
				/* as of 1/23/2010, webkit browsers send both divs like this and plain text nodes. ignore the divs and the plain text will get put into p nodes automatically
				continue;
				if(true) {
					continue;
				}
				if(currentChild.innerHTML == '<br style="">') {
					continue;
				}
				if(currentParent) {

					hadParent = true;
				}

				currentParent = BSDDOMUtils.createElementByDoc(doc, "p");				
				currentParent.innerHTML = currentChild.innerHTML;
				currentParent.id = i;


				BSDArrayUtils.append(moveArray, currentChild);
				BSDArrayUtils.append(moveParentArray, currentParent);

				prevElement = currentParent;
				if(hadParent) {
					currentParent = BSDDOMUtils.createElementByDoc(doc, "p");
				}
				*/
			} else if(currentChild.nodeType == 1) {

				prevElement = currentChild;
			}
			if(currentChild.nodeType == 3 && !isBr && currentParent && currentParent != parent && currentChild.nodeValue && BSDStringUtils.trim(currentChild.nodeValue).length > 0) { //move text nodes
				currentParent.innerHTML = currentChild.nodeValue;
				/*if(parent.parentNode) {
					BSDDOMUtils.insertAfter(prevElement, currentParent);					
				}*/
				/*if(!isBr && prevBr) {
					BSDArrayUtils.append(moveArray, prevBr);
					BSDArrayUtils.append(moveParentArray, currentParent);
				}*/

				BSDArrayUtils.append(moveArray, currentChild);
				BSDArrayUtils.append(moveParentArray, currentParent);
				prevElement = currentParent;
			}
			if(!isBr) {
				prevBr = null;				
			}

			
		}	

		for(var i = 0; i < moveArray.length; i++) {
			var currentChild = moveArray[i];
			var currentParent = moveParentArray[i];
			/*if(!currentChild.nodeValue && currentChild.childNodes.length < 1) {
				continue;
			}*/


			BSDDOMUtils.replaceElement(currentChild, currentParent);


			if(!currentParent.parentNode && !BSDBrowserUtils.getIsWebkit()) {
				parent.appendChild(currentParent); //ff doesn't have a parent for the pasted node
			}
		}	
		for(var i = 0; i < deleteArray.length; i++) {
			var currentChild = deleteArray[i];
			BSDDOMUtils.removeElement(currentChild);
		}

	},
	
	isBlockElement: function(currentChild) {
		return currentChild.nodeName == 'P' || currentChild.nodeName == 'DIV' || currentChild.nodeName == 'UL' 
				|| currentChild.nodeName == 'BLOCKQUOTE' || currentChild.nodeName == 'DL' || currentChild.nodeName == 'FORM'
				|| currentChild.nodeName == 'HR' || currentChild.nodeName == 'OL' || currentChild.nodeName == 'TABLE';
	}

}
BSDArrayUtils = {
	DEPENDENCIES: new Array("BSDTypeUtils"),
	
	insert: function(array, value, index) {
		if(array.splice && BSDTypeUtils.isArray(value)) {
			for(var i = 0; i < value.length; i++) {
				array.splice(index + i, 0, value[i]);
			}		
		} else if(array.splice) {
			array.splice(index, 0, value);
		} else if(BSDTypeUtils.isArray(value)) {
			for(var i = array.length - 1 + value.length; i > index; i--) {
				array[i] = array[i-1];			
			}
			for(var i = 0; i < value.length; i++) {
				array[index + i] = value[i];
			}		
		} else {
			for(var i = array.length; i > index; i--) {
				array[i] = array[i-1];			
			}
			array[index] = value;
		}
	},
	
	append: function(array, value) {
		if(array.push && !BSDTypeUtils.isArray(value)) {
			array.push(value);
		} else if(BSDTypeUtils.isArray(value)) {
			var j = 0;
			var newLength = array.length + value.length;
			for(var i = array.length; i < newLength; i++) {
				array[i] = value[j];
				j++
			}
		} else {	
			array[array.length] = value;
		}
	},
	
	deleteElement: function(array, index, count) {
		if(!count) {
			count = 1;
		}
		if(array.splice) {
			array.splice(index, count);
			return array;
		} else {
			var newArray = new Array();
			for(var i = 0; i < array.length; i++) {
				if(i < index && i >= index + count) {
					BSDArrayUtils.append(newArray, array[i]);
				}
			}
			return newArray;
		}
	}, 
	
	replace: function(array, index, value) {
		array[index] = value;
	},
	
	copy: function(sourceArray, targetArray) {
		var j = targetArray.length;
		for(var i = 0; i < sourceArray.length; i++) {
			targetArray[j + i] = sourceArray[i];
		}
	},
	
	toCommaDelimitedString: function(sourceArray) {
		var value = "";
		for(var i = 0; i < sourceArray.length; i++) {
			value += sourceArray[i];
			if(i < sourceArray.length - 1) {
				value += ",";
			}
		}	
		return value;
	},
	
	insertUnique: function(array, value, index) {
		for(var i = 0; i < array.length; i++) {
			if(array[i] == value) {
				BSDArrayUtils.deleteElement(array, i);
				break;
			}
		}
		BSDArrayUtils.insert(array, value, index);
	},
	
	contains: function(array, value) {
		for(var i = 0; i < array.length; i++) {
			if(array[i] == value) {
				return true;
			}
		}
		return false;
	}
}


BSDLogUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDClass"),
	VERSION: 1.1,

	isLogWindowEnabled: false,
	
	debugEnabled: true,
	warningEnabled: true,
	errorEnabled: true,
	
	logStatements: new Array(),

	showLogWindow: function() {
		var logElement = BSDLogUtils.logElement;
		if(!logElement) {
			logElement = BSDDOMUtils.getObjectById("BSDLogWindow");
		}

		if(!logElement) {
			logElement = BSDDOMUtils.createElement("div");
			logElement.id = "BSDLogWindow";
			BSDDOMUtils.changeElementStyle(logElement, 'position', 'absolute');			
			BSDDOMUtils.changeElementStyle(logElement, 'text-align', 'left');	
			BSDLogUtils.logElement = logElement;
			document.body.appendChild(logElement);

			BSDLogUtils.showLogStatements();
		}
		BSDDOMUtils.changeElementStyle(logElement, "top", 0); // + currentScrollPosition.y);
		BSDDOMUtils.changeElementStyle(logElement, "left", 450); // + currentScrollPosition.x);
				
	},

	showLogStatements: function() {
		var logElement = BSDLogUtils.logElement;
		for(var i = 0; i < BSDLogUtils.logStatements.length; i++) {
			var currentStatement = BSDLogUtils.logStatements[i];
			if(currentStatement.isError && !BSDLogUtils.errorEnabled) {
				continue;
			} else if(currentStatement.isWarning && !BSDLogUtils.warningEnabled) {
				continue;
			} else if(currentStatement.isDebug && !BSDLogUtils.debugEnabled) {
				continue;
			}
			
			BSDLogUtils.displayLogStatement(currentStatement);
		}
	},
	
	displayLogStatement: function(statement) {
		var logElement = BSDLogUtils.logElement;

		var statementElement = BSDDOMUtils.createElement("div", logElement, null, "BSDLogStatement");			
		statementElement.statementId = statement.id;

		var statementDateElement = BSDDOMUtils.createElement("span", statementElement, null, "BSDLogStatementDate");
		statementDateElement.innerHTML = statement.date.getHours() + ":" + statement.date.getMinutes() + ":" + statement.date.getSeconds();

		var statementTypeElement = BSDDOMUtils.createElement("span", statementElement, null, "BSDLogStatementType");
		statementTypeElement.innerHTML = statement.type;
		
		var statementMsgElement = BSDDOMUtils.createElement("span", statementElement, null, "BSDLogStatementMessage");
		statementMsgElement.innerHTML = statement.message;

	},		
	
	error: function(message) {
		var newStatement = new BSDLogStatement(BSDLogUtils.logStatements.length, "ERROR", message);
		BSDArrayUtils.append(BSDLogUtils.logStatements, newStatement);
		if(BSDLogUtils.errorEnabled) {
			BSDLogUtils.displayLogStatement(newStatement);		
		}
	},
	
	warning: function(message) {
		var newStatement = new BSDLogStatement(BSDLogUtils.logStatements.length, "WARNING", message);
		BSDArrayUtils.append(BSDLogUtils.logStatements, newStatement);
		if(BSDLogUtils.warningEnabled) {
			BSDLogUtils.displayLogStatement(newStatement);		
		}
	},
	
	debug: function(message) {
		var newStatement = new BSDLogStatement(BSDLogUtils.logStatements.length, "DEBUG", message);
		BSDArrayUtils.append(BSDLogUtils.logStatements, newStatement);
		if(BSDLogUtils.debugEnabled) {
			BSDLogUtils.displayLogStatement(newStatement);		
		}
	},
	
	registerEvent: function(element, type, func) {
	    if(element.addEventListener) {
			element.addEventListener(type, func, true);
	    } else if(element.attachEvent) {
			element.attachEvent('on' + type, func);
	    } else {
	    	alert("ERROR: Couldn't register event: " + type + " " + func);
	    }

	},
	
	recordImageTime: function(src) {
		var image = new Image();
		image.src = src;
		var breakBlock = BSDDOMUtils.getObjectById('kcmBreakBlock');
		if(!breakBlock) {
			breakBlock = document.body;
		}
		BSDDOMUtils.insertChild(breakBlock, image, 0);

	}
	
}

if(BSDLogUtils.isLogWindowEnabled) {
	BSDLogUtils.registerEvent(window, "load", BSDLogUtils.showLogWindow);
}


BSDLogStatement = BSDClass.create();
BSDLogStatement.prototype = {

	className: "BSDLogStatement",
	initialize: function(id, type, message) {
		this.id = id;
	    this.type = type;
		this.message = message;
		this.date = new Date();
	},
	
	isError: function() {
		if(this.type == 'ERROR') {
			return true;
		}
		return false;
	},

	isWarning: function() {
		if(this.type == 'WARNING') {
			return true;
		}
		return false;
	},

	isDebug: function() {
		if(this.type == 'DEBUG') {
			return true;
		}
		return false;
	}

}

BSDDateUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDLogUtils"),
	MILLISECOND: 14,
	SECOND: 13,
	MINUTE: 12,
	HOUR_OF_DAY: 11,
	HOUR: 10,
	AM_PM: 9,
	DAY_OF_WEEK_IN_MONTH: 8,
	DAY_OF_WEEK: 7,
	DAY_OF_YEAR: 6,
	DAY_OF_MONTH: 5,
	WEEK_OF_MONTH: 4,
	WEEK_OF_YEAR: 3,
	MONTH: 2,
	YEAR: 1,
	
	parse: function(strDate) {
		if(strDate.indexOf("-") > 0) {
			strDate = strDate.replace(/\-/g, "/");
		}
		var uTime = Date.parse(strDate);
		return new Date(uTime);
	},
	
	formatTime: function(date, pattern) {
		if(pattern && pattern == 'timeAgo') {
			return BSDDateUtils.formatTimeAgo(date, BSDDateUtils.MINUTE, BSDDateUtils.YEAR); 
		} else {
			return date.toString();
		}	
	},
	
	formatTimeAgo: function(date, minTimeUnit, maxTimeUnit, messageSuffix) {
		var buf = "";
		var currentTime = new Date().getTime();
    	var previousTime = date.getTime();
    	var timeSec = Math.abs(currentTime - previousTime)/1000;
    	timeSec = Math.floor(timeSec);
    	var timeMin = Math.floor(timeSec/60);
    	var timeAdjusted = 0;
    	var timeUnit = null;
    	if(timeSec < 60 && BSDDateUtils.SECOND <= minTimeUnit && BSDDateUtils.SECOND >= maxTimeUnit) {
    		timeAdjusted = timeSec;
    		timeUnit = "second";
    	} else if(timeMin < 60 && BSDDateUtils.MINUTE <= minTimeUnit && BSDDateUtils.MINUTE >= maxTimeUnit) {
    		timeAdjusted = timeMin;
    		timeUnit = "minute";
    	} else if(timeMin < 60*24 && BSDDateUtils.HOUR_OF_DAY <= minTimeUnit && BSDDateUtils.HOUR_OF_DAY >= maxTimeUnit) {
        	timeAdjusted = Math.floor(timeMin/60);
        	timeUnit = "hour";
    	} else if(timeMin < 60*24*7 && BSDDateUtils.DAY_OF_WEEK <= minTimeUnit && BSDDateUtils.DAY_OF_WEEK >= maxTimeUnit) {
        	timeAdjusted = Math.floor(timeMin/(60*24));
        	timeUnit = "day";
    	} else if(timeMin < 60*24*30 && BSDDateUtils.WEEK_OF_MONTH <= minTimeUnit && BSDDateUtils.WEEK_OF_MONTH >= maxTimeUnit) {
        	timeAdjusted = Math.floor(timeMin/(60*24*7));
        	timeUnit = "week";
    	} else if(timeMin < 60*24*365 && BSDDateUtils.MONTH  <= minTimeUnit && BSDDateUtils.MONTH >= maxTimeUnit) {
        	timeAdjusted = Math.floor(timeMin/(60*24*30));
        	timeUnit = "month";
    	} else if(timeMin < 60*24*30 && BSDDateUtils.YEAR <= minTimeUnit && BSDDateUtils.YEAR >= maxTimeUnit) {
        	timeAdjusted = Math.floor(timeMin/(60*24*365));
        	timeUnit = "year";
    	}
    	
    	if(timeUnit == null) {
    		return null;
    	}
    	if(timeAdjusted < 1) {
    		buf += "less than ";
    		if(timeUnit.search("^h") > -1) {
    			buf += "an";
    		} else {
    			buf += "a";    			
    		}
    	} else {
    		buf += timeAdjusted;
    	}
    	buf += " ";
    	buf += timeUnit;
    	if(timeAdjusted > 1.99) {
    		buf += "s";
    	}
    	if(messageSuffix) {	
    		buf += messageSuffix;
    	}
		
		return buf;
	},
	
	coordinateDateSelectById: function(sourceId, targetId, increment) {
		var source = BSDDOMUtils.getObjectById(sourceId);
		var target = BSDDOMUtils.getObjectById(targetId);
		if(!source) {
			BSDLogUtils.error("Couldn't find date select with id " + sourceId);
			return;
		}
		if(!target) {
			BSDLogUtils.error("Couldn't find date select with id " + targetId);
			return;
		}
		
		var selectedIndex = source.selectedIndex;
		if(selectedIndex == null) {
			selectedIndex = 0;
		}	
		if(!increment) {
			increment = 0;
		}
		target.selectedIndex = selectedIndex + increment;
		
	}
	
	
	
	
}




/**
 * Copyright 2008 Tim Down.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * log4javascript
 *
 * log4javascript is a logging framework for JavaScript based on log4j
 * for Java. This file contains all core log4javascript code and is the only
 * file required to use log4javascript, unless you require support for
 * document.domain, in which case you will also need console.html, which must be
 * stored in the same directory as the main log4javascript.js file.
 *
 * Author: Tim Down <tim@log4javascript.org>
 * Version: 1.4
 * Edition: log4javascript
 * Build date: 30 October 2008
 * Website: http://log4javascript.org
 */
	/* ---------------------------------------------------------------------- */


	var SimpleDateFormat;

	(function() {
		var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/;
		var monthNames = ["January", "February", "March", "April", "May", "June",
			"July", "August", "September", "October", "November", "December"];
		var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
		var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
		var types = {
			G : TEXT2,
			y : YEAR,
			M : MONTH,
			w : NUMBER,
			W : NUMBER,
			D : NUMBER,
			d : NUMBER,
			F : NUMBER,
			E : TEXT3,
			a : TEXT2,
			H : NUMBER,
			k : NUMBER,
			K : NUMBER,
			h : NUMBER,
			m : NUMBER,
			s : NUMBER,
			S : NUMBER,
			Z : TIMEZONE
		};
		var ONE_DAY = 24 * 60 * 60 * 1000;
		var ONE_WEEK = 7 * ONE_DAY;
		var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;

		var newDateAtMidnight = function(year, month, day) {
			var d = new Date(year, month, day, 0, 0, 0);
			d.setMilliseconds(0);
			return d;
		};

		Date.prototype.getDifference = function(date) {
			return this.getTime() - date.getTime();
		};

		Date.prototype.isBefore = function(d) {
			return this.getTime() < d.getTime();
		};

		Date.prototype.getUTCTime = function() {
			return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
					this.getSeconds(), this.getMilliseconds());
		};

		Date.prototype.getTimeSince = function(d) {
			return this.getUTCTime() - d.getUTCTime();
		};

		Date.prototype.getPreviousSunday = function() {

			var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
			var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
			return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
					previousSunday.getDate());
		};

		Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
			if (isUndefined(this.minimalDaysInFirstWeek)) {
				minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
			}
			var previousSunday = this.getPreviousSunday();
			var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
			var numberOfSundays = previousSunday.isBefore(startOfYear) ?
				0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
			var numberOfDaysInFirstWeek =  7 - startOfYear.getDay();
			var weekInYear = numberOfSundays;
			if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
				weekInYear--;
			}
			return weekInYear;
		};

		Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
			if (isUndefined(this.minimalDaysInFirstWeek)) {
				minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
			}
			var previousSunday = this.getPreviousSunday();
			var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
			var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
				0 : 1 + Math.floor(previousSunday.getTimeSince(startOfMonth) / ONE_WEEK);
			var numberOfDaysInFirstWeek =  7 - startOfMonth.getDay();
			var weekInMonth = numberOfSundays;
			if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
				weekInMonth++;
			}
			return weekInMonth;
		};

		Date.prototype.getDayInYear = function() {
			var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
			return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
		};

		/* ------------------------------------------------------------------ */

		SimpleDateFormat = function(formatString) {
			this.formatString = formatString;
		};

		/**
		 * Sets the minimum number of days in a week in order for that week to
		 * be considered as belonging to a particular month or year
		 */
		SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
			this.minimalDaysInFirstWeek = days;
		};

		SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function() {
			return isUndefined(this.minimalDaysInFirstWeek)	?
				DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
		};

		var padWithZeroes = function(str, len) {
			while (str.length < len) {
				str = "0" + str;
			}
			return str;
		};

		var formatText = function(data, numberOfLetters, minLength) {
			return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
		};

		var formatNumber = function(data, numberOfLetters) {
			var dataString = "" + data;

			return padWithZeroes(dataString, numberOfLetters);
		};

		SimpleDateFormat.prototype.format = function(date) {
			var formattedString = "";
			var result;
			var searchString = this.formatString;
			while ((result = regex.exec(searchString))) {
				var quotedString = result[1];
				var patternLetters = result[2];
				var otherLetters = result[3];
				var otherCharacters = result[4];

				if (quotedString) {
					if (quotedString == "''") {
						formattedString += "'";
					} else {
						formattedString += quotedString.substring(1, quotedString.length - 1);
					}
				} else if (otherLetters) {

				} else if (otherCharacters) {

					formattedString += otherCharacters;
				} else if (patternLetters) {

					var patternLetter = patternLetters.charAt(0);
					var numberOfLetters = patternLetters.length;
					var rawData = "";
					switch(patternLetter) {
						case "G":
							rawData = "AD";
							break;
						case "y":
							rawData = date.getFullYear();
							break;
						case "M":
							rawData = date.getMonth();
							break;
						case "w":
							rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
							break;
						case "W":
							rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
							break;
						case "D":
							rawData = date.getDayInYear();
							break;
						case "d":
							rawData = date.getDate();
							break;
						case "F":
							rawData = 1 + Math.floor((date.getDate() - 1) / 7);
							break;
						case "E":
							rawData = dayNames[date.getDay()];
							break;
						case "a":
							rawData = (date.getHours() >= 12) ? "PM" : "AM";
							break;
						case "H":
							rawData = date.getHours();
							break;
						case "k":
							rawData = date.getHours() || 24;
							break;
						case "K":
							rawData = date.getHours() % 12;
							break;
						case "h":
							rawData = (date.getHours() % 12) || 12;
							break;
						case "m":
							rawData = date.getMinutes();
							break;
						case "s":
							rawData = date.getSeconds();
							break;
						case "S":
							rawData = date.getMilliseconds();
							break;
						case "Z":
							rawData = date.getTimezoneOffset(); // This returns the number of minutes since GMT was this time.
							break;
					}

					switch(types[patternLetter]) {
						case TEXT2:
							formattedString += formatText(rawData, numberOfLetters, 2);
							break;
						case TEXT3:
							formattedString += formatText(rawData, numberOfLetters, 3);
							break;
						case NUMBER:
							formattedString += formatNumber(rawData, numberOfLetters);
							break;
						case YEAR:
							if (numberOfLetters <= 3) {

								var dataString = "" + rawData;
								formattedString += dataString.substr(2, 2);
							} else {
								formattedString += formatNumber(rawData, numberOfLetters);
							}
							break;
						case MONTH:
							if (numberOfLetters >= 3) {
								formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
							} else {

								formattedString += formatNumber(rawData + 1, numberOfLetters);
							}
							break;
						case TIMEZONE:
							var isPositive = (rawData > 0);


							var prefix = isPositive ? "-" : "+";
							var absData = Math.abs(rawData);

							var hours = "" + Math.floor(absData / 60);
							hours = padWithZeroes(hours, 2);

							var minutes = "" + (absData % 60);
							minutes = padWithZeroes(minutes, 2);

							formattedString += prefix + hours + minutes;
							break;
					}
				}
				searchString = searchString.substr(result.index + result[0].length);
			}
			return formattedString;
		};
	})();


BSDEventUtils = {
	DEPENDENCIES: new Array("BSDLogUtils"),

	registerEvent: function(element, type, func) {
	    if(element.addEventListener) {
			element.addEventListener(type, func, true);			
	    } else if(element.attachEvent) {
			element.attachEvent('on' + type, func);
	    } else {
	    	BSDLogUtils.error("ERROR: Couldn't register event: " + type + " " + func);
	    	return false;
	    }
		return true;
	},
	
	stopPropagation: function(event) {	    
	    if(event.stopPropagation) {
	    	event.stopPropagation(); 
		} else {
	    	event.cancelBubble = true; 
	  	}

	  	if(event.preventDefault) {
	      	event.preventDefault(); 
	  	} else {
	      	event.returnValue = false; 
		}
	},
	
	removeEvent: function(element, type, func) {
		if(element.removeEventListener) {
			element.removeEventListener(type, func, true);
		} else if(element.detachEvent) { //was: && element['on' + type]) {
			element.detachEvent('on' + type, func);
		} else {
			BSDLogUtils.error("Couldn't removeEvent: " + element.detachEvent + " " + element[type]);
			return false;
		}	
		return true;
	},
	
	
	fixEventTarget: function(event) {
	    if(!event) {
			event = window.event;
	    }
	
	    if(event.target) {
			if(event.target.nodeType == 3) {
			    event.target = event.target.parentNode;
			}
		} else if(event.srcElement) {
			event.target = event.srcElement
	    }
	    return event.target;
	},

	getKeyPressed: function(event) {
	    var nbr;
	    if(window.event) {
			nbr = event.which;
	    } else { 
			nbr = event.keyCode;
	    }
	    var keyChar = String.fromCharCode(nbr);
		return keyChar;	
	},

	handleKeyPress: function(event, targetKeyCode) {
	    var nbr;
	    if(window.event) {
			nbr = event.which;
	    } else { 
			nbr = event.keyCode;
	    }

		
		for(var i = 1; i < arguments.length; i++) {
			var currentCode = arguments[i];

			if(nbr == currentCode) {
				return true;
			}
		}
		
		return false;
	},
	
	getIsLeftClick: function(event) {
		var nbr;
		if(event.which) {
			nbr = event.which;			
		} else {
			nbr = event.button;					
		}

		if(nbr == 1) {
			return true;
		}
		return false;
	},
	
	getIsRightClick: function(event) {
		var nbr;
		if(event.which) {
			nbr = event.which;			
		} else {
			nbr = event.button;					
		}
		if(nbr == 3) {
			return true;
		}
		return false;
	
	},
	
	registerDOMLoadEvent: function(newEvent) {
		if(!document.bsdDOMLoadInitialized) {
			BSDEventUtils.initializeDOMLoadEvents();
		}
		document.bsdDOMLoadEvents[document.bsdDOMLoadEvents.length] = newEvent;
	},
	
	initializeDOMLoadEvents: function() {
		document.bsdDOMLoadInitialized = true;
		if(!document.bsdDOMLoadEvents) {
			document.bsdDOMLoadEvents = new Array();
		}

		/*@cc_on @*/
		/*@if (@_win32)
		document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
		var script = document.getElementById("__ie_onload");
		script.onreadystatechange = function() {
			if (this.readyState == "complete") {
		    	BSDEventUtils.doDOMLoadEvents(); // call the onload handler
		  	}
		};
		/*@end @*/
		
		if(/WebKit/i.test(navigator.userAgent)) { // sniff
			var _timer = setInterval(function() {
				if(/loaded|complete/.test(document.readyState)) {
					clearInterval(_timer);
					BSDEventUtils.doDOMLoadEvents(); // call the onload handler
			    }
			}, 10);
		} else if(document.addEventListener) { // for Mozilla browsers
			document.addEventListener("DOMContentLoaded", BSDEventUtils.doDOMLoadEvents, false);
		}
	},
	
	doDOMLoadEvents: function() {
		if(!document.bsdDOMLoadEvents) {
			return;
		} 
		for(var i = 0; i < document.bsdDOMLoadEvents.length; i++) {
			document.bsdDOMLoadEvents[i].call();
		}
		
		document.bsdDOMLoadEvents = null;
	}

}


BSDNavigationUtils = {
	DEPENDENCIES: new Array("BSDEventUtils"),
	VERSION: 1.1,
	
	registerClickNavigation: function(element, href) {  

		this.navigateToHref = function(event) {
			window.location = href;
		}
		BSDEventUtils.registerEvent(element, "click", navigateToHref);
	},
	
	refreshPage: function() {

		if(window.location && window.location.reload) {
			window.location.reload(true);
		} else if(window.location && window.location.replace) {
			window.location.replace(document.URL);
		} else {
			window.location.href = documentURL;
		}
	},
	
	refreshWithArgs: function(queryArgs) {
		var url = document.URL;
		var args = BSDNavigationUtils.getDocumentQueryArgs();
		if(url.indexOf("?") > 0) {
			url = url.substring(0, url.indexOf("?"));
		}

		for(var name in queryArgs) {
			args[name] = queryArgs[name];
		}

		url += "?";
		var i = 0;
		for(var name in args) {
			var value = args[name];
			if(!value || value.length < 1) {
				continue;
			}
			if(i != 0) {
				url += "&";
			}
			url += name;
			url += "="
			url += value;
			i++;
		}

		BSDNavigationUtils.navigateTo(url);
	},
	
	navigateTo: function(href) {
		window.location.href = href;
	},
	
	getHost: function() {
		return window.location.protocol + "//" + window.location.host;
	},
	
	getDocumentURI: function(includeQueryString) {
		var url = document.URL;
		return BSDNavigationUtils.getURI(url, includeQueryString);
	},
	
	getURI: function(url, includeQueryString) {
		var beginIndex = url.indexOf("://");
		if(beginIndex < 0) {
			beginIndex = 0;
		} else {
			beginIndex += 4;
		}
		var slashIndex = url.indexOf("/", beginIndex);
		if(slashIndex < 0) {
			return;
		}
		
		var qIndex = -1;
		if(!includeQueryString) {
			qIndex = url.indexOf("?", beginIndex);
		}
		if(qIndex > 0) {	
			return url.substring(slashIndex, qIndex);
		} else {
			return url.substring(slashIndex);
		}
		
	},
	
	populateQueryArgs: function(args, dashToUnderscore, unescape) {
		args = BSDNavigationUtils.populateQueryArgsByUrl(args, dashToUnderscore, document.URL, unescape);
		return args;
	},
	
	populateQueryArgsByUrl: function(args, dashToUnderscore, url, unescape) {

		var queryIndex = url.indexOf("?");
		var hashIndex = url.indexOf("#");
		if(queryIndex < 0) {
			return args;
		}
		
		var query;
		if(hashIndex > 0) {
			query = url.substring(queryIndex + 1, hashIndex);
		} else {
			query = url.substring(queryIndex + 1);
		}
		
		var argsArray = query.split("&");
		for(var i = 0; i < argsArray.length; i++) {
			var parts = argsArray[i].split('=');
			if(parts.length > 1) {
				var name = parts[0];
				if(dashToUnderscore) {		
					var nameRegex = /-/g;
					name = name.replace(nameRegex, "_");
				}
				var value = BSDStringUtils.trim(parts[1]);
				if(value && unescape && BSDTypeUtils.isString(value) && value.indexOf('%') > -1) {
					if(window.unescape && BSDTypeUtils.isFunction(unescape)) {
						value = unescape(value);
					} else if(window.decodeURIComponent) {
						value = window.decodeURIComponent(value);
					}
				}
				args[BSDStringUtils.trim(name)] = value;
			}
		}
		return args;
	},
	
	getQueryStringByHash: function(args) {
		var strQueryArgs = "";
		for(var key in args) {
			strQueryArgs += escape(key);
			strQueryArgs += "=";
			strQueryArgs += escape(args[key]);
			strQueryArgs += "&";
		}
		return strQueryArgs;
	},
		
	getDocumentQueryArgs: function() {
		var args = {};
		BSDNavigationUtils.populateQueryArgs(args);
		return args;
	}
}	
BSDInteractiveCalendarFunctions = {
	MONTH_FUNCTIONS: new Object(),
	DAY_FUNCTIONS: new Object(),

	getMonthFunction: function(calendarId) {
		return BSDInteractiveCalendarFunctions.MONTH_FUNCTIONS[calendarId];
	},
	
	addMonthFunction: function(calendarId, newFunc) {
		BSDInteractiveCalendarFunctions.MONTH_FUNCTIONS[calendarId] = newFunc;
	},

	getDayFunction: function(calendarId) {
		return BSDInteractiveCalendarFunctions.DAY_FUNCTIONS[calendarId];
	},
	
	addDayFunction: function(calendarId, newFunc) {
		BSDInteractiveCalendarFunctions.DAY_FUNCTIONS[calendarId] = newFunc;
	}
    
    
}

BSDInteractiveCalendarUtils = {     
    test: function() {
    	alert("arguments: " + arguments[0]);
    }
    
}

BSDInteractiveCalendar = BSDClass.create();
BSDInteractiveCalendar.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDArrayUtils", "time/BSDDateUtils", "BSDNavigationUtils");
BSDInteractiveCalendar.prototype = {
	
	className: "BSDInteractiveCalendar",
	initialize: function(elementId) {
	    this.elementId = elementId;
	    
	    this.initializeCalendar();
   	},

	initializeCalendar: function() {
		this.element = BSDDOMUtils.getObjectById(this.elementId);
		if(!this.element) {
			BSDLogUtils.error("Couldn't find calendar element with id " + elementId);
			return;
		}

		this.curDateElement = BSDDOMUtils.getObjectByIdFromParent(this.element, 'CALENDAR_CUR_DATE');
		if(!this.curDateElement) {
			BSDLogUtils.error("Couldn't load current date element for calendar: " + this.elementId);
			return;
		}
		
		this.previousLink = BSDDOMUtils.getObjectByIdFromParent(this.element, 'CALENDAR_LINK_PREVIOUS');
		this.nextLink = BSDDOMUtils.getObjectByIdFromParent(this.element, 'CALENDAR_LINK_NEXT');
		this.monthLabel = BSDDOMUtils.getObjectByIdFromParent(this.element, 'CALENDAR_LABEL');

		if(!this.previousLink || !this.nextLink) {
			BSDLogUtils.error("Couldn't find calendar previous or next links with id " + elementId);
			return;
		}		

		var controller = this;
		function previousClickHandler(e) {
			BSDEventUtils.stopPropagation(e);
			controller.doSwitch(-1);
			return false;			
		}  		
		function nextClickHandler(e) {

			BSDEventUtils.stopPropagation(e);
			controller.doSwitch(1);
			return false;			
		}  		
		
		this.initializeDateLinks();
		
		BSDEventUtils.registerEvent(this.previousLink, "click", previousClickHandler);
		BSDEventUtils.registerEvent(this.nextLink, "click", nextClickHandler);
		BSDLogUtils.debug("Initialized calendar: " + this.curDateElement + " " + this.elementId);
	},

	doSwitch: function(increment) {
		var date = this.getCurDate();
		if(!date) {
			return;
		}
		var newDate = new Date();
		newDate.setTime(date.getTime());

		if(increment > 0 && newDate.getMonth() < 11) {
			newDate.setMonth(date.getMonth() + increment);
		} else if(increment > 0) {
			newDate.setMonth(0);
			newDate.setYear(1900 + date.getYear() + 1);
		} else if(increment < 0 && newDate.getMonth() > 0) {
			newDate.setMonth(date.getMonth() + increment);
		} else if(increment < 0) {
			newDate.setMonth(11);
			newDate.setYear(1900 + date.getYear() - 1);
		}
		newDate.setDate(1);
		
		var beginDay = newDate.getDay();
				
		var weekIndex = 0;
		var oldWeeks = BSDDOMUtils.getObjectsByClass('calendarweek', this.element);
		if(oldWeeks.length < 1) {
			BSDLogUtils.error("Couldn't find calendarweek rows in calendar " + this.elementId);
			return;
		}

		var weekIndex = oldWeeks.length/2; //want to get the middle week
		if(weekIndex >= oldWeeks.length) {
			weekIndex = oldWeeks.length - 1;
		}
		var oldWeek = oldWeeks[weekIndex];
		var dayElements = BSDDOMUtils.getObjectsByClass('calendarday', oldWeek);
		var modelDay = null;
		if(dayElements.length > 0) {
			modelDay = dayElements[0];
		}		
		
		if(!modelDay) {
			BSDLogUtils.error("Couldn't find calendarday element in calendar " + this.elementId);
			return;		
		}

		var format1 = new SimpleDateFormat("MMMMMM yyyy");
		BSDDOMUtils.setText(this.monthLabel, format1.format(newDate));
		var format2 = new SimpleDateFormat("MM-dd-yyyy");
		BSDDOMUtils.setText(this.curDateElement, format2.format(newDate));


		var currentDate = this.getWeekBeginDate(newDate, beginDay);
		var lastWeek = null;
		for(var i = 0; i < oldWeeks.length; i++) {
			var currentWeek = oldWeeks[i];

			currentDate = this.buildWeek(currentDate, modelDay, currentWeek);


		}

		
		while(currentDate.getMonth() == newDate.getMonth()) {
			var newWeek = BSDDOMUtils.cloneElement(oldWeek);
			currentDate = this.buildWeek(currentDate, modelDay, newWeek);
			BSDDOMUtils.addChild(oldWeek.parentNode, newWeek);
		}
		
		var monthFunction = BSDInteractiveCalendarFunctions.getMonthFunction(this.elementId);
		if(monthFunction) {
			monthFunction.call(this, newDate);
		}		

			
	},
	
	buildWeek: function(currentDate, modelDay, weekElement) {
		BSDDOMUtils.clear(weekElement);

		for(var i = 0; i < 7; i++) {
			var newDay = BSDDOMUtils.cloneElement(modelDay);
			var label = BSDDOMUtils.getObjectByIdFromParent(newDay, 'calendardaylabel');
			if(!label) {
				label = newDay;
			}
			var dayOfMonth = currentDate.getDate();
			BSDDOMUtils.setText(label, dayOfMonth);
			
			var link = BSDDOMUtils.getObjectByIdFromParent(newDay, 'calendardaylink');
			if(link && link.href) {
				var args = new Array();
				var qIndex = link.href.indexOf("?");
				if(qIndex > 0) {
					BSDNavigationUtils.populateQueryArgsByUrl(args, false, link.href);
					var format2 = new SimpleDateFormat("MM-dd-yyyy");
					args["calendar-date"] = format2.format(currentDate);
					var queryString = BSDNavigationUtils.getQueryStringByHash(args);
					link.href = link.href.substring(0, qIndex + 1) + queryString;
				}				

				this.addDayFunction(link, currentDate, i);
			}
			
			BSDDOMUtils.addChild(weekElement, newDay);
			
			var curTime = currentDate.getTime();
			curTime += 1000*60*60*24;
			currentDate = new Date();
			currentDate.setTime(curTime);
		}
		return currentDate;
	},
	
	addDayFunction: function(link, currentDate, i) {
		var dayFunction = BSDInteractiveCalendarFunctions.getDayFunction(this.elementId);

		if(dayFunction) {				
			var dayDate = new Date(currentDate.getTime());
			
			function doDayClick(e) {

				BSDEventUtils.stopPropagation(e);
				dayFunction.call(this, this, dayDate);
				return false;
			}
			BSDEventUtils.registerEvent(link, "click", doDayClick);
		}
	
	},
	
	initializeDateLinks: function() {
		var days = BSDDOMUtils.getObjectsByClass("calendardaylink", this.element);
		for(var i = 0; i < days.length; i++) {
			var link = days[i];
			var args = new Object();
			BSDNavigationUtils.populateQueryArgsByUrl(args, false, link.href);
			var strDate = args["calendar-date"];
			if(strDate) {
				var date = BSDDateUtils.parse(strDate);

				this.addDayFunction(link, date, i);
			}
		}
	},
	
	getWeekBeginDate: function(date, monthBeginDay) {
		var beginDate = date;
		while(monthBeginDay > 0) {
			var previousTime = beginDate.getTime() - 1000*60*60*24;
			var previousDate = new Date();
			previousDate.setTime(previousTime);
			monthBeginDay = previousDate.getDay();
			beginDate = previousDate;
		}
		return beginDate;
	},
	
	getCurDate: function() {
		var strDate = BSDDOMUtils.getText(this.curDateElement);
		BSDLogUtils.debug("Got date for calendar: " + strDate);
		var regex = new RegExp("([0-9]+)\-([0-9]+)\-([0-9]{4})");
		var result = regex.exec(strDate);
		if(!result || result.length < 4) {
			BSDLogUtils.error("Got invalid date for calendar " + this.elementId + ": " + strDate);
			return;
		}
		var month = result[1];
		var day = result[2];
		var year = result[3];
		var date = new Date();
		date.setMonth(month - 1);
		date.setDate(day);
		date.setFullYear(year);
		return date;
	},
   	
    toString: function() {
		var str = "[" + this.elementId + "]";
		return str;
    },
    
    clone: function() {
    	var clone = new BSDInteractiveCalendar(this.ccid, this.ccdid, this.isChild, this.templateElementId, this.relationshipTargetTypeId, this.relationshipTargetId);
    	return clone;
    }
    
    
}
BSDVisibilityUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils"),
	VERSION: 1.1,
		
	switchById: function(current,next) {
	    var currentObj = BSDDOMUtils.getObjectById(current);
	    var nextObj = BSDDOMUtils.getObjectById(next);
	    if(!currentObj || ! nextObj) {
	    		return;
	    }
	    var nextObjDisplay = nextObj.style.display;
	    var nextObjVisibility = nextObj.style.visibility;
	    nextObj.style.display = currentObj.style.display;
	    nextObj.style.visibility = currentObj.style.visibility;
	    currentObj.style.display = nextObjDisplay;
	    currentObj.style.visibility = nextObjVisibility;
	},
	
	showByClass: function(className, parentNode) {
		var objects = BSDDOMUtils.getObjectsByClass(className, parentNode);
		for(var i = 0; i < objects.length; i++) {
			BSDVisibilityUtils.showObject(objects[i]);
		}
	},
	
	showByClassAndParentId: function(className, parentId) {
		var parent = BSDDOMUtils.getObjectById(parentId);	
		BSDVisibilityUtils.showByClassAndParent(className, parent);
	},
	
	showByClassAndParent: function(className, parent) {
		var objects = BSDDOMUtils.getObjectsByClass(className, parent);
		for(var i = 0; i < objects.length; i++) {
			BSDVisibilityUtils.showObject(objects[i]);
		}
	},
	
	hideByClass: function(className) {
		var objects = BSDDOMUtils.getObjectsByClass(className);
		for(var i = 0; i < objects.length; i++) {
			BSDVisibilityUtils.hideObject(objects[i]);
		}
	},
	
	hideByClassAndParentId: function(className, parentId) {
		var parent = BSDDOMUtils.getObjectById(parentId);
		BSDVisibilityUtils.hideByClassAndParent(className, parent);
	},
	
	hideByClassAndParent: function(className, parent) {
		var objects = BSDDOMUtils.getObjectsByClass(className, parent);
		for(var i = 0; i < objects.length; i++) {
			BSDVisibilityUtils.hideObject(objects[i]);
		}
	},
	
	showById: function(objectName) {
	    var object = BSDDOMUtils.getObjectById(objectName);
	    BSDVisibilityUtils.showObject(object);
	    return object;
	},
	
	showObject: function(object) {
		if(!object) {
			return;
		}
		object.style.display = "";
		object.style.visibility = "visible";
	},
	
	hideById: function(objectName) {
	    var object = BSDDOMUtils.getObjectById(objectName);
		BSDVisibilityUtils.hideObject(object);
		return object;
	},
	
	hideObject: function(object, ignoreDisplay) {
		if(!object) {
			return;
		}
		try {
			if(!ignoreDisplay) {
			    object.style.display = "none";
			} 
		    if((object.nodeName == 'TR' || object.nodeName == 'TD')) {
		    	object.style.visibility = "collapse";
		    } else {
			    object.style.visibility = "hidden";
		    }
		} catch (err) {
			try {
				if(object.nodeName == 'TR' || object.nodeName == 'TD') {
					object.style.visibility = 'hidden';
				} else {
					BSDLogUtils.error("Couldn't hide object: " + object.nodeName + " " + object.id + " " + err);
				}
		
			} catch (err2) {
				BSDLogUtils.error("Couldn't hide object: " + object.nodeName + " " + object.id + " " + err2);
			}
		}
	},
	
	showByObject: function(currentObj, nextObj) {
	    BSDVisibilityUtils.showObject(nextObj);
	    BSDVisibilityUtils.hideObject(currentObj);
	},
	
	isObjectHidden: function(object) {
	    if(object.style && object.style.display && object.style.display.toLowerCase() == 'none') {
			return true;
	    }
	    return false;
	},
	
	toggleObject: function(object) {
		if(BSDVisibilityUtils.isObjectHidden(object)) {
			BSDVisibilityUtils.showObject(object);
		} else {
			BSDVisibilityUtils.hideObject(object);		
		}
	},
	
	toggleById: function(elementId, parent) {
		var object;
		if(parent) {
			object = BSDDOMUtils.getObjectByIdFromParent(parent, elementId);
		} else {
			object = BSDDOMUtils.getObjectById(elementId);
		}
		if(object) {
			BSDVisibilityUtils.toggleObject(object);
		}
	},
	
	switchByNameAndJustify: function(switchObjectName, justifyObjectName) {
	    var switchObj = BSDDOMUtils.getObjectById(switchObjectName);
	    var justifyObj = BSDDOMUtils.getObjectById(justifyObjectName);
	
	    var existingHeight = 0;
	    if(!BSDVisibilityUtils.isObjectHidden(justifyObj)) {
			existingHeight = parseInt(justifyObj.style.height);
	    }
	    if(BSDVisibilityUtils.isObjectHidden(switchObj)) {
			showObject(switchObj);
			if(existingHeight > 0) { 
		    		switchObj.style.height = (existingHeight/2) + "%";
		    		justifyObj.style.height = (existingHeight/2) + "%";
			} 
	    } else {
	 		BSDVisibilityUtils.hideObject(switchObj);
			if(existingHeight > 0) { 
		    		switchObj.style.height = '0%';
		    		justifyObj.style.height = (existingHeight*2) + "%";
			} 
	    }
	},
	
	showIfSelected: function(object, searchValue, objectIdToShow) {

		if(object.value && object.value == searchValue) {
	       	BSDVisibilityUtils.showById(objectIdToShow);
	  	} else {
	       	BSDVisibilityUtils.hideById(objectIdToShow);
	   	}
	},

	showIfSelectedById: function(objectId, searchValue, objectIdToShow) {
		var object = BSDDOMUtils.getObjectById(objectId);
		BSDVisibilityUtils.showIfSelected(object, searchValue, objectIdToShow);
	}

}

BSDColorUtils = {
	DEPENDENCIES: new Array(),
	
	rgbColorToHex: function(rgb) {  
		if(!rgb) {
			return;
		}
		var color = '#';  
  		if(rgb.slice(0,4) == 'rgb(') {  
    			var cols = rgb.slice(4, rgb.length-1).split(',');  
    			var i=0; do { color += this.toColorPart(parseInt(cols[i])) } while (++i<3);  
  		} else {  
    			if(rgb.slice(0,1) == '#') {  
	      			if(rgb.length==4) { 
	      				for(var i=1;i<4;i++) {
	      					color += (rgb.charAt(i) + rgb.charAt(i)).toLowerCase();  
	      				}
	      			}
	      			if(rgb.length==7) {
	      				color = rgb.toLowerCase();  
	      			}
    			}  
  		}  
  		if(color.length == 7) {
  			return color;
  		}
  		return rgb;
	},
	
	toColorPart: function(intValue) {
	    var digits = intValue.toString(16);
	    if (intValue < 16) return '0' + digits;
	    return digits;
  	},
  	
  	rgbRegex: /\s*rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,?\s*(\d+)?\s*\)\s*/,
  	
  	incrementRGBColor: function(rgbColor, incrementValue) {
  		if(!incrementValue) {
  			incrementValue = 50;
  		}

  		var rIncrementValue = incrementValue;
  		var gIncrementValue = incrementValue;
  		var bIncrementValue = incrementValue;
  		if(incrementValue.length && incrementValue.length > 2) {
  			rIncrementValue = incrementValue[0];
  			gIncrementValue = incrementValue[1];
  			bIncrementValue = incrementValue[2];
  		}
  		
  		var regexResult = rgbColor.match(BSDColorUtils.rgbRegex);
  		if(regexResult != null && regexResult.length > 0) {
  			var r = parseInt(regexResult[1]);
  			var g = parseInt(regexResult[2]);
  			var b = parseInt(regexResult[3]);
  			if(regexResult.length > 4) {
  				var a = parseInt(regexResult[4]);
  				if(a == 0 || (r > 245 && g > 245 && b > 245)) {
  					rIncrementValue = -1 * Math.abs(rIncrementValue);
  					gIncrementValue = -1 * Math.abs(gIncrementValue);
  					bIncrementValue = -1 * Math.abs(bIncrementValue);
  					r = 255;
  					g = 255;
  					b = 255;
  				}
  			}
  			r += rIncrementValue;
  			g += gIncrementValue;
  			b += bIncrementValue;

  			rgbColor = "rgb(" + r + "," + g + "," + b + ")";
  		} else if(rgbColor.toLowerCase() == "transparent") {
  			var r = 255 - Math.abs(rIncrementValue);
  			var g = 255 - Math.abs(gIncrementValue);
  			var b = 255 - Math.abs(bIncrementValue);
  			rgbColor = "rgb(" + r + ", " + g + ", " + b + ")";
  		} else {
  			BSDLogUtils.debug("No rgb match: [" + rgbColor + "]");
  		}
  		return rgbColor;
  	}

}	
BSDHighlightUtils = {
	DEPENDENCIES: new Array("BSDColorUtils", "BSDDOMUtils", "BSDLogUtils"),
	VERSION: 1.1,

	highlightElement: function(element, newColor, incrementValue) {
		if(element.oldBGColor) {
			return;
		}
		var currentBGColor = BSDDOMUtils.getElementStyle(element, 'background-color');

		if(!element.oldBGColor) {
			element.oldBGColor = BSDColorUtils.rgbColorToHex(currentBGColor);
		}
		if(!newColor) {
			newColor = BSDColorUtils.incrementRGBColor(currentBGColor, incrementValue);
			newColor = BSDColorUtils.rgbColorToHex(newColor);
		} 
		element.style.backgroundColor = newColor;

	},
	
	unHighlightElement: function(element) {
		var oldBGColor = element.oldBGColor;
		if(!oldBGColor) {
			oldBGColor = null;
		}	

		element.style.backgroundColor = oldBGColor;
		element.oldBGColor = null;
	},
	
	highlightText: function(element, newColor, incrementValue) {
		if(element.oldFGColor) {
			return;
		}
		var currentFGColor = BSDDOMUtils.getElementStyle(element, 'color');

		if(!element.oldFGColor) {
			element.oldFGColor = BSDColorUtils.rgbColorToHex(currentFGColor);
		}
		if(!newColor) {
			newColor = BSDColorUtils.incrementRGBColor(currentFGColor, incrementValue);
			newColor = BSDColorUtils.rgbColorToHex(newColor);
		} 
		element.style.color = newColor;

	},
	
	setMoveCursor: function(element) {
		if(element.oldCursor) {
			return;
		}
		var oldCursor = BSDDOMUtils.getElementStyle(element, 'cursor');
		if(!oldCursor) {
			oldCursor = "default";
		}
		element.style.cursor = "move";
		element.oldCursor = oldCursor;

	},
	
	unSetMoveCursor: function(element) {
		var oldCursor = element.oldCursor;
		element.style.cursor = oldCursor;
		element.oldCursor = null;

	},
	
	setBorderOnElement: function(element, width, newColor, incrementValue) {
		if(element.oldBorder) {
			return;
		}
		
		if(!width) {
			width = 1;
		}
		if(!newColor) {
			var currentBGColor = BSDDOMUtils.getElementStyle(element, 'background-color');
			if(!currentBGColor) {
				hexColor = "#555";
			} else {
				newColor = BSDColorUtils.incrementRGBColor(currentBGColor, incrementValue);
				newColor = BSDColorUtils.rgbColorToHex(newColor);
			}
		}
		var oldBorder = BSDDOMUtils.getElementStyle(element, "border");
		if(oldBorder) {
			element.oldBorder = oldBorder;
		}
		var newBorder = " " + width + "px solid " + newColor;

		BSDDOMUtils.changeElementStyle(element, "border", newBorder);
		BSDLogUtils.debug("Set element border: " + newBorder);

		
	},
	
	unSetBorderOnElement: function(element) {
		var oldBorder = element.oldBorder;
		if(!oldBorder) {
			oldBorder = "none";
		}
		BSDDOMUtils.changeElementStyle(element, "border", oldBorder);
		element.oldBorder = null;
	},
	
	highlightElementByOverlay: function(element) {
		if(element.bsdHighlightOverlay) {
			return;
		}
		var highlightElement = BSDDOMUtils.createElement("div", document.body);
		highlightElement.className = 'BSDHighlightedBox';
		var positionX = BSDLocationUtils.getObjectLocationX(element);
		var positionY = BSDLocationUtils.getObjectLocationY(element);
		var adjustX = 5;
		if(positionX < adjustX) {
			adjustX = positionX;
		}
		var adjustY = 5;
		if(positionY < adjustY) {
			adjustY = positionY;
		}
		var width = 5 + adjustX;
		var height = 5 + adjustY;
		BSDLocationUtils.makeElementAbsolutelyPositioned(highlightElement);
		BSDLocationUtils.cloneElementLocation(element, highlightElement, -adjustX, -adjustY, width, height);
		element.bsdHighlightOverlay = highlightElement;
	},
	
	unHighlightElementByOverlay: function(element) {
		if(!element.bsdHighlightOverlay) {
			return;
		}
		BSDDOMUtils.removeElement(element.bsdHighlightOverlay);
		element.bsdHighlightOverlay = null;
	}
	
	
	
}
BSDTimeoutUtils = {
	DEPENDENCIES: new Array("BSDArrayUtils"),

	setTimeout: function(functionName, timeInMillis) {
		var argsString = BSDTimeoutUtils.getArgumentsString(2, arguments);
		window.setTimeout(functionName + argsString, timeInMillis);
	},

	setManagedTimeout: function(timeInMillis) {
		var argsString = BSDTimeoutUtils.getArgumentsString(1, arguments);
		window.setTimeout("BSDTimeoutUtils.handleTimeout" + argsString, timeInMillis);
	},
		
	timeoutManagers: new Object(),
	
	addTimeoutManager: function(newManager) {
		BSDTimeoutUtils.timeoutManagers[newManager.key] = newManager;
	},
	
	handleTimeout: function(managerKey, timeoutRequestId) {

		var manager = BSDTimeoutUtils.timeoutManagers[managerKey];
		if(!manager) {
			BSDLogUtils.error("Couldn't find timeout manager with key: " + managerKey);
			return;
		}
		manager.handleTimeout(timeoutRequestId);
	},
	
	getArgumentsString: function(beginningIndex, argsArray) {
		var argsString = "(";
		for(var i = beginningIndex; i < argsArray.length; i++) {
			if(i > 1 && argsString != "(") {
				argsString += ", ";
			}
			var isString = typeof(argsArray[i]) == 'string';
			if(isString) {
				argsString += "'";
			}
			argsString += argsArray[i];
			if(isString) {
				argsString += "'";
			}
		}	
		argsString += ")";
		return argsString;
	}
	
	
}

BSDTimeoutManager = BSDClass.create();
BSDTimeoutManager.DEPENDENCIES = new Array("BSDClass", "BSDTimeoutUtils");
BSDTimeoutManager.prototype = {

	className: "BSDTimeoutManager",
	initialize: function(key) {
		this.key = key;
		this.timeoutRequestHash = new Object();
		BSDTimeoutUtils.addTimeoutManager(this);
   	},
   	
	setTimeout: function(timeoutRequest, timeoutInMillis) {
		this.timeoutRequestHash[timeoutRequest.timeoutRequestId] = timeoutRequest;
		var timeoutId = BSDTimeoutUtils.setManagedTimeout(timeoutInMillis, this.key, timeoutRequest.timeoutRequestId);
		timeoutRequest.timeoutId = timeoutId;
		return timeoutId;
	},
	
	handleTimeout: function(timeoutRequestId) {

		var timeoutRequest = this.timeoutRequestHash[timeoutRequestId];
		if(!timeoutRequest) {
			BSDLogUtils.debug("Couldn't find timeout request: " + timeoutRequestId);
			return;
		}		
		
		timeoutRequest.execute();
		timeoutRequest[timeoutRequestId] = null;
	}


}

BSDTimeoutRequest = BSDClass.create();
BSDTimeoutRequest.prototype = {

	initialize: function(timeoutRequestId, timeoutTarget, timeoutFunction, timeoutFunctionArgs) {
		this.creationDate = new Date();
		this.timeoutRequestId = timeoutRequestId;
		this.timeoutTarget = timeoutTarget;
		this.timeoutFunction = timeoutFunction;
		this.timeoutFunctionArgs = timeoutFunctionArgs;
   	},
   	
	execute: function() {

		if(this.timeoutFunction) {
			if(this.timeoutFunction.apply) {
				if(!this.timeoutFunctionArgs) {
					this.timeoutFunctionArgs = new Array(); //IE doesn't allow null args
				}
				this.timeoutFunction.apply(this.timeoutTarget, this.timeoutFunctionArgs);
			}
		}
	}


}
BSDAnimatedElement = BSDClass.create();
BSDAnimatedElement.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDTimeoutManager", "BSDArrayUtils");
BSDAnimatedElement.prototype = {

	className: "BSDAnimatedElement",
	initialize: function(displayElement, key, animationRoutines, frequencyMilliseconds, lengthSeconds, startDelayMillis) {	
		this.displayElement = displayElement;
		this.key = key;
		this.animationRoutines = animationRoutines;
		this.frequencyMilliseconds = frequencyMilliseconds;
		this.lengthSeconds = lengthSeconds;
		this.startDelayMillis = startDelayMillis;
		this.initDate = new Date();
		
		this.timeoutManager = new BSDTimeoutManager(this.key);
		this.cumulativeRequestCount = 0;
		if(!this.frequencyMilliseconds) {
			frequencyMilliseconds = 1000;
		}

	},

	start: function() {
		this.beginTime = new Date();
		this.enabled = true;
		this.executeAnimation();
	},
	
	stop: function() {
		this.enabled = false;
	},

	executeAnimation: function() {
		var doDelay = false;
		if(this.startDelayMillis && this.initDate.getTime() + this.startDelayMillis > new Date().getTime()) {
			BSDLogUtils.debug("executingAnimation: skipping due to delay time");

		}

		var doCleanup = false;
		if(!this.enabled) {
			doCleanup = true;
		} else {
			var currentTime = new Date();
			var elapsedTime = currentTime.getTime() - this.beginTime.getTime();
			if(this.lengthSeconds && elapsedTime > this.lengthSeconds*1000) {
				doCleanup = true;
			}
		}
		
		if(!doDelay || doCleanup) {
			this.executeAnimationRoutines(doCleanup);
		}
		
		var animElement = this;
		function timeoutFunc() {
			animElement.executeAnimation();
		}

		if(!doCleanup) {
			var count = this.cumulativeRequestCount++;
			var arguments = null;
			var timeoutRequest = new BSDTimeoutRequest(count, animElement, timeoutFunc, arguments);
			this.timeoutManager.setTimeout(timeoutRequest, this.frequencyMilliseconds);
		}	
	},
	
	executeAnimationRoutines: function(doCleanup) {

		for(var i = 0; i < this.animationRoutines.length; i++) {
			var currentRoutine = this.animationRoutines[i];
			if(!doCleanup) {
				currentRoutine.executeAnimation(this.displayElement, this.elapsedTime);
			} else {
				currentRoutine.executeCleanup(this.displayElement, this.elapsedTime);			
			}
		}		
	
	}


}


BSDAnimatedRoutine = BSDClass.create();
BSDAnimatedRoutine.prototype = {


	initialize: function(animationFunction, animationFunctionTarget, cleanupFunction, cleanupFunctionTarget) {	
		this.animationFunction = animationFunction;
		this.animationFunctionTarget = animationFunctionTarget;
		this.cleanupFunction = cleanupFunction;
		this.cleanupFunctionTarget = cleanupFunctionTarget;
	},
	

	executeAnimation: function(displayElement, elapsedTimeMillis) {	
		if(this.animationFunction && this.animationFunctionTarget) {
			var arguments = new Array();
			BSDArrayUtils.append(arguments, displayElement);
			BSDArrayUtils.append(arguments, elapsedTimeMillis);
			this.animationFunction.apply(this.animationFunctionTarget, arguments);
		}
	},
	
	executeCleanup: function(displayElement, elapsedTimeMillis) {

		if(this.cleanupFunction && this.cleanupFunctionTarget) {
			var arguments = new Array();
			BSDArrayUtils.append(arguments, displayElement);
			BSDArrayUtils.append(arguments, elapsedTimeMillis);
			this.cleanupFunction.apply(this.cleanupFunctionTarget, arguments);
		}
	}
	

}

BSDAnimatedCharacterRoutine = BSDClass.create();
BSDAnimatedCharacterRoutine.prototype = {


	initialize: function(animatedCharacter, maxLength, clearAfterMaxReached) {	
		this.animatedCharacter = animatedCharacter;
		this.maxLength = maxLength;
		this.clearAfterMaxReached = clearAfterMaxReached;
	},
	

	executeAnimation: function(displayElement, elapsedTimeMillis) {	
		var text = BSDDOMUtils.getText(displayElement);

		if(this.maxLength && text.length >= this.maxLength) {
			text = "";
		} else {
			text += this.animatedCharacter;
		}

		BSDDOMUtils.setText(displayElement, text);
	},
	
	executeCleanup: function(displayElement, elapsedTimeMillis) {

		if(displayElement && displayElement.childNodes && displayElement.childNodes.length > 0) {
			BSDDOMUtils.addText(displayElement, "");		
		}
	}
	
	
}


BSDAnimatedCountdownRoutine = BSDClass.create();
BSDAnimatedCountdownRoutine.prototype = {

	initialize: function(lengthSeconds, callback) {	
		this.lengthSeconds = lengthSeconds
		this.callback = callback;
		this.currentLength = this.lengthSeconds
	},

	executeAnimation: function(displayElement, elapsedTimeMillis) {	

		if(!this.currentLength) {
			return;
		}
		var text = "" + this.currentLength;

		BSDDOMUtils.setText(displayElement, text);
		this.currentLength--;
	},
	
	executeCleanup: function(displayElement, elapsedTimeMillis) {
		this.callback.apply();
	}
	
} 

BSDAnimatedOpacityRoutine = BSDClass.create();
BSDAnimatedOpacityRoutine.prototype = {


	initialize: function(opacityIncrement, maxOpacity) {	
		if(!opacityIncrement) {
			opacityIncrement = .1;
		}
		this.opacityIncrement = opacityIncrement;
		this.maxOpacity = maxOpacity;
	},
	

	executeAnimation: function(displayElement, elapsedTimeMillis) {	
		var currentOpacity = BSDDOMUtils.getElementStyle(displayElement, "opacity");
		if(!currentOpacity) {
			currentOpacity = 0;
		} else {
			currentOpacity = parseFloat(currentOpacity);
		}
		if(this.maxOpacity && this.maxOpacity <= currentOpacity + this.opacityIncrement) {
			return;
		}
		currentOpacity = currentOpacity + this.opacityIncrement;

		BSDDOMUtils.changeElementStyle(displayElement, "opacity", currentOpacity);
		
	},
	
	executeCleanup: function(displayElement, elapsedTimeMillis) {

	}
	
	
}

BSDContentRatingEditorMessages = BSDClass.create();
BSDContentRatingEditorMessages.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDVisibilityUtils", "BSDLogUtils", "BSDHighlightUtils", "BSDArrayUtils", "animation/BSDAnimatedElement");
BSDContentRatingEditorMessages.prototype = {

	className: "BSDFiveStarContentRatingEditor",
	initialize: function(editorElement, type, isBinaryChoice, containerElementId) {
		this.editorElement = editorElement;
		this.type = type;

		
		if(!this.editorElement || !this.type) {
			return;
		}

		this.statusContainer = BSDDOMUtils.getObjectByIdFromParent(editorElement, "CONTENT_RATING_STATUS");
		if(!this.statusContainer) {
			BSDLogUtils.error("ERROR: Couldn't find status container");
			return;
		}
		
		this.message = 'You must <a href="/login">login</a> to give a rating';
		
		if(!isBinaryChoice) {
			this.initializeImages();
		} else {
			this.initializeYesButton();
			this.initializeNoButton();
		}
		this.initializeInappropriateButton();

		this.submitAnimationRoutines = new Array();
		BSDArrayUtils.append(this.submitAnimationRoutines, new BSDAnimatedCharacterRoutine(" .", 13));

		this.messageTimeoutRoutines = new Array();
		BSDArrayUtils.append(this.messageTimeoutRoutines, new BSDAnimatedRoutine(null, null, this.cleanupStatusAnimatedElement, this));

	},

  	initializeImages: function() {

		var images = BSDDOMUtils.getObjectsByClass("CONTENT_RATING_IMAGE", this.editorElement);
		if(images.length < 1) {
			BSDLogUtils.error("ERROR: didn't get any images for FiveStarContentRatingEditor");
			return;
		}
		for(var i = 0; i < images.length; i++) {
			this.initializeImage(images[i]);
		}
	},
	
	initializeImage: function(image) {
		var editor = this;
  		function imageClickHandler(e) {

			editor.doMessage(editor.message, "saveRatingStatusError");	 
			BSDEventUtils.stopPropagation(e);
			return false;			
		}
		BSDEventUtils.registerEvent(image, "click", imageClickHandler);

		this.initializeImageHover(image);
  	},
  	
  	
  	initializeImageHover: function(image) {
		function imageMouseoverHandler(e) {

			var imageSrc = image.src;
			if(!image.originalSrc) {
				image.originalSrc = imageSrc;
			}
			var newSrc;
			if(image.highlightSrc) {
				newSrc = image.highlightSrc;
			} else {
				newSrc = imageSrc.replace(/\.gif$/i, 'Active.gif');
				newSrc = newSrc.replace(/\.jpg$/i, 'Active.jpg');
				image.highlightSrc = newSrc;
			}

			BSDDOMUtils.setAttributeValue(image, "src", newSrc);

			return false;			
		}  		

		function imageMouseoutHandler(e) {

			if(image.originalSrc) {
				image.src = image.originalSrc;
			}
			return false;			
		}  		
		
		BSDEventUtils.registerEvent(image, "mouseover", imageMouseoverHandler);
		BSDEventUtils.registerEvent(image, "mouseout", imageMouseoutHandler);
  	
  	},  	

  	initializeInappropriateButton: function() {
		var buttons = BSDDOMUtils.getObjectsByClass("CONTENT_RATING_INAPPROPRIATE_BUTTON", this.editorElement);
		if(buttons.length < 1) {
			BSDLogUtils.error("ERROR: didn't get any inappropriate buttons for FiveStarContentRatingEditor");
			return;
		}

		this.inappropriateButton = buttons[0];
  	
  		if(!this.inappropriateButton) {
  			return;
  		}
  		
		var editor = this;
		function inappropriateButtonClickHandler(e) {
			BSDLogUtils.debug("Got inappropriate click");
			editor.doMessage(editor.message, "saveRatingStatusError");	 
			BSDEventUtils.stopPropagation(e);
			return false;			
		}  		


		BSDEventUtils.registerEvent(this.inappropriateButton, "click", inappropriateButtonClickHandler);
  	},
  	
  	initializeYesButton: function() {
		var buttons = BSDDOMUtils.getObjectsByClass("CONTENT_RATING_YES_BUTTON", this.editorElement);
		if(buttons.length < 1) {
			BSDLogUtils.error("ERROR: didn't get any yes buttons for BinaryChoiceContentRatingEditor");
			return;
		}

		this.yesButton = buttons[0];
  	
  		if(!this.yesButton) {
  			return;
  		}
  		
		var editor = this;
		function yesButtonClickHandler(e) {
			BSDLogUtils.debug("Got yes click");
			editor.doMessage(editor.message, "saveRatingStatusError");	 
			BSDEventUtils.stopPropagation(e);
			return false;			
		}  		


		BSDEventUtils.registerEvent(this.yesButton, "click", yesButtonClickHandler);
  	},  	

  	initializeNoButton: function() {
		var buttons = BSDDOMUtils.getObjectsByClass("CONTENT_RATING_NO_BUTTON", this.editorElement);
		if(buttons.length < 1) {
			BSDLogUtils.error("ERROR: didn't get any no buttons for BinaryChoiceContentRatingEditor");
			return;
		}

		this.noButton = buttons[0];
  	
  		if(!this.noButton) {
  			return;
  		}
  		
		var editor = this;
		function noButtonClickHandler(e) {
			editor.doMessage(editor.message, "saveRatingStatusError");	 
			BSDEventUtils.stopPropagation(e);
			return false;			
		}  		


		BSDEventUtils.registerEvent(this.noButton, "click", noButtonClickHandler);
  	},  	
  	 	

  	
  	doMessage: function(message, messageClass) {
		this.cleanupStatusAnimatedElement();
		
	  	this.statusElement = BSDDOMUtils.createElement("span", this.statusContainer);
  		this.statusElement.innerHTML = message;  
  		this.statusElement.className = messageClass;  		 			
	 	this.statusAnimatedElement = new BSDAnimatedElement(this.statusElement, this.pageURL, this.messageTimeoutRoutines, 3000, 3);
		this.statusAnimatedElement.start();  		

		  	
  	},
  	
   	cleanupStatusAnimatedElement: function() {

  		if(this.statusAnimatedElement) {
			this.statusAnimatedElement.stop();
			this.statusAnimatedElement = null;
		}
		if(this.statusElement && this.statusElement.parentNode) {
	  		this.statusElement.parentNode.removeChild(this.statusElement);
			this.statusElement = null;
		}
		if(this.statusProgressElement) {
			if(this.statusProgressElement.parentNode) {
				this.statusProgressElement.parentNode.removeChild(this.statusProgressElement);
			}
			this.statusProgressElement = null;
		}
		if(this.statusContainer) {
			BSDDOMUtils.clear(this.statusContainer);
		}
  	} 	
  	
  	
}


BSDContentRatingEditorMessages.initializeRatingEditorMessages = function(className, type, isBinaryChoice) {
	var elements = BSDDOMUtils.getObjectsByClass(className);

	for(var i = 0; i < elements.length; i++) {
		var currentElement = elements[i];
		var newEditor = new BSDContentRatingEditorMessages(currentElement, type, isBinaryChoice);			
	}
		
}

BSDPoint = BSDClass.create();
BSDPoint.DEPENDENCIES = new Array("BSDClass");
BSDPoint.prototype = {

	className: "BSDPoint",
	initialize: function(x, y) {
	    this.x = parseInt(x);
	    this.y = parseInt(y);
   	},
   	
    toString: function() {
		var str = "[" + this.x + "," + this.y + "]";
		return str;
    }
}

BSDPoint.calculateDistance = function(point1, point2) {
	var distance1 = Math.pow((point1.x - point2.x), 2);
	var distance2 = Math.pow((point1.y - point2.y), 2);
	var distance = distance1 + distance2;
	if(isNaN(distance)) {
		alert("NAN: " + point1 + point2 + " " + distance1 + " " + distance2 + " " + distance);
	}
	distance = Math.sqrt(distance);
	return distance;
}
BSDElementPosition = BSDClass.create();
BSDElementPosition.DEPENDENCIES = new Array("BSDClass", "BSDLocationUtils", "BSDArrayUtils");
BSDElementPosition.prototype = {

	className: "BSDElementPosition",
	initialize: function(element, xPosition, yPosition, includeMargins) {
	    if(xPosition) {
			this.x = xPosition;
	    } else {
	        this.x = BSDLocationUtils.getObjectLocationX(element);
	    }
	    if(yPosition) {
	        this.y = yPosition;
	    } else {
	        this.y = BSDLocationUtils.getObjectLocationY(element);
	    }
	    this.minX = this.x;
	    this.minY = this.y;
	    
	    if(element) {
		    this.width = BSDDOMUtils.getElementWidth(element);
		    this.height = BSDDOMUtils.getElementHeight(element);
		}
	    this.maxX = this.minX + this.width;
	    this.maxY = this.minY + this.height;
	    this.element = element;
	    
	    if(includeMargins && element) {
	  		var marginLeft = BSDDOMUtils.getElementStyle(element, 'margin-left');
	  		if(marginLeft) {
	  			marginLeft = marginLeft.replace(/\s*px\s*/i, '');
	  			this.minX -= parseInt(marginLeft);
	  		}

	  		var marginRight = BSDDOMUtils.getElementStyle(element, 'margin-right');
	  		if(marginRight) {
	  			marginRight = marginRight.replace(/\s*px\s*/i, '');
	  			this.maxX += parseInt(marginRight);
	  		}
	  		var marginTop = BSDDOMUtils.getElementStyle(element, 'margin-top');
	  		if(marginTop) {
	  			marginTop = marginTop.replace(/\s*px\s*/i, '');
	  			this.minY -= parseInt(marginTop);
	  		}
	  		var marginBottom = BSDDOMUtils.getElementStyle(element, 'margin-bottom');
	  		if(marginBottom) {
	  			marginBottom = marginBottom.replace(/\s*px\s*/i, '');
	  			this.maxY -= parseInt(marginBottom);
	  		}
	    }
	},
	
	clone: function() {
		var newPosition = new BSDElementPosition(this.element);
		newPosition.x = this.x;
		newPosition.y = this.y;
		newPosition.minX = this.minX;
		newPosition.minY = this.minY;
		newPosition.maxX = this.maxX;
		newPosition.maxY = this.maxY;
		newPosition.width = this.width;
		newPosition.height = this.height;
		return newPosition;
	},

	addExclusionPosition: function(newExclusionPosition) {
		if(!this.exclusionPositions) {
			this.exclusionPositions = new Array();			
		}
		BSDArrayUtils.append(this.exclusionPositions, newExclusionPosition);
	},

    contains: function(x, y) {
		if(x < this.minX) {
	        return false;
	    } else if(y < this.minY) {
		    return false;
	    } else if(x > this.maxX) { 
		    return false;
	    } else if(y > this.maxY) {
		    return false;
	    }
	    for(var i = 0; this.exclusionPositions && i < this.exclusionPositions.length; i++) {
	    	var currentExclusionPosition = this.exclusionPositions[i];
	    	if(currentExclusionPosition.contains(x, y)) {
	    		return false;
	    	}
	    }
		return true;
    },

	containsPosition: function(position) {
		return this.contains(position.x, position.y);
	},
	
	getCenter: function() {
		return new BSDPoint(this.minX + this.width/2, this.minY + this.height/2);		
	},

    toString: function() {
		var str = "[" + this.x + "," + this.y + " " + this.maxX + ",";
		str += this.maxY + "]";
		return str;
    },
    
    setWidthFromParent: function() {
    	var parent = this.element.parentNode;
    	while(parent && parent.offsetWidth == 0) {
    		parent = parent.parentNode;
    	}
    	var width = parent.offsetWidth;
    	this.setWidth(width);
    },
    
    setWidth: function(newWidth) {
    	this.width = newWidth;
    	this.maxX = this.minX + this.width;
    },
    
    setHeight: function(newWidth) {
    	this.height = newHeight;
    	this.maxY = this.minY + this.height;
    },
    

    
    checkDimensions: function(element) {

		var children = element.childNodes;
		if(!children) {
			return null;
		}
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(this.width < currentChild.offsetWidth) {
				this.width = currentChild.offsetWidth;
			}
			if(this.height < currentChild.offsetHeight) {
				this.height = currentChild.offsetHeight;
			}
			checkDimensions(currentChild);
		}
    
    },
    
    getDistance: function(x, y) {
    	var xSqrd = Math.pow(x - this.x, 2);
    	var ySqrd = Math.pow(y - this.y, 2);
    	var distance = Math.sqrt(xSqrd + ySqrd);
    	return distance;
    },
    
    getArea: function() {
    	return (this.width * this.height);
    }
    
}
BSDDebugUtils = {
	DEPENDENCIES: new Array(),
	
	debugDOM: function(element) {  
		if(!element) {
			return;
		}
		var message = BSDDebugUtils.getElementMessage(element);		
		alert(message);		
	},
	
	getElementMessage: function(element, indentLevel) {
		if(!indentLevel) {
			indentLevel = 0;
		}
		var message = "\n";
		message += BSDDebugUtils.getIndentSpaces(indentLevel);
		message += "[";
		message += element.nodeName;
		message += "][";
		message += element.id
		message += "][";
		message += element.className
		message += "]";
		
		for(var i = 0; element && element.childNodes && element.childNodes.length > i; i++) {
			message += BSDDebugUtils.getElementMessage(element.childNodes[i], indentLevel + 1);
		}
		return message;
	},
	
	getIndentSpaces: function(indentLevel) {
		var indent = "";
		for(var i = 0; i < indentLevel; i++) {
			indent += "   ";
		}
		return indent;
	},
	
	dumpObject: function(objectToDump) {
		if(!objectToDump) {
			return "";
		} 
		var content;
		for(propertyName in objectToDump) {
			var propertyValue = objectToDump[propertyName];
			if(!content) {
				content = "";
			} else {
				content += "\n";
			}
			content += "[" + propertyName + "=" + propertyValue + "]";	
		}
		if(!content) {
			return "";
		}
		return content;

	}

}	
BSDScrollUtils = {
	DEPENDENCIES: new Array("BSDPoint", "BSDTimeoutUtils", "BSDLogUtils", "BSDDebugUtils"),
	WINDOW_IS_SCROLLING: false,

	scrollTo: function(x, y) {
		window.scrollTo(x, y);
	},
	
	scrollBy: function(xIncrement, yIncrement) {

		window.scrollBy(xIncrement, yIncrement);
	},

	scrollBySlowly: function(x, y, xIncrement, yIncrement, timeoutMillis) {
		if(BSDScrollUtils.WINDOW_IS_SCROLLING) {
			return;
		}
		BSDScrollUtils.WINDOW_IS_SCROLLING = true;
		BSDScrollUtils.scrollBySlowlyInternal(x, y, xIncrement, yIncrement, timeoutMillis, null, null);
	},
	
	scrollBySlowlyInternal: function(x, y, xIncrement, yIncrement, timeoutMillis, xCyclesRemaining, yCyclesRemaining) {
		if(xCyclesRemaining == null && xIncrement != 0) {
			xCyclesRemaining = x/xIncrement; 
		}
		if(yCyclesRemaining == null && yIncrement != 0) {
			yCyclesRemaining = y/yIncrement; 
		}

		var currentXIncrement = 0;
		var currentYIncrement = 0;
		if(xCyclesRemaining > 0) {
			currentXIncrement = xIncrement;
			x -= xIncrement;			
		}

		if(yCyclesRemaining > 0) {
			currentYIncrement = yIncrement;
			y -= yIncrement;			
		}

		xCyclesRemaining -= 1;
		yCyclesRemaining -= 1;

		if(xCyclesRemaining > 0 || yCyclesRemaining > 0) {
			BSDScrollUtils.scrollBy(currentXIncrement, currentYIncrement);
	        BSDTimeoutUtils.setTimeout("BSDScrollUtils.scrollBySlowlyInternal", timeoutMillis);

	    } else {
	    	BSDScrollUtils.WINDOW_IS_SCROLLING = false;	    	
	    }
	},
	
	getCurrentScrollPosition: function() {
		var x,y;
		if (self.pageYOffset) { // all except Explorer
			x = self.pageXOffset;
			y = self.pageYOffset;
		} else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
			x = document.documentElement.scrollLeft;
			y = document.documentElement.scrollTop;
		} else if (document.body) { // all other Explorers
			x = document.body.scrollLeft;
			y = document.body.scrollTop;
		}
		return new BSDPoint(x, y);
	},
	
	getCurrentPageDimensions: function() {
		var x,y;
		if (self.innerHeight) { // all except Explorer		
			x = self.innerWidth;
			y = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode		
			x = document.documentElement.clientWidth;
			y = document.documentElement.clientHeight;
		} else if (document.body) { // other Explorers
			x = document.body.clientWidth;
			y = document.body.clientHeight;
		}	
		return new BSDPoint(x, y);
	},
	
	getDistanceFromTopLeftOfWindow: function(point) {
		var currentScrollPosition = BSDScrollUtils.getCurrentScrollPosition();
		var xDiff = point.x - currentScrollPosition.x;
		var yDiff = point.y - currentScrollPosition.y;
		return new BSDPoint(xDiff, yDiff);
	},

	getDistanceFromBottomRightOfWindow: function(point) {
		var currentScrollPosition = BSDScrollUtils.getCurrentScrollPosition();
		var currentPageDimensions = BSDScrollUtils.getCurrentPageDimensions();
		var xDiff = currentScrollPosition.x + currentPageDimensions.x - point.x;
		var yDiff = currentScrollPosition.y + currentPageDimensions.y - point.y;
		return new BSDPoint(xDiff, yDiff);
	},
	
	
	debugScroll: function(scrollElement) {
		if(!this.bsdScrollCount) {
			this.bsdScrollCount = 0;
		}

		this.bsdScrollCount++;
		
		BSDLogUtils.debug("Top: " + scrollElement.scrollTop + " Height: " + scrollElement.scrollHeight);









	},
	
	debugScrollTable: function(scrollTable) {
		var children = scrollTable.childNodes;
		var beginVisible = 0;
		var endVisible = 0;
		var message = "";
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(currentChild.nodeName == 'TBODY') {
				i = 0;
				children = currentChild.childNodes;
			} else if(currentChild.nodeName == 'TR') {
				message += "\n" + i + ": " + BSDDOMUtils.getElementStyle(currentChild, 'visibility') + " " + BSDDOMUtils.getElementStyle(currentChild, 'display');
			} else if(currentChild.nodeType == 1) {
				BSDLogUtils.error("unknown table child: " + currentChild.nodeName);
			}
		}
		BSDLogUtils.debug(message);
	}
	
			
}
BSDLocationUtils = {
	DEPENDENCIES: new Array("BSDPoint", "BSDElementPosition", "BSDScrollUtils"),

	getObjectLocationX: function(obj) {
		try {
			var curleft = 0;
			if (obj.offsetParent) {
				while (obj.offsetParent) {
					curleft += obj.offsetLeft;
					obj = obj.offsetParent;
				}
			} else if (obj.x) {
				curleft = obj.x;
			}
			return curleft;
		} catch (err) {  

		}
	},
	
	getObjectLocationY: function(obj) {
		try {
			var curtop = 0;
			if (obj.offsetParent) {
				while (obj.offsetParent) {
					curtop += obj.offsetTop;
					obj = obj.offsetParent;
				}
			} else if (obj.y) {
				curtop = obj.y;
			}
			return curtop;
		} catch (err) {  

		}
	},
	
	getObjectLocation: function(obj) {
		return new BSDPoint(BSDLocationUtils.getObjectLocationX(obj), BSDLocationUtils.getObjectLocationY(obj));
	},
	
	getEventPosition: function(e) {
		var posx = 0;
		var posy = 0;
		if(!e) {
		 	e = window.event;
		}
		if(e.pageX || e.pageY) {
			posx = e.pageX;
			posy = e.pageY;
		} else if(e.clientX || e.clientY) {
			posx = e.clientX + document.body.scrollLeft;
			posy = e.clientY + document.body.scrollTop;
		} else if(e.eventX || e.eventY) { //a way for us to manually set location of an event
			posx = e.eventX;
			posy = e.eventY;
		}


		var position = new BSDPoint(posx, posy);
		return position;
	},
	
	getIsAbolutelyPositioned: function(element) {
		var position = BSDDOMUtils.getElementStyle(element, 'position');
		if(position == 'absolute') {
			return true;
		}
		return false;
	},
	
	cloneElementLocation: function(source, target, adjustX, adjustY, adjustWidth, adjustHeight) {
		if(!adjustX) {
			adjustX = 0;
		}
		if(!adjustY) {
			adjustY = 0;
		}
		if(!adjustWidth) {
			adjustWidth = 0;
		}	
		if(!adjustHeight) {
			adjustHeight = 0;
		}
		var location = BSDLocationUtils.getObjectLocation(source);
	    var offsetWidth = source.offsetWidth + adjustWidth;
	    var offsetHeight = source.offsetHeight + adjustHeight;	
	    var locationX = location.x + adjustX;
	    var locationY = location.y + adjustY;
		BSDDOMUtils.changeElementStyle(target, 'left', locationX + "px");
	    BSDDOMUtils.changeElementStyle(target, 'top', locationY + "px");
	    BSDDOMUtils.changeElementStyle(target, 'width', offsetWidth + "px");
	    BSDDOMUtils.changeElementStyle(target, 'height', offsetHeight + "px");

	    var zIndex = BSDDOMUtils.getElementStyle(source, "z-index");
	    if(zIndex) {
		    BSDDOMUtils.changeElementStyle(target, 'z-index', zIndex);
		}
	},
	
	setElementLocation: function(element, point) {
		if(point.x) {
			BSDDOMUtils.changeElementStyle(element, 'left', point.x + "px");
		}
		if(point.y) {
		    BSDDOMUtils.changeElementStyle(element, 'top', point.y + "px");	
		}
	},
	
	adjustElementLocation: function(element, point) {
		var location = BSDLocationUtils.getObjectLocation(element);
		if(point.x) {
			BSDDOMUtils.changeElementStyle(element, "left", (location.x + point.x) + "px");
		}
		if(point.y) {
			BSDDOMUtils.changeElementStyle(element, "top", (location.y + point.y) + "px");
		}
	},
	
	setElementOrientation: function(element, orientation) {
		var location = new Object();
	    var width = element.offsetWidth;
	    var height = element.offsetHeight;
		if(orientation == 'top-left') {
			return; //default orientation is top-left
		} else if(orientation == 'top') {
			location.x = -width/2;
		} else if(orientation == 'top-right') {
			location.x = -width;
		} else if(orientation == 'right') {
			location.x = -width;
			location.y = -height/2;
		} else if(orientation == 'bottom-right') {
			location.x = -width;
			location.y = -height;
		} else if(orientation == 'bottom') {
			location.x = -width/2;
			location.y = -height;
		} else if(orientation == 'bottom-left') {
			location.y = -height;
		} else if(orientation == 'left') {
			location.y = -height/2;
		} else if(orientation == 'center') {
			location.x = -width/2;
			location.y = -height/2;
		} else {
			BSDLogUtils.warning("Unknown value for orientation: " + orientation);
			return;
		}
		BSDLogUtils.debug("Setting orientation: [" + location.x + "][" + location.y + "][" + orientation + "][" + width + "][" + height + "]");
		BSDLocationUtils.adjustElementLocation(element, location);
	}, 
	
	makeElementAbsolutelyPositioned: function(element, parent, retainDimensions, position) {
		var oldStyle = BSDLocationUtils.createOldStyle(element);
		element.bsdOldStyle = oldStyle;
		
	    var posX = BSDLocationUtils.getObjectLocationX(element);
	    var posY = BSDLocationUtils.getObjectLocationY(element);
	    if(position && position.x && position.y) {
	    	posX = position.x;
	    	posY = position.y;
	    }
	    var offsetWidth = element.offsetWidth;
	    var offsetHeight = element.offsetHeight;

	    if(parent) {
			posX = BSDLocationUtils.getObjectLocationX(parent);
	    	posY = BSDLocationUtils.getObjectLocationY(parent);
	  		offsetWidth = parent.offsetWidth;
	    	offsetHeight = parent.offsetHeight;
	    }
	    BSDDOMUtils.changeElementStyle(element, 'position', 'absolute');
	    BSDDOMUtils.changeElementStyle(element, 'left', posX + "px");
	    BSDDOMUtils.changeElementStyle(element, 'top', posY + "px");
	    if(!retainDimensions) {
		    BSDDOMUtils.changeElementStyle(element, 'width', offsetWidth);
		    BSDDOMUtils.changeElementStyle(element, 'height', offsetHeight);
		}
	    BSDDOMUtils.changeElementStyle(element, 'z-index', '100000');

	},
	
	makeElementNormallyPositioned: function(element, parent) {
		var position = BSDDOMUtils.getElementStyle(element, 'position');
		if(!position || position.toLowerCase() != 'absolute') {
			return;
		}

		var oldStyle = element.bsdOldStyle;
		if(!oldStyle) {
			oldStyle = BSDLocationUtils.createOldStyle(element);
		}
	    BSDDOMUtils.changeElementStyle(element, 'position', oldStyle.position);
	    BSDDOMUtils.changeElementStyle(element, 'left', oldStyle.left);
	    BSDDOMUtils.changeElementStyle(element, 'top', oldStyle.top);
	    BSDDOMUtils.changeElementStyle(element, 'width', oldStyle.width);
	    BSDDOMUtils.changeElementStyle(element, 'height', oldStyle.height);
	    BSDDOMUtils.changeElementStyle(element, 'z-index', oldStyle.zIndex);
	    element.bsdOldStyle = null;
	},
	
	createOldStyle: function(element) {
		var oldStyle = new Object();
		
		var position;
		var left;
		var top;
		var width;
		var height;
		var zIndex;
		if(element) {
			position = BSDDOMUtils.getElementStyle(element, 'position');
			left = BSDDOMUtils.getElementStyle(element, 'left');
			top = BSDDOMUtils.getElementStyle(element, 'top');
			width = BSDDOMUtils.getElementStyle(element, 'width');
			height = BSDDOMUtils.getElementStyle(element, 'height');
			zIndex = BSDDOMUtils.getElementStyle(element, 'z-index');
		}
		if(!position) {
			position = '';
		}
		if(!left) {
			left = '';
		}
		if(!top) {
			top = '';
		}
		if(!width) {
			width = '';
		}
		if(!height) {
			height = '';
		}
		if(!zIndex) {
			zIndex = '';
		}
	    oldStyle.position = position;
	    oldStyle.left = left;
	    oldStyle.top = top
	    oldStyle.width = width
	    oldStyle.height = height
	    oldStyle.zIndex = zIndex;
		return oldStyle;
	},
	
	getObjectFromParentAndLocation: function(parentElement, x, y) {
		if(!parentElement) {
			return;
		}
		var children = parentElement.childNodes;
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			var position = new BSDElementPosition(currentChild);
			if(position.contains(x, y)) {
				var childContains = BSDLocationUtils.getObjectFromParentAndLocation(currentChild, x, y);
				if(childContains) {
					return childContains;
				} else {
					return currentChild;
				}
			}
		}
		return null;
	},
	
	getObjectFromParentAndClassAndLocation: function(parentElement, className, x, y, elementToIgnore) {
	    var elements = BSDDOMUtils.getObjectsByClass(className, parentElement);
	    for(var i = 0; elements && i < elements.length; i++) {
	    		var currentElement = elements[i];
			var position = new BSDElementPosition(currentElement);
			if(position.contains(x, y) && currentElement != elementToIgnore) {
			    return currentElement;
		    }
	    }	        
	},
	
	getObjectFromParentAndNodeNameAndLocation: function(parentElement, nodeName, x, y, elementToIgnore) {
	    var elements = BSDDOMUtils.getObjectsByNodeName(parentElement, nodeName);
	    for(var i = 0; elements && i < elements.length; i++) {
	    		var currentElement = elements[i];
			var position = new BSDElementPosition(currentElement);
			if(position.contains(x, y) && currentElement != elementToIgnore) {
			    return currentElement;
		    }
	    }	        
	},
	
	centerElementWithinWindow: function(element) {
		var elementPosition = new BSDElementPosition(element);
		var scrollPosition = BSDScrollUtils.getCurrentScrollPosition();
		var pageDimensions = BSDScrollUtils.getCurrentPageDimensions();
		
		var position = new Object();
		position.x = pageDimensions.x/2 - elementPosition.width/2 + scrollPosition.x;
		position.y = pageDimensions.y/2 - elementPosition.height/2 + scrollPosition.y;
		
		BSDLocationUtils.setElementLocation(element, position);
	
	},
	
	positionElementWithinWindow: function(element, keepDimensionFixed, bufferSizeX, bufferSizeY) {
		var elementPosition = new BSDElementPosition(element);
		var scrollPosition = BSDScrollUtils.getCurrentScrollPosition();
		var pageDimensions = BSDScrollUtils.getCurrentPageDimensions();
		if(!bufferSizeX) {
			bufferSizeX = 20; //provide an extra margin of 20px
		}
		if(!bufferSizeY) {
			bufferSizeY = 20;
		}
		var newX;
		var newY;
		var newWidth;
		var newHeight;



		if(elementPosition.minX < scrollPosition.x) {
			newX = scrollPosition.x;
		}
		if(elementPosition.minY < scrollPosition.y) {
			newY = scrollPosition.y;
		}
		var maxXDelta = elementPosition.maxX - (scrollPosition.x + pageDimensions.x) + bufferSizeX; 
		if(maxXDelta > 0) {
			if(!newX) {
				newX = elementPosition.minX - maxXDelta - 1;
			} else if(newX) {
				newWidth = elementPosition.width - (maxXDelta + newX);
			}
		} 
		
		var maxYDelta = elementPosition.maxY - (scrollPosition.y + pageDimensions.y) + bufferSizeY; 

		if(maxYDelta > 0) {
			if(!newY) {
				newY = elementPosition.minY - maxYDelta - 1;
			} else if(newY) {
				newHeight = elementPosition.height - (maxYDelta + newY);
			}
		}
		
		if(newX < scrollPosition.x) {
			newWidth = elementPosition.width - (scrollPosition.x - newX) - bufferSizeX; 
			newX = scrollPosition.x + 1;
		}
		if(newY < scrollPosition.y) {
			newHeight = elementPosition.height - (scrollPosition.y - newY) - bufferSizeY;
			newY = scrollPosition.y + 1;
		}
		
		
		if(newX || newY) {

			var newTopLeft = new BSDPoint();
			if(newX) {
				newTopLeft.x = newX
			} else {
				newTopLeft.x = elementPosition.minX;
			}
			if(newY) {
				newTopLeft.y = newY;				
			} else {
				newTopLeft.y = elementPosition.minY;
			}

			BSDLocationUtils.setElementLocation(element, newTopLeft);
		}
		
		if(keepDimensionFixed) {
			return;
		}

		if(newWidth) {
		    BSDDOMUtils.changeElementStyle(element, 'width', newWidth);
		}
		if(newHeight) {
			BSDDOMUtils.changeElementStyle(element, 'height', newHeight);
		}
		
	}

}
// Provide a default path to dwr.engine
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

if (KCMAjaxGui == null) var KCMAjaxGui = {};
KCMAjaxGui._path = '/ajax';
KCMAjaxGui.doNavigation = function(p0, p1, callback) {
  dwr.engine._execute(KCMAjaxGui._path, 'KCMAjaxGui', 'doNavigation', p0, p1, callback);
}
KCMAjaxGui.doRendering = function(p0, p1, callback) {
  dwr.engine._execute(KCMAjaxGui._path, 'KCMAjaxGui', 'doRendering', p0, p1, callback);
}
KCMAjaxGui.getRenderedContent = function(p0, p1, callback) {
  dwr.engine._execute(KCMAjaxGui._path, 'KCMAjaxGui', 'getRenderedContent', p0, p1, callback);
}
/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Declare an object to which we can add real functions.
 */
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

/**
 * Set an alternative error handler from the default alert box.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setErrorHandler = function(handler) {
  dwr.engine._errorHandler = handler;
};

/**
 * Set an alternative warning handler from the default alert box.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setWarningHandler = function(handler) {
  dwr.engine._warningHandler = handler;
};

/**
 * Setter for the text/html handler - what happens if a DWR request gets an HTML
 * reply rather than the expected Javascript. Often due to login timeout
 */
dwr.engine.setTextHtmlHandler = function(handler) {
  dwr.engine._textHtmlHandler = handler;
}

/**
 * Set a default timeout value for all calls. 0 (the default) turns timeouts off.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setTimeout = function(timeout) {
  dwr.engine._timeout = timeout;
};

/**
 * The Pre-Hook is called before any DWR remoting is done.
 * @see getahead.org/dwr/browser/engine/hooks
 */
dwr.engine.setPreHook = function(handler) {
  dwr.engine._preHook = handler;
};

/**
 * The Post-Hook is called after any DWR remoting is done.
 * @see getahead.org/dwr/browser/engine/hooks
 */
dwr.engine.setPostHook = function(handler) {
  dwr.engine._postHook = handler;
};

/**
 * Custom headers for all DWR calls
 * @see getahead.org/dwr/????
 */
dwr.engine.setHeaders = function(headers) {
  dwr.engine._headers = headers;
};

/**
 * Custom parameters for all DWR calls
 * @see getahead.org/dwr/????
 */
dwr.engine.setParameters = function(parameters) {
  dwr.engine._parameters = parameters;
};

/** XHR remoting type constant. See dwr.engine.set[Rpc|Poll]Type() */
dwr.engine.XMLHttpRequest = 1;

/** XHR remoting type constant. See dwr.engine.set[Rpc|Poll]Type() */
dwr.engine.IFrame = 2;

/** XHR remoting type constant. See dwr.engine.setRpcType() */
dwr.engine.ScriptTag = 3;

/**
 * Set the preferred remoting type.
 * @param newType One of dwr.engine.XMLHttpRequest or dwr.engine.IFrame or dwr.engine.ScriptTag
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setRpcType = function(newType) {
  if (newType != dwr.engine.XMLHttpRequest && newType != dwr.engine.IFrame && newType != dwr.engine.ScriptTag) {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidRpcType", message:"RpcType must be one of dwr.engine.XMLHttpRequest or dwr.engine.IFrame or dwr.engine.ScriptTag" });
    return;
  }
  dwr.engine._rpcType = newType;
};

/**
 * Which HTTP method do we use to send results? Must be one of "GET" or "POST".
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setHttpMethod = function(httpMethod) {
  if (httpMethod != "GET" && httpMethod != "POST") {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidHttpMethod", message:"Remoting method must be one of GET or POST" });
    return;
  }
  dwr.engine._httpMethod = httpMethod;
};

/**
 * Ensure that remote calls happen in the order in which they were sent? (Default: false)
 * @see getahead.org/dwr/browser/engine/ordering
 */
dwr.engine.setOrdered = function(ordered) {
  dwr.engine._ordered = ordered;
};

/**
 * Do we ask the XHR object to be asynchronous? (Default: true)
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setAsync = function(async) {
  dwr.engine._async = async;
};

/**
 * Does DWR poll the server for updates? (Default: false)
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setActiveReverseAjax = function(activeReverseAjax) {
  if (activeReverseAjax) {
    // Bail if we are already started
    if (dwr.engine._activeReverseAjax) return;
    dwr.engine._activeReverseAjax = true;
    dwr.engine._poll();
  }
  else {
    // Can we cancel an existing request?
    if (dwr.engine._activeReverseAjax && dwr.engine._pollReq) dwr.engine._pollReq.abort();
    dwr.engine._activeReverseAjax = false;
  }
  // TODO: in iframe mode, if we start, stop, start then the second start may
  // well kick off a second iframe while the first is still about to return
  // we should cope with this but we don't
};

/**
 * Set the preferred polling type.
 * @param newPollType One of dwr.engine.XMLHttpRequest or dwr.engine.IFrame
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setPollType = function(newPollType) {
  if (newPollType != dwr.engine.XMLHttpRequest && newPollType != dwr.engine.IFrame) {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidPollType", message:"PollType must be one of dwr.engine.XMLHttpRequest or dwr.engine.IFrame"  });
    return;
  }
  dwr.engine._pollType = newPollType;
};

/**
 * The default message handler.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.defaultErrorHandler = function(message, ex) {
  dwr.engine._debug("Error: " + ex.name + ", " + ex.message, true);

  if (message == null || message == "") alert("A server error has occured. More information may be available in the console.");
  // Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky
  else if (message.indexOf("0x80040111") != -1) dwr.engine._debug(message);
  else alert(message);
};

/**
 * The default warning handler.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.defaultWarningHandler = function(message, ex) {
  dwr.engine._debug(message);
};

/**
 * For reduced latency you can group several remote calls together using a batch.
 * @see getahead.org/dwr/browser/engine/batch
 */
dwr.engine.beginBatch = function() {
  if (dwr.engine._batch) {
    dwr.engine._handleError(null, { name:"dwr.engine.batchBegun", message:"Batch already begun" });
    return;
  }
  dwr.engine._batch = dwr.engine._createBatch();
};

/**
 * Finished grouping a set of remote calls together. Go and execute them all.
 * @see getahead.org/dwr/browser/engine/batch
 */
dwr.engine.endBatch = function(options) {
  var batch = dwr.engine._batch;
  if (batch == null) {
    dwr.engine._handleError(null, { name:"dwr.engine.batchNotBegun", message:"No batch in progress" });
    return;
  }
  dwr.engine._batch = null;
  if (batch.map.callCount == 0) return;

  // The hooks need to be merged carefully to preserve ordering
  if (options) dwr.engine._mergeBatch(batch, options);

  // In ordered mode, we don't send unless the list of sent items is empty
  if (dwr.engine._ordered && dwr.engine._batchesLength != 0) {
    dwr.engine._batchQueue[dwr.engine._batchQueue.length] = batch;
  }
  else {
    dwr.engine._sendData(batch);
  }
};

/** @deprecated */
dwr.engine.setPollMethod = function(type) { dwr.engine.setPollType(type); };
dwr.engine.setMethod = function(type) { dwr.engine.setRpcType(type); };
dwr.engine.setVerb = function(verb) { dwr.engine.setHttpMethod(verb); };

//==============================================================================
// Only private stuff below here
//==============================================================================

/** The original page id sent from the server */
dwr.engine._origScriptSessionId = "F220D46730C3989C9777C99DEF1E7352";

/** The session cookie name */
dwr.engine._sessionCookieName = "JSESSIONID"; // JSESSIONID

/** Is GET enabled for the benefit of Safari? */
dwr.engine._allowGetForSafariButMakeForgeryEasier = "false";

/** The script prefix to strip in the case of scriptTagProtection. */
dwr.engine._scriptTagProtection = "throw 'allowScriptTagRemoting is false.';";

/** The default path to the DWR servlet */
dwr.engine._defaultPath = "/ajax";

/** The read page id that we calculate */
dwr.engine._scriptSessionId = null;

/** The function that we use to fetch/calculate a session id */
dwr.engine._getScriptSessionId = function() {
  if (dwr.engine._scriptSessionId == null) {
    dwr.engine._scriptSessionId = dwr.engine._origScriptSessionId + Math.floor(Math.random() * 1000);
  }
  return dwr.engine._scriptSessionId;
};

/** A function to call if something fails. */
dwr.engine._errorHandler = dwr.engine.defaultErrorHandler;

/** For debugging when something unexplained happens. */
dwr.engine._warningHandler = dwr.engine.defaultWarningHandler;

/** A function to be called before requests are marshalled. Can be null. */
dwr.engine._preHook = null;

/** A function to be called after replies are received. Can be null. */
dwr.engine._postHook = null;

/** An map of the batches that we have sent and are awaiting a reply on. */
dwr.engine._batches = {};

/** A count of the number of outstanding batches. Should be == to _batches.length unless prototype has messed things up */
dwr.engine._batchesLength = 0;

/** In ordered mode, the array of batches waiting to be sent */
dwr.engine._batchQueue = [];

/** What is the default rpc type */
dwr.engine._rpcType = dwr.engine.XMLHttpRequest;

/** What is the default remoting method (ie GET or POST) */
dwr.engine._httpMethod = "POST";

/** Do we attempt to ensure that calls happen in the order in which they were sent? */
dwr.engine._ordered = false;

/** Do we make the calls async? */
dwr.engine._async = true;

/** The current batch (if we are in batch mode) */
dwr.engine._batch = null;

/** The global timeout */
dwr.engine._timeout = 0;

/** ActiveX objects to use when we want to convert an xml string into a DOM object. */
dwr.engine._DOMDocument = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"];

/** The ActiveX objects to use when we want to do an XMLHttpRequest call. */
dwr.engine._XMLHTTP = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];

/** Are we doing comet or polling? */
dwr.engine._activeReverseAjax = false;

/** What is the default polling type */
dwr.engine._pollType = dwr.engine.XMLHttpRequest;
//dwr.engine._pollType = dwr.engine.IFrame;

/** The iframe that we are using to poll */
dwr.engine._outstandingIFrames = [];

/** The xhr object that we are using to poll */
dwr.engine._pollReq = null;

/** How many milliseconds between internal comet polls */
dwr.engine._pollCometInterval = 200;

/** How many times have we re-tried to poll? */
dwr.engine._pollRetries = 0;
dwr.engine._maxPollRetries = 0;

/** Do we do a document.reload if we get a text/html reply? */
dwr.engine._textHtmlHandler = null;

/** If you wish to send custom headers with every request */
dwr.engine._headers = null;

/** If you wish to send extra custom request parameters with each request */
dwr.engine._parameters = null;

/** Undocumented interceptors - do not use */
dwr.engine._postSeperator = "\n";
dwr.engine._defaultInterceptor = function(data) {return data;}
dwr.engine._urlRewriteHandler = dwr.engine._defaultInterceptor;
dwr.engine._contentRewriteHandler = dwr.engine._defaultInterceptor;
dwr.engine._replyRewriteHandler = dwr.engine._defaultInterceptor;

/** Batch ids allow us to know which batch the server is answering */
dwr.engine._nextBatchId = 0;

/** A list of the properties that need merging from calls to a batch */
dwr.engine._propnames = [ "rpcType", "httpMethod", "async", "timeout", "errorHandler", "warningHandler", "textHtmlHandler" ];

/** Do we stream, or can be hacked to do so? */
dwr.engine._partialResponseNo = 0;
dwr.engine._partialResponseYes = 1;
dwr.engine._partialResponseFlush = 2;

/**
 * @private Send a request. Called by the Javascript interface stub
 * @param path part of URL after the host and before the exec bit without leading or trailing /s
 * @param scriptName The class to execute
 * @param methodName The method on said class to execute
 * @param func The callback function to which any returned data should be passed
 *       if this is null, any returned data will be ignored
 * @param vararg_params The parameters to pass to the above class
 */
dwr.engine._execute = function(path, scriptName, methodName, vararg_params) {
  var singleShot = false;
  if (dwr.engine._batch == null) {
    dwr.engine.beginBatch();
    singleShot = true;
  }
  var batch = dwr.engine._batch;
  // To make them easy to manipulate we copy the arguments into an args array
  var args = [];
  for (var i = 0; i < arguments.length - 3; i++) {
    args[i] = arguments[i + 3];
  }
  // All the paths MUST be to the same servlet
  if (batch.path == null) {
    batch.path = path;
  }
  else {
    if (batch.path != path) {
      dwr.engine._handleError(batch, { name:"dwr.engine.multipleServlets", message:"Can't batch requests to multiple DWR Servlets." });
      return;
    }
  }
  // From the other params, work out which is the function (or object with
  // call meta-data) and which is the call parameters
  var callData;
  var lastArg = args[args.length - 1];
  if (typeof lastArg == "function" || lastArg == null) callData = { callback:args.pop() };
  else callData = args.pop();

  // Merge from the callData into the batch
  dwr.engine._mergeBatch(batch, callData);
  batch.handlers[batch.map.callCount] = {
    exceptionHandler:callData.exceptionHandler,
    callback:callData.callback
  };

  // Copy to the map the things that need serializing
  var prefix = "c" + batch.map.callCount + "-";
  batch.map[prefix + "scriptName"] = scriptName;
  batch.map[prefix + "methodName"] = methodName;
  batch.map[prefix + "id"] = batch.map.callCount;
  for (i = 0; i < args.length; i++) {
    dwr.engine._serializeAll(batch, [], args[i], prefix + "param" + i);
  }

  // Now we have finished remembering the call, we incr the call count
  batch.map.callCount++;
  if (singleShot) dwr.engine.endBatch();
};

/** @private Poll the server to see if there is any data waiting */
dwr.engine._poll = function(overridePath) {
  if (!dwr.engine._activeReverseAjax) return;

  var batch = dwr.engine._createBatch();
  batch.map.id = 0; // TODO: Do we need this??
  batch.map.callCount = 1;
  batch.isPoll = true;
  if (navigator.userAgent.indexOf("Gecko/") != -1) {
    batch.rpcType = dwr.engine._pollType;
    batch.map.partialResponse = dwr.engine._partialResponseYes;
  }
  else if (document.all) {
    batch.rpcType = dwr.engine.IFrame;
    batch.map.partialResponse = dwr.engine._partialResponseFlush;
  }
  else {
    batch.rpcType = dwr.engine._pollType;
    batch.map.partialResponse = dwr.engine._partialResponseNo;
  }
  batch.httpMethod = "POST";
  batch.async = true;
  batch.timeout = 0;
  batch.path = (overridePath) ? overridePath : dwr.engine._defaultPath;
  batch.preHooks = [];
  batch.postHooks = [];
  batch.errorHandler = dwr.engine._pollErrorHandler;
  batch.warningHandler = dwr.engine._pollErrorHandler;
  batch.handlers[0] = {
    callback:function(pause) {
      dwr.engine._pollRetries = 0;
      setTimeout("dwr.engine._poll()", pause);
    }
  };

  // Send the data
  dwr.engine._sendData(batch);
  if (batch.rpcType == dwr.engine.XMLHttpRequest) {
  // if (batch.map.partialResponse != dwr.engine._partialResponseNo) {
    dwr.engine._checkCometPoll();
  }
};

/** Try to recover from polling errors */
dwr.engine._pollErrorHandler = function(msg, ex) {
  // if anything goes wrong then just silently try again (up to 3x) after 10s
  dwr.engine._pollRetries++;
  dwr.engine._debug("Reverse Ajax poll failed (pollRetries=" + dwr.engine._pollRetries + "): " + ex.name + " : " + ex.message);
  if (dwr.engine._pollRetries < dwr.engine._maxPollRetries) {
    setTimeout("dwr.engine._poll()", 10000);
  }
  else {
    dwr.engine._debug("Giving up.");
  }
};

/** @private Generate a new standard batch */
dwr.engine._createBatch = function() {
  var batch = {
    map:{
      callCount:0,
      page:window.location.pathname + window.location.search,
      httpSessionId:dwr.engine._getJSessionId(),
      scriptSessionId:dwr.engine._getScriptSessionId()
    },
    charsProcessed:0, paramCount:0,
    headers:[], parameters:[],
    isPoll:false, headers:{}, handlers:{}, preHooks:[], postHooks:[],
    rpcType:dwr.engine._rpcType,
    httpMethod:dwr.engine._httpMethod,
    async:dwr.engine._async,
    timeout:dwr.engine._timeout,
    errorHandler:dwr.engine._errorHandler,
    warningHandler:dwr.engine._warningHandler,
    textHtmlHandler:dwr.engine._textHtmlHandler
  };
  if (dwr.engine._preHook) batch.preHooks.push(dwr.engine._preHook);
  if (dwr.engine._postHook) batch.postHooks.push(dwr.engine._postHook);
  var propname, data;
  if (dwr.engine._headers) {
    for (propname in dwr.engine._headers) {
      data = dwr.engine._headers[propname];
      if (typeof data != "function") batch.headers[propname] = data;
    }
  }
  if (dwr.engine._parameters) {
    for (propname in dwr.engine._parameters) {
      data = dwr.engine._parameters[propname];
      if (typeof data != "function") batch.parameters[propname] = data;
    }
  }
  return batch;
}

/** @private Take further options and merge them into */
dwr.engine._mergeBatch = function(batch, overrides) {
  var propname, data;
  for (var i = 0; i < dwr.engine._propnames.length; i++) {
    propname = dwr.engine._propnames[i];
    if (overrides[propname] != null) batch[propname] = overrides[propname];
  }
  if (overrides.preHook != null) batch.preHooks.unshift(overrides.preHook);
  if (overrides.postHook != null) batch.postHooks.push(overrides.postHook);
  if (overrides.headers) {
    for (propname in overrides.headers) {
      data = overrides.headers[propname];
      if (typeof data != "function") batch.headers[propname] = data;
    }
  }
  if (overrides.parameters) {
    for (propname in overrides.parameters) {
      data = overrides.parameters[propname];
      if (typeof data != "function") batch.map["p-" + propname] = "" + data;
    }
  }
};

/** @private What is our session id? */
dwr.engine._getJSessionId =  function() {
  var cookies = document.cookie.split(';');
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i];
    while (cookie.charAt(0) == ' ') cookie = cookie.substring(1, cookie.length);
    if (cookie.indexOf(dwr.engine._sessionCookieName + "=") == 0) {
      return cookie.substring(11, cookie.length);
    }
  }
  return "";
}

/** @private Check for reverse Ajax activity */
dwr.engine._checkCometPoll = function() {
  for (var i = 0; i < dwr.engine._outstandingIFrames.length; i++) {
    var text = "";
    var iframe = dwr.engine._outstandingIFrames[i];
    try {
      text = dwr.engine._getTextFromCometIFrame(iframe);
    }
    catch (ex) {
      dwr.engine._handleWarning(iframe.batch, ex);
    }
    if (text != "") dwr.engine._processCometResponse(text, iframe.batch);
  }
  if (dwr.engine._pollReq) {
    var req = dwr.engine._pollReq;
    var text = req.responseText;
    dwr.engine._processCometResponse(text, req.batch);
  }

  // If the poll resources are still there, come back again
  if (dwr.engine._outstandingIFrames.length > 0 || dwr.engine._pollReq) {
    setTimeout("dwr.engine._checkCometPoll()", dwr.engine._pollCometInterval);
  }
};

/** @private Extract the whole (executed an all) text from the current iframe */
dwr.engine._getTextFromCometIFrame = function(frameEle) {
  var body = frameEle.contentWindow.document.body;
  if (body == null) return "";
  var text = body.innerHTML;
  // We need to prevent IE from stripping line feeds
  if (text.indexOf("<PRE>") == 0 || text.indexOf("<pre>") == 0) {
    text = text.substring(5, text.length - 7);
  }
  return text;
};

/** @private Some more text might have come in, test and execute the new stuff */
dwr.engine._processCometResponse = function(response, batch) {
  if (batch.charsProcessed == response.length) return;
  if (response.length == 0) {
    batch.charsProcessed = 0;
    return;
  }

  var firstStartTag = response.indexOf("//#DWR-START#", batch.charsProcessed);
  if (firstStartTag == -1) {
    // dwr.engine._debug("No start tag (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed) + "'");
    batch.charsProcessed = response.length;
    return;
  }
  // if (firstStartTag > 0) {
  //   dwr.engine._debug("Start tag not at start (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed, firstStartTag) + "'");
  // }

  var lastEndTag = response.lastIndexOf("//#DWR-END#");
  if (lastEndTag == -1) {
    // dwr.engine._debug("No end tag. unchanged charsProcessed=" + batch.charsProcessed);
    return;
  }

  // Skip the end tag too for next time, remembering CR and LF
  if (response.charCodeAt(lastEndTag + 11) == 13 && response.charCodeAt(lastEndTag + 12) == 10) {
    batch.charsProcessed = lastEndTag + 13;
  }
  else {
    batch.charsProcessed = lastEndTag + 11;
  }

  var exec = response.substring(firstStartTag + 13, lastEndTag);

  dwr.engine._receivedBatch = batch;
  dwr.engine._eval(exec);
  dwr.engine._receivedBatch = null;
};

/** @private Actually send the block of data in the batch object. */
dwr.engine._sendData = function(batch) {
  batch.map.batchId = dwr.engine._nextBatchId++;
  dwr.engine._batches[batch.map.batchId] = batch;
  dwr.engine._batchesLength++;
  batch.completed = false;

  for (var i = 0; i < batch.preHooks.length; i++) {
    batch.preHooks[i]();
  }
  batch.preHooks = null;
  // Set a timeout
  if (batch.timeout && batch.timeout != 0) {
    batch.interval = setInterval(function() { dwr.engine._abortRequest(batch); }, batch.timeout);
  }
  // Get setup for XMLHttpRequest if possible
  if (batch.rpcType == dwr.engine.XMLHttpRequest) {
    if (window.XMLHttpRequest) {
      batch.req = new XMLHttpRequest();
    }
    // IE5 for the mac claims to support window.ActiveXObject, but throws an error when it's used
    else if (window.ActiveXObject && !(navigator.userAgent.indexOf("Mac") >= 0 && navigator.userAgent.indexOf("MSIE") >= 0)) {
      batch.req = dwr.engine._newActiveXObject(dwr.engine._XMLHTTP);
    }
  }

  var prop, request;
  if (batch.req) {
    // Proceed using XMLHttpRequest
    if (batch.async) {
      batch.req.onreadystatechange = function() { dwr.engine._stateChange(batch); };
    }
    // If we're polling, record this for monitoring
    if (batch.isPoll) {
      dwr.engine._pollReq = batch.req;
      // In IE XHR is an ActiveX control so you can't augment it like this
      // however batch.isPoll uses IFrame on IE so were safe here
      batch.req.batch = batch;
    }
    // Workaround for Safari 1.x POST bug
    var indexSafari = navigator.userAgent.indexOf("Safari/");
    if (indexSafari >= 0) {
      var version = navigator.userAgent.substring(indexSafari + 7);
      if (parseInt(version, 10) < 400) {
        if (dwr.engine._allowGetForSafariButMakeForgeryEasier == "true") batch.httpMethod = "GET";
        else dwr.engine._handleWarning(batch, { name:"dwr.engine.oldSafari", message:"Safari GET support disabled. See getahead.org/dwr/server/servlet and allowGetForSafariButMakeForgeryEasier." });
      }
    }
    batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall;
    request = dwr.engine._constructRequest(batch);
    try {
      batch.req.open(batch.httpMethod, request.url, batch.async);
      try {
        for (prop in batch.headers) {
          var value = batch.headers[prop];
          if (typeof value == "string") batch.req.setRequestHeader(prop, value);
        }
        if (!batch.headers["Content-Type"]) batch.req.setRequestHeader("Content-Type", "text/plain");
      }
      catch (ex) {
        dwr.engine._handleWarning(batch, ex);
      }
      batch.req.send(request.body);
      if (!batch.async) dwr.engine._stateChange(batch);
    }
    catch (ex) {
      dwr.engine._handleError(batch, ex);
    }
  }
  else if (batch.rpcType != dwr.engine.ScriptTag) {
    // Proceed using iframe
    var idname = batch.isPoll ? "dwr-if-poll-" + batch.map.batchId : "dwr-if-" + batch.map["c0-id"];
    batch.div = document.createElement("div");
    batch.div.innerHTML = "<iframe src='javascript:void(0)' frameborder='0' style='width:0px;height:0px;border:0;' id='" + idname + "' name='" + idname + "'></iframe>";
    document.body.appendChild(batch.div);
    batch.iframe = document.getElementById(idname);
    batch.iframe.batch = batch;
    batch.mode = batch.isPoll ? dwr.engine._ModeHtmlPoll : dwr.engine._ModeHtmlCall;
    if (batch.isPoll) dwr.engine._outstandingIFrames.push(batch.iframe);
    request = dwr.engine._constructRequest(batch);
    if (batch.httpMethod == "GET") {
      batch.iframe.setAttribute("src", request.url);
      // document.body.appendChild(batch.iframe);
    }
    else {
      batch.form = document.createElement("form");
      batch.form.setAttribute("id", "dwr-form");
      batch.form.setAttribute("action", request.url);
      batch.form.setAttribute("target", idname);
      batch.form.target = idname;
      batch.form.setAttribute("method", batch.httpMethod);
      for (prop in batch.map) {
        var value = batch.map[prop];
        if (typeof value != "function") {
          var formInput = document.createElement("input");
          formInput.setAttribute("type", "hidden");
          formInput.setAttribute("name", prop);
          formInput.setAttribute("value", value);
          batch.form.appendChild(formInput);
        }
      }
      document.body.appendChild(batch.form);
      batch.form.submit();
    }
  }
  else {
    batch.httpMethod = "GET"; // There's no such thing as ScriptTag using POST
    batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall;
    request = dwr.engine._constructRequest(batch);
    batch.script = document.createElement("script");
    batch.script.id = "dwr-st-" + batch.map["c0-id"];
    batch.script.src = request.url;
    document.body.appendChild(batch.script);
  }
};

dwr.engine._ModePlainCall = "/call/plaincall/";
dwr.engine._ModeHtmlCall = "/call/htmlcall/";
dwr.engine._ModePlainPoll = "/call/plainpoll/";
dwr.engine._ModeHtmlPoll = "/call/htmlpoll/";

/** @private Work out what the URL should look like */
dwr.engine._constructRequest = function(batch) {
  // A quick string to help people that use web log analysers
  var request = { url:batch.path + batch.mode, body:null };
  if (batch.isPoll == true) {
    request.url += "ReverseAjax.dwr";
  }
  else if (batch.map.callCount == 1) {
    request.url += batch.map["c0-scriptName"] + "." + batch.map["c0-methodName"] + ".dwr";
  }
  else {
    request.url += "Multiple." + batch.map.callCount + ".dwr";
  }
  // Play nice with url re-writing
  var sessionMatch = location.href.match(/jsessionid=([^?]+)/);
  if (sessionMatch != null) {
    request.url += ";jsessionid=" + sessionMatch[1];
  }

  var prop;
  if (batch.httpMethod == "GET") {
    // Some browsers (Opera/Safari2) seem to fail to convert the callCount value
    // to a string in the loop below so we do it manually here.
    batch.map.callCount = "" + batch.map.callCount;
    request.url += "?";
    for (prop in batch.map) {
      if (typeof batch.map[prop] != "function") {
        request.url += encodeURIComponent(prop) + "=" + encodeURIComponent(batch.map[prop]) + "&";
      }
    }
    request.url = request.url.substring(0, request.url.length - 1);
  }
  else {
    // PERFORMANCE: for iframe mode this is thrown away.
    request.body = "";
    for (prop in batch.map) {
      if (typeof batch.map[prop] != "function") {
        request.body += prop + "=" + batch.map[prop] + dwr.engine._postSeperator;
      }
    }
    request.body = dwr.engine._contentRewriteHandler(request.body);
  }
  request.url = dwr.engine._urlRewriteHandler(request.url);
  return request;
};

/** @private Called by XMLHttpRequest to indicate that something has happened */
dwr.engine._stateChange = function(batch) {
  var toEval;

  if (batch.completed) {
    dwr.engine._debug("Error: _stateChange() with batch.completed");
    return;
  }

  var req = batch.req;
  try {
    if (req.readyState != 4) return;
  }
  catch (ex) {
    dwr.engine._handleWarning(batch, ex);
    // It's broken - clear up and forget this call
    dwr.engine._clearUp(batch);
    return;
  }

  try {
    var reply = req.responseText;
    reply = dwr.engine._replyRewriteHandler(reply);
    var status = req.status; // causes Mozilla to except on page moves

    if (reply == null || reply == "") {
      dwr.engine._handleWarning(batch, { name:"dwr.engine.missingData", message:"No data received from server" });
    }
    else if (status != 200) {
      dwr.engine._handleError(batch, { name:"dwr.engine.http." + status, message:req.statusText });
    }
    else {
      var contentType = req.getResponseHeader("Content-Type");
      if (!contentType.match(/^text\/plain/) && !contentType.match(/^text\/javascript/)) {
        if (contentType.match(/^text\/html/) && typeof batch.textHtmlHandler == "function") {
          batch.textHtmlHandler();
        }
        else {
          dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidMimeType", message:"Invalid content type: '" + contentType + "'" });
        }
      }
      else {
        // Comet replies might have already partially executed
        if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) {
          dwr.engine._processCometResponse(reply, batch);
        }
        else {
          if (reply.search("//#DWR") == -1) {
            dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidReply", message:"Invalid reply from server" });
          }
          else {
            toEval = reply;
          }
        }
      }
    }
  }
  catch (ex) {
    dwr.engine._handleWarning(batch, ex);
  }

  dwr.engine._callPostHooks(batch);

  // Outside of the try/catch so errors propogate normally:
  dwr.engine._receivedBatch = batch;
  if (toEval != null) toEval = toEval.replace(dwr.engine._scriptTagProtection, "");
  dwr.engine._eval(toEval);
  dwr.engine._receivedBatch = null;

  dwr.engine._clearUp(batch);
};

/** @private Called by the server: Execute a callback */
dwr.engine._remoteHandleCallback = function(batchId, callId, reply) {
  var batch = dwr.engine._batches[batchId];
  if (batch == null) {
    dwr.engine._debug("Warning: batch == null in remoteHandleCallback for batchId=" + batchId, true);
    return;
  }
  // Error handlers inside here indicate an error that is nothing to do
  // with DWR so we handle them differently.
  try {
    var handlers = batch.handlers[callId];
    if (!handlers) {
      dwr.engine._debug("Warning: Missing handlers. callId=" + callId, true);
    }
    else if (typeof handlers.callback == "function") handlers.callback(reply);
  }
  catch (ex) {
    dwr.engine._handleError(batch, ex);
  }
};

/** @private Called by the server: Handle an exception for a call */
dwr.engine._remoteHandleException = function(batchId, callId, ex) {
  var batch = dwr.engine._batches[batchId];
  if (batch == null) { dwr.engine._debug("Warning: null batch in remoteHandleException", true); return; }
  var handlers = batch.handlers[callId];
  if (handlers == null) { dwr.engine._debug("Warning: null handlers in remoteHandleException", true); return; }
  if (ex.message == undefined) ex.message = "";
  if (typeof handlers.exceptionHandler == "function") handlers.exceptionHandler(ex.message, ex);
  else if (typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex);
};

/** @private Called by the server: The whole batch is broken */
dwr.engine._remoteHandleBatchException = function(ex, batchId) {
  var searchBatch = (dwr.engine._receivedBatch == null && batchId != null);
  if (searchBatch) {
    dwr.engine._receivedBatch = dwr.engine._batches[batchId];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleError(dwr.engine._receivedBatch, ex);
  if (searchBatch) {
    dwr.engine._receivedBatch = null;
    dwr.engine._clearUp(dwr.engine._batches[batchId]);
  }
};

/** @private Called by the server: Reverse ajax should not be used */
dwr.engine._remotePollCometDisabled = function(ex, batchId) {
  dwr.engine.setActiveReverseAjax(false);
  var searchBatch = (dwr.engine._receivedBatch == null && batchId != null);
  if (searchBatch) {
    dwr.engine._receivedBatch = dwr.engine._batches[batchId];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleError(dwr.engine._receivedBatch, ex);
  if (searchBatch) {
    dwr.engine._receivedBatch = null;
    dwr.engine._clearUp(dwr.engine._batches[batchId]);
  }
};

/** @private Called by the server: An IFrame reply is about to start */
dwr.engine._remoteBeginIFrameResponse = function(iframe, batchId) {
  if (iframe != null) dwr.engine._receivedBatch = iframe.batch;
  dwr.engine._callPostHooks(dwr.engine._receivedBatch);
};

/** @private Called by the server: An IFrame reply is just completing */
dwr.engine._remoteEndIFrameResponse = function(batchId) {
  dwr.engine._clearUp(dwr.engine._receivedBatch);
  dwr.engine._receivedBatch = null;
};

/** @private This is a hack to make the context be this window */
dwr.engine._eval = function(script) {
  if (script == null) return null;
  if (script == "") { dwr.engine._debug("Warning: blank script", true); return null; }
  // dwr.engine._debug("Exec: [" + script + "]", true);
  return eval(script);
};

/** @private Called as a result of a request timeout */
dwr.engine._abortRequest = function(batch) {
  if (batch && !batch.completed) {
    clearInterval(batch.interval);
    dwr.engine._clearUp(batch);
    if (batch.req) batch.req.abort();
    dwr.engine._handleError(batch, { name:"dwr.engine.timeout", message:"Timeout" });
  }
};

/** @private call all the post hooks for a batch */
dwr.engine._callPostHooks = function(batch) {
  if (batch.postHooks) {
    for (var i = 0; i < batch.postHooks.length; i++) {
      batch.postHooks[i]();
    }
    batch.postHooks = null;
  }
}

/** @private A call has finished by whatever means and we need to shut it all down. */
dwr.engine._clearUp = function(batch) {
  if (!batch) { dwr.engine._debug("Warning: null batch in dwr.engine._clearUp()", true); return; }
  if (batch.completed == "true") { dwr.engine._debug("Warning: Double complete", true); return; }

  // IFrame tidyup
  if (batch.div) batch.div.parentNode.removeChild(batch.div);
  if (batch.iframe) {
    // If this is a poll frame then stop comet polling
    for (var i = 0; i < dwr.engine._outstandingIFrames.length; i++) {
      if (dwr.engine._outstandingIFrames[i] == batch.iframe) {
        dwr.engine._outstandingIFrames.splice(i, 1);
      }
    }
    batch.iframe.parentNode.removeChild(batch.iframe);
  }
  if (batch.form) batch.form.parentNode.removeChild(batch.form);

  // XHR tidyup: avoid IE handles increase
  if (batch.req) {
    // If this is a poll frame then stop comet polling
    if (batch.req == dwr.engine._pollReq) dwr.engine._pollReq = null;
    delete batch.req;
  }

  if (batch.map && batch.map.batchId) {
    delete dwr.engine._batches[batch.map.batchId];
    dwr.engine._batchesLength--;
  }

  batch.completed = true;

  // If there is anything on the queue waiting to go out, then send it.
  // We don't need to check for ordered mode, here because when ordered mode
  // gets turned off, we still process *waiting* batches in an ordered way.
  if (dwr.engine._batchQueue.length != 0) {
    var sendbatch = dwr.engine._batchQueue.shift();
    dwr.engine._sendData(sendbatch);
  }
};

/** @private Generic error handling routing to save having null checks everywhere */
dwr.engine._handleError = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex);
  else if (dwr.engine._errorHandler) dwr.engine._errorHandler(ex.message, ex);
  dwr.engine._clearUp(batch);
};

/** @private Generic error handling routing to save having null checks everywhere */
dwr.engine._handleWarning = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.warningHandler == "function") batch.warningHandler(ex.message, ex);
  else if (dwr.engine._warningHandler) dwr.engine._warningHandler(ex.message, ex);
  dwr.engine._clearUp(batch);
};

/**
 * @private Marshall a data item
 * @param batch A map of variables to how they have been marshalled
 * @param referto An array of already marshalled variables to prevent recurrsion
 * @param data The data to be marshalled
 * @param name The name of the data being marshalled
 */
dwr.engine._serializeAll = function(batch, referto, data, name) {
  if (data == null) {
    batch.map[name] = "null:null";
    return;
  }

  switch (typeof data) {
  case "boolean":
    batch.map[name] = "boolean:" + data;
    break;
  case "number":
    batch.map[name] = "number:" + data;
    break;
  case "string":
    batch.map[name] = "string:" + encodeURIComponent(data);
    break;
  case "object":
    if (data instanceof String) batch.map[name] = "String:" + encodeURIComponent(data);
    else if (data instanceof Boolean) batch.map[name] = "Boolean:" + data;
    else if (data instanceof Number) batch.map[name] = "Number:" + data;
    else if (data instanceof Date) batch.map[name] = "Date:" + data.getTime();
    else if (data && data.join) batch.map[name] = dwr.engine._serializeArray(batch, referto, data, name);
    else batch.map[name] = dwr.engine._serializeObject(batch, referto, data, name);
    break;
  case "function":
    // We just ignore functions.
    break;
  default:
    dwr.engine._handleWarning(null, { name:"dwr.engine.unexpectedType", message:"Unexpected type: " + typeof data + ", attempting default converter." });
    batch.map[name] = "default:" + data;
    break;
  }
};

/** @private Have we already converted this object? */
dwr.engine._lookup = function(referto, data, name) {
  var lookup;
  // Can't use a map: getahead.org/ajax/javascript-gotchas
  for (var i = 0; i < referto.length; i++) {
    if (referto[i].data == data) {
      lookup = referto[i];
      break;
    }
  }
  if (lookup) return "reference:" + lookup.name;
  referto.push({ data:data, name:name });
  return null;
};

/** @private Marshall an object */
dwr.engine._serializeObject = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  // This check for an HTML is not complete, but is there a better way?
  // Maybe we should add: data.hasChildNodes typeof "function" == true
  if (data.nodeName && data.nodeType) {
    return dwr.engine._serializeXml(batch, referto, data, name);
  }

  // treat objects as an associative arrays
  var reply = "Object_" + dwr.engine._getObjectClassName(data) + ":{";
  var element;
  for (element in data) {
    if (typeof data[element] != "function") {
      batch.paramCount++;
      var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
      dwr.engine._serializeAll(batch, referto, data[element], childName);

      reply += encodeURIComponent(element) + ":reference:" + childName + ", ";
    }
  }

  if (reply.substring(reply.length - 2) == ", ") {
    reply = reply.substring(0, reply.length - 2);
  }
  reply += "}";

  return reply;
};

/** @private Returns the classname of supplied argument obj */
dwr.engine._errorClasses = { "Error":Error, "EvalError":EvalError, "RangeError":RangeError, "ReferenceError":ReferenceError, "SyntaxError":SyntaxError, "TypeError":TypeError, "URIError":URIError };
dwr.engine._getObjectClassName = function(obj) {
  // Try to find the classname by stringifying the object's constructor
  // and extract <class> from "function <class>".
  if (obj && obj.constructor && obj.constructor.toString)
  {
    var str = obj.constructor.toString();
    var regexpmatch = str.match(/function\s+(\w+)/);
    if (regexpmatch && regexpmatch.length == 2) {
      return regexpmatch[1];
    }
  }

  // Now manually test against the core Error classes, as these in some 
  // browsers successfully match to the wrong class in the 
  // Object.toString() test we will do later
  if (obj && obj.constructor) {
	for (var errorname in dwr.engine._errorClasses) {
      if (obj.constructor == dwr.engine._errorClasses[errorname]) return errorname;
    }
  }

  // Try to find the classname by calling Object.toString() on the object
  // and extracting <class> from "[object <class>]"
  if (obj) {
    var str = Object.prototype.toString.call(obj);
    var regexpmatch = str.match(/\[object\s+(\w+)/);
    if (regexpmatch && regexpmatch.length==2) {
      return regexpmatch[1];
    }
  }

  // Supplied argument was probably not an object, but what is better?
  return "Object";
};

/** @private Marshall an object */
dwr.engine._serializeXml = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  var output;
  if (window.XMLSerializer) output = new XMLSerializer().serializeToString(data);
  else if (data.toXml) output = data.toXml;
  else output = data.innerHTML;

  return "XML:" + encodeURIComponent(output);
};

/** @private Marshall an array */
dwr.engine._serializeArray = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  var reply = "Array:[";
  for (var i = 0; i < data.length; i++) {
    if (i != 0) reply += ",";
    batch.paramCount++;
    var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
    dwr.engine._serializeAll(batch, referto, data[i], childName);
    reply += "reference:";
    reply += childName;
  }
  reply += "]";

  return reply;
};

/** @private Convert an XML string into a DOM object. */
dwr.engine._unserializeDocument = function(xml) {
  var dom;
  if (window.DOMParser) {
    var parser = new DOMParser();
    dom = parser.parseFromString(xml, "text/xml");
    if (!dom.documentElement || dom.documentElement.tagName == "parsererror") {
      var message = dom.documentElement.firstChild.data;
      message += "\n" + dom.documentElement.firstChild.nextSibling.firstChild.data;
      throw message;
    }
    return dom;
  }
  else if (window.ActiveXObject) {
    dom = dwr.engine._newActiveXObject(dwr.engine._DOMDocument);
    dom.loadXML(xml); // What happens on parse fail with IE?
    return dom;
  }
  else {
    var div = document.createElement("div");
    div.innerHTML = xml;
    return div;
  }
};

/** @param axarray An array of strings to attempt to create ActiveX objects from */
dwr.engine._newActiveXObject = function(axarray) {
  var returnValue;  
  for (var i = 0; i < axarray.length; i++) {
    try {
      returnValue = new ActiveXObject(axarray[i]);
      break;
    }
    catch (ex) { /* ignore */ }
  }
  return returnValue;
};

/** @private Used internally when some message needs to get to the programmer */
dwr.engine._debug = function(message, stacktrace) {
  var written = false;
  try {
    if (window.console) {
      if (stacktrace && window.console.trace) window.console.trace();
      window.console.log(message);
      written = true;
    }
    else if (window.opera && window.opera.postError) {
      window.opera.postError(message);
      written = true;
    }
  }
  catch (ex) { /* ignore */ }

  if (!written) {
    var debug = document.getElementById("dwr-debug");
    if (debug) {
      var contents = message + "<br/>" + debug.innerHTML;
      if (contents.length > 2048) contents = contents.substring(0, 2048);
      debug.innerHTML = contents;
    }
  }
};

/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Declare an object to which we can add real functions.
 */
if (dwr == null) var dwr = {};
if (dwr.util == null) dwr.util = {};
if (DWRUtil == null) var DWRUtil = dwr.util;

/** @private The flag we use to decide if we should escape html */
dwr.util._escapeHtml = true;

/**
 * Set the global escapeHtml flag
 */
dwr.util.setEscapeHtml = function(escapeHtml) {
  dwr.util._escapeHtml = escapeHtml;
}

/** @private Work out from an options list and global settings if we should be esccaping */
dwr.util._shouldEscapeHtml = function(options) {
  if (options && options.escapeHtml != null) {
    return options.escapeHtml;
  }
  return dwr.util._escapeHtml;
}

/**
 * Return a string with &, <, >, ' and " replaced with their entities
 * @see TODO
 */
dwr.util.escapeHtml = function(original) {
  var div = document.createElement('div');
  var text = document.createTextNode(original);
  div.appendChild(text);
  return div.innerHTML;
}

/**
 * Replace common XML entities with characters (see dwr.util.escapeHtml())
 * @see TODO
 */
dwr.util.unescapeHtml = function(original) {
  var div = document.createElement('div');
  div.innerHTML = original.replace(/<\/?[^>]+>/gi, '');
  return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
}

/**
 * Replace characters dangerous for XSS reasons with visually similar characters
 * @see TODO
 */
dwr.util.replaceXmlCharacters = function(original) {
  original = original.replace("&", "+");
  original = original.replace("<", "\u2039");
  original = original.replace(">", "\u203A");
  original = original.replace("\'", "\u2018");
  original = original.replace("\"", "\u201C");
  return original;
}

/**
 * Return true iff the input string contains any XSS dangerous characters
 * @see TODO
 */
dwr.util.containsXssRiskyCharacters = function(original) {
  return (original.indexOf('&') != -1
    || original.indexOf('<') != -1
    || original.indexOf('>') != -1
    || original.indexOf('\'') != -1
    || original.indexOf('\"') != -1);
}

/**
 * Enables you to react to return being pressed in an input
 * @see http://getahead.org/dwr/browser/util/selectrange
 */
dwr.util.onReturn = function(event, action) {
  if (!event) event = window.event;
  if (event && event.keyCode && event.keyCode == 13) action();
};

/**
 * Select a specific range in a text box. Useful for 'google suggest' type functions.
 * @see http://getahead.org/dwr/browser/util/selectrange
 */
dwr.util.selectRange = function(ele, start, end) {
  ele = dwr.util._getElementById(ele, "selectRange()");
  if (ele == null) return;
  if (ele.setSelectionRange) {
    ele.setSelectionRange(start, end);
  }
  else if (ele.createTextRange) {
    var range = ele.createTextRange();
    range.moveStart("character", start);
    range.moveEnd("character", end - ele.value.length);
    range.select();
  }
  ele.focus();
};

/**
 * Find the element in the current HTML document with the given id or ids
 * @see http://getahead.org/dwr/browser/util/$
 */
if (document.getElementById) {
  dwr.util.byId = function() {
    var elements = new Array();
    for (var i = 0; i < arguments.length; i++) {
      var element = arguments[i];
      if (typeof element == 'string') {
        element = document.getElementById(element);
      }
      if (arguments.length == 1) {
        return element;
      }
      elements.push(element);
    }
    return elements;
  };
}
else if (document.all) {
  dwr.util.byId = function() {
    var elements = new Array();
    for (var i = 0; i < arguments.length; i++) {
      var element = arguments[i];
      if (typeof element == 'string') {
        element = document.all[element];
      }
      if (arguments.length == 1) {
        return element;
      }
      elements.push(element);
    }
    return elements;
  };
}

/**
 * Alias $ to dwr.util.byId
 * @see http://getahead.org/dwr/browser/util/$
 */
var $;
if (!$) {
  $ = dwr.util.byId;
}

/**
 * This function pretty-prints simple data or whole object graphs, f ex as an aid in debugging.
 * @see http://getahead.org/dwr/browser/util/todescriptivestring
 */
dwr.util.toDescriptiveString = function(data, showLevels, options) {
  if (showLevels === undefined) showLevels = 1;
  var opt = {};
  if (dwr.util._isObject(options)) opt = options;
  var defaultoptions = {
    escapeHtml:false,
    baseIndent: "",
    childIndent: "\u00A0\u00A0",
    lineTerminator: "\n",
    oneLineMaxItems: 5,
    shortStringMaxLength: 13,
    propertyNameMaxLength: 30 
  };
  for (var p in defaultoptions) if (!(p in opt)) opt[p] = defaultoptions[p];
  if (typeof options == "number") {
    var baseDepth = options;
    opt.baseIndent = dwr.util._indent2(baseDepth, opt);
  }

  var skipDomProperties = {
    document:true, ownerDocument:true,
    all:true,
    parentElement:true, parentNode:true, offsetParent:true,
    children:true, firstChild:true, lastChild:true,
    previousSibling:true, nextSibling:true,
    innerHTML:true, outerHTML:true,
    innerText:true, outerText:true, textContent:true,
    attributes:true,
    style:true, currentStyle:true, runtimeStyle:true,
    parentTextEdit:true
  };
  
  function recursive(data, showLevels, indentDepth, options) {
    var reply = "";
    try {
      // string
      if (typeof data == "string") {
        var str = data;
        if (showLevels == 0 && str.length > options.shortStringMaxLength)
          str = str.substring(0, options.shortStringMaxLength-3) + "...";
        if (options.escapeHtml) {
          // Do the escape separately for every line as escapeHtml() on some 
          // browsers (IE) will strip line breaks and we want to preserve them
          var lines = str.split("\n");
          for (var i = 0; i < lines.length; i++) lines[i] = dwr.util.escapeHtml(lines[i]);
          str = lines.join("\n");
        }
        if (showLevels == 0) { // Short format
          str = str.replace(/\n|\r|\t/g, function(ch) {
            switch (ch) {
              case "\n": return "\\n";
              case "\r": return "";
              case "\t": return "\\t";
            }
          });
        }
        else { // Long format
          str = str.replace(/\n|\r|\t/g, function(ch) {
            switch (ch) {
              case "\n": return options.lineTerminator + indent(indentDepth+1, options);
              case "\r": return "";
              case "\t": return "\\t";
            }
          });
        }
        reply = '"' + str + '"';
      }
      
      // function
      else if (typeof data == "function") {
        reply = "function";
      }
    
      // Array
      else if (dwr.util._isArray(data)) {
        if (showLevels == 0) { // Short format (don't show items)
          if (data.length > 0)
            reply = "[...]";
          else
            reply = "[]";
        }
        else { // Long format (show items)
          var strarr = [];
          strarr.push("[");
          var count = 0;
          for (var i = 0; i < data.length; i++) {
            if (! (i in data)) continue;
            var itemvalue = data[i];
            if (count > 0) strarr.push(", ");
            if (showLevels == 1) { // One-line format
              if (count == options.oneLineMaxItems) {
                strarr.push("...");
                break;
              }
            }
            else { // Multi-line format
              strarr.push(options.lineTerminator + indent(indentDepth+1, options));
            }
            if (i != count) {
              strarr.push(i);
              strarr.push(":");
            }
            strarr.push(recursive(itemvalue, showLevels-1, indentDepth+1, options));
            count++;
          }
          if (showLevels > 1) strarr.push(options.lineTerminator + indent(indentDepth, options));
          strarr.push("]");
          reply = strarr.join("");
        }
      }
      
      // Objects except Date
      else if (dwr.util._isObject(data) && !dwr.util._isDate(data)) {
        if (showLevels == 0) { // Short format (don't show properties)
          reply = dwr.util._detailedTypeOf(data);
        }
        else { // Long format (show properties)
          var strarr = [];
          if (dwr.util._detailedTypeOf(data) != "Object") {
            strarr.push(dwr.util._detailedTypeOf(data));
            if (typeof data.valueOf() != "object") {
              strarr.push(":");
              strarr.push(recursive(data.valueOf(), 1, indentDepth, options));
            }
            strarr.push(" ");
          }
          strarr.push("{");
          var isDomObject = dwr.util._isHTMLElement(data); 
          var count = 0;
          for (var prop in data) {
            var propvalue = data[prop];
            if (isDomObject) {
              if (!propvalue) continue;
              if (typeof propvalue == "function") continue;
              if (skipDomProperties[prop]) continue;
              if (prop.toUpperCase() == prop) continue;
            }
            if (count > 0) strarr.push(", ");
            if (showLevels == 1) { // One-line format
              if (count == options.oneLineMaxItems) {
                strarr.push("...");
                break;
              }
            }
            else { // Multi-line format
              strarr.push(options.lineTerminator + indent(indentDepth+1, options));
            }
            strarr.push(prop.length > options.propertyNameMaxLength ? prop.substring(0, options.propertyNameMaxLength-3) + "..." : prop);
            strarr.push(":");
            strarr.push(recursive(propvalue, showLevels-1, indentDepth+1, options));
            count++;
          }
          if (showLevels > 1 && count > 0) strarr.push(options.lineTerminator + indent(indentDepth, options));
          strarr.push("}");
          reply = strarr.join("");
        }
      }
  
      // undefined, null, number, boolean, Date
      else {
        reply = "" + data;
      }
  
      return reply;
    }
    catch(err) {
      return (err.message ? err.message : ""+err);
    }
  }

  function indent(count, options) {
    var strarr = [];
    strarr.push(options.baseIndent);
    for (var i=0; i<count; i++) {
      strarr.push(options.childIndent);
    }
    return strarr.join("");
  };
  
  return recursive(data, showLevels, 0, opt);
}

/**
 * Setup a GMail style loading message.
 * @see http://getahead.org/dwr/browser/util/useloadingmessage
 */
dwr.util.useLoadingMessage = function(message) {
  var loadingMessage;
  if (message) loadingMessage = message;
  else loadingMessage = "Loading";
  dwr.engine.setPreHook(function() {
    var disabledZone = dwr.util.byId('disabledZone');
    if (!disabledZone) {
      disabledZone = document.createElement('div');
      disabledZone.setAttribute('id', 'disabledZone');
      disabledZone.style.position = "absolute";
      disabledZone.style.zIndex = "1000";
      disabledZone.style.left = "0px";
      disabledZone.style.top = "0px";
      disabledZone.style.width = "100%";
      disabledZone.style.height = "100%";
      document.body.appendChild(disabledZone);
      var messageZone = document.createElement('div');
      messageZone.setAttribute('id', 'messageZone');
      messageZone.style.position = "absolute";
      messageZone.style.top = "0px";
      messageZone.style.right = "0px";
      messageZone.style.background = "red";
      messageZone.style.color = "white";
      messageZone.style.fontFamily = "Arial,Helvetica,sans-serif";
      messageZone.style.padding = "4px";
      disabledZone.appendChild(messageZone);
      var text = document.createTextNode(loadingMessage);
      messageZone.appendChild(text);
      dwr.util._disabledZoneUseCount = 1;
    }
    else {
      dwr.util.byId('messageZone').innerHTML = loadingMessage;
      disabledZone.style.visibility = 'visible';
      dwr.util._disabledZoneUseCount++;
    }
  });
  dwr.engine.setPostHook(function() {
    dwr.util._disabledZoneUseCount--;
    if (dwr.util._disabledZoneUseCount == 0) {
      dwr.util.byId('disabledZone').style.visibility = 'hidden';
    }
  });
};

/**
 * Set a global highlight handler
 */
dwr.util.setHighlightHandler = function(handler) {
  dwr.util._highlightHandler = handler;
};

/**
 * An example highlight handler
 */
dwr.util.yellowFadeHighlightHandler = function(ele) {
  dwr.util._yellowFadeProcess(ele, 0);
};
dwr.util._yellowFadeSteps = [ "d0", "b0", "a0", "90", "98", "a0", "a8", "b0", "b8", "c0", "c8", "d0", "d8", "e0", "e8", "f0", "f8" ];
dwr.util._yellowFadeProcess = function(ele, colorIndex) {
  ele = dwr.util.byId(ele);
  if (colorIndex < dwr.util._yellowFadeSteps.length) {
    ele.style.backgroundColor = "#ffff" + dwr.util._yellowFadeSteps[colorIndex];
    setTimeout("dwr.util._yellowFadeProcess('" + ele.id + "'," + (colorIndex + 1) + ")", 200);
  }
  else {
    ele.style.backgroundColor = "transparent";
  }
};

/**
 * An example highlight handler
 */
dwr.util.borderFadeHighlightHandler = function(ele) {
  ele.style.borderWidth = "2px";
  ele.style.borderStyle = "solid";
  dwr.util._borderFadeProcess(ele, 0);
};
dwr.util._borderFadeSteps = [ "d0", "b0", "a0", "90", "98", "a0", "a8", "b0", "b8", "c0", "c8", "d0", "d8", "e0", "e8", "f0", "f8" ];
dwr.util._borderFadeProcess = function(ele, colorIndex) {
  ele = dwr.util.byId(ele);
  if (colorIndex < dwr.util._borderFadeSteps.length) {
    ele.style.borderColor = "#ff" + dwr.util._borderFadeSteps[colorIndex] + dwr.util._borderFadeSteps[colorIndex];
    setTimeout("dwr.util._borderFadeProcess('" + ele.id + "'," + (colorIndex + 1) + ")", 200);
  }
  else {
    ele.style.backgroundColor = "transparent";
  }
};

/**
 * A focus highlight handler
 */
dwr.util.focusHighlightHandler = function(ele) {
  try {
    ele.focus();
  }
  catch (ex) { /* ignore */ }
};

/** @private the current global highlight style */
dwr.util._highlightHandler = null;

/**
 * Highlight that an element has changed
 */
dwr.util.highlight = function(ele, options) {
  if (options && options.highlightHandler) {
    options.highlightHandler(dwr.util.byId(ele));
  }
  else if (dwr.util._highlightHandler != null) {
    dwr.util._highlightHandler(dwr.util.byId(ele));
  }
};

/**
 * Set the value an HTML element to the specified value.
 * @see http://getahead.org/dwr/browser/util/setvalue
 */
dwr.util.setValue = function(ele, val, options) {
  if (val == null) val = "";
  if (options == null) options = {};
  if (dwr.util._shouldEscapeHtml(options) && typeof(val) == "string") {
    val = dwr.util.escapeHtml(val);
  }

  var orig = ele;
  if (typeof ele == "string") {
    ele = dwr.util.byId(ele);
    // We can work with names and need to sometimes for radio buttons, and IE has
    // an annoying bug where getElementById() returns an element based on name if
    // it doesn't find it by id. Here we don't want to do that, so:
    if (ele && ele.id != orig) ele = null;
  }
  var nodes = null;
  if (ele == null) {
    // Now it is time to look by name
    nodes = document.getElementsByName(orig);
    if (nodes.length >= 1) ele = nodes.item(0);
  }

  if (ele == null) {
    dwr.util._debug("setValue() can't find an element with id/name: " + orig + ".");
    return;
  }

  // All paths now lead to some update so we highlight a change
  dwr.util.highlight(ele, options);

  if (dwr.util._isHTMLElement(ele, "select")) {
    if (ele.type == "select-multiple" && dwr.util._isArray(val)) dwr.util._selectListItems(ele, val);
    else dwr.util._selectListItem(ele, val);
    return;
  }

  if (dwr.util._isHTMLElement(ele, "input")) {
    if (ele.type == "radio" || ele.type == "checkbox") {
      if (nodes && nodes.length >= 1) {
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes.item(i);
          if (node.type != ele.type) continue;
          if (dwr.util._isArray(val)) {
            node.checked = false;
            for (var j = 0; j < val.length; j++)
              if (val[i] == node.value) node.checked = true;
          }
          else {
            node.checked = (node.value == val);
          }
        }
      }
      else ele.checked = (val == true);
    }
    else ele.value = val;

    return;
  }

  if (dwr.util._isHTMLElement(ele, "textarea")) {
    ele.value = val;
    return;
  }

  // If the value to be set is a DOM object then we try importing the node
  // rather than serializing it out
  if (val.nodeType) {
    if (val.nodeType == 9 /*Node.DOCUMENT_NODE*/) val = val.documentElement;
    val = dwr.util._importNode(ele.ownerDocument, val, true);
    ele.appendChild(val);
    return;
  }

  // Fall back to innerHTML
  ele.innerHTML = val;
};

/**
 * @private Find multiple items in a select list and select them. Used by setValue()
 * @param ele The select list item
 * @param val The array of values to select
 */
dwr.util._selectListItems = function(ele, val) {
  // We deal with select list elements by selecting the matching option
  // Begin by searching through the values
  var found  = false;
  var i;
  var j;
  for (i = 0; i < ele.options.length; i++) {
    ele.options[i].selected = false;
    for (j = 0; j < val.length; j++) {
      if (ele.options[i].value == val[j]) {
        ele.options[i].selected = true;
      }
    }
  }
  // If that fails then try searching through the visible text
  if (found) return;

  for (i = 0; i < ele.options.length; i++) {
    for (j = 0; j < val.length; j++) {
      if (ele.options[i].text == val[j]) {
        ele.options[i].selected = true;
      }
    }
  }
};

/**
 * @private Find an item in a select list and select it. Used by setValue()
 * @param ele The select list item
 * @param val The value to select
 */
dwr.util._selectListItem = function(ele, val) {
  // We deal with select list elements by selecting the matching option
  // Begin by searching through the values
  var found = false;
  var i;
  for (i = 0; i < ele.options.length; i++) {
    if (ele.options[i].value == val) {
      ele.options[i].selected = true;
      found = true;
    }
    else {
      ele.options[i].selected = false;
    }
  }

  // If that fails then try searching through the visible text
  if (found) return;

  for (i = 0; i < ele.options.length; i++) {
    if (ele.options[i].text == val) {
      ele.options[i].selected = true;
    }
    else {
      ele.options[i].selected = false;
    }
  }
};

/**
 * Read the current value for a given HTML element.
 * @see http://getahead.org/dwr/browser/util/getvalue
 */
dwr.util.getValue = function(ele, options) {
  if (options == null) options = {};
  var orig = ele;
  if (typeof ele == "string") {
    ele = dwr.util.byId(ele);
    // We can work with names and need to sometimes for radio buttons, and IE has
    // an annoying bug where getElementById() returns an element based on name if
    // it doesn't find it by id. Here we don't want to do that, so:
    if (ele && ele.id != orig) ele = null;
  }
  var nodes = null;
  if (ele == null) {
    // Now it is time to look by name
    nodes = document.getElementsByName(orig);
    if (nodes.length >= 1) ele = nodes.item(0);
  }
  if (ele == null) {
    dwr.util._debug("getValue() can't find an element with id/name: " + orig + ".");
    return "";
  }

  if (dwr.util._isHTMLElement(ele, "select")) {
    // Using "type" property instead of "multiple" as "type" is an official 
    // client-side property since JS 1.1
    if (ele.type == "select-multiple") {
      var reply = new Array();
      for (var i = 0; i < ele.options.length; i++) {
        var item = ele.options[i];
        if (item.selected) {
          var valueAttr = item.getAttributeNode("value");
          if (valueAttr && valueAttr.specified) {
            reply.push(item.value);
          }
          else {
            reply.push(item.text);
          }
        }
      }
      return reply;
    }
    else {
      var sel = ele.selectedIndex;
      if (sel != -1) {
        var item = ele.options[sel];
        var valueAttr = item.getAttributeNode("value");
        if (valueAttr && valueAttr.specified) {
          return item.value;
        }
        return item.text;
      }
      else {
        return "";
      }
    }
  }

  if (dwr.util._isHTMLElement(ele, "input")) {
    if (ele.type == "radio") {
      if (nodes && nodes.length >= 1) {
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes.item(i);
          if (node.type == ele.type) {
            if (node.checked) return node.value;
          }
        }
      }
      return ele.checked;
    }
    if (ele.type == "checkbox") {
      if (nodes && nodes.length >= 1) {
        var reply = [];
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes.item(i);
          if (node.type == ele.type) {
            if (node.checked) reply.push(node.value);
          }
        }
        return reply;
      }
      return ele.checked;
    }
    return ele.value;
  }

  if (dwr.util._isHTMLElement(ele, "textarea")) {
    return ele.value;
  }

  if (dwr.util._shouldEscapeHtml(options)) {
    if (ele.textContent) return ele.textContent;
    else if (ele.innerText) return ele.innerText;
  }
  return ele.innerHTML;
};

/**
 * getText() is like getValue() except that it reads the text (and not the value) from select elements
 * @see http://getahead.org/dwr/browser/util/gettext
 */
dwr.util.getText = function(ele) {
  ele = dwr.util._getElementById(ele, "getText()");
  if (ele == null) return null;
  if (!dwr.util._isHTMLElement(ele, "select")) {
    dwr.util._debug("getText() can only be used with select elements. Attempt to use: " + dwr.util._detailedTypeOf(ele) + " from  id: " + orig + ".");
    return "";
  }

  // This is a bit of a scam because it assumes single select
  // but I'm not sure how we should treat multi-select.
  var sel = ele.selectedIndex;
  if (sel != -1) {
    return ele.options[sel].text;
  }
  else {
    return "";
  }
};

/**
 * Given a map, or a recursive structure consisting of arrays and maps, call 
 * setValue() for all leaf entries and use intermediate levels to form nested
 * element ids.
 * @see http://getahead.org/dwr/browser/util/setvalues
 */
dwr.util.setValues = function(data, options) {
  var prefix = "";
  if (options && options.prefix) prefix = options.prefix;
  if (options && options.idPrefix) prefix = options.idPrefix;
  dwr.util._setValuesRecursive(data, prefix);
};

/**
 * @private Recursive helper for setValues()
 */
dwr.util._setValuesRecursive = function(data, idpath) {
  // Array containing objects -> add "[n]" to prefix and make recursive call
  // for each item object
  if (dwr.util._isArray(data) && data.length > 0 && dwr.util._isObject(data[0])) {
    for (var i = 0; i < data.length; i++) {
      dwr.util._setValuesRecursive(data[i], idpath+"["+i+"]");
    }
  }
  // Object (not array) -> handle nested object properties
  else if (dwr.util._isObject(data) && !dwr.util._isArray(data)) {
    for (var prop in data) {
      var subidpath = idpath ? idpath+"."+prop : prop;
      // Object (not array), or array containing objects -> call ourselves recursively
      if (dwr.util._isObject(data[prop]) && !dwr.util._isArray(data[prop]) 
          || dwr.util._isArray(data[prop]) && data[prop].length > 0 && dwr.util._isObject(data[prop][0])) {
        dwr.util._setValuesRecursive(data[prop], subidpath);
      }
      // Functions -> skip
      else if (typeof data[prop] == "function") {
        // NOP
      }
      // Only simple values left (or array of simple values, or empty array)
      // -> call setValue()
      else {
        // Are there any elements with that id or name
        if (dwr.util.byId(subidpath) != null || document.getElementsByName(subidpath).length >= 1) {
          dwr.util.setValue(subidpath, data[prop]);
        }
      }
    }
  }
};

/**
 * Given a map, or a recursive structure consisting of arrays and maps, call 
 * getValue() for all leaf entries and use intermediate levels to form nested
 * element ids.
 * Given a string or element that refers to a form, create an object from the 
 * elements of the form.
 * @see http://getahead.org/dwr/browser/util/getvalues
 */
dwr.util.getValues = function(data, options) {
  if (typeof data == "string" || dwr.util._isHTMLElement(data)) {
    return dwr.util.getFormValues(data);
  }
  else {
    var prefix = "";
    if (options != null && options.prefix) prefix = options.prefix;
    if (options != null && options.idPrefix) prefix = options.idPrefix;
    dwr.util._getValuesRecursive(data, prefix);
    return data;
  }
};

/**
 * Given a string or element that refers to a form, create an object from the 
 * elements of the form.
 * @see http://getahead.org/dwr/browser/util/getvalues
 */
dwr.util.getFormValues = function(eleOrNameOrId) {
  var ele = null;
  if (typeof eleOrNameOrId == "string") {
    ele = document.forms[eleOrNameOrId];
    if (ele == null) ele = dwr.util.byId(eleOrNameOrId);
  }
  else if (dwr.util._isHTMLElement(eleOrNameOrId)) {
    ele = eleOrNameOrId;
  }
  if (ele != null) {
    if (ele.elements == null) {
      alert("getFormValues() requires an object or reference to a form element.");
      return null;
    }
    var reply = {};
    var name;
    var value;
    for (var i = 0; i < ele.elements.length; i++) {
      if (ele[i].type in {button:0,submit:0,reset:0,image:0,file:0}) continue;
      if (ele[i].name) {
        name = ele[i].name;
        value = dwr.util.getValue(name);
      }
      else {
        if (ele[i].id) name = ele[i].id;
        else name = "element" + i;
        value = dwr.util.getValue(ele[i]);
      }
      reply[name] = value;
    }
    return reply;
  }
};

/**
 * @private Recursive helper for getValues().
 */
dwr.util._getValuesRecursive = function(data, idpath) {
  // Array containing objects -> add "[n]" to idpath and make recursive call
  // for each item object
  if (dwr.util._isArray(data) && data.length > 0 && dwr.util._isObject(data[0])) {
    for (var i = 0; i < data.length; i++) {
      dwr.util._getValuesRecursive(data[i], idpath+"["+i+"]");
    }
  }
  // Object (not array) -> handle nested object properties
  else if (dwr.util._isObject(data) && !dwr.util._isArray(data)) {
    for (var prop in data) {
      var subidpath = idpath ? idpath+"."+prop : prop;
      // Object, or array containing objects -> call ourselves recursively
      if (dwr.util._isObject(data[prop]) && !dwr.util._isArray(data[prop])
          || dwr.util._isArray(data[prop]) && data[prop].length > 0 && dwr.util._isObject(data[prop][0])) {
        dwr.util._getValuesRecursive(data[prop], subidpath);
      }
      // Functions -> skip
      else if (typeof data[prop] == "function") {
        // NOP
      }
      // Only simple values left (or array of simple values, or empty array)
      // -> call getValue()
      else {
        // Are there any elements with that id or name
        if (dwr.util.byId(subidpath) != null || document.getElementsByName(subidpath).length >= 1) {
          data[prop] = dwr.util.getValue(subidpath);
        }
      }
    }
  }
};

/**
 * Add options to a list from an array or map.
 * @see http://getahead.org/dwr/browser/lists
 */
dwr.util.addOptions = function(ele, data/*, options*/) {
  ele = dwr.util._getElementById(ele, "addOptions()");
  if (ele == null) return;
  var useOptions = dwr.util._isHTMLElement(ele, "select");
  var useLi = dwr.util._isHTMLElement(ele, ["ul", "ol"]);
  if (!useOptions && !useLi) {
    dwr.util._debug("addOptions() can only be used with select/ul/ol elements. Attempt to use: " + dwr.util._detailedTypeOf(ele));
    return;
  }
  if (data == null) return;
  
  var argcount = arguments.length;
  var options = {};
  var lastarg = arguments[argcount - 1]; 
  if (argcount > 2 && dwr.util._isObject(lastarg)) {
    options = lastarg;
    argcount--;
  }
  var arg3 = null; if (argcount >= 3) arg3 = arguments[2];
  var arg4 = null; if (argcount >= 4) arg4 = arguments[3];
  if (!options.optionCreator && useOptions) options.optionCreator = dwr.util._defaultOptionCreator;
  if (!options.optionCreator && useLi) options.optionCreator = dwr.util._defaultListItemCreator;

  var text, value, li;
  if (dwr.util._isArray(data)) {
    // Loop through the data that we do have
    for (var i = 0; i < data.length; i++) {
      options.data = data[i];
      options.text = null;
      options.value = null;
      if (useOptions) {
        if (arg3 != null) {
          if (arg4 != null) {
            options.text = dwr.util._getValueFrom(data[i], arg4);
            options.value = dwr.util._getValueFrom(data[i], arg3);
          }
          else options.text = options.value = dwr.util._getValueFrom(data[i], arg3);
        }
        else options.text = options.value = dwr.util._getValueFrom(data[i]);

        if (options.text != null || options.value) {
          var opt = options.optionCreator(options);
          opt.text = options.text;
          opt.value = options.value;
          ele.options[ele.options.length] = opt;
        }
      }
      else {
        options.value = dwr.util._getValueFrom(data[i], arg3);
        if (options.value != null) {
          li = options.optionCreator(options);
          if (dwr.util._shouldEscapeHtml(options)) {
            options.value = dwr.util.escapeHtml(options.value);
          }
          li.innerHTML = options.value;
          ele.appendChild(li);
        }
      }
    }
  }
  else if (arg4 != null) {
    if (!useOptions) {
      alert("dwr.util.addOptions can only create select lists from objects.");
      return;
    }
    for (var prop in data) {
      options.data = data[prop];
      options.value = dwr.util._getValueFrom(data[prop], arg3);
      options.text = dwr.util._getValueFrom(data[prop], arg4);

      if (options.text != null || options.value) {
        var opt = options.optionCreator(options);
        opt.text = options.text;
        opt.value = options.value;
        ele.options[ele.options.length] = opt;
      }
    }
  }
  else {
    if (!useOptions) {
      dwr.util._debug("dwr.util.addOptions can only create select lists from objects.");
      return;
    }
    for (var prop in data) {
      options.data = data[prop];
      if (!arg3) {
        options.value = prop;
        options.text = data[prop];
      }
      else {
        options.value = data[prop];
        options.text = prop;
      }
      if (options.text != null || options.value) {
        var opt = options.optionCreator(options);
        opt.text = options.text;
        opt.value = options.value;
        ele.options[ele.options.length] = opt;
      }
    }
  }

  // All error routes through this function result in a return, so highlight now
  dwr.util.highlight(ele, options); 
};

/**
 * @private Get the data from an array function for dwr.util.addOptions
 */
dwr.util._getValueFrom = function(data, method) {
  if (method == null) return data;
  else if (typeof method == 'function') return method(data);
  else return data[method];
};

/**
 * @private Default option creation function
 */
dwr.util._defaultOptionCreator = function(options) {
  return new Option();
};

/**
 * @private Default list item creation function
 */
dwr.util._defaultListItemCreator = function(options) {
  return document.createElement("li");
};

/**
 * Remove all the options from a select list (specified by id)
 * @see http://getahead.org/dwr/browser/lists
 */
dwr.util.removeAllOptions = function(ele) {
  ele = dwr.util._getElementById(ele, "removeAllOptions()");
  if (ele == null) return;
  var useOptions = dwr.util._isHTMLElement(ele, "select");
  var useLi = dwr.util._isHTMLElement(ele, ["ul", "ol"]);
  if (!useOptions && !useLi) {
    dwr.util._debug("removeAllOptions() can only be used with select, ol and ul elements. Attempt to use: " + dwr.util._detailedTypeOf(ele));
    return;
  }
  if (useOptions) {
    ele.options.length = 0;
  }
  else {
    while (ele.childNodes.length > 0) {
      ele.removeChild(ele.firstChild);
    }
  }
};

/**
 * Create rows inside a the table, tbody, thead or tfoot element (given by id).
 * @see http://getahead.org/dwr/browser/tables
 */
dwr.util.addRows = function(ele, data, cellFuncs, options) {
  ele = dwr.util._getElementById(ele, "addRows()");
  if (ele == null) return;
  if (!dwr.util._isHTMLElement(ele, ["table", "tbody", "thead", "tfoot"])) {
    dwr.util._debug("addRows() can only be used with table, tbody, thead and tfoot elements. Attempt to use: " + dwr.util._detailedTypeOf(ele));
    return;
  }
  if (!options) options = {};
  if (!options.rowCreator) options.rowCreator = dwr.util._defaultRowCreator;
  if (!options.cellCreator) options.cellCreator = dwr.util._defaultCellCreator;
  var tr, rowNum;
  if (dwr.util._isArray(data)) {
    for (rowNum = 0; rowNum < data.length; rowNum++) {
      options.rowData = data[rowNum];
      options.rowIndex = rowNum;
      options.rowNum = rowNum;
      options.data = null;
      options.cellNum = -1;
      tr = dwr.util._addRowInner(cellFuncs, options);
      if (tr != null) ele.appendChild(tr);
    }
  }
  else if (typeof data == "object") {
    rowNum = 0;
    for (var rowIndex in data) {
      options.rowData = data[rowIndex];
      options.rowIndex = rowIndex;
      options.rowNum = rowNum;
      options.data = null;
      options.cellNum = -1;
      tr = dwr.util._addRowInner(cellFuncs, options);
      if (tr != null) ele.appendChild(tr);
      rowNum++;
    }
  }

  dwr.util.highlight(ele, options);
};

/**
 * @private Internal function to draw a single row of a table.
 */
dwr.util._addRowInner = function(cellFuncs, options) {
  var tr = options.rowCreator(options);
  if (tr == null) return null;
  for (var cellNum = 0; cellNum < cellFuncs.length; cellNum++) {
    var func = cellFuncs[cellNum];
    if (typeof func == 'function') options.data = func(options.rowData, options);
    else options.data = func || "";
    options.cellNum = cellNum;
    var td = options.cellCreator(options);
    if (td != null) {
      if (options.data != null) {
        if (dwr.util._isHTMLElement(options.data)) td.appendChild(options.data);
        else {
          if (dwr.util._shouldEscapeHtml(options) && typeof(options.data) == "string") {
            td.innerHTML = dwr.util.escapeHtml(options.data);
          }
          else {
            td.innerHTML = options.data;
          }
        }
      }
      tr.appendChild(td);
    }
  }
  return tr;
};

/**
 * @private Default row creation function
 */
dwr.util._defaultRowCreator = function(options) {
  return document.createElement("tr");
};

/**
 * @private Default cell creation function
 */
dwr.util._defaultCellCreator = function(options) {
  return document.createElement("td");
};

/**
 * Remove all the children of a given node.
 * @see http://getahead.org/dwr/browser/tables
 */
dwr.util.removeAllRows = function(ele, options) {
  ele = dwr.util._getElementById(ele, "removeAllRows()");
  if (ele == null) return;
  if (!options) options = {};
  if (!options.filter) options.filter = function() { return true; };
  if (!dwr.util._isHTMLElement(ele, ["table", "tbody", "thead", "tfoot"])) {
    dwr.util._debug("removeAllRows() can only be used with table, tbody, thead and tfoot elements. Attempt to use: " + dwr.util._detailedTypeOf(ele));
    return;
  }
  var child = ele.firstChild;
  var next;
  while (child != null) {
    next = child.nextSibling;
    if (options.filter(child)) {
      ele.removeChild(child);
    }
    child = next;
  }
};

/**
 * dwr.util.byId(ele).className = "X", that we can call from Java easily.
 */
dwr.util.setClassName = function(ele, className) {
  ele = dwr.util._getElementById(ele, "setClassName()");
  if (ele == null) return;
  ele.className = className;
};

/**
 * dwr.util.byId(ele).className += "X", that we can call from Java easily.
 */
dwr.util.addClassName = function(ele, className) {
  ele = dwr.util._getElementById(ele, "addClassName()");
  if (ele == null) return;
  ele.className += " " + className;
};

/**
 * dwr.util.byId(ele).className -= "X", that we can call from Java easily
 * From code originally by Gavin Kistner
 */
dwr.util.removeClassName = function(ele, className) {
  ele = dwr.util._getElementById(ele, "removeClassName()");
  if (ele == null) return;
  var regex = new RegExp("(^|\\s)" + className + "(\\s|$)", 'g');
  ele.className = ele.className.replace(regex, '');
};

/**
 * dwr.util.byId(ele).className |= "X", that we can call from Java easily.
 */
dwr.util.toggleClassName = function(ele, className) {
  ele = dwr.util._getElementById(ele, "toggleClassName()");
  if (ele == null) return;
  var regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
  if (regex.test(ele.className)) {
    ele.className = ele.className.replace(regex, '');
  }
  else {
    ele.className += " " + className;
  }
};

/**
 * Clone a node and insert it into the document just above the 'template' node
 * @see http://getahead.org/dwr/???
 */
dwr.util.cloneNode = function(ele, options) {
  ele = dwr.util._getElementById(ele, "cloneNode()");
  if (ele == null) return null;
  if (options == null) options = {};
  var clone = ele.cloneNode(true);
  if (options.idPrefix || options.idSuffix) {
    dwr.util._updateIds(clone, options);
  }
  else {
    dwr.util._removeIds(clone);
  }
  ele.parentNode.insertBefore(clone, ele);
  return clone;
};

/**
 * @private Update all of the ids in an element tree
 */
dwr.util._updateIds = function(ele, options) {
  if (options == null) options = {};
  if (ele.id) {
    ele.setAttribute("id", (options.idPrefix || "") + ele.id + (options.idSuffix || ""));
  }
  var children = ele.childNodes;
  for (var i = 0; i < children.length; i++) {
    var child = children.item(i);
    if (child.nodeType == 1 /*Node.ELEMENT_NODE*/) {
      dwr.util._updateIds(child, options);
    }
  }
};

/**
 * @private Remove all the Ids from an element
 */
dwr.util._removeIds = function(ele) {
  if (ele.id) ele.removeAttribute("id");
  var children = ele.childNodes;
  for (var i = 0; i < children.length; i++) {
    var child = children.item(i);
    if (child.nodeType == 1 /*Node.ELEMENT_NODE*/) {
      dwr.util._removeIds(child);
    }
  }
};

/**
 * Clone a template node and its embedded template child nodes according to
 * cardinalities (of arrays) in supplied data.  
 */
dwr.util.cloneNodeForValues = function(templateEle, data, options) {
  templateEle = dwr.util._getElementById(templateEle, "cloneNodeForValues()");
  if (templateEle == null) return null;
  if (options == null) options = {};
  var idpath;
  if (options.idPrefix != null)
    idpath = options.idPrefix;
  else
    idpath = templateEle.id || ""; 
  return dwr.util._cloneNodeForValuesRecursive(templateEle, data, idpath, options);
};

/**
 * @private Recursive helper for cloneNodeForValues(). 
 */
dwr.util._cloneNodeForValuesRecursive = function(templateEle, data, idpath, options) {
  // Incoming array -> make an id for each item and call clone of the template 
  // for each of them
  if (dwr.util._isArray(data)) {
    var clones = [];
    for (var i = 0; i < data.length; i++) {
      var item = data[i];
      var clone = dwr.util._cloneNodeForValuesRecursive(templateEle, item, idpath + "[" + i + "]", options);
      clones.push(clone);
    }
    return clones;
  }
  else
  // Incoming object (not array) -> clone the template, add id prefixes, add 
  // clone to DOM, and then recurse into any array properties if they contain 
  // objects and there is a suitable template
  if (dwr.util._isObject(data) && !dwr.util._isArray(data)) {
    var clone = templateEle.cloneNode(true);
    if (options.updateCloneStyle && clone.style) {
      for (var propname in options.updateCloneStyle) {
        clone.style[propname] = options.updateCloneStyle[propname];
      }
    }
    dwr.util._replaceIds(clone, templateEle.id, idpath);
    templateEle.parentNode.insertBefore(clone, templateEle);
    dwr.util._cloneSubArrays(data, idpath, options);
    return clone;
  }

  // It is an error to end up here so we return nothing
  return null;
};

/**
 * @private Substitute a leading idpath fragment with another idpath for all 
 * element ids tree, and remove ids that don't match the idpath. 
 */
dwr.util._replaceIds = function(ele, oldidpath, newidpath) {
  if (ele.id) {
    var newId = null;
    if (ele.id == oldidpath) {
      newId = newidpath;
    }
    else if (ele.id.length > oldidpath.length) {
      if (ele.id.substr(0, oldidpath.length) == oldidpath) {
        var trailingChar = ele.id.charAt(oldidpath.length);
        if (trailingChar == "." || trailingChar == "[") {
          newId = newidpath + ele.id.substr(oldidpath.length);
        }
      }
    }
    if (newId) {
      ele.setAttribute("id", newId);
    }
    else {
      ele.removeAttribute("id");
    }
  }
  var children = ele.childNodes;
  for (var i = 0; i < children.length; i++) {
    var child = children.item(i);
    if (child.nodeType == 1 /*Node.ELEMENT_NODE*/) {
      dwr.util._replaceIds(child, oldidpath, newidpath);
    }
  }
};

/**
 * @private Finds arrays in supplied data and uses any corresponding template 
 * node to make a clone for each item in the array. 
 */
dwr.util._cloneSubArrays = function(data, idpath, options) {
  for (prop in data) {
    var value = data[prop];
    // Look for potential recursive cloning in all array properties
    if (dwr.util._isArray(value)) {
      // Only arrays with objects are interesting for cloning
      if (value.length > 0 && dwr.util._isObject(value[0])) {
        var subTemplateId = idpath + "." + prop;
        var subTemplateEle = dwr.util.byId(subTemplateId);
        if (subTemplateEle != null) {
          dwr.util._cloneNodeForValuesRecursive(subTemplateEle, value, subTemplateId, options);
        }
      }
    }
    // Continue looking for arrays in object properties
    else if (dwr.util._isObject(value)) {
      dwr.util._cloneSubArrays(value, idpath + "." + prop, options);
    }
  }
}

/**
 * @private Helper to turn a string into an element with an error message
 */
dwr.util._getElementById = function(ele, source) {
  var orig = ele;
  ele = dwr.util.byId(ele);
  if (ele == null) {
    dwr.util._debug(source + " can't find an element with id: " + orig + ".");
  }
  return ele;
};

/**
 * @private Is the given node an HTML element (optionally of a given type)?
 * @param ele The element to test
 * @param nodeName eg "input", "textarea" - check for node name (optional)
 *         if nodeName is an array then check all for a match.
 */
dwr.util._isHTMLElement = function(ele, nodeName) {
  if (ele == null || typeof ele != "object" || ele.nodeName == null) {
    return false;
  }
  if (nodeName != null) {
    var test = ele.nodeName.toLowerCase();
    if (typeof nodeName == "string") {
      return test == nodeName.toLowerCase();
    }
    if (dwr.util._isArray(nodeName)) {
      var match = false;
      for (var i = 0; i < nodeName.length && !match; i++) {
        if (test == nodeName[i].toLowerCase()) {
          match =  true;
        }
      }
      return match;
    }
    dwr.util._debug("dwr.util._isHTMLElement was passed test node name that is neither a string or array of strings");
    return false;
  }
  return true;
};

/**
 * @private Like typeOf except that more information for an object is returned other than "object"
 */
dwr.util._detailedTypeOf = function(x) {
  var reply = typeof x;
  if (reply == "object") {
    reply = Object.prototype.toString.apply(x); // Returns "[object class]"
    reply = reply.substring(8, reply.length-1);  // Just get the class bit
  }
  return reply;
};

/**
 * @private Object detector. Excluding null from objects.
 */
dwr.util._isObject = function(data) {
  return (data && typeof data == "object");
};

/**
 * @private Array detector. Note: instanceof doesn't work with multiple frames.
 */
dwr.util._isArray = function(data) {
  return (data && data.join);
};

/**
 * @private Date detector. Note: instanceof doesn't work with multiple frames.
 */
dwr.util._isDate = function(data) {
  return (data && data.toUTCString) ? true : false;
};

/**
 * @private Used by setValue. Gets around the missing functionallity in IE.
 */
dwr.util._importNode = function(doc, importedNode, deep) {
  var newNode;

  if (importedNode.nodeType == 1 /*Node.ELEMENT_NODE*/) {
    newNode = doc.createElement(importedNode.nodeName);

    for (var i = 0; i < importedNode.attributes.length; i++) {
      var attr = importedNode.attributes[i];
      if (attr.nodeValue != null && attr.nodeValue != '') {
        newNode.setAttribute(attr.name, attr.nodeValue);
      }
    }

    if (typeof importedNode.style != "undefined") {
      newNode.style.cssText = importedNode.style.cssText;
    }
  }
  else if (importedNode.nodeType == 3 /*Node.TEXT_NODE*/) {
    newNode = doc.createTextNode(importedNode.nodeValue);
  }

  if (deep && importedNode.hasChildNodes()) {
    for (i = 0; i < importedNode.childNodes.length; i++) {
      newNode.appendChild(dwr.util._importNode(doc, importedNode.childNodes[i], true));
    }
  }

  return newNode;
};

/** @private Used internally when some message needs to get to the programmer */
dwr.util._debug = function(message, stacktrace) {
  var written = false;
  try {
    if (window.console) {
      if (stacktrace && window.console.trace) window.console.trace();
      window.console.log(message);
      written = true;
    }
    else if (window.opera && window.opera.postError) {
      window.opera.postError(message);
      written = true;
    }
  }
  catch (ex) { /* ignore */ }

  if (!written) {
    var debug = document.getElementById("dwr-debug");
    if (debug) {
      var contents = message + "<br/>" + debug.innerHTML;
      if (contents.length > 2048) contents = contents.substring(0, 2048);
      debug.innerHTML = contents;
    }
  }
};


BSDAjaxUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "BSDLocationUtils", "BSDPoint", "BSDTypeUtils", "BSDLogUtils", "/ajax/interface/KCMAjaxGui.js", "/ajax/engine.js", "/ajax/util.js"),

	doNavigation: function(pageID, pageArguments, replyFunction, activityMessage) {
		if(BSDAjaxUtils.getIsAjaxDisabled()) {
			BSDLogUtils.debug("doNavigation: Ajax disabled");
			return;
		}
		if(activityMessage) {
			BSDAjaxUtils.showActivityMessage(activityMessage);
		}
		if(!replyFunction) {
			replyFunction = BSDAjaxUtils.doNavigationReply;
		}
		BSDLogUtils.debug("doNavigation: " + pageID);
		KCMAjaxGui.doNavigation(pageID, pageArguments, replyFunction);
	},
	
	doNavigationReply: function(data, suppressAlert) {
		BSDAjaxUtils.hideActivityMessage();
		if(data.httpResponseCode != 200) {

			BSDLogUtils.error(DWRUtil.toDescriptiveString(data, 2));
			return false;
		}
		return true;
	},
	
	doRendering: function(pageID, pageArguments, replyFunction, parentElementId, activityMessage) {
		if(BSDAjaxUtils.getIsAjaxDisabled()) {
			BSDLogUtils.debug("doRendering: Ajax disabled");
			return;
		}
		if(activityMessage) {
			BSDAjaxUtils.showActivityMessage(activityMessage);
		}
		if(!replyFunction) {
			replyFunction = function(str) { 
						BSDAjaxUtils.doRenderingReply(str, parentElementId, null, activityMessage) 
					};
		}
		
		if(!pageArguments) {
			pageArguments = new Array();
		}
		
		if(!pageID) {
			pageID =  document.URL; //'document_url' :
		}
		
		BSDLogUtils.debug("doRendering: " + pageID);
		KCMAjaxGui.doRendering(pageID, pageArguments, replyFunction);
	},
	
	doRenderingReply: function(data, parentElementId, suppressAlert, activityMessage) {
		BSDAjaxUtils.hideActivityMessage();
		if(data.httpResponseCode != 200) {
			BSDLogUtils.error(DWRUtil.toDescriptiveString(data, 2));
			return false;
		}
		if(!parentElementId) {
			return true;
		}
		var element = BSDDOMUtils.getObjectById(parentElementId);
		if(!element) {
			BSDLogUtils.error(DWRUtil.toDescriptiveString(data, 2));
			return true;
		}
		var parentElement = BSDDOMUtils.getObjectById(parentElementId);
		if(!parentElement) {
			BSDLogUtils.error("ERROR: doRenderingReply couldn't find parent element: " + parentElementId);
			return;
		}
		parentElement.innerHTML = data.html;


		if(activityMessage) {
			BSDAjaxUtils.hideActivityMessage();
		}
		return true;
	},
	
	showActivityMessage: function(message) {
		var messageElement = document.bsdAjaxActivityMessage; 
		if(!messageElement) {
			messageElement = BSDDOMUtils.createElement("div");
			BSDVisibilityUtils.hideObject(messageElement);
			messageElement.className = "BSDAjaxActivityMessage";
			document.body.appendChild(messageElement);
			document.bsdAjaxActivityMessage = messageElement;
		}
		BSDDOMUtils.setText(messageElement, message);
		var location;
		if(document.documentElement && document.documentElement.scrollLeft && document.documentElement.scrollTop) {
			location = new BSDPoint(document.documentElement.scrollLeft, document.documentElement.scrollTop);
		} else if(window.pageXOffset || window.pageYOffset) {
			var x = window.pageXOffset;
			if(!x) {
				x = 1;
			}
			var y = window.pageYOffset;
			if(!y) {
				y = 1;
			}
			location = new BSDPoint(x, y);
		} else if(document.body && document.body.scrollLeft && document.body.scrollTop) {
			location = new BSDPoint(document.body.scrollLeft, document.body.scrollTop);
		} else {
			location = new BSDPoint(1, 1);
		}
		BSDLocationUtils.setElementLocation(messageElement, location);
		BSDVisibilityUtils.showObject(messageElement);

	},
	
	hideActivityMessage: function() {
		var messageElement = document.bsdAjaxActivityMessage; 
		if(messageElement) {
			BSDVisibilityUtils.hideObject(messageElement);
		}
	},
	
	getIsAjaxDisabled: function() {

		return (document.bsdAjaxDisabled && document.bsdAjaxDisabled == true);
	},
	
	setIsAjaxDisabled: function(value) {

		if(BSDTypeUtils.isString(value) && value == "on") {
			document.bsdAjaxDisabled = true;
		} else if(BSDTypeUtils.isBoolean(value) && value) {
			document.bsdAjaxDisabled = true;		
		} else {
			document.bsdAjaxDisabled = false;		
		}
	},
	
	doJavascriptInit: function(parentNode) {
		var jsInit;
		if(parentNode) {
			jsInit = BSDDOMUtils.getObjectByIdFromParent(parentNode, 'JAVASCRIPT_INITIALIZATION');
		} else {
			jsInit = BSDDOMUtils.getObjectById('JAVASCRIPT_INITIALIZATION');
		}
		
		if(jsInit && jsInit.nodeName && jsInit.nodeName.toLowerCase() == 'script') {


			eval(jsInit.innerHTML);
		}
	}
	
}
BSDContentTagModerationUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDAjaxUtils"),
	VERSION: 1.1,

	moderateContentTag: function(tagLinkElement, tagName, tagElementId) {
		var tagElement = BSDDOMUtils.getObjectById(tagElementId);
		if(!tagElement) {
			BSDLogUtils.error("ERROR: Couldn't find tag row from moderation link for tag " + tagName);
			return true;
		}
		
		BSDContentTagModerationUtils.executeModeration(tagElement, tagLinkElement, tagName);
		
		return false;
	},
	
	unmoderateContentTag: function(tagLinkElement, tagName, tagElementId) {
		var tagElement = BSDDOMUtils.getObjectById(tagElementId);
		if(!tagElement) {
			BSDLogUtils.error("ERROR: Couldn't find tag row from moderation link for tag " + tagName);
			return true;
		}

		BSDContentTagModerationUtils.executeModeration(tagElement, tagLinkElement, tagName);
		
		return false;
	
	},
	
	highlightContentTag: function(tagElementId) {
		var tagElement = BSDDOMUtils.getObjectById(tagElementId);
		if(!tagElement) {
			BSDLogUtils.error("ERROR: Couldn't find tag row from moderation link for tag " + tagName);
			return true;
		}
		BSDHighlightUtils.highlightElementByOverlay(tagElement);
	},

	unHighlightContentTag: function(tagElementId) {
		var tagElement = BSDDOMUtils.getObjectById(tagElementId);
		if(!tagElement) {
			BSDLogUtils.error("ERROR: Couldn't find tag row from moderation link for tag " + tagName);
			return true;
		}
		BSDHighlightUtils.unHighlightElementByOverlay(tagElement);
	},

  	executeModeration: function(tagElement, tagLinkElement, tagName) {
		this.printReply = true;

		var pageID = '/cms/content/ajax/contenttagmoderator';
		/*if(restore) {
			pageID = '/cms/content/ajax/contenttagunmoderator';
		}*/
		var pageArguments = {'content_tag_name' : tagName
							};
		if(!confirm("Are you sure you want to delete the tag \"" + tagName + "\"?\n(WARNING: Tag deletions can not be undone, and will remove this tag from the entire site.  Deleted tags cannot be chosen again)\n\nDo you only want to remove the tag from this entry?  Use \"Remove Tag\" instead")) {
			return;
		}

		BSDLogUtils.debug("Doing tag moderation: " + tagName);

		BSDAjaxUtils.doNavigation(pageID, pageArguments,  
				{ 
					callback:function(str) { 
						BSDContentTagModerationUtils.doModerationReply(str, tagElement, tagLinkElement, tagName);
					} 
				});  	
  	},
  	
  	doModerationReply: function(data, tagElement, tagLinkElement, tagName) {
  		var successful = BSDAjaxUtils.doNavigationReply(data, true);
		
		if(!successful) {
			alert("ERROR: A system error occured.  Please try again");
	    } else if(data.errorMessage) {
	  		BSDLogUtils.debug("Got error: " + data.errorMessage);
			alert(data.errorMessage);
		} else {
	  		BSDLogUtils.debug("Got response: " + data.html);
			BSDDOMUtils.changeElementStyle(tagElement, "text-decoration", "line-through");
			var nameElement = BSDDOMUtils.getObjectByIdFromParent(tagElement, 'CONTENT_TAG_NAME');
			var linkElement = BSDDOMUtils.getObjectByIdFromParent(tagElement, 'DISCUSSION_TAG_LINK');

			if(nameElement) {
				BSDDOMUtils.changeElementStyle(nameElement, "text-decoration", "line-through");
				BSDDOMUtils.changeElementStyle(nameElement, "color", "red");
			} else if(linkElement) {

				BSDDOMUtils.changeElementStyle(linkElement, "text-decoration", "line-through");
				BSDDOMUtils.changeElementStyle(linkElement, "color", "red");
			}
		}
  	},
  	
  	
  	removeContentTag: function(tagLinkElement, tagName, tagElementId, tagDeploymentId) {
		var tagElement = BSDDOMUtils.getObjectById(tagElementId);
		if(!tagElement) {
			alert("No tag element: " + tagElementId);
			BSDLogUtils.error("ERROR: Couldn't find tag row from moderation link for tag " + tagName);
			return true;
		}

		BSDContentTagModerationUtils.executeRemove(tagElement, tagLinkElement, tagName, tagDeploymentId);
		
		return false;
	},
  	
  	executeRemove: function(tagElement, tagLinkElement, tagName, tagDeploymentId) {
		this.printReply = true;

		var pageID = '/cms/content/ajax/contenttagmoderator';
		/*if(restore) {
			pageID = '/cms/content/ajax/contenttagunmoderator';
		}*/

		var pageArguments = {'content_tag_name' : tagName,
							 'content_tag_deployment_id' : tagDeploymentId
							};

		BSDLogUtils.debug("Doing tag removal: " + tagName + " " + tagDeploymentId + " " + pageArguments);

		BSDAjaxUtils.doNavigation(pageID, pageArguments,  
				{ 
					callback:function(str) { 
						BSDContentTagModerationUtils.doModerationReply(str, tagElement, tagLinkElement, tagName);
					} 
				});  	
  	}
		  	
  		
	
}
BSDFormUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDNavigationUtils"),
	VERSION: 1.0,
	
	backgroundFormIndex: 0,
	
	submitOnEnter: function(formField, e) {
		var keycode;
		if(window.event) {
			keycode = window.event.keyCode;
		} else if(e) {
			keycode = e.which;
		} else {
			return true;
		}
		if(keycode == 13) {
	   		formField.form.submit();
	 		return false;
	   	} else {
		   return true;
		}
	},
	
	cloneFormElement: function(formElementId, formElementParentId, maxCount, dontReIdElements) {
		var parentElement;
		var inputElement;
		if(formElementParentId) {
			parentElement = BSDDOMUtils.getObjectById(formElementParentId);
		}
		if(parentElement) {
			inputElement = BSDDOMUtils.getObjectByIdFromParent(parentElement, formElementId);
		} else {
			inputElement = BSDDOMUtils.getObjectById(formElementId);
			parentElement = inputElement;
		}
	
	    var newParentElement = null;
		var newElement = null;
        if(inputElement) {
			var count = 1;
			if(inputElement.cloneCount) {
				count = parseInt(inputElement.cloneCount);
			}
			if(maxCount && count >= maxCount) {
				return;
			}
			
        	if(!inputElement.name) {
        		alert("ERROR: Couldn't get name of input element");
        		return;
        	}
            newParentElement = parentElement.cloneNode(true);
            if(parentElement.nextSibling) {
             	parentElement.parentNode.insertBefore(newParentElement, parentElement.nextSibling);
            } else {
             	parentElement.parentNode.appendChild(newParentElement);           
            }
            parentElement.parentNode.appendChild(newParentElement);
			
			count++;
			inputElement.cloneCount = count;

			if(newParentElement.id != formElementId) {
				newElement = BSDDOMUtils.getObjectByIdFromParent(newParentElement, formElementId);
			} else {
				newElement = newParentElement;
			}


			if(!dontReIdElements) {
				newElement.name = newElement.name + count;
				newElement.id = newElement.id + count;
			}
			if(!dontReIdElements && newParentElement != newElement) {
				newParentElement.id = newParentElement.id + count;
				var childFields = newParentElement.childNodes;
				for(var i = 0; i < childFields.length; i++) {
					var currentChild = childFields[i];
					if(currentChild.id == formElementId) {
						continue;
					}
					currentChild.id = currentChild.id + count;
				}
			}

			if(!newElement.form) {
				BSDArrayUtils.append(inputElement.form.elements, newElement);
			}

        } else {
             alert("ERROR: Couldn't find element with id " + formElementId);
        }
        return newParentElement;	
	},
	
	debugForms: function() {
	    var message = "";
		var forms = document.forms;
		for(var i = 0; i < forms.length; i++) {
			message += debugFormElements(forms[i]);
		}
		alert(message);
	},
	
	debugFormElements: function(elements) {
		var message = "";
		for(var i = 0; i < elements.length; i++) {
			var currentElement = elements[i];
			message += currentElement.name + ": [" + currentElement.value + "]\n";
		}
		return message;
	},
	
	getIsOptionSelectedById: function(elementId, targetValue) {
		var select = BSDDOMUtils.getObjectById(elementId);
		if(!select || !select.options) {
			BSDLogUtils.debug("Couldn't find select with id: " + elementId);
			return false;
		}
		
		var value = select.options[select.selectedIndex].value;
		if(!value) {
			return false;
		}
		return targetValue == value;
	},
	
	getFormParamsByName: function(formName) {
		var form = document.forms[formName];
		if(!form) {
			BSDLogUtils.error("Couldn't find form with name: " + formName);
			return;
		}
	},
	
	getFormParams: function(form) {
		var data = {};

		for(var i = 0; i < form.elements.length; i++) {
			var currentElement = form.elements[i];

			if(currentElement.value) {
				data[currentElement.name] = currentElement.value;
			}
		}
		return data;
	},
	
	selectAllByClass: function(className) {
		var elements = BSDDOMUtils.getObjectsByClass(className);
		for(var i = 0; i < elements.length; i++) {
			elements[i].checked = true;
		}
		return true;
	},
	
	getFieldValueById: function(fieldElementId, parent) {
		var element;
		if(parent) {
			element = BSDDOMUtils.getObjectByIdFromParent(parent, fieldElementId);
		} else {
		 	element = BSDDOMUtils.getObjectById(fieldElementId);
		}
		if(!element) {
			BSDLogUtils.error("Couldn't find element with id: " + fieldElementId);
			return;
		}
		return BSDFormUtils.getFieldValue(element);
	},
	
	getFieldValue: function(fieldElement) {
		var value = fieldElement.value;
		if(fieldElement && fieldElement.nodeName.toLowerCase() == 'input' && BSDDOMUtils.getAttributeValue(fieldElement, "type").toLowerCase() == 'radio') {
			value = null;
			var radioElement = fieldElement.form.elements[fieldElement.name];
			for(var i = 0; i < radioElement.length; i++) {
				var currentRadio = radioElement[i];
				if(currentRadio.checked) {
					value = currentRadio.value;
					break;
				}
			}
			
			BSDLogUtils.debug("Got value for radio field " + fieldElement.id + " [" + value + "]");
		} else if(fieldElement && fieldElement.nodeName.toLowerCase() == 'input' && BSDDOMUtils.getAttributeValue(fieldElement, "type").toLowerCase() == 'checkbox') {
			value = fieldElement.checked;
		} else if(fieldElement && fieldElement.nodeName.toLowerCase() == 'select' && (!value || value.length < 1) ) {

			var selectedIndex = fieldElement.selectedIndex;
			var strSelected = " " + selectedIndex;

			if(strSelected.length > 1 && fieldElement.options && fieldElement.options.length > selectedIndex) {
				var option = fieldElement.options[selectedIndex];
				if(option) {
					value = option.value;
				}
				if(!value || value.length < 1) {
					value = BSDDOMUtils.getText(option);
				}

				BSDLogUtils.debug("Got option value: [" + value + "]");

			}
		}
		return value;
	
	},
	
	getSelectLabel: function(fieldElement) {

		var label;
		var selectedIndex = fieldElement.selectedIndex;
		var strSelected = " " + selectedIndex;

		if(strSelected.length > 1 && fieldElement.options && fieldElement.options.length > selectedIndex) {
			var option = fieldElement.options[selectedIndex];
			label = option.text;
			if(!label || label.length < 1) {
				label = BSDDOMUtils.getText(option);
			}



		}
		return label;
	},
	
	setFieldValueById: function(fieldElementId, value, parent) {
		var element;
		if(parent) {
			element = BSDDOMUtils.getObjectByIdFromParent(parent, fieldElementId);
		} else {
			element = BSDDOMUtils.getObjectById(fieldElementId);
		}
		if(element) {
			BSDFormUtils.setFieldValue(element, value);
		}
	},
	
	setFieldValue: function(fieldElement, value) {
		if(fieldElement.nodeName.toLowerCase() == 'select') {
			for(var i = 0; i < fieldElement.options.length; i++) {
				if(fieldElement.options[i].value == value) {
					fieldElement.selectedIndex = i;
					return;
				}
			}
		} else {
			fieldElement.value = value;
		}
	},
	
	toggleNestedSelect: function(parentSelect, childSelectIdPrefix, defaultChildId) {
		var selectedValue = BSDFormUtils.getFieldValue(parentSelect);
		var defaultChild = BSDDOMUtils.getObjectById(defaultChildId);
		if(!defaultChild) {
			BSDLogUtils.error("Couldn't find default nested select element with id: " + defaultChildId);
		}
		if(!selectedValue || selectedValue.length < 1) {
			if(defaultChild) {
				BSDDOMUtils.setAttributeValue(defaultChild, 'disabled', 'disabled');
			}
			return true;
		}
		
		var children = BSDDOMUtils.getObjectsByClass(parentSelect.id + "_CHILD");
		selectedValue = BSDStringUtils.stripWhitespace(selectedValue);
		var selectedChild;
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];

			if(BSDStringUtils.endsWith(currentChild.id, "_" + selectedValue)) {
				selectedChild = currentChild;
			} else if(currentChild != defaultChild) {
				BSDVisibilityUtils.hideObject(currentChild);
				currentChild.disabled = true;

				
			}
				if(currentChild.otherField) {

					BSDVisibilityUtils.hideObject(currentChild.otherField);
				}
		}

		if(selectedChild) {
			
			BSDVisibilityUtils.showObject(selectedChild);
			BSDDOMUtils.setAttributeValue(selectedChild, 'disabled', false);
			
			selectedChild.disabled = false;
			if(defaultChild && defaultChild != selectedChild) {
				BSDVisibilityUtils.hideObject(defaultChild);
			}
			if(selectedChild.otherField) {
				BSDVisibilityUtils.showObject(selectedChild.otherField);
			}
			
		} else {
			if(defaultChild) {
				BSDVisibilityUtils.showObject(defaultChild);
				defaultChild.disabled = true;

				if(defaultChild.otherField) {
					BSDVisibilityUtils.hideObject(currentChild.otherField);
				}
				
			}
		}
		
		return true;
	},
	
	toggleSelectOther: function(parentSelect, otherFieldId, otherValue) {

		var otherField = BSDDOMUtils.getObjectById(otherFieldId);
		if(!otherField) {
			BSDLogUtils.error("Couldn't find default other field with id: " + otherFieldId);
			return true;
		}
		
		var selectedValue = BSDFormUtils.getFieldValue(parentSelect);
		if(selectedValue && selectedValue == otherValue) {
			BSDVisibilityUtils.showObject(otherField);
			parentSelect.otherField = otherField;
		} else {
			BSDVisibilityUtils.hideObject(otherField);
			parentSelect.otherField = null;
		}
		
		return true;
		
	},
	
	mirrorFieldValue: function(field, targetElementId, parentElementId, nlToBr) {
		
		var target;
		if(parentElementId) {
			var parentElement = BSDDOMUtils.getObjectById(parentElementId);
			if(parentElement) {
				target = BSDDOMUtils.getObjectByIdFromParent(parentElement, targetElementId);
			} else {
				BSDLogUtils.error("ERROR: Couldn't get parent for mirrorFieldValue: " + parentElementId);
			}
		} else {
			target = BSDDOMUtils.getObjectById(targetElementId);
		}
		if(!target) {
			BSDLogUtils.error("ERROR: Couldn't get target for mirrorFieldValue: " + targetElementId);
			return;
		}
		
		var value = field.value;
		var changed = false;
		if(nlToBr && value) {
			var regexp = new RegExp("\n", "gi");
			var newValue = value.replace(regexp, "<br/>");
			changed = newValue != value;
			value = newValue;
		}
		if(changed) {
			target.innerHTML = value;
		} else {
			BSDDOMUtils.setText(target, value);
		}

	},
	
	refreshOnSelect: function(field, otherParams) {
		var value = BSDFormUtils.getFieldValue(field);
		var args = {};
		args[field.name] = value;
		if(otherParams) {
			for(var key in otherParams) {
				args[key] = otherParams[key];
			}
		}
		BSDNavigationUtils.refreshWithArgs(args);
	},
	
	buildArgHash: function(elementId, args, parentElement) {
		var element;
		if(parentElement) {
			element = BSDDOMUtils.getObjectByIdFromParent(parentElement, elementId);
		} else {
			element = BSDDOMUtils.getObjectById(elementId);
		}
		if(!element || !element.name) {
			return false;
		}
		var value = BSDFormUtils.getFieldValue(element);
		args[element.name] = value;
	}
	

	
	
}
var bsdValidatedFormList = {};
var bsdValidatedFormIndex = 0;
BSDValidatedForm = BSDClass.create();
BSDValidatedForm.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDVisibilityUtils", "BSDEventUtils", "BSDLogUtils", "BSDHighlightUtils", "BSDArrayUtils");
BSDValidatedForm.VERSION = 1.1;

BSDValidatedForm.prototype = {

	className: "BSDValidatedForm",
	initialize: function(formName, submitButtonId, validationFields, validationOptions) {

		this.formName = formName;
		this.form = document.forms[formName];
		
		this.options = validationOptions;
		if(!this.options) {
			this.options = this.getDefaultOptions();
		}
		if(!this.options.messageHash) {
			this.options.messageHash = new BSDFormMessageHash();
		}
		
		if(validationFields && validationFields.length > 0) {
			this.fields = validationFields;
		} else {
			this.fields = new Array();
		}

		if(!this.form) {
			BSDLogUtils.error("ERROR: Couldn't find form for validation: " + formName);
			for(var i = 0; i < document.forms.length; i++) {
				BSDLogUtils.debug("Form: " + document.forms[i].name);
			}
			return;
		}

		var initSuccess = this.initializeForm();
		if(!initSuccess) {
			this.form = this.retryInitializeForm();
		}
		if(!this.form) {
			return;
		}
		this.initializePageErrorMessage();
		this.initializeFields();
		
		bsdValidatedFormList[this.formName] = this;
		this.bsdIndex = bsdValidatedFormIndex++;
		BSDLogUtils.debug("Initialized validation form " + formName + " " + this.bsdIndex + " " + this.form.bsdIndex + " " + (bsdValidatedFormList[this.formName] == this) );
		this.form.bsdIndex = this.bsdIndex;
  	},
  	

	doFormSubmit: function(e) {

		var form = this.form;
		if(form.skipValidation || BSDDOMUtils.getAttributeValue(form, 'skip-validation') == 'true') {
			return true;
		}
		if(typeof tinyMCE != 'undefined') {
			tinyMCE.triggerSave();
		}
		
		var result = this.doValidation();

		if(result.isValid) {

			form.hasError = false;
			return true;
		}
		
		if(this.pageErrorMessageTextElement && result.pageMessage) {
			var pageMessage = this.options.messageHash.getMessage(result.pageMessage);
			this.pageErrorMessageTextElement.innerHTML = pageMessage;
			BSDVisibilityUtils.showObject(this.pageErrorMessageContainer);
			for(var i = 0; i < this.pageErrorMessageContainer.parentNode.childNodes.length; i++) {
				var currentChild = this.pageErrorMessageContainer.parentNode.childNodes[i];
				if(currentChild != this.pageErrorMessageElemen
							&& currentChild.id 
							&& currentChild.id.indexOf(this.pageErrorMessageContainer.id) > 0) {
					this.pageErrorMessageContainer.parentNode.removeChild(currentChild);
				}
			}
		}

		if(e) {
			BSDEventUtils.stopPropagation(e);
		}
		form.hasError = true;
		BSDNavigationUtils.navigateTo("#" + form.name);
		return false;
		
	},
  	

	doValidation: function() {

		var listFieldsInPageMessage = this.options.listFieldsInPageMessage;
		var fieldPageMessageEnabled = this.options.fieldPageMessageEnabled;

		var pageMessage = this.options.defaultPageMessage;
		var pageMessagePrefix = this.options.pageMessagePrefix;
		if(!pageMessagePrefix) {
			pageMessagePrefix = "";
		}
		var pageMessageSuffix = this.options.pageMessageSuffix;
		if(!pageMessageSuffix) {
			pageMessageSuffix = "";
		}

		var isValid = true;
		for(var i = 0; i < this.fields.length; i++) {
			var currentField = this.fields[i];

			var result = currentField.validate();
			if(result && !result.isValid) {
				isValid = false;


				if(listFieldsInPageMessage && currentField.getLabel()) {
					pageMessage += pageMessagePrefix + currentField.getLabel + pageMessageSuffix;
				} else if((fieldPageMessageEnabled || currentField.forcePageMessage) && result.pageMessage) {
					var resultMessage = this.options.messageHash.getMessage(result.pageMessage);
					pageMessage += pageMessagePrefix + resultMessage + pageMessageSuffix;				
				} 
			}

		}

		
		var result = new BSDValidatedFormResult(isValid, null, pageMessage);				

		return result;
	},
	
	getDefaultOptions: function() {
		var options = new Object();
		options.defaultPageMessage = "There are some problems with your submission.  Please fix them and try again:";
		options.pageMessagePrefix = "<br/>";
		options.validateFieldsOnChange = true;
		options.fieldHighlightStyle = "fieldError";
		options.labelHighlightStyle = "fieldLabelError";
		
		return options;
	},
  	
  	addValidationField: function(validationField) {

  		this.initializeField(validationField);
  		BSDArrayUtils.append(this.fields, validationField);


  	},
  	
  	addField: function(fieldElementId, validator, messageElementId, labelElementId) {
  		var validationField = new BSDValidatedFormField(this.options, fieldElementId, validator, messageElementId, labelElementId);
  		this.addValidationField(validationField);
  	},
  	
  	removeField: function(fieldElementId) {
  		for(var i = 0; i < this.fields.length; i++) {
  			if(this.fields[i].fieldElement && this.fields[i].fieldElement.id == fieldElementId) {
  				BSDArrayUtils.deleteElement(this.fields, i);
  			}
  		}

  	},
  	
  	initializePageErrorMessage: function() {
  		var errorMessageContainerId = "ERROR_MESSAGE_ROW";
  		if(this.options.errorMessageContainerId) {
  			errorMessageContainerId = this.options.errorMessageContainerId;
  		}
  		var containerElement = BSDDOMUtils.getObjectByIdFromParent(this.form, errorMessageContainerId);
  		if(!containerElement) {
  			containerElement = BSDDOMUtils.getObjectById(errorMessageContainerId);
  		}
  		if(!containerElement) {
  			BSDLogUtils.error("ERROR: Couldn't find error message container for form validator [" + errorMessageContainerId + "]");  		
  			return;  		
  		}
  		this.pageErrorMessageContainer = containerElement;

  		var errorMessageTextId = "ERROR_MESSAGE_TEXT";
  		if(this.options.errorMessageTextId) {
  			errorMessageTextId = this.options.errorMessageTextElementId;
  		}
  		var textElement = BSDDOMUtils.getObjectById(errorMessageTextId);
  		if(!textElement) {
  			BSDLogUtils.error("ERROR: Couldn't find error message text element for form validator");  		
  			return;  		
  		}
  		this.pageErrorMessageTextElement = textElement;

  	},
  	
  	initializeForm: function() {
  		var formValidator = this;
  		if(this.form.isValidationHandlerInitialized) {
  			return true;
  		}
		function formOnSubmitHandler(e) {

			return formValidator.doFormSubmit(e);			
		}   


		var success = BSDEventUtils.registerEvent(this.form, "submit", formOnSubmitHandler);
		if(success) {
			this.form.isValidationHandlerInitialized = true;
		}
		return success;
  	},
  	
  	retryInitializeForm: function() {

		this.form = BSDDOMUtils.getObjectById(this.formName);
		if(!this.form || !this.initializeForm()) {
			BSDLogUtils.error("Couldn't initialize form " + this.formName + " by id");
			return;
		}

		if(this.form.bsdIndex && this.form.bsdIndex <= bsdValidatedFormIndex) {

			var formList = BSDDOMUtils.getObjectsById(this.formName, document);

			for(var i = 0; i < formList.length; i++) {
				if(!formList[i].bsdIndex) {
					this.form = formList[i];
					BSDLogUtils.debug("Got form from list: " + this.form.name);
					break;
				}
			}
		} 
		return this.form; 	
  	},
  	
  	initializeFields: function() {  	
  		for(var i = 0; i < this.fields.length; i++) {
  			var currentField = this.fields[i];
  			this.initializeField(currentField);
  		}
  	},
  	
  	initializeField: function(field) {
		field.initializeElements(this.form, this.options);  		
  	}, 
  	
  	replaceValidator: function(fieldId, newValidator) {
  		for(var i = 0; i < this.fields.length; i++) {
  			var currentField = this.fields[i];

  			if(currentField.fieldElementId == fieldId) {  				
  				for(var j = 0; j < currentField.validators.length; j++) {
  					var currentValidator = currentField.validators[j];
  					if(currentValidator.className != newValidator.className) {
  						continue;
  					}
  					if(newValidator.fieldMessage) {
  						newValidator.fieldMessage = currentValidator.fieldMessage;
  					}
  					if(newValidator.pageMessage) {
  						newValidator.pageMessage = currentValidator.pageMessage;
  					}
  					BSDArrayUtils.replace(currentField.validators, j, newValidator);
  					return currentField;
  				}
  			}
  		}
		return;  	
  	} 	
}

BSDValidatedFormUtils = {
	reinitializeForms: function() {

		for(var formName in bsdValidatedFormList) {
			var currentForm = bsdValidatedFormList[formName];

			currentForm.initializeFields();
		}
	},
	
	replaceValidator: function(fieldId, newValidator) {

		for(var formName in bsdValidatedFormList) {
			var currentForm = bsdValidatedFormList[formName];

			var validatedField = currentForm.replaceValidator(fieldId, newValidator);
			if(validatedField) {
				return validatedField;
			}
		}

	}
}

BSDValidatedFormField = BSDClass.create();
BSDValidatedFormField.prototype = {

	initialize: function(validationOptions, fieldElementId, messageElementId, labelElementId, validator) {

		this.options = validationOptions;
		this.fieldElementId = fieldElementId;
		this.messageElementId = messageElementId;
		this.labelElementId = labelElementId;

		this.validators = new Array();
		if(validator) {
			this.addValidator(validator);
		}
	},
	
	initializeElements: function(parentFormElement, options) {
		if(parentFormElement) {
			this.parentFormElement = parentFormElement;
		}

  		if(!this.options) {
  			this.options = options;
  		}  	
  		if(this.fieldElementId) {
  			var element = BSDDOMUtils.getObjectByIdFromParent(this.parentFormElement, this.fieldElementId);
  			if(!element) {
  				BSDLogUtils.error("ERROR: Couldn't find field element with id " + this.fieldElementId);
  				this.fieldElement = null;
  			} else {
  				element.bsdIndex = this.bsdIndex;
  				this.fieldElement = element;

  			}
  		}
  		if(this.labelElementId) {
  			var element = BSDDOMUtils.getObjectByIdFromParent(this.parentFormElement, this.labelElementId);
  			if(!element) {
  				BSDLogUtils.error("ERROR: Couldn't find field label with id " + this.labelElementId);
  			} else {
  				this.labelElement = element;
  			}  		
  		}
  		if(this.messageElementId) {
  			var element = BSDDOMUtils.getObjectByIdFromParent(this.parentFormElement, this.messageElementId);
  			if(!element) {
  				BSDLogUtils.error("ERROR: Couldn't find field message with id " + this.messageElementId);
  			} else {
  				this.messageElement = element;
  			}    			
  		}
  		
  		if(this.options && this.options.validateFieldsOnChange && this.fieldElement) {
			this.initializeOnChange(this);  			
  		}
  		
  		this.bsdIndex = this.bsdIndex;
	
	},
	
  	initializeOnChange: function(field) {
  		
		function fieldOnChangeHandler(e) {
			BSDLogUtils.debug("Got field onchange event");
			var validationResult = field.validate();
		}   
		
		function fieldOnKeydownHandler(e) {
			field.clearError();			
		}	
		
		BSDEventUtils.registerEvent(field.fieldElement, "change", fieldOnChangeHandler);		
		BSDEventUtils.registerEvent(field.fieldElement, "keydown", fieldOnKeydownHandler);		
  	},	
	
	addValidator: function(newValidator) {	
		BSDArrayUtils.append(this.validators, newValidator);
	},
	
	validate: function() {
		var result;
		for(var i = 0; i < this.validators.length; i++) {
			result = this.validateByValidator(this.validators[i]);
			if(!result.isValid) {
				return result;
			}
		}
		return result;
	},
	
	validateByValidator: function(validator) {
		BSDLogUtils.debug("validateByValidator: BEGIN " + this.fieldElementId + " " + this.fieldElement);
		if(!this.fieldElement && this.fieldElementId) {
			this.initializeElements();
		}
		if(!this.fieldElement || this.fieldElement.skipValidation) {
			var result = new Object();
			result.isValid = true;
			return result;
		}
		var value = BSDFormUtils.getFieldValue(this.fieldElement);

		var result = validator.validate(value);

		if(result.newValue && result.newValue.length > 0) {

			this.fieldElement.value = result.newValue;
		}
		/* This doesn't work because the response doesn't come back until the current thread is finished.   Need to rework messaging to happen asyncronously
		var beginTime = new Date();
		var currentTime = new Date();
		
  		BSDLogUtils.debug("Checking is pending: " + result.isPending + " " + (currentTime.getTime() - beginTime.getTime()));
  		while(result.isPending && currentTime.getTime() - beginTime.getTime() < 2000) { //2 second timeout
  			BSDLogUtils.debug("Is pending, looping: " + (currentTime.getTime() - beginTime.getTime()));
			var x = "y" + "z" + "w";
			var y = x + "A";		
			currentTime = new Date();
					
  		}
  		*/


		if(this.fieldElement && !result.isValid && this.options.fieldHighlightStyle) {
			BSDDOMUtils.addClass(this.fieldElement, this.options.fieldHighlightStyle);
		}

		if(this.messageElement && !result.isValid && result.fieldMessage) {
			var resultMessage = this.options.messageHash.getMessage(result.fieldMessage);
		  	this.messageElement.innerHTML = resultMessage;

			for(var i = 0; i < this.messageElement.childNodes.length; i++) {
				var currentChild = this.messageElement.childNodes[i];
				if(currentChild.nodeType == 1 && !BSDVisibilityUtils.isObjectHidden(currentChild)) {
					BSDVisibilityUtils.hideObject(currentChild);
					currentChild.hiddenByValidator = true;
				}				
			}
		}
		
		if(this.labelElement && !result.isValid && this.options.labelHighlightStyle) {
			BSDDOMUtils.addClass(this.labelElement, this.options.labelHighlightStyle);
		}
		
		if(this.isError && result.isValid) {			
			this.clearError();		
		}
		this.isError = !result.isValid;


		return result;		
	},

	
	clearError: function() {

		if(this.fieldElement && this.options.fieldHighlightStyle) {
			BSDDOMUtils.removeClass(this.fieldElement, this.options.fieldHighlightStyle);
		}
		
		if(this.messageElement) {
			this.messageElement.innerHTML = "";
			for(var i = 0; i < this.messageElement.childNodes.length; i++) {
				var currentChild = this.messageElement.childNodes[i];
				if(currentChild.nodeType == 1 && currentChild.hiddenByValidator) {
					BSDVisibilityUtils.showObject(currentChild);
					currentChild.hiddenByValidator = false;
				}				
			}
		}
		
		if(this.labelElement && this.options.labelHighlightStyle) {
			BSDDOMUtils.removeClass(this.labelElement, this.options.labelHighlightStyle);
		}
	}
	
	
}


BSDValidatedFormResult = BSDClass.create();
BSDValidatedFormResult.prototype = {

	initialize: function(isValid, fieldMessage, pageMessage) {
		this.isValid = isValid;
		this.isPending = false;
		this.fieldMessage = fieldMessage;
		this.pageMessage = pageMessage;
	},
	
	
	handleValidationResponse: function(data) {
		var successful = BSDAjaxUtils.doNavigationReply(data, true);

		if(!successful) {
			BSDLogUtils.error("Ajax validation request failed: " + data);
	    } else if(data.errorMessage) {
	    	this.isValid = false;
		} else {
			this.isValid = true;
		}	
		this.isPending = false;
	}	
	
}


BSDRegexFormFieldValidator = BSDClass.create();
BSDRegexFormFieldValidator.prototype = {
	
	initialize: function(isRequired, fieldMessage, pageMessage, regex) {
		this.regex = new RegExp(regex);
		this.fieldMessage = fieldMessage;
		this.isRequired = isRequired;
		this.pageMessage = pageMessage;
	},
	
	validate: function(value) {
		var result;

		if(this.isRequired && (!value || value.length < 1)) {
			result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);				
		} else if(value && value.length > 0 && this.regex && this.regex.exec && this.regex.exec(value) == null) {
			result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);							
		} else {
			result = new BSDValidatedFormResult(true);									
		}
		return result;
	}

}


BSDRequiredFormFieldValidator = BSDClass.create();
BSDRequiredFormFieldValidator.prototype = {
	
	initialize: function(isRequired, fieldMessage, pageMessage) {
		this.isRequired = isRequired;
		this.fieldMessage = fieldMessage;
		this.pageMessage = pageMessage;
	},
	
	validate: function(value) {
		var result;

		
		if(!value && this.isRequired) {
			result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
		} else if(this.minLength && value && value.length < this.minLength) {
			result = new BSDValidatedFormResult(false, this.minLengthErrorMessage, this.pageMessage);										
		} else {
			result = new BSDValidatedFormResult(true);									
		}
		return result;
	}

}


BSDCardCodeFormFieldValidator = BSDClass.create();
BSDCardCodeFormFieldValidator.prototype = {
	
	initialize: function(isRequired, fieldMessage, pageMessage) {
		this.isRequired = isRequired;
		this.fieldMessage = fieldMessage;
		this.pageMessage = pageMessage;
		this.regex = new RegExp(/[\d]{3,4}/);
	},
	
	validate: function(value) {
		var result;
		if(!value && this.isRequired) {
			result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
		} else if(this.minLength && value && value.length < this.minLength) {
			result = new BSDValidatedFormResult(false, this.minLengthErrorMessage, this.pageMessage);										
		} else if(value && value.indexOf('*') > -1) {
			result = new BSDValidatedFormResult(true);									
		} else if(value) {
			var typeId = BSDCardNumberFormFieldValidator.getCardTypeId();
			if(!typeId) {
				result = new BSDValidatedFormResult(false);
			} else if(typeId == 15 && value.length != 4) {
				result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
			} else if(value.length != 3) {
				result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
			} else if(this.regex && this.regex.exec && this.regex.exec(value) == null) {
				result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
			} else {
				result = new BSDValidatedFormResult(true);												
			}
		}
		return result;
	}

}



BSDCardNumberFormFieldValidator = BSDClass.create();
BSDCardNumberFormFieldValidator.prototype = {
	
	initialize: function(isRequired, fieldMessage, pageMessage) {
		this.isRequired = isRequired;
		this.fieldMessage = fieldMessage;
		this.pageMessage = pageMessage;
		
	},
	
	validate: function(value) {
		var result;
		if(!value && this.isRequired) {
			result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
		} else if(this.minLength && value && value.length < this.minLength) {
			result = new BSDValidatedFormResult(false, this.minLengthErrorMessage, this.pageMessage);										
		} else if(value && value.indexOf('*') > -1) {
			result = new BSDValidatedFormResult(true);									
		} else if(value) {
			value = value.replace(/[\D]+/g, "");
			var typeId = BSDCardNumberFormFieldValidator.getCardTypeId();
			if(!typeId) {
				result = new BSDValidatedFormResult(false);
			} else if(!validateCardNumber(value, typeId)) { //this function is inserted into the page html by java
				result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);		
			} else {
				result = new BSDValidatedFormResult(true);												
			}
		}
		return result;
	}

}

BSDCardNumberFormFieldValidator.getCardTypeId = function() {
	var typeIdElement = BSDDOMUtils.getObjectById("PAYMENT_METHOD_TYPE_ID");
	if(typeIdElement && typeIdElement.value && typeIdElement.value.length > 0) {
		return new typeIdElement.value;
	}
	
	var buttons = BSDDOMUtils.getObjectsByClass('PAYMENT_METHOD_TYPE_RADIO_BUTTON');
	for(var i = 0; i < buttons.length; i++) {
		var currentButton = buttons[i];
		if(currentButton.checked && currentButton.value && currentButton.value.length > 0) {
			return currentButton.value;
		} 
	}

	BSDLogUtils.error("Couldn't find PAYMENT_METHOD_TYPE_ID field value for form validation");
	return null;
		
}

BSDCardNumberFormFieldValidator.validateCardNumberLength = function(cardNumber) {
	for(var i = 1; i < arguments.length; i++) {
		if(cardNumber.length == arguments[i]) {
			return true;
		}
	}
	return false;
}

BSDCardNumberFormFieldValidator.validateCardNumberPrefix = function(cardNumber) {
	for(var i = 1; i < arguments.length; i++) {
		var value = arguments[i] + '';
		if(cardNumber.indexOf(value) == 0) {
			return true;
		}
	}

	return false;
}

BSDCardNumberFormFieldValidator.validateMod10 = function(cardNumber) {
	var total = 0;
	var j = 1;	
	for(var i = cardNumber.length - 1; i >= 0; i--) {
		var digit = cardNumber.charAt(i);
		if(j % 2 == 0) {
			var multDigit = parseInt(digit) * 2;
			if(multDigit > 9) {
				var strMultDigit = multDigit + '';
				var digit1 = parseInt(strMultDigit.charAt(0));
				var digit2 = parseInt(strMultDigit.charAt(1));
				total += digit1 + digit2;
			} else {
				total += multDigit;
			}
		} else {
			total += parseInt(digit);
		}
		j++;
	}
	return total % 10 == 0;
}

BSDExerciseValidator = BSDClass.create();
BSDExerciseValidator.prototype = {
	
	initialize: function(isRequired, fieldMessage, pageMessage) {
		this.isRequired = isRequired;
		this.fieldMessage = fieldMessage;
		this.pageMessage = pageMessage;
		
	},
	
	validate: function(value) {
		var result = new BSDValidatedFormResult(true, this.fieldMessage, this.pageMessage);
		if(!value || value.length < 1) {
			return result;
		} 
		
		var parts = value.split(/\|/);
		if(parts.length < 4) {
			return result;
		}
		
		var algorithm = parts[1];
		var expressionResult = '';
		if(algorithm == 'eval') {
			expressionResult = eval(parts[3]);
		}
		value = parts[0] + "|" + parts[1] + "|" + parts[2] + "|" + parts[3] + "|" + expressionResult;
		result.newValue = value;
		return result;
	}

}
		


BSDSupportedFileTypeValidator = BSDClass.create();
BSDSupportedFileTypeValidator.prototype = {
	
	className: "BSDSupportedFileTypeValidator",
	initialize: function(fieldMessage, pageMessage, validImageFileExtensions, validVideoFileExtensions, validAudioFileExtensions, validDocumentFileExtensions, validFlashFileExtensions) {
		this.fieldMessage = fieldMessage;
		this.pageMessage = pageMessage;
		this.validFileExtensions = new Array();
		BSDArrayUtils.append(this.validFileExtensions, validImageFileExtensions);
		BSDArrayUtils.append(this.validFileExtensions, validVideoFileExtensions);

		BSDArrayUtils.append(this.validFileExtensions, validDocumentFileExtensions);
		BSDArrayUtils.append(this.validFileExtensions, validFlashFileExtensions);
		
		if(!this.fieldMessage) {
			this.fieldMessage = "This type of file isn't supported";
		}
		if(!this.pageMessage) {
			this.pageMessage = "One of your files has an unsupported format.  Valid file types include: <ul>";
			if(validImageFileExtensions && validImageFileExtensions.length > 0) {
				this.pageMessage += "<li>Images:  " + this.getFileExtensionMessage(validImageFileExtensions) + "</li>";
			}
			if(validVideoFileExtensions && validVideoFileExtensions.length > 0) {
				this.pageMessage += "<li>Video:  " + this.getFileExtensionMessage(validVideoFileExtensions) + "</li>";
			}
			if(validAudioFileExtensions && validAudioFileExtensions.length > 0) {
				this.pageMessage += "<li>Audio:  " + this.getFileExtensionMessage(validAudioFileExtensions) + "</li>";
			}
			if(validDocumentFileExtensions && validDocumentFileExtensions.length > 0) {
				this.pageMessage += "<li>Documents:  " + this.getFileExtensionMessage(validDocumentFileExtensions) + "</li>";
			}
			if(validFlashFileExtensions && validFlashFileExtensions.length > 0) {
				this.pageMessage += "<li>Flash:  " + this.getFileExtensionMessage(validFlashFileExtensions) + "</li>";
			}
			this.pageMessage += "</ul>";
		}
	},
	
	validate: function(value) {
		var result;
		if(!value || value.length < 1) {
			return new BSDValidatedFormResult(true);		
		} 
		
		var fileParts = value.split(".");
		var extension = fileParts[fileParts.length - 1];
		BSDLogUtils.debug("Got file type result: " + extension + " [" + this.fieldMessage + "][" + this.pageMessage + "] " + this.validFileExtensions);
		if(!extension || extension.length < 1) {
			result = new BSDValidatedFormResult(false, 'This file is missing its file extension (e.g. .jpg)', this.pageMessage);		
		} else if(!BSDArrayUtils.contains(this.validFileExtensions, extension.toLowerCase())) {
			result = new BSDValidatedFormResult(false, this.fieldMessage, this.pageMessage);				
		} else {
			return new BSDValidatedFormResult(true);		
		}
		return result;
	},
	
	getFileExtensionMessage: function(extensions) {
		var message = "";
		for(var i = 0; i < extensions.length; i++) {
			message += extensions[i];
			if(i < extensions.length - 1) {
				message += ", ";
			}
		}
		return message;
	}
}

BSDFormMessageHash = BSDClass.create();
BSDFormMessageHash.prototype = {
	
	initialize: function(hash) {
		this.hash = hash;
	},
	
	getMessage: function(messageId) {
		if(!messageId) {
			return messageId;
		}
		var newMessage = this.hash[messageId];
		if(newMessage) {
			return newMessage;
		}
		return messageId;
	}

}


BSDAjaxFormUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "forms/BSDFormUtils", "forms/BSDValidatedForm", "BSDAjaxUtils"),

	submitAjaxFormByElement: function(element, pageID, callback, activityMessage) {
		var parent = element.parentNode;
		var form;
		while(parent) {
			if(parent.nodeName && parent.nodeName.toLowerCase() == 'form') {
				form = parent;
				break;
			}
			parent = parent.parentNode;
		}

		if(!form) {
			BSDLogUtils.error("Couldn't find form from element: " + element.nodeName + " " + element.id + " " + element.className);
			return;
		}

		return BSDAjaxFormUtils.submitAjaxForm(form, pageID, callback, activityMessage);
	},

	submitAjaxFormByName: function(formName, pageID, callback, activityMessage) {
		var form = document.forms[formName];
		if(!form) {
			BSDLogUtils.error("Couldn't find form with name: " + formName);
			return;
		}
		return BSDAjaxFormUtils.submitAjaxForm(form, pageID, callback, activityMessage);
	},
	
	
	submitAjaxForm: function(form, pageID, callback, activityMessage) {
		var validatedForm = bsdValidatedFormList[form.name];
		if(validatedForm && validatedForm.form != form) {
			var docForm = document.forms[form.name];
			validatedForm.form = form;
		}
		if(validatedForm && !validatedForm.doFormSubmit()) {
			BSDLogUtils.debug("Form has errors: " + form.name);

			return;
		}
		BSDLogUtils.debug("Submitting form: " + form.name);
	
		var pageArguments = BSDFormUtils.getFormParams(form);	

		
		function doSubmitReply(data) {

			BSDAjaxUtils.doNavigationReply(data);
			if(callback) {
				callback.call(this, data.errorMessage);
			}
		}
		
		BSDAjaxUtils.doNavigation(pageID, pageArguments, doSubmitReply, activityMessage);
		
	},
	
	debugForm: function(form, currentParent) {
		if(!currentParent) {
			currentParent = document;
		}
		var childNodes = currentParent.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			if(currentChild.nodeName.toLowerCase() == 'form' && currentChild.name == form.name) {
				var path = '';
				var parent = currentChild.parentNode;
				while(parent) {
					path += parent.nodeName + "/" + parent.id;
					parent = parent.parentNode;
				}
				BSDLogUtils.debug("Found form: " + (currentChild == form) + " " + form.name + ": " + path);
			} else {
				BSDAjaxFormUtils.debugForm(form, currentChild);
			}
		}
	}
	
	
	
}
BSDImageUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "BSDEventUtils"),
	
	handleMousedown: function(buttonElement, mousedownUrl) {
		if(buttonElement.nodeName.toLowerCase() != 'img') {
			var oldClassName = buttonElement.className;
			if(!oldClassName || oldClassName.length < 1) {
				return;
			}
			var newClass = '';
			var split = oldClassName.split(/\s+/);
        	for(var j = 0; j < split.length; j++) {
				var currentSplit = split[j];
				if(j == 0 && currentSplit.indexOf('Click') < 0) {
					newClass += currentSplit + 'Click';
				} else {
					newClass += ' ' + currentSplit;				
				}
			}
			
			buttonElement.className = newClass;

			function handleMouseUp(e) {

				buttonElement.className = oldClassName;
			}
		
			BSDEventUtils.registerEvent(buttonElement, 'mouseup', handleMouseUp);
			return;
		}

		var src = BSDDOMUtils.getAttributeValue(buttonElement, 'src');
		if(!src) {
			return;
		}
		if(!mousedownUrl) {
			var regex = new RegExp(/(\w+).(\w{3})/);
			mousedownUrl = src.replace(/(.\w{3})$/, 'Click$1');

		}
		
		buttonElement.src = mousedownUrl;
		
		function handleImgMouseUp(e) {
			buttonElement.src = src;
		}
		
		BSDEventUtils.registerEvent(buttonElement, 'mouseup', handleImgMouseUp);
		
	}
	
	
}

BSDContentUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDAjaxUtils", "BSDNavigationUtils"),

	doRendering: function(templateElementId, pageArguments, pageID, callback, pageURL, activityMessage) {	
	    if(!templateElementId) {
	    	BSDLogUtils.error("ERROR: Couldn't find element with id " + templateElementId + " for rendering");
	    	return;
	    }

		if(!pageID) {
			pageID = '/ajaxpages/contentrender';
		}

	    var parentNode = BSDDOMUtils.getObjectById(templateElementId);
	    
	    BSDContentUtils.doRenderingByParentElement(templateElementId, parentNode, pageArguments, pageID, callback, pageURL, activityMessage);
	},	
	
	doRenderingByFormElement: function(templateElementId, formElement, callback, activityMessage) {
		var pageArguments = {};
		BSDNavigationUtils.populateQueryArgs(pageArguments, true);
		var previousArguments = formElement.form.previousAjaxArguments;
		if(!previousArguments) {
			previousArguments = {};
			formElement.form.previousAjaxArguments = previousArguments;
		} else {
			for(var name in previousArguments) {
				pageArguments[name] = previousArguments[name];
			}			
		}
		var nameRegex = /-/g;
		var name = formElement.name.replace(nameRegex, "_");
		pageArguments[name] = formElement.value;
		previousArguments[name] = formElement.value;
		var url = BSDNavigationUtils.getDocumentURI();

		BSDContentUtils.doRendering(templateElementId, pageArguments, null, callback, url, activityMessage);	
	},
	
	doRenderingByParentElement: function(templateElementId, parentNode, pageArguments, pageID, callback, pageURL, activityMessage, useChildElement) {

		if(!pageArguments) {
			pageArguments = {};			
		}
		if(templateElementId) {
			pageArguments['template_element_id'] = templateElementId;
		}
		if(!pageID) {
			pageID = '/ajaxpages/contentrender';
		}
		
		if(!pageURL) {
			pageURL = document.URL;
		}
		var hashIndex = pageURL.indexOf('#');
		if(hashIndex > -1) {
			pageURL = pageURL.substring(0, hashIndex);
		}
		
		pageArguments['document_url'] = pageURL;
		
		BSDLogUtils.debug("Doing content render: " + pageID + " " + pageURL + " " + templateElementId);
		if(activityMessage) {
			BSDAjaxUtils.showActivityMessage(activityMessage);
		}
		BSDLogUtils.debug("doRenderingByParentElement: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentUtils.doRenderingReply(str, templateElementId, parentNode, callback, useChildElement);
					} 
				});

		return true;
	},
	
	doRenderingReply: function(data, templateElementId, parentNode, callback, useChildElement) {
		BSDLogUtils.debug("Doing render reply: " + templateElementId + " " + parentNode);

		if(!BSDAjaxUtils.doRenderingReply(data) || data.errorMessage) {
			var args = new Array();
			args[0] = true;
			args[1] = data.errorMessage;
			BSDLogUtils.error("Got error on render: [" + data.errorMessage + "]");
			if(callback) {
				callback.call(this, data, data.errorMessage, true);
			}
			return;
		} 


		if(parentNode && useChildElement && parentNode.id != templateElementId) { //for some reason this was always programmed wrong, and now there's a lot of code that depends on it being wrong. use this bogus param to make it work right for new uses
			var childElement = BSDDOMUtils.getObjectByIdFromParent(parentNode, templateElementId);
			if(childElement) {
				childElement.innerHTML = data.html;
			}
			BSDContentUtils.doJavascriptInit(childElement);
		} else if(parentNode) {
			parentNode.innerHTML = data.html;
			BSDContentUtils.doJavascriptInit(parentNode);
		}

		
		if(callback) {
			callback.call(this, data);
		}
		
		BSDLogUtils.debug("Render reply done");
		
		return;
	},
	
	doJavascriptInit: function(parentNode) {
		BSDAjaxUtils.doJavascriptInit(parentNode);		
	}	
	
}
BSDPopupPosition = BSDClass.create();
BSDPopupPosition.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils");
BSDPopupPosition.prototype = {

	className: "BSDPopupPosition",
	initialize: function(orientation, deltaX, deltaY) {
		this.orientation = orientation;
		this.deltaX = deltaX;
		this.deltaY = deltaY;
   	}

	
    
}






BSDDragProperties = BSDClass.create();
BSDDragProperties.DEPENDENCIES = new Array("drag/BSDDragUtils", "BSDDOMUtils", "BSDLocationUtils");
BSDDragProperties.prototype = {

	className: "BSDDragProperties",	
	initialize: function(elementToDrag, dragEvent, dragActionType) {
		if(elementToDrag) {
			this.element = elementToDrag;
			this.parentElement = this.element.parentNode;
		}
		if(dragEvent) {
		    this.event = dragEvent;
		}
		if(dragActionType) {
		    this.dragActionType = dragActionType;
		}
	    this.originalPosition = new BSDElementPosition(this.element);
	    this.currentPosition = new BSDElementPosition(this.element);
	    this.cursorPosition = new BSDElementPosition(this.element);
	   	this.deltaX = 0;
	    this.deltaY = 0;
	    this.isResize = false;
	    this.beginTime = new Date();
	
	    if(this.dragActionType && this.dragActionType == 'resize') {
	    	this.isResize = true;
	    } else if(this.dragActionType && this.dragActionType == 'move') {
	    	this.isResize = false;
		} else if(dragActionType) {
	    	alert("Invalid value for drag dragActionType: expecting resize or move");
	       	return;
	   	}
	
		if(!this.event) {
			return;
		}
		
		this.setOriginalPosition(this.event);
		

	},	
	
	
	setOriginalPosition: function(dragEvent) {
		this.event = dragEvent;
		var eventPosition = BSDLocationUtils.getEventPosition(this.event);
		this.originalCusorPosition = eventPosition;



	    if(this.isResize) {
	    	this.deltaX = eventPosition.x; 
	       	this.deltaY = eventPosition.y; 
	   	} else {
	   		var margin = BSDDOMUtils.getElementMargin(this.element, true);

	    	this.deltaX = eventPosition.x - this.originalPosition.x + parseInt(margin.left); // parseInt(this.element.style.left);
	      	this.deltaY = eventPosition.y - this.originalPosition.y + parseInt(margin.top); //parseInt(this.element.style.top);
	   	}
		BSDLogUtils.debug("Set drag delta: " + this.deltaX + " " + this.deltaY + " " + eventPosition.x + " " + eventPosition.y + " " + this.element.style.left + " " + this.element.style.top);
	    this.updateCurrentLocation(this.event);

	},
	
	
	updateCurrentLocation: function(event) {
		var eventPosition = BSDLocationUtils.getEventPosition(event);
      	var newX = eventPosition.x - this.deltaX;
       	var newY = eventPosition.y - this.deltaY;

       	this.currentPosition = new BSDElementPosition(this.element, newX, newY);
      	this.cursorPosition = new BSDElementPosition(this.element, eventPosition.x,
      	  							eventPosition.y);


		this.event = event;
		this.currentEventPosition = eventPosition;
	},
	
	getHasMovedOutsideSelf: function() {
		if(!this.currentEventPosition || !this.originalPosition) {
			return false;
		}
		if(this.originalPosition.contains(this.currentEventPosition.x, this.currentEventPosition.y)) {
			return false;
		}
		BSDLogUtils.debug("has moved outside: [" + this.originalPosition.x + " " + this.originalPosition.y + " " + this.originalPosition.maxX + " " + this.originalPosition.maxY + "] [" + this.currentEventPosition.x + " " + this.currentEventPosition.y + "]");
		return true;
	},
	
	getHasMovedAtLeast: function(pixels) {
		if(!this.cursorPosition || !this.originalCursorPosition) {
			return false;
		}
	
		if(Math.abs(this.cursorPosition.x - this.originalCursorPosition.x) > pixels) {
			return true;
		}
		if(Math.abs(this.cursorPosition.y - this.originalCursorPosition.y) > pixels) {
			return true;
		}
		return false;
	},
	
	getDoesSourceContainTarget: function(target, source) {
		if(!source) {
			source = this.element;
		}
		if(!source || !target) {
			return false;
		}
		if(source == target) {
			return true;
		}
		var parentNode = target.parentNode;
		while(parentNode != null) {
			if(parentNode == source) {
				return true;
			}
			parentNode = parentNode.parentNode;
		}

		return false;
	},
	
	getDoesTargetContainSource: function(target, source) {
		if(!source) {
			source = this.element;
		}
		if(!source || !target) {
			return false;
		}
		if(source == target) {
			return true;
		}
		var parentNode = source.parentNode;
		while(parentNode != null) {
			if(parentNode == target) {
				return true;
			}
			parentNode = parentNode.parentNode;
		}
		return false;	
	},
	
    getOriginalParent: function() {
	    if(this.element.customDragParent) {
			return this.element.customDragParent;
		}
		var parent = BSDDOMUtils.getObjectById(this.originalParentId);
		if(!parent) {
			parent = BSDDOMUtils.getObjectByIdFromParent(document, this.originalParentId);
		}	
    	return parent;
    }
}
BSDDragUtils = {
	DEPENDENCIES: new Array("BSDEventUtils", "BSDElementPosition", "BSDLocationUtils", "BSDDOMUtils", "BSDHighlightUtils", "drag/BSDDragProperties", "BSDEventUtils", "util/BSDBrowserUtils"),

	beginDrag: function(elementToDrag, event, dragActionType, moveInitializationActions, moveValidators, 
												moveCompletionActions, dragProperties) {
		if(!BSDEventUtils.getIsLeftClick(event)) { // shift and alt aren't working yet || event.shiftKey || event.altKey) { //don't want to drag on right-click
			return false;
		}	

		if(BSDDragUtils.getShouldIgnoreDragEvent(event)) {


	    	return false;
	    }





		if(!dragProperties) {
	    	dragProperties = new BSDDragProperties(elementToDrag, event, dragActionType);
	    }
		for(var i = 0; moveInitializationActions && i < moveInitializationActions.length; i++) {

			var result = moveInitializationActions[i].executeDragAction(dragProperties);
			if(!result) {
				BSDLogUtils.debug("Abandoning drag: " + i + " " + moveInitializationActions[i].toString());
				return false;
			}
		}	

		BSDEventUtils.registerEvent(document, "mousemove", moveHandler); 
		BSDEventUtils.registerEvent(document, "mouseup", upHandler);
		BSDEventUtils.registerEvent(window, "mouseout", windowMouseoutHandler);  //for firefox if mouse exits the window
		BSDEventUtils.registerEvent(elementToDrag, "mousedown", upHandler); //incase we miss the mouseup
		/*if(window.setCapture) { //TODO: Figure out why this doesn't work for IE outside-the-window bug
			window.setCapture(true);
		}*/

		BSDEventUtils.stopPropagation(event);


		function moveHandler(e) {

		
			BSDEventUtils.fixEventTarget(e);
		    dragProperties.updateCurrentLocation(e);

			BSDEventUtils.stopPropagation(e);
			if(elementToDrag.bsdDragManager && !elementToDrag.bsdDragManager.currentDragElement) {
				if(elementToDrag.bsdDragManager.currentDragElement) {
					alert("wrong element: " + elementToDrag.bsdDragManager.currentDragElement);
				}
				elementToDrag.bsdDragManager.currentDragElement = elementToDrag;
			}
			dragProperties.isValidMove = true;
		    for(var i = 0; moveValidators && i < moveValidators.length; i++) {
		    	var currentValidator = moveValidators[i];
		        var result = currentValidator.executeDragAction(dragProperties);		        
		        if(!result) {

					dragProperties.isValidMove = false;
		        } else {

		        }
		   	}


		    if(dragProperties.isResize) {
		    	var oldWidth = parseInt(BSDDOMUtils.getElementStyle(elementToDrag, "width")); // elementToDrag.style.width);
		    	var oldHeight = parseInt(BSDDOMUtils.getElementStyle(elementToDrag, "height")); // elementToDrag.style.height);
		    	if(!dragProperties.originalWidth || !dragProperties.originalHeight) {
		    		dragProperties.originalWidth = oldWidth;
		    		dragProperties.originalHeight = oldHeight;
		    	}
			  	var newWidth =  (oldWidth + e.clientX - dragProperties.deltaX);
		      	var newHeight = (oldHeight + e.clientY - dragProperties.deltaY);

				var strMinWidth = BSDDOMUtils.getElementStyle(elementToDrag, "min-width");
				var minWidth = 1;
				if(strMinWidth) {
					minWidth = parseInt(strMinWidth);
				}
				var strMinHeight = BSDDOMUtils.getElementStyle(elementToDrag, "min-height");
				var minHeight = 1;
				if(strMinHeight) {
					minHeight = parseInt(strMinHeight);
				}


				if(newWidth >= minWidth) {
					elementToDrag.style.width = newWidth + "px";
					dragProperties.newWidth = newWidth;
			        dragProperties.deltaX = e.clientX;
				} 
				
				if(newHeight >= minHeight) {
					elementToDrag.style.height = newHeight + "px";
					dragProperties.newHeight = newHeight;
			        dragProperties.deltaY = e.clientY;
				}	


			} else {
		    	var oldWidth = parseInt(BSDDOMUtils.getElementStyle(elementToDrag, "width")); // elementToDrag.style.width);
		    	var oldHeight = parseInt(BSDDOMUtils.getElementStyle(elementToDrag, "height")); // elementToDrag.style.height);
				elementToDrag.style.left = dragProperties.currentPosition.x + "px";
		      	elementToDrag.style.top = dragProperties.currentPosition.y + "px";
		      	elementToDrag.style.width = oldWidth;
		      	elementToDrag.style.height = oldHeight;

		    }
		
		}
		
		function upHandler(e) {

			BSDEventUtils.fixEventTarget(e);

			BSDEventUtils.removeEvent(document, "mouseup", upHandler);
			BSDEventUtils.removeEvent(document, "mousemove", moveHandler);
			BSDEventUtils.removeEvent(window, "mouseout", windowMouseoutHandler);
			BSDEventUtils.removeEvent(elementToDrag, "mousedown", upHandler);
			/*if(document.releaseCapture) {
				document.releaseCapture();
			}*/
			
			BSDEventUtils.stopPropagation(e);

		    for(var i = 0; moveCompletionActions && i < moveCompletionActions.length; i++) {
		    	var currentAction = moveCompletionActions[i];
		       	var result = currentAction.executeDragAction(dragProperties);
		       	if(!result) {
		       		BSDLogUtils.debug("got false from completion action: " + currentAction);
		       		return;
		       	}
		    }
		    	
		    dragProperties.element.dragProperties = null;
		    if(elementToDrag.bsdDragManager) {
				elementToDrag.bsdDragManager.currentDragElement = null;
			}

		    	
		}
		
		function windowMouseoutHandler(e) {
			if(!e.relatedTarget) {
				upHandler(e);
			}
		}
		
		return true;
	},
		
	
	getDragEnabledElement: function(element) {
		while(element && !element.bsdDragEnabled) {
			element = element.parentNode;
		}
		if(!element.bsdDragEnabled) {
			alert("Couldn't find element with bsdDragElement property enabled");
			return;
		}
		return element;	
	},
	
	getShouldIgnoreDragEvent: function(event) {
		if(!event || !event.target) {
			return false;
		}
		if(event.shiftKey || event.altKey || event.ctrlKey) {
			return true;
		}


		var nodeName = event.target.nodeName;
		
		if(BSDDragUtils.getIsIgnorableNodeName(nodeName)) {
			return true;
		}

		var parentNode = event.target.parentNode;
		while(parentNode) {
			if(BSDDragUtils.getIsIgnorableNodeName(parentNode.nodeName)) {
				return true;
			}		
			parentNode = parentNode.parentNode;
		}
		
		var testNode = event.target;
		if(BSDDOMUtils.getAttributeValue(testNode, "nodrag")) {
			return true;
		}
		testNode = testNode.parentNode;
		
		while(testNode && testNode != document) {
		    var nodrag = BSDDOMUtils.getAttributeValue(testNode, "nodrag");

			if(nodrag && nodrag.toLowerCase && nodrag.toLowerCase() == "all") {
				return true;
			}
			testNode = testNode.parentNode;
		}
		
		return false;
	},
	
	getIsIgnorableNodeName: function(nodeName) {

		if(!nodeName) {
			return false;
		}
		nodeName = nodeName.toUpperCase();
		if(nodeName == 'SELECT' || nodeName == 'A' || nodeName == 'INPUT' || nodeName == 'TEXTAREA' || nodeName == 'OBJECT' || nodeName == 'EMBED') {
			return true;
		}
		return false;	
	},
	
	beginAbsoluteDrag: function(elementToDrag, event, dragActionType, dragOptions, 
													elementParent) {
	    if(BSDDragUtils.getShouldIgnoreDragEvent(event)) {

	    	return;
	    }
	    if(!elementToDrag) {
			alert("Didn't get element to drag");
	    }

		elementToDrag = BSDDragUtils.getDragEnabledElement(elementToDrag);

		var dragProperties = new BSDDragProperties(elementToDrag, event, dragActionType);

		elementToDrag.bsdDragOptions = dragOptions;
		if(!elementToDrag.parentNode.id) {
			elementToDrag.parentNode.id = (new Date()).getTime() + "." + Math.round(10000*Math.random());
		}
		dragProperties.originalParentId = elementToDrag.parentNode.id;
		dragProperties.originalParentIndex = BSDDOMUtils.getElementParentIndex(elementToDrag);
		
		
		if(dragOptions.allowMovementOutsideParent) {
	    	elementParent = document.body;		
		} else if(!elementParent) {
	        elementParent = BSDDragUtils.getDragElementParent(elementToDrag);
	    }
		elementToDrag.bsdDragParent = elementParent;

		

		var moveInitializationActions = dragOptions.moveInitializationActions;
		var moveValidationActions = dragOptions.moveValidationActions;
		var moveCompletionActions = dragOptions.moveCompletionActions;
		var convertToAbsoluteAction = new BSDDragActions.ConvertToAbsoluteAction(); 
		var placeholderAction = new BSDDragActions.CreateDragPlaceholderAction();
		var addAbsolute = true;
		var addPlaceholder = true;
		for(var i = 0; i < moveValidationActions.length; i++) {
			var currentAction = moveValidationActions[i];
			if(currentAction.toString() == convertToAbsoluteAction.toString()) {
				addAbsolute = false;
			} else if(currentAction.toString() == placeholderAction.toString()) {
				addPlaceholder = false;
			}
		}
		if(addAbsolute) {
			BSDArrayUtils.insert(moveValidationActions, convertToAbsoluteAction, 0);
		}
		if(addPlaceholder) {
			BSDArrayUtils.insert(moveInitializationActions, placeholderAction, 0);
		}

	    var drug = BSDDragUtils.beginDrag(elementToDrag, event, dragActionType, moveInitializationActions,
	    							moveValidationActions, moveCompletionActions, dragProperties);
	    return drug;			
	},
	
	initializeDragElementsById: function(dragOptions, id) {
	    /*this.doDrag = function(event) {
			event = BSDEventUtils.fixEventTarget(event);
			BSDDragUtils.beginAbsoluteDrag(event.target, event, 'move');
	    }*/
	
	    var elements = new Array();
	    var element = BSDDOMUtils.getObjectById(id);
	    elements[0] = element;
	    BSDDragUtils.initializeDragElements(dragOptions, elements);
	},
	
	initializeDragElementsByClass: function(dragOptions, className, parentId) {
	    /*this.doDrag = function(event) {
			var target = BSDEventUtils.fixEventTarget(event);
			BSDDragUtils.beginAbsoluteDrag(target, event, 'move');
	    }
		*/
	    var parentElement;
	    if(parentId) {
			parentElement = BSDDOMUtils.getObjectById(parentId);
			if(!parentElement) {
			    alert("Couldn't get parent: " + parentId);
			    return;
	        }
	    }
	    if(!parentElement) {
			parentElement = document.body;
	    }
	    var elements = BSDDOMUtils.getObjectsByClass(className, parentElement);
	    BSDDragUtils.initializeDragElements(dragOptions, elements);
	},
	
	initializeDragElementsByParentId: function(dragOptions, parentId) {
	    var parentElement = BSDDOMUtils.getObjectById(parentId);
	    if(!parentElement) {
	    	return;
	    }
	    var elements = parentElement.childNodes;
	    BSDDragUtils.initializeDragElements(dragOptions, elements);
	},
	
	initializeDragElements: function(dragOptions, elements) {
	    for(var i = 0; i < elements.length; i++) {
			var currentElement = elements[i];
			BSDDragUtils.initializeDragElement(dragOptions, currentElement);
	    }
	},

	initializeDragElement: function(dragOptions, element) {
		var dragElementPosition = new BSDElementPosition(element);
		element.dragElementPosition = dragElementPosition;

		function doDrag(event) {
			var target = BSDEventUtils.fixEventTarget(event);
			BSDDragUtils.beginAbsoluteDrag(target, event, 'move', dragOptions);
	    }
	    
		BSDEventUtils.registerEvent(element, "mousedown", doDrag);
		element.bsdDragEnabled = true;
		
		/** This causes too many issues - removing.  Maybe consider only changing cursor during drag events 
		if(element.nodeType == 1) {
			BSDHighlightUtils.setMoveCursor(element);		
		} else {
			BSDHighlightUtils.setMoveCursor(element.parentNode);				
		}
		*/
	},

	fixDropdragElementPosition: function(element) {	

		var elementSibling = element.nextSibling;
		while(elementSibling && elementSibling.nodeType != 1) {
			elementSibling = elementSibling.nextSibling;
		}
		if(elementSibling && element.dragElementPosition && elementSibling.dragElementPosition) {
			if(elementSibling.dragElementPosition.y > element.dragElementPosition.maxY + 1) {
				element.dragElementPosition.maxY = elementSibling.dragElementPosition.y - 1;
			}
		}
	},
	
	initializeDropZoneElement: function(dragOptions, element) {
		var dragElementPosition = new BSDElementPosition(element, null, null, true);
		element.dragElementPosition = dragElementPosition;

		if(!dragOptions.getHighlightDropzones()) {
			return;
		}
		function doDropMouseover(event) {

			var target = BSDEventUtils.fixEventTarget(event);
			var element = BSDDragUtils.getDragEnabledElement(target);
			if(element.bsdDragManager && element.bsdDragManager.currentDragElement) {
				BSDHighlightUtils.highlightElement(element);
			} 
		}
		BSDEventUtils.registerEvent(element, "mouseover", doDropMouseover);
	},
		
	getDragElementParent: function(elementToDrag) {
		if(elementToDrag.customDragParent) {
			return elementToDrag.customDragParent;
		} else if(elementToDrag.parentNode.nodeName == "TD") {
			return BSDDragUtils.getDragElementParent(elementToDrag.parentNode);
	    } else if(elementToDrag.parentNode.nodeName == "TBODY") {
			return BSDDragUtils.getDragElementParent(elementToDrag.parentNode);
	    } else if(elementToDrag.parentNode.nodeName == "TR") {
			return BSDDragUtils.getDragElementParent(elementToDrag.parentNode);   
	    } else {
			return elementToDrag.parentNode;
	    }
	},
	
	
	cancelMove: function(parentElement, selectedElement, placeHolderElement, dragProperties) {

		if(!selectedElement) {
			BSDLogUtils.error("cancelMove: selected element null");
			return;
		}
		if(!parentElement) {
			BSDLogUtils.error("cancelMove: parent element null");
			return;
		}

		if((!placeHolderElement || !placeHolderElement.parentNode) && selectedElement.bsdDragOldPlaceHolders) {
			BSDLogUtils.debug("Checking old placeholders: " + selectedElement.bsdDragOldPlaceHolders.length);
			for(var j = 0; j < selectedElement.bsdDragOldPlaceHolders.length; j++) {
				var currentPlaceHolder = selectedElement.bsdDragOldPlaceHolders[j];
				if(currentPlaceHolder.parentNode) {
					placeHolderElement = currentPlaceHolder;
				}
			}
		}

		if(!parentElement && placeHolderElement && placeHolderElement.parentElement) {
			parentElement = placeHolderElement.parentNode;
		}

		if(parentElement && parentElement.nodeType != 1 && selectedElement && selectedElement.parentNode && selectedElement.parentNode.nodeType == 1) {

			return;
		}

		if(!parentElement) {
			BSDLogUtils.error("ERROR: Couldn't find parent node");
			return;
		}
		
		if(!placeHolderElement) {
			BSDLogUtils.error("ERROR: cancelContentCopyMove: Couldn't find placeHolderElement for selectedElement: " + selectedElement.id);
			return;
		}

		
		BSDLocationUtils.makeElementNormallyPositioned(selectedElement);
		var success = BSDDragUtils.replacePlaceholderElement(selectedElement, placeHolderElement, parentElement);
		if(!success && placeHolderElement) {
			success = BSDDragUtils.placeElementInOriginalLocation(selectedElement, placeHolderElement, dragProperties);

			BSDDOMUtils.removeElement(placeHolderElement);
		}
		if(!success) {
			BSDLogUtils.error("ERROR: Couldn't replace placeholder element for cancelContentCopyMove: [" + selectedElement.id + "][" + parentElement.id + "][" + parentElement.nodeName + "][" + parentElement.className + "]");
		} else {
			selectedElement.bsdDragPlaceHolder = null;
			selectedElement.bsdDragOldPlaceHolders = null;
		}

			
	},
	
	replacePlaceholderElement: function(selectedElement, placeHolderElement, parentElement, isRecursive) {
		if(!placeHolderElement) {
			BSDLogUtils.error("Replace 0: got null placeHolderElement for selectedElement " + selectedElement.id);
			return;
		}

		BSDDOMUtils.changeElementStyle(placeHolderElement, "border", "1px solid #ff0000");
		if(!parentElement || parentElement.nodeType != 1) {

			return false;
		}
		var childNodes = parentElement.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];

			if(currentChild.nodeName.toUpperCase() == 'TBODY') {
				var success = BSDContentCopyEditorUtils.replacePlaceholderElement(selectedElement, placeHolderElement, currentChild);
				if(success) {
					return true;
				}	
			}
		
			if(currentChild == placeHolderElement) {

				placeHolderElement.parentNode.replaceChild(selectedElement, placeHolderElement);
				return true;				
			} else if(currentChild == selectedElement) {

				if(currentChild != placeHolderElement && parentElement.nodeName.toLowerCase() == 'body' && placeHolderElement && placeHolderElement.parentNode) {

					placeHolderElement.parentNode.replaceChild(selectedElement, placeHolderElement);
				} 

				return true;
			} 
		}

		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			if(currentChild.nodeType != 1) {
				continue;
			}
			var success = BSDDragUtils.replacePlaceholderElement(selectedElement, placeHolderElement, currentChild, true);
			if(success) {
				return true;
			}
		}


		/*
		var placeParent = placeHolderElement.parentNode;
		if(!placeParent) {
			BSDLogUtils.debug("Replace 5: placeholderParent is null");
		}
		while(!isRecursive && placeParent) {
			BSDLogUtils.debug("Replace 5.1: placeholderParent: [" + placeParent.nodeName + "][" + placeParent.id + "][" + placeParent.className);
			placeParent = placeParent.parentNode;
		}
		*/
		return false;
	},
	
	placeElementInOriginalLocation: function(selectedElement, placeHolderElement, dragProperties) {

		var parent = dragProperties.getOriginalParent();

		if(!parent) {
			BSDLogUtils.error("Couldn't find parent of dragElement: " + selectedElement.id + " " + dragProperties.originalParentId + " " + dragProperties.originalParentIndex);
			return false;
		}

		BSDDOMUtils.insertChild(parent, selectedElement, dragProperties.originalParentIndex);
		return true;
	}
	
	
	
}

BSDAjaxLimiter = BSDClass.create();
BSDAjaxLimiter.DEPENDENCIES = new Array("BSDClass", "BSDTimeoutManager", "BSDArrayUtils");
BSDAjaxLimiter.prototype = {
 	DEPENDENCIES: new Array(),

	className: "BSDAjaxLimiter",
	initialize: function(targetObject, ajaxRequestFunction, ajaxReplyFunction, frequencyLimitMillis, threadCountLimit) {
		this.targetObject = targetObject;
		this.ajaxRequestFunction = ajaxRequestFunction;
		this.ajaxReplyFunction = ajaxReplyFunction;
		this.frequencyLimitMillis = frequencyLimitMillis;
		this.threadCountLimit = threadCountLimit;
		this.threadCount = 0;
		this.cumulativeRequestCount = 0;
		this.requestQueue = new Array();
		this.isHandlingRequest = false;
		this.timeoutManager = new BSDTimeoutManager(this.hashCode());
   	},

	executeRequest: function() {
		var count = this.cumulativeRequestCount++;
		var timeoutRequest = new BSDTimeoutRequest(count, this, this.executeRequestQueue, arguments);
		BSDArrayUtils.append(this.requestQueue, timeoutRequest);
		this.timeoutManager.setTimeout(timeoutRequest, 250);
	},
	
	executeRequestQueue: function() {
		if(!this.isCallAllowed()) {
			if(!this.refreshTimeout && this.requestQueue.length > 0) {


				var count = this.cumulativeRequestCount++;
				var timeoutRequest = new BSDTimeoutRequest(count, this, this.executeRequestQueue, arguments);
				this.refreshTimeout = this.timeoutManager.setTimeout(timeoutRequest, this.frequencyLimitMillis/2);					
			}

			return;
		}
		this.refreshTimeout = null;
		
		var lastRequest = this.popFromQueue();
		if(!lastRequest) {

			return;
		}
		this.executeRequestInternal.apply(this, lastRequest.timeoutFunctionArgs);
	},
	
	executeRequestInternal: function() {

		this.markCallBegin();
		this.ajaxRequestFunction.apply(this.targetObject, arguments);	
	},
	
	handleReply: function() {
		this.markCallEnd();
		this.ajaxReplyFunction.apply(this.targetObject, arguments);
	},
   	
   	isCallAllowed: function() {   	
   		var currentTime = new Date();
   		if(this.lastCallDate && this.frequencyLimitMillis && this.lastCallDate.getTime() > ((new Date).getTime() - this.frequencyLimitMillis)) {

   			return false;
   		}
   		var queueLength = this.requestQueue.length;
   		if(queueLength > 0) {
   			var lastRequest = this.requestQueue[queueLength - 1];
   			if(lastRequest.creationDate.getTime() > ((new Date).getTime() - 250)) {
   				return false;
   			}
   		}

   		if(this.threadCountLimit && this.threadCount && this.threadCount >= this.threadCountLimit) {
   			return false;
   		}
   		return true;
   	},
   	
   	markCallBegin: function() {
   		this.lastCallDate = new Date();
   		this.threadCount++;
   	},
   	
   	markCallEnd: function() {
   		this.threadCount--;   	
   		if(this.threadCount < 0) {
   			this.threadCount = 0;
   		}
   	},
   	
   	popFromQueue: function(quantityTo) {
		if(this.isHandlingRequest) {
			return null;
		} 
		this.isHandlingRequest = true;

		var lastRequest = null;
		var length = this.requestQueue.length;
		if(length > 0) {
			var lastRequest = this.requestQueue[length - 1];
			this.clearQueue(length);
		}
		
		this.isHandlingRequest = false;
   		return lastRequest;
   	},
   	
   	clearQueue: function(length) {
   		if(!length) {
			length = this.requestQueue.length;   		
   		}
		this.requestQueue = BSDArrayUtils.deleteElement(this.requestQueue, 0, length);
   	
   	},
   	
   	hashCode: function() {
   		var hashcode = this.targetObject.hashCode();
   		if(!hashcode) {
   			alert("ERROR: AjaxLimiter targets must implement a hashCode");
   		}
   		return hashcode;
   	}
   	
   	


}
BSDDynamicTable = BSDClass.create();
BSDDynamicTable.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDEventUtils", "BSDLocationUtils", "drag/BSDDragUtils", "table/BSDDynamicTableUtils", "BSDAjaxLimiter");
BSDDynamicTable.prototype = {

	className: "BSDDynamicTable",
	initialize: function(key, tableElement, totalRowCount, pageSize, maxCachedRows, ajaxFrequencyLimitMillis) {

		this.key = key;
		if(!tableElement) {
			BSDLogUtils.error("Got null table element for BSDDynamicTable " + key + " (is this is a popupwindow, make sure there is a div with id BSDDialogWindowContent");
			return;
		}
		if(!this.key) {
			this.key = tableElement.id;
		}
		if(!tableElement.className != "BSDDynamicTable") {
			var elements = BSDDOMUtils.getObjectsByClass("BSDDynamicTable", tableElement, null, "BSDDynamicTable");
			if(elements.length > 0) {
				tableElement = elements[0];
			}
		}
		if(!tableElement) {
			BSDLogUtils.error("Couldn't find table element for BSDDynamicTable " + key);
			return;
		}
	    this.tableElement = tableElement;
		if(!totalRowCount) {
			var strRowCount = BSDDOMUtils.getAttributeValue(this.tableElement, "total-row-count");

			if(strRowCount && strRowCount.length > 0) {
				totalRowCount = parseInt(strRowCount);
			} 
		}
		this.setRowParent(this.tableElement);
		this.cleanupTable();

		if(totalRowCount) {
			this.totalRowCount = totalRowCount;
		} else {
			this.totalRowCount = this.rowParent.length;
		}
		if(!pageSize) {
			var strPageSize = BSDDOMUtils.getAttributeValue(this.tableElement, "page-size");
			if(strPageSize && strPageSize.length > 0) {
				pageSize = parseInt(strPageSize);
			} 
		}
		this.pageSize = pageSize;
		this.pagingParamPrefix = BSDDOMUtils.getAttributeValue(this.tableElement, "paging-param-prefix");
		if(this.pagingParamPrefix) {
			this.pagingParamPrefix = this.pagingParamPrefix.replace(/-/, '_');
		}
		
	    if(!this.tableElement.bsdScrollableElement) {
	    	var scrollableElement = new BSDScrollableElement(this.tableElement, this.totalRowCount);	    	
	    } else {
	    	this.tableElement.bsdScrollableElement.averageRowHeight.rowCount = totalRowCount;
	    }
	    this.averageRowHeight = this.tableElement.bsdScrollableElement.averageRowHeight
	    
	    if(this.tableElement.bsdSortableTable) {
	    	this.tableElement.bsdSortableTable.setDynamicLoadEventHandler(
	    					this.tableElement.bsdScrollableElement.headerRow,
	    					this.executeSortableDynamicLoad, this);
	    }
		
		this.setModelRow(this.rowParent);

	    this.initializeTableEvents();

	    this.initializeEmptyRows();

	   	var scrollableElement = new BSDScrollableElement(this.tableElement);	    	
		BSDDynamicTableUtils.printScaledVisibleRange(this.tableElement);
		this.tableElement.bsdScrollableElement.fixHeaderColumnWidth();
		
		if(!ajaxFrequencyLimitMillis) {
			ajaxFrequencyLimitMillis = 500;
		}
		this.ajaxFrequencyLimitMillis = ajaxFrequencyLimitMillis;
		this.maximumConcurrentThreads = 2;

		this.setAjaxFunctions(this.doDynamicLoad, this.doDynamicLoadReply);		
		
		this.tablePageID = BSDDOMUtils.getAttributeValue(this.tableElement, 'table-page-id');
		this.renderComponentId = BSDDOMUtils.getAttributeValue(this.tableElement, 'render-id'); 
		this.tableQueryArgs = BSDDOMUtils.getAttributeValue(this.tableElement, 'query-args');
		
		this.tableElement.bsdScrollableElement.totalCount = this.totalRowCount;
		this.scrollColumns = this.tableElement.bsdScrollableElement.scrollColumns;
		if(!this.scrollColumns) {
			this.scrollColumns = 1;
		}

		BSDLogUtils.debug("Got params: " + this.tablePageID + " " + this.renderComponentId + " " + this.tableQueryArgs + " " + this.pageSize);
   	},
   	
   	cleanupTable: function() {
   		var childNodes = this.rowParent.childNodes;
   		var original = childNodes.length;
   		var deleteArray = new Array();
   		for(var i = 0; i < childNodes.length; i++) {
   			var currentChild = childNodes[i];
   			if(currentChild.nodeType != 1) {
   				BSDArrayUtils.append(deleteArray, currentChild);
   			}
   		}
   		for(var i = 0; i < deleteArray.length; i++) {
   			var currentChild = deleteArray[i];
   			this.rowParent.removeChild(currentChild);
   		}
   	},
   	
   	hashCode: function() {
   		return this.key;
  	}, 	

	initializeEmptyRows: function() {
		var totalRowCount = Math.ceil(this.totalRowCount/this.tableElement.bsdScrollableElement.scrollColumns);
		var visibleRowCount = this.tableElement.bsdScrollableElement.visibleRowCount;
		var existingRowCount = this.rowParent.childNodes.length; //assumes that initial set of rows was present on page load
		var unloadedRowCount = totalRowCount - existingRowCount;
		
		var ratio = this.calculateEmptyRowRatio(visibleRowCount, unloadedRowCount);
		var scaledRowCount = Math.floor(unloadedRowCount / ratio);
		var emptyRowCount = scaledRowCount + (unloadedRowCount - scaledRowCount);
		
		BSDLogUtils.debug("empty rows: " + existingRowCount + " " + totalRowCount + " " + ratio + " " + visibleRowCount + " " + scaledRowCount + " " + emptyRowCount + " " + this.rowParent.nodeName + " " + this.modelRow1.nodeName);
		var i = existingRowCount;
		var j = existingRowCount;

		while(i < totalRowCount) {		
			this.createEmptyRow(i, j, totalRowCount, ratio);
			
			i += ratio;
			j++;

		}

		
	},
	
	createEmptyRow: function(i, j, totalRowCount, ratio, deleteExistingRow) {
		if(deleteExistingRow && i < this.rowParent.childNodes.length) {
			BSDLogUtils.debug("createEmptyRow: Deleting existing row: " + i + " " + j);
			if(this.rowParent.deleteRow) {
				this.rowParent.deleteRow(i);
			} else {
				var currentRow = this.rowParent.childNodes[i];
				this.rowParent.removeChild(currentRow);
			}
		}
		var newRow;
		if(this.rowParent.insertRow) {

			newRow = this.rowParent.insertRow(j);				
		} else if(this.rowParent.parentNode.insertRow) {
			newRow = this.rowParent.parentNode.insertRow(j);			
		} else {
			newRow = BSDDOMUtils.createElement("div");
			this.rowParent.appendChild(newRow);
		}
		if(newRow) {

			var newCell = newRow.insertCell(0);
			BSDDOMUtils.setAttributeValue(newRow, "height", this.averageRowHeight);
			BSDDOMUtils.addText(newCell, (i));

		}
		if(i % 2 && newRow && this.modelRow2) {
			newRow.className = this.modelRow2.className;
		} else if(newRow) {
			newRow.className = this.modelRow1.className;			
		} else if(i % 2 && this.modelRow2) {
			newRow = this.modelRow2.cloneNode(false);
			this.rowParent.appendChild(newRow);
		} else {
			newRow = this.modelRow1.cloneNode(false);			
			this.rowParent.appendChild(newRow);
		}
		BSDDOMUtils.setAttributeValue(newRow, "isunloaded", "true");
		if(totalRowCount - i >= ratio) {
			newRow.scaledRowCount = ratio;
		} else {
			newRow.scaledRowCount = totalRowCount - i;
		}	
		return newRow;
	}, 
	
	calculateEmptyRowRatio: function(visibleRowCount, unloadedRowCount) {
		var multiplier = 30;	
		var maxRows = multiplier * visibleRowCount;
		var ratio = Math.floor(unloadedRowCount / maxRows);
		BSDLogUtils.debug("Calculated raw ratio: " + ratio + " " + visibleRowCount + " " + unloadedRowCount + " " + maxRows);
		if(ratio < 1) {
			ratio = 1;
		}
		return ratio;
	},
	
    toString: function() {
		var str = "[" + "DynamicTable" + "]";
		return str;
    },
    
    setAjaxFunctions: function(newQueryFunction,  newReplyHandler) {
    	this.queryFunction = newQueryFunction;
    	this.replyHandler = newReplyHandler;		
    	
    	this.initializeQueryLimiter();		
    },
    
   	initializeQueryLimiter: function() {
   		this.queryFunctionLimiter = new BSDAjaxLimiter(this, this.queryFunction, this.replyHandler, this.ajaxFrequencyLimitMillis, this.maximumConcurrentThreads);	   	
   	},
   	
		
	setRowParent: function(tableElement) {
		var childNodes = tableElement.childNodes;
		var bestChild;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];

			if(currentChild.nodeType != 1) {
				continue;
			} else if(currentChild.nodeName == 'TABLE') {
				this.setRowParent(currentChild);
				return;
			} else if(currentChild.nodeName == 'TBODY') {
				this.setRowParent(currentChild);
				return;
			}
		}
		this.rowParent = tableElement;
	},

	setModelRow: function(rowParent) {
		var modelRow1;
		var modelRow2;
		var childNodes = rowParent.childNodes
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			if(currentChild.nodeType != 1) {
				continue;
			} else if(!modelRow1) {
				modelRow1 = currentChild;
			} else if(!modelRow2 && currentChild.className && modelRow1.className && currentChild.className != modelRow1.className) {
				modelRow2 = currentChild;
			} else {
				break;
			}
		}
		if(rowParent.nodeName == 'TABLE' || rowParent.nodeName == 'TBODY') {
			modelRow1 = BSDDOMUtils.createElement('TR');
		} else {
			modelRow1 = BSDDOMUtils.createElement('DIV');			
		}
		this.modelRow1 = modelRow1;
		this.modelRow2 = modelRow2;
	},
	
	initializeTableEvents: function() {
		if(this.tableElement) {
			var table = this;
			function handleScroll(event) {
				BSDDynamicTableUtils.printScaledVisibleRange(table.tableElement);
				var sortFields;
				if(table.tableElement.bsdSortableTable) {
					sortFields = table.tableElement.bsdSortableTable.sortFields;
				}
				table.executeDynamicLoad(event, table, sortFields);
			}
			BSDEventUtils.registerEvent(this.tableElement, "scroll", handleScroll);		
		}

	},
	
	executeSortableDynamicLoad: function(event, sortFields) {

		BSDLogUtils.debug("executeSortableDynamicLoad: begin");
		var totalRowCount = this.totalRowCount;
		
		var ratio = 1;
		BSDLogUtils.debug("executeSortableDynamicLoad: " + totalRowCount + " " + this.rowParent.childNodes.length);
		var j = 0;
		for(var i = 0; i < this.rowParent.childNodes.length || j < totalRowCount; i++) {
			var currentRatio = ratio;
			var currentRow = null;
			var isUnloaded = false;
			if(i < this.rowParent.childNodes.length) {
				currentRow = this.rowParent.childNodes[i];
				var strIsUnloaded = BSDDOMUtils.getAttributeValue(currentRow, "isunloaded");
				if(strIsUnloaded && strIsUnloaded == "true") {
					isUnloaded = true;
				}

				var rowRatio = currentRow.scaledRowCount;
				if(rowRatio && isUnloaded) {
					currentRatio = rowRatio;
				} else if(!isUnloaded) {
					currentRatio = 1;  //must be 1 if row is loaded
				}
				if(currentRatio > ratio) {
					ratio = currentRatio; //we want to pick the largest overall ratio
				}
				if(j >= totalRowCount || !isUnloaded) {
					BSDLogUtils.debug("Deleting existing row: " + i + " " + j);
					if(this.rowParent.deleteRow) {
						this.rowParent.deleteRow(i);
					} else {
						this.rowParent.removeChild(currentRow);
					}
				} 
			} 
			

			if((!isUnloaded || !currentRow) && j < totalRowCount) {
				if(j > totalRowCount - currentRatio) {
					currentRatio = totalRowCount - j;
				} 
				
				BSDLogUtils.debug("Creating empty row: " + i + " " + j + " " + currentRatio);
				this.createEmptyRow(j, i, totalRowCount, currentRatio);					
			}
			
			j+= currentRatio;			
			
		}
		this.executeDynamicLoad(event, this, sortFields, true);
		
		
		
	},
	
	executeDynamicLoad: function(event, table, sortFields, forceDynamicLoad) {

		var rowParent = table.rowParent;
		var visibleRowCount = table.tableElement.bsdScrollableElement.visibleRowCount;
		var visibleRange = table.tableElement.bsdScrollableElement.getScaledVisibleRange(table.tableElement);
		var maxRange = table.tableElement.bsdScrollableElement.getScaledRowCount(); 
		visibleRange.maximumRowCount = maxRange;

		BSDLogUtils.debug("Visible scaled range: " + visibleRange.scaledBeginIndex + " " + visibleRange.scaledEndIndex + " " + this.pageSize + " " + visibleRowCount);
		var pageSize = this.pageSize;
		if(!pageSize || pageSize < visibleRowCount * 2) {
			pageSize = visibleRowCount * 2 * this.scrollColumns;
		}
		var unloadedRange = visibleRange.clone();
		BSDLogUtils.debug("Page Size: " + pageSize + " " + unloadedRange.scaledBeginIndex + " " + unloadedRange.scaledEndIndex + " " + (unloadedRange.scaledEndIndex - unloadedRange.scaledBeginIndex));
		unloadedRange.setScaledCenteredRowCount(pageSize);
		BSDLogUtils.debug("Page Size after reset: " + pageSize + " " + unloadedRange.scaledBeginIndex + " " + unloadedRange.scaledEndIndex + " " + (unloadedRange.scaledEndIndex - unloadedRange.scaledBeginIndex));
		var scaledBeginIndex = unloadedRange.scaledBeginIndex;
		var scaledEndIndex = unloadedRange.scaledEndIndex;
		
		BSDLogUtils.debug("Scaled range to check: " + scaledBeginIndex + " " + scaledEndIndex);
		
		var message = "";
		unloadedRange.resetVisibleIndexes();
		var visibleBeginUnloadedIndex = -1;
		var visibleEndUnloadedIndex;
		BSDLogUtils.debug("Getting ranges: " + unloadedRange.visibleBeginIndex + " " + unloadedRange.visibleEndIndex);
		for(var i = unloadedRange.visibleBeginIndex; i <= unloadedRange.visibleEndIndex && !forceDynamicLoad; i++) {
			var currentRow = table.rowParent.childNodes[i];
			var isUnloaded = BSDDOMUtils.getAttributeValue(currentRow, "isunloaded");
			if(isUnloaded && isUnloaded == "true") {
				if(visibleBeginUnloadedIndex < 0) {
					visibleBeginUnloadedIndex = i;
				} 
				visibleEndUnloadedIndex = i;							
				message += "<br/>Row Unloaded " + i + ": " + isUnloaded + " " + visibleBeginUnloadedIndex + " " + visibleEndUnloadedIndex;
			} 
		}
		BSDLogUtils.debug("Got ranges: " + unloadedRange.visibleBeginIndex + " " + unloadedRange.visibleEndIndex + " " + visibleBeginUnloadedIndex + " " + visibleEndUnloadedIndex);

		if(visibleBeginUnloadedIndex > -1 && !forceDynamicLoad) {
			unloadedRange.visibleBeginIndex = visibleBeginUnloadedIndex;
			unloadedRange.visibleEndIndex = visibleEndUnloadedIndex;
			unloadedRange.resetScaledIndexes();
			if(unloadedRange.getScaledRowCount() < pageSize && 
						(unloadedRange.visibleBeginIndex < visibleRange.visibleBeginIndex - visibleRowCount
						|| unloadedRange.visibleBeginIndex > visibleRange.visibleEndIndex + visibleRowCount)) {
				BSDLogUtils.error("executeDynamicLoad:  Unloaded area not within visible buffer: " + unloadedRange.visibleBeginIndex + " " + visibleRange.visibleBeginIndex + " " + unloadedRange.visibleBeginIndex + " " + visibleRange.visibleEndIndex);
				return;
			}
			BSDLogUtils.debug("Reset unloadedRange: " + unloadedRange.visibleBeginIndex + " " + unloadedRange.visibleEndIndex + " " + unloadedRange.scaledBeginIndex + " " + unloadedRange.scaledEndIndex);
		} else if(visibleBeginUnloadedIndex < 0 && !forceDynamicLoad) {
			BSDLogUtils.debug("skipping executeDyamicLoad - nothing to load: " + visibleBeginUnloadedIndex + "\n" + message);
			this.queryFunctionLimiter.clearQueue();  //clear the queue so a previous request doesn't jump us away from where we've landed
			return; //nothing to load
		} 
		
		BSDLogUtils.debug("unloaded visible range: " + unloadedRange.visibleBeginIndex + " " + unloadedRange.visibleEndIndex + " " + table.rowParent.childNodes.length);
		BSDLogUtils.debug("unloaded scaled range: " + unloadedRange.scaledBeginIndex + " " + unloadedRange.scaledEndIndex);

		BSDLogUtils.debug(message);

		if(forceDynamicLoad) {
			this.doDynamicLoad(event, table, sortFields, rowParent, visibleRange, unloadedRange, forceDynamicLoad);
		} else {
			BSDLogUtils.debug("queueing request: " + unloadedRange.scaledBeginIndex + " " + unloadedRange.scaledEndIndex);
			table.queryFunctionLimiter.executeRequest(event, table, sortFields, rowParent, visibleRange, unloadedRange, forceDynamicLoad);
		}
	},
	
	doDynamicLoad: function(event, table, sortFields, rowParent, visibleRange, unloadedRange, forceDynamicLoad) {
		if(BSDAjaxUtils.getIsAjaxDisabled()) {
			BSDLogUtils.debug("doDynamicLoad: Ajax disabled");
			return;
		}
							
		BSDLogUtils.debug("doDynamicLoad: Loading data: " + unloadedRange.scaledBeginIndex + "/" + unloadedRange.getBeginIndex() + " " + unloadedRange.scaledEndIndex + "/" + unloadedRange.getEndIndex(1));
		
		var strSortFields;
		if(sortFields && sortFields.length > 0) {
			strSortFields = BSDArrayUtils.toCommaDelimitedString(sortFields);
		}
		var sortOnParamName = 'sort_on';
		if(this.pagingParamPrefix) {
			sortOnParamName = this.pagingParamPrefix + "_" + sortOnParamName;
		}
		var pageID = '/ajaxpages/dynamictableloader';
		var pageArguments = {'table_page_id' : table.tablePageID,
							'render_id': table.renderComponentId,
							'query_args': table.tableQueryArgs,
							'table_begin_index': unloadedRange.getBeginIndex(),
							'table_end_index': unloadedRange.getEndIndex(1)
							};
		pageArguments[sortOnParamName] = strSortFields;
		
			
		BSDLogUtils.debug("doDynamicLoad: doNavigation");
		KCMAjaxGui.doNavigation(pageID, pageArguments, 
			{ 
				callback:function(str) { 
					if(forceDynamicLoad) {
						table.doDynamicLoadReply(str, rowParent, visibleRange, unloadedRange, table, sortFields);
					} else {
						table.queryFunctionLimiter.handleReply(str, rowParent, visibleRange, unloadedRange, table, sortFields);
					}
				} 
			});
			
		BSDLogUtils.debug("doDynamicLoad: Loading data: DONE");
			
	},

	doDynamicLoadReply: function(data, tableRowParent, visibleRange, unloadedRange, table, sortFields) {

		var scaledBeginUnloadedIndex = unloadedRange.scaledBeginIndex;
		var scaledEndUnloadedIndex = unloadedRange.scaledEndIndex;

		if(this.isLocked) {
			BSDLogUtils.error("BSDDynamicTable.doDynamicLoadReply: table locked, skipping ajax call");
			return;
		}

		this.isLocked = true;
		try {
			this.doDynamicLoadReplyUnSynced(data, tableRowParent, visibleRange, scaledBeginUnloadedIndex, scaledEndUnloadedIndex, table, sortFields);				
		} catch(err) {
			this.isLocked = null;	
			throw err;
		}
		this.isLocked = null;	
	},

	doDynamicLoadReplyUnSynced: function(data, tableRowParent, visibleRange, scaledBeginUnloadedIndex, scaledEndUnloadedIndex, table, sortFields) {
		BSDLogUtils.debug("doDynamicLoadReplyUnSynced: BEGIN");
		if(!BSDAjaxUtils.doNavigationReply(data)) {
			return;
		}

		var tempTable = document.createElement("DIV");
		tempTable.innerHTML = data.html;
	    var tempTableRowParent = BSDDynamicTableUtils.getTableRowParent(tempTable);
		if(!tempTableRowParent) {
			BSDLogUtils.error("Couldn't load temp table row parent");
			alert(data.html);
			return;
		}
		BSDLogUtils.debug("Got table: " + tableRowParent + " " + scaledBeginUnloadedIndex + " " + scaledEndUnloadedIndex + " " + tempTableRowParent.childNodes.length);

		
		var message = "";
		var tempChildren = new Array();
		for(var i = 0; i < tempTableRowParent.childNodes.length; i++) {
			var currentChild = tempTableRowParent.childNodes[i];
			if(currentChild.nodeType != 1) {

				continue;
			}
			BSDArrayUtils.append(tempChildren, currentChild);
		}
		BSDLogUtils.debug("Got temp children: " + tempChildren.length);
		var originalScrollHeight = table.tableElement.scrollHeight;
		BSDLogUtils.debug("ScrollHeight: " + originalScrollHeight +  " CumHeight: " + table.tableElement.bsdScrollableElement.rowGroupList.lastGroup().cumulativeHeight + " RowCount: " + tableRowParent.childNodes.length);
		
		var beginGroup = table.tableElement.bsdScrollableElement.getRowGroupByScaledRowIndex(scaledBeginUnloadedIndex);
		if(!beginGroup) {
			BSDLogUtils.error("Couldn't find beginGroup for scaledUnloadedIndex: " + scaledBeginUnloadedIndex);
			return;
		}
		BSDLogUtils.debug("Got begin group for scaledBeginUnloadedIndex: " + scaledBeginUnloadedIndex + " " + beginGroup.visibleBeginIndex + " " + beginGroup.visibleEndIndex);
		var visibleBeginUnloadedIndex = beginGroup.getVisibleIndex(scaledBeginUnloadedIndex);
		var visibleEndUnloadedIndex;
		var beginMod = (scaledBeginUnloadedIndex - beginGroup.scaledBeginIndex) % beginGroup.scalingFactor;
		var beginBuffer = 0;
		if(beginMod > 0) {
			beginBuffer = beginGroup.scalingFactor - beginMod; //Here we've got to make sure we leave part of a scaled row because our scaled index is in the middle of it
		}
		BSDLogUtils.debug("Got Mod: " + beginBuffer + " " + scaledBeginUnloadedIndex + " " + beginGroup.scaledBeginIndex + " " + (scaledBeginUnloadedIndex - beginGroup.scaledBeginIndex) + " " + beginGroup.scalingFactor + " " + visibleBeginUnloadedIndex + " " + tableRowParent.childNodes.length + " " + tempChildren.length);
		var j = visibleBeginUnloadedIndex;
		var existingRow = null;
		for(var i = 0; i < tempChildren.length; i++) {

			var currentChild = tempChildren[i];





			if(!existingRow && j < tableRowParent.childNodes.length) {
				BSDLogUtils.debug("Loading existing row: " + j + " " + tableRowParent.childNodes.length);
				existingRow = tableRowParent.childNodes[j];
			} else if(!existingRow) {

				BSDLogUtils.error("ERROR: Not enough existing rows to handle dynamic load reply");
				return;
			}

			var scaledRowCount = existingRow.scaledRowCount;
			BSDLogUtils.debug("got scaled row count: " + scaledRowCount + " " + beginBuffer);
			if(beginBuffer > 0) {


				var nextRow;
				if(j + 1 < tableRowParent.childNodes.length) {
					nextRow = tableRowParent.childNodes[j + 1];
				}
				if(nextRow) {
					tableRowParent.insertBefore(currentChild, nextRow);
					message += "<BR>InsertedAfter row: " + j + "/" + i + " " + existingRow.scaledRowCount + " " + beginBuffer;
				} else {
					tableRowParent.appendChild(currentChild);
					message += "<BR>AppendedAfter row: " + j + "/" + i + " " + existingRow.scaledRowCount + " " + beginBuffer;
				}
				existingRow.scaledRowCount -= 1;				
				beginBuffer -= 1;
				if(beginBuffer < 1 || existingRow.scaledRowCount < 2) {
					existingRow = null;
					visibleBeginUnloadedIndex++; //need to adjust and pass correct index to recalc function below
					j++;
				}
			} else if(scaledRowCount && scaledRowCount > 1) {

				tableRowParent.insertBefore(currentChild, existingRow);				
				message += "<BR>InsertedBefore row: " + j + "/" + i + " " + existingRow.scaledRowCount;
				existingRow.scaledRowCount -= 1;
			} else {

				tableRowParent.replaceChild(currentChild, existingRow);
				message += "<BR>Replaced row: " + j + "/" + i;
				existingRow = null;
			}

			visibleEndUnloadedIndex = j;
			j++;	

			this.initializeRow(currentChild);
		}

		BSDLogUtils.debug(message);
		BSDLogUtils.debug("Comparing indexes: " + scaledEndUnloadedIndex + "/" +  scaledBeginUnloadedIndex + " " + visibleEndUnloadedIndex + "/" +  visibleBeginUnloadedIndex + " " + (scaledEndUnloadedIndex - scaledBeginUnloadedIndex) + " " + (visibleEndUnloadedIndex - visibleBeginUnloadedIndex) + " " + tempChildren.length + " " + tableRowParent.childNodes.length);		
		var newGroups = table.tableElement.bsdScrollableElement.recalculateByScaledIndexRange(table.tableElement, visibleBeginUnloadedIndex, scaledBeginUnloadedIndex, scaledEndUnloadedIndex, originalScrollHeight);
		if(!newGroups) {
			BSDLogUtils.debug("BSDDynamicTable: Got null groups from recalculate");
		}
		
		BSDLogUtils.debug("A: " + newGroups);
		for(var i = 0; i < newGroups.length; i++) {
			var currentGroup = newGroups[i];
			BSDLogUtils.debug("CurrentGroup: " + currentGroup + " " + visibleRange);
			if(currentGroup.scaledBeginIndex <= visibleRange.scaledBeginIndex) {
				var height = currentGroup.getCumulativeHeightAtScaledIndex(visibleRange.scaledBeginIndex);
				table.tableElement.scrollTop = height;				
			}
		}

		BSDLogUtils.debug("B: " + table.tableElement.bsdScrollableElement.rowGroupList.length() + " " + newGroups.length + " " + table.tableElement.bsdScrollableElement.rowGroupList.getScaledRowCount());
		table.tableElement.bsdScrollableElement.printScaledVisibleRange(table.tableElement);

		BSDLogUtils.debug("ScrollHeight (after add): " + table.tableElement.scrollHeight +  " CumHeight: " + table.tableElement.bsdScrollableElement.rowGroupList.lastGroup().cumulativeHeight + " RowCount: " + tableRowParent.childNodes.length);
		
	},	

	getPopupElementFromEvent: function(event) {
		BSDEventUtils.fixEventTarget(event);
		var target = event.target;
		while(target && target.className != "BSDDialogWindow") {
			target = target.parentNode;
		}
		return target;
	},
	

	ResizeCompletionAction: function(popupWindow) {
		this.popupWindow = popupWindow;
		this.executeDragAction = function(dragProperties) {
			return this.popupWindow.fixPopupContentDimensions();
		}
		
	},
	
	initializeRow: function(row) {
		if(this.newRowCallback) {
			this.newRowCallback.call(this, row);
		}
	}
	
    
}


 
BSDDynamicTableUtils = {
	DEPENDENCIES: new Array("table/BSDDynamicTable"),
	
	
	getTableRowParent: function(tableElement) {
		var childNodes = tableElement.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			if(currentChild.nodeType != 1) {
				continue;
			} else if(currentChild.nodeName == 'TABLE') {
				return this.getTableRowParent(currentChild);
			} else if(currentChild.nodeName == 'TBODY') {
				return this.getTableRowParent(currentChild);			
			} else {
				return tableElement;
			}
		}	
	},
	
	printScaledVisibleRange: function(element) {
		if(!element.bsdScrollableElement) {
			var scrollableElement = new BSDScrollableElement(element);
		}
		element.bsdScrollableElement.printScaledVisibleRange(element);
	},
	
	initializeDynamicTableById: function(elementId) {
		var element = BSDDOMUtils.getObjectById(elementId);
		BSDDynamicTableUtils.initializeDynamicTable(element);
	},
	
	initializeDynamicTable: function(element) {
		var table = new BSDDynamicTable(element.id, element);
	}
		
    
}






BSDExceptionUtils = {
	DEPENDENCIES: new Array("BSDLogUtils"),
	
	throwException: function(message) {
		BSDLogUtils.error(message);
		throw message;
	}
	
}

BSDSortableTable = BSDClass.create();
BSDSortableTable.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDEventUtils");
BSDSortableTable.prototype = {

	className: "BSDSortableTable",
	initialize: function(table, header, dynamicLoadFunction, dynamicLoadTarget) {
		this.dynamicLoadFunction = dynamicLoadFunction;
		this.dynamicLoadTarget = dynamicLoadTarget;
		this.initializeHeaderLinks(header);
		this.sortFields = new Array();
	},
	
	initializeHeaderLinks: function(header) {
		for(var i = 0; i < header.childNodes.length; i++) {
			var currentChild = header.childNodes[i];
			if(currentChild.nodeName == 'TD') {
				this.initializeHeaderColumnLink(currentChild);
			} else {
				this.initalizeHeaderLinks(currentChild);
			}			
		}
	},
	
	initializeHeaderColumnLink: function(headerColumn) {

		var href = BSDDOMUtils.createElement("A");
		BSDDOMUtils.setAttributeValue(href, "href", "#");
		for(var i = 0; i < headerColumn.childNodes.length; i++) {
			var currentChild = headerColumn.childNodes[i];
			headerColumn.removeChild(currentChild);
			href.appendChild(currentChild);
		}
		headerColumn.appendChild(href);
		this.initializeHeaderColumnEvents(href);
	},

	initializeHeaderEvents: function(header) {
		for(var i = 0; i < header.childNodes.length; i++) {
			var currentChild = header.childNodes[i];
			if(currentChild.nodeName == 'A') {
				this.initializeHeaderColumnEvents(currentChild);
			} else {
				this.initializeHeaderEvents(currentChild);
			}			
		}
	},
		
	initializeHeaderColumnEvents: function(headerHref) {
		if(!this.dynamicLoadFunction || !this.dynamicLoadTarget) {
			return;
		}
		this.initializeHeaderColumnFieldName(headerHref);
		var sortableTable = this;
		function headerSortEventHandler(event) {

			var sortFields = sortableTable.getSortFieldsByEvent(event);			
			var dynamicLoadArgs = new Array();
			dynamicLoadArgs[0] = event;
			dynamicLoadArgs[1] = sortFields;
			sortableTable.dynamicLoadFunction.apply(sortableTable.dynamicLoadTarget, dynamicLoadArgs);
		}
		BSDEventUtils.registerEvent(headerHref, "click", headerSortEventHandler);
	},
	
	initializeHeaderColumnFieldName: function(headerHref) {

		var fieldName = BSDDOMUtils.getAttributeValue(headerHref, "field-name");
		if(!fieldName || fieldName.length < 1) {
			fieldName = BSDDOMUtils.getAttributeValue(headerHref.parentNode, "field-name");
		}
		headerHref.bsdSortFieldName = fieldName;
		BSDLogUtils.debug("Found sortable field name: " + fieldName);
	},
	
	setDynamicLoadEventHandler: function(header, dynamicLoadFunction, dynamicLoadTarget) {
		this.dynamicLoadFunction = dynamicLoadFunction;
		this.dynamicLoadTarget = dynamicLoadTarget;
		this.initializeHeaderEvents(header);
	},
	
	getSortFieldsByEvent: function(event) {
		var target = BSDEventUtils.fixEventTarget(event);
		var sortFields = this.sortFields;
		if(target.bsdSortFieldName) {
			this.insertUniqueField(sortFields, target.bsdSortFieldName);
		} 
		return sortFields;
	},
	
	insertUniqueField: function(array, fieldName) {

		var regex = new RegExp(/\s*(\w+)(?:\s+desc|asc)?\s*/i);
		var fieldNameParts = regex.exec(fieldName);
		if(fieldNameParts.length < 2) {
			BSDLogUtils.error("Couldn't parse sort field name: " + fieldName);				
			return;		
		} 
		fieldName = fieldNameParts[1];

		var index = 0;
		for(var i = 0; i < array.length; i++) {
			var currentFieldName = array[i];
			if(!regex.test(currentFieldName)) {
				BSDLogUtils.error("Sort column failed regex test: " + fieldName + " " + currentFieldName);				
				continue;	
			} 
			var cleanedCurrentFieldName = regex.exec(currentFieldName)[1];
			if(cleanedCurrentFieldName == fieldName) {
				BSDArrayUtils.deleteElement(array, i);
				if(i == 0 && currentFieldName.search(/\s*\w+\s+desc\s*$/i) >= 0) {

					fieldName = cleanedCurrentFieldName + " asc";
				} else if(i == 0 && currentFieldName.search(/\s*\w+\s+asc\s*$/i) >= 0) {

					fieldName = cleanedCurrentFieldName + " desc";				
				} else if(i == 0) {

					fieldName += " desc";
				} else {

				}
				break;
			} else {

			}
		}
		BSDArrayUtils.insert(array, fieldName, index);
	}
}
BSDScrollableElement = BSDClass.create();
BSDScrollableElement.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDElementPosition", "BSDArrayUtils", "BSDExceptionUtils", "table/BSDSortableTable");
BSDScrollableElement.prototype = {

	className: "BSDScrollableElement",
	initialize: function(element, rowCount) {

		element.bsdScrollableElement = this;





		this.scrollColumns = 1;
		var strScrollColumns = BSDDOMUtils.getAttributeValue(element, "scroll-columns");
		if(strScrollColumns && strScrollColumns.length > 0) {
			this.scrollColumns = parseInt(strScrollColumns);
		}
		
		this.position = new BSDElementPosition(element);
		this.setRowParent(element); 
		this.scrollTop = parseInt(element.scrollTop);
		this.scrollHeight = parseInt(element.scrollHeight);
		this.scrollWidth = parseInt(element.scrollWidth);
		this.visibleHeight = parseInt(this.position.height);
		this.countRows(this.rowParent.childNodes);

		if(rowCount) {
			this.rowCount = rowCount;
		}
		
		this.averageRowHeight = Math.round(this.scrollHeight / this.calculatedRowCount);
		this.visibleRowCount = Math.round(this.visibleHeight / this.averageRowHeight);

		if(this.maxHeight == 0) {

			this.countRows(this.rowParent.childNodes);
			if(rowCount) {
				this.rowCount = rowCount;
			}
		}
		
		this.calculateCellSpacing();
		this.initializeHeader(element);

	},
	
	calculateCellSpacing: function() {
		var element = this.rowParent;
		while(element && element.nodeName != "TABLE") {
			element = element.parentNode;
		}
		if(element && element.nodeName == 'TABLE') {		
			this.cellPadding = BSDDOMUtils.getElementStyle(element, "cellpadding");
			this.cellSpacing = BSDDOMUtils.getElementStyle(element, "cellspacing");
			if(!this.cellPadding && !this.cellSpacing) {
				this.cellPadding = BSDDOMUtils.getAttributeValue(element, "cellpadding");
				this.cellSpacing = BSDDOMUtils.getAttributeValue(element, "cellspacing");
			}
			BSDLogUtils.debug("got table spacing: " + this.cellPadding + " " + this.cellSpacing);
		}
	},
	
	initializeHeader: function(element) {
		var header = this.getHeaderRow(this.rowParent);	
		if(!header) {
			BSDLogUtils.debug("Failed to initialize header: no row found");
			return;
		}

		header.parentNode.removeChild(header);

		if(header.nodeName == 'TR') {
			header = this.initializeHeaderTable(element, header);
		} else {
			element.parentNode.insertBefore(header, element);
		}
		this.headerRow = header;
		BSDLogUtils.debug("got headerRow: " + this.headerRow.nodeName);
		this.fixHeaderColumnWidth();

		if(!element.bsdSortableTable) {			
			var sortableTable = new BSDSortableTable(element, header);
			element.bsdSortableTable = sortableTable;
		}
	},
	
	
	initializeHeaderTable: function(element, header) {
		var table = BSDDOMUtils.createElement("TABLE");
		BSDDOMUtils.setAttributeValue(table, "width", this.scrollWidth);
		if(this.cellPadding) {
			BSDDOMUtils.setAttributeValue(table, "cellpadding", this.cellPadding);		
		}
		if(this.cellSpacing) {
			BSDDOMUtils.setAttributeValue(table, "cellspacing", this.cellSpacing);		
		}
		var row = table.insertRow(0);
		row.className = header.className;
		row.id = header.id;
		var columnIndex = 0;
		for(var i = 0; i < header.childNodes.length; i++) {
			var currentColumn = header.childNodes[i];
			if(currentColumn.nodeType != 1) {
				continue;
			}
			var newColumn = row.insertCell(columnIndex);
			columnIndex++;
			if(currentColumn.id) {
				newColumn.id = currentColumn.id;
			}
			if(currentColumn.className) {
				newColumn.className = currentColumn.className;
			}
			var fieldName = BSDDOMUtils.getAttributeValue(currentColumn, "field-name");
			if(fieldName) {
				BSDDOMUtils.setAttributeValue(newColumn, "field-name", fieldName);
			}
			newColumn.innerHTML = currentColumn.innerHTML;
		}
		element.parentNode.insertBefore(table, element);
		return row;
	},
	
	fixHeaderColumnWidth: function() {
		if(!this.headerRow) {
			BSDLogUtils.debug("No header row found");
			return;
		}
		if(!this.rowParent || this.rowParent.childNodes.length < 1) {
			return;
		}
		var rowColumns = this.rowParent.childNodes[0].childNodes;
		var headerColumns = this.headerRow.childNodes;
		
		var headerIndex = 0;
		var rowIndex = 0;
		while(headerIndex < headerColumns.length && rowIndex < rowColumns.length) {
			var headerColumn = headerColumns[headerIndex];
			var rowColumn = rowColumns[rowIndex];
			if(headerColumn.nodeType != 1) {
				headerIndex++;
				continue;
			}
			if(rowColumn.nodeType != 1) {
				rowIndex++;
				continue;
			}
			var oldWidth = headerColumn.offsetWidth;
			var width = rowColumn.offsetWidth;

			BSDLogUtils.debug("setting columnwidth: " + headerColumn.nodeName + " " + headerColumn.innerHTML + " " + oldWidth + " " + width + " " + this.scrollWidth);
			if(!width) {
				continue;
			}
		
			try {
				if(headerColumn.nodeName == 'TD' || headerColumn.nodeName == 'TH') {
					BSDDOMUtils.setAttributeValue(headerColumn, "width", width);
				} else {
					BSDDOMUtils.changeElementStyle(headerColumn, "width", width + "px");
				}
				if(rowColumn.nodeName == 'TD' || rowColumn.nodeName == 'TH') {
					BSDDOMUtils.setAttributeValue(rowColumn, "width", width);				
				} else {
					BSDDOMUtils.changeElementStyle(rowColumn, "width", width + "px");					
				}
			} catch (err) {  
				BSDLogUtils.debug("caught error: " + err);

			}
			
			headerIndex++;
			rowIndex++;
				
		}
	
	},
	
	getHeaderRow: function(rowParent) {
		var childNodes = rowParent.childNodes;
		for(var i = 0; i < childNodes.length && i < 5; i++) {
			var currentChild = childNodes[i];

			if(currentChild.nodeType != 1) {
				continue;
			} else if(currentChild.className && currentChild.className == 'BSDTableHeader') {
				return currentChild;
			} else if(currentChild.nodeName == 'TH') {
				return rowParent;
			} else {
				return this.getHeaderRow(currentChild);
			}
		}
		return null;
	},
	
	
	countRows: function(children) {

		var count = 0;
		var scaledCount = 0;
		this.rowGroupList = new BSDScrollableElementRowGroupList(this.scrollColumns); 
		this.maxHeight = 0;
		var currentRowGroup;
		var cumulativeHeight = 0;
		var previousRowHeight = this.averageRowHeight;
		var previousScaledRowCount;
		var message = "";
		for(var i = 0; i < children.length; i++) {
			var currentChild = children[i];
			if(currentChild.nodeType != 1 || BSDDOMUtils.containsClass(currentChild, "BSDPopupMenu")) {
				continue;
			}
			
			var currentHeight = this.calculateRowHeight(currentChild);
			if(currentHeight) {
				previousRowHeight = currentHeight;
			} else {
				currentHeight = previousRowHeight;
			}
			if(currentHeight && currentHeight > this.maxHeight) {
				this.maxHeight = currentHeight;
			}
			var scalingFactor = currentChild.scaledRowCount;
			if(!scalingFactor) {
				scalingFactor = 1;
			}
			
			if(!currentRowGroup || currentRowGroup.averageHeight != currentHeight || currentRowGroup.scalingFactor != scalingFactor) {
				BSDLogUtils.debug("adding row group: " + currentHeight + " [" + currentChild.id + "][" + currentChild.className + "]");
				currentRowGroup = new BSDScrollableElementRowGroup(count, currentHeight, cumulativeHeight, scaledCount, scalingFactor, null, null, this.scrollColumns);			
				this.rowGroupList.addRowGroup(currentRowGroup);	

			} else {
				currentRowGroup.visibleEndIndex = count;
			}

			cumulativeHeight += currentHeight;
			currentRowGroup.cumulativeHeight = cumulativeHeight;
			count++; //can't use children.length because it would include elements of nodeType != 1
			scaledCount += scalingFactor;
		}

		this.rowCount = count;
		this.calculatedRowCount = count;
		
		if(this.rowCount > 0) {
			this.fixRowGroupHeight();
		}



		return count;
	},
	

	fixRowGroupHeight: function() {

		var cumulativeHeight = this.rowGroupList.getRowGroup(this.rowGroupList.length() - 1).cumulativeHeight;
		var difference = this.scrollHeight - cumulativeHeight;
		this.rowGroupList.fixRowGroupHeight(difference, this.rowCount);
	},
	
	calculateRowHeight: function(row) {
		var height = BSDDOMUtils.getElementHeight(row);
		if(height && height > 0) {
			return height;
		}
		var rowChildren = row.childNodes;
		for(var i = 0; i < rowChildren.length; i++) {
			var currentRowChild = rowChildren[i];
			
			if(currentRowChild.nodeType != 1) {
				continue;
			} 
			height = currentRowChild.offsetHeight; 
			if(height && height > 0) {
				return height;
			}
		}
		return null;
	},
	
	getBeginTargetHeight: function(element) {
		var targetHeight = element.scrollTop + 1;
		return targetHeight;	
	},
	
	getEndTargetHeight: function(element, beginTargetHeight) {
		if(!beginTargetHeight) {
			beginTargetHeight = this.getBeginTargetHeight(element);
		}
		var targetHeight =  beginTargetHeight + this.visibleHeight;
		if(targetHeight > 16) {
			targetHeight = targetHeight - 15; //magic number to make things come out right
		}
		return targetHeight;	
	},

	getScaledRowCount: function() {
		return this.rowGroupList.getScaledRowCount()
	},
	
	getTotalCount: function() {
		if(this.totalCount) {
			return this.totalCount;
		} else {
			return this.getScaledRowCount() * this.scrollColumns;
		}
	},
	
	getScaledVisibleRange: function(element) {
		var beginTargetHeight = this.getBeginTargetHeight(element);
		var endTargetHeight = this.getEndTargetHeight(element, beginTargetHeight);
		var visibleRange = this.rowGroupList.getScaledRangeByHeight(beginTargetHeight, endTargetHeight);

		return visibleRange;
	},
	
	printScaledVisibleRange: function(element) {
		var visibleRange = this.getScaledVisibleRange(element);

		var beginElement = BSDDOMUtils.getObjectByIdFromParent(element.parentNode, "BSDScrollBeginIndex");
		if(beginElement) {
			var beginIndex = visibleRange.getBeginIndex(1);
			if(beginIndex < 0) {
				beginIndex = 0;
			}
			BSDDOMUtils.setText(beginElement, beginIndex);
		} else {
			BSDLogUtils.warning("Couldn't find beginElement: BSDScrollBeginIndex from parent " + element.parentNode.id);
		}
		var endElement = BSDDOMUtils.getObjectByIdFromParent(element.parentNode, "BSDScrollEndIndex");
		if(endElement) {
			var endIndex = visibleRange.getEndIndex(1);
			if(endIndex > this.getTotalCount()) {
				endIndex = this.getTotalCount();
			}
			BSDDOMUtils.setText(endElement, endIndex);
		} else {
			BSDLogUtils.warning("Couldn't findendElement: BSDScrollEndIndex from parent " + element.parentNode.id);
		}
		var countElement = BSDDOMUtils.getObjectByIdFromParent(element.parentNode, "BSDScrollRowCount");
		if(countElement) {
			BSDDOMUtils.setText(countElement, this.getTotalCount());		
		} else {
			BSDLogUtils.warning("Couldn't find total count element:  BSDScrollRowCount from parent " + element.parentNode.id);
		}
		
	},
	
	setRowParent: function(tableElement) {
		var childNodes = tableElement.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];

			if(currentChild.nodeType != 1) {
				continue;
			} else if(currentChild.nodeName == 'TABLE') {
				this.setRowParent(currentChild);
				return;
			} else if(currentChild.nodeName == 'TBODY') {
				this.setRowParent(currentChild);
				return;
			} else {


			}
		}
		this.rowParent = tableElement;
	},
	
	recalculateByScaledIndexRange: function(tableElement, visibleBeginIndex, scaledBeginIndex, scaledEndIndex, previousScrollHeight) {
		var newGroups = this.rowGroupList.recalculateByScaledIndexRange(visibleBeginIndex, scaledBeginIndex, scaledEndIndex, this.rowParent, tableElement.scrollHeight); 
		this.scrollHeight = tableElement.scrollHeight;
		return newGroups;
	},
	
	getRowGroupIndexByScaledRowIndex: function(scaledIndex) {
		return this.rowGroupList.getRowGroupIndexByScaledRowIndex(scaledIndex);
	},

	getRowGroupByScaledRowIndex: function(scaledIndex) {
		return this.rowGroupList.getRowGroupByScaledRowIndex(scaledIndex);
	}
	

    
}

BSDScrollableElementRowGroup = BSDClass.create();
BSDScrollableElementRowGroup.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDExceptionUtils");
BSDScrollableElementRowGroup.prototype = {
	/*
		To make this work with scaled rows, we need to:
		1.  Make rowGroup understand difference between visible rows and hidden rows
		2.  Split rowGroup whenever rows are unhidden
		3.  Eventually merge neighboring rowGroups that have the same parameters
		
	*/
	initialize: function(visibleBeginIndex, averageHeight, previousCumulativeHeight, scaledBeginIndex, scalingFactor, visibleEndIndex, id, scrollColumns) {
		this.visibleBeginIndex = visibleBeginIndex;
		if(!visibleEndIndex) {
			this.visibleEndIndex = visibleBeginIndex;
		} else {
			this.visibleEndIndex = visibleEndIndex;
		}
		BSDLogUtils.debug("Initializing BSDScrollableElementRowGroup: " + visibleBeginIndex + " " + scaledBeginIndex + " " + averageHeight + " " + previousCumulativeHeight + " " + scrollColumns);
		if(!averageHeight || isNaN(averageHeight)) {
			BSDLogUtils.error("Got NaN for row group height: " + visibleBeginIndex + " " + averageHeight + " " + previousCumulativeHeight + " " + scaledBeginIndex);
		}
		this.averageHeight = averageHeight;
		this.previousCumulativeHeight = previousCumulativeHeight;
		if(!scalingFactor) {
			scalingFactor = 1;
		}
		this.scalingFactor = scalingFactor;
		if(!scaledBeginIndex) {
			scaledBeginIndex = visibleBeginIndex;
		}
		this.scaledBeginIndex = scaledBeginIndex;
		this.setCumulativeHeight();
		this.id=id;
		this.scrollColumns = scrollColumns;
	},
	
	setCumulativeHeight: function() {
		this.cumulativeHeight = this.previousCumulativeHeight + this.getVisibleRowCount() * this.averageHeight;
	},
	
	getVisibleRowCount: function() {	
		return (this.visibleEndIndex - this.visibleBeginIndex + 1); //don't want to include scaling factor here
	},
	
	getScaledRowCount: function() {
		return this.getVisibleRowCount() * this.scalingFactor;
	},
	
	getVisibleHeight: function() {
		return Math.round(this.getVisibleRowCount() * this.averageHeight);
	},
	
	setVisibleHeight: function(newHeight) {
		this.averageHeight = newHeight / this.getVisibleRowCount();		
		this.cumulativeHeight = this.previousCumulativeHeight + newHeight;
	},
	
	getScaledHeight: function() {
		return Math.round(this.getScaledRowCount() * this.averageHeight);	
	},
	
	containsHeight: function(targetHeight) {

		if(this.previousCumulativeHeight <= targetHeight && targetHeight < this.cumulativeHeight) {
			return true;
		}
		return false;
	},

	containsScaledIndex: function(scaledIndex) {
		if(this.scaledBeginIndex > scaledIndex || scaledIndex > this.getScaledEndIndex()) {
			return false;
		}	
		return true;
	},

	containsVisibleIndex: function(scaledIndex) {
		if(this.visibleBeginIndex > scaledIndex || scaledIndex > this.visibleEndIndex) {
			return false;
		}	
		return true;
	},
		
	getContainedIndex: function(targetHeight) {
		var targetDifference = targetHeight - this.previousCumulativeHeight;
		var index = targetDifference / this.averageHeight;
		index = Math.floor(index);
		if(index < 0) {
			index = 0;
		}

		return this.visibleBeginIndex + index;
	},
	
	getVisibleIndex: function(scaledIndex) {
		return this.visibleBeginIndex + Math.floor((scaledIndex - this.scaledBeginIndex) / this.scalingFactor);
	},
	
	getScaledIndex: function(visibleIndex) {

		return this.scaledBeginIndex + (visibleIndex - this.visibleBeginIndex) * this.scalingFactor;
	},
	
	getScaledEndIndex: function() {
		return this.scaledBeginIndex + this.getVisibleRowCount() * this.scalingFactor - 1;
	},
	
	getCumulativeHeightAtScaledIndex: function(scaledIndex) {
		var ratio = (scaledIndex - this.scaledBeginIndex) / this.getScaledRowCount();
		var height = (this.cumulativeHeight - this.previousCumulativeHeight) * ratio;
		return this.previousCumulativeHeight + height;
	},
	
	
	recalculate: function(previousRowGroup) {
		var visibleRowCount = this.getVisibleRowCount();
		if(this.visibleBeginIndex != previousRowGroup.visibleEndIndex + 1) {
			this.visibleBeginIndex = previousRowGroup.visibleEndIndex + 1;
			this.visibleEndIndex = this.visibleBeginIndex + visibleRowCount - 1;
		}
		if(this.scaledBeginIndex != previousRowGroup.getScaledEndIndex() + 1) {
			this.scaledBeginIndex = previousRowGroup.getScaledEndIndex() + 1;
		}
	
		var previousCumulativeHeight = previousRowGroup.cumulativeHeight;
		if(previousCumulativeHeight) {
			this.previousCumulativeHeight = previousCumulativeHeight;
		}
		this.setCumulativeHeight();
	},
	
	isCompatibleWith: function(groupToCompare) {
		if(groupToCompare.averageHeight != this.averageHeight) {
			return false;
		}
		if(groupToCompare.scalingFactor != this.scalingFactor) {
			return false;
		}
		return true;
	},
	
	splitAtScaledIndex: function(scaledIndex, newGroupScalingFactor) {

		if(scaledIndex < this.scaledBeginIndex || scaledIndex > this.getScaledEndIndex()) {
			var message = "Invalid index for split: " + scaledIndex + " " + this.scaledBeginIndex + "/" + this.getScaledEndIndex();
			BSDExceptionUtils.throwException(message);
		}
		if(!newGroupScalingFactor) {
			newGroupScalingFactor = this.scalingFactor;
		}
		var newSplitGroups = new Array(); //we might create more than one new group out of the split if necessary to keep scalingFactor
		
		var remainingScaledBeginIndex = scaledIndex - this.scaledBeginIndex;
		var remainingScaledEndIndex = this.getScaledEndIndex() - scaledIndex + 1;
		var beginMod = remainingScaledBeginIndex % this.scalingFactor;
		var endMod = remainingScaledEndIndex % newGroupScalingFactor;


		var newVisibleEnd = (remainingScaledBeginIndex - beginMod) / this.scalingFactor + this.visibleBeginIndex - 1; 
		var newGroupVisibleBegin = newVisibleEnd + 1;
		if(beginMod > 0) {
			newGroupVisibleBegin++; //beginMod will only have one visible row - rest is scaling
		}
		var newGroupScaledBegin = scaledIndex;
		var newGroupScaledEnd = this.getScaledEndIndex();
		if(endMod > 0) {


			newGroupScaledBegin += endMod;
			newGroupVisibleBegin++;
		}
		var newGroupVisibleCount = (newGroupScaledEnd - newGroupScaledBegin + 1) / newGroupScalingFactor;
		var newGroupVisibleEnd = newGroupVisibleBegin + newGroupVisibleCount - 1;
		
		BSDLogUtils.debug("Creating split: " + remainingScaledBeginIndex + " " + remainingScaledEndIndex + " " + beginMod + " " + endMod + " " + " "  + newVisibleEnd + " " + newGroupVisibleBegin + "/" + newGroupVisibleEnd + " " + newGroupScaledBegin + "/" + newGroupScaledEnd + " " + newGroupVisibleCount);
		var newGroupPreviousCumulativeHeight = this.previousCumulativeHeight;
		var newBeginGroup = null;
		if(remainingScaledBeginIndex >= this.scalingFactor) {
			newBeginGroup = new BSDScrollableElementRowGroup(this.visibleBeginIndex, this.averageHeight, this.previousCumulativeHeight, this.scaledBeginIndex, this.scalingFactor, newVisibleEnd, null, this.scrollColumns);
			newGroupPreviousCumulativeHeight = newBeginGroup.cumulativeHeight;
			BSDArrayUtils.append(newSplitGroups, newBeginGroup);
			newSplitGroups.beginGroup = newBeginGroup;
			BSDLogUtils.debug("Created begin split: " + newBeginGroup.debug());
		}

		if(beginMod > 0) {
			var preSplitGroup = new BSDScrollableElementRowGroup(newVisibleEnd + 1, this.averageHeight, newGroupPreviousCumulativeHeight, scaledIndex - beginMod, beginMod, newVisibleEnd + 1, null, this.scrollColumns);
			newGroupPreviousCumulativeHeight = preSplitGroup.cumulativeHeight;
			BSDArrayUtils.append(newSplitGroups, preSplitGroup);
			if(!newBeginGroup) {
				newSplitGroups.beginGroup = preSplitGroup;				
			} else {
				newSplitGroups.preSplit = preSplitGroup;
			}
			BSDLogUtils.debug("Created presplit: " + preSplitGroup.debug());
		}		

		if(endMod > 0) {
			var postSplitGroup = new BSDScrollableElementRowGroup(newGroupVisibleBegin - 1, this.averageHeight, newGroupPreviousCumulativeHeight, scaledIndex, endMod, newGroupVisibleBegin - 1, null, this.scrollColumns);
			newGroupPreviousCumulativeHeight = postSplitGroup.cumulativeHeight;
			BSDArrayUtils.append(newSplitGroups, postSplitGroup);									

			newSplitGroups.postSplit = postSplitGroup;				
			BSDLogUtils.debug("Created postSplit: " + postSplitGroup.debug());			
		}
	
		var splitGroup;
		if(remainingScaledEndIndex >= newGroupScalingFactor) {
			BSDLogUtils.debug("Creating splitGroup...");
			splitGroup = new BSDScrollableElementRowGroup(newGroupVisibleBegin, this.averageHeight, newGroupPreviousCumulativeHeight, newGroupScaledBegin, newGroupScalingFactor, newGroupVisibleEnd, null, this.scrollColumns);
			newGroupPreviousCumulativeHeight = splitGroup.cumulativeHeight;
			BSDArrayUtils.append(newSplitGroups, splitGroup);									
			newSplitGroups.splitGroup = splitGroup;					
			BSDLogUtils.debug("Created splitGroup: " + splitGroup.debug());
		}
		
		return newSplitGroups;
	},
	
	fixHeight: function(rowDifference, previousCumulativeHeight) {
		if(previousCumulativeHeight) {
			this.previousCumulativeHeight = previousCumulativeHeight
		}
		this.averageHeight = Math.round(this.averageHeight + rowDifference);
		this.setCumulativeHeight();
	},
	
	debug: function() {
		var message = "[" + this.id + "] " + this.visibleBeginIndex + "/" + this.visibleEndIndex + " " + this.scaledBeginIndex + "/" + this.getScaledEndIndex() + " " + this.averageHeight + " " + this.scalingFactor + " " + this.previousCumulativeHeight + " " + this.cumulativeHeight;
		return message;
	}

}

BSDScrollableElementRowGroupList = BSDClass.create();
BSDScrollableElementRowGroupList.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils");
BSDScrollableElementRowGroupList.prototype = {
	initialize: function(scrollColumns) {
		this.rowGroups = new Array();
		this.nextGroupId = 0;
		this.scrollColumns = scrollColumns;
	},
	
	getRowGroup: function(i) {

		return this.rowGroups[i];
	},
	
	length: function() {
		return this.rowGroups.length;
	},
	
	getNextGroupId: function() {
		return this.nextGroupId++;
	},
	
	getScaledRowCount: function() {
		if(!this.rowGroups || this.rowGroups.length < 1) {
			return 0;
		}
		return this.rowGroups[this.rowGroups.length - 1].getScaledEndIndex() + 1;
	},
	
	addRowGroup: function(newGroup) {
		BSDArrayUtils.append(this.rowGroups, newGroup);
		this.totalHeight += newGroup.getVisibleHeight();
		if(!newGroup.id) {
			newGroup.id = this.getNextGroupId();
		}
		/*if(newGroup.visibleEndIndex > this.visibleEndIndex) {
			this.visibleEndIndex = newGroup.visibleEndIndex;
		} else {
			BSDLogUtils.error("ERROR: Tried to append new rowGroup with visibleEndIndex smaller than existing one: " + this.visibleEndIndex + " " + newGroup.visibleEndIndex);
		}
		if(newGroup.getScaledEndIndex() > this.scaledEndIndex) {
			this.scaledEndIndex = newGroup.getScaledEndIndex();
		} else {
			BSDLogUtils.error("ERROR: Tried to append new rowGroup with scaledEndIndex smaller than existing one: " + this.visibleEndIndex + " " + newGroup.visibleEndIndex);
		}
		*/
	},
	
	addRowGroups: function(newGroups) {
		for(var i = 0; i < newGroups.length; i++) {
			this.addRowGroup(newGroups[i]);
		}
	},
	
	getRowGroupIndexByHeight: function(targetHeight, beginIndex) {
		if(!beginIndex) {
			beginIndex = 0;
		}

		for(var i = beginIndex; i < this.rowGroups.length; i++) {
			var currentRowGroup = this.rowGroups[i];

			if(currentRowGroup.containsHeight(targetHeight)) {

				return i;
			}
		}
		BSDLogUtils.error("Couldn't find row group containing height: " + targetHeight + ": " + beginIndex);
		return this.rowGroups.length - 1;
	},
	
	getRowGroupIndexByScaledRowIndex: function(scaledIndex) {
		for(var i = 0; i < this.rowGroups.length; i++) {

			var currentGroup = this.rowGroups[i];

			if(currentGroup.scaledBeginIndex >= 0 && currentGroup.scaledBeginIndex <= scaledIndex && currentGroup.getScaledEndIndex() >= scaledIndex) {
				return i;
			}
		}
		return -1;
	},	
	
	getRowGroupByScaledRowIndex: function(scaledIndex) {
		var index = this.getRowGroupIndexByScaledRowIndex(scaledIndex);
		if(index >= 0) {
			return this.rowGroups[index];
		}
		return null;
	},
	
	getRowGroupIndexByVisibleRowIndex: function(visibleIndex) {
		for(var i = 0; i < this.rowGroups.length; i++) {

			var currentGroup = this.rowGroups[i];
			if(currentGroup.visibleBeginIndex && currentGroup.visibleBeginIndex <= visibleIndex && currentGroup.visibleEndIndex >= visibleIndex) {
				return i;
			}
		}
		return -1;
	},	
	
	getRowGroupByVisibleRowIndex: function(visibleIndex) {
		var index = this.getRowGroupIndexByVisibleRowIndex(visibleIndex);
		if(index >= 0) {
			return this.rowGroups[index];
		}
		return null;
	},
	
	getVisibleIndexByScaledIndex: function(scaledIndex, groupToCheck) {
		if(!groupToCheck || !groupToCheck.containsScaledIndex(scaledIndex)) {
			groupToCheck = this.getRowGroupByScaledRowIndex(scaledIndex);
		}
		return groupToCheck.getVisibleIndex(scaledIndex);
	},
	
	getScaledIndexByVisibleIndex: function(visibleIndex, groupToCheck) {
		if(!groupToCheck || !groupToCheck.containsVisibleIndex(visibleIndex)) {
			groupToCheck = this.getRowGroupByVisibleRowIndex(visibleIndex);
		}
		if(!groupToCheck) {
			return null;
		}
		return groupToCheck.getScaledIndex(visibleIndex);
	},	

	getScaledEndIndexByVisibleIndex: function(visibleIndex, groupToCheck) {
		if(!groupToCheck || !groupToCheck.containsVisibleIndex(visibleIndex)) {
			groupToCheck = this.getRowGroupByVisibleRowIndex(visibleIndex);
		}
		if(!groupToCheck) {
			return null;
		}

		return groupToCheck.getScaledIndex(visibleIndex) + groupToCheck.scalingFactor - 1;
	},	

	recalculateByRowGroupIndex: function(startPoint) {
		if(!startPoint) {
			startPoint = 0;
		}
		var previousRowGroup;
		for(var i = startPoint; i < this.rowGroups.length; i++) {

			var currentRowGroup = this.rowGroups[i];
			if(previousRowGroup) {
				currentRowGroup.recalculate(previousRowGroup);
			} 
			previousRowGroup = currentRowGroup;
		}
	},
	
	getScaledRangeByHeight: function(beginHeight, endHeight) {
		var range = new BSDTableRowRange(this);

		var beginGroupIndex = this.getRowGroupIndexByHeight(beginHeight);
		var beginGroup = this.rowGroups[beginGroupIndex];
		if(!beginGroup) {
			BSDLogUtils.warning("Couldn't find begin group for scrollable element " + this.rowParent);
			return range;
		}
		var beginIndex = beginGroup.getContainedIndex(beginHeight);
		var scaledBeginIndex = beginGroup.getScaledIndex(beginIndex);
		range.visibleBeginIndex = beginIndex;
		range.scaledBeginIndex = scaledBeginIndex;
		range.beginGroup = beginGroup;

		var deltaHeight = endHeight - beginHeight;
		var deltaScaled = beginGroup.getScaledEndIndex() - scaledBeginIndex;
		var visibleScaledRows = Math.round(deltaHeight / beginGroup.averageHeight);
		
		var endGroup = this.rowGroups[this.rowGroups.length - 1];

		if(endHeight >= endGroup.cumulativeHeight - 15) {

			range.scaledEndIndex = endGroup.getScaledEndIndex();
			visibleScaledRows = Math.round(deltaHeight / endGroup.averageHeight);
			beginGroup = endGroup;
			range.scaledBeginIndex = 0;
			if(range.scaledEndIndex > visibleScaledRows) {
				range.scaledBeginIndex = range.scaledEndIndex - visibleScaledRows + 1;
			}
			beginGroupIndex = this.rowGroups.length - 1;
			while(beginGroupIndex > 0 && beginGroup.scaledBeginIndex > range.scaledBeginIndex) {
				beginGroupIndex--;
				beginGroup = this.rowGroups[beginGroupIndex];
			}
			range.endGroup = endGroup;
			range.beginGroup = beginGroup;
			range.visibleEndIndex = endGroup.visibleEndIndex;
			range.visibleBeginIndex = beginGroup.getVisibleIndex(range.scaledBeginIndex);

			BSDLogUtils.debug("getScaledRangeByHeight 3: " + range.scaledBeginIndex + " " + range.scaledEndIndex);

			return range;			
		}
		endGroup = null;
		
		if(visibleScaledRows <= deltaScaled || beginGroupIndex == this.rowGroups.length - 1) {
			range.scaledEndIndex = scaledBeginIndex + visibleScaledRows - 1;
			range.visibleEndIndex = beginGroup.getVisibleIndex(range.scaledEndIndex);
			range.endGroup = beginGroup;

			return range;
		}

		var remainingHeight = deltaHeight - deltaScaled * beginGroup.averageHeight;

		for(var i = beginGroupIndex + 1; i < this.rowGroups.length; i++) {
			endGroup = this.rowGroups[i];

			
			var visibleScaledEndRows = Math.round(remainingHeight / endGroup.averageHeight);
			range.scaledEndIndex = endGroup.scaledBeginIndex + visibleScaledEndRows - 1;
			if(endGroup.getScaledHeight() >= remainingHeight) {
				break;
			} else {
				remainingHeight -= endGroup.getScaledHeight();
			}
		}
		range.visibleEndIndex = endGroup.getVisibleIndex(range.scaledEndIndex);
		range.endGroup = endGroup;

		return range;		
	},
	
	
	
	recalculateByScaledIndexRange: function(visibleBeginIndex, scaledBeginIndex, scaledEndIndex, rowParent, newHeight) {
		BSDLogUtils.debug("recalculateByScaledIndexRange: " + visibleBeginIndex + " " + scaledBeginIndex + " " + scaledEndIndex);
		if(!scaledBeginIndex || !scaledEndIndex || scaledBeginIndex == scaledEndIndex) {
			return new Array();
		}
		this.debug();
		var beginGroupIndex = this.getRowGroupIndexByScaledRowIndex(scaledBeginIndex);
		if(beginGroupIndex < 0) {		
			BSDLogUtils.error("recalculateByScaledIndexRange: Couldn't find beginGroupIndex");
			return;
		}
		var beginGroup = this.rowGroups[beginGroupIndex];
		var endGroupIndex = beginGroupIndex;
		var endGroup = beginGroup;
		while(endGroupIndex < this.rowGroups.length - 1 && endGroup && endGroup.getScaledEndIndex() <= scaledEndIndex) {
			endGroupIndex++;
			endGroup = this.rowGroups[endGroupIndex];
		}

		/*seems smart, but risky. removing for now
		if(beginGroup && beginGroup.scaledBeginIndex && beginGroup.visibleBeginIndex) {
			scaledBeginIndex = beginGroup.scaledBeginIndex;
			visibleBeginIndex = beginGroup.visibleBeginIndex;
		}
		if(endGroup && endGroup.scaledEndIndex) {
			scaledEndIndex = endGroup.scaledEndIndex;
		}*/

		var newGroups = this.createRowGroupsFromElements(scaledBeginIndex, scaledEndIndex, visibleBeginIndex, rowParent.childNodes);
		this.debug("NewGroups1", newGroups);

		if(newGroups.length < 1) {
			BSDLogUtils.error("recalculateByScaledIndexRange:  Got 0 new groups for scaled range " + scaledBeginIndex + " " + scaledEndIndex);
			return;
		}


		BSDLogUtils.debug("Checking new/old group compatibility: " + beginGroupIndex + "/" + beginGroup.scaledBeginIndex + "/" + beginGroup.getScaledEndIndex() + " " + endGroupIndex + "/" + endGroup.scaledBeginIndex + "/" + endGroup.getScaledEndIndex());
		for(var i = 0; i < newGroups.length; i++) {
			if(!newGroups[i].isCompatibleWith(beginGroup) || !newGroups[i].isCompatibleWith(endGroup)) {
				var splitGroups = new Array();
				BSDArrayUtils.copy(newGroups, splitGroups);
				this.insertGroups(splitGroups, beginGroupIndex, endGroupIndex);
				break;
			}
		}


		this.recalculateByRowGroupIndex(beginGroupIndex - 1);
		var extraHeight = newHeight - this.lastGroup().cumulativeHeight;
		this.fixRowGroupHeight(extraHeight, scaledEndIndex - scaledBeginIndex + 1, newGroups);

		this.debug("NewGroups2", newGroups);

		
		this.recalculateByRowGroupIndex(beginGroupIndex - 1);


		this.fixRowGroupsScale(scaledBeginIndex, scaledEndIndex, rowParent, beginGroupIndex);
		
		this.debug("FinalGroups");
		
		return newGroups;
	},
	
	fixRowGroupsScale: function(scaledBeginIndex, scaledEndIndex, rowParent, beginGroupIndex) {
		if(!beginGroupIndex) {
			beginGroupIndex = this.getRowGroupIndexByScaledRowIndex(scaledBeginIndex);
		}

		var message = "";
		var beginGroup = this.rowGroups[beginGroupIndex];
		if(!beginGroup) {
			BSDLogUtils.error("Couldn't find beginGroup for fixRowGroupsScale: " + beginGroupIndex + " " + this.rowGroups.length);
			return;
		}


		for(var i = beginGroup.visibleBeginIndex; i < beginGroup.visibleBeginIndex + 10 && i <= beginGroup.visibleEndIndex; i++) {
			var currentRow = rowParent.childNodes[i];
			message += this.fixRowScale(currentRow, i, beginGroup);
		}

		if(beginGroup.getVisibleRowCount() > 10) {

			for(var i = beginGroup.visibleEndIndex - 10; i <= beginGroup.visibleEndIndex; i++) {
				var currentRow = rowParent.childNodes[i];
				message += this.fixRowScale(currentRow, i, beginGroup);
			}
		}	
		BSDLogUtils.debug("Fixing rowGroups scale c");
		for(var i = beginGroupIndex + 1; i < this.rowGroups.length; i++) {

			var currentGroup = this.rowGroups[i];
			if(currentGroup.scaledBeginIndex < scaledBeginIndex) {
				for(var j = currentGroup.visibleBeginIndex; j < currentGroup.visibleBeginIndex + 10 && j <= currentGroup.visibleEndIndex; j++) {
					var currentRow = rowParent.childNodes[j];
					message += this.fixRowScale(currentRow, j, currentGroup);
				}
				break;
			}	
		}

	},
	
	fixRowScale: function(currentRow, rowIndex, group) {
		var message = "";
		var scaledRowCount = 1;
		if(currentRow.scaledRowCount) {
			scaledRowCount = currentRow.scaledRowCount;
		}
		if(scaledRowCount != group.scalingFactor) {
			message = "Found beginRow that doesn't match: " + rowIndex + " " + scaledRowCount + " " + group.scalingFactor;
		}
		return message;
	},
	
	insertGroups: function(newGroups, beginGroupIndex, endGroupIndex) {
		var beginGroup = this.rowGroups[beginGroupIndex];
		if(!beginGroup) {
			BSDLogUtils.error("Couldn't find beginGroup for insertGroups: " + beginGroupIndex + " " + this.rowGroups.length);
			return;
		}
		var endGroup = this.rowGroups[endGroupIndex];
		var scaledBeginIndex = newGroups[0].scaledBeginIndex;
		var scaledEndIndex = newGroups[newGroups.length - 1].getScaledEndIndex();
		var groupsToDelete = new Array();
		BSDLogUtils.debug("splitGroups: " + beginGroupIndex + " " + endGroupIndex + " " + beginGroup);
		BSDLogUtils.debug("splitGroups: " + beginGroup.scaledBeginIndex + " " + endGroup.getScaledEndIndex() + " " + scaledBeginIndex + " " + scaledEndIndex);
		if(beginGroup.scaledBeginIndex != scaledBeginIndex) {
			var newBeginGroups = beginGroup.splitAtScaledIndex(scaledBeginIndex, 1);
			beginGroup = newBeginGroups.beginGroup;
			beginGroup.id = this.getNextGroupId();
			BSDArrayUtils.insert(newGroups, beginGroup, 0);
			BSDLogUtils.debug("Got beginSplit: " + beginGroup.debug());
			if(newBeginGroups.preSplit) {
				var preSplitGroup = newBeginGroups.preSplit;
				preSplitGroup.id = this.getNextGroupId();
				BSDArrayUtils.insert(newGroups, preSplitGroup, 1);
				BSDLogUtils.debug("Got preSplit: " + preSplitGroup.debug());
			}
		}// else, there's no need to split.  we'll just replace the existing begin group
		
		if(endGroup.getScaledEndIndex() != scaledEndIndex) {
			BSDLogUtils.debug("splitting endgroup: " + endGroup.scaledBeginIndex + " " + endGroup.getScaledEndIndex());
			var newEndGroups = endGroup.splitAtScaledIndex(scaledEndIndex + 1, endGroup.scalingFactor);
			BSDLogUtils.debug("split endgroup: " + newEndGroups.length);
			if(newEndGroups.postSplit) {
				var postSplitGroup = newEndGroups.postSplit;
				postSplitGroup.id = this.getNextGroupId();
				BSDArrayUtils.append(newGroups, postSplitGroup);
				BSDLogUtils.debug("Got postSplit: " + postSplitGroup.debug());								
			}
			var splitGroup = newEndGroups.splitGroup;
			if(splitGroup) {
				splitGroup.id = this.getNextGroupId();
				BSDArrayUtils.append(newGroups, splitGroup);
				BSDLogUtils.debug("Got endSplit: " + splitGroup.debug());
			}
		}// else, there's no need to split.  we'll just replace the existing end group





		var j = 0;
		for(var i = beginGroupIndex; (i <= endGroupIndex || j < newGroups.length); i++) {

			if(i <= endGroupIndex && j < newGroups.length) {

				BSDLogUtils.debug("swapping: " + i + " " + this.rowGroups.length + " " + j + " " + newGroups.length + " " + beginGroupIndex + " " + endGroupIndex);
				BSDLogUtils.debug("insertGroups: swapping " + i + ":<br/> " + this.rowGroups[i].debug() + "<br/>" + j + " " + newGroups[j].debug());
				BSDArrayUtils.replace(this.rowGroups, i, newGroups[j]);
			} else if(i <= endGroupIndex) {

				if(i < this.rowGroups.length) {
					BSDLogUtils.debug("insertGroups: deleting " + i + ":<br/> " + this.rowGroups[i].debug() + "<br/>" + j);
					BSDArrayUtils.deleteElement(this.rowGroups, i);
				}
			} else {



				BSDArrayUtils.insert(this.rowGroups, newGroups[j], i);
			}
			j++;
		}

		
	},
	
	createRowGroupsFromElements: function(scaledBeginIndex, scaledEndIndex, visibleBeginIndex, elements) {
		BSDLogUtils.debug("Creating rowGroups from elements: " + scaledBeginIndex + " " + scaledEndIndex + " " + visibleBeginIndex + " " + elements.length);
		var groups = new Array();
		var currentRowGroup;
		var previousRowHeight;
		var cumulativeHeight = 0;
		var j = visibleBeginIndex;  
		var scaledCount = scaledBeginIndex;
		for(var i = scaledBeginIndex; i <= scaledEndIndex && j < elements.length; i++) {
			var currentChild = elements[j];
			if(currentChild.nodeType != 1) {
				continue;
			}		
			var currentHeight = this.calculateRowHeight(currentChild);
			
			if(currentHeight) {
				previousRowHeight = currentHeight;
			} else {
				currentHeight = previousRowHeight;
			}
			var scalingFactor = currentChild.scaledRowCount;
			if(!scalingFactor) {
				scalingFactor = 1;
			}
			if(!currentHeight) {
				BSDLogUtils.error("Got element without height: " + currentChild.nodeName + " " + currentChild.id + " " + currentChild.className);
			}
			
			if(!currentRowGroup || (currentHeight && currentRowGroup.averageHeight != currentHeight) || currentRowGroup.scalingFactor != scalingFactor) {			
				BSDLogUtils.debug("Creating new group: " + j + " " + currentHeight + " " + " " + scalingFactor); // + "<br/>" + currentChild.innerHTML);
				if(currentRowGroup) {
					BSDLogUtils.debug("Creating new group scalingFactor: " + currentRowGroup.scalingFactor);
				}
				currentRowGroup = new BSDScrollableElementRowGroup(j, currentHeight, cumulativeHeight, scaledCount, scalingFactor, null, this.getNextGroupId(), this.scrollColumns);			
				BSDArrayUtils.append(groups, currentRowGroup);	
			} else {
				currentRowGroup.visibleEndIndex = j;
			}

			cumulativeHeight += currentHeight;
			currentRowGroup.cumulativeHeight = cumulativeHeight;
			j++; //can't use children.length because it would include elements of nodeType != 1
			scaledCount += scalingFactor;			
		}
		
		return groups;
	},
	
	calculateRowHeight: function(row) {
		var height = row.offsetHeight; 
		if(height && height > 0) {
			return height;
		}
		var strHeight = BSDDOMUtils.getAttributeValue(row, 'height');
		if(strHeight && strHeight.length > 0) {
			return parseInt(strHeight);
		}
		var rowChildren = row.childNodes;
		for(var i = 0; i < rowChildren.length; i++) {
			var currentRowChild = rowChildren[i];
			if(currentRowChild.nodeType != 1) {
				continue;
			} 
			height = currentRowChild.offsetHeight; 
			if(height && height > 0) {
				return height;
			}
		}
		return null;
	},
	
	fixRowGroupHeight: function(heightDifference, rowCount, rowGroups) {
		if(heightDifference < 1) {
			BSDLogUtils.debug("fixRowGroupHeight: Got < 1 heightDifference: " + heightDifference);
			return;
		} 
		if(!rowGroups) {
			BSDLogUtils.debug("Setting rowGroups");
			rowGroups = this.rowGroups;
		}
		if(!rowCount) {
			rowCount = 0;
			for(var i = 0; i < rowGroups.length; i++) {
				rowCount += rowGroups[i].getVisibleRowCount();
			}
		}

		BSDLogUtils.debug("fixRowGroupHeight: " + heightDifference + " " + rowCount + " " + rowGroups.length + " " + rowDifference);

		var remainingHeightDifference = heightDifference;
		var previousCumulativeHeight = 0;
		for(var i = 0; i < rowGroups.length; i++) {
			var currentRowGroup = rowGroups[i];
			var rowRatio = currentRowGroup.getVisibleRowCount() / rowCount;
			var rowGroupDifference = rowRatio * heightDifference;
			if(rowGroupDifference > remainingHeightDifference) {
				rowGroupDifference = remainingHeightDifference;
			}
			var rowDifference = rowGroupDifference / currentRowGroup.getVisibleRowCount();
			currentRowGroup.fixHeight(rowDifference, previousCumulativeHeight);
			previousCumulativeHeight = currentRowGroup.cumulativeHeight;			
			BSDLogUtils.debug("fixRowGroupHeight group " + i + ": " + rowDifference + " " + currentRowGroup.averageHeight + " " + currentRowGroup.previousCumulativeHeight + " " + currentRowGroup.cumulativeHeight);
			remainingHeightDifference -= rowGroupDifference;
		}
		
	},
	
	lastGroup: function() {
		return this.rowGroups[this.rowGroups.length - 1];
	},
	
	debug: function(label, groups) {
		if(!label) {
			label = "Group";
		}
		if(!groups) {
			groups = this.rowGroups;
		}
		var message = ""; //"Scroll: " + this.scrollHeight + " " + this.rowCount;
		for(var i = 0; i < groups.length; i++) {
			var currentGroup = groups[i];
			message += "<br/>" + label + ": " + i + " " + currentGroup.debug();
		}
		BSDLogUtils.debug(message);
	}


}

BSDTableRowRange = BSDClass.create();
BSDTableRowRange.prototype = {
	initialize: function(rowGroupList, maximumRowCount, visibleBeginIndex, visibleEndIndex, scaledBeginIndex, scaledEndIndex) {
		this.rowGroupList = rowGroupList;
		this.maximumRowCount = maximumRowCount;
		if(visibleBeginIndex) {
			this.visibleBeginIndex = visibleBeginIndex;
		} else {
			this.visibleBeginIndex = -1;
		}
		if(visibleEndIndex) {
			this.visibleEndIndex = visibleEndIndex;
		} else {
			this.visibleEndIndex = -1;
		}
		if(scaledBeginIndex) {
			this.scaledBeginIndex = scaledBeginIndex;
		} else {
			this.scaledBeginIndex = -1;
		}
		if(scaledEndIndex) {
			this.scaledEndIndex = scaledEndIndex;
		} else {
			this.scaledEndIndex = -1;
		}
	},
	
	getBeginIndex: function(adjustment) {
		if(!adjustment) {
			adjustment = 0;
		}
		return this.scaledBeginIndex * this.rowGroupList.scrollColumns + adjustment;
	},

	getEndIndex: function(adjustment) {
		if(!adjustment) {
			adjustment = 0;
		}
		return (this.scaledEndIndex + adjustment) * this.rowGroupList.scrollColumns;
	},
		
	clone: function() {
		var newRange = new BSDTableRowRange(this.rowGroupList, this.maximumRowCount);
		newRange.visibleBeginIndex = this.visibleBeginIndex;
		newRange.visibleEndIndex = this.visibleEndIndex;
		newRange.scaledBeginIndex = this.scaledBeginIndex;
		newRange.scaledEndIndex = this.scaledEndIndex;
		newRange.beginGroup = this.beginGroup;
		newRange.endGroup = this.endGroup;		
		return newRange;
	},

	setVisibleCenteredRowCount: function(newRowCount) {
		if(this.visibleBeginIndex > -1 && this.visibleEndIndex) {
			var range = this.setCenteredRowCountInternal(newRowCount, this.getVisibleRowCount(), this.visibleBeginIndex, this.visibleEndIndex);
			this.visibleBeginIndex = range.beginIndex;
			this.visibleEndIndex = range.endIndex;
			this.resetScaledIndexes();
		}	
	},
	
	setScaledCenteredRowCount: function(newRowCount) {
		if(this.scaledBeginIndex > -1 && this.scaledEndIndex) {
			var range = this.setCenteredRowCountInternal(newRowCount, this.getScaledRowCount(), this.scaledBeginIndex, this.scaledEndIndex);
			this.scaledBeginIndex = range.beginIndex;
			this.scaledEndIndex = range.endIndex;
			this.resetVisibleIndexes();
		}
	},
	
	setCenteredRowCountInternal: function(newRowCount, currentRowCount, beginIndex, endIndex) {

		var rowDifference = newRowCount - currentRowCount; 
		if(rowDifference <= 0 || (this.beginIndexFixed && this.endIndexFixed)) {
			return;
		}
		var rowDifferenceMod = rowDifference % 2;
		var leftRowDifference = (rowDifference - rowDifferenceMod) / 2;
		var rightRowDifference = leftRowDifference + rowDifferenceMod;
		var newBeginIndex = beginIndex - leftRowDifference;
		var newEndIndex = endIndex + rightRowDifference;
		if(this.beginIndexFixed) {
			rightRowDifference += leftRowDifference;
		} else if(newBeginIndex < 0) {
			rightRowDifference += -newBeginIndex;
			newBeginIndex = 0;
		}
		newEndIndex = endIndex + rightRowDifference;

		
		if(this.endIndexFixed) {
			leftRowDifference += rightRowDifference;
		} else if(newEndIndex > this.maximumRowCount - 1) {
			leftRowDifference += newEndIndex - (this.maximumRowCount - 1);
			newEndIndex = this.maximumRowCount - 1;
		}
		newBeginIndex = beginIndex - leftRowDifference;

		if(newBeginIndex < 0) {
			newBeginIndex = 0;
		}
		
		if(!this.beginIndexFixed) {
			beginIndex = newBeginIndex;
		}
		if(!this.endIndexFixed) {
			endIndex = newEndIndex; 
		}

		var range = new Object();
		range.beginIndex = beginIndex;
		range.endIndex = endIndex;

		return range;
	},
	
	getScaledRowCount: function() {
		return this.scaledEndIndex - this.scaledBeginIndex + 1;
	},
	
	getVisibleRowCount: function() {
		return this.visibleEndIndex - this.visibleBeginIndex + 1;
	},
	
	resetVisibleIndexes: function() {
		this.visibleBeginIndex = this.rowGroupList.getVisibleIndexByScaledIndex(this.scaledBeginIndex, this.beginGroup);
		this.visibleEndIndex = this.rowGroupList.getVisibleIndexByScaledIndex(this.scaledEndIndex, this.endGroup);
	},

	resetScaledIndexes: function() {
		this.scaledBeginIndex = this.rowGroupList.getScaledIndexByVisibleIndex(this.visibleBeginIndex, this.beginGroup);
		this.scaledEndIndex = this.rowGroupList.getScaledEndIndexByVisibleIndex(this.visibleEndIndex, this.endGroup);
	}
		
	
	
}


BSDPopupWindow = BSDClass.create();
BSDPopupWindow.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDEventUtils", "BSDLocationUtils", "drag/BSDDragUtils", "table/BSDDynamicTableUtils", "table/BSDDynamicTable", "table/BSDScrollableElement");
BSDPopupWindow.prototype = {

	className: "BSDPopupWindow",
	initialize: function(popupElement) {
		if(!popupElement) {
			BSDLogUtils.error("Couldn't find popupElement for popupWindow");
			return;
		}
		if(!popupElement.className != "BSDDialogWindow") {
			var elements = BSDDOMUtils.getObjectsByClass("BSDDialogWindow", popupElement, null, "BSDDialogWindow");
			if(elements.length > 0) {
				popupElement = elements[0];
			}
		}
		if(!popupElement) {
			BSDLogUtils.error("Couldn't find popupElement for popupWindow");
			return;
		}
	    this.popupElement = popupElement;
	    this.popupHeader = this.getPopupHeader(popupElement);
	    this.popupFooter = this.getPopupFooter(popupElement);
	    this.popupContent = this.getPopupContent(popupElement);
	    this.popupContentScroll = this.getPopupContentScroll(popupElement);

	    if(this.popupContentScroll) {
	    	this.popupContentScrollHeight = this.popupContentScroll.offsetHeight;	   
	    	this.popupContentScrollWidth = this.popupContentScroll.offsetWidth;	   
	    	this.staticContentHeight = this.popupElement.offsetHeight - this.popupContentScrollHeight;
	    	this.staticContentWidth = this.popupElement.offsetWidth - this.popupContentScrollWidth;
	    }
	    this.popupResizeTool = this.getPopupResizeTool(popupElement);	    

	    this.fixPopupContentDimensions();
	    BSDLocationUtils.positionElementWithinWindow(popupElement, false, 70);
		this.fixPopupContentDimensions();

	    this.initializePopupEvents();
	    
	    BSDLocationUtils.positionElementWithinWindow(this.popupElement, true);

   	},

	doPostInitialization: function() {
	    if(this.popupContentScroll && !this.dynamicTable) {
	    	this.dynamicTable = new BSDDynamicTable(this.popupElement.id, this.popupContent);
	    }
		if(this.popupContentScroll) {
			BSDDynamicTableUtils.printScaledVisibleRange(this.popupContentScroll);	    
		}
	},


	fixPopupContentDimensions: function() {
		if(this.staticContentHeight) {

		} else {

		}
		return true;		
	},
	
	fixPopupDynamicContentDimensions: function() {
		var containerHeight = this.popupContent.offsetHeight;
		var childHeight = 0;
		for(var i = 0; i < this.popupContent.childNodes.length; i++) {
			var currentChild = this.popupContent.childNodes[i];
			var currentChildHeight = currentChild.offsetHeight;
			if(currentChildHeight && currentChildHeight > 0) {
				childHeight += currentChildHeight;
			}
		}
		var windowHeight = this.popupElement.offsetHeight;
		
		var headerHeight = 0;
		var strHeaderHeight = BSDDOMUtils.getElementStyle(this.popupHeader, "height");
		if(strHeaderHeight && strHeaderHeight != "0px") {
			headerHeight = parseInt(strHeaderHeight);
			headerHeight += 3; //adjustment for firefox
		} else {
			for(var i = 0; i < this.popupHeader.childNodes.length; i++) {
				var currentChild = this.popupHeader.childNodes[i];
				var currentChildHeight = currentChild.offsetHeight;
				if(currentChildHeight && currentChildHeight > 0) {
					headerHeight += currentChildHeight;
				}
			}
		}
		var footerHeight = 0;
		var strFooterHeight = BSDDOMUtils.getElementStyle(this.popupFooter, "height");
		if(strFooterHeight && strFooterHeight != "0px") {
			footerHeight = parseInt(strFooterHeight);
			footerHeight += 3; //adjustment for firefox
		} else {
			for(var i = 0; i < this.popupFooter.childNodes.length; i++) {
				var currentChild = this.popupFooter.childNodes[i];
				var currentChildHeight = currentChild.offsetHeight;
				if(currentChildHeight && currentChildHeight > 0) {
					footerHeight += currentChildHeight;
				}
			}
		}

		var heightDifference = windowHeight - headerHeight - footerHeight - containerHeight;
		if(heightDifference > 0) {

			BSDDOMUtils.changeElementStyle(this.popupElement, 'height', windowHeight - heightDifference); 
		}	
	},
   	
   	fixPopupStaticContentDimensions: function() {
		var strMinWidth = BSDDOMUtils.getElementStyle(this.popupContentScroll, "min-width");
		var minWidth = 0;
		if(strMinWidth) {
			minWidth = parseInt(strMinWidth);
		}
		var strMinHeight = BSDDOMUtils.getElementStyle(this.popupContentScroll, "min-height");
		var minHeight = 0;
		if(strMinHeight) {
			minHeight = parseInt(strMinHeight);
		}

	    var popupHeight = parseInt(BSDDOMUtils.getElementStyle(this.popupElement, "height")); 
	    var popupWidth = parseInt(BSDDOMUtils.getElementStyle(this.popupElement, "width")); 
		var newContentHeight = popupHeight - this.staticContentHeight;
		var newContentWidth = popupWidth - this.staticContentWidth;

		var isBelowMin = false;
		if(newContentWidth < minWidth) {

			var windowWidth = this.staticContentWidth + minWidth; // + 1;
			BSDDOMUtils.changeElementStyle(this.popupElement, 'width', windowWidth); 
			isBelowMin = true;
			newContentWidth = minWidth;
		}
		if(newContentHeight < minHeight) {
			var windowHeight = this.staticContentHeight + minHeight; // + 1;
			BSDDOMUtils.changeElementStyle(this.popupElement, 'height', windowHeight); 
			isBelowMin = true;	
			newContentHeight = minHeight;	
		}
		
		if(this.popupContentScroll) {

			BSDDOMUtils.changeElementStyle(this.popupContentScroll, 'height', newContentHeight); //this is necessary for IE to get scroll bars			
			BSDDOMUtils.changeElementStyle(this.popupContentScroll, 'width', newContentWidth);
		}	
		
		return !isBelowMin;	   	
   	},
   	
    toString: function() {
		var str = "[" + "PopupWindow" + "]";
		return str;
    },

	getPopupHeader: function(popupElement) {
		var elements = BSDDOMUtils.getObjectsByClass("BSDDialogWindowHeader", popupElement, null, "BSDDialogWindowContent");
		if(elements.length > 0) {
			return elements[0];
		}
		return null;	
	},
	
	getPopupFooter: function(popupElement) {
		var elements = BSDDOMUtils.getObjectsByClass("BSDDialogWindowFooter", popupElement, null, "BSDDialogWindowContent");
		if(elements.length > 0) {
			return elements[0];
		}
		return null;	
	},	

	getPopupContent: function(popupElement) {
		return BSDDOMUtils.getObjectByIdFromParent(popupElement, "BSDDialogWindowContent");	
	},

	
	getPopupContentScroll: function(popupElement) {
		var elements = BSDDOMUtils.getObjectsByClass("BSDDialogScrollArea", popupElement, null, "BSDDialogScrollArea");
		if(elements.length > 0) {
			return elements[0];
		}
		elements = BSDDOMUtils.getObjectsByClass("BSDDynamicTable", popupElement, null, "BSDDynamicTable");
		if(elements.length > 0) {
			return elements[0];
		}
		return null;	
	},
		
	getPopupResizeTool: function(popupElement) {
		var elements = BSDDOMUtils.getObjectsByClass("BSDDialogWindowResizeTool", popupElement, null, "BSDDialogWindowContent");
		if(elements.length > 0) {
			return elements[0];
		}
		return null;	
	},

	
	initializePopupEvents: function() {
		if(this.popupHeader) {
		    BSDEventUtils.registerEvent(this.popupHeader, "mousedown", this.handleMove);	
		}
		if(this.popupFooter) {

		}
		if(this.popupResizeTool) {
			var popupWindow = this;
			function handleResize(event) {
				var popupElement = BSDPopupWindow.prototype.getPopupElementFromEvent(event);
				if(!popupElement) {
				alert("Doing resize");
					BSDLogUtils.debug("Couldn't find popupWindow element from event");
					return;
				}
			
				var validationActions = new Array();
				validationActions[0] = new BSDPopupWindow.prototype.ResizeCompletionAction(popupWindow);
				BSDDragUtils.beginDrag(popupElement, event, 'resize', null, validationActions);	
			}
			BSDEventUtils.registerEvent(this.popupResizeTool, "mousedown", handleResize);
		}
		
		if(this.popupContentScroll) {

			var popupWindow = this;
			function handleScroll(event) {
				BSDDynamicTableUtils.printScaledVisibleRange(popupWindow.popupContentScroll);
			}
			BSDEventUtils.registerEvent(this.popupContentScroll, "scroll", handleScroll);
		
		}
		
		var buttons = BSDDOMUtils.getObjectsByClass('BSDDialogButtonLarge', this.popupElement, null, "BSDDialogWindowContent");
		for(var i = 0; i < buttons.length; i++) {
			var currentButton = buttons[i];
			BSDEventUtils.registerEvent(currentButton, "mousedown", this.handleButtonMousedown);
			BSDEventUtils.registerEvent(currentButton, "mouseup", this.handleButtonMouseup);
			BSDEventUtils.registerEvent(currentButton, "mouseout", this.handleButtonMouseup);
		}
		
	},
	
	handleButtonMousedown: function(event) {
		BSDEventUtils.fixEventTarget(event);
		var target = event.target;	
		while(target && target.className != "BSDDialogButtonLarge") {
			target = target.parentNode;
		}
		if(target) {

			target.className = "BSDDialogButtonLargeClick";		
			BSDEventUtils.stopPropagation(event);
			return false;
		}	
	},

	handleButtonMouseup: function(event) {
		BSDEventUtils.fixEventTarget(event);
		var target = event.target;	
		while(target && target.className != "BSDDialogButtonLargeClick") {
			target = target.parentNode;
		}
		if(target) {

			target.className = "BSDDialogButtonLarge";		
			BSDEventUtils.stopPropagation(event);
			return false;
		}
	},
		
	getPopupElementFromEvent: function(event) {
		BSDEventUtils.fixEventTarget(event);
		var target = event.target;
		while(target && target.className != "BSDDialogWindow") {
			target = target.parentNode;
		}
		return target;
	},
	
	handleMove: function(event) {
		var popupElement = BSDPopupWindow.prototype.getPopupElementFromEvent(event);
		if(!popupElement) {
			BSDLogUtils.debug("Couldn't find popupWindow element from event");
			return;
		}


		BSDDragUtils.beginDrag(popupElement, event, 'move');
	},

	ResizeCompletionAction: function(popupWindow) {
		this.popupWindow = popupWindow;
		this.executeDragAction = function(dragProperties) {
			return this.popupWindow.fixPopupContentDimensions();
		}
		
	},
	
	close: function(e) {
		if(this.closeCallback) {
			this.closeCallback.call(this, e);
		}
	}
	
	
    
}






BSDPopupUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "BSDLocationUtils", "BSDEventUtils", "BSDArrayUtils", "content/BSDContentUtils", "popup/BSDPopupPosition", "BSDPopupWindow", "BSDTypeUtils"),
	
	OPEN_POPUPS: new Array(),
	ALL_POPUPS: {},
	ALL_POPUP_OBJECTS: {},
	
	launchPopup: function(popupId, clickElement, position, level, retainWidth, dynamicUrl, dynamicArguments, clickFunction) {

		var popupElement = BSDDOMUtils.getObjectById(popupId);
		if(!popupElement) {
			BSDLogUtils.warning("WARNING: Couldn't find popup element with id: " + popupId);
			return true;
		}
		
		if(dynamicUrl && dynamicUrl.length > 0) {
			return BSDPopupUtils.launchDynamicPopup(popupElement, clickElement, position, level, retainWidth, dynamicUrl, dynamicArguments, clickFunction);
		} else {
			return BSDPopupUtils.launchPopupByElement(popupElement, clickElement, position, level, retainWidth);
		}
		
		return false; //keeps firefox from scrolling the page
	},

	launchDynamicPopup: function(popupElement, clickElement, position, level, retainWidth, dynamicUrl, dynamicArguments, clickFunction, launchCallback) {
		var contentElement = BSDDOMUtils.getObjectByIdFromParent(popupElement, 'POPUP_CONTENT');

		if(!contentElement) {
			BSDLogUtils.warning("WARNING: Couldn't find POPUP_CONTENT element for popup with id: " + popupElement.id);
			return true;		
		}
		
		BSDAjaxUtils.showActivityMessage('Loading');
		
		popupElement.isDynamic = true;
		function popupCallback(data, message, isError) {
	    	if(isError) {
	    	    if(message) {
	    	    	BSDDOMUtils.setText(contentElement, message);
	    	    } else {
			    	BSDDOMUtils.setText("Couldn't load page " + dynamicUrl);
			    }
			    return true;
	    	}

		    var popupObject = BSDPopupUtils.launchPopupByElementInternal(popupElement, clickElement, position, level, retainWidth);			
		    BSDAjaxUtils.hideActivityMessage();
		    
		    if(clickFunction && BSDTypeUtils.isString(clickFunction)) {
		    	clickFunction = clickFunction.replace("BSDPopupUtils.closeCurrentWindow()", "BSDPopupUtils.closePopupByElement(popupElement)");
		    	function clickHandler(e) {
		    		BSDEventUtils.fixEventTarget(e);
		    		eval(clickFunction);
		    		BSDEventUtils.stopPropagation(e);
		    	}
				BSDEventUtils.registerEvent(contentElement, "click", clickHandler);
		    } else if(clickFunction) {
				BSDEventUtils.registerEvent(contentElement, "click", clickFunction);
		    }
		    if(launchCallback && launchCallback.apply) {
		    	launchCallback.call(this, popupObject);
		    }
	    }

	    var pageArguments;
	    if(dynamicArguments) {
			pageArguments = dynamicArguments;
		} else {
			pageArguments = {};			
		}
	    
		var pageID = '/ajaxpages/pagerender';
		
		BSDContentUtils.doRenderingByParentElement(null, contentElement, pageArguments, pageID, popupCallback, dynamicUrl, "Loading...");
		return false;
	},

	
	launchMouseoverPopup: function(popupId, parentElement, triggerElement, orientation, deltaX, deltaY) {
		var position = new Object();
		position.orientation = orientation;
		position.deltaX = deltaX;
		position.deltaY = deltaY;
		
		var popupElement = BSDDOMUtils.getObjectById(popupId);
		if(!parentElement && popupElement) {
			parentElement = popupElement.parentNode;
		}
		if(!triggerElement) {
			triggerElement = parentElement;
		}

		var failure = BSDPopupUtils.launchPopup(popupId, parentElement, position, null, true);	
		if(!failure && triggerElement && !popupElement.isCloseEventRegistered) {
			function popupClose(e) {
				var triggerPosition = new BSDElementPosition(triggerElement);
				var popupPosition = new BSDElementPosition(popupElement);
				var eventPosition = BSDLocationUtils.getEventPosition(e);
				if(!triggerPosition.containsPosition(eventPosition) && !popupPosition.containsPosition(eventPosition)) {
					BSDVisibilityUtils.hideObject(popupElement);
				}				
			}
			BSDEventUtils.registerEvent(triggerElement, "mouseout", popupClose);
			BSDEventUtils.registerEvent(popupElement, "mouseout", popupClose);
			BSDEventUtils.registerEvent(document.body, "click", popupClose);
			popupElement.isCloseEventRegistered = true;
		}
		
		BSDVisibilityUtils.showObject(popupElement);
	},

	launchPopupByElement: function(popupElement, clickElement, position, level, retainWidth) {
		var popupObject = BSDPopupUtils.launchPopupByElementInternal(popupElement, clickElement, position, level, retainWidth);
		if(popupObject) {
			return false;
		} else {
			return true;
		}
	},
	
	launchPopupByElementInternal: function(popupElement, clickElement, position, level, retainWidth) {


		var popupObject = BSDPopupUtils.initializePopup(popupElement);
		popupElement.level = level; //level is used to determine which popups should be closed when this one is open (upper or equal == close, otherwise leave open)


		
		if(popupElement.isDynamic) {
			BSDPopupUtils.movePopupChildren(popupElement);
			var customButtonRows = BSDDOMUtils.getObjectsByClass('POPUP_CUSTOM_BUTTON_ROW', popupElement);
			if(customButtonRows.length > 0) {
				var standardRow = BSDDOMUtils.getObjectByIdFromParent(popupElement, 'POPUP_BUTTON_ROW');
				if(standardRow) {
					BSDDOMUtils.removeElement(standardRow);
				}
			}
		}

			BSDLocationUtils.makeElementAbsolutelyPositioned(popupElement, clickElement, retainWidth);

		
		if(position && position.orientation == 'parent' && popupElement.parentPopupId) {
			var absoluteParent = BSDPopupUtils.ALL_POPUPS[popupElement.parentPopupId];
			if(absoluteParent) {
				BSDLocationUtils.cloneElementLocation(absoluteParent, popupElement);

			}
		}
	
		BSDPopupUtils.closeOpenPopups(popupElement);
		if(BSDPopupUtils.OPEN_POPUPS.length < 1) {
			BSDPopupUtils.initializeDocumentEvents(popupElement);
		} 

		BSDVisibilityUtils.showObject(popupElement);

			BSDPopupUtils.setPopupOrientation(popupElement, position);
			BSDLocationUtils.positionElementWithinWindow(popupElement, true);
			BSDPopupUtils.adjustPopupPosition(popupElement, position);  //intentionally positioned after withinWindow check to allow it to be overridden
			popupElement.isPositioned = true;

		
		if(popupObject) {
			popupObject.doPostInitialization();
		}
		
		BSDArrayUtils.append(BSDPopupUtils.OPEN_POPUPS, popupElement);
		BSDPopupUtils.ALL_POPUPS[popupElement.id] = popupElement;
		BSDPopupUtils.ALL_POPUP_OBJECTS[popupElement.id] = popupObject;

		BSDLogUtils.debug("Launched popup " + popupElement.id + " " + new BSDElementPosition(popupElement) + " " + BSDVisibilityUtils.isObjectHidden(popupElement));
		
		BSDPopupUtils.debugPopupParents(popupElement);
		
		if(popupElement.bsdMap && popupElement.bsdMap && popupElement.bsdMap.checkResize) {
			popupElement.bsdMap.checkResize();
		}
		
		return popupObject;
	},
	
	debugPopupParents: function(popupElement) {
		var parent = popupElement.parentNode;
		while(parent) {

			parent = parent.parentNode;
		}
	},
	
	getAbsolutelyPositionedParent: function(popupElement) {
		var parentNode = popupElement.parentNode;
		while(parentNode) {
			if(BSDLocationUtils.getIsAbolutelyPositioned(parentNode)) {
				return parentNode;
			}
			parentNode = parentNode.parentNode;
		}		
	},
	
	setPopupOrientation: function(popupElement, position) {
		if(position && position.orientation) {
			BSDLocationUtils.setElementOrientation(popupElement, position.orientation);
		}	
	
	},
	
	adjustPopupPosition: function(popupElement, position) {
		if(position && (position.deltaX || position.deltaY)) { 
			var point = new Object();
			if(position.deltaX) {
				point.x = position.deltaX;
			}
			if(position.deltaY) {
				point.y = position.deltaY;
			}
			BSDLocationUtils.adjustElementLocation(popupElement, point);
		} else if(position && (position.x || position.y)) { 
			BSDLocationUtils.setElementLocation(popupElement, position);
		} 
		 
	},
	
	closeOpenPopups: function(newPopup, e, doCompletion) {
		BSDLogUtils.debug("Closing open popups: " + (newPopup != null));
		var popupsToClose = new Array();
		var maxOpenLevel;
		if(!newPopup) {
		    for(var i = 0; i < BSDPopupUtils.OPEN_POPUPS.length; i++) {
				var currentPopup = BSDPopupUtils.OPEN_POPUPS[i];
				if(!maxOpenLevel || currentPopup.level > maxOpenLevel) {
					maxOpenLevel = currentPopup.level;
				}
			}
		}
	    for(var i = 0; i < BSDPopupUtils.OPEN_POPUPS.length; i++) {
			var currentPopup = BSDPopupUtils.OPEN_POPUPS[i];
			var parent;
			if(newPopup) {
				parent = BSDPopupUtils.getParentPopup(newPopup);
			}
			if(newPopup && parent) {
				BSDPopupUtils.detachChild(newPopup, parent);
			}
			BSDLogUtils.debug("Trying close: " + maxOpenLevel + " " + currentPopup.level);
			if((newPopup && currentPopup.level >= newPopup.level) || currentPopup.level >= maxOpenLevel) {

				BSDArrayUtils.append(popupsToClose, currentPopup);
			}
			BSDLogUtils.debug("Tried close");
		}
		BSDLogUtils.debug("Closing popups " + popupsToClose.length);
		for(var i = 0; i < popupsToClose.length; i++) {
			BSDLogUtils.debug("Closing open popup: " + popupsToClose[i].id);
			BSDPopupUtils.closePopupByElement(popupsToClose[i], e, doCompletion);
		}
	},
	
	closePopupByElement: function(popupElement, e, doCompletion) {
				
		if(doCompletion && popupElement.completeFunction) {
			var completeValue = popupElement.completeFunction.call();
			if(!completeValue) {
				return;
			}
		}		

		BSDVisibilityUtils.hideObject(popupElement, popupElement.ignoreHideDisplay);

		for(var i = 0; i < BSDPopupUtils.OPEN_POPUPS.length; i++) {
			var currentPopup = BSDPopupUtils.OPEN_POPUPS[i];
			if(currentPopup == popupElement) {
				if((currentPopup.isDynamic)) {
					var content = BSDDOMUtils.getObjectByIdFromParent(currentPopup, 'POPUP_CONTENT'); //only remove the content, since we need the outer part to plug the content back into later
					if(content) {
						content.innerHTML = null;
					}
				}
				BSDArrayUtils.deleteElement(BSDPopupUtils.OPEN_POPUPS, i);
				break;	
			}
		}
		
		if(BSDPopupUtils.OPEN_POPUPS.length < 1) {
			BSDPopupUtils.removeDocumentEvents();
		}
		
		var popupObject = BSDPopupUtils.ALL_POPUP_OBJECTS[popupElement.id];
		if(popupObject) {
			popupObject.close(e);
		}
		
		if(popupElement.closeFunction) {
			popupElement.closeFunction.call();
		}
	},
	
	handleKeyPress: function(e) {
		var valid = false; ; //enter or escape
		var doComplete = false;
		if(BSDEventUtils.handleKeyPress(e, 13)) {
			if(!BSDPopupUtils.isPopupCloseOnEnterDisabled(e)) {
				doComplete = true;
				valid = true;
			}
		} else if(BSDEventUtils.handleKeyPress(e, 27)) {
			valid = true;
		}
		if(valid) {
			BSDPopupUtils.closeOpenPopups(null, e, doComplete);
			return false;
		}
		return true;
	},
	
	handleEscape: function(e) {
		var valid = false; ; //enter or escape
		var doComplete = false;
		if(BSDEventUtils.handleKeyPress(e, 27)) {
			valid = true;
		}
		if(valid) {
			BSDPopupUtils.closeOpenPopups(null, e, doComplete);
			return false;
		}
		return true;
	},
	
	isPopupCloseOnEnterDisabled: function(e) {
		var node = BSDEventUtils.fixEventTarget(e);
		while(node) {
			if(BSDDOMUtils.containsClass(node, 'POPUP_FORM')) {
				return true;
			} else if (BSDDOMUtils.containsClass(node, 'STATIC_POPUP_WINDOW')) {
			    return false;
			} else if(node.bsdPopupCloseOnEnterDisabled) {
				return true;
			}
			node = node.parentNode;
		}
	},
	
	initializePopup: function(popupElement) {
		
		function closeButtonClick(e) {

			BSDPopupUtils.closeOpenPopups(popupElement, e, false);
			return false;
		}
		
		function completeButtonClick(e) {

			BSDPopupUtils.closeOpenPopups(popupElement, e, true);
			return false;
		}
		
		var closeButtons = BSDDOMUtils.getObjectsByClass('popupCloseButton', popupElement);
		for(var i = 0; i < closeButtons.length; i++) {
			var currentButton = closeButtons[i];
			if(BSDPopupUtils.getParentPopup(currentButton, popupElement)) {
				BSDLogUtils.debug("Adding close button event: " + popupElement.id + " " + currentButton.id + " " + currentButton.className);
				BSDEventUtils.registerEvent(currentButton, 'click', closeButtonClick);
			}
		}

		var completeButtons = BSDDOMUtils.getObjectsByClass('popupCompleteButton', popupElement);
		for(var i = 0; i < completeButtons.length; i++) {
			var currentButton = completeButtons[i];
			if(BSDPopupUtils.getParentPopup(currentButton, popupElement)) {
				BSDLogUtils.debug("Adding complete button event: " + popupElement.id + " " + currentButton.id + " " + currentButton.className);
				BSDEventUtils.registerEvent(currentButton, 'click', completeButtonClick);
			}
		}
		
		var popup = new BSDPopupWindow(popupElement);
		popupElement.bsdPopupInitialized = true;	
		return popup;
	},
	
	initializeDocumentEvents: function() {
		BSDEventUtils.registerEvent(document, 'keypress', BSDPopupUtils.handleKeyPress);
	},
	
	removeDocumentEvents: function() {
		BSDEventUtils.removeEvent(document, 'keypress', BSDPopupUtils.handleKeyPress);
	},
	
	getParentPopup: function(childElement, popupElement) {
		var parent = childElement.parentNode;
		while(parent) {

			if(popupElement && parent == popupElement) {
				return parent;
			} else if(BSDPopupUtils.isPopupWindow(parent)) {
				if(popupElement) {
					return; //logic: if parent is passed, return false since this isn't a part of the parent.  else true as it is part of a window
				} else {
					return parent;
				}
			}
			parent = parent.parentNode;
		}
		return;
	},
	
	isPopupWindow: function(element) {
		if(!element.id || !element.className) {
			return false;
		}
		return BSDDOMUtils.containsClass(element, 'POPUP_WINDOW')
			    || BSDDOMUtils.containsClass(element, 'STATIC_POPUP_WINDOW') 
			    || BSDDOMUtils.containsClass(element, 'DYNAMIC_POPUP_WINDOW');
	},
	
	movePopupChildren: function(popupElement, currentElement) {
		if(currentElement && BSDPopupUtils.isPopupWindow(currentElement)) {
			BSDPopupUtils.detachChild(currentElement, popupElement);
		} else if(!currentElement) {
			currentElement = popupElement;
		}
		
		var childNodes = currentElement.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			BSDPopupUtils.movePopupChildren(popupElement, childNodes[i]);
		}
			
	},
	
	detachChild: function(currentElement, parent) {
		var existingWindow = BSDPopupUtils.ALL_POPUPS[currentElement.id];

		if(existingWindow && existingWindow != currentElement) {

			BSDDOMUtils.replaceElement(existingWindow, currentElement); //window already exists - replace it
		} else {
			if(!currentElement.isDynamic) {
				currentElement.isDynamic = parent.isDynamic;
			}		
			currentElement.parentPopupId = parent.id;

			BSDLogUtils.debug("Moving child element: " + currentElement.id);
			BSDDOMUtils.moveElement(currentElement, document.body); //can't have popups stay nested			
		}

	}
	
	
}




BSDShareUtils = {
	DEPENDENCIES: new Array("popup/BSDPopupUtils"),
	
	completeEmailSend: function() {
		BSDPopupUtils.closeOpenPopups();
		
		var emailForm = document.forms['shareForm'];
		if(emailForm && emailForm.reset) {
			emailForm.reset();
		}
		
	}


}
