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

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

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

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

BSDBrowserUtils = {
	DEPENDENCIES: new Array(),

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

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

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

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

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

	getAttributeValue: function(element, attributeName) {

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

		}	



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

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

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

		}		
	},

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

		}		
	},

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

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

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

			return element.parentNode;
		} else {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


		return margin;
	},

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

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

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

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

	},

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

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

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

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

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

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

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

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

			var currentElement = parentElement.childNodes[i];

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

		}
	

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

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

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

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

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

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

		if(tbody && removeExisting) {

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

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

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

			return null;
		}

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

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


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

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

			BSDDOMUtils.removeElement(range.startContainer);

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

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




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



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

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

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

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

					hadParent = true;
				}

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


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

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

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

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

			
		}	

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


			BSDDOMUtils.replaceElement(currentChild, currentParent);


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

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

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


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

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

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

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

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

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

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

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

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

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

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

	}
	
}

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


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

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

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

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

}

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




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

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


	var SimpleDateFormat;

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

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

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

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

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

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

		Date.prototype.getPreviousSunday = function() {

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

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

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

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

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

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

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

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

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

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

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

			return padWithZeroes(dataString, numberOfLetters);
		};

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

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

				} else if (otherCharacters) {

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

}


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


		}

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

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

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

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

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

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

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

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

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

}

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 != "(") {
				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 = "F003CD10443981D189B5CA3994598630";

/** 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);
		}
	}
	
}
BSDWysiwygUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils"), 


	setTextareaToTinyMCE: function(objectId) {

		if(typeof(tinyMCE) == 'undefined' ) {
			BSDLogUtils.debug("Couldn't find tinyMCE for setTextareToTinyMCE");
			return;
		}
		var txt = BSDDOMUtils.getObjectById(objectId);
		if(txt) {
			txt.value = BSDStringUtils.lbToBR(txt.value);


			if(tinyMCE.isGecko || tinyMCE.isMSIE5 || tinyMCE.isSafari) {

				tinyMCE.execCommand('mceAddControl', true, objectId);
			}
		}
	},
	
	switchTinyMCEToTextarea: function(objectId) {
		if(typeof(tinyMCE) == 'undefined' ) {
			BSDLogUtils.debug("Couldn't find tinyMCE for setTextareToTinyMCE");
			return;
		}
		var editor = BSDDOMUtils.getObjectById(objectId);
		if(editor) {
			tinyMCE.execCommand('mceRemoveControl', true, objectId);
			editor.value = BSDStringUtils.brToLB(editor.value);
		}
	}
	
}

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;
	}
	
	
	
}
BSDDragProperties = BSDClass.create();
BSDDragProperties.DEPENDENCIES = new Array("drag/BSDDragUtils", "BSDDOMUtils", "BSDLocationUtils");
BSDDragProperties.prototype = {

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

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



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

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

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

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


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

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

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

		if(BSDDragUtils.getShouldIgnoreDragEvent(event)) {


	    	return false;
	    }





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

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

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

		BSDEventUtils.stopPropagation(event);


		function moveHandler(e) {

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

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

					dragProperties.isValidMove = false;
		        } else {

		        }
		   	}


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

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


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


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

		    }
		
		}
		
		function upHandler(e) {

			BSDEventUtils.fixEventTarget(e);

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

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

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


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

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

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

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

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

		elementToDrag = BSDDragUtils.getDragEnabledElement(elementToDrag);

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

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

		

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

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

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

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

	fixDropdragElementPosition: function(element) {	

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

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

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

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

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

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

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

			return;
		}

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

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

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

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

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

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

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

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

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

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

				return true;
			} 
		}

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


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

		var parent = dragProperties.getOriginalParent();

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

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

BSDDragActions = {
	DEPENDENCIES: new Array("drag/BSDDragUtils", "BSDElementPosition", "BSDLocationUtils", "BSDDOMUtils", "BSDScrollUtils", "BSDLogUtils"),

	ConvertToAbsoluteAction: function(elementToDrag, parentElement, dragOptions, placeHolderElement) {
		this.elementToDrag = elementToDrag;
		this.executeDragAction = function(dragProperties) {
		    var elementToDrag = this.elementToDrag;
		    if(!elementToDrag && dragProperties) {
		    	elementToDrag = dragProperties.element;
		    }
		    	
		    if(!BSDDOMUtils.elementContainsStyle(elementToDrag, 'position', 'absolute')) {
		    	var initialDistanceOK = this.checkInitialDistance(elementToDrag, dragProperties);
		    	if(!initialDistanceOK) {
		    		return false;
		    	}
			    var parentElement = elementToDrag.bsdDragParent;
			    var placeHolderElement = elementToDrag.bsdDragPlaceHolder;		    		
		    	var directElementParent = elementToDrag.parentNode;
		        BSDLocationUtils.makeElementAbsolutelyPositioned(elementToDrag);
				directElementParent.replaceChild(placeHolderElement, elementToDrag);
		        document.body.appendChild(elementToDrag);

		    }	
		    return true;
		};
		this.checkInitialDistance = function(elementToDrag, dragProperties) {
			var dragOptions = elementToDrag.bsdDragOptions;
			if(dragOptions && dragOptions.getInitialDistanceLimit() && dragOptions.getInitialDistanceLimit() > 0) {

				if(!this.initialDistanceLimiter) {
			    		this.initialDistanceLimiter = new BSDDragActions.InitialDistanceLimiter();
			    	}
			    	var limitResult = this.initialDistanceLimiter.executeDragAction(dragProperties);
			    	if(!limitResult) {
			    		return false;
			    	}
			}	
			return true;	
		};
		
	    this.toString = function() {
	    	return "ConvertToAbsoluteAction";
	    }
		
	},
	
	CreateDragPlaceholderAction: function() {
		this.count = 0;
		this.executeDragAction = function(dragProperties) {
			var elementToDrag = dragProperties.element;
			var dragOptions = elementToDrag.bsdDragOptions;
			var placeHolderElement = BSDDOMUtils.createElement(elementToDrag.nodeName);
			placeHolderElement.innerHTML = "&nbsp;"; // + this.count++;
			if(!dragOptions.ignoreDragElementDimensions) {
				BSDDOMUtils.cloneElementDimensions(elementToDrag, placeHolderElement);
				BSDDOMUtils.cloneElementMargins(elementToDrag, placeHolderElement, false);	/*sending true here causes drag/drop to push content down for two-column ui */			
				placeHolderElement.dragElementPosition = elementToDrag.dragElementPosition;
			}
			if(elementToDrag.bsdDragPlaceHolder) {

				var oldPlaceHolders = elementToDrag.bsdDragOldPlaceHolders;
				if(!oldPlaceHolders) {
					oldPlaceHolders = new Array();
					elementToDrag.bsdDragOldPlaceHolders = oldPlaceHolders;
				}
				BSDArrayUtils.append(oldPlaceHolders, elementToDrag.bsdDragPlaceHolder);

			}
			elementToDrag.bsdDragPlaceHolder = placeHolderElement;		

			return true;
		};
		
	    this.toString = function() {
	    	return "InsertDragPlaceholderAction";
	    }
				
	},
	
	HighlightAction: function() {
		this.executeDragAction = function(dragProperties) {
			BSDHighlightUtils.highlightElement(dragProperties.element);
			return true;
		};
		
	    this.toString = function() {
	    	return "HighlightAction";
	    }
		
	},	
	
	UnHighlightAction: function() {
		this.executeDragAction = function(dragProperties) {
			BSDHighlightUtils.unHighlightElement(dragProperties.element);			
		    return true;
		};
		
	    this.toString = function() {
	    	return "UnHighlightAction";
	    }
		
	},		
	
	HighlightDropzoneAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			if(!element.bsdDragManager) {
				return false;
			}
			var dragManager = element.bsdDragManager;

			if(dragManager.currentDropZone && dragManager.currentDropZone.dragElementPosition.contains(dragProperties.cursorPosition.x, dragProperties.cursorPosition.y)) {
				return true;
			} else if(dragManager.currentDropZone) {
				BSDHighlightUtils.unHighlightElement(dragManager.currentDropZone);
			}

			var dropZones = dragManager.dropZoneElements;
			for(var i = 0; dropZones && i < dropZones.length; i++) {
				var currentZone = dropZones[i];
				if(currentZone == element) {
					continue;
				}
				var currentZonePosition = new BSDElementPosition(currentZone);
				if(currentZonePosition.contains(dragProperties.cursorPosition.x, dragProperties.cursorPosition.y)) {
					dragManager.currentDropZone = currentZone;
					BSDHighlightUtils.highlightElement(currentZone, "#99ee99");
				} else if(currentZone.dragElementPosition) {
					BSDHighlightUtils.unHighlightElement(currentZone);
				}
			}
			
			return true;
		};
		
	    this.toString = function() {
	    		return "HighlightDropzoneAction";
	    }
		
	},

	UnHighlightDropzoneAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			var dragManager = element.bsdDragManager;
			if(!dragManager) {
				return true;
			}
			if(dragManager.currentDropZone) {
				BSDHighlightUtils.unHighlightElement(dragManager.currentDropZone);
			}
			var dropZones = dragManager.dropZoneElements;

			for(var i = 0; dropZones && i < dropZones.length; i++) {
				var currentZone = dropZones[i];
				var currentZonePosition = new BSDElementPosition(currentZone);
				if(currentZone.dragElementPosition) {
					BSDHighlightUtils.unHighlightElement(currentZone);

				}
			}
			return true;		
		};
		
	    this.toString = function() {
	    	return "UnHighlightDropzoneAction";
	    }
		
	},
	
	InitialDistanceLimiter: function() {
		this.executeDragAction = function(dragProperties) {
			var distance = BSDPoint.calculateDistance(dragProperties.originalPosition, dragProperties.currentPosition);
			var targetDistance = 10.0;			
			if(dragProperties.bsdDragOptions && dragProperties.bsdDragOptions.getInitialDistanceLimit()) {
				targetDistance = dragProperties.bsdDragOptions.getInitialDistanceLimit();
			}
			if(distance < targetDistance && !dragProperties.beyondInitialDistance) {
				return false;
			}	
			dragProperties.beyondInitialDistance = true;
			return true;
		};
		
	    this.toString = function() {
	    	return "InitialDistanceLimiter";
	    }
		
	},

	ParentDragLimiter: function() {
		this.limitTravelRange = false;


	    this.executeDragAction = function(dragProperties) {
	    	var parentElement = dragProperties.getOriginalParent();
	    	if(!parentElement) {
	    		parentElement = dragProperties.element.bsdDragParent;
	    	}	
	    	if(!parentElement) {
	    		parentElement = dragProperties.element.parentNode;
	    	}
	    	var position = new BSDElementPosition(parentElement);
	        
	     	var result = true;
	        if(dragProperties.currentPosition.x > position.maxX) {
		    	dragProperties.currentPosition.x = position.maxX - dragProperties.currentPosition.width;
		    	if(this.limitTravelRange) {
		    		result = false;
		    	}

	        } 
	        if(dragProperties.currentPosition.y > position.maxY) {
		    	dragProperties.currentPosition.y = position.maxY - dragProperties.currentPosition.height;
		    	if(this.limitTravelRange) {
		    		result = false;
		    	}
		    } 
			if(dragProperties.currentPosition.x < position.minX) {
		    	dragProperties.currentPosition.x = position.minX;
		    	if(this.limitTravelRange) {
		    		result = false;
		    	}
	        } 
	        if(dragProperties.currentPosition.y < position.minY) {
		    	dragProperties.currentPosition.y = position.minY;
		    	if(this.limitTravelRange) {
		    		result = false;
		    	}
	        } 
	        /*
	        BSDLogUtils.debug("ParentDragLimiter: " + result + " " + this.limitTravelRange + " " + parentElement.nodeName + " " + parentElement.id + "["
	        										 + dragProperties.currentPosition.x + "-" + position.minX + "][" 
	        										 + dragProperties.currentPosition.x + "-" + position.maxX + "][" 
	        										 + dragProperties.currentPosition.y + "-" + position.minY + "]["
	           										 + dragProperties.currentPosition.y + "-" + position.maxY + "]"); 
	        */
	        return result;
	    };
	    	
	    this.toString = function() {
	    	return "ParentDragLimiter";
	    }
	    	
	},
	
	SameIdSiblingLimiter: function() {


	    this.executeDragAction = function(dragProperties) {
	    	var dragElement = dragProperties.element;
	    	var parentElement = dragProperties.element.bsdDragParent;
	    	if(!parentElement) {
	    		parentElement = dragProperties.element.parentNode;
	    	}
	    	if(!parentElement) {
	    		return false;
	    	}
	    	
	    	var siblings = parentElement.childNodes;
	    	for(var i = 0; i < siblings.length; i++) {
	    		if(siblings[i] != dragElement && siblings[i].id && siblings[i].id == dragElement.id) {

	    			return true;
	    		}
	    	}

	        return false;
	    };
	    	
	    this.toString = function() {
	    	return "SameIdSiblingLimiter";
	    }	
	},
	
	
	DropzoneGroupLimiter: function() {


	    this.executeDragAction = function(dragProperties) {
	    	var dragElement = dragProperties.element;
	    	if(!dragElement.bsdDropzoneGroup) {
	    		return true;
	    	}
	    	
			var dragManager = bsdDragManager;
			var targetElement;

			if(dragManager) {
				targetElement = dragManager.calculateCurrentDropZone(dragProperties);
			}
			
			if(!targetElement || !targetElement.bsdDropzoneGroup) {

				return false;
			}

			return targetElement.bsdDropzoneGroup == dragElement.bsdDropzoneGroup;
	    };
	    	
	    this.toString = function() {
	    	return "DropzoneGroupLimiter";
	    }	
	},	
	
	ParentSwapDragAction: function() {
	    this.executeDragAction = function(dragProperties) {
			var childClassName; // = this.childClassName;
			if(!childClassName) {
			    childClassName = dragProperties.element.className;
		    }
		    var selectedElement = dragProperties.element;
		    var parentElement = selectedElement.bsdDragParent;
		    var placeHolderElement = selectedElement.bsdDragPlaceHolder;
			var elementInLocation;
			if(childClassName) {
			  	elementInLocation = BSDLocationUtils.getObjectFromParentAndClassAndLocation(
					parentElement, childClassName, 
					dragProperties.cursorPosition.x, 
					dragProperties.cursorPosition.y, 
					selectedElement);
			} else {
				elementInLocation = BSDLocationUtils.getObjectFromParentAndNodeNameAndLocation(
					parentElement, dragProperties.element.nodeName,
					dragProperties.cursorPosition.x,
					dragProperties.cursorPosition.y,
					selectedElement);
			}
		    BSDLocationUtils.makeElementNormallyPositioned(selectedElement);
			selectedElement.parentNode.removeChild(selectedElement);
			if(elementInLocation && elementInLocation.parentNode) {

			    var locationParent = elementInLocation.parentNode;
			    locationParent.replaceChild(selectedElement, elementInLocation);
			    if(!placeHolderElement.parentNode) {
			    	alert("placeholderParent is null");
			    } else {
					placeHolderElement.parentNode.replaceChild(elementInLocation, placeHolderElement);
				}
		   	} else {
			    placeHolderElement.parentNode.replaceChild(selectedElement, placeHolderElement);
			}
			
			return true;
	    };
	    
	    this.toString = function() {
	    	return "ParentSwapDragAction";
	    }
    },
	    

	AllowableDropzoneImageAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			if(!element.bsdDragManager) {
				return false;
			}
			var dragManager = element.bsdDragManager;
			var newDropZone = dragManager.calculateCurrentDropZone(dragProperties);
		    var placeHolderElement = element.bsdDragPlaceHolder;
			var placeHolderPosition = placeHolderElement.dragElementPosition;
			if(!placeHolderPosition) {
				placeHolderPosition = new BSDElementPosition(placeHolderElement);
			}
			
			var isValidZone = newDropZone && dragProperties.isValidMove;
			
		    var statusImage = dragManager.statusImage;

		    var placeHolderContainsPosition = placeHolderPosition.contains(dragProperties.cursorPosition.x, dragProperties.cursorPosition.y);

			if(placeHolderContainsPosition) {
		    	if(statusImage) {
		    		dragManager.statusImage = null;
		    		statusImage.parentNode.removeChild(statusImage);
		    	}
		    } else if(isValidZone) {
				if(!statusImage) {
					statusImage = this.createStatusImage(dragProperties, dragManager, element);
				} 
				if(!statusImage.isCheck) {
					statusImage.isCheck = true;
					statusImage.isVerboten = false;
					statusImage.className = 'BSDDropAllowImage';
					statusImage.src = '/media/xsite/cms/checkmark.gif';
				}
				this.setImageLocation(dragProperties, statusImage, element);			

			BSDVisibilityUtils.showObject(statusImage);
			} else {
				if(!statusImage) {
					statusImage = this.createStatusImage(dragProperties, dragManager, element);
				} 
				if(!statusImage.isVerboten) {
					statusImage.isCheck = false;
					statusImage.isVerboten = true;
					statusImage.className = 'BSDDropDenyImage';
					statusImage.src = '/media/xsite/cms/xmark.gif';
				}
				this.setImageLocation(dragProperties, statusImage, element);
				/*var testElement = BSDLocationUtils.getObjectFromParentAndLocation(document, dragProperties.cursorPosition.x, dragProperties.cursorPosition.y);
				if(testElement) {
					BSDLogUtils.debug("Found element: " + testElement.nodeType);				
				} else {
					BSDLogUtils.debug("No element found at location");
				}*/
			}
			return true;
		};
		
		this.createStatusImage = function(dragProperties, dragManager, element) {
			statusImage = BSDDOMUtils.createElement("img");
			statusImage.id = "BSDDragCheckmark";
			dragManager.statusImage = statusImage;
			this.setImageLocation(dragProperties, statusImage);
			BSDDOMUtils.changeElementStyle(statusImage, 'position', 'absolute');				
			document.body.appendChild(statusImage);		

			/* this was't working - rely now on css to set z-index
			var iZIndex = 5100000;
			if(element) {
				var zIndex = BSDDOMUtils.getElementStyle(element, "z-index");
				BSDLogUtils.debug("Got z-index: " + zIndex);
				if(zIndex) {
					iZIndex = parseInt(zIndex) + 100
				} 
			}
			BSDDOMUtils.changeElementStyle(statusImage, "z-index", iZIndex);
			BSDLogUtils.debug("Set drop image zindex: " + iZIndex + " " + (element != null) + " " + BSDDOMUtils.getElementStyle(element, "z-index"));
			*/
			return statusImage;
		};

		this.setImageLocation = function(dragProperties, statusImage, element) {
			BSDDOMUtils.changeElementStyle(statusImage, "top", dragProperties.cursorPosition.y - 20 + "px");
			BSDDOMUtils.changeElementStyle(statusImage, "left", dragProperties.cursorPosition.x + 10 + "px");
		}
		
	    this.toString = function() {
	    	return "AllowableDropzoneImageAction";
	    }
		
	},
	
	AllowableDropzoneImageDeleterAction: function() {		
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			if(!element.bsdDragManager) {
				return false;
			}
			var dragManager = element.bsdDragManager;
			var statusImage = dragManager.statusImage;
			if(statusImage) {
				dragManager.statusImage = null;
				statusImage.parentNode.removeChild(statusImage);
			}
			return true;
		};

	    this.toString = function() {
	    	return "AllowableDropzoneImageDeleterAction";
	    }
	},
	
	DropzoneMockInsertionAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			if(!element.bsdDragManager) {
				return false;
			}
			
			var dragManager = element.bsdDragManager;
			var insertionSpace = dragManager.bsdMoveInsertionSpace;
			var newDropZone = dragManager.calculateCurrentDropZone(dragProperties);
			if(!newDropZone) {
				if(insertionSpace) {
					if(insertionSpace.parentNode) {
						insertionSpace.parentNode.removeChild(insertionSpace);				
					}
					dragManager.bsdMoveInsertionSpace = null;
					element.bsdDragInsertBefore = false;
				}

				
				return true;
			} else if(insertionSpace && insertionSpace.bsdDropZone != newDropZone) {
				if(insertionSpace.parentNode) {
					insertionSpace.parentNode.removeChild(insertionSpace);					
				}
				dragManager.bsdMoveInsertionSpace = null;
				element.bsdDragInsertBefore = false;
				insertionSpace = null;			
			}


			if(!dragProperties.isValidMove) {
				return true;
			}

			
			if(!insertionSpace) {
				insertionSpace = BSDDOMUtils.createElement(element.nodeName);
				BSDDOMUtils.cloneElementDimensions(element, insertionSpace);
				BSDDOMUtils.cloneElementMargins(element, insertionSpace, false);	/*sending true here causes drag/drop to push content down for two-column ui */			
				
				dragManager.bsdMoveInsertionSpace = insertionSpace;
				insertionSpace.innerHTML = "&nbsp;";
				insertionSpace.bsdDropZone = newDropZone;
				insertionSpace.dragElementPosition = newDropZone.dragElementPosition;
			}

			var targetCcid = BSDDOMUtils.getAttributeValue(newDropZone, "ccid");

			if(!targetCcid && !this.ignoreCcid) {
				return true; //don't want empty templateElements to move around
			}

			var placeHolderElement = element.bsdDragPlaceHolder;
			var dropZonePosition = new BSDElementPosition(newDropZone);

			
			if(dragProperties.cursorPosition.y > dropZonePosition.getCenter().y) {

				var nextSibling = BSDDOMUtils.getNextElementSibling(newDropZone);
				if(!nextSibling || nextSibling != placeHolderElement) {
					if(nextSibling) {
						newDropZone.parentNode.insertBefore(insertionSpace, nextSibling);
					} else if(newDropZone.parentNode) {
						newDropZone.parentNode.appendChild(insertionSpace);
					}
				}
				element.bsdDragInsertBefore = false;
			} else {

				var previousSibling = BSDDOMUtils.getPreviousElementSibling(newDropZone);
				if(!previousSibling || previousSibling != placeHolderElement) {
					if(newDropZone.parentNode) {
						newDropZone.parentNode.insertBefore(insertionSpace, newDropZone);			
					} else {

					}
				}
				element.bsdDragInsertBefore = true;
			}
			return true;
		};

	    this.toString = function() {
	    	return "DropzoneMockInsertionAction";
	    }
	},
	
	DropzoneMockInsertionDeleterAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			if(!element.bsdDragManager) {
				return false;
			}
			var dragManager = element.bsdDragManager;
			var insertionSpace = dragManager.bsdMoveInsertionSpace;
			if(insertionSpace) {
				if(insertionSpace.parentNode) {
					insertionSpace.parentNode.removeChild(insertionSpace);				
				}	
				dragManager.bsdMoveInsertionSpace = null;
			}
			return true;
		};

	    this.toString = function() {
	    	return "DropzoneMockInsertionDeleterAction";
	    }
	},
	
	DragWindowScrollAction: function() {
		this.executeDragAction = function(dragProperties) {			
			var upperDiff = BSDScrollUtils.getDistanceFromTopLeftOfWindow(dragProperties.cursorPosition);
			var lowerDiff = BSDScrollUtils.getDistanceFromBottomRightOfWindow(dragProperties.cursorPosition);
			var timeInMillis = 1;
			var bufferSize = 20;
			var increment = 5;

			if(upperDiff.x < bufferSize && upperDiff.y < bufferSize) {
				BSDScrollUtils.scrollBySlowly(-bufferSize, -bufferSize, -increment, -increment, timeInMillis);							
			} else if(upperDiff.x < bufferSize) {
				BSDScrollUtils.scrollBySlowly(-bufferSize, 0, -increment, 0, timeInMillis);										
			} else if(upperDiff.y < bufferSize) {
				BSDScrollUtils.scrollBySlowly(0, -bufferSize, 0, -increment, timeInMillis);										
			} else if(lowerDiff.x < bufferSize && lowerDiff.y < bufferSize) {
				BSDScrollUtils.scrollBySlowly(bufferSize, bufferSize, increment, increment, timeInMillis);				
			} else if(lowerDiff.x < bufferSize) {
				BSDScrollUtils.scrollBySlowly(bufferSize, 0, increment, 0, timeInMillis);				
			} else if(lowerDiff.y < bufferSize) {
				BSDScrollUtils.scrollBySlowly(0, bufferSize, 0, increment, timeInMillis);				
			}
			return true;
		};

	    this.toString = function() {
	    	return "DragWindowScrollAction";
	    }
	}, 
	
	BorderSelectAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			BSDHighlightUtils.highlightElementByOverlay(element);
			return true;
		};

	    this.toString = function() {
	    	return "BorderSelectAction";
	    }
	},	

	BorderSelectDeleterAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			BSDHighlightUtils.unHighlightElementByOverlay(element);
			return true;			
		};

	    this.toString = function() {
	    	return "BorderSelectDeleterAction";
	    }
	},
	
	
	SetMoveCursorAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			BSDHighlightUtils.setMoveCursor(element);
			return true;
		};

	    this.toString = function() {
	    	return "SetMoveCursorAction";
	    }
	},	

	UnSetMoveCursorAction: function() {
		this.executeDragAction = function(dragProperties) {
			var element = dragProperties.element;
			BSDHighlightUtils.unSetMoveCursor(element);
			return true;			
		};

	    this.toString = function() {
	    	return "UnSetMoveCursorAction";
	    }
	},
	
	MoveCompletionAction: function() {
		this.executeDragAction = function(dragProperties) {


			var childClassName;
			if(!childClassName) {
			    childClassName = dragProperties.element.className;
		    }

		    var selectedElement = dragProperties.element;
		    var parentElement = selectedElement.bsdDragParent;
		    var placeHolderElement = selectedElement.bsdDragPlaceHolder;
			var dragManager = this.dragManager;
			if(!dragManager) {
				dragManager = bsdDragManager;
			}
			var targetElement;

			if(dragManager) {
				targetElement = dragManager.calculateCurrentDropZone(dragProperties);
			}
			if(!targetElement || !dragProperties.getHasMovedOutsideSelf() || dragProperties.getDoesSourceContainTarget(targetElement)
						|| dragProperties.getDoesTargetContainSource(targetElement)) {
				BSDLogUtils.debug("move 1a: " + !targetElement + " " + !dragProperties.getHasMovedOutsideSelf() + " " + dragProperties.getDoesSourceContainTarget(targetElement)
						 + " " + dragProperties.getDoesTargetContainSource(targetElement));
				BSDDragUtils.cancelMove(parentElement, selectedElement, placeHolderElement, dragProperties);

				return true;
			} else if(targetElement.bsdDropzoneGroup && selectedElement.bsdDropzoneGroup && targetElement.bsdDropzoneGroup != selectedElement.bsdDropzoneGroup) {
				BSDLogUtils.debug("Canceling move due to dropzoneGroup mismatch: " + targetElement.bsdDropzoneGroup + " " + selectedElement.bsdDropzoneGroup);
				BSDDragUtils.cancelMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;
			}
			
			var previousSibling = BSDDOMUtils.getPreviousSiblingElement(placeHolderElement);
			var nextSibling = BSDDOMUtils.getNextSiblingElement(placeHolderElement);
			if(targetElement == placeHolderElement) {
				BSDLogUtils.debug("Canceling move due to target == placeHolder");
				BSDDragUtils.cancelMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;				
			} else if(selectedElement.bsdDragInsertBefore && targetElement == nextSibling) {
				BSDLogUtils.debug("Canceling move due to target == placeHolder.nextSibling");
				BSDDragUtils.cancelMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;
			} else if(!selectedElement.bsdDragInsertBefore && targetElement == previousSibling) {
				BSDLogUtils.debug("Canceling move due to target == placeHolder.previousSibling");
				BSDDragUtils.cancelMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;
			}




			if(this.callback) {
				var arguments = new Array();
				var result = new Object();
				BSDArrayUtils.append(arguments, result);
				BSDArrayUtils.append(arguments, dragProperties);
				BSDArrayUtils.append(arguments, selectedElement);
				BSDArrayUtils.append(arguments, parentElement);
				BSDArrayUtils.append(arguments, targetElement);
				this.callback.apply(this, arguments);
				return result.result;
			}
			return true;
		}		
	}		

}
BSDDragOptions = BSDClass.create();
BSDDragOptions.DEPENDENCIES = new Array("BSDClass", "drag/BSDDragActions");
BSDDragOptions.prototype = {

	className: "BSDDragOptions",
	initialize: function(ignoreDragElementDimensions) {
	    if(ignoreDragElementDimensions) {
	    	this.ignoreDragElementDimensions = ignoreDragElementDimensions;
	    } else {
	    	this.ignoreDragElementDimensions = false;
	    }

	    this.elementWidthDelta = 0;
	    this.elementHeightDelta = 0;
	    
	    this.nodeInitializationActions = new Array();
	    this.moveInitializationActions = new Array();
		this.moveValidationActions = new Array();
		this.moveCompletionActions = new Array();
		this.moveCleanupActions = new Array();
	    	
	},
	
	getInitialDistanceLimit: function() {
		return this.initialDistanceLimit;
	},
	
	setInitialDistanceLimit: function(newLimit) {
		this.initialDistanceLimit = newLimit;
	},	

	getAllowMovementOutsideParent: function() {
		return this.allowMovementOutsideParent;
	},

	setAllowMovementOutsideParent: function(allowMovement) {
		this.allowMovementOutsideParent = allowMovement;
	},
	
	getHighlightDropzones: function() {
		return this.highlightDropzones;
	},
	
	setHighlightDropzones: function(doHighlight) {
		this.highlightDropzones = doHighlight;
	},
	
	addNodeInitializationAction: function(newAction) {
		this.nodeInitializationActions[this.nodeInitializationActions.length] = newAction;
	},
	
	addInitializationAction: function(newAction) {
		this.moveInitializationActions[this.moveInitializationActions.length] = newAction;
	},
	
	addValidationAction: function(newAction) {
		this.moveValidationActions[this.moveValidationActions.length] = newAction;
	},
	
	addCompletionAction: function(newAction) {
		this.moveCompletionActions[this.moveCompletionActions.length] = newAction;
	},
	
	addCleanupAction: function(newAction) {
		this.moveCleanupActions[this.moveCleanupActions.length] = newAction;
	},
		
	initializeDragDelayActions: function() {
		var initialDistanceLimiter = new BSDDragActions.InitialDistanceLimiter();
		this.addValidationAction(initialDistanceLimiter);	
	},
	
	initializeDragHighlightActions: function() {
		var highlightAction = new BSDDragActions.HighlightAction();
		this.addInitializationAction(highlightAction);
		var unHighlightAction = new BSDDragActions.UnHighlightAction();
		this.addCompletionAction(unHighlightAction);	
	},
	
	initializeDropzoneHighlightActions: function() {
		var dropzoneHighlighter = new BSDDragActions.HighlightDropzoneAction();
		this.addValidationAction(dropzoneHighlighter);
		var dropzoneUnHighlighter = new BSDDragActions.UnHighlightDropzoneAction();
		this.addCompletionAction(dropzoneUnHighlighter);
	},
		
	initializeStandardActions: function() {
		this.initializeDragDelayActions();
		this.initializeDragHighlightActions();
		 
	},
	
	initializeSwapActions: function() {
		var parentSwapAction = new BSDDragActions.ParentSwapDragAction();
		this.addCompletionAction(parentSwapAction);	
	}
	
	
}
var bsdDragManager;

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

	className: "BSDDragManager",
	initialize: function(dragOptions) {
		this.dragOptions = dragOptions;
		this.clearElements();
	},
	
	clearElements: function() {
		this.dragableElements = new Array();
		this.dropZoneElements = new Array();	
	},
	
	addDragableElement: function(newElement) {
		if(this.dragOptions && this.dragOptions.nodeInitializationActions) {
			var actions = this.dragOptions.nodeInitializationActions;
			var dragProperties = new Object();
			dragProperties.element = newElement;
			for(var i = 0; i < actions.length; i++) {
				if(!actions[i].executeDragAction(dragProperties)) {
					return;
				}
			}
		}
		this.dragableElements[this.dragableElements.length] = newElement;
		BSDDragUtils.initializeDragElement(this.dragOptions, newElement);
		newElement.bsdDragManager = this;
	},
	
	addDragableElements: function(newElements) {
		for(var i = 0; i < newElements.length; i++) {
			this.addDragableElement(newElements[i]);		
		}
	},
	
	addDragableElementsByClass: function(elementClass) {
		var elements = BSDDOMUtils.getObjectsByClass(elementClass);
		this.addDragableElements(elements);
	},

	addDropZoneElement: function(newElement) {
		this.dropZoneElements[this.dropZoneElements.length] = newElement;
		newElement.isDropzone = true;
		newElement.bsdDragManager = this; //TODO: remove this
		BSDDragUtils.initializeDropZoneElement(this.dragOptions, newElement);
	},
	
	addDropZoneElements: function(newElements) {
		for(var i = 0; i < newElements.length; i++) {
			this.addDropZoneElement(newElements[i]);		
		}
	},
	
	addDropZoneElementsByClass: function(elementClass) {
		var elements = BSDDOMUtils.getObjectsByClass(elementClass);
		this.addDropZoneElements(elements);
	},
	
	addElementsByClass: function(elementClass) {
		this.addDragableElementsByClass(elementClass);
		this.addDropZoneElementsByClass(elementClass);
	},
	
	calculateCurrentDropZone: function(dragProperties, parentElement) {
		var element = dragProperties.element;
		
		/*  This caching breaks drag/drop for nested copy elements, since the parent could be cached and always return true, even though the child is the actual target
		if(this.currentDropZone && this.currentDropZone.dragElementPosition.contains(dragProperties.cursorPosition.x, dragProperties.cursorPosition.y)) {
			return this.currentDropZone;
		} else if(this.currentDropZone) {

			this.currentDropZone = null;
		}
		*/

		var dropZones = this.dropZoneElements;

		var bestZone;
		var bestArea;
		for(var i = 0; dropZones && i < dropZones.length; i++) {
			var currentZone = dropZones[i];
			if(currentZone == element) {
				continue;
			}
			var currentZonePosition = currentZone.dragElementPosition;
			var contains = currentZonePosition.contains(dragProperties.cursorPosition.x, dragProperties.cursorPosition.y);
			var isChildElement = this.getIsDragZoneAParent(currentZone, element);

			if(contains && !isChildElement) {
				if(element.bsdDropzoneGroup && currentZone.bsdDropzoneGroup && element.bsdDropzoneGroup == currentZone.bsdDropzoneGroup) {
					bestZone = currentZone;
					break;
				}
				var currentArea = currentZonePosition.getArea();
				if(!bestZone || bestArea > currentArea) {
					bestZone = currentZone;
					bestArea = currentArea;					
				}
			} else if(currentZone.dragElementPosition) {

				currentZone.zoneSiblingPosition = null;
			}
		}
		
		if(bestZone) {
			this.currentDropZone = bestZone;


			return this.currentDropZone;		
		}
		
		if(this.bsdMoveInsertionSpace) {

			var insertionSpace = this.bsdMoveInsertionSpace;
			var insertionSpacePosition = new BSDElementPosition(insertionSpace);
			if(insertionSpacePosition.contains(dragProperties.cursorPosition.x, dragProperties.cursorPosition.y)) {

				this.currentDropZone = insertionSpace.bsdDropZone;
				return this.currentDropZone;
			} 
		}		

		return null;
	},
	
	getIsDragZoneAParent: function(dragZone, dragElement) {
		var dragParent = dragElement.bsdDragParent;
		while(dragParent) {
			if(dragParent == dragZone) {
				return true;
			}
			dragParent = dragParent.parentNode;
		}
		return false;
	}	
	
}
BSDContentCopy = BSDClass.create();
BSDContentCopy.DEPENDENCIES = new Array("BSDClass");
BSDContentCopy.prototype = {
	
	className: "BSDContentCopy",
	initialize: function(ccid, ccdid, isChild, templateElementId, relationshipTargetTypeId, relationshipTargetId) {
	    this.ccid = ccid;
	    this.ccdid = ccdid;
	    this.isChild = isChild;
	    this.templateElementId = templateElementId;
	    this.relationshipTargetTypeId = relationshipTargetTypeId;
	    this.relationshipTargetId = relationshipTargetId;
   	},
   	
    toString: function() {
		var str = "[" + this.ccid + "," + this.ccdid + "," + this.isChild + "]";
		return str;
    },
    
    clone: function() {
    	var clone = new BSDContentCopy(this.ccid, this.ccdid, this.isChild, this.templateElementId, this.relationshipTargetTypeId, this.relationshipTargetId);
    	return clone;
    }
    
}
BSDMenuItem = BSDClass.create();
BSDMenuItem.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDEventUtils", "BSDElementPosition");
BSDMenuItem.prototype = {

	className: "BSDMenuItem",
	initialize: function(parentMenu, label, onclick, isDisabled, isSelected, href, childMenu, isInheritable, onmouseover, onmouseout) {
		this.parentMenu = parentMenu;
	    this.label = label;
	    this.onclick = onclick;
	    this.onmouseover = onmouseover;
	    this.onmouseout = onmouseout;
	    this.isDisabled = isDisabled;
	    this.isSelected = isSelected;
	    this.href = href;
	    this.setChildMenu(childMenu);
	    this.isInheritable = isInheritable;
   	},
   	
   	setChildMenu: function(childMenu) {
	    this.childMenu = childMenu;
   		if(childMenu) {
			childMenu.parentMenuItem = this;
			childMenu.parentMenu = this.parentMenu;
			childMenu.rootParentMenu = BSDPopupMenu.getRootParentMenu(this.parentMenu);
		}
   	
   	},
   	
    toString: function() {
		var str = "[" + this.label + "," + this.href + "," + this.isDisabled + "," + this.onclick + "]";
		return str;
    },
    
    clickTest: function(message) {
    	alert("Testing: " + message);
    	var functionTest = this['BSDMenuUtils']['clickTest3'];
    	alert(functionTest);
    	return false;
    },
    

	toHtml: function(parentElement, index) {
		var row;

		if(parentElement && parentElement.insertRow) {
			row = parentElement.insertRow(index);
		} else {
		 	row = BSDDOMUtils.createElement("div");	
		}
		if(this.isDisabled) {
			BSDDOMUtils.setClass(row, "BSDMenuItemDisabled");		
		} else {
			BSDDOMUtils.setClass(row, "BSDMenuItem");
		}
		
		var leftColumn;
		if(parentElement && row.insertCell) {
			leftColumn = row.insertCell(0);
		} else {
			leftColumn = BSDDOMUtils.createElement("span");
			row.appendChild(leftColumn);
		}
		 
		BSDDOMUtils.setClass(leftColumn, "BSDMenuItemLeftColumn");
		if(this.isSelected) {
			var bullet = "\u2022";
			BSDDOMUtils.addText(leftColumn, bullet);
		} else {
			BSDDOMUtils.addText(leftColumn, " ");		
		}
		
		var labelColumn;
		if(parentElement && row.insertCell) {
			labelColumn = row.insertCell(1);
		} else {
			labelColumn = BSDDOMUtils.createElement("span");
			row.appendChild(labelColumn);
		}		
		BSDDOMUtils.setClass(labelColumn, "BSDMenuItemLabelColumn");
	
		var href;
		if(this.href && !this.isDisabled) {
			href = BSDDOMUtils.createElement("a");	
			labelColumn.appendChild(href);
			BSDDOMUtils.setClass(href, "BSDMenuItemLink");
			BSDDOMUtils.setAttributeValue(href, "href", this.href);	
		} 
		
		if(href) {
			BSDDOMUtils.addText(href, this.label);
		} else {
			BSDDOMUtils.addText(labelColumn, this.label);
		}	
		
		var rightColumn;
		if(parentElement && row.insertCell) {
			rightColumn = row.insertCell(2);
		} else {
			rightColumn = BSDDOMUtils.createElement("span");
			row.appendChild(rightColumn);
		}		
		
		BSDDOMUtils.setClass(rightColumn, "BSDMenuItemRightColumn");
		if(this.childMenu) {
			BSDDOMUtils.addText(rightColumn, ">");
		} else {
			BSDDOMUtils.addText(rightColumn, " ");		
		}
		
		this.registerItemEvents(this, row);

		this.element = row;
		if(!parentElement) {
			return row;
		}	
	}, 
	
	setIsSelected: function(isSelected) {
		this.isSelected = isSelected;
		var leftColumn = this.getLeftColumn();
		if(!leftColumn) {
			BSDLogUtils.error("Couldn't find left column for setIsSelected");
			return;
		}
		if(isSelected) {
			var bullet = "\u2022";
			BSDDOMUtils.addText(leftColumn, bullet);
		} else {
			BSDDOMUtils.setText(leftColumn, "");	
		}
	},
	
	getLeftColumn: function(parentElement) {
		if(!parentElement) {
			parentElement = this.element;
		}
		if(!parentElement) {
			return null;
		}
		for(var i = 0; parentElement.childNodes && i < parentElement.childNodes.length; i++) {
			var currentElement = parentElement.childNodes[i];
			if(currentElement.className == 'BSDMenuItemLeftColumn') {
				return currentElement;
			} else {
				var leftColumn = getLeftColumn(currentElement);
				if(leftColumn) {
					return leftColumn;
				}
			}
		}
		return null;
	},
	
	handleMouseover: function(event, element) {


		if(!this.isHighlighted) {
			this.highlight(event, element);
		}

		event.bsdMenuEventPosition = this.parentMenu.eventPosition;		
		event.bsdMenuEvent = this.parentMenu;

		if(this.onmouseover) {
			this.onmouseover.call(this, event);
		}
	},
	
	highlight: function(event, element) {
		if(!element) {
			element = this.element;
		}
	
		if(this.activeParentMenu) {
			this.activeParentMenu.deselectAllItems();
		} else {
			this.parentMenu.deselectAllItems();
		}
		this.isHighlighted = true;
		element.className = "BSDMenuItemActive";
		if(!this.itemPosition) {
			var mousePosition = BSDLocationUtils.getEventPosition(event);	

			var itemPosition = new BSDElementPosition(element);
			if(itemPosition.minY == itemPosition.maxY) {
				itemPosition.minY = mousePosition.y - 5;
				itemPosition.maxY = mousePosition.y + 5;
			}
			if(itemPosition.width == 0) {
				itemPosition.setWidthFromParent();
			}				
			this.itemPosition = itemPosition;
		}
		if(this.childMenu) {

			this.doMenuChildOpen(event);
			var eventMenu = this;
			function handleChildMouseout(e) {
				eventMenu.handleMouseout(e);
			}
			BSDEventUtils.registerEvent(this.childMenu.element, "mouseout", handleChildMouseout);
		}
	},

	doMenuChildOpen: function(event) {


		if(this.itemPosition) {
			var newPosition = this.itemPosition.clone();
			newPosition.minY = newPosition.minY - 10;
			var point = newPosition.getCenter();
			point.x = newPosition.maxX -= 10;
			this.childMenu.show(point, this.activeParentMenu);		
		}

	},

	
	handleMouseout: function(event, element) {
		if(!this.isHighlighted) {
			return;
		}
		if(event && this.childMenu) {
			var mousePosition = BSDLocationUtils.getEventPosition(event);
			var childPosition = new BSDElementPosition(this.childMenu.element);
			var contains = childPosition.contains(mousePosition.x, mousePosition.y);

			if(contains) {
				return; //mouse is in the child menu
			}
		}
		
		event.bsdMenuEventPosition = this.parentMenu.eventPosition;		
		event.bsdMenuEvent = this.parentMenu;
		
		this.unhighlight(element, event);	
	},
			
	unhighlight: function(element, event) {

		if(!element) {
			element = this.element;
		}
				
		element.className = "BSDMenuItem";
	
		if(this.childMenu) {
			this.doMenuChildClose();
		}
		this.isHighlighted = false;
		
		if(this.onmouseout) { //this is here ins unlighlight to handle cases of a click on the item

			this.onmouseout.call(this, event);
		}
		
	},
	
			
	doMenuChildClose: function() {
		this.childMenu.hide();
		this.childMenuOpen = false;	
	},
	
	handleOnClick: function(event, element) {

		var foundSelected = false;
		for(var i = 0; i < this.parentMenu.menuItems.length; i++) {
			var currentItem = this.parentMenu.menuItems[i];
			if(currentItem.isSelected) {
				currentItem.setIsSelected(false);
				foundSelected = true;
			}
		}
		if(foundSelected) {
			this.setIsSelected(true);
		}
		event.bsdMenuEventPosition = this.parentMenu.eventPosition;		
		event.bsdMenuEvent = this.parentMenu;
		
		
	},

	registerItemEvents: function(menuItem, element) {
		if(menuItem.isDisabled) {
			return;
		}
		BSDEventUtils.registerEvent(element, "mouseover", doMenuItemMouseover);
		BSDEventUtils.registerEvent(element, "mouseout", doMenuItemMouseout);

		function doOnClick(event) {
			menuItem.handleOnClick(event, element);
		}
		
		function doMenuItemMouseover(event) {
			menuItem.handleMouseover(event, element);
		}

		function doMenuItemMouseout(event) {
			menuItem.handleMouseout(event, element);
		}


		if(menuItem.childMenu) {
			BSDEventUtils.registerEvent(element, "click", doMenuItemMouseover);
		} else if(menuItem.onclick) {

			element.onclick = menuItem.onclick;
			BSDEventUtils.registerEvent(element, "click", doOnClick);
		}

		menuItem.isHighlighted = false;


		
	}	
	
}

var bsdMenuCount = 0;

BSDPopupMenu = BSDClass.create();
BSDPopupMenu.DEPENDENCIES = new Array("BSDClass", "BSDArrayUtils", "BSDVisibilityUtils", "BSDDOMUtils", "BSDEventUtils", "BSDLocationUtils", "/menu/BSDMenuItem", "/menu/BSDMenuUtils", "BSDDebugUtils");
BSDPopupMenu.prototype = {

	className: "BSDPopupMenu",
	initialize: function(target, label, name) {
	    this.target = target;
	    this.label = label;
	    this.name = name;
	    this.index = bsdMenuCount;
	    bsdMenuCount++;
	    this.rootParentMenu = this;
   	},
   	
    toString: function() {
		var str = "[" + this.index + "]";
		return str;
    },

	attachToTarget: function(target) {
		if(!this.target) {
			this.target = target;  //this will probably be a memory leak due to circular reference
		}
	    target.popupMenu = this;


	    
	    if(!document.hasPopupClearHandler) {
	    	BSDMenuUtils.initializeDocumentHandlers();
		}    
	},


	addMenuItem: function(menuItem) {
		if(!this.menuItems) {
			this.menuItems = new Array();	
		} else if(this.containsItem(menuItem)) {
			return;
		}

		BSDArrayUtils.append(this.menuItems, menuItem);    
    },

	addInheritableMenuItems: function(menuItemArray) {
		if(!menuItemArray) {
			return;
		}
		for(var i = 0; i < menuItemArray.length; i++) {
			if(menuItemArray[i].isInheritable) {

				this.addMenuItem(menuItemArray[i]);
			}
		}
    },
    
	createMenuItem: function(label, onclick, isDisabled, isSelected, href, childMenu, isInheritable) {
		var menuItem = new BSDMenuItem(this, label, onclick, isDisabled, isSelected, href, childMenu, isInheritable);
		this.addMenuItem(menuItem);
	},
	
	containsItem: function(menuItem) {
		if(!this.menuItems || !menuItem || !menuItem.label) {
			return false;
		}
		var label = menuItem.label;
		for(var i = 0; i < this.menuItems.length; i++) {
			var currentItem = this.menuItems[i];
			if(currentItem.label && BSDStringUtils.equalsTrimmed(currentItem.label, label)) {
				return true;
			}
		}
		return false;
	},
	
	deselectAllItems: function() {
		if(!this.menuItems) {
			return;
		}
		for(var i = 0; i < this.menuItems.length; i++) {
			var currentItem = this.menuItems[i];
			if(currentItem.isHighlighted) {
				currentItem.unhighlight();
			}
		}
	},
	
	clickTest2: function(message) {
    	alert("Testing2: " + message);
    	return false;
    },
	
	
	toHtml: function() {
		var div = BSDDOMUtils.createElement("div");	
		BSDDOMUtils.setClass(div, "BSDPopupMenu");
		if(this.label) {
			var label = BSDDOMUtils.createElement("span");
			BSDDOMUtils.setClass(label, "BSDMenuItemLabel");
			BSDDOMUtils.addText(label, this.label);
			div.appendChild(label);
		}
		
		var table = BSDDOMUtils.createElement("table");		
		BSDDOMUtils.setClass(table, "BSDPopupMenuTable");
		div.appendChild(table);

		for(var i = 0; this.menuItems && i < this.menuItems.length; i++) {
			var currentItem = this.menuItems[i];
			var row = currentItem.toHtml(table, i);
			if(row) {
				table.appendChild(row);
			}
		}				

		BSDVisibilityUtils.hideObject(div);
		return div;	
	},

	createElement: function() {
		var menuElement = this.toHtml();

		document.body.appendChild(menuElement);  //the positioning is wrong if it's attached to the target
		menuElement.menuSourceNode = this.menuSourceNode; //required to find original parent node for dom traversals
		this.element = menuElement;	
		if(document.popupMenuArray) {
			BSDArrayUtils.append(document.popupMenuArray, this);
		}
		return menuElement;
	},
	
	isMenuRelated: function(menu) {
		if(!menu) {
			return false;
		}
		if(menu == this) {
			return true;
		}
		var thisRootMenu = BSDPopupMenu.getRootParentMenu(this);
		var thatRootMenu = BSDPopupMenu.getRootParentMenu(menu);

		if(thisRootMenu && thatRootMenu && thisRootMenu == thatRootMenu) {
			return true;
		}

		return false;
	},
	
	show: function(position, parentMenu, event) {
		if(document.popupMenu && document.popupMenu != parentMenu && !this.isMenuRelated(document.popupMenu)) {
			document.popupMenu.hide();
			document.popupMenu = null;
		}
		this.eventPosition = position;
		if(event) {
			this.event = event;
		} else if(parentMenu) {
			this.event = parentMenu.event;
		} else {
			this.event = null;
		}
		var element = this.element;
		if(!element) {
			element = this.createElement();
		}

		
		BSDLocationUtils.setElementLocation(element, position);
		BSDVisibilityUtils.showObject(element);
		BSDLocationUtils.positionElementWithinWindow(element, true);

		if(!this.parentMenuItem) {
			document.popupMenu = this;
		}
		
		for(var i = 0; i < this.menuItems.length; i++) {
			var currentItem = this.menuItems[i];
			currentItem.activeParentMenu = this;
			currentItem.itemPosition = null;
		}	
	},

	hide: function(position) {
		var element = this.element;
		if(!element) {
			return;
		}
		for(var i = 0; this.menuItems && i < this.menuItems.length; i++) {
			var currentItem = this.menuItems[i];
			currentItem.unhighlight();
			currentItem.itemPosition = null;
		}
		BSDVisibilityUtils.hideObject(element);

		for(var i = 0; i < this.menuItems.length; i++) {
			var currentItem = this.menuItems[i];
			if(currentItem.activeParentMenu == this) {
				currentItem.activeParentMenu = null;
			}
		}	

	},
	/*
	handleRightClick: function(event) {
		alert("right click 1");
		BSDEventUtils.fixEventTarget(event);



		var target = event.target;
		var menu = target.popupMenu;
		alert("right click 2");
		while(target && !menu) {
			target = target.parentNode;
			menu = target.popupMenu;
		}
		alert("right click 3");
		if(menu) {
			var eventPosition = BSDLocationUtils.getEventPosition(event);
			menu.show(eventPosition);
			BSDEventUtils.stopPropagation(event);
			return false;
		} 
		alert("right click 4");
	},*/

	clearRightClick: function(event) {
		var menu = document.popupMenu;
		if(menu) {
			menu.hide();
			document.popupMenu = null;
			BSDEventUtils.stopPropagation(event);
			return false;
		} 
	},
	
	doNothing: function(event) {
		BSDEventUtils.stopPropogation(event);
		return false;
	}
	
    
}


BSDPopupMenu.getRootParentMenu = function(menu) {
	if(menu.rootParentMenu) {
		return menu.rootParentMenu;
	}
	return menu;
}






BSDMenuUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "/menu/BSDPopupMenu", "BSDEventUtils", "BSDStringUtils", "BSDLocationUtils"),
	
	initializeMenus: function(parentElement) {
	    if(!document.hasPopupClearHandler) {
	    	BSDMenuUtils.initializeDocumentHandlers();
		}    

		var popupMenuNodes = BSDDOMUtils.getObjectsByClass("BSDPopupMenu", parentElement);
		BSDMenuUtils.initializeMenuArray(popupMenuNodes);
	},
	
	initializeMenuArray: function(popupMenuNodes) {

		for(var i = 0; i < popupMenuNodes.length; i++) {
			var currentMenuNode = popupMenuNodes[i];

			BSDMenuUtils.initializeMenuNode(currentMenuNode);
		}	
	},
	
	initializeMenuById: function(menuId) {
		if(!document.hasPopupClearHandler) {
	    	BSDMenuUtils.initializeDocumentHandlers();
		}    

		var popupMenuNode = BSDDOMUtils.getObjectById(menuId);
		BSDMenuUtils.initializeMenuNode(popupMenuNode);	
	},
	
	initializeMenuNode: function(currentMenuNode) {
		var i = 0;
		var parentNode = currentMenuNode.parentNode;

		while(parentNode && (parentNode.nodeName == 'TBODY' || parentNode.nodeName == 'TR')) {

			parentNode = parentNode.parentNode;
			i++;
		}

		/*if(parentNode.popupMenuNode) {
			currentMenuNode.hasSiblingMenus = true; //in case there are two menu nodes attached to the same parent
		}*/
		parentNode.popupMenuNode = currentMenuNode;
		BSDEventUtils.registerEvent(parentNode, "contextmenu", BSDMenuUtils.launchPopup);
		BSDVisibilityUtils.hideObject(currentMenuNode);	


	},
	
	removePopupMenu: function(target) {
		if(target.popupMenu) {
			target.popupMenu = null;
			target.popupMenuNode = null;
		}
		BSDEventUtils.removeEvent(target, "contextmenu", BSDMenuUtils.launchPopup);
	},
	
	clickTest3: function(message) {
    	alert("Testing3: " + message);
    	return false;
    },
    
	initializeDocumentHandlers: function() {
		window.oncontextmenu = function () { return false; } //prevents the normal popup from coming on firefox
		BSDEventUtils.registerEvent(document, "click", BSDMenuUtils.clearRightClick);
		document.hasPopupClearHandler = true;	
		document.popupMenuArray = new Array();
	},
	
	initializeParentMenuItems: function(menu, parentNode) {
		var menuNode = menu.menuSourceNode;


		if(!menuNode) {
			return;
		}
		if(!parentNode) {
			parentNode = menuNode.parentNode;		
		}
		if(!parentNode) {
			return;
		}
		parentNode = parentNode.parentNode;
		
		while(parentNode) {
			var parentMenu;

			if(parentNode.popupMenu) {
				parentMenu = parentNode.popupMenu
			} else if(parentNode.popupMenuNode) {

				if(parentNode.popupMenuNode == menuNode) {

					continue;
				}
				parentMenu = BSDMenuUtils.getPopupMenuFromNode(parentNode.popupMenuNode);
			}
			
			if(parentMenu) {

				menu.addInheritableMenuItems(parentMenu.menuItems);

			}
			
			parentNode = parentNode.parentNode;
			if(parentNode == document) {
				parentNode = null;
			}
		}
	
	},
	
	REQUIRE_RIGHTCLICK_MODIFIER: false,
	launchPopup: function(event) {
		if(!(BSDMenuUtils.REQUIRE_RIGHTCLICK_MODIFIER) && (event.shiftKey || event.altKey)) {
			return true;
		} else if(event.type == 'contextmenu' && BSDMenuUtils.REQUIRE_RIGHTCLICK_MODIFIER && !event.shiftKey && !event.altKey) {
			return true;
		}
		BSDEventUtils.fixEventTarget(event);

		var eventPosition = BSDLocationUtils.getEventPosition(event);
		var target = BSDMenuUtils.getPopupMenuEventTarget(event, eventPosition);
		
		var originalTarget = target;
				
		if(target.popupMenu) {

			target.popupMenu.show(eventPosition, null, event);
			BSDEventUtils.stopPropagation(event);			
			return false;
		}
		
		var menuNode = target.popupMenuNode;
		var menu = target.popupMenu;
		while(target && !menuNode && !menu) {
			target = target.parentNode;
			menuNode = target.popupMenuNode;
			menu = target.popupMenu;
			var noMenuParam = BSDDOMUtils.getAttributeValue(target, "nomenu");
			if(noMenuParam && noMenuParam.toLowerCase() == 'true') {
				return true;
			}
		}

		if(menu) {


			target.popupMenu.show(eventPosition, null, event);
			BSDEventUtils.stopPropagation(event);
			return false;
		}
	
		if(menuNode) {

			menu = BSDMenuUtils.getPopupMenuFromNode(menuNode);		
			BSDMenuUtils.initializeParentMenuItems(menu, target);

			
			menu.attachToTarget(target);
			menu.show(eventPosition, null, event);
			BSDEventUtils.stopPropagation(event);
			return false;
		}
		
	},
	
	getPopupMenuEventTarget: function(event, eventPosition) {

		var target = event.target;
		var newTarget = BSDLocationUtils.getObjectFromParentAndLocation(target, eventPosition.x, eventPosition.y);
		if(newTarget) {
			return newTarget;
		}
		return target;
	},
	
	getPopupMenuFromNode: function(menuNode) {

		var label = BSDDOMUtils.getAttributeValue(menuNode, "label");
		var name = BSDDOMUtils.getAttributeValue(menuNode, "name");
		var menu = new BSDPopupMenu(null, label, name);
		menu.menuSourceNode = menuNode; //TODO:  Need a different way to store this relationship

		BSDMenuUtils.initializeMenuNodeItems(menuNode, menu);
		/*if(menuNode.hasSiblingMenus) {
			var parent = menuNode.parentNode;
			for(var i = 0; i < parent.childNodes.length; i++) {
				var currentChild = parent.childNodes[i];
				if(currentChild == menuNode) {
					continue;
				}
				if(BSDDOMUtils.containsClass(currentChild, 'BSDPopupMenu')) {
					BSDMenuUtils.initializeMenuNodeItems(currentChild, menu);			
				}
			}
		}				
		BSDLogUtils.debug("gotPopupMenuFromNode: " + name + " " + label);		
		*/
		return menu;
	},
	
	initializeMenuNodeItems: function(menuNode, menu) {
		var label;
		for(var i = 0; i < menuNode.childNodes.length; i++) {
			var menuItemNode = menuNode.childNodes[i];
			if(menuItemNode.nodeType == 3) {
				var nodeValue = menuItemNode.nodeValue;
				if(nodeValue && (!label || BSDStringUtils.trim(nodeValue).length > 0)) {
					label = nodeValue;
				}
			} else if(menuItemNode.className && menuItemNode.className == 'BSDMenuItem') {
				var menuItem = BSDMenuUtils.getPopupMenuItemFromNode(menu, menuItemNode);
				menu.addMenuItem(menuItem);			
			}
		}
		return label;
	},
	
	getPopupMenuItemFromNode: function(parentMenu, menuItemNode) {
		var label = BSDDOMUtils.getAttributeValue(menuItemNode, "label");
		var onclick = menuItemNode.onclick;
		var onmouseover = menuItemNode.onmouseover;
		var onmouseout = menuItemNode.onmouseout;
		var isDisabled = BSDDOMUtils.getAttributeValue(menuItemNode, "isDisabled");
		var isSelected = BSDDOMUtils.getAttributeValue(menuItemNode, "isSelected");
		var isInheritable = BSDDOMUtils.getAttributeValue(menuItemNode, "isInheritable");
		var href = BSDDOMUtils.getAttributeValue(menuItemNode, "href");
		var childMenu = null;
		for(var i = 0; i < menuItemNode.childNodes.length; i++) {
			var childNode = menuItemNode.childNodes[i];
			if(childNode.nodeType == 3) {
				var nodeValue = childNode.nodeValue;
				if(nodeValue && (!label || BSDStringUtils.trim(nodeValue).length > 0)) {
					label = nodeValue;
				}
			} else if(childNode.className && childNode.className == 'BSDPopupMenu') {
				childMenu = BSDMenuUtils.getPopupMenuFromNode(childNode);			
			}
		}
		var menuItem = new BSDMenuItem(parentMenu, label, onclick, isDisabled, isSelected, href, childMenu, isInheritable, onmouseover, onmouseout);

		return menuItem;
	},

	removePopupMenuFromNode: function(menuNode) {
		menuNode.popupMenu = null;
		menuNode.popupMenuNode = null;
		BSDEventUtils.removeEvent(menuNode, "contextmenu", BSDMenuUtils.launchPopup);
	},
	
	
	
	clearRightClick: function(event) {

		var menu = document.popupMenu;
		if(menu) {

			menu.hide();
			document.popupMenu = null;

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






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

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


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

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

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

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

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

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

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

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

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

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

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



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

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

				
			}
				if(currentChild.otherField) {

					BSDVisibilityUtils.hideObject(currentChild.otherField);
				}
		}

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

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

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

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

	
	
}
var bsdContentPageProperties = new Object();

BSDContentCopyEditorUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "BSDAjaxUtils", "BSDLocationUtils", "BSDWysiwygUtils", "drag/BSDDragOptions", "drag/BSDDragManager", "/content/BSDContentCopy", "BSDLogUtils", "/menu/BSDMenuUtils", "BSDPopupWindow", "BSDHighlightUtils", "forms/BSDFormUtils"),

	

	getInlineContentCopyEditor: function() {
		var editorId = 'AJAX_DIALOG_WINDOW';
		var editor = BSDDOMUtils.getObjectById(editorId);
		return editor;	
	},
	
		
	getContentCopyElement: function(contentCopyId, copyElementId, cloneAndInsertIfNone, cloneTargetCopyId, isLenient) {

		
		var copyElement = BSDDOMUtils.getObjectById(copyElementId);
		if(!copyElement) {

			var elements = BSDDOMUtils.getObjectsByClass("COPY");
			for(var i = 0; i < elements.length; i++) {
				if(elements[i].id == copyElementId && elements[i].parentNode) {
					copyElement = elements[i];
					break;
				} else {
					BSDLogUtils.debug("Skipping element: " + elements[i].id + " " + elements[i].parentNode + " " + copyElementId);
				}
			}
		}
		if(!copyElement) {
			BSDLogUtils.error("ERROR: Couldn't find copy element with id " + copyElementId);
			return;
		}


		var ccid;
		if(copyElement.bsdContentCopy && copyElement.bsdContentCopy.ccid) {
			ccid = copyElement.bsdContentCopy.ccid;
		} else {

		}

		if(ccid == contentCopyId || (!ccid && !contentCopyId)) {
			return copyElement;
		}
		var bestElement;
		var siblings = copyElement.parentNode.childNodes;
		for(var i = 0; siblings && i < siblings.length; i++) {
			var currentSibling = siblings[i];
			if(currentSibling.id == copyElementId) {
				var ccid;
				if(currentSibling.bsdContentCopy && currentSibling.bsdContentCopy.ccid) {
					ccid = currentSibling.bsdContentCopy.ccid
				} else {

				}
				
				if(ccid == contentCopyId) {
					return currentSibling;
				} else if(ccid == cloneTargetCopyId || !copyElement) {
					copyElement = currentSibling;
				}
				bestElement = currentSibling;
			}
		}
		
		if(isLenient && bestElement) {
			return bestElement;
		}
		
		if(cloneAndInsertIfNone && copyElement && copyElement.id == copyElementId) {
			var newElement = copyElement.cloneNode(true);
			BSDDOMUtils.setAttributeValue(newElement, "ccid", contentCopyId);
			BSDDOMUtils.insertBefore(copyElement, newElement);
			BSDLogUtils.debug("Added new copy " + contentCopyId + " before element with id: " + cloneTargetCopyId + " " + BSDDOMUtils.getAttributeValue(copyElement, "ccid"));
			return newElement;
		}
		
		BSDLogUtils.error("ERROR: Couldn't find copy element with id " + copyElementId + " and ccid " + contentCopyId + " - got " + ccid + " instead");
	},	

	getInlineContentCopyEditorTextField: function(editorId) {
		BSDLogUtils.debug("Getting editor text field for editorId: " + editorId);
		if(editorId) {
			return BSDDOMUtils.getObjectById(editorId);
		} else {
			return BSDDOMUtils.getObjectById('CONTENT_COPY_TEXT_INLINE_EDITOR');
		}
		if(!textElement) {
			var ajaxDialogWindow = BSDDOMUtils.getObjectById('AJAX_DIALOG_WINDOW');
			if(!ajaxDialogWindow) {
				alert("ERROR: Couldn't find ajax dialog window");
			}
			alert("ERROR: Couldn't find copy text value element: " + ajaxDialogWindow.innerHTML);
			return;
		}
	},
		

	hideInlineContentCopyEditor: function() {
		var editor = BSDContentCopyEditorUtils.getInlineContentCopyEditor();
		
		if(typeof(tinyMCE) != 'undefined') {
			for(var n in tinyMCE.instances){
		    		var currentInstance = tinyMCE.instances[n]; //this makes sure the instance is removed from the tinyMCE to avoid null-pointer exceptions on next editor invocation
				tinyMCE.instances[n] = null;
			}
		}
		if(editor) {
			BSDVisibilityUtils.hideObject(editor);


		}
		
		if(bsdContentPageProperties && bsdContentPageProperties.bsdInsertionElement) {
			BSDContentCopyEditorUtils.unHighlightContentCopyElement(bsdContentPageProperties.bsdInsertionElement);
			BSDContentCopyEditorUtils.unHighlightSiblings(bsdContentPageProperties.bsdInsertionElement);
		}
		
		BSDEventUtils.removeEvent(document, 'keypress', BSDContentCopyEditorUtils.handleKeyPress);
		
	},

	saveInlineContentCopyEditor: function(editorId, targetCopyId) {
		BSDLogUtils.debug("saveInlineContentCopyEditor: " + editorId);
		BSDContentCopyEditorUtils.doSaveInlineContentCopyEditor('content_copy_text', 'content_copy_title', '/ajaxpages/inlinecopyeditor', editorId);	
	},
	
	saveInlineContentCopyTextEditor: function(editorId, targetCopyId) {
		BSDLogUtils.debug("saveInlineContentCopyTextEditor: " + editorId);
		BSDContentCopyEditorUtils.doSaveInlineContentCopyEditor('content_copy_text', null, '/ajaxpages/inlinecopytexteditor', editorId);	
	},
	
	saveInlineContentCopyTitleEditor: function(editorId, targetCopyId) {
		BSDLogUtils.debug("saveInlineContentCopyTitleEditor: " + editorId);
		BSDContentCopyEditorUtils.doSaveInlineContentCopyEditor('content_copy_title', null, '/ajaxpages/inlinecopytitleeditor', editorId);
	},
	
	saveInlineContentCopyTemplateEditor: function(editorId, targetCopyId) {
		BSDLogUtils.debug("saveInlineContentCopyTemplateEditor: " + editorId);
		BSDContentCopyEditorUtils.doSaveInlineContentCopyEditor('content_template_template', null, '/ajaxpages/contentcopytemplateedit', editorId, true);	
	},
	
	doSaveInlineContentCopyEditor: function(fieldName1, fieldName2, pageID, editorId, doRender) {
		/*var formElement = null;
		if(document.AJAX_DIALOG_WINDOW_FORM) {
			formElement = document.AJAX_DIALOG_WINDOW_FORM;
		} else {
			alert("ERROR: Couldn't find editor form");
			return;
		}

		tinyMCE.triggerSave();
		var contentCopyId = formElement.content_copy_deployment_id.value; 
		var fieldValue = formElement.content_copy_text.value; 
		*/
		var popupWindow = BSDContentCopyEditorUtils.getInlineContentCopyEditor();
		
		var copyElementIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_ELEMENT_ID_INLINE_EDITOR');
		var copyElementId;
		BSDLogUtils.debug("Got copyElementID field: " + copyElementIdField);
		if(copyElementIdField) {
			copyElementId = copyElementIdField.value; //the id of the actual COPY dom element
			BSDLogUtils.debug("Got copyElement id: " + copyElementId);
		}
		
		var copyIdElement = BSDDOMUtils.getObjectById('CONTENT_COPY_DEPLOYMENT_ID_INLINE_EDITOR');
		if(!copyIdElement) {
			alert("ERROR: Couldn't find copyId element");
			return;
		}
		var contentCopyId = copyIdElement.value;

		var categoryId;
		var categoryIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_CATEGORY_ID');
		if(categoryIdField) {
			categoryId = categoryIdField.value;
			BSDLogUtils.debug("Got category id field: [" + categoryIdField.value + "][" + categoryId + "]");			
		}
		
		/** Category may not be required - assume not for now
		if((!contentCopyId || contentCopyId.length < 1) && (!categoryId || categoryId.length < 1)) {

			alert("ERROR: You must select a category");
			return;
		}
		*/
		
		var relationshipTargetTypeId;
		var relationshipTargetId;
		var relationshipTargetTypeIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_DEPLOYMENT_RELATIONSHIP_TARGET_TYPE_ID_INLINE_EDITOR');
		var relationshipTargetIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_DEPLOYMENT_RELATIONSHIP_TARGET_ID_INLINE_EDITOR');
		if(relationshipTargetTypeIdField && relationshipTargetIdField) {
			relationshipTargetTypeId = relationshipTargetTypeIdField.value;
			relationshipTargetId = relationshipTargetIdField.value;
		}		

		var strIsBefore = "false";
		var targetCopyId;
		var targetCopyIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'TARGET_CONTENT_COPY_DEPLOYMENT_ID');
		if(targetCopyIdField) {
			targetCopyId = targetCopyIdField.value;
			if(targetCopyId) {	
				var targetElement = BSDContentCopyEditorUtils.getContentCopyElement(targetCopyId, copyElementId);
				if(targetElement && targetElement.bsdDragInsertBefore) {
					strIsBefore = "true";
				}
			}
		}
		
		var attachToParent = true;
		var contentCopyElement = BSDDOMUtils.getObjectById(copyElementId);
		/* TODO: Finish "change all pages like this 
		if(contentCopyElement && attachToParent) {
			var parent = contentCopyElement.parentNode;
			while(parent && (!BSDDOMUtils.containsClass(parent, "COPY") || !BSDDOMUtils.getAttributeValue(parent, "ccid"))) {
				parent = parent.parentNode;
			}
			if(parent) {
				var parentCcId = BSDDOMUtils.getAttributeValue(parent, "ccid");
				alert("Got parent ccid: " + parentCcId);
			} else {
				alert("Couldn't find parent");
			}
		}
		*/
		
		var textElement = BSDContentCopyEditorUtils.getInlineContentCopyEditorTextField(editorId);
		if(!textElement) {
			BSDLogUtils.error("ERROR: Couldn't find inline editor field with name: " + editorId);
			return;
		}
		var fieldValue = textElement.value;
		var fieldValue2;

		var pageArguments = {'content_copy_deployment_id' : contentCopyId,
							'template_element_id' : copyElementId,
							'content_copy_category_id' : categoryId,
							'relationship_target_type_id' : relationshipTargetTypeId,
							'relationship_target_id' : relationshipTargetId,
							'target_content_copy_deployment_id' : targetCopyId,
							'is_before' : strIsBefore
							};
		pageArguments[fieldName1] = fieldValue;  
		if(fieldName2) {
			var editorId2 = "2_" + editorId;
			var textElement2 = BSDContentCopyEditorUtils.getInlineContentCopyEditorTextField(editorId2);
			if(textElement2) {
				fieldValue2 = textElement2.value;
				pageArguments[fieldName2] = fieldValue2;  		
			} else if(contentCopyId) {
				BSDLogUtils.debug("Couldn't find inline editor field2 with name: " + fieldName1);			
			} else {
				BSDLogUtils.error("ERROR: Couldn't find inline editor field2 with name: " + fieldName1);
				return;			
			}
		}

		if(BSDContentCopyEditorUtils.getIsCopyUnchanged(contentCopyId, copyElementId, fieldName1, fieldValue, fieldName2, fieldValue2)) {
			BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
			return;
		}	
	
		BSDAjaxUtils.showActivityMessage('Saving...');	
		if(doRender) { //only used in the edit template case
			pageArguments['document_url'] = document.URL;
			var parentNode = contentCopyElement.parentNode;
			BSDLogUtils.debug("doSaveInlineContentCopyEditor: doRendering");
			KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doContentCopyRenderReply(str, copyElementId, parentNode);
						BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
					} 
				});
		} else {
			BSDLogUtils.debug("doSaveInlineContentCopyEditor: doNavigation");
			KCMAjaxGui.doNavigation(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doSaveInlineContentCopyEditorReply(str, fieldName1, fieldName2, contentCopyId, copyElementId, targetCopyId) 
						BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
					} 
				});
		}						
	},


	getIsCopyUnchanged: function(contentCopyId, copyElementId, fieldName1, fieldValue1, fieldName2, fieldValue2) {
		var copyElement = BSDContentCopyEditorUtils.getContentCopyElement(contentCopyId, copyElementId);
		BSDLogUtils.debug("getIsCopyUnchanged 1");
		if(!copyElement || !fieldName1) {
			return;
		}
		BSDLogUtils.debug("getIsCopyUnchanged 2");
		var childElement1, childElement2;
		childElement1 = BSDContentCopyEditorUtils.getContentCopyChildElement(copyElement, fieldName1.toUpperCase(), false, copyElementId);
		if(fieldName2) {
			childElement2 = BSDContentCopyEditorUtils.getContentCopyChildElement(copyElement, fieldName2.toUpperCase(), false, copyElementId);		
		}
		BSDLogUtils.debug("getIsCopyUnchanged 3");
		if(!childElement1 && !childElement2 && !BSDDOMUtils.getContainsChildElements(copyElement, "BSDPopupMenu")) {
			childElement1 = copyElement;
		}
		if(!childElement1) {
			return;
		}
		BSDLogUtils.debug("getIsCopyUnchanged 4: [" + fieldName1 + "][" + BSDDOMUtils.getText(childElement1) + "][" + fieldValue1 + "]");
		if(!BSDStringUtils.equalsTrimmed(BSDDOMUtils.getText(childElement1), fieldValue1)) {
			return false;
		}
		BSDLogUtils.debug("getIsCopyUnchanged 5: [" + fieldName2 + "][" + BSDDOMUtils.getText(childElement2) + "][" + fieldValue2 + "]");
		if((childElement2 || fieldValue2) && !BSDStringUtils.equalsTrimmed(BSDDOMUtils.getText(childElement2), fieldValue2)) {
			return false;
		}
		BSDLogUtils.debug("getIsCopyUnchanged 6");

		return true;

	},

	doSaveInlineContentCopyEditorReply: function(data, childElementId1, childElementId2, contentCopyId, copyElementId, targetCopyId) {

  		var successful = BSDAjaxUtils.doRenderingReply(data);
		if(!successful) {
			alert("ERROR: A system error occured.  Please try again");
			return;
	    } else if(data.errorMessage) {
	  		BSDLogUtils.debug("Got error: " + data.errorMessage);
			alert(data.errorMessage);
			return;
		}

		if(data.data && data.data['CONTENT_COPY']) {
			var contentCopy = data.data['CONTENT_COPY'];
			
			childElementId1 = childElementId1.toUpperCase();
			if(childElementId2) {
				childElementId2 = childElementId2.toUpperCase();
			}
			
			var isChild = bsdContentPageProperties.bsdInlineEditorIsChild;
			if(isChild) {
				childElementId1 = "CHILD_" + childElementId1;
				if(childElementId2) {
					childElementId2 = "CHILD_" + childElementId2;
				}
			}
			bsdContentPageProperties.bsdInlineEditorIsChild = null;
			
			var copyElement;
			var childElement1;
			var childElement2;
			if(copyElementId) {
				copyElement = BSDContentCopyEditorUtils.getContentCopyElement(contentCopyId, copyElementId);
				if(!copyElement && contentCopy && contentCopy.id && contentCopy.id.id != copyElementId) {
					copyElement = BSDContentCopyEditorUtils.getContentCopyElement(contentCopy.id.id, copyElementId, true, targetCopyId);
				} else if(!copyElement) {
					BSDLogUtils.error("Couldn't find copy element for editor reply: " + contentCopyId + " " + copyElementId);
					return;
				}
				
				childElement1 = BSDContentCopyEditorUtils.getContentCopyChildElement(copyElement, childElementId1, isChild, copyElementId);

				if(childElementId2 && childElementId2 != null) {
					BSDLogUtils.debug("getting childElement2: " + childElementId2);
					childElement2 = BSDContentCopyEditorUtils.getContentCopyChildElement(copyElement, childElementId2, isChild, copyElementId);
				}
			} 

			/*if(!childElement1  || childElement1 == null) {
				BSDLogUtils.error("Couldn't find child element for with id " + childElementId1 + " for copy " + copyElementId);
				return;
			}*/

			
			var childValue1 = contentCopy.text;
			var childValue2 = contentCopy.title;
			if(childElementId1.indexOf('CONTENT_COPY_TITLE') > -1) {
				childValue1 = contentCopy.title;
				childValue2 = contentCopy.text;
			}

			if(!childElement1 || childElement1 == null) {
				BSDDOMUtils.setElementValue(copyElement, childValue1);
			} else {			
				BSDDOMUtils.setElementValue(childElement1, childValue1); 
				if(childElement2) {
					BSDDOMUtils.setElementValue(childElement2, childValue2); 
				}
			}

			if(copyElement) {
				var ccid = BSDDOMUtils.getAttributeValue(copyElement, 'ccid');
				var ccdid = contentCopy.contentCopyDeploymentId;
				if(ccdid && (!ccid || ccid.length < 1 || ccid != ccdid.id)) {

					ccid = ccdid.id;

					BSDDOMUtils.setAttributeValue(copyElement, 'ccid', ccid);

					BSDMenuUtils.removePopupMenuFromNode(copyElement);
					BSDContentCopyEditorUtils.initializeCopyPopupMenu(data, copyElement, childElement1, childElement2, false);
					BSDMenuUtils.initializeMenus(copyElement);	
					BSDContentCopyEditorUtils.initializeContentCopyElement(copyElement, bsdDragManager, false);		
				}
			}

			if(copyElement && copyElement.id && BSDDOMUtils.containsClass(copyElement, 'DISCUSSION_COPY')) {

				var elements = BSDDOMUtils.getObjectsByClass('DISCUSSION_COPY');
				var ccid = BSDDOMUtils.getAttributeValue(copyElement, 'ccid');
				var isTitle = BSDStringUtils.startsWith(copyElement.id, 'DISCUSSION_TITLE');
				for(var i = 0; i < elements.length; i++) {
					var currentElement = elements[i];
					var currentCcid = BSDDOMUtils.getAttributeValue(currentElement, 'ccid');
					var currentIsTitle = BSDStringUtils.startsWith(currentElement.id, 'DISCUSSION_TITLE');
					if(currentCcid && ccid == currentCcid && currentElement.id != copyElement.id && isTitle == currentIsTitle) {
						BSDDOMUtils.setElementValue(currentElement, copyElement.innerHTML);							
					}					
				}
			}
			
		} else {
			alert("ERROR: Couldn't find content copy in response");
		}	

	},
	
	initializeCopyPopupMenu: function(data, copyElement, textElement, titleElement, editOnly) {
		if(!data.data) {
			BSDLogUtils.debug("Couldn't find data hash in ajax response: " + BSDDebugUtils.dumpObject(data));
			return;
		}

		if(titleElement) {
			BSDContentCopyEditorUtils.initializeCopyPopupMenuOnElement(data, 'CONTENT_COPY_TITLE_MENU', titleElement);
		}
		
		if(textElement) {
			BSDContentCopyEditorUtils.initializeCopyPopupMenuOnElement(data, 'CONTENT_COPY_TEXT_MENU', textElement);
		}

		if(!copyElement) {
			BSDLogUtils.error("ERROR: Didn't get copyElement for popup menu initialization");
			return;
		}

		if(editOnly && !titleElement && !textElement) {
			BSDContentCopyEditorUtils.initializeCopyPopupMenuOnElement(data, 'CONTENT_COPY_TEXT_MENU', copyElement);
		} else if(titleElement || textElement) {
			BSDContentCopyEditorUtils.initializeCopyPopupMenuOnElement(data, 'CONTENT_COPY_COPY_MENU', copyElement);
		} else {
			BSDContentCopyEditorUtils.initializeCopyPopupMenuOnElement(data, 'CONTENT_COPY_TOTAL_MENU', copyElement);
		}
		
		return;
	},
	
	initializeCopyPopupMenuOnElement: function(data, menuKey, element) {
		var menu = data.data[menuKey];
		if(!menu) {
			BSDLogUtils.error("Didn't get menu " + menuKey + " from copy edit response: " + BSDDebugUtils.dumpObject(data.data));
			return;
		}
		var value = element.innerHTML;
		value += menu;
		element.innerHTML = value;
	
	},
	

	getParentContentCopyElementFromEvent: function(event) {
		BSDEventUtils.fixEventTarget(event);
		if(event == null) {
			return null;
		}
		var target = event.target;


		return BSDContentCopyEditorUtils.getParentContentCopyElementFromChild(target);
	},

	getParentContentCopyElementFromChild: function(childElement) {
		var copyObject = null;
		while(childElement) {
			copyObject = childElement.bsdContentCopy;
			if(copyObject) {
				return childElement;
			}
			childElement = childElement.parentNode;
		}

		alert("Couldn't find parent copy element");
		return null;
	
	},

	showInlineContentCopyEditorById: function(ccid, isChild, copyElementId, relationshipTargetTypeId, relationshipTargetId, targetCopyId) { 
		var copyElement;
		if(copyElementId) {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		}
        if(!copyElement) {
        	BSDLogUtils.error("Couldn't find copy element with id: " + ccid + " " + copyElementId);
        	return;
        }

		var copyObject = copyElement.bsdContentCopy;
		if(!copyObject) {
			BSDContentCopyEditorUtils.initializeContentCopyElement(copyElement, null, false);
			copyObject = copyElement.bsdContentCopy;
			copyObject.ccid = ccid;
		}
		
		if(relationshipTargetTypeId) {
			copyObject.relationshipTargetTypeId = relationshipTargetTypeId;
			copyObject.relationshipTargetId = relationshipTargetId;
		}
		BSDContentCopyEditorUtils.showInlineContentCopyEditor(copyElement, copyObject, '/ajaxpages/inlinecopyeditor', 'content_copy_title', 'content_copy_text', targetCopyId);
	},

	showInlineContentCopyTextEditorById: function(ccid, isChild, copyElementId, relationshipTargetTypeId, relationshipTargetId, targetCopyId) { 
		var copyElement;
		if(copyElementId) {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		}
        if(!copyElement) {
        	BSDLogUtils.error("Couldn't find copy element with id: " + ccid + " " + copyElementId);
        	return;
        }

		var copyObject = copyElement.bsdContentCopy;
		if(!copyObject) {
			BSDContentCopyEditorUtils.initializeContentCopyElement(copyElement, null, false);
			copyObject = copyElement.bsdContentCopy;
			copyObject.ccid = ccid;
		}
		if(relationshipTargetTypeId) {
			copyObject.relationshipTargetTypeId = relationshipTargetTypeId;
			copyObject.relationshipTargetId = relationshipTargetId;
		}
		BSDContentCopyEditorUtils.showInlineContentCopyEditor(copyElement, copyObject, '/ajaxpages/inlinecopytexteditor', 'content_copy_text', null, targetCopyId);
	},

	showInlineContentCopyTitleEditorById: function(ccid, isChild, copyElementId, relationshipTargetTypeId, relationshipTargetId, targetCopyId) {
		var copyElement;
		if(copyElementId) {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		}
        if(!copyElement) {
        	BSDLogUtils.error("Couldn't find copy element with id: " + ccid + " " + copyElementId);
        	return;
        }

		var copyObject = copyElement.bsdContentCopy;
		if(!copyObject) {
			BSDContentCopyEditorUtils.initializeContentCopyElement(copyElement, null, false);
			copyObject = copyElement.bsdContentCopy;
			copyObject.ccid = ccid;
		}
		if(relationshipTargetTypeId) {
			copyObject.relationshipTargetTypeId = relationshipTargetTypeId;
			copyObject.relationshipTargetId = relationshipTargetId;
		}
		
		BSDContentCopyEditorUtils.showInlineContentCopyEditor(copyElement, copyObject, '/ajaxpages/inlinecopytitleeditor', 'content_copy_title', null, targetCopyId);
	},

	showInlineContentCopyTemplateEditorById: function(ccid, isChild, copyElementId, relationshipTargetTypeId, relationshipTargetId, targetCopyId) {
		var copyElement;
		if(copyElementId) {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		}
        if(!copyElement) {
        	BSDLogUtils.error("Couldn't find copy element with id: " + ccid + " " + copyElementId);
        	return;
        }

		var copyObject = copyElement.bsdContentCopy;
		if(!copyObject) {
			BSDContentCopyEditorUtils.initializeContentCopyElement(copyElement, null, false);
			copyObject = copyElement.bsdContentCopy;
			copyObject.ccid = ccid;
		}
		if(relationshipTargetTypeId) {
			copyObject.relationshipTargetTypeId = relationshipTargetTypeId;
			copyObject.relationshipTargetId = relationshipTargetId;
		}
		
		function sizeWindowHelper(popupWindow) {
			var popupElement =  popupWindow.popupElement;
			var width = popupElement.offsetWidth;
			var height = popupElement.offsetHeight;
			var pageDimensions = BSDScrollUtils.getCurrentPageDimensions();
			
			if(width < 800) {
				if(pageDimensions.x > 800) {
					newWidth = "750px";
				} else {
					newWidth = "500px";
				}
				BSDDOMUtils.changeElementStyle(popupElement, "width", newWidth);
			}
			if(height < 500) {
				var newHeight;
				if(pageDimensions.y > 500) {
					newHeight = "450px";
				} else {
					newHeight = "300px";
				}
				BSDDOMUtils.changeElementStyle(popupElement, "height", newHeight);
			}
			BSDLocationUtils.positionElementWithinWindow(popupElement, false);
		}

		BSDContentCopyEditorUtils.showInlineContentCopyEditor(copyElement, copyObject, '/ajaxpages/inlinecopytemplateeditor', 'content_copy_title', null, targetCopyId, null, sizeWindowHelper);
	},		
	showInlineContentCopyTextEditorByEvent: function(event) { //contentCopyId, contentCopyButtonElement, isChild) {
		var copyElement = BSDContentCopyEditorUtils.getParentContentCopyElementFromEvent(event);
		var copyObject = copyElement.bsdContentCopy;
		BSDContentCopyEditorUtils.showInlineContentCopyEditor(copyElement, copyObject, '/ajaxpages/inlinecopytexteditor', 'content_copy_text');
	},
	
	showInlineContentCopyTitleEditorByEvent: function(event) {
		var copyElement = BSDContentCopyEditorUtils.getParentContentCopyElementFromEvent(event);
		var copyObject = copyElement.bsdContentCopy;
		BSDContentCopyEditorUtils.showInlineContentCopyEditor(copyElement, copyObject, '/ajaxpages/inlinecopytitleeditor', 'content_copy_text');
	},
	
	showInlineContentCopyEditor: function(contentCopyElement, copyObject, pageID, fieldName1, fieldName2, targetCopyId, existingPopupWindow, windowAdjustmentFunction) { //contentCopyId, contentCopyButtonElement, pageID, isChild) {



		var x, y, width, height;
		if(existingPopupWindow) {
			x = BSDLocationUtils.getObjectLocationX(existingPopupWindow.popupElement); 
			y = BSDLocationUtils.getObjectLocationY(existingPopupWindow.popupElement);
			width = existingPopupWindow.popupElement.offsetWidth; 
			height = existingPopupWindow.popupElement.offsetHeight;
		} else {
			x = BSDLocationUtils.getObjectLocationX(contentCopyElement); 
			y = BSDLocationUtils.getObjectLocationY(contentCopyElement);
			width = contentCopyElement.offsetWidth; 
			height = contentCopyElement.offsetHeight;
		}
		var pageDimensions = BSDScrollUtils.getCurrentPageDimensions();
		if(width > pageDimensions.x) {
			width = .8 * pageDimensions.x | 0;
		}
		if(height > pageDimensions.y) {
			height = .8 * pageDimensions.y | 0;
		}
		
		var doWysiwyg = pageID != null && pageID.indexOf("template") < 0;
		
		var pageArguments = {'content_copy_deployment_id' : copyObject.ccid, //contentCopyId, 
							'content_copy_inline_editor_location_x' : x,
							'content_copy_inline_editor_location_y' : y,
							'content_copy_inline_editor_width' : width,
							'content_copy_inline_editor_height' : height,
							'target_content_copy_deployment_id' : targetCopyId
							};
		var parentElementId = 'AJAX_DIALOG_WINDOW_PARENT';
		
		BSDAjaxUtils.showActivityMessage('Loading...');
		BSDLogUtils.debug("showCopyEditor: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doShowInlineContentCopyEditorReply(str, parentElementId, contentCopyElement, copyObject, fieldName1, fieldName2, targetCopyId, existingPopupWindow, windowAdjustmentFunction, doWysiwyg) 
					} 
				});

				
	},
	
	doShowInlineContentCopyEditorReply: function(data, parentElementId, contentCopyElement, copyObject, childElementId1, childElementId2, targetCopyId, existingPopupWindow, windowAdjustmentFunction, doWysiwyg) {
		if(existingPopupWindow) {
			BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
		}

		if(!childElementId1 && !childElementId2) {
			BSDLogUtils.error("Can't show copy editor without text or title element id being set");		
			return;
		}

		var isChild = copyObject.isChild;
		var successful = BSDAjaxUtils.doRenderingReply(data, parentElementId);
				
		if(!successful) {
			alert("ERROR: A system error occured.  Please try again");
			return;
	    } else if(data.errorMessage) {
	  		BSDLogUtils.debug("Got error: " + data.errorMessage);
			alert(data.errorMessage);
			return;
		} 

		bsdContentPageProperties.bsdInlineEditorIsChild = isChild;
		var editorId = data.data['AJAX_DIALOG_WINDOW_FIELD_ID'];
		if(!editorId) {
			alert("ERROR: Couldn't get editor id from response");
			return;
		}


		var popupWindow = BSDContentCopyEditorUtils.initializePopupWindow(parentElementId);
		if(doWysiwyg) {
			BSDAjaxUtils.doJavascriptInit(popupWindow.popupElement);
			BSDWysiwygUtils.setTextareaToTinyMCE(editorId);
		}

		var copyElementIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'CONTENT_COPY_ELEMENT_ID_INLINE_EDITOR');
		if(contentCopyElement && copyElementIdField) {
			copyElementIdField.value = contentCopyElement.id;
			BSDLogUtils.debug("Set copyElementId field: " + copyElementIdField.value);
		}
		
		var relationshipTargetTypeIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'CONTENT_COPY_DEPLOYMENT_RELATIONSHIP_TARGET_TYPE_ID_INLINE_EDITOR');
		var relationshipTargetIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'CONTENT_COPY_DEPLOYMENT_RELATIONSHIP_TARGET_ID_INLINE_EDITOR');
		if(relationshipTargetTypeIdField && relationshipTargetIdField) {
			relationshipTargetTypeIdField.value =  copyObject.relationshipTargetTypeId;
			relationshipTargetIdField.value = copyObject.relationshipTargetId;
		} else {
			BSDLogUtils.warning("ERROR: Couldn't find relationship target fields from inline editor popup window: [" + relationshipTargetTypeIdField + "][" + relationshipTargetIdField + "]");			
		}

		if(childElementId2) {
			BSDContentCopyEditorUtils.populateInlineEditorField(popupWindow, contentCopyElement, childElementId1, isChild, '2_CONTENT_COPY_TEXT_INLINE_EDITOR');
			BSDContentCopyEditorUtils.populateInlineEditorField(popupWindow, contentCopyElement, childElementId2, isChild, 'CONTENT_COPY_TEXT_INLINE_EDITOR');
		} else {
			BSDContentCopyEditorUtils.populateInlineEditorField(popupWindow, contentCopyElement, childElementId1, isChild, 'CONTENT_COPY_TEXT_INLINE_EDITOR');			
		}
		
		bsdContentPageProperties.bsdInsertionElementId = contentCopyElement.id;
		if(targetCopyId) { //save these params for possible later use by the copy selector tool
			bsdContentPageProperties.bsdInsertionCCID = targetCopyId;
			bsdContentPageProperties.bsdInsertionElement = BSDContentCopyEditorUtils.getContentCopyElement(targetCopyId, contentCopyElement.id);
			bsdContentPageProperties.bsdInsertionCopyObject = bsdContentPageProperties.bsdInsertionElement.bsdContentCopy;
		} else {
			BSDContentCopyEditorUtils.resetSelectVariables();
		}

		function launchSelectWindow(e) {
			BSDEventUtils.stopPropagation(e);
			BSDContentCopyEditorUtils.launchCopySelectWindowFromEdit(popupWindow);
			return false;			
		}

		var selectCopyLink = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'CONTENT_COPY_SELECT_URL');
		if(!selectCopyLink) {
			BSDLogUtils.warning("Warning: Couldn't find new copy link in copy select popup window");
		}
		
		var hasTextFields = BSDContentCopyEditorUtils.getCopyElementHasTextFields(contentCopyElement);
		if(selectCopyLink && copyObject.ccid) {
			selectCopyLink.parentNode.removeChild(selectCopyLink);
			var parent = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'CONTENT_COPY_DEPLOYMENT_SELECT_LINK_INLINE_EDITOR');
			if(parent) {
				BSDDOMUtils.removeElement(parent);
			}
		} else if(selectCopyLink) {
			BSDEventUtils.registerEvent(selectCopyLink, "click", launchSelectWindow);		
		}
		
		if(windowAdjustmentFunction) {
			windowAdjustmentFunction.call(this, popupWindow);
		}
		
		if(BSDBrowserUtils.getIsMSIE()) {

			var textareas = BSDDOMUtils.getObjectsByClass('BSDDialogTextarea', popupWindow.popupElement);
			if(textareas.length > 0) {
				var textarea = textareas[textareas.length - 1];
				if(textarea) {
					var height = popupWindow.popupElement.offsetHeight;
					BSDDOMUtils.changeElementStyle(textarea, 'height', height);
				}
			}
		}

	},
	
	populateInlineEditorField: function(popupWindow, contentCopyElement, childElementId, isChild, editorFieldIdPrefix) {
		var textAreaElement = BSDDOMUtils.getObjectByIdPrefixFromParent(popupWindow.popupElement, editorFieldIdPrefix);
		var contentCopyChildElement = BSDContentCopyEditorUtils.getContentCopyChildElement(contentCopyElement, childElementId.toUpperCase(), isChild);
		if(textAreaElement) {
			var value = textAreaElement.value;
			if(!value || BSDStringUtils.trim(value).length < 1) {
				var newValue;
				BSDLogUtils.debug("populateInlineEditorField: BEGIN: [" + contentCopyElement.id + "][" + childElementId + "][" + textAreaElement.id + "][" + contentCopyChildElement + "]");
				if(contentCopyChildElement) {
					newValue = BSDDOMUtils.getText(contentCopyChildElement);
					BSDLogUtils.debug("populateInlineEditorField: child text: [" + newValue + "][" + childElementId + "]");
				} else { 
					newValue = BSDDOMUtils.getText(contentCopyElement);		
					if(!newValue || BSDStringUtils.trim(newValue).length < 1) {
						newValue = contentCopyElement.innerHTML;
					}

				}
				if(newValue) {
					newValue = BSDStringUtils.trim(newValue);

					textAreaElement.value = newValue;
				}
			}

		} else {
			BSDLogUtils.debug("Couldn't find CONTENT_COPY_TEXT_INLINE_EDITOR from inline editor popup window: [" + textAreaElement + "][" + editorFieldIdPrefix + "]");

		}
			
	},
		
	getOptimumInsertionElement: function(contentCopyElement, insertionPoint, cardinality) {



		var siblings = contentCopyElement.parentNode.childNodes;
		var cumX = 0;
		var cumY = 0;
		var previousX = 0;
		var previousY = 0;
		var closestElement;
		var closestPosition;
		var smallestDistance;
		for(var i = 0; i < siblings.length; i++) {
			var currentSibling = siblings[i];
			if(currentSibling.nodeType != 1 || !currentSibling.id || !BSDDOMUtils.containsClass(currentSibling, "COPY")) {
				continue;
			}
			var currentPosition = new BSDElementPosition(currentSibling);
			if(previousX != 0 && previousY != 0) {
				cumX += currentPosition.x - previousX;
				cumY += currentPosition.y - previousY;
			}
			previousX = currentPosition.x;
			previousY = currentPosition.y;
			var currentDistance = currentPosition.getDistance(insertionPoint.x, insertionPoint.y);

			if(!smallestDistance || smallestDistance > currentDistance) {
				smallestDistance = currentDistance;
				closestElement = currentSibling;
				closestPosition = currentPosition;
			}
		}

		if(!closestElement) {
			BSDLogUtils.warning("Couldn't find closest element for copy element " + contentCopyElement.id + " and point " + insertionPoint);
			return contentCopyElement;
		}

		var diff;
		if(cumY > cumX) {
			diff = insertionPoint.y - (closestPosition.y + closestPosition.height/2);
		} else {
			diff = insertionPoint.x - (closestPosition.x + closestPosition.width/2);
		}
		if(diff > 0) {
			cardinality.value = 1; //insert after
		} else {
			cardinality.value = 0; //insert before
		}


		return closestElement;
	},

    getNearestContentCopyElement: function(ccid, copyElementId, event) {
		var contentCopyElement;
		if(copyElementId) {
			contentCopyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId, null, null, true);
			if(!contentCopyElement) {
				BSDLogUtils.error("ERROR: Couldn't find copy element with id " + copyElementId + " and ccid " + ccid);
				return;
			}
		} 
		
		var eventPosition;
		var cardinality = new Object();
		if(event && event.bsdMenuEventPosition && contentCopyElement) {
			eventPosition = event.bsdMenuEventPosition;
			contentCopyElement = BSDContentCopyEditorUtils.getOptimumInsertionElement(contentCopyElement, eventPosition, cardinality);
			contentCopyElement.cardinality = cardinality;
		} else {
			cardinality.value = false;
		}    
		
		return contentCopyElement;
    },
	
	showInlineContentCopySelectorById: function(ccid, copyElementId, isChild, existingPopupWindow, replaceExistingSelector, typeId, event) { 
		var x, y, width, height;
		var contentCopyElement = BSDContentCopyEditorUtils.getNearestContentCopyElement(ccid, copyElementId, event);
		if(contentCopyElement) {
			x = BSDLocationUtils.getObjectLocationX(contentCopyElement); 
			y = BSDLocationUtils.getObjectLocationY(contentCopyElement);
			width = contentCopyElement.offsetWidth; 
			height = contentCopyElement.offsetHeight;
			var copyObject = contentCopyElement.bsdContentCopy;			
			if(copyObject && copyObject.ccid) {
				bsdContentPageProperties.bsdInsertionCCID = contentCopyElement.bsdContentCopy.ccid;
			} else {
				bsdContentPageProperties.bsdInsertionCCID = ccid;
			}
			bsdContentPageProperties.bsdInsertionElement = contentCopyElement;
			bsdContentPageProperties.bsdInsertionCopyObject = copyObject;
			bsdContentPageProperties.bsdInsertionElementId = contentCopyElement.id;
			var cardinality = contentCopyElement.cardinality;
			if(cardinality) {
				if(cardinality.value) {
					bsdContentPageProperties.bsdInsertionElement.bsdDragInsertBefore = false;
				} else {
					bsdContentPageProperties.bsdInsertionElement.bsdDragInsertBefore = true;			
				}						
			}
		} else if(existingPopupWindow) {
			x = BSDLocationUtils.getObjectLocationX(existingPopupWindow.popupElement); 
			y = BSDLocationUtils.getObjectLocationY(existingPopupWindow.popupElement);
			width = existingPopupWindow.popupElement.offsetWidth; 
			height = existingPopupWindow.popupElement.offsetHeight;
		} else {
			BSDLogUtils.error("ERROR: Can't launch copy selector without one of copyElementId or existingPopupWindow");
		}

		
		var pageID = "/ajaxpages/inlinecopyselector";
		var pageArguments = {'content_copy_deployment_id' : bsdContentPageProperties.bsdInsertionCCID, 
							'content_copy_inline_editor_location_x' : x,
							'content_copy_inline_editor_location_y' : y,
							'content_copy_inline_editor_width' : width,
							'content_copy_inline_editor_height' : height
							};
		if(typeId) {
			pageArguments['type_int'] = typeId;
		}
		var parentElementId = 'AJAX_DIALOG_WINDOW_PARENT';
		
		BSDAjaxUtils.showActivityMessage('Loading...');
		BSDLogUtils.debug("showCopySelector: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doInlineContentCopySelectorReply(str, parentElementId, contentCopyElement, existingPopupWindow, replaceExistingSelector) 
					} 
				});
	},
			
	doInlineContentCopySelectorReply: function(data, parentElementId, contentCopyElement, existingPopupWindow, replaceExistingSelector) {
		if(existingPopupWindow && !replaceExistingSelector) {
			BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
		}

		if(!BSDAjaxUtils.doRenderingReply(data, parentElementId)) {
			BSDLogUtils.error("Got error on copy selector reply: " + data.errorMessage);

			return;
		}

		var popupWindow	= BSDContentCopyEditorUtils.initializePopupWindow(parentElementId);
		if(replaceExistingSelector) {
			existingPopupWindow.innerHTML = popupWindow.innerHTML;
			BSDDOMUtils.removeElement(popupWindow);


			BSDAjaxUtils.hideActivityMessage();
		}
		
		var contentCopy = data.data.CONTENT_COPY;
		if(!contentCopy) {
			BSDLogUtils.error("ERROR: Couldn't find copy object from inline selector replly");
			return;
		}



		var copyObject = bsdContentPageProperties.bsdInsertionCopyObject;
		if(!copyObject) {
			BSDLogUtils.error("ERROR: Couldn't find document copy object for insertion");
			return;
		}

		if(contentCopy.relationshipTargetTypeId) {
			copyObject.relationshipTargetTypeId = contentCopy.relationshipTargetTypeId.id;
		}
		if(contentCopy.relationshipTargetId) {
			copyObject.relationshipTargetId = contentCopy.relationshipTargetId.id;
		}
		copyObject.templateElementId = contentCopy.templateElementId;
		
		var newCopyLink = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'CONTENT_COPY_NEW_URL');
		if(!newCopyLink) {
			BSDLogUtils.warning("Warning: Couldn't find new copy link in copy select popup window");
			return;
		}

		function launchEditWindow(e) {
			BSDEventUtils.stopPropagation(e);
			BSDContentCopyEditorUtils.launchCopyEditWindowFromSelect(popupWindow);
			return false;			
		}
		
		var hasTextFields = BSDContentCopyEditorUtils.getCopyElementHasTextFields(contentCopyElement);
		if(hasTextFields == 0) {
			newCopyLink.parentNode.removeChild(newCopyLink);
		} else {
			BSDEventUtils.registerEvent(newCopyLink, "click", launchEditWindow);		
		}
		
	},
	
	
	doContentCopyDialogSelection: function(contentCopyElement, cid) {


		if(bsdContentPageProperties.bsdSelectedDialogCId == cid) {
			return; //already selected
		}
		if(bsdContentPageProperties.bsdSelectedDialogContentCopyElement) {
			bsdContentPageProperties.bsdSelectedDialogContentCopyElement.className = bsdContentPageProperties.bsdSelectedDialogContentCopyElement.oldClassName;
		}
		bsdContentPageProperties.bsdSelectedDialogCID = cid;
		bsdContentPageProperties.bsdSelectedDialogContentCopyElement = contentCopyElement;
		contentCopyElement.oldClassName = contentCopyElement.className;
		contentCopyElement.className = "BSDDialogScrollRowSelected";
	},
	
	saveContentCopyDialogSelection: function() {
		var insertionPointId = bsdContentPageProperties.bsdInsertionCCID;
		var insertionPoint = bsdContentPageProperties.bsdInsertionElement;
		var selectedCopyId = bsdContentPageProperties.bsdSelectedDialogCID;
		if(!insertionPoint && bsdContentPageProperties.bsdInsertionElementId) { //case if this is the first copy for an element in the template
			insertionPoint = BSDDOMUtils.getObjectById(bsdContentPageProperties.bsdInsertionElementId);
		} else if(!insertionPoint) {
			BSDLogUtils.error("ERROR: Couldn't find insertionPoint or insertionElementid to launch copy selection window");
			return;
		}

		BSDContentCopyEditorUtils.contentCopyMove(null, null, null, insertionPoint, selectedCopyId);			
		BSDContentCopyEditorUtils.unHighlightSiblings(insertionPoint);
	},
		
	
	launchCopyEditWindowFromSelect: function(selectWindow) {
		var insertionPointId = bsdContentPageProperties.bsdInsertionCCID;
		var insertionPoint = bsdContentPageProperties.bsdInsertionElement;
		var insertionPointElementId = bsdContentPageProperties.bsdInsertionElementId;
		var copyObject = bsdContentPageProperties.bsdInsertionCopyObject;

		if(!insertionPointId || !insertionPoint || !copyObject) {
			BSDLogUtils.error("ERROR: Missing one or more of the copy insertion data members: [" + insertionPointId + "][" + insertionPoint + "][" + copyObject + "]");
			return;
		}
		
		insertionPoint.bsdDragInsertBefore = true;
		
		BSDLogUtils.debug("Launch inline editor: " + copyObject.relationshipTargetTypeId + " " + copyObject.relationshipTargetId + " " + copyObject.ccid + " " + copyObject.templateElementId);

		
		var newCopyObject = copyObject.clone();
		newCopyObject.ccid = null;
	    newCopyObject.ccdid = null;
		var newCopyElement = BSDDOMUtils.createElement("div", null, copyObject.templateElementId);
		
		var hasTextFields = BSDContentCopyEditorUtils.getCopyElementHasTextFields(insertionPoint);
		var editorUrl, elementId1, elementId2;
		if(hasTextFields == 3) {
			elementId1 = 'content_copy_title';
			editorUrl = '/ajaxpages/inlinecopytitleeditor';
		} else if(hasTextFields == 2 || hasTextFields == 1) {
			elementId1 = 'content_copy_text';
			editorUrl = '/ajaxpages/inlinecopytexteditor';		
		} else if(hasTextFields == 4) {
			elementId1 = 'content_copy_title';
			elementId2 = 'content_copy_text';
			editorUrl = '/ajaxpages/inlinecopyeditor';		
		} else {
			BSDLogUtils.error("User requested copy edit window for copy element without text elements");
			return;
		} 
		
		BSDContentCopyEditorUtils.showInlineContentCopyEditor(newCopyElement, newCopyObject, editorUrl, elementId1, elementId2, copyObject.ccid, selectWindow);

		return false;
	},
	
	launchCopySelectWindowFromEdit: function(editWindow) {
		var insertionPointId = bsdContentPageProperties.bsdInsertionCCID;
		var insertionPoint = bsdContentPageProperties.bsdInsertionElement;
		var copyObject = bsdContentPageProperties.bsdInsertionCopyObject;
	    var copyElementId;
	    if(insertionPoint) {
	    	copyElementId = insertionPoint.id
	    } else {
	    	copyElementId = bsdContentPageProperties.bsdInsertionElementId;
	    }
		BSDContentCopyEditorUtils.showInlineContentCopySelectorById(insertionPointId, copyElementId, false, editWindow);
	},
	
	getCopyElementHasTextFields: function(copyElement) {
		var titleElement = BSDContentCopyEditorUtils.getContentCopyChildElement(copyElement, 'CONTENT_COPY_TITLE', false, copyElement.id);
		var textElement = BSDContentCopyEditorUtils.getContentCopyChildElement(copyElement, 'CONTENT_COPY_TEXT', false, copyElement.id);
		if(titleElement && textElement) {
			return 4;
		}
		if(titleElement) {
			return 3;
		}
		if(textElement) {
			return 2;
		}
		if(!BSDDOMUtils.getContainsChildElements(copyElement, "BSDPopupMenu")) {
			return 1;
		}
		return 0;
	},
	
	initializePopupWindow: function(parentElementId) {
		var parentElement = BSDDOMUtils.getObjectById(parentElementId);
		var popupWindow = new BSDPopupWindow(parentElement);
		popupWindow.doPostInitialization();
		
		BSDEventUtils.registerEvent(document, 'keypress', BSDContentCopyEditorUtils.handleKeyPress);

		return popupWindow;
	},
	
	handleKeyPress: function(e) {
		var valid = false; ; //enter or escape
		var doComplete = false;
		if(BSDEventUtils.handleKeyPress(e, 27)) {
			BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
		}

		return true;
	},
	
	resetSelectVariables: function() {
		bsdContentPageProperties.bsdInsertionCCID = null;
		bsdContentPageProperties.bsdInsertionElement = null;
		bsdContentPageProperties.bsdInsertionCopyObject = null;
		bsdContentPageProperties.bsdInsertionElementId = null;
	},
	
	deleteContentCopyById: function(contentCopyIde) {
		var pageID = '/cms/content/ajax/contentcopydeleter';
		
		var pageArguments = {'content_copy_ide' : contentCopyIde
							};

		BSDLogUtils.debug("deleteContentCopy: doNavigation");
		KCMAjaxGui.doNavigation(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDAjaxUtils.doNavigationReply(str);
					} 
				}, 'Deleting');
		
	},
	
	removeContentCopyById: function(ccid, copyElementId, copyTypeId, contentCopyIde) {
		var copyElement;
		if(copyTypeId && !contentCopyIde && (copyTypeId == '30' || copyTypeId == '2')) {
			if(!confirm('Are you sure you want to remove this from the page')) {
				return;
			}
		}
		if(copyElementId) {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		} 
		var copyObject = copyElement.bsdContentCopy;		
		BSDContentCopyEditorUtils.removeContentCopy(copyElement, copyObject, contentCopyIde);
	},
	
	removeContentCopy: function(contentCopyElement, copyObject, contentCopyIde) { 
		var pageID = '/ajaxpages/contentcopyremove';

		
		var elementId = contentCopyElement.id;


		var parentNode = contentCopyElement.parentNode;
		var foundSibling = false;
		for(var i = 0; i < parentNode.childNodes.length; i++) {
			var currentChild = parentNode.childNodes[i];
			if(currentChild.id == elementId && currentChild != contentCopyElement) {
				foundSibling = true;
			}
		}

		
		BSDAjaxUtils.showActivityMessage('Deleting...');	
		if(foundSibling) {	
			var pageArguments = {'content_copy_deployment_id' : copyObject.ccid,
							'content_copy_ide' : contentCopyIde
							};

			BSDLogUtils.debug("removeContentCopy: doNavigation");
			KCMAjaxGui.doNavigation(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doRemoveContentCopyReply(str, contentCopyElement, foundSibling) 
					} 
				});
		} else {

			var pageArguments = {'content_copy_deployment_id' : copyObject.ccid,
							'content_copy_ide' : contentCopyIde,
							'template_element_id': elementId,
							'document_url' : document.URL
							};

			BSDLogUtils.debug("removeContentCopy: doRendering");
			KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doRemoveContentCopyReply(str, contentCopyElement, foundSibling) 
					} 
				});
		
		}
	},	
	
	doRemoveContentCopyReply: function(data, contentCopyElement, foundSibling) {	
		if(!BSDAjaxUtils.doRenderingReply(data)) {
			return;
		}
		var elementId = contentCopyElement.id;
		var parentNode = contentCopyElement.parentNode;
		parentNode.removeChild(contentCopyElement);			
		
		
		if(!foundSibling) {
			BSDContentCopyEditorUtils.doContentCopyRenderReply(data, elementId, parentNode);
		}
		
	},	
	
	getParentContentCopyObject: function(contentCopyButtonElement) {
		var currentElement = contentCopyButtonElement;
		while(currentElement != null) {
		    var currentId = currentElement.id;
			
			if(currentId && currentId == 'CONTENT_COPY_TITLE') {
				return currentElement;
			} else if(currentId && currentId == 'CHILD_CONTENT_COPY_TITLE') {
				return currentElement;
			} else if(currentId && currentId == 'CONTENT_COPY_TEXT') {

				return currentElement;

			} else if(currentId && currentId == 'CHILD_CONTENT_COPY_TEXT') {

				return currentElement;

			} else if(BSDDOMUtils.containsClass(currentElement, 'COPY')) {

				return currentElement;
			} else if(BSDDOMUtils.containsClass(currentElement, 'DISCUSSION_COPY')) {

				return currentElement;
			} else if(BSDDOMUtils.containsClass(currentElement, 'DISCUSSION_RELATED_CONTENT_COPY')) {

				return currentElement;
			} else if(currentId && currentId == 'RELATED_CONTENT_COPY_TITLE') {
				return currentElement;
			} else if(currentId && currentId == 'RELATED_CONTENT_COPY_TEXT') {
				return currentElement;
			} else if(currentId && currentId == 'CHILD_CONTENT_COPY') {

				return currentElement;
			}
			currentElement = currentElement.parentNode;
		}
		return contentCopyButtonElement;	
	},
	
	getContentCopyChildElement: function(copyElement, childElementId, isChildCopy, copyElementId) {

		if(BSDDOMUtils.containsClass(copyElement, 'DISCUSSION_RELATED_CONTENT_COPY')) {
			var childElement = BSDDOMUtils.getObjectByIdFromParent(copyElement, 'RELATED_' + childElementId);

			return childElement;
		}
		var currentElement = copyElement;
		while(currentElement != null) {
		    var currentId = currentElement.id;
			if(currentId && currentId == childElementId) {
				return currentElement;
			} else if(isChildCopy && currentId == 'CHILD_CONTENT_COPY') {
				return BSDDOMUtils.getObjectByIdFromParent(currentElement, childElementId, 'COPY', 'CHILD_CONTENT_COPY');					
			} else if(BSDDOMUtils.containsClass(currentElement, 'COPY')) {

				return BSDDOMUtils.getObjectByIdFromParent(currentElement, childElementId, 'COPY', 'CHILD_CONTENT_COPY');	
			} else if(copyElementId && currentId == copyElementId) {
				return BSDDOMUtils.getObjectByIdFromParent(currentElement, childElementId, 'COPY', 'CHILD_CONTENT_COPY');				
			}
			currentElement = currentElement.parentNode;
		}

		BSDLogUtils.debug("Checking copyChildElement for children: " + childElementId + " " + isChildCopy);

		if(!BSDDOMUtils.getContainsChildElements(copyElement)) {
			return copyElement;
		}
		
		BSDLogUtils.debug("DONE Getting copyChildElement: " + childElementId + " " + isChildCopy);
		
		return null;
	},

	initializeDragManager: function() {
		var options = new BSDDragOptions(false);
		options.initializeDragDelayActions();


		options.addNodeInitializationAction(new BSDDragActions.SameIdSiblingLimiter());
		options.addValidationAction(new BSDDragActions.SetMoveCursorAction());


		options.addValidationAction(new BSDDragActions.DropzoneGroupLimiter());
		options.addValidationAction(new BSDDragActions.AllowableDropzoneImageAction());
		options.addValidationAction(new BSDDragActions.DropzoneMockInsertionAction());
		options.addValidationAction(new BSDDragActions.DragWindowScrollAction());
		options.addCompletionAction(new BSDDragActions.AllowableDropzoneImageDeleterAction());
		options.addCompletionAction(new BSDDragActions.UnSetMoveCursorAction());
		options.addCompletionAction(new BSDDragActions.DropzoneMockInsertionDeleterAction());
		options.addCompletionAction(new BSDDragActions.BorderSelectDeleterAction());
		options.addCompletionAction(new this.ContentCopyMoveAction());
		
		options.setAllowMovementOutsideParent(false);
		options.setInitialDistanceLimit(10);
		var dragManager = new BSDDragManager(options);
		bsdDragManager = dragManager;	
		
		return dragManager;	
	},
	
	fixDropdragElementPositions: function(dragManager) {
		var dropZones = dragManager.dropZoneElements;
		for(var i = 0; i < dropZones.length; i++) {
			BSDDragUtils.fixDropdragElementPosition(dropZones[i]);
		}	
	},
	
	initializeDiscussionContentCopyElements: function(parentElement) {
		var copyElements = BSDDOMUtils.getObjectsByClass("DISCUSSION_COPY", parentElement);
		BSDContentCopyEditorUtils.initializeDiscussionContentCopyElementsByElementArray(copyElements);

		var summaryElements = BSDDOMUtils.getObjectsByClass("DISCUSSION_SUMMARY_CONTENT_COPY", parentElement);
		BSDContentCopyEditorUtils.initializeDiscussionContentCopyElementsByElementArray(summaryElements);
		var detailedElements = BSDDOMUtils.getObjectsByClass("DISCUSSION_DETAILS_CONTENT_COPY", parentElement);
		BSDContentCopyEditorUtils.initializeDiscussionContentCopyElementsByElementArray(detailedElements);
		var relatedElements = BSDDOMUtils.getObjectsByClass("DISCUSSION_RELATED_CONTENT_COPY", parentElement);
		BSDContentCopyEditorUtils.initializeDiscussionContentCopyElementsByElementArray(relatedElements);
		
	},
	
	
	initializeDiscussionContentCopyElementsByElementArray: function(elements) {

		for(var i = 0; i < elements.length; i++) {
			var currentElement = elements[i];
			currentElement.bsdContentCopy = new BSDContentCopy(BSDDOMUtils.getAttributeValue(currentElement, 'ccid'), null, false);

			BSDContentCopyEditorUtils.initializeContentCopyElementForEditing(currentElement);
		}
	},

	initializeWikiContentCopyElements: function() {
		var elements = BSDDOMUtils.getObjectsById("CONTENT_WIKI_CONTENT_COPY", document);

		
		for(var i = 0; i < elements.length; i++) {
			var currentElement = elements[i];
			currentElement.bsdContentCopy = new BSDContentCopy(BSDDOMUtils.getAttributeValue(currentElement, 'ccid'), null, false);
			BSDContentCopyEditorUtils.initializeContentCopyElementForEditing(currentElement);
		}
	},
		
	initializeContentCopyElements: function(cpid, parentElement) {



		BSDDOMUtils.changeElementStyle(document.body, "margin", 0); //this is necessary due to safari bug
		var dragManager = bsdDragManager;

		if(!dragManager) {
			dragManager = BSDContentCopyEditorUtils.initializeDragManager();
		}	
	
		var elements = BSDDOMUtils.getObjectsByClass("COPY", parentElement);
		BSDContentCopyEditorUtils.initializeContentCopyElementArray(elements, dragManager, cpid);
		BSDContentCopyEditorUtils.fixDropdragElementPositions(dragManager);

		elements = BSDDOMUtils.getObjectsByClass("DISCUSSION_COPY", parentElement);
		BSDContentCopyEditorUtils.initializeContentCopyElementArray(elements, null, cpid);

	},

	initializeContentCopyElementArray: function(elements, dragManager, cpid) {
		var elementIdCounts;
		for(var i = 0; i < elements.length; i++) {
			var currentElement = elements[i];
			BSDContentCopyEditorUtils.initializeContentCopyElement(currentElement, dragManager, false, elementIdCounts, cpid);
		}
	
	},
	
	initializeContentCopyElement: function(element, dragManager, isChild, elementIdCounts, cpid) {
		var currentElementId = element.id;
		if(!currentElementId) {
			BSDLogUtils.warning("ERROR: Unable to initialize copy element without id: " + element.nodeName + " " + element.className);
			return;
		}

		var ccid = BSDDOMUtils.getAttributeValue(element, 'ccid');
		element.bsdContentCopy = new BSDContentCopy(ccid, null, isChild);		


		BSDContentCopyEditorUtils.initializeContentCopyElementForEditing(element, cpid);


		if(dragManager && !BSDDOMUtils.getAttributeValue(element, "nodrag")) {

			dragManager.addDragableElement(element); 
			dragManager.addDropZoneElement(element);

			var dropzoneGroup = BSDDOMUtils.getAttributeValue(element, "drop-zone");
			if(!dropzoneGroup || dropzoneGroup.length < 1) {
				dropzoneGroup = element.id;
			}
			if(dropzoneGroup && dropzoneGroup.length > 0) {
				element.bsdDropzoneGroup = dropzoneGroup;
			}	
			/*if(element.parentNode && !element.parentNode.isDropzone && !BSDDOMUtils.containsClass(element.parentNode, "COPY")) {

				element.parentNode
			}*/
		} else {

		}

		var childElements = BSDDOMUtils.getObjectsById("CHILD_CONTENT_COPY", element);

		for(var i = 0; i < childElements.length; i++) {
			var currentChild = childElements[i];
			BSDContentCopyEditorUtils.initializeContentCopyElement(currentChild, dragManager, true);
			element.dragElementPosition.addExclusionPosition(currentChild.dragElementPosition);
		}
		if(childElements.length > 0) {
			element.bsdDragChildElements = childElements;
		}

	},
	
	
	initializeContentCopyElementForEditing: function(element, cpid) {

		var id = element.bsdContentCopy.ccid;


		if(!id && cpid && BSDDOMUtils.containsClass(element, "COPY")) {
			BSDDOMUtils.setAttributeValue(element, "cpid", cpid);
			element.bsdContentCopy.relationshipTargetTypeId = 31;
			element.bsdContentCopy.relationshipTargetId = cpid;
		}
		if(id || cpid) {
			BSDContentCopyEditorUtils.initializeContentCopyTextEdit(element);
			BSDContentCopyEditorUtils.initializeContentCopyTitleEdit(element);
		} 
	
	},
	
	initializeContentCopyTextEdit: function(element) {
		var elementID = "CONTENT_COPY_TEXT";
		if(element.bsdContentCopy.isChild) {
			elementID = "CHILD_" + elementID;
		}
		var textElement = BSDDOMUtils.getObjectByIdFromParent(element, elementID);
		if(textElement == null) {
			if(BSDDOMUtils.getContainsChildElements(element, "BSDPopupMenu")) { //check if its copy with only text and menu children

				return;
			}
			textElement = element;
		}

		BSDEventUtils.registerEvent(textElement, "dblclick", BSDContentCopyEditorUtils.showInlineContentCopyTextEditorByEvent);

		
		/*function textEditFunction() {

			BSDContentCopyEditorUtils.showInlineContentCopyTextEditor(id, textElement, isChild);
		}
		*/
	},
	
	initializeContentCopyTitleEdit: function(element) {
		var elementID = "CONTENT_COPY_TITLE";
		if(element.bsdContentCopy.isChild) {
			elementID = "CHILD_" + elementID;
		}
		var titleElement = BSDDOMUtils.getObjectByIdFromParent(element, elementID);
		if(titleElement == null) {

			return;
		}
		BSDEventUtils.registerEvent(titleElement, "dblclick", BSDContentCopyEditorUtils.showInlineContentCopyTitleEditorByEvent);

		
		/*function titleEditFunction() {
			BSDContentCopyEditorUtils.showInlineContentCopyTitleEditor(id, titleElement, isChild);
		}*/
	},	
	
	getElementSiblingCopyCount: function(element) {
		var parent = element.parentNode;
		if(!parent) {
			BSDLogUtils.error("Got element with null parent for getElemenSiblingCountById: " + element.id);
			return;
		}
		var count = 0;
		for(var i = 0; i < parent.childNodes.length; i++) {
			var currentSibling = parent.childNodes[i];
			if(BSDDOMUtils.containsClass(currentSibling, 'COPY')) {
				count++;
			}
		}
		return count;
	},

	
	cancelContentCopyMove: function(parentElement, selectedElement, placeHolderElement, dragProperties) {
		return BSDDragUtils.cancelMove(parentElement, selectedElement, placeHolderElement, dragProperties);			
	},
	
	replacePlaceholderElement: function(selectedElement, placeHolderElement, parentElement, isRecursive) {
		return BSDDragUtils.replacePlaceholderElement(selectedElement, placeHolderElement, parentElement, isRecursive);
	},
	
	placeElementInOriginalLocation: function(selectedElement, placeHolderElement, dragProperties) {
		return BSDDragUtils.placeElementInOriginalLocation(selectedElement, placeHolderElement, dragProperties);
	},
	
	ContentCopyMoveAction: function() {
		this.executeDragAction = function(dragProperties) {

			var childClassName;
			if(!childClassName) {
			    childClassName = dragProperties.element.className;
		    }

		    var selectedElement = dragProperties.element;
		    var parentElement = selectedElement.bsdDragParent;
		    var placeHolderElement = selectedElement.bsdDragPlaceHolder;
			var dragManager = bsdDragManager;
			var targetElement;

			if(dragManager) {
				targetElement = dragManager.calculateCurrentDropZone(dragProperties);
			}
			if(!targetElement || !dragProperties.getHasMovedOutsideSelf() || dragProperties.getDoesSourceContainTarget(targetElement)
						|| dragProperties.getDoesTargetContainSource(targetElement)) {

				BSDContentCopyEditorUtils.cancelContentCopyMove(parentElement, selectedElement, placeHolderElement, dragProperties);

				return true;
			} else if(targetElement.bsdDropzoneGroup && selectedElement.bsdDropzoneGroup && targetElement.bsdDropzoneGroup != selectedElement.bsdDropzoneGroup) {
				BSDLogUtils.debug("Canceling move due to dropzoneGroup mismatch" + targetElement.bsdDropzoneGroup + " " + selectedElement.bsdDropzoneGroup);
				BSDContentCopyEditorUtils.cancelContentCopyMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;
			}
			
			var previousSibling = BSDDOMUtils.getPreviousSiblingElement(placeHolderElement);
			var nextSibling = BSDDOMUtils.getNextSiblingElement(placeHolderElement);
			if(targetElement == placeHolderElement) {
				BSDLogUtils.debug("Canceling move due to target == placeHolder");
				BSDContentCopyEditorUtils.cancelContentCopyMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;				
			} else if(selectedElement.bsdDragInsertBefore && targetElement == nextSibling) {
				BSDLogUtils.debug("Canceling move due to target == placeHolder.nextSibling");
				BSDContentCopyEditorUtils.cancelContentCopyMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;
			} else if(!selectedElement.bsdDragInsertBefore && targetElement == previousSibling) {
				BSDLogUtils.debug("Canceling move due to target == placeHolder.previousSibling");
				BSDContentCopyEditorUtils.cancelContentCopyMove(parentElement, selectedElement, placeHolderElement, dragProperties);
				return true;
			}




			return BSDContentCopyEditorUtils.contentCopyMove(dragProperties, selectedElement, parentElement, targetElement);	
		}		
	},
	
	contentCopyMove: function(dragProperties, selectedElement, selectedElementParent, targetElement, cid) {
		BSDLogUtils.debug("move 2");
		var selectedId = null;
		if(selectedElement) {
			selectedId = selectedElement.bsdContentCopy.ccid; //BSDDOMUtils.getAttributeValue(selectedElement, "ccid");			
		}

		var targetId = targetElement.bsdContentCopy.ccid; //BSDDOMUtils.getAttributeValue(targetElement, "ccid");
		if(selectedElement && selectedId == targetId) {

			var placeHolderElement;
			if(selectedElement) {
				placeHolderElement = selectedElement.bsdDragPlaceHolder;
			}
			BSDContentCopyEditorUtils.cancelContentCopyMove(selectedElementParent, selectedElement, placeHolderElement, dragProperties); //added 11/2/06

			return true;
		}


		var targetElementId = targetElement.id; //used if target is empty space - should probably send templateElementId instead
		var parentTargetElement;
		var parentTargetElementId = "";
		
		if(!BSDDOMUtils.containsClass(targetElement, 'COPY')) {
			parentTargetElement = BSDDOMUtils.getParentObjectByClass(targetElement, "COPY");
			if(parentTargetElement) {
				parentTargetElementId = parentTargetElement.id;
			}
		}

		
		var strIsBefore = "false";
		if(selectedElement && selectedElement.bsdDragInsertBefore) {
			strIsBefore = "true";
		} else if(!selectedElement && targetElement && targetElement.bsdDragInsertBefore) { //handles copy insertion case, where there's no selected element
			strIsBefore = "true";
		}

		var pageID = '/ajaxpages/contentcopymove';
		var pageArguments = {'content_copy_deployment_id' : selectedId,
							'target_content_copy_deployment_id': targetId,
							'content_copy_id' : cid,
							'template_element_id': targetElementId,
							'parent_template_element_id': parentTargetElementId,
							'is_before': strIsBefore,
							'document_url' : document.URL
							};
	
		BSDLogUtils.debug("Moving content: " + selectedId + " " + targetId + " " + cid + " " + targetElementId + " " + parentTargetElementId + " " + strIsBefore);




		BSDAjaxUtils.showActivityMessage('Saving...');	

		BSDLogUtils.debug("sontentCopyMove: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doContentCopyMoveActionReply(str, dragProperties, selectedElementParent, selectedElement, targetElement);
						BSDContentCopyEditorUtils.hideInlineContentCopyEditor(); //for copy selection operations
					} 
				});

		return true;
	
	},

	doContentCopyMoveActionReply: function(data, dragProperties, originalParentElement, selectedElement, targetElement) {

		if(!BSDAjaxUtils.doRenderingReply(data)) {
			return;
		}


		if(dragProperties) {
			BSDLogUtils.debug("Move reply: 1.1");
			var cleanupActions = bsdDragManager.dragOptions.moveCleanupActions;
			for(var i = 0; i < cleanupActions.length; i++) {
				var currentAction = cleanupActions[i];
				currentAction.executeDragAction(dragProperties);
			}


			var selectedParent = dragProperties.parentElement;	
			if(selectedParent && selectedElement.parentNode) {
				selectedElement.parentNode.removeChild(selectedElement);
			}
		}
		
		if(selectedElement) {
			var placeHolderElement = selectedElement.bsdDragPlaceHolder;
			if(placeHolderElement && placeHolderElement.parentNode) {
				selectedParent = placeHolderElement.parentNode;
				placeHolderElement.parentNode.removeChild(placeHolderElement);
			}

			if(selectedElement.id && selectedElement.id != "CHILD_CONTENT_COPY") {
				var selectedElementSiblings = BSDDOMUtils.getObjectsById(selectedElement.id);
				if(selectedElementSiblings < 2) {

					this.doContentCopyRender(selectedElement.id, selectedParent);
				}
			}

		}		

		
		var parentNode = targetElement.parentNode;
		if(!parentNode || targetElement.id == "CHILD_CONTENT_COPY") {
			while(parentNode && !BSDDOMUtils.containsClass(parentNode, 'COPY')) {
				parentNode = parentNode.parentNode;
			}
			if(parentNode) {
				parentNode = parentNode.parentNode; //need the parent of the copy node
			}
		}


		if(parentNode) {		
			parentNode.innerHTML = data.html;
			bsdDragManager.clearElements(); 
			BSDDOMUtils.buildObjectsByClassHash(); //this will be a big performance issue.  Need to find a way to initialize only what changed
			BSDContentCopyEditorUtils.initializeContentCopyElements();
			BSDContentCopyEditorUtils.initializeDiscussionContentCopyElements();
			var popupMenuNodes = BSDDOMUtils.getObjectsByClass("BSDPopupMenu", parentNode);
			BSDMenuUtils.initializeMenuArray(popupMenuNodes);			

		} else {
			BSDContentCopyEditorUtils.cancelContentCopyMove(originalParentElement, selectedElement, placeHolderElement, dragProperties); //added 11/2/06
			return;

		}
		if(!this.moveCount) {
			this.moveCount = 0;
		}
		this.moveCount++;



		
		
		return;
	},


	doContentCopyRender: function(templateElementId, parentNode) {	
		var pageID = '/ajaxpages/contentcopyrender';
		var pageArguments = {'template_element_id': templateElementId,
							'document_url' : document.URL
							};


		BSDLogUtils.debug("doContentCopyRender: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doContentCopyRenderReply(str, templateElementId, parentNode);
					} 
				});

		return true;
	},	
	
	doContentCopyRenderReply: function(data, templateElementId, parentNode) {

		if(!BSDAjaxUtils.doRenderingReply(data)) {
			return;
		}

		if(parentNode) {
			
			parentNode.innerHTML = data.html;
			BSDContentCopyEditorUtils.initializeInsertedContent(data, parentNode);
		}
		
		return;
	},
	
	
	initializeInsertedContent: function(data, parentNode) {
		var page;
		if(data && data.data) {
			page = data.data.CONTENT_PAGE;
		}
		var cpid;
		if(page && page.id) {
			cpid = page.id.id;
		}

		this.initializeContentCopyElements(cpid, parentNode);
		this.initializeDiscussionContentCopyElements(parentNode);		
		BSDMenuUtils.initializeMenus(parentNode);	
	},

	cleanupAllNonCopyElements: function() {


		BSDContentCopyEditorUtils.cleanupNonCopyElementsByNodeName("A");
		BSDContentCopyEditorUtils.cleanupNonCopyElementsByNodeName("INPUT", true);
		BSDContentCopyEditorUtils.cleanupNonCopyElementsByNodeName("SELECT", true);
		BSDContentCopyEditorUtils.cleanupNonCopyElementsByNodeName("TEXTAREA", true);		
		BSDContentCopyEditorUtils.cleanupNonCopyElementsByNodeName("EMBED", true);					
		BSDContentCopyEditorUtils.cleanupNonCopyElementsByNodeName("OBJECT", true);		
	},
	
	cleanupNonCopyElementsByNodeName: function(nodeName, setFocus) {
		/*this.doNothing = function(event) {
			alert("Doing nothing");
			BSDEventUtils.stopPropagation(event);
			return false;
		}*/
		var elements = BSDDOMUtils.getObjectsByNodeName(document, nodeName);

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






	    }	   
	},
	

	renderCopyList: function(typeId) {
		var ccid = bsdContentPageProperties.bsdInsertionCCID;
		var insertionElement = bsdContentPageProperties.bsdInsertionElement;
		var insertionElementId;
		if(insertionElement) {
			insertionElementId = insertionElement.id;
		} else {
			insertionElementId = bsdContentPageProperties.bsdInsertionElementId;
		}

		
		var existingPopupWindow = BSDContentCopyEditorUtils.getInlineContentCopyEditor();
		BSDContentCopyEditorUtils.showInlineContentCopySelectorById(ccid, insertionElementId, false, existingPopupWindow, true, typeId);

	},
	
	highlightContentCopyElement: function(ccid, copyElementId, doNearestElement, event) {
		var copyElement;
		if(doNearestElement) {
			copyElement = BSDContentCopyEditorUtils.getNearestContentCopyElement(ccid, copyElementId, event);
		} else {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		}
		if(!copyElement) {
			BSDLogUtils.debug("Couldn't find copy element for highlight with id " + ccid + " " + copyElementId);
			return;
		}

		
		if(!copyElement.bsdIsHighlighted) {
			BSDHighlightUtils.highlightElementByOverlay(copyElement);
			copyElement.bsdIsHighlighted = true;
		} 
		
	},
	
	unHighlightContentCopyElement: function(ccid, copyElementId, doNearestElement, event) {
		var copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		if(doNearestElement) {
			copyElement = BSDContentCopyEditorUtils.getNearestContentCopyElement(ccid, copyElementId, event);
		} else {
			copyElement = BSDContentCopyEditorUtils.getContentCopyElement(ccid, copyElementId);
		}

		if(!copyElement) {
			BSDLogUtils.debug("Couldn't find copy element for unhighlight with id " + ccid + " " + copyElementId);
			return;
		}
		
		if(copyElement.bsdIsHighlighted) {
			BSDHighlightUtils.unHighlightElementByOverlay(copyElement);
			copyElement.bsdIsHighlighted = false;
		}
		
	},
	
	unHighlightSiblings: function(element) {
		if(!element || !element.parentNode) {
			return;
		}

		var childNodes = element.parentNode.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			var currentChild = childNodes[i];
			if(currentChild.bsdIsHighlighted) {
				BSDLogUtils.debug("unhighlighting sibling: " + currentChild.id + " " + currentChild.className);
				BSDHighlightUtils.unHighlightElementByOverlay(currentChild);
				currentChild.bsdIsHighlighted = false;			
			}
		}
	},
	
	showPropertiesEditor: function(menuNode, elementId, copyIde, ccdid, copyPageId, notInline) {

		var pageArguments = {
							'content_copy_deployment_id' : ccdid, 
							'content_copy_ide' : copyIde,
							'content_copy_page_id' : copyPageId
							};
		
		var parentElementId = 'AJAX_DIALOG_WINDOW_PARENT';
		
		var pageID = "/ajaxpages/inlinepropertieseditor";
		BSDAjaxUtils.showActivityMessage('Loading...');
		BSDLogUtils.debug("showPropertiesEditor: doRendering");
		
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doShowPropertiesEditorReply(str, parentElementId, elementId, copyIde, ccdid, notInline);
					} 
				});

				
	},
	
	doShowPropertiesEditorReply: function(data, parentElementId, elementId, copyIde, ccdid, notInline) {
		var successful = BSDAjaxUtils.doRenderingReply(data, parentElementId);
		if(!successful) {
			alert("ERROR: A system error occured.  Please try again");
			return;
	    } else if(data.errorMessage) {
	  		BSDLogUtils.debug("Got error: " + data.errorMessage);
			alert(data.errorMessage);
			return;
		} 
		var popupWindow = BSDContentCopyEditorUtils.initializePopupWindow(parentElementId);
		popupWindow.popupElement.bsdNotInline = notInline;
		
		var featuresTab = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'FEATURE_TAB');
		if(featuresTab) {
			featuresTab.className = featuresTab.className + 'on';
		}
		
		function doChange(e) {
			var target = BSDEventUtils.fixEventTarget(e);
			target.bsdChanged = true;
		}
		
		var propertyFields = BSDDOMUtils.getObjectsByClass('CONTENT_PROPERTY_FIELD', popupWindow.popupElement);
		for(var i = 0; i < propertyFields.length; i++) {
			var currentField = propertyFields[i];
			BSDEventUtils.registerEvent(currentField, 'change', doChange);
		}
		
		var copyDeleteButton = BSDDOMUtils.getObjectByIdFromParent(popupWindow.popupElement, 'COPY_DELETE_BUTTON');
		if(copyDeleteButton) {
			var pages = BSDDOMUtils.getObjectsByClass('CONTENT_PAGE_ROW', popupWindow.popupElement);
			function doDelete(e) {
				BSDEventUtils.stopPropagation(e);
				var msg;
				if(pages.length > 0) {
					msg = "This component is used on " + pages.length + " pages. \nAre you sure you want to permanently delete it from the site?";
				} else {
					msg = "Deleting a component will completely and permanently remove it from the system.\nAre you sure you want to delete it?";
				}
				if(confirm(msg)) {
					BSDContentCopyEditorUtils.deleteContentCopyById(copyIde);
					BSDDOMUtils.removeElement(popupWindow.popupElement);					
				}
				return false;
			}
			BSDEventUtils.registerEvent(copyDeleteButton, "click", doDelete);
		}
		
		/*
		var globalField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_ARGS_GLOBAL');
		if(globalField && !ccdid) {
			globalField.disabled = true;
		}
		*/

	},
	
	saveProperties: function(editorId) {

		var popupWindow = BSDContentCopyEditorUtils.getInlineContentCopyEditor();
		
		var copyElementIdField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_ELEMENT_ID_INLINE_EDITOR');
		var copyElementId;
		BSDLogUtils.debug("Got copyElementID field: " + copyElementIdField);
		if(copyElementIdField) {
			copyElementId = copyElementIdField.value; //the id of the actual COPY dom element
			BSDLogUtils.debug("Got copyElement id: " + copyElementId);
		}
		
		var deploymentIdElement = BSDDOMUtils.getObjectById('CONTENT_COPY_DEPLOYMENT_ID_INLINE_EDITOR');
		if(!deploymentIdElement) {
			alert("ERROR: Couldn't find copyId element");
			return;
		}
		var ccdid = deploymentIdElement.value;

		var pageArguments = {
							'content_copy_deployment_id' : ccdid,
							'SAVECOPY.X' : 'true'
							};

		var copyIdeField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_IDE_INLINE_EDITOR');
		if(!ccdid && copyIdeField) {
			pageArguments['content_copy_ide'] = copyIdeField.value;
			pageArguments['content_copy_args_global'] = 'true';
		} else {
			var globalField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_ARGS_GLOBAL');
			if(globalField) {
				pageArguments['content_copy_args_global'] = globalField.checked;
			}
		}

		var copyNameField = BSDDOMUtils.getObjectByIdFromParent(popupWindow, 'CONTENT_COPY_NAME_INLINE_EDITOR');
		if(copyNameField) {
			pageArguments['content_copy_name'] = copyNameField.value;
		}
		
		var copyText;
		var copyTextField = BSDContentCopyEditorUtils.getInlineContentCopyEditorTextField(editorId);
		if(copyTextField) {
			pageArguments['content_copy_text'] = copyTextField.value;
		}

		
		var propsChanged = false;
		var propertyFields = BSDDOMUtils.getObjectsByClass('CONTENT_PROPERTY_FIELD', popupWindow);
		for(var i = 0; i < propertyFields.length; i++) {
			var currentField = propertyFields[i];
			var currentValue = BSDFormUtils.getFieldValue(currentField);

			propsChanged = propsChanged || currentField.bsdChanged;

			pageArguments[currentField.name] = currentValue;

		}
				
		var doInlineRender = propsChanged;
		if(!popupWindow.bsdNotInline && propsChanged && copyElementId) {
			pageArguments['document_url'] = document.URL;
			pageArguments['template_element_id'] = copyElementId
		} else {
			doInlineRender = false;
		}
		
		var pageID = "/ajaxpages/contentpropertiesedit";
		BSDAjaxUtils.showActivityMessage('Saving...');
		BSDLogUtils.debug("savePropertiesEditor: saving");

		if(doInlineRender) {
			KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doSavePropertiesReply(str, popupWindow, doInlineRender, copyElementId, ccdid);
					} 
				});
		} else {
			KCMAjaxGui.doNavigation(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCopyEditorUtils.doSavePropertiesReply(str, popupWindow, doInlineRender, copyElementId, ccdid);
					} 
				});
		}
		
	
	},
	
	doSavePropertiesReply: function(data, popupWindow, doInlineRender, copyElementId, ccdid) {
		if(data.httpResponseCode != 200) {
			alert("An error ocurred - please try again");
			return false;
		}

		if(doInlineRender) {
			var contentCopyElement = BSDDOMUtils.getObjectById(copyElementId);
			var parentNode = contentCopyElement.parentNode;
			BSDContentCopyEditorUtils.doContentCopyRenderReply(data, copyElementId, parentNode);
		}
		
		BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
		BSDAjaxUtils.hideActivityMessage();
		
	
	}
	


}
BSDContentCategoryEditorUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDAjaxUtils", "content/BSDContentCopyEditorUtils", "forms/BSDFormUtils"),
	VERSION: 1.2,

	showEditor: function(categoryId, categoryElementId, existingPopupWindow, parentIde) {	


		BSDLogUtils.debug("Got category element: " + categoryElementId);
		var categoryElement = BSDContentCategoryEditorUtils.getNearestCopyElement(categoryElementId);
		if(!categoryElement) {
			BSDLogUtils.error("Couldn't find category element for id " + categoryId);
			return false;
		}

		var x, y, width, height;
		if(existingPopupWindow) {
			x = BSDLocationUtils.getObjectLocationX(existingPopupWindow.popupElement); 
			y = BSDLocationUtils.getObjectLocationY(existingPopupWindow.popupElement);
		} else if(categoryElement) {
			x = BSDLocationUtils.getObjectLocationX(categoryElement); 
			y = BSDLocationUtils.getObjectLocationY(categoryElement);
		}
		var pageArguments = {'content_category_ide' : categoryId, 
							'content_copy_inline_editor_location_x' : x,
							'content_copy_inline_editor_location_y' : y,
							'content_category_parent_ide' : parentIde
							
							};
		
		BSDLogUtils.debug("Doing content category show: " + categoryId);

		var	pageID = '/ajaxpages/inlinecategoryeditor';
		BSDLogUtils.debug("showCategoryEditor: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCategoryEditorUtils.doShowEditorReply(str, categoryId, categoryElement, existingPopupWindow);
					} 
				});

		return false;
	},	
	
	doShowEditorReply: function(data, categoryId, categoryElement, existingPopupWindow) {
		var parentElementId = 'AJAX_DIALOG_WINDOW_PARENT';
		BSDLogUtils.debug("Doing show category edit reply: " + categoryId);

		if(!BSDAjaxUtils.doRenderingReply(data, parentElementId) || data.errorMessage) {
			return true;
		} 

		if(existingPopupWindow) {
			BSDContentCopyEditorUtils.hideInlineContentCopyEditor();
		}

		var parentElement = BSDDOMUtils.getObjectById(parentElementId);
		var popupWindow = new BSDPopupWindow(parentElement);
		if(categoryElement) {
			popupWindow.popupElement.categoryRowId = categoryElement.id;
			if(categoryElement.bsdContentCopy && categoryElement.bsdContentCopy.ccid) {
				popupWindow.popupElement.ccid = categoryElement.bsdContentCopy.ccid;
				BSDLogUtils.debug("Set ccid: " + popupWindow.popupElement.ccid);
			}
			
			BSDLogUtils.debug("Set catgory row id: " + popupWindow.popupElement.id + " " + popupWindow.popupElement.categoryRowId);
		}	
		
		var editor = BSDContentCategoryEditorUtils.getInlineEditor();
		var nameElement = BSDDOMUtils.getObjectByIdFromParent(editor, 'CONTENT_CATEGORY_NAME_INLINE_EDITOR');
		if(nameElement && nameElement.focus) {
			nameElement.focus();
		}

		function doKeyPress(e) {
			if(BSDEventUtils.handleKeyPress(e, 13)) {
				BSDContentCategoryEditorUtils.saveInlineEditor();
			}
			if(BSDEventUtils.handleKeyPress(e, 27)) {
				BSDContentCategoryEditorUtils.hideInlineEditor();
			}
		}
		BSDEventUtils.registerEvent(document, "keypress", doKeyPress);
		BSDContentCategoryEditorUtils.KEYFUNC = doKeyPress;
		
		
		return false;
	},
	
	saveInlineEditor: function(data) {

		var editor = BSDContentCategoryEditorUtils.getInlineEditor();
		if(!editor) {
			BSDLogUtils.debug("Couldn't find inline editor");
			return;
		}		
		var idElement = BSDDOMUtils.getObjectByIdFromParent(editor, 'CONTENT_CATEGORY_ID_INLINE_EDITOR');
		var nameElement = BSDDOMUtils.getObjectByIdFromParent(editor, 'CONTENT_CATEGORY_NAME_INLINE_EDITOR');
		var descriptionElement = BSDDOMUtils.getObjectByIdFromParent(editor, 'CONTENT_CATEGORY_DESCRIPTION_INLINE_EDITOR');
		var parentIdeElement = BSDDOMUtils.getObjectByIdFromParent(editor, 'CONTENT_CATEGORY_PARENT_IDE_INLINE_EDITOR');
		if(!idElement || !nameElement || !descriptionElement) {
			BSDLogUtils.error("Couldn't find one or more category fields: " + idElement + " " + nameElement + " " + descriptionElement);
			return;
		}
		var categoryId = idElement.value;
		var name = nameElement.value;
		var description = descriptionElement.value;
		if(!name || name.length < 1) {
			BSDContentCategoryEditorUtils.doErrorMessage(editor, "Please enter a name for the category");
			return;
		} else if(!description || description.length < 1) {
			BSDContentCategoryEditorUtils.doErrorMessage(editor, "Please enter a brief category description");
			return;
		}
		
		var allowContent = BSDFormUtils.getFieldValueById('CONTENT_CATEGORY_PARENT_ALLOW_CONTENT', editor);

		var siloContent = BSDFormUtils.getFieldValueById('CONTENT_CATEGORY_SILO_CONTENT', editor);

	
		
		var parentIde;
		if(parentIdeElement) {
			parentIde = parentIdeElement.value;
		}
		
		var categoryRowId = editor.categoryRowId;
		var parentElement;
		if(categoryRowId) {
			parentElement = BSDContentCategoryEditorUtils.getNearestCopyElement(categoryRowId, editor.ccid);
		} else {
			BSDLogUtils.error("Couldn't find category categoryRowId");
			return;
		}
		if(!parentElement) {
			BSDLogUtils.error("Couldn't find category parent element");
			return;
		}
		var templateElementId = parentElement.id;
		var ccid = editor.ccid;
		var pageArguments = {'content_category_ide' : categoryId, 
							'content_category_name' : name,
							'content_category_description' : description,
							'content_category_parent_ide' : parentIde,
							'content_category_parent_allow_content' : allowContent,
							'content_category_set_parent_allow_content' : 'true',
							'content_category_silo_content' : siloContent,
							'content_category_set_silo_content' : 'true',
							'template_element_id' : templateElementId,
							'content_copy_deployment_id' : ccid,
							'EDIT.X' : 'true',
							'document_url' :  document.URL
							};
		
		BSDLogUtils.debug("Doing content category show: " + categoryId);
		
		var	pageID = '/ajaxpages/contentcategoryedit';
		BSDLogUtils.debug("saveCategoryEditor: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCategoryEditorUtils.doSaveInlineEditorReply(str, categoryId, parentElement);
					} 
				});

		return false;
		
	},

	
	doSaveInlineEditorReply: function(data, categoryId, parentElement) {

		var editor = BSDContentCategoryEditorUtils.getInlineEditor();
		if(!editor) {
			BSDLogUtils.debug("Couldn't find inline editor");
			return;
		}		

		if(!BSDAjaxUtils.doRenderingReply(data) || data.errorMessage) {
			BSDContentCategoryEditorUtils.doErrorMessage(editor, data.errorMessage);
			return;
		} 


		if(!parentElement) {
			BSDLogUtils.error("Didn't get parent element for category doSaveInlineEditorReply");
			return;
		}

		if(parentElement.parentNode) {
			var parentNode = parentElement.parentNode;
			parentNode.innerHTML = data.html;
			BSDContentCopyEditorUtils.initializeInsertedContent(data, parentNode);
		} else {
			parentElement.innerHTML = data.html;
			BSDContentCopyEditorUtils.initializeInsertedContent(data, parentElement);
		}

		BSDContentCategoryEditorUtils.hideInlineEditor();

	},
	
		
	getNearestCopyElement: function(beginningElementId, ccid) {
		var parentElement = BSDDOMUtils.getObjectById(beginningElementId);
		BSDLogUtils.debug("Getting nearest copy: " + beginningElementId + " " + ccid + " " + parentElement);
		while(parentElement && (!BSDDOMUtils.containsClass(parentElement, 'COPY') || !parentElement.id)) {			
			parentElement = parentElement.parentNode;
		}
		if(ccid && parentElement && parentElement.bsdContentCopy && parentElement.bsdContentCopy.ccid != ccid && parentElement.parentNode) {
			var childNodes = parentElement.parentNode.childNodes;
			for(var i = 0; i < childNodes.length; i++) {
				var currentChild = childNodes[i];
				if(BSDDOMUtils.containsClass(currentChild, 'COPY') && currentChild.bsdContentCopy && currentChild.bsdContentCopy.ccid == ccid) {
					return currentChild;
				}
			}
		}
		return parentElement;
	},
	
	
	doErrorMessage: function(parentElement, message) {
		var errorRow = BSDDOMUtils.getObjectByIdFromParent(parentElement, 'ERROR_MESSAGE_ROW');
		var errorMsg = BSDDOMUtils.getObjectByIdFromParent(parentElement, 'ERROR_MESSAGE_TEXT');
		if(errorRow && errorMsg) {
			BSDVisibilityUtils.showObject(errorRow);
			BSDDOMUtils.setText(errorMsg, message);				
		} else {
			alert(message);
		}
		return false;	
	},
	
	hideInlineEditor: function() {
		var editor = BSDContentCategoryEditorUtils.getInlineEditor();
		
		if(editor) {
			BSDVisibilityUtils.hideObject(editor);
		}
		
		BSDEventUtils.removeEvent(document, 'keypress', BSDContentCategoryEditorUtils.KEYFUNC);
	},
	
	getInlineEditor: function() {
		var editorId = 'AJAX_DIALOG_WINDOW';
		var editor = BSDDOMUtils.getObjectById(editorId);
		return editor;	
	},
	
	deleteCategory: function(categoryId, categoryElementId) {	



		if(!confirm("Are you sure you want to delete this category?")) {
			return false;
		}	
		var	categoryElement = BSDContentCategoryEditorUtils.getNearestCopyElement(categoryElementId);
		if(!categoryElement) {
			BSDLogUtils.error("Couldn't find category element for delete: " + categoryElementId + " " + categoryId);
			return false;
		}
		var templateElementId = categoryElement.id;
		var ccid;
		if(categoryElement.bsdContentCopy) {
			ccid = categoryElement.bsdContentCopy.ccid;
		}
		
		var pageArguments = {'content_category_ide' : categoryId, 
							'template_element_id' : templateElementId,
							'content_copy_deployment_id' : ccid,
							'DELETE.X' : 'true',
							'document_url' :  document.URL
							};
		
		BSDLogUtils.debug("Doing content category delete: " + categoryId);
		
		var	pageID = '/ajaxpages/contentcategoryedit';
		BSDLogUtils.debug("deleteCategory: doRendering");
		KCMAjaxGui.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						BSDContentCategoryEditorUtils.doCategoryDeleteReply(str, categoryId, categoryElement);
					} 
				});

		return false;
		
	},	
	
	doCategoryDeleteReply: function(data, categoryId, parentElement) {
		if(!BSDAjaxUtils.doRenderingReply(data) || data.errorMessage) {
			BSDContentCategoryEditorUtils.doErrorMessage(parentElement, data.errorMessage);
			return;
		} 
	
		if(!parentElement) {
			BSDLogUtils.error("Got null parent element for delete");
			return;
		}

		if(parentElement.parentNode) {
			var parentNode = parentElement.parentNode;
			parentNode.innerHTML = data.html;
			BSDContentCopyEditorUtils.initializeInsertedContent(data, parentNode);
		} else {
			parentElement.innerHTML = data.html;
			BSDContentCopyEditorUtils.initializeInsertedContent(data, parentElement);
		}
		
		BSDContentCategoryEditorUtils.hideInlineEditor();
	},
	
	initializeDragManager: function() {
		var options = new BSDDragOptions(false);




		options.addValidationAction(new BSDDragActions.SetMoveCursorAction());

		var parentDragLimiter = new BSDDragActions.ParentDragLimiter();

		options.addValidationAction(parentDragLimiter);


		var mockInsertionAction = new BSDDragActions.DropzoneMockInsertionAction();
		mockInsertionAction.ignoreCcid = true;
		options.addValidationAction(mockInsertionAction);
		options.addValidationAction(new BSDDragActions.DragWindowScrollAction());


		options.addCompletionAction(new BSDDragActions.DropzoneMockInsertionDeleterAction());


		var moveAction = new BSDDragActions.MoveCompletionAction();
		moveAction.callback = BSDContentCategoryEditorUtils.categoryMove;
		options.addCompletionAction(moveAction);

		
		options.setAllowMovementOutsideParent(false);
		options.setInitialDistanceLimit(10);
		
		var dragManager = new BSDDragManager(options);
		moveAction.dragManager = dragManager;

		return dragManager;		
	},
	
	getOrCreateDragManager: function(parentElementId) {
		var parent = BSDDOMUtils.getObjectById(parentElementId);
		if(!parent) {
			BSDLogUtils.error("Couldn't find category with parent " + parentElementId);
			return;
		}
		
		var dragManager = BSDContentCategoryEditorUtils.initializeDragManager();
		parent.dragManager = dragManager;
		return dragManager;	
	},
	
	initializeCategoryList: function(parentElementId) {
		var rows = BSDDOMUtils.getObjectsByClass('CONTENT_CATEGORY_MOVE_ROW', null, null, 'xyz');//last param forces search without picking up old cached values
		BSDContentCategoryEditorUtils.initializeCategoryListRows(parentElementId, rows);
	},
	
	initializeCategoryListRows: function(parentElementId, rows) {
		if(parentElementId) {
			BSDContentCategoryEditorUtils.parentElementId = parentElementId;
		} else {
			parentElementId = BSDContentCategoryEditorUtils.parentElementId;
		}

		var dragManager = BSDContentCategoryEditorUtils.getOrCreateDragManager(parentElementId);
		for(var i = 0; i < rows.length; i++) {
			var currentChild = rows[i];
			currentChild.customDragParent = BSDDOMUtils.getParentObjectByClass(currentChild, 'CONTENT_CATEGORY_CHILD_GROUP');
			if(!currentChild.customDragParent) {
				currentChild.customDragParent = BSDDOMUtils.getParentObjectById(currentChild, 'CONTENT_CATEGORY_LIST');
			}
			
			dragManager.addDragableElement(currentChild);
			dragManager.addDropZoneElement(currentChild);
		}
		
	},	
	
	categoryMove: function(result, dragProperties, selectedElement, parentElement, targetElement) {
		var selectedId = BSDDOMUtils.getAttributeValue(selectedElement, "cat-id");
		var targetId = BSDDOMUtils.getAttributeValue(targetElement, "cat-id");

		var templateElementId = parentElement.id;
		while(parentElement && !templateElementId) {
			parent = parentElement.parentNode;
			templateElementId = parent.id;
		}

		var pageArguments = {'source_category_ide' : selectedId, 
							'target_category_ide' : targetId,
							'template_element_id' : templateElementId,
							'document_url' :  document.URL
							};
		
		BSDLogUtils.debug("Doing content category move: " + selectedId + " " + targetId);
		
		var	pageID = '/ajaxpages/contentcategorymove';
		BSDLogUtils.debug("categoryMove: doRendering");
		
		function replyCallback(data) {
			if(data && parentElement.parentNode) {
				var parentNode = parentElement.parentNode;
				BSDContentCopyEditorUtils.initializeInsertedContent(data, parentNode);
			} else if(data) {
				BSDContentCopyEditorUtils.initializeInsertedContent(data, parentElement);
			}
			BSDLogUtils.debug("Got data: " + data);
			if(selectedElement.parentNode) {
				selectedElement.parentNode.removeChild(selectedElement);
			}
			BSDContentCategoryEditorUtils.initializeCategoryList(parentElement.id);
		}
		
		BSDContentUtils.doRenderingByParentElement(templateElementId, parentElement, pageArguments, pageID, replyCallback, null, "Saving")


		return true;
		
	}

	
}
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);		
	}	
	
}
BSDAnimatedElement = BSDClass.create();
BSDAnimatedElement.DEPENDENCIES = new Array("BSDClass", "BSDDOMUtils", "BSDTimeoutManager", "BSDArrayUtils");
BSDAnimatedElement.prototype = {

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

	},

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

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

		}

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

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

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


}


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


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

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

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

}

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


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

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

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

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

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


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

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

	executeAnimation: function(displayElement, elapsedTimeMillis) {	

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

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

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


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

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

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

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

	className: "BSDAnimatedMessage",
	initialize: function(elementContainer, message, animatedCharacter, cssClass, cssClassError, frequencyMilliseconds) {	
		this.statusContainer = elementContainer;
		this.message = message;
		this.frequencyMilliseconds = frequencyMilliseconds;
		this.cssClass = cssClass;
		this.cssClassError = cssClassError;
		
		if(!this.frequencyMilliseconds) {
			frequencyMilliseconds = 1000;
		}
		if(!this.cssClass) {
			this.cssClass = "ajaxStatus";	
		}
		if(!this.cssClassError) {
			this.cssClassError = "ajaxStatusError";
		}
	
	    this.image = BSDDOMUtils.getObjectByIdFromParent(this.statusContainer, this.statusContainer.id + "_IMG");
	    this.statusElement = BSDDOMUtils.getObjectByIdFromParent(this.statusContainer, this.statusContainer.id + "_MSG");
		if(!this.statusElement) {
			this.statusElement = BSDDOMUtils.createElement("span", this.statusContainer, null, this.cssClass);			
		}
		if(this.message) {
			BSDDOMUtils.addText(this.statusElement, this.message);		
		}	
	
	    this.animatedCharacter = animatedCharacter;
		this.submitAnimationRoutines = new Array();			
		if(this.animatedCharacter) {
			BSDLogUtils.debug("Adding animated character routine: " + this.animatedCharacter);			
			BSDArrayUtils.append(this.submitAnimationRoutines, new BSDAnimatedCharacterRoutine(this.animatedCharacter, 13));				
		}
		BSDLogUtils.debug("Got message image: " + (this.image != null));

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

	},

	start: function() {
		BSDVisibilityUtils.showObject(this.statusContainer);
		if(this.image) {
			BSDVisibilityUtils.showObject(this.image);
		}
		if(this.statusElement) {
			BSDVisibilityUtils.showObject(this.statusElement);
		}		

		if(this.submitAnimationRoutines.length > 0) {
			BSDLogUtils.debug("Creating animated element");			
			this.statusProgressElement = BSDDOMUtils.createElement("span", this.statusElement);
			this.statusAnimatedElement = new BSDAnimatedElement(this.statusProgressElement, this.pageURL, this.submitAnimationRoutines, 700, 5);
			this.statusAnimatedElement.start();			
		} 
		BSDLogUtils.debug("Starting animated message: " + this.message);
	},
	
	stop: function() {
		BSDLogUtils.debug("Stopping animated message");
		this.cleanupAnimatedElement();
	},

  	doMessage: function(message, isError) {
		this.stop();
		
	  	this.statusElement = BSDDOMUtils.createElement("span", this.statusContainer);
  		BSDDOMUtils.setText(this.statusElement, message);  
  		if(isError) {
	  		this.statusElement.className = this.cssClassError;  		 			
	  	} else {
	  		this.statusElement.className = this.cssClass;  		 				  	
	  	}

	 	this.statusAnimatedElement = new BSDAnimatedElement(this.statusElement, this.pageURL, this.messageTimeoutRoutines, 5000, 3);
		this.statusAnimatedElement.start();  		
  		BSDLogUtils.debug("do message: " + message);
  	    if(this.statusContainer) {
			BSDVisibilityUtils.showObject(this.statusContainer);
		}
  		
  	},
  	
   	cleanupAnimatedElement: function() {

  		if(this.statusAnimatedElement) {
			this.statusAnimatedElement.stop();
			this.statusAnimatedElement = null;
		}
		if(this.statusElement) {
			if(this.statusElement.parentNode) {
		  		this.statusElement.parentNode.removeChild(this.statusElement);
		  	}
			this.statusElement = null;
		}
		if(this.statusProgressElement) {
			if(this.statusProgressElement.parentNode) {
				this.statusProgressElement.parentNode.removeChild(this.statusProgressElement);
			}
			this.statusProgressElement = null;
		}
		if(this.statusContainer) {
			BSDVisibilityUtils.hideObject(this.statusContainer);
		}
		if(this.image) {
			BSDVisibilityUtils.hideObject(this.image);
		}
		if(this.statusElement) {
			BSDVisibilityUtils.hideObject(this.statusElement);
		}		
		
  	} 	


}

BSDGoogleMapList = {
	ALL_GOOGLE_MAPS: new Array(),

	getMap: function(index) {
		return BSDGoogleMapList.ALL_GOOGLE_MAPS[index];
	},
	
	addMap: function(newMap) {
		BSDGoogleMapList.ALL_GOOGLE_MAPS[newMap.mapIndex] = newMap;
	}
    
}

BSDGoogleMap = BSDClass.create();
BSDGoogleMap.DEPENDENCIES = new Array ("BSDDOMUtils", "BSDEventUtils", "content/BSDContentLocationUtils", "BSDAjaxUtils", "BSDNavigationUtils", "maps/BSDGoogleMapUtils");
BSDGoogleMap.prototype = {
    map: null,
   	mapIndex: 0,
    geocoder: null,

    locationFormName: null,
    suppressMapButtons: false,
    enablePopupWindows: false,

    gmarkers: [ ],
    sidebarHtml: " ",
    markerIndex: 0,
    defaultLatitude: null,
    defaultLongitude: null,
    defaultZoomLevel: 11,
    defaultMapType: null,
    events: new Array(),
    initFunctions: new Array(),
    hasZoomLevelChanged: false,
    dynamicDataKey: null,
    pagingElementId: null,
    pointArray: new Array(),
    allOverlays: new Array(),
    /*icon0: null,
    icon1: null,*/

	className: "BSDGoogleMap",
	initialize: function(index) {
		this.mapIndex = index;
		BSDGoogleMapList.addMap(this);

	},

    load: function () {
      var mapElement = BSDDOMUtils.getObjectById("map" + this.mapIndex);
      if(!mapElement) {
	      mapElement = BSDDOMUtils.getObjectById("map");
	  }
	  
	  if(!mapElement) {
      	   BSDLogUtils.error("ERROR: Couldn't get map element");
      	   return;
      }
      
      var windowParent = BSDDOMUtils.getParentObjectByClass(mapElement, 'POPUP_WINDOW');
      if(windowParent) {
           windowParent.bsdMap = this;
      }
      
      var map = new GMap2(mapElement);
      this.map = map;
      
      BSDLogUtils.debug("Initialized map: " + this.map + " " + mapElement.id);
      
      if(this.defaultBounds) {
    	  this.setDefaultZoomLevel(this.map.getBoundsZoomLevel(this.defaultBounds));
      }

      var formlatitude = this.getLatitude();
      var formlongitude = this.getLongitude();
      if(formlatitude && formlongitude) {

	  		map.setCenter(new GLatLng(formlatitude, formlongitude), this.defaultZoomLevel, this.defaultMapType);  
	   	 	this.placeMarkerOnMap (formlatitude, formlongitude);
        
      } else if(this.mapPoints && this.mapPoints.length > 0 && this.mapPoints[0][1] && this.mapPoints[0][0]) {
	  	   map.setCenter(new GLatLng(this.mapPoints[0][0], this.mapPoints[0][1]), this.defaultZoomLevel, this.defaultMapType);
	  } else if(!this.mapPoints && !BSDTypeUtils.isUndefined(bsdGoogleMapPoints) && bsdGoogleMapPoints && bsdGoogleMapPoints.length > 0 
	  						&& bsdGoogleMapPoints[0][1] && bsdGoogleMapPoints[0][0] 
	                        && !this.suppressMapButtons && !this.enablePopupWindows) {
	  	   map.setCenter(new GLatLng(bsdGoogleMapPoints[0][0],bsdGoogleMapPoints[0][1]), this.defaultZoomLevel, this.defaultMapType);

	  } else if(!this.mapPoints && !BSDTypeUtils.isUndefined(bsdGoogleMapPoints) && bsdGoogleMapPoints && bsdGoogleMapPoints.length > 0 
	  						&& bsdGoogleMapPoints[0][1] && bsdGoogleMapPoints[0][0] 
	                        && this.suppressMapButtons && !this.enablePopupWindows) {
	  	   map.setCenter(new GLatLng(bsdGoogleMapPoints[0][0],bsdGoogleMapPoints[0][1]), this.defaultZoomLevel, this.defaultMapType);        
	  } else if(this.defaultLatitude && this.defaultLongitude) {
	       map.setCenter(new GLatLng(this.defaultLatitude, this.defaultLongitude), 
	                 this.defaultZoomLevel, this.defaultMapType); 
	  } else {

	       map.setCenter(new GLatLng(39.83, -98.58), 3, this.defaultMapType);      	  
	  }
	  
	  if(!this.suppressMapButtons && !this.enablePopupWindows) {
		   this.setZoomLevel();
	  }
	  
	  
	  if(!this.suppressMapButtons && !this.enablePopupWindows) {
	    map.addControl(new GSmallMapControl());
	  	map.addControl(new GMapTypeControl());
	  } else if(!this.suppressMapButtons && this.enablePopupWindows) {
	  		map.addControl(new GLargeMapControl());
	  		map.addControl(new GMapTypeControl());
	  } else {
	  		map.addControl(new GSmallZoomControl());
	  }
	  map.enableContinuousZoom();
	  map.disableScrollWheelZoom();
	  this.geocoder = new GClientGeocoder();
	  
	  /*var icon0 = this.icon0;
	  icon0 = new GIcon();
	  icon0.image = "http://labs.google.com/ridefinder/images/mm_20_blue.png";
      icon0.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
      icon0.iconSize = new GSize(12, 20);
      icon0.shadowSize = new GSize(22, 20);
      icon0.iconAnchor = new GPoint(6, 20);
      icon0.infoWindowAnchor = new GPoint(5, 1);
      
	  var icon1 = this.icon1;
      icon1 = new GIcon();
	  icon1.image = "http://labs.google.com/ridefinder/images/mm_20_red.png";
      icon1.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
      icon1.iconSize = new GSize(12, 20);
      icon1.shadowSize = new GSize(22, 20);
      icon1.iconAnchor = new GPoint(6, 20);
      icon1.infoWindowAnchor = new GPoint(5, 1);*/
      
  
	  
	
	  this.addPoints();
	  if(this.enablePopupWindows) {
	      var sidebar = document.getElementById("sidebar");
	      if(sidebar) {
		  	  sidebar.innerHTML = this.sidebarHtml;
		  }
	  }
	  if(this.doMapClick || (!this.suppressMapButtons && !this.enablePopupWindows)) {
	      var mapElementClick = mapElement.onclick;
		  var bsdMap = this;
	      if(mapElementClick) {
		      bsdMap.mapElementClick = mapElementClick;
		  	  function doMapClick() {
		  	  	  bsdMap.mapElementClick();
		  	  }

	      	this.addMapEvent("click", doMapClick);
	      	mapElement.onclick = null;
	  		if(mapElementClick) {
		      	this.addMapEvent("click", mapElementClick);
		    }
	      	mapElement.onclick = null;
	      }
	      var mapObj = this;
	      this.addMapEvent("click", function(overlay, point) {
		  	  if(overlay && bsdMap.enablePopupWindows) {
		  	  	return;
		  	  }
	          map.clearOverlays();
	          bsdMap.addPoints();

			  var form = bsdMap.getForm();
			  if(!form) {
			  	  return;
			  }

	          var addressElement = form.elements['content-location-address'];
	          if(addressElement) {
		          addressElement.value = "";
		      }
	      	  if(point) {
	      	  	  var gMarker = new GMarker(point/*, icon1*/);
	              map.addOverlay(gMarker);
	              map.panTo(point);
	              if(!bsdMap.suppressMapButtons && !bsdMap.enablePopupWindows) {
	              	  var zoomlevel = bsdMap.getZoomValue();
	              	  bsdMap.setZoomValue (zoomlevel);
	              }

				  bsdMap.doClick(point, gMarker);

		     	  if(bsdMap.mapClickCallback) {
		     	  	  bsdMap.mapClickCallback.call(this, overlay, point, gMarker);
		     	  }
	          }
          });
      }
      
      this.initializeEvents();
      this.doInitializeFunctions();
    },
    
    preInitializeMapPoint: function(newPoint) {
    	this.mapPoints[this.mapPoints.length] = newPoint;
    },
    
    doClick: function(point, gMarker) {
		var latitude = point.lat();
		var longitude = point.lng();
	
		this.setFormLatitudeAndLongitude(latitude, longitude);

		this.checkFieldReset(latitude, longitude); //clear the form
		var bsdMap = this;
		function showLocFromDb() {
			bsdMap.showLocationFromDatabase(arguments);
		}

		this.lastGMarker = gMarker;
		BSDContentLocationUtils.doSearchByAddress(showLocFromDb, latitude, longitude, this.getZoomlevel(), this.suppressNavigation, this.pageID, this.getForm());
    
    	var deleteElement = BSDDOMUtils.getObjectByIdFromParent(this.getForm(), 'LOCATION_DELETE_CHECKBOX');
    	if(deleteElement) {
    		deleteElement.checked = false;
    	}
    },
    
    
    getForm: function() {
		var form = document.forms[this.locationFormName];
	    if(!form) {
	    	form = BSDDOMUtils.getObjectById(this.locationFormName);
	    }
	    if(!form) {
	    	BSDLogUtils.error("Couldn't find form: " + this.locationFormName);
	        for(var i = 0; i < document.forms.length; i++) {
				BSDLogUtils.error("Form: [" + document.forms[i].name + "]");	          
	        }
	        return;
	   	}
	   	return form;
    },
    
    addInitFunction: function(func) {
    	this.initFunctions[this.initFunctions.length] = func;
    },
    
    doInitializeFunctions: function() {
    	for(var i = 0; i < this.initFunctions.length; i++) {
    		this.initFunctions[i].call();
    	}
    },
    
    addMapEvent: function(event, handler) {
    	if(this.map) {
    		GEvent.addListener(this.map, event, handler);    
    	} else {
    		var array = this.events[event];
    		if(!array) {
    			array = new Array();
    			this.events[event] = array;
    		}
    		array[array.length] = handler;
    	}
	    
    },
    
    initializeEvents: function() {
    	for(var event in this.events) {
    		var handlers = this.events[event];
    		if(handlers) {
    			for(var i = 0; i < handlers.length; i++) {
    				this.addMapEvent(event, handlers[i]);
    			}
    		}	
    	}
    	
    	if(this.dynamicDataKey) {
    		var mapObj = this;
	    	function doRefresh(e) {
	    		mapObj.doDynamicScroll(e);
	    	}
	    	
	    	this.addMapEvent("moveend", doRefresh);
	    }
    },
    
    getLatitude: function() {

    	var element = this.getLatitudeElement();
    	if (element) {
    		var latitude = element.value;
    		return latitude;
    	} else {
    		return null;
    	}
    },
    
    getLongitude: function() {

        var element = this.getLongitudeElement();
        if(element) {
        	var longitude = element.value;
			return longitude;
		} else {
			return null;
		}
	},
	
	getZoomlevel: function() {
	    var form = this.getForm(); 
	    if(!form) {
	    	return null;
	    }
	    var zoomElement = this.getForm().elements['map-zoom-level'];
	    if(zoomElement) {
			var formzoom = zoomElement.value;
			return formzoom;
		} else {
			return null;
		}
	},
	
	getLatitudeElement: function() {
		var form = this.getForm();
		if(form) {
    		var element = form.latitude;
    		if(!element) {
    			BSDLogUtils.error("ERROR: Couldn't find longitude form element with name latitude");
    		}
    		return element;
    	} else {
    		BSDLogUtils.error("ERROR: Couldn't find location form with name " + this.locationFormName);
    		return null;
    	}
    	
    },
    
    getLongitudeElement: function() {
    	var form = this.getForm();
    	if(form) {
    		var element = form.longitude;
    		if(!element) {
    			BSDLogUtils.error("ERROR: Couldn't find longitude form element with name longitude");
    		}
    		return element;
    	} else {
    		BSDLogUtils.error("ERROR: Couldn't find location form with name " + this.locationFormName);
    		return null;
    	}
    },
    
    clear: function() {
    	this.map.clearOverlays();
    	this.clearLocationFormFields();
    },
		
	addPoints: function() {
		var points = this.mapPoints;
	    if(!this.mapPoints && (BSDTypeUtils.isUndefined(bsdGoogleMapPoints) || !bsdGoogleMapPoints)) {
	    	return;
	    } else if(!this.mapPoints) {
	    	points = bsdGoogleMapPoints;
	    }
	    if(!points) {
	    	points = this.pointArray;
	    }
	    
	    this.addPointsByArray(points);
	    if(this.tmpOverlays) {
	    	for(var i = 0; i < this.tmpOverlays.length; i++) {
	    		this.addOverlay(this.tmpOverlays[i]);
	    	}
	    }
	},
	
	addPointsByArray: function(pointArray) {
		this.pointsById = new Array();
		for(var i = 0; i < pointArray.length; i++) {
			var pointObj = pointArray[i];
			this.addPoint(pointObj);
	    }
	},
	
	addPoint: function(pointObj) {
		var point = new GPoint(pointObj[1],pointObj[0]);
		if(pointObj[1] != 0 && pointObj[0] != 0) {
		    var popuphtml = pointObj[4];
		    var title = pointObj[3];
		    var iconPath;
		    var width;
		    var height;
		    if(pointObj.length > 8) {
		    	iconPath = pointObj[6];
		    	width = pointObj[7];
		    	height = pointObj[8]
		    }
		    var pointId = pointObj[5];
			var marker = this.createMarker(point,/* this.icon0,*/ title, popuphtml, pointId, iconPath, width, height);
			this.map.addOverlay(marker);
			marker.bsdPointId = pointId;
			this.pointsById[pointId] = marker;
			this.pointArray[this.pointArray.length] = pointObj;

			return marker;
		}	
	},
	
	createMarker: function(point,/* icon0,*/ title, popuphtml, markerId, iconPath, width, height) {
		var options;
		if(iconPath) {
			if(iconPath.indexOf('/') < 0) {
				iconPath = '/media/xsite/map/' + iconPath;
			}

			var curIcon = new GIcon(G_DEFAULT_ICON);
			if(!width || width == 0) {
				width = 20;
			} 
			if(!height || height == 0) {
				height = 20;
			}
			curIcon.iconSize = new GSize(width, height);
			curIcon.shadow = null;
			curIcon.iconAnchor = new GPoint(width/2, height/2);
			curIcon.image = iconPath;
			curIcon.infoWindowAnchor = new GPoint(width/2, height/2);
			curIcon.imageMap = [0,0, width,0, width,height, 0,height];
			if(iconPath.indexOf('Active') > -1) {
				curIcon.transparent = '/media/xsite/map/transparentMarkerActive.png';
			} else {
				curIcon.transparent = '/media/xsite/map/transparentMarker.png';
			}		
			
			options = {icon: curIcon};
		}
		var marker = new GMarker(point, options);
		
		if(this.enablePopupWindows) {

		    var popuphtml = "<div id=\"DISCUSSION_MAP_POPUP\">" + popuphtml + "<\/div>";
			GEvent.addListener(marker, "click", function() {
				marker.openInfoWindowHtml(popuphtml);
				return false;
	        });
	        this.gmarkers[this.markerIndex] = marker;
	        marker.bsdTitle = title;
	        this.createSidebarEntry(markerId, title);

	        this.markerIndex++;        
	    }
		return marker;
	},
	
	createSidebarEntry: function(markerId, title) {
		this.sidebarHtml += '<div style="margin:5px"><a href="javascript:BSDGoogleMapList.getMap(' + this.mapIndex + ').myclick(\'' + markerId + '\')">' + title + '</a></div><div class="sidebarHtml"></div>';
	},

	myclick: function(id) {
		var marker = this.pointsById[id];
		if(marker) {
	    	GEvent.trigger(marker, "click");
	    }
    },
    
    doDynamicScroll: function() {
		var pageArguments = {
		 					'do_ajax_load' : true,
							'template_element_id' : this.headerElementId,
							'document_url' :  document.URL
							};
		BSDNavigationUtils.populateQueryArgs(pageArguments, true, true);
		this.populateBounds(pageArguments);

		
		var	pageID = '/instantjournalist/locations/locationlistdynamicload';

		var bsdMap = this;
		BSDAjaxUtils.doRendering(pageID, pageArguments, 
				{ 
					callback:function(str) { 
						bsdMap.doDynamicScrollReply(str);
						BSDAjaxUtils.hideActivityMessage();
					} 
				}, null, "Loading");
    },

	doDynamicScrollReply: function(data) {

    	if(!data.html) {
    		BSDLogUtils.error("COuldn't find html from map dynamic scroll");
    		return;
    	}
		var headerElement = BSDDOMUtils.replaceElementByIdAndHtml(this.headerElementId, data.html);
		if(headerElement && this.pagingElementId) {
			var pagingElement = BSDDOMUtils.getObjectByIdFromParent(headerElement.parentNode, this.pagingElementId);
			var pagingElement2 = BSDDOMUtils.getObjectById(this.pagingElementId + "2");
			if(pagingElement && pagingElement2) {
				pagingElement2.innerHTML = pagingElement.innerHTML;
			}
		}
		
		if(!data) {

			BSDLogUtils.error("Didn't get any results from map dynamic scroll");
			return; 
		} else if(!data.data) {
			BSDLogUtils.error("Got error from map dynamic scroll: " + data.errorMessage);
			return;
		} else if(!data.data['LOCATED_CONTENT_ARRAY']) {
			BSDLogUtils.error("Got null results from map dynamic scroll");
			return;
		}
		
		var locationArray = data.data['LOCATED_CONTENT_ARRAY'];

    	
    	this.sidebarHtml = '';
    	var curLocationsById = new Array();
    	var oldMarkers = this.gmarkers;
    	this.gmarkers = new Array();
    	this.markerIndex = 0;
    	for(var i = 0; i < locationArray.length; i++) {
    		var curPoint = locationArray[i];
    		var curId = curPoint[5];
    		var curMarker = this.pointsById[curId];
    		if(!curMarker) {
	    		curMarker = this.addPoint(curPoint);
				this.pointsById[curId] = curMarker;    		

    		} else {

		        this.createSidebarEntry(curId, curPoint[3]);    		
			    this.gmarkers[this.markerIndex] = curMarker;
		        this.markerIndex++;
    		}
		    curLocationsById[curId] = curMarker;
    	}

    	for(var i = 0; i < oldMarkers.length; i++) {
    		var overlay = oldMarkers[i];
			if(!curLocationsById[overlay.bsdPointId]) {

	    		this.map.removeOverlay(overlay);
	    		this.pointsById[overlay.bsdPointId] = null;
	    	} else {

	    	}
    	}


		
	    var sidebar = document.getElementById("sidebar");
	    if(sidebar) {
			sidebar.innerHTML = this.sidebarHtml;
		}
		
	},
      
    populateBounds: function(pageArguments) {
    	var bounds = this.map.getBounds();
    	var topRight = bounds.getNorthEast();
    	var bottomLeft = bounds.getSouthWest();
    	
    	pageArguments['ne_lat'] = topRight.lat();
    	pageArguments['ne_lng'] = topRight.lng();
    	pageArguments['sw_lat'] = bottomLeft.lat();
    	pageArguments['sw_lng'] = bottomLeft.lng();
    	
    },
      
	setFormLatitudeAndLongitude: function (latitude, longitude) {

	   	var latElement = this.getLatitudeElement();
	   	if(latElement) {
			latElement.value = latitude;
	   	}
	   	var longElement = this.getLongitudeElement();
	   	if(longElement) {
	   		longElement.value = longitude;
	   	}
	},
	
	displayLocation: function (latLongElement) {
		this.map.clearOverlays();
		this.addPoints();
		var latitude = BSDDOMUtils.getAttributeValue(latLongElement, 'latitude');
		var longitude = BSDDOMUtils.getAttributeValue(latLongElement, 'longitude');
		this.placeMarkerOnMap(latitude, longitude);
	},
	

	
	setZoomValue: function (zoomvalue) {
	   var zoomValueElement = BSDDOMUtils.getObjectById("MAP_ZOOM_LEVEL");
	   if(zoomValueElement) {
		   zoomValueElement.value = zoomvalue;
	   }
	},
	
	setZoomLevel: function(zoomlevel) {
	    if(!zoomlevel) {
			var formzoom = this.getZoomlevel();
			if(formzoom) {
				zoomlevel = parseInt(formzoom);
		    }
		} else {
			this.setZoomValue("" + zoomlevel);
		}
		if(zoomlevel) {
			this.map.setZoom(zoomlevel);
		}			
	},
	
	setZoomLevelIfUnchanged: function(zoomlevel) {
		if(!this.hasZoomLevelChanged) {
			this.setZoomLevel(zoomlevel);
			this.hasZoomLevelChanged = true;				
		}
	},
	
	/*submitLocationForm: function () {
        document.forms[this.locationFormName].elements['LOOKUP_LOCATION.X2'].name= "LOOKUP_LOCATION.X";

	},
	
	*/



    addAddressToMap: function (response) {
      	this.map.clearOverlays();
      	this.addPoints();
        var point = new Object();
      	if(!response || response.Status.code != 200) {

      		return point;
      	} else {
	    	var place = response.Placemark[0];

        	point.latitude = place.Point.coordinates[1];
        	point.longitude = place.Point.coordinates[0];
	    	
			this.setFormLatitudeAndLongitude(point.latitude, point.longitude);
        	this.placeMarkerOnMap(point.latitude, point.longitude);

        	var zoomLevel = this.setLocationFormFields(place);
        	if(zoomLevel) {

	        	this.setZoomLevel(zoomLevel);
	        }

        	

      	}

		return point;
    },
    
    placeMarkerOnMap: function (latitude, longitude) {
    	if(!latitude || !longitude || latitude == 0 || longitude == 0) {
    		return;
    	}

        point = new GLatLng(latitude, longitude);
        marker = new GMarker(point/*, this.icon1*/);
		this.map.panTo(point);
        this.map.addOverlay(marker);
    },

	setLocationFormFields: function(place) {
		var countryCode = '';
		var state = '';
		var county = '';
		var city = '';
		var postalCodeNumber = '';
		var minZoomLevel;
		if(place && place.AddressDetails.Country) {
        	countryCode = place.AddressDetails.Country.CountryNameCode;
        	minZoomLevel = 3;
        	var adminArea = place.AddressDetails.Country.AdministrativeArea;
        	if(adminArea) {
        		state = adminArea.AdministrativeAreaName;
        		if(state) {
        			minZoomLevel = 5;
        		}
        		var subArea = adminArea.SubAdministrativeArea;
        		var locality = adminArea.Locality;


        		
        		if(subArea) {
        			county = subArea.SubAdministrativeAreaName;
        			if(county) {
			        	minZoomLevel = 8;
			        }
			        if(subArea.Locality) {	
				        locality = subArea.Locality;
				    }


				}
       			if(locality) {			        	
       				city = locality.LocalityName;
       				if(city) {
       					minZoomLevel = 9;
       				}
				    var postalCode = locality.PostalCode;

       				if(postalCode) {
       					postalCodeNumber = postalCode.PostalCodeNumber;
       					if(postalCodeNumber) {
       						minZoomLevel = 12;        						
       					}


       				}
       				var thoroughfare = locality.Thoroughfare;
       				if(thoroughfare && thoroughfare.ThoroughfareName && city) {

       					minZoomLevel = 16;

       				}
       			}
        	}
        }
        BSDDOMUtils.setTextById('MAP_LOCATION_COUNTRY', countryCode);
        BSDDOMUtils.setTextById('MAP_LOCATION_COUNTRY_SELECT', countryCode);
		BSDDOMUtils.setTextById('MAP_LOCATION_STATE', state);
		BSDDOMUtils.setTextById('MAP_LOCATION_CITY', city);
		BSDDOMUtils.setTextById('MAP_LOCATION_COUNTY', county);
		BSDDOMUtils.setTextById('ZIP_CODE_ZIP', postalCodeNumber);
		
		return minZoomLevel;
	},
	
	clearLocationFormFields: function() {
		this.setLocationFormFields();

	},
	

	doAddressSearch: function(suppressNavigation) {
		this.clearLocationFormFields(); //clear the form
		this.suppressNavigation = suppressNavigation;
		var bsdMap = this;
		function geocodeCallback(response) {
			bsdMap.doAddressSearchGeocodeCallback(response);
		}
		this.showLocation(geocodeCallback);		
		return false;
	},
	
	doAddressSearchGeocodeCallback: function(response) {
		var point = this.addAddressToMap(response);
		this.checkFieldReset(point.latitude, point.longitude);
		var bsdMap = this;
		function showDbLocation() {
			bsdMap.showLocationFromDatabase(arguments);
		}
		var parentForm = this.getForm();
		return BSDContentLocationUtils.doSearchByAddress(showDbLocation, point.latitude, point.longitude, this.getZoomlevel(), this.suppressNavigation, this.pageID, parentForm);
	},
	
	getFormObject: function(id) {
		var form = this.getForm();
		if(form) {
			return BSDDOMUtils.getObjectByIdFromParent(form, id);
		} else {
			return BSDDOMUtils.getObjectById(id);
		}
	},
	
	doAddressSearchByForm: function(locationElement) {
        var mapElement = this.getFormObject('CONTENT_LOCATION_ADDRESS');
        var addressElement = this.getFormObject('ADDRESS_LINE_1');
        var cityElement = this.getFormObject('ADDRESS_CITY');
        var stateElement = this.getFormObject('ADDRESS_STATE');
        var zipElement = this.getFormObject('ADDRESS_ZIP');

        var address= '';
        if(locationElement && locationElement.value && locationElement.value.length > 0) {
             address += locationElement.value + " ";
        }
        if(addressElement && addressElement.value && addressElement.value.length > 0) {
             address += addressElement.value + " ";
        }
        if(cityElement && cityElement.value && cityElement.value.length > 0) {
             address += cityElement.value + " ";
        }
        if(stateElement && stateElement.value && stateElement.value.length > 0) {
             address += stateElement.value + " ";
        }
        if(zipElement && zipElement.value && zipElement.value.length > 0) {
             address += zipElement.value + " ";
        }
        if(address.length > 4) {
             mapElement.value = address;
             this.doAddressSearch(true);
        }
	},	



    showLocation: function(geocodeCallback) {
	    /*if(this.isInCall) {
	    	return;
	    }
	    isInCall = true;
	    */

      	var address = this.getForm().elements['content-location-address'].value;
      	if(address && address.length > 0) {
      		if(!geocodeCallback) {
      			geocodeCallback = this.addAddressToMap;	
      		}
      		var bounds = this.map.getBounds();
      		this.geocoder.setViewport(bounds);
	    	this.geocoder.getLocations(address, geocodeCallback); 
	      	return true;     	
      	} else {
      	  	var latitude = this.getLatitude();
      	  	var longitude = this.getLongitude();
      	  	if(latitude && longitude) {

	          	this.checkFieldReset(latitude, longitude);
				var parentForm = this.getForm();
      	  		BSDContentLocationUtils.doSearchByAddress(this.showLocationFromDatabase, latitude, longitude, this.getZoomlevel(), this.suppressNavigation, this.pageID, parentForm);
	      	} else {
	      	 	alert("Please enter a zip code or address, or click to select a point on the map");
	      	}
	      	return false;
	   
      	}
    },
    
    showLocationFromDatabase: function () {
    	/*if(this.isInCall) {
    		return;
    	}
    	inInCall = true; */
    	var address = this.getForm().elements['content-location-address'].value;
    	var latitude = this.getLatitude();
    	var longitude = this.getLongitude();

    	if((address && address.length > 0) || (latitude && latitude.length != 0 && latitude != "0")) {
    		this.placeMarkerOnMap(latitude, longitude);
    	} else {
    		alert("You must enter a zipcode/address or click the map");
   		}
    },

    findLocation: function (address) {
      	this.getForm().elements['content-location-address'].value = address;
      	this.showLocation();
    },
    
    panTo: function(lat, lng) {
    	this.map.panTo(new GLatLng(lat, lng));
    },
    
    getZoomValue: function () {
       	zoomvalue = this.map.getZoom();
       	return zoomvalue;
    },
    
    getMapTypeName: function() {
       var type = this.map.getCurrentMapType();
       var strType;
       if(type.getName() == G_SATELLITE_MAP.getName()) {
           strType = 'G_SATELLITE_MAP';
       } else if(type.getName() == G_HYBRID_MAP.getName()) {
           strType = 'G_HYBRID_MAP';
       } else {
           strType = 'G_NORMAL_MAP';
       }
       return strType;
    },
	
	setIgnorePageOnload: function(ignore) {
		this.ignorePageOnload = ignore;
	},
	
	setDoMapClick: function(doClick) {
		this.doMapClick = doClick;
	},
	
	setSuppressNavigation: function(doSuppress) {
		this.suppressNavigation = doSuppress;
	},
	
	setPageID: function(newPageID) {
		this.pageID = newPageID;
	},
	
	initializePage: function (formName, suppressMapButtons, enablePopupWindows) {
	    this.locationFormName = formName;
	    this.suppressMapButtons = suppressMapButtons;
	    this.enablePopupWindows = enablePopupWindows;
	    var bsdMap = this;
	    if(!this.ignorePageOnload) {
		    function loadFunc(){
		    	bsdMap.load();
		    }
			BSDEventUtils.registerEvent(window, "load", loadFunc); 
		} else {
			this.postLoadEnabled = true;
		}
		BSDEventUtils.registerEvent(window, "unload", GUnload);
	},
	
	doPostInit: function() {
		if(this.postLoadEnabled) {
			this.load();
		}
	},
	
	setDefaultPosition: function(latitude, longitude) {
		this.defaultLatitude = latitude;
		this.defaultLongitude = longitude;
	},
	
	setDefaultBounds: function(bounds) {
		this.defaultBounds = bounds;
	},

	setDefaultZoomLevel: function(zoomLevel) {
		this.defaultZoomLevel = zoomLevel;
	},
	
	setDefaultMapType: function(newType) {
		this.defaultMapType = newType;
	},
	
	getCenter: function() {

		return this.map.getCenter();
	},
	
	checkResize: function() {
		this.map.checkResize();
		this.map.panTo(this.map.getCenter());
	},
	
	checkFieldReset: function(latitude, longitude) {
		var newPoint = new GLatLng(latitude, longitude);

		
		if(!this.oldPoint || this.oldPoint.distanceFrom(newPoint) > 2500) {
			this.clearLocationFormFields();
			this.oldPoint = newPoint;
		}
		
	},
	
	addOverlay: function(overlay) {
		if(this.map) {		
			this.map.addOverlay(overlay);
		} else {
			if(!this.tmpOverlays) {
				this.tmpOverlays = new Array();
			}
			this.tmpOverlays[this.tmpOverlays.length] = overlay;
		}
		this.allOverlays[this.allOverlays.length] = overlay;
		if(overlay.bsdPopupHtml) {
		    var popuphtml = "<div id=\"DISCUSSION_MAP_POPUP\">" + overlay.bsdPopupHtml + "<\/div>";
		    var map = this.map;
			GEvent.addListener(overlay, "click", function() {
				var position;
				if(overlay.lat && overlay.lng) {
					position = new GLatLng(overlay.lat, overlay.lng);
				} else if(overlay.getBounds) {
					position = overlay.getBounds().getCenter();
				}
				map.openInfoWindowHtml(position, popuphtml);
				return false;
	        });

		}
		return overlay;
	},
	
	removeOverlay: function(overlay) {
		return this.map.removeOverlay(overlay);	
	},
	
	setDynamicDataKey: function(dataKey) {
		this.dynamicDataKey = dataKey;
	},
	
	setPagingElementId: function(elementId) {
		this.pagingElementId = elementId;
	},
	
	setHeaderElementId: function(elementId) {
		this.headerElementId = elementId;
	},
	
	/*
	doGeocoding: function(latitude, longitude) {
		this.geocoder.getLocations(address, this.doGeocodingReply); 
		
	},
	
	doGeocodingReply: function(response) {
      	if(!response || response.Status.code != 200) {
        	BSDLogUtils.error("Couldn't get geocoding result: " + response.Status.code);
        	return;
		} 
		
		String
		
	},
	*/
	
	debugZoom: function() {
		var sw = this.map.getBounds().getSouthWest();
		var ne = this.map.getBounds().getNorthEast();
		var nw = new GLatLng(ne.lat(), sw.lng());
		
		var height = nw.lat() - sw.lat(); // nw.distanceFrom(sw);
		var width = ne.lng() - nw.lng(); //nw.distanceFrom(ne);

		
		var avgLat = (sw.lat() + nw.lat())/2;
		var correction = 1;
		if(avgLat > 40) {
			correction = (85.18 - (85.15-71.474)/10 * (avgLat - 40))/111.194;
		} else {
			correction = (96.297 - (96.297-85.18)/10 * (avgLat - 30))/111.194;			
		}

	    height = height/correction;
	    
	    var widthPerPixel = width/this.map.getSize().width;
	    var heightPerPixel = height/this.map.getSize().height;

		var msg = "";
		msg += this.map.getZoom();
		msg += " " + this.map.getSize();
		msg += " " + this.map.getBounds();

		
		alert(this.map.getZoom() + " " + this.map.getBounds().getCenter() + " " + this.map.getBounds().getSouthWest() + " " + nw + " " + this.map.getBounds().getNorthEast());
	}	
	
}
	
	
	

	
BSDGoogleMapUtils = {
	DEPENDENCIES: new Array ("BSDDOMUtils", "BSDEventUtils", "content/BSDContentLocationUtils", "maps/BSDGoogleMap"),
	VERSION: 1.1,
				
	bsdMap: new BSDGoogleMap(),		  
	getMap: function(index) {
		if(!index) {
			index = 0;		
		}
		var map = BSDGoogleMapList.getMap(index);
		if(!map) {
			map = bsdMap;
		}
		return map;
	},

    load: function () {
		return BSDGoogleMapUtils.getMap().load();      
    },
    
    addInitFunction: function(func) {
    	return BSDGoogleMapUtils.getMap().addInitFunction(func);
    },
    
    doInitializeFunctions: function() {
		return BSDGoogleMapUtils.getMap().doInitializeFunctions();
	},
    
    addMapEvent: function(event, handler) {
		return BSDGoogleMapUtils.getMap().addMapEvent(event, handler);
    },
    
    initializeEvents: function() {
		return BSDGoogleMapUtils.getMap().initializeEvents();
	},
    
    getLatitude: function() {
		return BSDGoogleMapUtils.getMap().getLatitude();
    },
    
    getLongitude: function() {
		return BSDGoogleMapUtils.getMap().getLongitude();
	},
	
	getZoomlevel: function() {
		return BSDGoogleMapUtils.getMap().getZoomLevel();
	},
	
	getLatitudeElement: function() {
		return BSDGoogleMapUtils.getMap().getLatitudeElement();
    },
    
    getLongitudeElement: function() {
		return BSDGoogleMapUtils.getMap().getLongitudeElement();
    },
		
	addPoints: function() {
		return BSDGoogleMapUtils.getMap().addPoints();
	},
	
	createMarker: function(point,/* icon0,*/ title, popuphtml) {
		return BSDGoogleMapUtils.getMap().createMarker(point, title, popuphtml);
	},

	myclick: function(i) {
		return BSDGoogleMapUtils.getMap().myclick(i);
    },
      
	setFormLatitudeAndLongitude: function (latitude, longitude) {
		return BSDGoogleMapUtils.getMap().setFormLatitudeAndLongitude(latitude, longitude);
	},
	
	displayLocation: function (latLongElement) {
		return BSDGoogleMapUtils.getMap().displayLocation(latLongElement);
	},
	
	setZoomValue: function (zoomvalue) {
		return BSDGoogleMapUtils.getMap().setZoomValue(zoomvalue);
	},
	
	setZoomLevel: function(zoomlevel) {
		return BSDGoogleMapUtils.getMap().setZoomLevel(zoomlevel);
	},
	
	setZoomLevelIfUnchanged: function(zoomlevel) {
		return BSDGoogleMapUtils.getMap().setZoomLevelIfUnchanged(zoomlevel);
	},
	
	/*submitLocationForm: function () {
        document.forms[BSDGoogleMapUtils.locationFormName].elements['LOOKUP_LOCATION.X2'].name= "LOOKUP_LOCATION.X";

	},
	
	*/



    addAddressToMap: function (response) {
		return BSDGoogleMapUtils.getMap().addAddressToMap(response);		
    },
    
    placeMarkerOnMap: function (latitude, longitude) {
		BSDGoogleMapUtils.getMap().placeMarketOnMap(latitude, longitude);
    },

	setLocationFormFields: function(place) {
		return BSDGoogleMapUtils.getMap().setLocationFormFields(place);
	},
	
	clearLocationFormFields: function() {
		return BSDGoogleMapUtils.getMap().clearLocationFormFields();
	},
	

	doAddressSearch: function(suppressNavigation) {
		return BSDGoogleMapUtils.getMap().doAddressSearch(suppressNavigation);
	},
	
	doAddressSearchGeocodeCallback: function(response) {
		return BSDGoogleMapUtils.getMap().doAddressSearchGeocodeCallback(response);
	},



    showLocation: function(geocodeCallback) {
		return BSDGoogleMapUtils.getMap().showLocation(geocodeCallback);
    },
    
    showLocationFromDatabase: function () {
		return BSDGoogleMapUtils.getMap().showLocationFromDatabase();
    },

    findLocation: function (address) {
		return BSDGoogleMapUtils.getMap().findLocation(address);
    },
    
    panTo: function(lat, lng) {
		return BSDGoogleMapUtils.getMap().panTo(lat, lng);
    },
    
    getZoomValue: function () {
		return BSDGoogleMapUtils.getMap().getZoomValue();
    },
    
    getMapTypeName: function() {
		return BSDGoogleMapUtils.getMap().getMapTypeName();
    },
	
	initializePage: function (formName, suppressMapButtons, enablePopupWindows) {
		return BSDGoogleMapUtils.getMap().initializePage(formName, suppressMapButtons, enablePopupWindows);
	},
	
	setDefaultPosition: function(latitude, longitude) {
		return BSDGoogleMapUtils.getMap().setDefaultPosition(latitude, longitude);
	},
	
	setDefaultBounds: function(bounds) {
		return BSDGoogleMapUtils.getMap().setDefaultBounds(bounds);
	},

	setDefaultZoomLevel: function(zoomLevel) {
		return BSDGoogleMapUtils.getMap().setDefaultZoomLevel(zoomLevel);
	},
	
	setDefaultMapType: function(newType) {
		return BSDGoogleMapUtils.getMap().setDefaultMapType(newType);
	},
	
	getCenter: function() {
		return BSDGoogleMapUtils.getMap().getCenter();
	},
	
	checkFieldReset: function(latitude, longitude) {
		return BSDGoogleMapUtils.getMap().checkFieldReset(latitude, longitude);		
	},
	
	addOverlay: function(overlay) {
		return BSDGoogleMapUtils.getMap().addOverlay(overlay);	
	},

	removeOverlay: function(overlay) {
		return BSDGoogleMapUtils.getMap().removeOverlay(overlay);	
	},
	
	setDynamicDataKey: function(dataKey) {
		return BSDGoogleMapUtils.getMap().setDynamicDataKey(dataKey);
	},
	
	/*
	doGeocoding: function(latitude, longitude) {
		BSDGoogleMapUtils.geocoder.getLocations(address, BSDGoogleMapUtils.doGeocodingReply); 
		
	},
	
	doGeocodingReply: function(response) {
      	if(!response || response.Status.code != 200) {
        	BSDLogUtils.error("Couldn't get geocoding result: " + response.Status.code);
        	return;
		} 
		
		String
		
	},
	*/
	
	debugZoom: function() {
		return BSDGoogleMapUtils.getMap().debugZoom();
	}	
	
}
	
	
	

	
BSDContentLocationUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "content/BSDContentUtils", "animation/BSDAnimatedMessage", "maps/BSDGoogleMapUtils", "BSDNavigationUtils"),

	initializeLocationSelector: function() {
		var lookupButton = BSDDOMUtils.getObjectById('LOCATION_LOOKUP_BUTTON');
		if(!lookupButton) {
			BSDLogUtils.error("Couldn't find location lookup button");
			return;
		}
		
		
	},
	
	doSearchByAddress: function(resultCallback, latitude, longitude, zoomlevel, suppressNavigation, pageID, parentNode) {

	    var addressElement;
	    if(parentNode) {
		    addressElement = BSDDOMUtils.getObjectByIdFromParent(parentNode, 'CONTENT_LOCATION_ADDRESS');
	    } else {
	    	addressElement = BSDDOMUtils.getObjectById('CONTENT_LOCATION_ADDRESS');
	    }
	    if(!addressElement) {
	    	BSDLogUtils.error("ERROR: Couldn't find location search results address field with id CONTENT_LOCATION_ADDRESS");
	    	return;	    
	    }
	    var locationAddress = addressElement.value;
		var countryCode = BSDDOMUtils.getTextById('MAP_LOCATION_COUNTRY', parentNode);
		var state = BSDDOMUtils.getTextById('MAP_LOCATION_STATE', parentNode);
		var county = BSDDOMUtils.getTextById('MAP_LOCATION_COUNTY', parentNode);
		var city = BSDDOMUtils.getTextById('MAP_LOCATION_CITY', parentNode);
		var zip = BSDDOMUtils.getTextById('ZIP_CODE_ZIP', parentNode);

		
	    if(!locationAddress && !latitude && !longitude && !zoomlevel) {
	    	BSDLogUtils.debug("Didn't find a value for location address");

	    	return;
	    }
	    var arguments = {};
	    if(locationAddress && locationAddress.length > 0) {
		    arguments['content_location_address'] = locationAddress;
		}
		if(city && city.length > 0) {
		    arguments['content_location_city'] = city;
		}
		if(state && state.length > 0) {
		    arguments['content_location_state'] = state;
		}
		if(zip && zip.length > 0) {
		    arguments['content_location_postal_code'] = zip;
		}
		if(countryCode && countryCode.length > 0) {
		    arguments['content_location_country'] = countryCode;
	    }
	    if(latitude) {
		    arguments['latitude'] = latitude;
		}
		if(longitude) {
			arguments['longitude'] = longitude;
		}
		if(zoomlevel) {    
		    arguments['map-zoom-level'] = zoomlevel;
		}
	    var statusContainer;
	    if(parentNode) {
	    	statusContainer = BSDDOMUtils.getObjectByIdFromParent(parentNode, 'CONTENT_LOCATION_SEARCH_STATUS');
	    } else {
			statusContainer = BSDDOMUtils.getObjectById('CONTENT_LOCATION_SEARCH_STATUS');
		}
	    var statusAnimation; 
	    if(statusContainer) {
	    	statusAnimation = new BSDAnimatedMessage(statusContainer);
	    	statusAnimation.start();
	    } else {
	   		BSDLogUtils.warning("WARNING:  Couldn't find location search status container with id CONTENT_LOCATION_SEARCH_STATUS");
	    }
	    
	    var resultsElementId = 'CONTENT_LOCATION_SEARCH_RESULTS';
	    
	    function resultsCleanup(data, message, isError) {
	    	if(statusAnimation) {
	    	    if(message) {
	    	    	statusAnimation.doMessage(message, isError);
	    	    } else {
			    	statusAnimation.stop();
			    }
	    	}
	    	
	    	var resultsElement;
	    	if(parentNode) {
	    		resultsElement = BSDDOMUtils.getObjectByIdFromParent(parentNode, resultsElementId);
	    	} else {
	    		resultsElement = BSDDOMUtils.getObjectById(resultsElementId);
	    	}
	    	if(resultsElement) {
		    	BSDVisibilityUtils.showObject(resultsElement);
				var resultRow = BSDDOMUtils.getObjectsByClass('CONTENT_LOCATION_ROW', resultsElement);
				BSDLogUtils.debug("Got location row: " + (resultRow.length));
				var locationCreateOption = BSDDOMUtils.getObjectByIdFromParent(resultsElement, 'CONTENT_LOCATION_OTHER');
				
				if(resultRow.length < 1) {
					var locationCreate = BSDDOMUtils.getObjectByIdFromParent(resultsElement, 'CONTENT_LOCATION_CREATE');
					BSDLogUtils.debug("Got create row: " + (locationCreate != null));
					if(locationCreate) {
						BSDLogUtils.debug("Showing create row: " + locationCreate.innerHTML);
						BSDVisibilityUtils.showObject(locationCreate);
					} else {
						BSDLogUtils.error("ERROR: Couldn't find location creation node: CONTENT_LOCATION_CREATE");
					}
					if(locationCreateOption) {
						locationCreateOption.checked = true;
					}
					BSDVisibilityUtils.hideById('USER_PROFILE_CONTENT_LOCATION_RESULTS_LABEL');
				} else if(locationCreateOption) {
					locationCreateOption.checked = false;
				}
				
			}    	
			if(resultCallback) {
				resultCallback.call(this);
			}
	    }
	    
	    var results;
	    if(parentNode) {
		    results = BSDDOMUtils.getObjectByIdFromParent(parentNode, resultsElementId);
	    } else {
	    	results = BSDDOMUtils.getObjectById(resultsElementId);
	    }
	    if(!results) {
	    	BSDLogUtils.error("ERROR: Couldn't find location search results element: " + resultsElementId);
	    	return;
	    }
	    
	    var strDoClear = BSDDOMUtils.getAttributeValue(results, "preclear");
	    if(!strDoClear || strDoClear != "false") {
		    results.innerHTML = null;	    
	    }

	    if(parentNode) {
		    BSDContentUtils.doRenderingByParentElement(resultsElementId, parentNode, arguments, null, resultsCleanup, pageID, null, true);
	    } else {
			BSDContentUtils.doRendering(resultsElementId, arguments, null, resultsCleanup, pageID);
		}
		if(!suppressNavigation) {
			BSDNavigationUtils.navigateTo("#location");
		}
		return false;
	},
	
    toggleLocationCreate: function(button) {
        /*
    	var locationSelect = document.forms['ProfileForm'].elements['content-location-ide'];
        var value = locationSelect.value;

        for(var i = 0 ; i < document.forms['ProfileForm'].elements.length; i++) {
        	var currentFormElement = document.forms['ProfileForm'].elements[i];
            BSDLogUtils.debug(currentFormElement.type + " " +currentFormElement.name + " " + currentFormElement.value + " " + currentFormElement.checked);             
            if(currentFormElement.type == 'radio') {

            }

        }
        */
		var resultsElement;
		if(button) {
			resultsElement = BSDDOMUtils.getParentObjectById(button, 'CONTENT_LOCATION_SEARCH_RESULTS');
		} 
		if(!resultsElement) {
		    resultsElement = BSDDOMUtils.getObjectById('CONTENT_LOCATION_SEARCH_RESULTS');
		}
		
        var otherRadio = BSDDOMUtils.getObjectByIdFromParent(resultsElement, 'CONTENT_LOCATION_OTHER');
		var locationCreate = BSDDOMUtils.getObjectByIdFromParent(resultsElement, 'CONTENT_LOCATION_CREATE');
		BSDLogUtils.debug("Got other radio: " + otherRadio.id + " " + otherRadio.checked);
        if(otherRadio.checked) {
        	BSDVisibilityUtils.showObject(locationCreate);
        } else {
        	BSDVisibilityUtils.hideObject(locationCreate);
        }
    },
    

	
	
	CONTENT_LOCATION_ROW_REGEX: new RegExp(/CONTENT_LOCATION(?:_ROW)?(?:_\d+)?$/),	
	getContentLocationParentElement: function(linkElement) {
		var locationElement = linkElement;
		while(locationElement) {

			if(locationElement.id && locationElement.id.match(BSDContentLocationUtils.CONTENT_LOCATION_ROW_REGEX)) {
				break;
			}
			if(locationElement.menuSourceNode) {
				locationElement = locationElement.menuSourceNode;
			} else {
				locationElement = locationElement.parentNode;
			}
		}
		
		if(!locationElement) {
			locationElement = BSDDOMUtils.getObjectById('CONTENT_LOCATION_ROW');
		}
		if(!locationElement) {
			locationElement = BSDDOMUtils.getObjectById('CONTENT_LOCATION');
		}
		
		if(locationElement) {
			BSDLogUtils.debug("Found location row: " + locationElement.id);
		} else {
			BSDLogUtils.debug("Couldn't find location row");
		}
		
		return locationElement;
	}	
		
	
	
}

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

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

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

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

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

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

	},

  	initializeImages: function() {

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

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

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

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

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

			return false;			
		}  		

		function imageMouseoutHandler(e) {

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

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

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


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

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


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

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

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


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

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

		  	
  	},
  	
   	cleanupStatusAnimatedElement: function() {

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


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

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

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

BSDValidatedForm.prototype = {

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

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

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

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

	doFormSubmit: function(e) {

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

		if(result.isValid) {

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

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

	doValidation: function() {

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

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

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

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


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

		}

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

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

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


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

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

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

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

			return formValidator.doFormSubmit(e);			
		}   


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

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

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

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

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

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

BSDValidatedFormUtils = {
	reinitializeForms: function() {

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

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

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

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

	}
}

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

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

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

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

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

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

		var result = validator.validate(value);

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

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


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

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

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


		return result;		
	},

	
	clearError: function() {

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


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

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

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


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

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

}


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

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

}


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

}



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

}

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

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

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

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

	return false;
}

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

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

}
		


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

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

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

}


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

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

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

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

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

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

		
		function doSubmitReply(data) {

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

			function handleMouseUp(e) {

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

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

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

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

	
    
}






BSDPopupUtils = {
	DEPENDENCIES: new Array("BSDDOMUtils", "BSDVisibilityUtils", "BSDLocationUtils", "BSDEventUtils", "BSDArrayUtils", "content/BSDContentUtils", "popup/BSDPopupPosition", "BSDPopupWindow", "BSDTypeUtils"),
	
	OPEN_POPUPS: new Array(),
	ALL_POPUPS: {},
	ALL_POPUP_OBJECTS: {},
	
	launchPopup: function(popupId, clickElement, position, level, retainWidth, dynamicUrl, dynamicArguments, clickFunction) {

		var popupElement = BSDDOMUtils.getObjectById(popupId);
		if(!popupElement) {
			BSDLogUtils.warning("WARNING: Couldn't find popup element with id: " + popupId);
			return true;
		}
		
		if(dynamicUrl && dynamicUrl.length > 0) {
			return BSDPopupUtils.launchDynamicPopup(popupElement, clickElement, position, level, retainWidth, dynamicUrl, dynamicArguments, clickFunction);
		} else {
			return BSDPopupUtils.launchPopupByElement(popupElement, clickElement, position, level, retainWidth);
		}
		
		return false; //keeps firefox from scrolling the page
	},

	launchDynamicPopup: function(popupElement, clickElement, position, level, retainWidth, dynamicUrl, dynamicArguments, clickFunction, launchCallback) {
		var contentElement = BSDDOMUtils.getObjectByIdFromParent(popupElement, 'POPUP_CONTENT');

		if(!contentElement) {
			BSDLogUtils.warning("WARNING: Couldn't find POPUP_CONTENT element for popup with id: " + popupElement.id);
			return true;		
		}
		
		BSDAjaxUtils.showActivityMessage('Loading');
		
		popupElement.isDynamic = true;
		function popupCallback(data, message, isError) {
	    	if(isError) {
	    	    if(message) {
	    	    	BSDDOMUtils.setText(contentElement, message);
	    	    } else {
			    	BSDDOMUtils.setText("Couldn't load page " + dynamicUrl);
			    }
			    return true;
	    	}

		    var popupObject = BSDPopupUtils.launchPopupByElementInternal(popupElement, clickElement, position, level, retainWidth);			
		    BSDAjaxUtils.hideActivityMessage();
		    
		    if(clickFunction && BSDTypeUtils.isString(clickFunction)) {
		    	clickFunction = clickFunction.replace("BSDPopupUtils.closeCurrentWindow()", "BSDPopupUtils.closePopupByElement(popupElement)");
		    	function clickHandler(e) {
		    		BSDEventUtils.fixEventTarget(e);
		    		eval(clickFunction);
		    		BSDEventUtils.stopPropagation(e);
		    	}
				BSDEventUtils.registerEvent(contentElement, "click", clickHandler);
		    } else if(clickFunction) {
				BSDEventUtils.registerEvent(contentElement, "click", clickFunction);
		    }
		    if(launchCallback && launchCallback.apply) {
		    	launchCallback.call(this, popupObject);
		    }
	    }

	    var pageArguments;
	    if(dynamicArguments) {
			pageArguments = dynamicArguments;
		} else {
			pageArguments = {};			
		}
	    
		var pageID = '/ajaxpages/pagerender';
		
		BSDContentUtils.doRenderingByParentElement(null, contentElement, pageArguments, pageID, popupCallback, dynamicUrl, "Loading...");
		return false;
	},

	
	launchMouseoverPopup: function(popupId, parentElement, triggerElement, orientation, deltaX, deltaY) {
		var position = new Object();
		position.orientation = orientation;
		position.deltaX = deltaX;
		position.deltaY = deltaY;
		
		var popupElement = BSDDOMUtils.getObjectById(popupId);
		if(!parentElement && popupElement) {
			parentElement = popupElement.parentNode;
		}
		if(!triggerElement) {
			triggerElement = parentElement;
		}

		var failure = BSDPopupUtils.launchPopup(popupId, parentElement, position, null, true);	
		if(!failure && triggerElement && !popupElement.isCloseEventRegistered) {
			function popupClose(e) {
				var triggerPosition = new BSDElementPosition(triggerElement);
				var popupPosition = new BSDElementPosition(popupElement);
				var eventPosition = BSDLocationUtils.getEventPosition(e);
				if(!triggerPosition.containsPosition(eventPosition) && !popupPosition.containsPosition(eventPosition)) {
					BSDVisibilityUtils.hideObject(popupElement);
				}				
			}
			BSDEventUtils.registerEvent(triggerElement, "mouseout", popupClose);
			BSDEventUtils.registerEvent(popupElement, "mouseout", popupClose);
			BSDEventUtils.registerEvent(document.body, "click", popupClose);
			popupElement.isCloseEventRegistered = true;
		}
		
		BSDVisibilityUtils.showObject(popupElement);
	},

	launchPopupByElement: function(popupElement, clickElement, position, level, retainWidth) {
		var popupObject = BSDPopupUtils.launchPopupByElementInternal(popupElement, clickElement, position, level, retainWidth);
		if(popupObject) {
			return false;
		} else {
			return true;
		}
	},
	
	launchPopupByElementInternal: function(popupElement, clickElement, position, level, retainWidth) {


		var popupObject = BSDPopupUtils.initializePopup(popupElement);
		popupElement.level = level; //level is used to determine which popups should be closed when this one is open (upper or equal == close, otherwise leave open)


		
		if(popupElement.isDynamic) {
			BSDPopupUtils.movePopupChildren(popupElement);
			var customButtonRows = BSDDOMUtils.getObjectsByClass('POPUP_CUSTOM_BUTTON_ROW', popupElement);
			if(customButtonRows.length > 0) {
				var standardRow = BSDDOMUtils.getObjectByIdFromParent(popupElement, 'POPUP_BUTTON_ROW');
				if(standardRow) {
					BSDDOMUtils.removeElement(standardRow);
				}
			}
		}

			BSDLocationUtils.makeElementAbsolutelyPositioned(popupElement, clickElement, retainWidth);

		
		if(position && position.orientation == 'parent' && popupElement.parentPopupId) {
			var absoluteParent = BSDPopupUtils.ALL_POPUPS[popupElement.parentPopupId];
			if(absoluteParent) {
				BSDLocationUtils.cloneElementLocation(absoluteParent, popupElement);

			}
		}
	
		BSDPopupUtils.closeOpenPopups(popupElement);
		if(BSDPopupUtils.OPEN_POPUPS.length < 1) {
			BSDPopupUtils.initializeDocumentEvents(popupElement);
		} 

		BSDVisibilityUtils.showObject(popupElement);

			BSDPopupUtils.setPopupOrientation(popupElement, position);
			BSDLocationUtils.positionElementWithinWindow(popupElement, true);
			BSDPopupUtils.adjustPopupPosition(popupElement, position);  //intentionally positioned after withinWindow check to allow it to be overridden
			popupElement.isPositioned = true;

		
		if(popupObject) {
			popupObject.doPostInitialization();
		}
		
		BSDArrayUtils.append(BSDPopupUtils.OPEN_POPUPS, popupElement);
		BSDPopupUtils.ALL_POPUPS[popupElement.id] = popupElement;
		BSDPopupUtils.ALL_POPUP_OBJECTS[popupElement.id] = popupObject;

		BSDLogUtils.debug("Launched popup " + popupElement.id + " " + new BSDElementPosition(popupElement) + " " + BSDVisibilityUtils.isObjectHidden(popupElement));
		
		BSDPopupUtils.debugPopupParents(popupElement);
		
		if(popupElement.bsdMap && popupElement.bsdMap && popupElement.bsdMap.checkResize) {
			popupElement.bsdMap.checkResize();
		}
		
		return popupObject;
	},
	
	debugPopupParents: function(popupElement) {
		var parent = popupElement.parentNode;
		while(parent) {

			parent = parent.parentNode;
		}
	},
	
	getAbsolutelyPositionedParent: function(popupElement) {
		var parentNode = popupElement.parentNode;
		while(parentNode) {
			if(BSDLocationUtils.getIsAbolutelyPositioned(parentNode)) {
				return parentNode;
			}
			parentNode = parentNode.parentNode;
		}		
	},
	
	setPopupOrientation: function(popupElement, position) {
		if(position && position.orientation) {
			BSDLocationUtils.setElementOrientation(popupElement, position.orientation);
		}	
	
	},
	
	adjustPopupPosition: function(popupElement, position) {
		if(position && (position.deltaX || position.deltaY)) { 
			var point = new Object();
			if(position.deltaX) {
				point.x = position.deltaX;
			}
			if(position.deltaY) {
				point.y = position.deltaY;
			}
			BSDLocationUtils.adjustElementLocation(popupElement, point);
		} else if(position && (position.x || position.y)) { 
			BSDLocationUtils.setElementLocation(popupElement, position);
		} 
		 
	},
	
	closeOpenPopups: function(newPopup, e, doCompletion) {
		BSDLogUtils.debug("Closing open popups: " + (newPopup != null));
		var popupsToClose = new Array();
		var maxOpenLevel;
		if(!newPopup) {
		    for(var i = 0; i < BSDPopupUtils.OPEN_POPUPS.length; i++) {
				var currentPopup = BSDPopupUtils.OPEN_POPUPS[i];
				if(!maxOpenLevel || currentPopup.level > maxOpenLevel) {
					maxOpenLevel = currentPopup.level;
				}
			}
		}
	    for(var i = 0; i < BSDPopupUtils.OPEN_POPUPS.length; i++) {
			var currentPopup = BSDPopupUtils.OPEN_POPUPS[i];
			var parent;
			if(newPopup) {
				parent = BSDPopupUtils.getParentPopup(newPopup);
			}
			if(newPopup && parent) {
				BSDPopupUtils.detachChild(newPopup, parent);
			}
			BSDLogUtils.debug("Trying close: " + maxOpenLevel + " " + currentPopup.level);
			if((newPopup && currentPopup.level >= newPopup.level) || currentPopup.level >= maxOpenLevel) {

				BSDArrayUtils.append(popupsToClose, currentPopup);
			}
			BSDLogUtils.debug("Tried close");
		}
		BSDLogUtils.debug("Closing popups " + popupsToClose.length);
		for(var i = 0; i < popupsToClose.length; i++) {
			BSDLogUtils.debug("Closing open popup: " + popupsToClose[i].id);
			BSDPopupUtils.closePopupByElement(popupsToClose[i], e, doCompletion);
		}
	},
	
	closePopupByElement: function(popupElement, e, doCompletion) {
				
		if(doCompletion && popupElement.completeFunction) {
			var completeValue = popupElement.completeFunction.call();
			if(!completeValue) {
				return;
			}
		}		

		BSDVisibilityUtils.hideObject(popupElement, popupElement.ignoreHideDisplay);

		for(var i = 0; i < BSDPopupUtils.OPEN_POPUPS.length; i++) {
			var currentPopup = BSDPopupUtils.OPEN_POPUPS[i];
			if(currentPopup == popupElement) {
				if((currentPopup.isDynamic)) {
					var content = BSDDOMUtils.getObjectByIdFromParent(currentPopup, 'POPUP_CONTENT'); //only remove the content, since we need the outer part to plug the content back into later
					if(content) {
						content.innerHTML = null;
					}
				}
				BSDArrayUtils.deleteElement(BSDPopupUtils.OPEN_POPUPS, i);
				break;	
			}
		}
		
		if(BSDPopupUtils.OPEN_POPUPS.length < 1) {
			BSDPopupUtils.removeDocumentEvents();
		}
		
		var popupObject = BSDPopupUtils.ALL_POPUP_OBJECTS[popupElement.id];
		if(popupObject) {
			popupObject.close(e);
		}
		
		if(popupElement.closeFunction) {
			popupElement.closeFunction.call();
		}
	},
	
	handleKeyPress: function(e) {
		var valid = false; ; //enter or escape
		var doComplete = false;
		if(BSDEventUtils.handleKeyPress(e, 13)) {
			if(!BSDPopupUtils.isPopupCloseOnEnterDisabled(e)) {
				doComplete = true;
				valid = true;
			}
		} else if(BSDEventUtils.handleKeyPress(e, 27)) {
			valid = true;
		}
		if(valid) {
			BSDPopupUtils.closeOpenPopups(null, e, doComplete);
			return false;
		}
		return true;
	},
	
	handleEscape: function(e) {
		var valid = false; ; //enter or escape
		var doComplete = false;
		if(BSDEventUtils.handleKeyPress(e, 27)) {
			valid = true;
		}
		if(valid) {
			BSDPopupUtils.closeOpenPopups(null, e, doComplete);
			return false;
		}
		return true;
	},
	
	isPopupCloseOnEnterDisabled: function(e) {
		var node = BSDEventUtils.fixEventTarget(e);
		while(node) {
			if(BSDDOMUtils.containsClass(node, 'POPUP_FORM')) {
				return true;
			} else if (BSDDOMUtils.containsClass(node, 'STATIC_POPUP_WINDOW')) {
			    return false;
			} else if(node.bsdPopupCloseOnEnterDisabled) {
				return true;
			}
			node = node.parentNode;
		}
	},
	
	initializePopup: function(popupElement) {
		
		function closeButtonClick(e) {

			BSDPopupUtils.closeOpenPopups(popupElement, e, false);
			return false;
		}
		
		function completeButtonClick(e) {

			BSDPopupUtils.closeOpenPopups(popupElement, e, true);
			return false;
		}
		
		var closeButtons = BSDDOMUtils.getObjectsByClass('popupCloseButton', popupElement);
		for(var i = 0; i < closeButtons.length; i++) {
			var currentButton = closeButtons[i];
			if(BSDPopupUtils.getParentPopup(currentButton, popupElement)) {
				BSDLogUtils.debug("Adding close button event: " + popupElement.id + " " + currentButton.id + " " + currentButton.className);
				BSDEventUtils.registerEvent(currentButton, 'click', closeButtonClick);
			}
		}

		var completeButtons = BSDDOMUtils.getObjectsByClass('popupCompleteButton', popupElement);
		for(var i = 0; i < completeButtons.length; i++) {
			var currentButton = completeButtons[i];
			if(BSDPopupUtils.getParentPopup(currentButton, popupElement)) {
				BSDLogUtils.debug("Adding complete button event: " + popupElement.id + " " + currentButton.id + " " + currentButton.className);
				BSDEventUtils.registerEvent(currentButton, 'click', completeButtonClick);
			}
		}
		
		var popup = new BSDPopupWindow(popupElement);
		popupElement.bsdPopupInitialized = true;	
		return popup;
	},
	
	initializeDocumentEvents: function() {
		BSDEventUtils.registerEvent(document, 'keypress', BSDPopupUtils.handleKeyPress);
	},
	
	removeDocumentEvents: function() {
		BSDEventUtils.removeEvent(document, 'keypress', BSDPopupUtils.handleKeyPress);
	},
	
	getParentPopup: function(childElement, popupElement) {
		var parent = childElement.parentNode;
		while(parent) {

			if(popupElement && parent == popupElement) {
				return parent;
			} else if(BSDPopupUtils.isPopupWindow(parent)) {
				if(popupElement) {
					return; //logic: if parent is passed, return false since this isn't a part of the parent.  else true as it is part of a window
				} else {
					return parent;
				}
			}
			parent = parent.parentNode;
		}
		return;
	},
	
	isPopupWindow: function(element) {
		if(!element.id || !element.className) {
			return false;
		}
		return BSDDOMUtils.containsClass(element, 'POPUP_WINDOW')
			    || BSDDOMUtils.containsClass(element, 'STATIC_POPUP_WINDOW') 
			    || BSDDOMUtils.containsClass(element, 'DYNAMIC_POPUP_WINDOW');
	},
	
	movePopupChildren: function(popupElement, currentElement) {
		if(currentElement && BSDPopupUtils.isPopupWindow(currentElement)) {
			BSDPopupUtils.detachChild(currentElement, popupElement);
		} else if(!currentElement) {
			currentElement = popupElement;
		}
		
		var childNodes = currentElement.childNodes;
		for(var i = 0; i < childNodes.length; i++) {
			BSDPopupUtils.movePopupChildren(popupElement, childNodes[i]);
		}
			
	},
	
	detachChild: function(currentElement, parent) {
		var existingWindow = BSDPopupUtils.ALL_POPUPS[currentElement.id];

		if(existingWindow && existingWindow != currentElement) {

			BSDDOMUtils.replaceElement(existingWindow, currentElement); //window already exists - replace it
		} else {
			if(!currentElement.isDynamic) {
				currentElement.isDynamic = parent.isDynamic;
			}		
			currentElement.parentPopupId = parent.id;

			BSDLogUtils.debug("Moving child element: " + currentElement.id);
			BSDDOMUtils.moveElement(currentElement, document.body); //can't have popups stay nested			
		}

	}
	
	
}




BSDShareUtils = {
	DEPENDENCIES: new Array("popup/BSDPopupUtils"),
	
	completeEmailSend: function() {
		BSDPopupUtils.closeOpenPopups();
		
		var emailForm = document.forms['shareForm'];
		if(emailForm && emailForm.reset) {
			emailForm.reset();
		}
		
	}


}
