
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';
	}
	
}

var bsdObjectsByClassHash;

BSDDOMUtils = {
	DEPENDENCIES: new Array("BSDStringUtils", "BSDTypeUtils"),
	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();



		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			var isBr = currentChild.nodeName == 'BR';
			var isDeleted = false;
			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);
				isDeleted = true;
				if(parent.parentNode) {
					BSDDOMUtils.insertAfter(parent, currentParent);					
				}
			} else if(currentChild.nodeType == 1) {
				BSDDOMUtils.convertDoubleBrToP(currentChild, doc, null, depth + 1);
			}
			if(!isBr && currentParent && currentParent != parent) {
				if(!isBr && prevBr) {
					BSDArrayUtils.append(moveArray, prevBr);
					BSDArrayUtils.append(moveParentArray, currentParent);
				}
				BSDArrayUtils.append(moveArray, currentChild);
				BSDArrayUtils.append(moveParentArray, currentParent);
			}
			if(!isBr) {
				prevBr = null;				
			}

			
		}	
		
		for(var i = 0; i < moveArray.length; i++) {
			var currentChild = moveArray[i];
			var currentParent = moveParentArray[i];

			BSDDOMUtils.removeElement(currentChild);
			currentParent.appendChild(currentChild);
			if(!parent.parentNode) {
				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;
    }
    
    
}
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;
	}
	

	
	
}
BSDVisibilityUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils"),
	VERSION: 1.0,
		
	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) {
		var 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;
	}
	
	
	
}
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;
	}

}


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);
    }
    
}
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 += ", ";
			}
			var isString = typeof(argsArray[i]) == 'string';
			if(isString) {
				argsString += "'";
			}
			argsString += argsArray[i];
			if(isString) {
				argsString += "'";
			}
		}	
		argsString += ")";
		return argsString;
	}
	
	
}
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 = "B769CF83EC9A140F46E8B01C75972B34";

/** 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);
		}
	}
	
}
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;
    }
}
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;
	},
	
	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);
	}
	
			
	
	
}	
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;
	}
	
	
	
}


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);
			}
		}
	}


}
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;
	},
	
	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();
		}
		
	}


}
