if (typeof DOMParser == "undefined") {
    DOMParser = function () {
    }

    DOMParser.prototype.parseFromString = function (str, contentType) {
	if (typeof ActiveXObject != "undefined") {
	    var d = new ActiveXObject("MSXML2.DomDocument");
	    d.loadXML(str);
	    return d;
	} else if (typeof XMLHttpRequest != "undefined") {
	    var req = new XMLHttpRequest;
	    req.open("GET", "data:" + (contentType || "application/xml") +
		     ";charset=utf-8," + encodeURIComponent(str), false);
	    if (req.overrideMimeType) {
		req.overrideMimeType(contentType);
	    }
	    req.send(null);
	    return req.responseXML;
	}
    }
}


if(typeof XMLSerializer == "undefined") {
    XMLSerializer = function() {
	//nothing
    }
    XMLSerializer.prototype.serializeToString = function(xml) {
	try {
	    return xml.xml;
	} catch (e) {
	}
    }
}

function EchoAsyncMonitor() { }
EchoAsyncMonitor.timeInterval = 500;
EchoAsyncMonitor.pollServiceRequest = "?serviceId=Echo.AsyncMonitor";
EchoAsyncMonitor.timeoutId = null;
EchoAsyncMonitor.connect = function() {
	var conn = new EchoHttpConnection(EchoClientEngine.baseServerUri + EchoAsyncMonitor.pollServiceRequest, "GET");
	conn.responseHandler = EchoAsyncMonitor.responseHandler;
	conn.invalidResponseHandler = EchoAsyncMonitor.invalidResponseHandler;
	conn.connect();
};
EchoAsyncMonitor.invalidResponseHandler = function() {
	EchoAsyncMonitor.start();
};
EchoAsyncMonitor.start = function() {
	if (!EchoServerTransaction.active) {
		EchoAsyncMonitor.timeoutId = window.setTimeout("EchoAsyncMonitor.connect();", 
				EchoAsyncMonitor.timeInterval);
	}
};
EchoAsyncMonitor.stop = function() {
	if (EchoAsyncMonitor.timeoutId) {
		window.clearTimeout(EchoAsyncMonitor.timeoutId);
		EchoAsyncMonitor.timeoutId = null;
	}
};
EchoAsyncMonitor.responseHandler = function(conn) {
	if ("true" == conn.getResponseXml().documentElement.getAttribute("request-sync")) {
		EchoServerTransaction.connect();
	} else {
		EchoAsyncMonitor.start();
	}
};
function EchoClientAnalyzer() { }
EchoClientAnalyzer.analyze = function() {
	var messagePartElement = EchoClientMessage.getMessagePart("EchoClientAnalyzer");
	EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorAppName", window.navigator.appName);
	EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorAppVersion", window.navigator.appVersion);
	EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorAppCodeName", window.navigator.appCodeName);
	EchoClientAnalyzer.setBooleanProperty(messagePartElement, "navigatorCookieEnabled", window.navigator.cookieEnabled);
	EchoClientAnalyzer.setBooleanProperty(messagePartElement, "navigatorJavaEnabled", window.navigator.javaEnabled());
	EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorLanguage", 
			window.navigator.language ? window.navigator.language : window.navigator.userLanguage);
	EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorPlatform", window.navigator.platform);
	EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorUserAgent", window.navigator.userAgent);
	if (window.screen) {
		EchoClientAnalyzer.setIntegerProperty(messagePartElement, "screenWidth", window.screen.width);
		EchoClientAnalyzer.setIntegerProperty(messagePartElement, "screenHeight", window.screen.height);
		EchoClientAnalyzer.setIntegerProperty(messagePartElement, "screenColorDepth", window.screen.colorDepth);
	}
	EchoClientAnalyzer.setIntegerProperty(messagePartElement, "utcOffset", 0 - parseInt((new Date()).getTimezoneOffset()));
};
EchoClientAnalyzer.setBooleanProperty = function(messagePartElement, propertyName, propertyValue) {
	propertyElement = messagePartElement.ownerDocument.createElement("property");
	propertyElement.setAttribute("type", "boolean");
	propertyElement.setAttribute("name", propertyName);
	propertyElement.setAttribute("value", propertyValue ? "true" : "false");
	messagePartElement.appendChild(propertyElement);
};
EchoClientAnalyzer.setIntegerProperty = function(messagePartElement, propertyName, propertyValue) {
	var intValue = parseInt(propertyValue);
	if (isNaN(intValue)) {
		return;
	}
	propertyElement = messagePartElement.ownerDocument.createElement("property");
	propertyElement.setAttribute("type", "integer");
	propertyElement.setAttribute("name", propertyName);
	propertyElement.setAttribute("value", intValue);
	messagePartElement.appendChild(propertyElement);
};
EchoClientAnalyzer.setTextProperty = function(messagePartElement, propertyName, propertyValue) {
	propertyElement = messagePartElement.ownerDocument.createElement("property");
	propertyElement.setAttribute("type", "text");
	propertyElement.setAttribute("name", propertyName);
	propertyElement.setAttribute("value", propertyValue);
	messagePartElement.appendChild(propertyElement);
};
function EchoClientConfiguration() { }
EchoClientConfiguration.MessageProcessor = function() { };
EchoClientConfiguration.MessageProcessor.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "store":
				EchoClientConfiguration.MessageProcessor.processStore(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoClientConfiguration.MessageProcessor.processStore = function(storeElement) {
	var propertyElements = storeElement.getElementsByTagName("property");
	for (var i = 0; i < propertyElements.length; ++i) {
		var name = propertyElements[i].getAttribute("name");
		var value = propertyElements[i].getAttribute("value");
		EchoClientConfiguration.propertyMap[name] = value;
	}
};
EchoClientConfiguration.propertyMap = new Array();
EchoClientConfiguration.propertyMap["defaultOutOfSyncErrorMessage"] 
                                    = "A synchronization error has occurred.  Click \"Ok\" to re-synchronize.";
EchoClientConfiguration.propertyMap["defaultServerErrorMessage"] 
                                    = "An application error has occurred.  Your session has been reset.";
EchoClientConfiguration.propertyMap["defaultSessionExpirationMessage"] 
                                    = "Your session has been reset due to inactivity.";
function EchoClientEngine() { }
EchoClientEngine.baseServerUri = null;
EchoClientEngine.debugEnabled = true;
EchoClientEngine.loadStatus = 2;
EchoClientEngine.transactionId = "";
EchoClientEngine.configure = function() {
	if (EchoClientProperties.get("quirkCssPositioningOneSideOnly")) {
		EchoVirtualPosition.init();
	}
};
EchoClientEngine.dispose = function() {
	EchoEventProcessor.dispose();
	EchoDomPropertyStore.disposeAll();
};
EchoClientEngine.init = function(baseServerUri, debugEnabled) {
	EchoClientEngine.baseServerUri = baseServerUri;
	EchoClientEngine.debugEnabled = debugEnabled;
	if (EchoClientEngine.debugEnabled && 
			window.location.search && window.location.search.toLowerCase().indexOf("debug") != -1) {
		EchoDebugManager.launch();
	}
	EchoClientMessage.setInitialize();
	EchoClientAnalyzer.analyze();
	EchoServerTransaction.connect();
	EchoDomUtil.addEventListener(window, "unload", EchoClientEngine.dispose);
	EchoClientEngine.incrementLoadStatus();
};
EchoClientEngine.processClientError = function(message, ex) {
	var errorText;
	if (ex instanceof Error) {
		errorText = "Error Name: " + ex.name + "\n"
		+ "Error Message: " + ex.message;
	} else {
		errorText = "Error: " + ex;
	}
};	
EchoClientEngine.processSessionExpiration = function() {
	var message = EchoClientConfiguration.propertyMap["sessionExpirationMessage"];
	var uri = EchoClientConfiguration.propertyMap["sessionExpirationUri"];
	if (message || uri) {
		if (message) {
		}
		if (uri) {
			window.location.href = uri;
		} else {
			window.location.reload();
		}
	} else {
		message = EchoClientConfiguration.propertyMap["defaultSessionExpirationMessage"];
		window.location.reload();
	}
};
EchoClientEngine.processServerError = function() {
	var message = EchoClientConfiguration.propertyMap["serverErrorMessage"];
	var uri = EchoClientConfiguration.propertyMap["serverErrorUri"];
	if (message || uri) {
		if (message) {
		}
		if (uri) {
			window.location.href = uri;
		} else {
			if (EchoServerMessage.transactionCount > 0) {
				window.location.reload();
			}
		}
	} else {
		message = EchoClientConfiguration.propertyMap["defaultServerErrorMessage"];
		if (EchoServerMessage.transactionCount > 0) {
			window.location.reload();
		}
	}
};
EchoClientEngine.disableLoadStatus = function() {
	if (EchoClientEngine.loadStatus != -1) {
		EchoClientEngine.loadStatus = -1;
		EchoClientEngine.renderLoadStatus();
	}
};
EchoClientEngine.incrementLoadStatus = function() {
	if (EchoClientEngine.loadStatus != -1) {
		++EchoClientEngine.loadStatus;
		EchoClientEngine.renderLoadStatus();
	}
};
EchoClientEngine.renderLoadStatus = function() {
	var loadStatusDivElement = document.getElementById("loadstatus");
	if (!loadStatusDivElement) {
		return;
	}
	if (EchoClientEngine.loadStatus == -1) {
		loadStatusDivElement.parentNode.removeChild(loadStatusDivElement);
	} else {
		var text = "";
		for (var i = 0; i < EchoClientEngine.loadStatus; ++i) {
			text += "|";
		}
		loadStatusDivElement.removeChild(loadStatusDivElement.firstChild);
		loadStatusDivElement.appendChild(document.createTextNode(text));
	}
};
EchoClientEngine.updateTransactionId = function(newValue) {
	EchoClientEngine.transactionId = newValue;
	EchoClientMessage.messageDocument.documentElement.setAttribute("trans-id", EchoClientEngine.transactionId);
};
EchoClientEngine.verifyInput = function(element, allowInputDuringTransaction) {
	if (!allowInputDuringTransaction && EchoServerTransaction.active) {
		return false;
	}
	if (!EchoModalManager.isElementInModalContext(element)) {
		return false;
	}
	if (EchoDomPropertyStore.getPropertyValue(element, "EchoClientEngine.inputDisabled")) {
		return false;
	}
	return true;
};
function EchoClientMessage() { }
EchoClientMessage.messageDocument = null;
EchoClientMessage.createPropertyElement = function(componentId, propertyName) {
	var messagePartElement = EchoClientMessage.getMessagePart("EchoPropertyUpdate");
	var propertyElements = messagePartElement.getElementsByTagName("property");
	var propertyElement = null;
	for (var i = 0; i < propertyElements.length; ++i) {
		if (componentId == propertyElements[i].getAttribute("component-id") &&
				propertyName == propertyElements[i].getAttribute("name")) {
			propertyElement = propertyElements[i];
			break;
		} 
	}
	if (!propertyElement) {
		propertyElement = messagePartElement.ownerDocument.createElement("property");
		propertyElement.setAttribute("component-id", componentId);
		propertyElement.setAttribute("name", propertyName);
		messagePartElement.appendChild(propertyElement);
	}
	return propertyElement;
};
EchoClientMessage.getMessagePart = function(processor) {
	var messagePartElements = EchoClientMessage.messageDocument.getElementsByTagName("message-part");
	for (var i = 0; i < messagePartElements.length; ++i) {
		if (messagePartElements[i].getAttribute("processor") == processor) {
			return messagePartElements[i];
		}
	}
	var messagePartElement = EchoClientMessage.messageDocument.createElement("message-part");
	messagePartElement.setAttribute("processor", processor);
	EchoClientMessage.messageDocument.documentElement.appendChild(messagePartElement);
	return messagePartElement;
};
EchoClientMessage.getPropertyValue = function(componentId, propertyName) {
	var messagePartElement = EchoClientMessage.getMessagePart("EchoPropertyUpdate");
	var propertyElements = messagePartElement.getElementsByTagName("property");
	for (var i = 0; i < propertyElements.length; ++i) {
		if (componentId == propertyElements[i].getAttribute("component-id") &&
				propertyName == propertyElements[i].getAttribute("name")) {
			return propertyElements[i].getAttribute("value");
		}
	}
	return null;
};
EchoClientMessage.removePropertyElement = function(componentId, propertyName) {
	var messagePartElement = EchoClientMessage.getMessagePart("EchoPropertyUpdate");
	var propertyElements = messagePartElement.getElementsByTagName("property");
	var propertyElement = null;
	for (var i = 0; i < propertyElements.length; ++i) {
		if (componentId == propertyElements[i].getAttribute("component-id") &&
				propertyName == propertyElements[i].getAttribute("name")) {
			propertyElement = propertyElements[i];
			break;
		} 
	}
	if (propertyElement) {
		messagePartElement.removeChild(propertyElement);
	}
};
EchoClientMessage.reset = function() {
	EchoClientMessage.messageDocument = null;
	EchoClientMessage.messageDocument 
	= EchoDomUtil.createDocument("http://www.nextapp.com/products/echo2/climsg", "client-message");
};
EchoClientMessage.setActionValue = function(componentId, actionName, actionValue) {
	var messagePartElement = EchoClientMessage.getMessagePart("EchoAction");
	var actionElement = messagePartElement.ownerDocument.createElement("action");
	actionElement.setAttribute("component-id", componentId);
	actionElement.setAttribute("name", actionName);
	if (actionValue !== undefined) {
		actionElement.setAttribute("value", actionValue);
	}
	messagePartElement.appendChild(actionElement);
	EchoDebugManager.updateClientMessage();
	return actionElement;
};
EchoClientMessage.setPropertyValue = function(componentId, propertyName, newValue) {
	var propertyElement = EchoClientMessage.createPropertyElement(componentId, propertyName);
	propertyElement.setAttribute("value", newValue);
	EchoDebugManager.updateClientMessage();
};
EchoClientMessage.setInitialize = function() {
	EchoClientMessage.messageDocument.documentElement.setAttribute("type", "initialize");
};
function EchoClientProperties() { }
EchoClientProperties.propertyMap = new Array();
EchoClientProperties.MessageProcessor = function() { };
EchoClientProperties.MessageProcessor.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "store":
				EchoClientProperties.MessageProcessor.processStore(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoClientProperties.MessageProcessor.processStore = function(storeElement) {
	var propertyElements = storeElement.getElementsByTagName("property");
	for (var i = 0; i < propertyElements.length; ++i) {
		var name = propertyElements[i].getAttribute("name");
		var value = propertyElements[i].getAttribute("value");
		if (value == "true") {
			value = true;
		} else if (value == "false") {
			value = false;
		}
		EchoClientProperties.propertyMap[name] = value;
	}
	EchoClientEngine.configure();
};
EchoClientProperties.get = function(name) {
	return EchoClientProperties.propertyMap[name];
};
EchoCollectionsMap = function() {
	this.removeCount = 0;
	this.garbageCollectionInterval = 250;
	this.associations = new Array();
};
EchoCollectionsMap.prototype.get = function(key) {
	return this.associations[key];
};
EchoCollectionsMap.prototype.put = function(key, value) {
	if (value === null) {
		this.remove(key);
		return;
	}
	this.associations[key] = value;
};
EchoCollectionsMap.prototype.garbageCollect = function() {
	this.removeCount = 0;
	var newAssociations = new Array();
	var i = 0;
	for (var key in this.associations) {
		newAssociations[key] = this.associations[key];
		++i;
	}
	this.associations = newAssociations;
};
EchoCollectionsMap.prototype.remove = function(key) {
	delete this.associations[key];
	++this.removeCount;
	if (this.removeCount >= this.garbageCollectionInterval) {
		this.garbageCollect();
	}
};
function EchoCssUtil() { }
EchoCssUtil.applyStyle = function(element, cssText) {
	var styleProperties = cssText.split(";");
	var styleData = new Array();
	for (var i = 0; i < styleProperties.length; ++i) {
		var separatorIndex = styleProperties[i].indexOf(":");
		if (separatorIndex == -1) {
			continue;
		}
		var attributeName = styleProperties[i].substring(0, separatorIndex);
		var propertyName = EchoDomUtil.cssAttributeNameToPropertyName(attributeName);
		var propertyValue = styleProperties[i].substring(separatorIndex + 1);
		element.style[propertyName] = propertyValue;
	}
};
EchoCssUtil.applyTemporaryStyle = function(element, cssStyleText) {
	if (!EchoDomPropertyStore.getPropertyValue(element, "EchoCssUtil.originalStyle")) {
		if (EchoDomUtil.getCssText(element)) {
			EchoDomPropertyStore.setPropertyValue(element, "EchoCssUtil.originalStyle", EchoDomUtil.getCssText(element));
		} else {
			EchoDomPropertyStore.setPropertyValue(element, "EchoCssUtil.originalStyle", "-");
		}
	}
	EchoCssUtil.applyStyle(element, cssStyleText);
};
EchoCssUtil.restoreOriginalStyle = function(element) {
	var originalStyle = EchoDomPropertyStore.getPropertyValue(element, "EchoCssUtil.originalStyle");
	if (!originalStyle) {
		return;
	}
	EchoDomUtil.setCssText(element, originalStyle == "-" ? "" : originalStyle);
	EchoDomPropertyStore.setPropertyValue(element, "EchoCssUtil.originalStyle", null);
};
EchoCssUtil.Bounds = function(element) {
	this.left = 0;
	this.top = 0;
	this.width = element.offsetWidth;
	this.height = element.offsetHeight;
	while (element != null) {
		if (element.scrollTop) {
			this.top -= element.scrollTop;
		}
		if (element.scrollLeft) {
			this.left -= element.scrollLeft;
		}
		this.left += element.offsetLeft;
		this.top += element.offsetTop;
		element = element.offsetParent;
	}
};
EchoCssUtil.Bounds.prototype.toString = function() {
	return "(" + this.left + ", " + this.top + ") [" + this.width + "x" + this.height + "]";
};
function EchoDebugManager() { }
EchoDebugManager.debugWindow = null;
EchoDebugManager.consoleWrite = function(message) {
	if (!EchoDebugManager.debugWindow || EchoDebugManager.debugWindow.closed) {
		return;
	}
	EchoDebugManager.debugWindow.EchoDebug.Console.write(message);
};
EchoDebugManager.launch = function() {
	EchoDebugManager.debugWindow = window.open(EchoClientEngine.baseServerUri + "?serviceId=Echo.Debug", "EchoDebug", 
			"width=400,height=650,resizable=yes", true);
};
EchoDebugManager.updateClientMessage = function() {
	if (!EchoDebugManager.debugWindow || EchoDebugManager.debugWindow.closed) {
		return;
	}
	EchoDebugManager.debugWindow.EchoDebug.Sync.displayClientMessage(EchoClientMessage.messageDocument);
};
EchoDebugManager.updateServerMessage = function() {
	if (!EchoDebugManager.debugWindow || EchoDebugManager.debugWindow.closed) {
		return;
	}
	EchoDebugManager.debugWindow.EchoDebug.Sync.displayServerMessage(EchoServerMessage.messageDocument);
};
function EchoDomPropertyStore() { }
EchoDomPropertyStore.MessageProcessor = function() { };
EchoDomPropertyStore.MessageProcessor.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "store-property":
				EchoDomPropertyStore.MessageProcessor.processStoreProperty(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoDomPropertyStore.MessageProcessor.processStoreProperty = function(storePropertyElement) {
	var propertyName = storePropertyElement.getAttribute("name");
	var propertyValue = storePropertyElement.getAttribute("value");
	var items = storePropertyElement.getElementsByTagName("item");
	for (var i = 0; i < items.length; ++i) {
		var elementId = items[i].getAttribute("eid");
		EchoDomPropertyStore.setPropertyValue(elementId, propertyName, propertyValue);
	}
};
EchoDomPropertyStore.dispose = function(element) {
	if (typeof element == "string") {
		element = document.getElementById(element);
	}
	if (!element) {
		throw new Error("Element not found.");
	}
	if (element.echoDomPropertyStore) {
		for (var elementProperty in element.echoDomPropertyStore) {
			delete element.echoDomPropertyStore[elementProperty];
		}
	}
	element.echoDomPropertyStore = undefined;
};
EchoDomPropertyStore.disposeAll = function() {
	EchoDomPropertyStore.disposeAllRecurse(document.documentElement);
};
EchoDomPropertyStore.disposeAllRecurse = function(element) {
	if (element.echoDomPropertyStore) {
		for (var elementProperty in element.echoDomPropertyStore) {
			delete element.echoDomPropertyStore[elementProperty];
		}
	}
	element.echoDomPropertyStore = undefined;
	for (var childElement = element.firstChild; childElement; childElement = childElement.nextSibling) {
		if (childElement.nodeType == 1) {
			EchoDomPropertyStore.disposeAllRecurse(childElement);
		}
	}
};
EchoDomPropertyStore.getPropertyValue = function(element, propertyName) {
	if (typeof element == "string") {
		element = document.getElementById(element);
	}
	if (!element) {
		return null;
	}
	if (element.echoDomPropertyStore) {
		return element.echoDomPropertyStore[propertyName];
	} else {
		return null;
	}
};
EchoDomPropertyStore.setPropertyValue = function(element, propertyName, propertyValue) {
	if (typeof element == "string") {
		element = document.getElementById(element);
	}
	if (!element) {
		return;
	}
	if (!element.echoDomPropertyStore) {
		element.echoDomPropertyStore = new Array();
	}
	element.echoDomPropertyStore[propertyName] = propertyValue;
};
function EchoDomUpdate() { }
EchoDomUpdate.MessageProcessor = function() { };
EchoDomUpdate.MessageProcessor.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "dom-add":
				this.processAdd(messagePartElement.childNodes[i]);
				break;
			case "dom-remove":
				this.processRemove(messagePartElement.childNodes[i]);
				break;
			case "dom-remove-children":
				this.processRemoveChildren(messagePartElement.childNodes[i]);
				break;
			case "attribute-update":
				this.processAttributeUpdate(messagePartElement.childNodes[i]);
				break;
			case "style-update":
				this.processStyleUpdate(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoDomUpdate.MessageProcessor.processAdd = function(domAddElement) {
	var contentElements = domAddElement.getElementsByTagName("content");
	for (var i = 0; i < contentElements.length; ++i) {
		var parentId = contentElements[i].getAttribute("parent-id");
		var siblingId = contentElements[i].getAttribute("sibling-id");
		var parentElement = document.getElementById(parentId);
		var siblingElement = null;
		if (siblingId) {
			siblingElement = document.getElementById(siblingId);
			if (!siblingElement) {
				throw new EchoDomUpdate.TargetNotFoundException("Add", "sibling", siblingId);
			}
		}
		if (!parentElement) {
			throw new EchoDomUpdate.TargetNotFoundException("Add", "parent", parentId);
		}
		for (var j = 0; j < contentElements[i].childNodes.length; ++j) {
			var importedNode = EchoDomUtil.importNode(parentElement.ownerDocument, contentElements[i].childNodes[j], true);
			var newElementId = importedNode.getAttribute("id");
			if (!newElementId) {
				throw "Cannot add element without id attribute.";
			}
			if (document.getElementById(newElementId)) {
				throw "Element " + newElementId + " already exists in document; cannot add.";
			}
			if (siblingElement) {
				parentElement.insertBefore(importedNode, siblingElement);
			} else {
				parentElement.appendChild(importedNode);
			}
		}
	}
};
EchoDomUpdate.MessageProcessor.processAttributeUpdate = function(attributeUpdateElement) {
	var targetId = attributeUpdateElement.getAttribute("target-id");
	var targetElement = document.getElementById(targetId);
	if (!targetElement) {
		throw new EchoDomUpdate.TargetNotFoundException("AttributeUpdate", "target", targetId);
	}
	targetElement[attributeUpdateElement.getAttribute("name")] = attributeUpdateElement.getAttribute("value");
};
EchoDomUpdate.MessageProcessor.processRemove = function(domRemoveElement) {
	var targetId = domRemoveElement.getAttribute("target-id");
	var targetElement = document.getElementById(targetId);
	if (!targetElement) {
		return;
	}
	targetElement.parentNode.removeChild(targetElement);
};
EchoDomUpdate.MessageProcessor.processRemoveChildren = function(domRemoveElement) {
	var targetId = domRemoveElement.getAttribute("target-id");
	var targetElement = document.getElementById(targetId);
	if (!targetElement) {
		return;
	}
	var childNode = targetElement.firstChild;
	while (childNode) {
		var nextChildNode = childNode.nextSibling;
		targetElement.removeChild(childNode);
		childNode = nextChildNode;
	}
};
EchoDomUpdate.MessageProcessor.processStyleUpdate = function(styleUpdateElement) {
	var targetId = styleUpdateElement.getAttribute("target-id");
	var targetElement = document.getElementById(targetId);
	if (!targetElement) {
		throw new EchoDomUpdate.TargetNotFoundException("StyleUpdate", "target", targetId);
	}
	targetElement.style[styleUpdateElement.getAttribute("name")] = styleUpdateElement.getAttribute("value");
};
EchoDomUpdate.TargetNotFoundException = function(updateType, targetType, targetId) {
	this.targetId = targetId;
	this.targetType = targetType;
	this.updateType = updateType;
};
EchoDomUpdate.TargetNotFoundException.prototype.toString = function() {
	return "Failed to perform \"" + this.updateType + "\": " + this.targetType + " element \"" + this.targetId + "\" not found.";
};
function EchoDomUtil() { }
EchoDomUtil.attributeToPropertyMap = null;
EchoDomUtil.addEventListener = function(eventSource, eventType, eventListener, useCapture) {
	if (eventSource.addEventListener) {
		eventSource.addEventListener(eventType, eventListener, useCapture);
	} else if (eventSource.attachEvent) {
		eventSource.attachEvent("on" + eventType, eventListener);
	}
};
EchoDomUtil.initAttributeToPropertyMap = function() {
	var m = new Array();
	m["accesskey"] = "accessKey";
	m["cellpadding"] = "cellPadding";
	m["cellspacing"] = "cellSpacing";
	m["class"] = "className";
	m["codebase"] = "codeBase";
	m["codetype"] = "codeType";
	m["colspan"] = "colSpan";
	m["datetime"] = "dateTime";
	m["frameborder"] = "frameBorder";
	m["longdesc"] = "longDesc";
	m["marginheight"] = "marginHeight";
	m["marginwidth"] = "marginWidth";
	m["maxlength"] = "maxLength";
	m["noresize"] = "noResize";
	m["noshade"] = "noShade";
	m["nowrap"] = "noWrap";
	m["readonly"] = "readOnly";
	m["rowspan"] = "rowSpan";
	m["tabindex"] = "tabIndex";
	m["usemap"] = "useMap";
	m["valign"] = "vAlign";
	m["valueType"] = "valueType";
	EchoDomUtil.attributeToPropertyMap = m; 
};
EchoDomUtil.createDocument = function(namespaceUri, qualifiedName) {
	if (document.implementation && document.implementation.createDocument) {
		var dom = document.implementation.createDocument(namespaceUri, qualifiedName, null);
		if (!dom.documentElement) {
			dom.appendChild(dom.createElement(qualifiedName));
		}
		return dom;
	} else if (window.ActiveXObject) {
		var createdDocument = new ActiveXObject("Microsoft.XMLDOM");
		var documentElement = createdDocument.createElement(qualifiedName);
		createdDocument.appendChild(documentElement);
		return createdDocument;
	} else {
		throw "Unable to create new Document.";
	}
};
EchoDomUtil.cssAttributeNameToPropertyName = function(attribute) {
	var segments = attribute.split("-");
	var out = segments[0];
	for (var i = 1; i < segments.length; ++i) {
		out += segments[i].substring(0, 1).toUpperCase();
		out += segments[i].substring(1);
	}
	return out;
};
EchoDomUtil.fixSafariAttrs = function(node) {
	if (node.nodeType == 1) {
		for (i = 0; i < node.attributes.length; ++i) {
			var attribute = node.attributes[i];
			node.setAttribute(attribute.name, attribute.value.replace("\x26\x2338\x3B", "&"));
		}
	}
	for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
		EchoDomUtil.fixSafariAttrs(childNode);
	}
};
EchoDomUtil.getComponentId = function(elementId) {
	if ("c_" != elementId.substring(0, 2)) {
		return null;
	}
	var extensionStart = elementId.indexOf("_", 2);
	if (extensionStart == -1) {
		return elementId;
	} else {
		return elementId.substring(0, extensionStart);
	}
};
EchoDomUtil.getCssText = function(element) {
	if (EchoClientProperties.get("quirkOperaNoCssText")) {
		return element.getAttribute("style");
	} else {
		return element.style.cssText;
	}
}
EchoDomUtil.getEventTarget = function(e) {
	return e.target ? e.target : e.srcElement;
};
EchoDomUtil.importNode = function(targetDocument, sourceNode, importChildren) {
	if (targetDocument.importNode) {
		return targetDocument.importNode(sourceNode, importChildren);
	} else {
		return EchoDomUtil.importNodeImpl(targetDocument, sourceNode, importChildren);
	}
};
EchoDomUtil.importNodeImpl = function(targetDocument, sourceNode, importChildren) {
	var targetNode; 
	if (importChildren || !sourceNode.hasChildNodes()) {
		targetNode = EchoDomUtil.importNodeByInnerHtml(targetDocument, sourceNode, importChildren);
		if (targetNode != null) {
			return targetNode;
		}
	}
	targetNode = EchoDomUtil.importNodeByDom(targetDocument, sourceNode);
	if (importChildren && sourceNode.hasChildNodes()) {
		for (var sourceChildNode = sourceNode.firstChild; sourceChildNode; sourceChildNode = sourceChildNode.nextSibling) {
			var targetChildNode = EchoDomUtil.importNodeImpl(targetDocument, sourceChildNode, true);
			if (targetChildNode) {
				targetNode.appendChild(targetChildNode);
			}
		}
	}
	return targetNode;
};
EchoDomUtil.importNodeByDom = function(targetDocument, sourceNode) {
	var targetNode, i;
	switch (sourceNode.nodeType) {
	case 1:
		targetNode = targetDocument.createElement(sourceNode.nodeName);
		for (i = 0; i < sourceNode.attributes.length; ++i) {
			var attribute = sourceNode.attributes[i];
			if ("style" == attribute.name) {
				targetNode.style.cssText = attribute.value;
			} else {
				if (EchoDomUtil.attributeToPropertyMap === null) {
					EchoDomUtil.initAttributeToPropertyMap();
				}
				var propertyName = EchoDomUtil.attributeToPropertyMap[attribute.name];
				if (propertyName) {
					targetNode[propertyName] = attribute.value;
				} else {
					targetNode[attribute.name] = attribute.value;
				}
			}
		}
		break;
	case 3:
		targetNode = targetDocument.createTextNode(sourceNode.nodeValue);
		break;
	}
	return targetNode;
};
EchoDomUtil.importNodeByInnerHtml = function(targetDocument, sourceNode) {
	var targetNode;
	if (sourceNode.nodeName.match(/tr|tbody|table|thead|tfoot|colgroup/)) {
		return null;
	}
	var sourceHTML = "";
	for (var sourceChildNode = sourceNode.firstChild; sourceChildNode; sourceChildNode = sourceChildNode.nextSibling) {
		var childHTML = sourceChildNode.xml;
		if (childHTML == null) {
			return null;
		}
		sourceHTML += childHTML;
	}
	targetNode = EchoDomUtil.importNodeByDom(targetDocument, sourceNode);
	var expr = new RegExp("<(?:(?!br|img|hr)([a-zA-Z]+))([^>]*)/>", "ig");
	sourceHTML = sourceHTML.replace(expr, "<$1$2></$1>");
	if (EchoStringUtil.trim(sourceHTML).length > 0) {
		targetNode.innerHTML = sourceHTML;
	}
	return targetNode;
};
EchoDomUtil.isAncestorOf = function(ancestorNode, descendantNode) {
	var testNode = descendantNode;
	while (testNode !== null) {
		if (testNode == ancestorNode) {
			return true;
		}
		testNode = testNode.parentNode;
	}
	return false;
};
EchoDomUtil.preventEventDefault = function(e) {
	if (e.preventDefault) {
		e.preventDefault();
	} else {
		e.returnValue = false;
	}
};
EchoDomUtil.removeEventListener = function(eventSource, eventType, eventListener, useCapture) {
	if (eventSource.removeEventListener) {
		eventSource.removeEventListener(eventType, eventListener, useCapture);
	} else if (eventSource.detachEvent) {
		eventSource.detachEvent("on" + eventType, eventListener);
	}
};
EchoDomUtil.setCssText = function(element, cssText) {
	if (EchoClientProperties.get("quirkOperaNoCssText")) {
		if (cssText) {
			element.setAttribute("style", cssText);
		} else {
			element.removeAttribute("style");
		}
	} else {
		element.style.cssText = cssText;
	}
};
EchoDomUtil.stopPropagation = function(e) {
	if (e.stopPropagation) {
		e.stopPropagation();
	} else {
		e.cancelBubble = true;
	}
};
function EchoEventProcessor() { }
EchoEventProcessor.EventHandlerData = function(handlerName, capture) { 
	this.handlerName = handlerName;
	this.capture = capture;
};
EchoEventProcessor.MessageProcessor = function() { };
EchoEventProcessor.MessageProcessor.process = function(messagePartElement) {
	var i, j, k, eventTypes, handlers, addItems, removeItems, elementId;
	var adds = messagePartElement.getElementsByTagName("event-add");
	var removes = messagePartElement.getElementsByTagName("event-remove");
	for (i = 0; i < adds.length; ++i) {
		eventTypes = adds[i].getAttribute("type").split(",");
		handlers = adds[i].getAttribute("handler").split(",");
		if (handlers.length != eventTypes.length) {
			throw "Handler count and event type count do not match.";
		}
		addItems = adds[i].getElementsByTagName("item");
		for (j = 0; j < addItems.length; ++j) {
			elementId = addItems[j].getAttribute("eid");
			for (k = 0; k < eventTypes.length; ++k) {
				EchoEventProcessor.addHandler(elementId, eventTypes[k], handlers[k]);
			}
		}
	}
	for (i = 0; i < removes.length; ++i) {
		eventTypes = removes[i].getAttribute("type").split(",");
		removeItems = removes[i].getElementsByTagName("item");
		for (j = 0; j < removeItems.length; ++j) {
			elementId = removeItems[j].getAttribute("eid");
			for (k = 0; k < eventTypes.length; ++k) {
				EchoEventProcessor.removeHandler(elementId, eventTypes[k]);
			}
		}
	}
};
EchoEventProcessor.eventTypeToHandlersMap = new EchoCollectionsMap();
EchoEventProcessor.addHandler = function(element, eventType, handler, capture) {
	var elementId;
	if (typeof element == "string") {
		elementId = element;
		element = document.getElementById(element);
	} else {
		elementId = element.id;
	}
	EchoDomUtil.addEventListener(element, eventType, EchoEventProcessor.processEvent, false);
	var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType);
	if (!elementIdToHandlerMap) {
		elementIdToHandlerMap = new EchoCollectionsMap();
		EchoEventProcessor.eventTypeToHandlersMap.put(eventType, elementIdToHandlerMap);
	}
	var handlerData = new EchoEventProcessor.EventHandlerData(handler, capture);
	elementIdToHandlerMap.put(elementId, handlerData);
};
EchoEventProcessor.dispose = function() {
	for (var eventType in EchoEventProcessor.eventTypeToHandlersMap.associations) {
		var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.associations[eventType];
		for (var elementId in elementIdToHandlerMap.associations) {
			var element = document.getElementById(elementId);
			if (element) {
				EchoDomUtil.removeEventListener(element, eventType, EchoEventProcessor.processEvent, false);
			}
			elementIdToHandlerMap.remove(elementId);
		}
		EchoEventProcessor.eventTypeToHandlersMap.remove(eventType);
	}
};
EchoEventProcessor.getHandler = function(eventType, elementId) {
	var handlerData = EchoEventProcessor.getHandlerData(eventType, elementId);
	return handlerData ? handlerData.handlerName : null;
};
EchoEventProcessor.getHandlerData = function(eventType, elementId) {
	var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType);
	if (!elementIdToHandlerMap) {
		return null;
	}
	return elementIdToHandlerMap.get(elementId);
};
EchoEventProcessor.getHandlerElementIds = function(eventType) {
	var elementIds = new Array();
	var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType);
	if (elementIdToHandlerMap) {
		for (var elementId in elementIdToHandlerMap.associations) {
			elementIds.push(elementId);
		}
	}
	return elementIds;
};
EchoEventProcessor.getHandlerEventTypes = function() {
	var handlerTypes = new Array();
	for (var eventType in EchoEventProcessor.eventTypeToHandlersMap.associations) {
		handlerTypes.push(eventType);
	}
	return handlerTypes;
};
EchoEventProcessor.processEvent = function(e) {
	e = e ? e : window.event;
	var eventType = e.type;
	if (!e.target && e.srcElement) {
		e.target = e.srcElement;
	}
	var targetElement = e.target;
	var handlerDatas = new Array();
	var targetElements = new Array();
	var hasCapturingHandlers = false;
	while (targetElement) {
		if (targetElement.nodeType == 1) { 
			var handlerData = EchoEventProcessor.getHandlerData(eventType, targetElement.id);
			if (handlerData) {
				hasCapturingHandlers |= handlerData.capture;
				handlerDatas.push(handlerData);
				targetElements.push(targetElement);
			}
		}
		targetElement = targetElement.parentNode;
	}
	if (handlerDatas.length == 0) {
		return;
	}
	var propagate = true;
	if (hasCapturingHandlers) {
		for (var i = handlerDatas.length - 1; i >=0; --i) {
			if (!handlerDatas[i].capture) {
				continue;
			}
			var handler;
			try {
				handler = eval(handlerDatas[i].handlerName);
			} catch (ex) {
				throw "Invalid handler: " + handlerDatas[i].handlerName + " (" + ex + ")";
			}
			e.registeredTarget = targetElements[i];
			propagate = handler(e);
			if (!propagate) {
				break;
			}
		}
	}
	if (propagate) {
		for (var i = 0; i < handlerDatas.length; ++i) {
			if (handlerDatas[i].capture) {
				continue;
			}
			var handler;
			try {
				handler = eval(handlerDatas[i].handlerName);
			} catch (ex) {
				throw "Invalid handler: " + handlerDatas[i].handlerName + " (" + ex + ")";
			}
			e.registeredTarget = targetElements[i];
			propagate = handler(e);
			if (!propagate) {
				break;
			}
		}
	}
	if (!propagate) {
		EchoDomUtil.stopPropagation(e);
	}
};
EchoEventProcessor.removeHandler = function(element, eventType) {
	var elementId;
	if (typeof element == "string") {
		elementId = element;
		element = document.getElementById(element);
	} else {
		elementId = element.id;
	}
	if (element) {
		EchoDomUtil.removeEventListener(element, eventType, EchoEventProcessor.processEvent, false);
	}
	var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType);
	if (!elementIdToHandlerMap) {
		return;
	}
	elementIdToHandlerMap.remove(elementId);
};
function EchoFocusManager() { }
EchoFocusManager.setFocusedState = function(componentId, focusState) {
	if (focusState) {
		EchoClientMessage.messageDocument.documentElement.setAttribute("focus", componentId);
	} else {
		var focusedComponentId = EchoClientMessage.messageDocument.documentElement.getAttribute("focus");
		if (!focusedComponentId || componentId == focusedComponentId) {
			EchoClientMessage.messageDocument.documentElement.setAttribute("focus", "");
		}
	}
	EchoDebugManager.updateClientMessage();
};
function EchoHttpConnection(url, method, messageObject, contentType) {
	this.url = url;
	this.contentType = contentType;
	this.method = method;
	this.messageObject = messageObject;
	this.responseHandler = null;
	this.invalidResponseHandler = null;
	this.disposed = false;
};
EchoHttpConnection.prototype.connect = function() {
	if (!this.responseHandler) {
		throw "EchoHttpConnection response handler not set.";
	}
        this.xmlHttpRequest =  new AJAST.JsHttpRequest();
	this.xmlHttpRequest.prototype = {instance:this};
	this.xmlHttpRequest.onreadystatechange = function() { 	
	    if (this.readyState == 4) {
		if (this.status == 200) {
		    if (!this.prototype.instance.responseHandler) {
			this.prototype.instance.dispose();
			throw "EchoHttpConnection response handler not set.";
		    }
		    if(this.responseJSON.cookie) {
			document.cookie = this.responseJSON.cookie;
		    }
		    this.prototype.instance.responseHandler(this.prototype.instance);
		    this.prototype.instance.dispose();
		}
	    }
	};
        this.xmlHttpRequest.open("POST", ajastServer + Math.random(), true);
	if (this.contentType) {
	    this.xmlHttpRequest.setRequestHeader("Content-Type", this.contentType);
	    var headers = {Content_Type: this.contentType,
			   Connection: "close",
			   Cache_Control: "no-cache"};
	    var  serializer = new XMLSerializer();
	    var data = this.messageObject? serializer.serializeToString(this.messageObject) : null;
	    this.xmlHttpRequest.send(new PostObject(this.url, headers, data, getConvertCookie(), null));
	} else {
	    var  serializer = new XMLSerializer();
	    var data = this.messageObject? serializer.serializeToString(this.messageObject) : null;
	    this.xmlHttpRequest.send(new PostObject(this.url, {Connection:"close"} , this.messageObject ? this.messageObject : null, getConvertCookie(), null));
	}
};
EchoHttpConnection.prototype.dispose = function() {
	this.messageObject = null;
	this.responseHandler = null;
	this.invalidResponseHandler = null;
	this.xmlHttpRequest = null;
	this.disposed = true;
};
EchoHttpConnection.prototype.getResponseText = function() {
	return this.xmlHttpRequest ? this.xmlHttpRequest.responseJSON.content : null;
};
EchoHttpConnection.prototype.getResponseXml = function() {
        var parser = new DOMParser();        
    return this.xmlHttpRequest ? parser.parseFromString(this.xmlHttpRequest.responseJSON.content, "text/xml") : null;
};
EchoHttpConnection.prototype.processReadyStateChange = function() {
	if (this.disposed) {
		return;
	}
	if (this.xmlHttpRequest.readyState == 4) {
		if (this.xmlHttpRequest.status == 200) {
			if (!this.responseHandler) {
				this.dispose();
				throw "EchoHttpConnection response handler not set.";
			}
			this.responseHandler(this);
			this.dispose();
		} else {
			if (this.invalidResponseHandler) {
				this.invalidResponseHandler(this);
				this.dispose();
			} else {
				var statusValue = this.xmlHttpRequest.status;
				this.dispose();
				throw "Invalid HTTP Response code (" + statusValue + ") and no handler set.";
			}
		}
	}
};
EchoHttpConnection.nullMethod = function() { };
function EchoModalManager() { }
EchoModalManager.modalElementId = null;
EchoModalManager.isElementInModalContext = function(element) {
	if (typeof element == "string") {
		element = document.getElementById(element);
	}
	if (EchoModalManager.modalElementId) {
		var modalElement = document.getElementById(EchoModalManager.modalElementId);
		return EchoDomUtil.isAncestorOf(modalElement, element);
	} else {
		return true;
	}
};
function EchoScriptLibraryManager() { }
EchoScriptLibraryManager.STATE_REQUESTED = 1;
EchoScriptLibraryManager.STATE_LOADED = 2;
EchoScriptLibraryManager.STATE_INSTALLED = 3;
EchoScriptLibraryManager.librarySourceMap = new Array();
EchoScriptLibraryManager.libraryLoadStateMap = new Array();
EchoScriptLibraryManager.getState = function(serviceId) {
	return EchoScriptLibraryManager.libraryLoadStateMap[serviceId];
};
EchoScriptLibraryManager.installLibrary = function(serviceId) {
	if (EchoScriptLibraryManager.getState(serviceId) == EchoScriptLibraryManager.STATE_INSTALLED) {
		return;
	}
	try {
		var source = EchoScriptLibraryManager.librarySourceMap[serviceId];
		eval(source);
		EchoScriptLibraryManager.librarySourceMap[serviceId] = null;
		EchoScriptLibraryManager.libraryLoadStateMap[serviceId] = EchoScriptLibraryManager.STATE_INSTALLED;
	} catch (ex) {
		EchoClientEngine.processClientError("Cannot load script module \"" + serviceId + "\"", ex);
		throw ex;
	}
};
EchoScriptLibraryManager.loadLibrary = function(serviceId) {
	if (EchoScriptLibraryManager.getState(serviceId)) {
		return;
	}
	var conn = new EchoHttpConnection(EchoClientEngine.baseServerUri + "?serviceId=" + serviceId, "GET");
	conn.serviceId = serviceId;
	conn.responseHandler = EchoScriptLibraryManager.responseHandler;
	conn.connect();
	EchoScriptLibraryManager.libraryLoadStateMap[serviceId] = EchoScriptLibraryManager.STATE_REQUESTED;
};
EchoScriptLibraryManager.responseHandler = function(conn) {
	EchoScriptLibraryManager.librarySourceMap[conn.serviceId] = conn.getResponseText();
	EchoScriptLibraryManager.libraryLoadStateMap[conn.serviceId] = EchoScriptLibraryManager.STATE_LOADED;
	EchoClientEngine.incrementLoadStatus();
};
function EchoServerDelayMessage() { }
EchoServerDelayMessage.MessageProcessor = function() { };
EchoServerDelayMessage.MessageProcessor.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "set-message":
				EchoServerDelayMessage.MessageProcessor.processSetDelayMessage(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoServerDelayMessage.MessageProcessor.processSetDelayMessage = function(setDirectiveElement) {
	var i;
	var serverDelayMessage = document.getElementById(EchoServerDelayMessage.MESSAGE_ELEMENT_ID);
	if (serverDelayMessage) {
		serverDelayMessage.parentNode.removeChild(serverDelayMessage);
	}
	var contentElement = setDirectiveElement.getElementsByTagName("content")[0];
	if (!contentElement) {
		return;
	}
	var bodyElement = document.getElementsByTagName("body")[0];
	for (i = 0; i < contentElement.childNodes.length; ++i) {
		bodyElement.appendChild(EchoDomUtil.importNode(document, contentElement.childNodes[i], true));
	}
};
EchoServerDelayMessage.MESSAGE_ELEMENT_ID = "serverDelayMessage";
EchoServerDelayMessage.MESSAGE_ELEMENT_LONG_ID = "serverDelayMessageLong";
EchoServerDelayMessage.delayMessageTimeoutId = null;

EchoServerDelayMessage.timeout = 750;
EchoServerDelayMessage.activate = function() {
	var serverDelayMessage = document.getElementById(EchoServerDelayMessage.MESSAGE_ELEMENT_ID);
	if (!serverDelayMessage) {
		return;
	}
	serverDelayMessage.style.visibility = "visible";
	EchoServerDelayMessage.delayMessageTimeoutId = window.setTimeout("EchoServerDelayMessage.activateDelayMessage()", 
			EchoServerDelayMessage.timeout);
};
EchoServerDelayMessage.activateDelayMessage = function() {
	EchoServerDelayMessage.cancelDelayMessageTimeout();
	var longMessage = document.getElementById(EchoServerDelayMessage.MESSAGE_ELEMENT_LONG_ID);
	if (!longMessage) {
		return;
	}
	longMessage.style.visibility = "hidden";
};
EchoServerDelayMessage.cancelDelayMessageTimeout = function() {
	if (EchoServerDelayMessage.delayMessageTimeoutId) {
		window.clearTimeout(EchoServerDelayMessage.delayMessageTimeoutId);
		EchoServerDelayMessage.delayMessageTimeoutId = null;
	}
};
EchoServerDelayMessage.deactivate = function() {
	EchoServerDelayMessage.cancelDelayMessageTimeout();
	var serverDelayMessage = document.getElementById(EchoServerDelayMessage.MESSAGE_ELEMENT_ID);
	var longMessage = document.getElementById(EchoServerDelayMessage.MESSAGE_ELEMENT_LONG_ID);
	if (serverDelayMessage) {
		serverDelayMessage.style.visibility = "hidden";
	}
	if (longMessage) {
		longMessage.style.visibility = "hidden";
	}
};
function EchoServerMessage() { }
EchoServerMessage.STATUS_INITIALIZED = 0;
EchoServerMessage.STATUS_PROCESSING = 1;
EchoServerMessage.STATUS_PROCESSING_COMPLETE = 2;
EchoServerMessage.STATUS_PROCESSING_FAILED = 3;
EchoServerMessage.transactionCount = 0;
EchoServerMessage.messageDocument = null;
EchoServerMessage.backgroundIntervalId = null;
EchoServerMessage.enableFixSafariAttrs = false;
EchoServerMessage.processingCompleteListener = null;
EchoServerMessage.temporaryProperties = null;
EchoServerMessage.status = EchoServerMessage.STATUS_INITIALIZED;
EchoServerMessage.getTemporaryProperty = function(key) {
	if (!EchoServerMessage.temporaryProperties) {
		return undefined;
	} else {
		return EchoServerMessage.temporaryProperties[key];
	}
};
EchoServerMessage.init = function(messageDocument, processingCompleteListener) {
	EchoServerMessage.temporaryProperties = null;
	EchoServerMessage.messageDocument = messageDocument;
	EchoServerMessage.backgroundIntervalId = null;
	EchoServerMessage.processingCompleteListener = processingCompleteListener;
	EchoDebugManager.updateServerMessage();
};
EchoServerMessage.isLibraryLoadComplete = function() {
	var complete = false;
	try {
		var librariesElement = EchoServerMessage.messageDocument.getElementsByTagName("libraries").item(0);
		var libraryElements = librariesElement.getElementsByTagName("library");
		var returnValue = true;
		for (var i = 0; i < libraryElements.length; ++i) {
			var serviceId = libraryElements.item(i).getAttribute("service-id");
			var libraryState = EchoScriptLibraryManager.getState(serviceId);
			if (libraryState != EchoScriptLibraryManager.STATE_LOADED 
					&& libraryState != EchoScriptLibraryManager.STATE_INSTALLED) {
				returnValue = false;
				break;
			}
		}
		complete = true;
		return returnValue;
	} finally {
		if (!complete) {
			EchoServerMessage.status = EchoServerMessage.STATUS_PROCESSING_FAILED;
		}
	}
};
EchoServerMessage.loadLibraries = function() {
	var librariesElement = EchoServerMessage.messageDocument.getElementsByTagName("libraries").item(0);
	var headElement = document.getElementsByTagName("head").item(0);
	if (!librariesElement) {
		return;
	}
	var libraryElements = librariesElement.getElementsByTagName("library");
	for (var i = 0; i < libraryElements.length; ++i) {
		var serviceId = libraryElements.item(i).getAttribute("service-id");
		EchoScriptLibraryManager.loadLibrary(serviceId);
	}
};
EchoServerMessage.installLibraries = function() {
	var librariesElement = EchoServerMessage.messageDocument.getElementsByTagName("libraries").item(0);
	var headElement = document.getElementsByTagName("head").item(0);
	if (!librariesElement) {
		return;
	}
	var libraryElements = librariesElement.getElementsByTagName("library");
	for (var i = 0; i < libraryElements.length; ++i) {
		var serviceId = libraryElements.item(i).getAttribute("service-id");
		EchoScriptLibraryManager.installLibrary(serviceId);
		EchoClientEngine.incrementLoadStatus();
	}
};
EchoServerMessage.process = function() {
	EchoServerMessage.prepare();
	EchoServerMessage.processPhase1();
};
EchoServerMessage.prepare = function() {
	EchoClientEngine.updateTransactionId(EchoServerMessage.messageDocument.documentElement.getAttribute("trans-id"));
	if (EchoServerMessage.messageDocument.documentElement.getAttribute("xml-attr-test") == "x&#38;y") {
		EchoServerMessage.enableFixSafariAttrs = true;
	}
	if (EchoServerMessage.enableFixSafariAttrs) {
		EchoDomUtil.fixSafariAttrs(EchoServerMessage.messageDocument.documentElement);
	}
};
EchoServerMessage.processAsyncConfig = function() {
	var timeInterval = parseInt(EchoServerMessage.messageDocument.documentElement.getAttribute("async-interval"));
	if (!isNaN(timeInterval)) {
		EchoAsyncMonitor.timeInterval = timeInterval;
		EchoAsyncMonitor.start();
	}
};
EchoServerMessage.processApplicationProperties = function() {
	EchoModalManager.modalElementId = EchoServerMessage.messageDocument.documentElement.getAttribute("modal-id");
	if (EchoServerMessage.messageDocument.documentElement.getAttribute("root-layout-direction")) {
		document.documentElement.style.direction 
		= EchoServerMessage.messageDocument.documentElement.getAttribute("root-layout-direction");
	}
};
EchoServerMessage.processMessageParts = function() {
	var complete = false;
	try {
		var messagePartElements = EchoServerMessage.messageDocument.getElementsByTagName("message-part");
		for (var i = 0; i < messagePartElements.length; ++i) {
			var messagePartElement = messagePartElements[i];
			var processorName = messagePartElement.getAttribute("processor");
			var processor = eval(processorName);
			if (!processor || !processor.process) {
				throw "Processor \"" + processorName + "\" not available.";
			}
			processor.process(messagePartElement);
		}
		complete = true;
	} finally {
		EchoServerMessage.status = complete ? EchoServerMessage.STATUS_PROCESSING_COMPLETE 
				: EchoServerMessage.STATUS_PROCESSING_FAILED;
	}
};
EchoServerMessage.processPhase1 = function() {
	try {
		EchoServerMessage.status = EchoServerMessage.STATUS_PROCESSING;
		EchoServerMessage.loadLibraries();
		if (EchoServerMessage.isLibraryLoadComplete()) {
			EchoServerMessage.processPhase2();
		} else {
			EchoServerMessage.backgroundIntervalId = window.setInterval("EchoServerMessage.waitForLibraries();", 20);
		}
	} catch (ex) {
		EchoServerMessage.temporaryProperties = null;
		EchoClientEngine.processClientError("Cannot process ServerMessage (Phase 1)", ex);
		throw ex;
	}
};
EchoServerMessage.processPhase2 = function() {
	try {
		EchoServerMessage.installLibraries();
		EchoClientEngine.disableLoadStatus();
		EchoServerMessage.processMessageParts();
		EchoServerMessage.processApplicationProperties();
		if (EchoServerMessage.processingCompleteListener) {
			EchoServerMessage.processingCompleteListener();
		}
		EchoServerMessage.processAsyncConfig();
	} catch (ex) {
		EchoClientEngine.processClientError("Cannot process ServerMessage (Phase 2)", ex);
		throw ex;
	} finally {
		EchoServerMessage.temporaryProperties = null;
	}
	EchoVirtualPosition.redraw();
	++EchoServerMessage.transactionCount;
};
EchoServerMessage.setTemporaryProperty = function(key, value) {
	if (!EchoServerMessage.temporaryProperties) {
		EchoServerMessage.temporaryProperties = new Array();
	}
	EchoServerMessage.temporaryProperties[key] = value;
};
EchoServerMessage.waitForLibraries = function() {
	if (EchoServerMessage.isLibraryLoadComplete()) {
		window.clearInterval(EchoServerMessage.backgroundIntervalId);
		EchoServerMessage.backgroundIntervalId = null;
		EchoServerMessage.processPhase2();
	}
};
function EchoServerTransaction() { }
EchoServerTransaction.active = false;
EchoServerTransaction.synchronizeServiceRequest = "?serviceId=Echo.Synchronize";
EchoServerTransaction.connect = function() {
	var conn2 = new EchoHttpConnection2("/", "GET");
	conn2.responseHandler = function() {
	    if (EchoServerTransaction.active) {
		return false;
	    }
	    EchoServerDelayMessage.activate();
	    EchoAsyncMonitor.stop();
	    var conn = new EchoHttpConnection(EchoClientEngine.baseServerUri + EchoServerTransaction.synchronizeServiceRequest, 
					      "POST", EchoClientMessage.messageDocument, "text/xml");
	    conn.responseHandler = EchoServerTransaction.responseHandler;
	    conn.invalidResponseHandler = EchoServerTransaction.invalidResponseHandler;
	    EchoServerTransaction.active = true;
	    conn.connect();
	    EchoClientMessage.reset();
	}
	conn2.invalidResponseHandler = EchoServerTransaction.invalidResponseHandler;
	conn2.connect();
	return true;
};
EchoServerTransaction.invalidResponseHandler = function(conn) {
	EchoServerTransaction.postProcess();
	if (conn.xmlHttpRequest.status == 500) {
		EchoClientEngine.processServerError();
	} else if (conn.xmlHttpRequest.status == 400) {
		EchoClientEngine.processSessionExpiration();
	} else {
	}
};
EchoServerTransaction.postProcess = function() {
	EchoServerDelayMessage.deactivate();
	EchoServerTransaction.active = false;
};
EchoServerTransaction.responseHandler = function(conn) {
	EchoServerMessage.init(conn.getResponseXml(), EchoServerTransaction.postProcess);
	EchoServerMessage.process();
};
function EchoStringUtil() {};
EchoStringUtil.trim = function(s) {
	var result = s.replace(/^\s+/g, ""); 
	return result.replace(/\s+$/g, ""); 
};
EchoVirtualPosition = function() { };
EchoVirtualPosition.elementIdList = new Array();
EchoVirtualPosition.elementIdSet = new EchoCollectionsMap();
EchoVirtualPosition.enabled = false;
EchoVirtualPosition.elementIdListSorted = true;
EchoVirtualPosition.adjust = function(element) {
	if (EchoVirtualPosition.verifyPixelValue(element.style.top)
			&& EchoVirtualPosition.verifyPixelValue(element.style.bottom)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.paddingTop)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.paddingBottom)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.marginTop)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.marginBottom)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.borderTopWidth)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.borderBottomWidth)) {
		var parentHeight = element.parentNode.offsetHeight;
		var topPixels = parseInt(element.style.top);
		var bottomPixels = parseInt(element.style.bottom);
		var paddingPixels = EchoVirtualPosition.toInteger(element.style.paddingTop) 
		+ EchoVirtualPosition.toInteger(element.style.paddingBottom);
		var marginPixels = EchoVirtualPosition.toInteger(element.style.marginTop) 
		+ EchoVirtualPosition.toInteger(element.style.marginBottom);
		var borderPixels = EchoVirtualPosition.toInteger(element.style.borderTopWidth) 
		+ EchoVirtualPosition.toInteger(element.style.borderBottomWidth);
		var calculatedHeight = parentHeight - topPixels - bottomPixels - paddingPixels - marginPixels - borderPixels;
		if (calculatedHeight <= 0) {
			element.style.height = 0;
		} else {
			if (element.style.height != calculatedHeight + "px") {
				element.style.height = calculatedHeight + "px";
			}
		}
	}
	if (EchoVirtualPosition.verifyPixelValue(element.style.left)
			&& EchoVirtualPosition.verifyPixelValue(element.style.right)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.paddingLeft)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.paddingRight)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.marginLeft)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.marginRight)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.borderLeftWidth)
			&& EchoVirtualPosition.verifyPixelOrUndefinedValue(element.style.borderRightWidth)) {
		var parentWidth = element.parentNode.offsetWidth;
		var leftPixels = parseInt(element.style.left);
		var rightPixels = parseInt(element.style.right);
		var paddingPixels = EchoVirtualPosition.toInteger(element.style.paddingLeft) 
		+ EchoVirtualPosition.toInteger(element.style.paddingRight);
		var marginPixels = EchoVirtualPosition.toInteger(element.style.marginLeft) 
		+ EchoVirtualPosition.toInteger(element.style.marginRight);
		var borderPixels = EchoVirtualPosition.toInteger(element.style.borderLeftWidth) 
		+ EchoVirtualPosition.toInteger(element.style.borderRightWidth);
		var calculatedWidth = parentWidth - leftPixels - rightPixels - paddingPixels - marginPixels - borderPixels;
		if (calculatedWidth <= 0) {
			element.style.width = 0;
		} else {
			if (element.style.width != calculatedWidth + "px") {
				element.style.width = calculatedWidth + "px";
			}
		}
	}
};
EchoVirtualPosition.init = function() {
	EchoVirtualPosition.enabled = true;
	EchoDomUtil.addEventListener(window, "resize", EchoVirtualPosition.resizeListener, false);
};
EchoVirtualPosition.redraw = function(element) {
	if (!EchoVirtualPosition.enabled) {
		return;
	}
	var removedIds = false;
	if (element != undefined) {
		EchoVirtualPosition.adjust(element);
	} else {
		if (!EchoVirtualPosition.elementIdListSorted) {
			EchoVirtualPosition.sort();
		}
		for (var i = 0; i < EchoVirtualPosition.elementIdList.length; ++i) {
			element = document.getElementById(EchoVirtualPosition.elementIdList[i]);
			if (element) {
				EchoVirtualPosition.adjust(element);
			} else {
				EchoVirtualPosition.elementIdSet.remove(EchoVirtualPosition.elementIdList[i]);
				EchoVirtualPosition.elementIdList[i] = null;
				removedIds = true;
			}
		}
		if (removedIds) {
			var updatedIdList = new Array();
			for (var i = 0; i < EchoVirtualPosition.elementIdList.length; ++i) {
				if (EchoVirtualPosition.elementIdList[i] != null) {
					updatedIdList.push(EchoVirtualPosition.elementIdList[i]);
				}
			}
			EchoVirtualPosition.elementIdList = updatedIdList;
		}
	}
};
EchoVirtualPosition.register = function(elementId) {
	if (!EchoVirtualPosition.enabled) {
		return;
	}
	EchoVirtualPosition.elementIdListSorted = false;
	EchoVirtualPosition.elementIdList.push(elementId);
	EchoVirtualPosition.elementIdSet.put(elementId, 1);
};
EchoVirtualPosition.resizeListener = function(e) {
	e = e ? e : window.event;
	EchoVirtualPosition.redraw();
};
EchoVirtualPosition.sort = function() {
	var sortedList = new Array();
	EchoVirtualPosition.sortImpl(document.documentElement, sortedList); 
	EchoVirtualPosition.elementIdList = sortedList;
	EchoVirtualPosition.elementIdListSorted = true;
};
EchoVirtualPosition.sortImpl = function(element, sortedList) {
	if (element.id && EchoVirtualPosition.elementIdSet.get(element.id)) {
		sortedList.push(element.id);
	}
	for (var child = element.firstChild; child; child = child.nextSibling) {
		if (child.nodeType == 1) {
			EchoVirtualPosition.sortImpl(child, sortedList);
		}
	}
};
EchoVirtualPosition.toInteger = function(value) {
	value = parseInt(value);
	return isNaN(value) ? 0 : value;
};
EchoVirtualPosition.verifyPixelValue = function(value) {
	if (value == null || value == "" || value == undefined) {
		return false;
	}
	var valueString = value.toString();
	return valueString == "0" || valueString.indexOf("px") != -1;
};
EchoVirtualPosition.verifyPixelOrUndefinedValue = function(value) {
	if (value == null || value == "" || value == undefined) {
		return true;
	}
	var valueString = value.toString();
	return valueString == "0" || valueString.indexOf("px") != -1;
};
EchoVirtualPosition.MessageProcessor = function() { };
EchoVirtualPosition.MessageProcessor.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "register":
				EchoVirtualPosition.MessageProcessor.processRegister(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoVirtualPosition.MessageProcessor.processRegister = function(registerElement) {
	var items = registerElement.getElementsByTagName("item");
	for (var i = 0; i < items.length; ++i) {
		var elementId = items[i].getAttribute("eid");
		EchoVirtualPosition.register(elementId);
	}
};
function EchoWindowUpdate() { }
EchoWindowUpdate.process = function(messagePartElement) {
	for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
		if (messagePartElement.childNodes[i].nodeType == 1) {
			switch (messagePartElement.childNodes[i].tagName) {
			case "reload":
				EchoWindowUpdate.processReload(messagePartElement.childNodes[i]);
				break;
			case "set-focus":
				EchoWindowUpdate.processSetFocus(messagePartElement.childNodes[i]);
				break;
			case "set-title":
				EchoWindowUpdate.processSetTitle(messagePartElement.childNodes[i]);
				break;
			}
		}
	}
};
EchoWindowUpdate.processReload = function(reloadElement) {
	var message = EchoClientConfiguration.propertyMap["defaultOutOfSyncErrorMessage"]
	                                                  if (message) {
	                                                  }
	window.location.reload();
};
EchoWindowUpdate.processSetFocus = function(setFocusElement) {
	EchoVirtualPosition.redraw();
	var elementId = setFocusElement.getAttribute("element-id");
	var element = document.getElementById(elementId);
	if (element && element.focus) {
		element.focus();
	}
};
EchoWindowUpdate.processSetTitle = function(setTitleElement) {
	document.title = setTitleElement.getAttribute("title");
};
EchoClientMessage.reset();


function EchoHttpConnection2(url, method, messageObject, contentType) {
    this.url = url;
    this.contentType = contentType;
    this.method = method;
    this.messageObject = messageObject;
    this.responseHandler = null;
    this.invalidResponseHandler = null;
    this.disposed = false;
};
EchoHttpConnection2.prototype.connect = function() {
    if (!this.responseHandler) {
	throw "EchoHttpConnection2 response handler not set.";
    }
    var usingActiveXObject = false;
    if (window.XMLHttpRequest) {
	this.xmlHttpRequest = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
	usingActiveXObject = true;
	this.xmlHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
    } else {
	throw "Connect failed: Cannot create XMLHttpRequest.";
    }
    var instance = this;
    this.xmlHttpRequest.onreadystatechange = function() { 
	if (!instance) {
	    return;
	}
	try {
	    instance.processReadyStateChange();
	} finally {
	    if (instance.disposed) {
		instance = null;
	    }
	}
    };
    this.xmlHttpRequest.open(this.method, this.url, true);
    if (this.contentType && (usingActiveXObject || this.xmlHttpRequest.setRequestHeader)) {
	this.xmlHttpRequest.setRequestHeader("Content-Type", this.contentType);
    }
    this.xmlHttpRequest.send(this.messageObject ? this.messageObject : null);
};
EchoHttpConnection2.prototype.dispose = function() {
    this.messageObject = null;
    this.responseHandler = null;
    this.invalidResponseHandler = null;
    this.xmlHttpRequest = null;
    this.disposed = true;
};
EchoHttpConnection2.prototype.getResponseText = function() {
    return this.xmlHttpRequest ? this.xmlHttpRequest.responseText : null;
};
EchoHttpConnection2.prototype.getResponseXml = function() {
    return this.xmlHttpRequest ? this.xmlHttpRequest.responseXML : null;
};
EchoHttpConnection2.prototype.processReadyStateChange = function() {
    if (this.disposed) {
	return;
    }
    if (this.xmlHttpRequest.readyState == 4) {
	if (this.xmlHttpRequest.status == 200) {
	    if (!this.responseHandler) {
		this.dispose();
		throw "EchoHttpConnection2 response handler not set.";
	    }
	    this.responseHandler(this);
	    this.dispose();
	} else {
	    if (this.invalidResponseHandler) {
		this.invalidResponseHandler(this);
		this.dispose();
	    } else {
		var statusValue = this.xmlHttpRequest.status;
		this.dispose();
		throw "Invalid HTTP Response code (" + statusValue + ") and no handler set.";
	    }
	}
    }
};
EchoHttpConnection2.nullMethod = function() { };



/*
    http://www.JSON.org/json2.js
    2008-11-19

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    JSON = {};
}
(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
})();




//
// AJAST- Asynchronous Javascript and Script Tags v1.0
//
// Copyright (c) 2009, Jason Riffel
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the orgranization nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY Jason Riffel ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL Jason Riffel  BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// AJAST is a library that implements a Javascript objected named JsHttpRequest
// which can be used in place of the standard XmlHttpRequest object for
// performing AJAX requests. The main advantage of AJAST is its ability to make
// requests to foreign hosts which a standard AJAX request cannot do.
//
// The form of the request is:
//
// http[s]://[host]/[path]/jsproxy.js?ajast_s=s&ajast_l=l[&ajast_k=k]&ajast_p=p
//
// Where:
//
// ajast_s - The sequence number, required to generate unique global return
//           value variables in the return javascript. This should be unique
//           per transaction for any given client.
//
// ajast_l - The total length of the entire post payload being sent.
//
// ajast_p - The post being proxied to the remote host. This entire post may
//           not be accepted completely by the proxy, in which case the http
//           status code 100 'Continue' will be returned.  The HTTP status
//           string will contain a continuation key and the total length
//           received so far.
//
// ajast_k - The continuation key which was provided in the status text response
//           of a 100 'Continue' which was issued because they entire payload was
//           not recieved.  The client must send this along with the continued
//           payload to complete a transaction so the server can stitch the
//           partial deliveries back together before proceding.
//
// The form of the response is a javascript source file containing:
//
// AJAST.INCOMING.tN.status
//
//   This will be set to valid HTTP result status codes most likely from the
//   remote host except in proxy error cases and partial upload in which case
//   100 (Continue) will be returned.
//
// AJAST.INCOMING.tN.statusText
//
//   This value contains the statusText string from the remote host or proxy
//   describing the result of the last transaction.
//
// AJAST.INCOMING.tN.responseText
//
//   This value contains the payload returned form the remote server if any.
//   In partial messages or error cases in the proxy this value will be a blank
//   string.
//
// WHERE: tN = 't' + Sequence
//

// Create and use AJAST namespace to avoid conflicts and pollution of the 
// global namespace.
if (typeof AJAST == 'undefined') AJAST = {};

(function () 
{
  // A unique sequence number to each request which can be integrated into 
  // the global variables returned by the javascript file responses.  This 
  // is far better than using global variables in the javascript payload 
  // which can collide.
  if (typeof AJAST.Sequence == 'undefined') AJAST.Sequence = 0;

  // My research shows IE has a hard limit of URL length of 2083 while other 
  // mainstream browsers have much higher, if not unlimited lengths.  We'll 
  // use 2083 as the default for everyone.
  if (typeof AJAST.MaxPost == 'undefined') AJAST.MaxURL = 2083;

  // The configuration for the rate (in MS) in which the browser polls for 
  // the results and checks for timeout.
  if (typeof AJAST.pollingInterval == 'undefined') AJAST.pollingInterval = 25;

  // The configuration for the maximum amount (in MS) of time any request can 
  // take before a failure is reported.
  if (typeof AJAST.maxInterval == 'undefined') AJAST.maxInterval = 15000;

  // Use an internal namespace of 'INCOMING' for inbound request variables so
  // we keep from polluting our own name space.  In a worst case scenario we can
  // prune the INCOMING namespace for garbage collection if we start losing
  // track of our own requests without impacting our other members.
  if (typeof AJAST.INCOMING == 'undefined') AJAST.INCOMING = {};

  if (typeof AJAST.JsHttpRequest == 'undefined') AJAST.JsHttpRequest = function()
  {
    var me = this; // Who am I?

    // ----------------------------------------------------------------------------
    // Public properties
    // ----------------------------------------------------------------------------
    me.readyState      = 0;
    me.status          = 404;
    me.statusText      = 'Requested resource cannot be accessed at this time';
    me.responseText    = '';
    me.responseXML     = null;
    me.elapsedTime     = 0;
    me.sequence        = ++AJAST.Sequence;
    me.maxInterval     = AJAST.maxInterval;
    me.pollingInterval = AJAST.pollingInterval;
    
    // ----------------------------------------------------------------------------
    // Stubbed functions
    // ----------------------------------------------------------------------------
    // Overriden by caller as needed
    me.setRequestHeader    = function() {}
    me.onreadystatechange  = function() {}
    me.respondToReadyState = function() {}

    // Probably should not be overridden, just for compatibility
    me.setRequestHeader      = function () {}
    me.getResponseHeader     = function () { return ''; }
    me.getAllResponseHeaders = function () { return ''; }
    
    // ----------------------------------------------------------------------------
    // Public functions
    // ----------------------------------------------------------------------------
    me.initialize = function() { me.readyState = 0; }
    
    me.open = function(method, url, asynchronous) 
    {
      me.readyState = 1;
      me.respondToReadyState(1);
      me.onreadystatechange();

      // Inject the sequence number and record the URL
      if (url.match(/\?/)) _url = url.replace(/\?/, '?ajast_s=' + me.sequence + '&');
      else _url = url + '?ajast_s=' + me.sequence;
    }

    me.send = function(body) 
    {
      // Prepare simulated post into _post if capable
      if (typeof body != 'undefined')
      {
        if (typeof body == 'object')
        {
          if (typeof JSON == 'undefined')
          {
            me.status = 400;
            me.statusText = 'AJAST does not support sending native Javascript objects without first loading json2.js from http://www.json.org';
            return callback(); // Callback will finish the request with an error to the caller
          }
          else _post = JSON.stringify(body);
        }
        else _post = body;
      }

      me.readyState = 2;
      me.onreadystatechange();
      generateScriptTag(_url, 0);
    }

    // Flag as aborted to stop callbacks, try and unload the script tag
    me.abort = function () 
    {
      // Kill the timer if it is still active
      if (_checkTimer) clearInterval(_checkTimer); _checkTimer = null;

      // Enforce aborted status and only allow one call to callback per instance
      if (_aborted) return; _aborted = true; 

      collectGarbage(); // Clean up
    }

    // ----------------------------------------------------------------------------
    // Private properties
    // ----------------------------------------------------------------------------
    var _head = document.getElementsByTagName('HEAD')[0];
    var _startTime = new Date().getTime();
    var _aborted = false;
    var _post = null;
    var _node;
    var _url;
    
    // Polling mechanism for webkit browsers
    var _timepassed = 0;
    var _checkTimer = null;

    // ----------------------------------------------------------------------------
    // Private functions
    // ----------------------------------------------------------------------------
    // The file has loaded, harvest the results from the global variables mashed 
    // with the sequence and notify the client  
    function callback()
    {
      // Kill the timer if it is still active
      if (_checkTimer) clearInterval(_checkTimer); _checkTimer = null;

      // Enforce aborted status and only allow one call to callback per instance
      if (_aborted) return; _aborted = true;

      var t;
      t = getResponseValue('status');       if (t != null) me.status       = t;
      t = getResponseValue('statusText');   if (t != null) me.statusText   = t;
      t = getResponseValue('responseText'); if (t != null) me.responseText = t;

      collectExtras(); // Our nice little extension

      collectGarbage(); // Clean up after ourselves

      // Use try catch to create responseXML, result to NULL on failure
      if (me.responseText.match(/\s*</)) // Appears to contain XML
      {
        try 
        { 
          var parser = new DOMParser();
          me.responseXML = parser.parseFromString(me.responseText, "text/xml"); 
        } 
        catch(e) { me.responseXML = null; }
      }
      
      // Check for post which spans multiple requests
      if (me.status == 100 && _post)
      {
        // Decode: 'Continue at 1377 with key 907' for the offset and key
        var pieces = me.statusText.split(/\s+/);
        try { return generateScriptTag(_url, pieces[2], pieces[5]); } catch(e) { }
      }
      
      // Calculate the elapsed time
      var _endTime = new Date().getTime();
      me.elapsedTime = (_endTime - _startTime) / 1000;

      me.readyState = 4;
      me.onreadystatechange();
    }

    function generateScriptTag(url, postOffset, key)
    {
      // Handle simulated post
      if (_post)
      {
        // Compose the prefix, all leading query data before payload
        var prefix = '&ajast_l=' + _post.length;
        if (key) prefix += '&ajast_k=' + key;
        prefix += '&ajast_p=';

        // Glue together a query string minding the configured maximum length
        var maxPostLen = AJAST.MaxURL - (url.length + prefix.length);
        var payload = encodeURIComponent(_post.substring(postOffset)).substring(0, maxPostLen);
        url += prefix + payload;

        // Need to ensure that a URL encoded character is not split across the end of 
        // the payload, check for a % character in the last 2 bytes... if so strip it off.
        if (url.length == AJAST.MaxURL)
        {
          var offset = url.substring(url.length-2).indexOf('%');
          if (offset > -1) url = url.substring(0, url.length - (2 - offset));
        }
      }

      // Generate <script> node
      _node = document.createElement('script');
      _node.type = 'text/javascript';
      _node.src = url;

      // Notification strategy - Instead of using browser detection employ all three notification
      // strategies in parallel.  This gets us simpler and more reliable code and also adds timeout
      // support for all browser types in an easier way.

      // Gecko based browsers like FireFox will fire the onload method
      _node.onload = function() { callback(); } 

      // IE will fire onreadystatechange events onto the script tag, detect load with that
      _node.onreadystatechange = function()
      {
        if (this.readyState == "complete" || this.readyState == "loaded") 
          return callback();
      }

      // WebKit browsers do not support either of the previous messages so we employ a polling
      // mechanism.  The benefit of the polling mechanism is that the timeout gets applied to
      // all browser types.
      _checkTimer = setInterval(function()
      {
        _timepassed += me.pollingInterval;
        if (null != getResponseValue('status') || _timepassed > me.maxInterval)
        {
          clearInterval(_checkTimer);
          callback(); // If max polling interval was exceeded callback will finish the request with an error to the caller
        }
      }, me.pollingInterval);

      // Inject <script> node into <head> of document
      _aborted = false;
      me.readyState = 3;
      me.onreadystatechange();
      _head.appendChild(_node);
    }

    // Try for the proper result using the sequence number
    function getResponseValue(type)
    {
      try // Try for the proper result using the supplied sequence number
      {
        var val = eval('AJAST.INCOMING.t' + me.sequence + '.' + type);
        if (typeof val != 'undefined' && val != null) return val;
      }
      catch(e) {}
      
      return null; // Not found at all
    }

    // Clean up after ourselves
    function collectGarbage()
    {
      try
      {
        // Remove the script node
        _head.removeChild(_node);
        eval('delete AJAST.INCOMING.t' + me.sequence + ';');
      }
      catch(e) {}
    }

    // Extras collection will take any other received values from the response Javascript
    // and make them publicly available on the JsHttpRequest object.  This is useful for
    // returning other types or values directly from the server.
    function collectExtras()
    {
      var response = eval('AJAST.INCOMING.t' + me.sequence);
      if (typeof response == 'undefined') return;

      for (var n in response)
      {
        if (n == 'status')       continue; // Do not mess with the required values
        if (n == 'statusText')   continue;
        if (n == 'responseText') continue;
        me[n] = response[n];
      }
    }
  }
})();




var sku = 0;
var price = 1;
var quantity = 2;
var backupDelivered=false;
var ajastServer = httpToHttpsIfNeed("http://cvt2.convertglobal.com/Convert_client_1006/ajast/");

function inlineHcClosure(divId, engagementId, url, timeout, request) {
	return function() {
		if (request.readyState==4) // 4 = "loaded"
		{
		    if (request.status == 200) {
			ConvertLogger.log("inside handlereturn " + divId + " " + engagementId
					  + " " + url);
			if(request.responseJSON.cookie) {
			    document.cookie = request.responseJSON.cookie;
			}
			htmlCode = request.responseJSON.content;
			
			var htmlCodeWithHandler = ConvertHC.addUrlRedirect(htmlCode,
									   engagementId);
			var element = document.getElementById(divId);
			
			if (element) {
			    
			    var engagement = new ConvertEngagementWrapper(divId, engagementId,
									  timeout);
			    if (!engagement.containedEngagement) {
				element.innerHTML = htmlCodeWithHandler;
			    } else {
				new ConvertEngagementResult(engagementId,
							    ConvertEngagementResult.error,
						"Div for inline is not empty").send();
			    }
			} else {
			    new ConvertEngagementResult(engagementId,
							ConvertEngagementResult.error, "No div for inline").send();
			}
			ConvertEngagementResult.registerStartTime(engagementId);
		    }
		}
	};
};

function popupHcClosure(divId, engagementId, url, timeout, request) {
	return function() {
		if (request.readyState==4) // 4 = "loaded"
		{
		    if (xmlhttp.status == 200) {
			if(request.responseJSON.cookie){
			    document.cookie = request.responseJSON.cookie;
			}
			htmlCode = request.responseJSON.content;
			if (htmlCode == null) {
		new ConvertEngagementResult(engagementId,
					    ConvertEngagementResult.error,
					    "HTML content for inline is empty").send();
			    return;
			}
			var element = document.getElementById(ConvertHC.popupContainerId);
			var htmlCodeWithHandler = this.addUrlRedirect(htmlCode, engagementId);
			
			ConvertHC.popupContainer.style.maxWidth = width + "px";
			ConvertHC.popupContainer.style.maxHeight = height + "px";
			var engagement = new ConvertEngagementWrapper(
			    ConvertHC.popupContainerId, engagementId, timeout);
			if (!engagement.containedEngagement) {
			    element.innerHTML = htmlCodeWithHandler;
			} else {
			    new ConvertEngagementResult(engagementId,
							ConvertEngagementResult.error,
							"Div for inline is not empty").send();
			}
			
			new ConvertAnimation(ConvertHC.popupContainer, corner, x, y, movement,
					     speed, tripCount).start();
			ConvertHC.popupContainer.style.display = "inline";
			var hcLinks = document.getElementById(ConvertHC.popupContainerId)
			    .getElementsByTagName('a');
			for ( var i = 0; i < hcLinks.length; i++) {
			    ConvertEngine.addEventListener('click', hcLinks[i], this.acceptHC);
			}
			ConvertEngagementResult.registerStartTime(engagementId);
		    }
		}
	};
};





function PostObject(url, headers, post, cookie, rest) {
    this.url = url;
    this.headers = headers;
    this.post = post;
    this.cookie = cookie;
    this.rest = rest;
};




var addDOMLoadEvent = (function(){
    // create event function stack
    var load_events = [],
        load_timer,
        script,
        done,
        exec,
        old_onload,
        init = function () {
            done = true;

            // kill the timer
            clearInterval(load_timer);

            // execute each function in the stack in the order they were added
            while (exec = load_events.shift())
                exec();

            if (script) script.onreadystatechange = '';
        };

    return function (func) {
        // if the init function was already ran, just run this function now and stop
        if (done) return func();

        if (!load_events[0]) {
            // for Mozilla/Opera9
            if (document.addEventListener)
                document.addEventListener("DOMContentLoaded", init, false);


            // for Safari
            if (/WebKit/i.test(navigator.userAgent)) { // sniff
                load_timer = setInterval(function() {
                    if (/loaded|complete/.test(document.readyState))
                        init(); // call the onload handler
                }, 10);
            }

            // for other browsers set the window.onload, but also execute the old window.onload
            old_onload = window.onload;
            window.onload = function() {
                init();
                if (old_onload) old_onload();
            };
        }

        load_events.push(func);
    }
})();


getCookie =  function(name) {
		var result = null;
		var myCookie = " " + document.cookie + ";";
		var searchName = " " + name + "=";
		var startOfCookie = myCookie.indexOf(searchName);
		var endOfCookie;
		if (startOfCookie != -1) {
			startOfCookie += searchName.length;
			endOfCookie = myCookie.indexOf(";", startOfCookie);
			result = unescape(myCookie.substring(startOfCookie, endOfCookie));
		}
		return result;
	}




function getConvertCookie()
{
    var specialCookie="";
    if (getCookie("CVTSESSION") != null)
    {
	specialCookie= specialCookie + "CVTSESSION="+ getCookie("CVTSESSION");
    }
    
    if (getCookie("Engage1UID") != null)
    {
	if (specialCookie != "")
	{
	    specialCookie = specialCookie + "; ";
	}
	specialCookie= specialCookie + "Engage1UID=" + getCookie("Engage1UID");
    }
    if (getCookie("JSESSIONID") != null)
    {
		    if (specialCookie != "")
	{
	    specialCookie = specialCookie + "; ";
	}
	specialCookie= specialCookie + "JSESSIONID=" + getCookie("JSESSIONID");
    }
    return specialCookie;
    
}


function getConvertCookieWithoutSpaces()
{
    var specialCookie="";
    if (getCookie("CVTSESSION") != null)
    {
	specialCookie= specialCookie + "CVTSESSION="+ getCookie("CVTSESSION");
    }
    
    if (getCookie("Engage1UID") != null)
    {
	if (specialCookie != "")
	{
	    specialCookie = specialCookie + ";";
	}
	specialCookie= specialCookie + "Engage1UID=" + getCookie("Engage1UID");
    }
    if (getCookie("JSESSIONID") != null)
    {
		    if (specialCookie != "")
	{
	    specialCookie = specialCookie + ";";
	}
	specialCookie= specialCookie + "JSESSIONID=" + getCookie("JSESSIONID");
    }
    return specialCookie;
    
}




function processForm(formobj,formurl,elementId,afterAction) {

	if(elementId=="tabcontents") {
		document.getElementById("tabcontents").innerHTML = loadingImageTag;
	}
	var formArray = formobj.getElementsByTagName("input");
	var textAreaArray = formobj.getElementsByTagName("textarea");
	var selectArray = formobj.getElementsByTagName("select");
	var select_on_complete = false;
	var data = '';
	for (var i = 0; i < formArray.length; i++) {
		if (formArray[i].name != "") {
			switch (formArray[i].type) {
				case "text":
				case "password":
				case "hidden":
					if (formArray[i].name == "__select_on_complete") {
						if(formArray[i].value == "true") {
							select_on_complete = true;
						}
					} else {
						data += "&" + formArray[i].name + "=" + encodeURIComponent(formArray[i].value);
					};
					break;
				case "checkbox":
					if(formArray[i].checked) {
						data += "&" +formArray[i].name + "=on";
					} else {
						data += "&" +formArray[i].name + "=off";
					};
					break;
				case "radio":
					if(formArray[i].checked) {
						data += "&" +formArray[i].name + "=" + encodeURIComponent(formArray[i].value);
					}
					break;
			}

		}
	}
	for (var i = 0; i < textAreaArray.length; i++) {
		if (textAreaArray[i].name != "") {
			data += "&" + textAreaArray[i].name + "=" + encodeURIComponent(textAreaArray[i].value);
		}
	}
	for (var i = 0; i < selectArray.length; i++) {
		if (selectArray[i].name != "") {
			if(selectArray[i].selectedIndex) {
				data += "&" + selectArray[i].name + "=" + encodeURIComponent(selectArray[i].options[selectArray[i].selectedIndex].value);
			} else if (selectArray[i].options[0]) {
				data += "&" + selectArray[i].name + "=" + encodeURIComponent(selectArray[i].options[0].value);
			}
		}
	}
	var http = getHTTPObject();
	http.open("POST",ajastServer + Math.random(),true);
	http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	http.setRequestHeader("Content-length", data.length);
	http.setRequestHeader("Connection","close");
        var headers = {
	    Content_Type:"application/x-www-form-urlencoded",
	    Connection:"close"
	};
	http.onreadystatechange = function() {
		if (http.readyState == 4) {
		    if (http.status == 200) {
			if(http.responseJSON.content) {
			    document.getElementById(elementId).innerHTML = http.responseJSON.content;
			}
			if(afterAction) afterAction();
		    } else {
			alert('ERROR: ' + xmlhttp.status + ' -> ' + xmlhttp.statusText);
		    }
		}
	}

	p =    new PostObject(formurl,headers, data, getConvertCookie(), null)     ;
        http.send(p);

	return false;
};




function getHTTPObject() {
	var http;
	if(window.XMLHttpRequest) {
		try {
			http = new XMLHttpRequest();
		} catch(e) {
			http = false;
		}
	// branch for IE/Windows ActiveX version
	} else if(window.ActiveXObject) {
		try {
			http = new ActiveXObject("Msxml2.XMLHTTP");
		} catch(e) {
			try {
				http = new ActiveXObject("Microsoft.XMLHTTP");
			} catch(e) {
				http = false;
			}
		}
	}
	return new AJAST.JsHttpRequest();
}


function addScriptObj(text) {
	if (navigator.userAgent.search(/WebKit/i) != -1) {
		var headObj = document.getElementsByTagName("head")[0];
		var scriptObj = document.createElement('script');
		scriptObj.setAttribute("type","text/javascript");
		scriptObj.defer = true;
		scriptObj.text = text;
		headObj.appendChild(scriptObj);
		return;
	}

	var _global = this;
  if (window.execScript)
    window.execScript(text);
  else {
  	if (_global.eval)
  		_global.eval(text)
  	else
  		eval(text);
	}
}

// Configuration

// todo implement in standard manner
function callMeNow(url, type, id) {
    window.open(url + '?type=' + type + '&id=' + id, '_blank',
            'width=498,height=347,resizable=no,scrollbars=no,menubar=no,toolbar=no,' +
            'status=no,location=no,directories=no,copyhistory=no');
}

//adding convenient method to array
// for removing element
Array.prototype.remove = function(s) {
   for (i = 0; i < this.length; i++) {
     if (s == this[i])
        this.splice(i, 1);
   }
 }

//checks whether element is of string type
function isString(elem) {
    if (typeof elem == 'string') {
        return true;
    }
    return false;
}

//if current page is under https - also convert url to https
function httpToHttpsIfNeed(url) {
	if (document.location.href.indexOf("https:") >= 0
			&& url.indexOf("http:") >= 0) {
		return url.replace(/http:/g, "https:");
	} else {
		return url;
	}
}

//convert array of urls refer remote server to
// array of urls refer proxy
function convertUrlsToProxy(urls) {
    if (ConvertPage.proxyPath == "") {
        return urls;
    }
    var proxyUrls = new Array();
    for (var name in urls) {
        if (isString(urls[name])) {
            proxyUrls[name] = convertUrl(urls[name]);
        }
    }
    return proxyUrls;
}

//convert one url refers remote server
// to url refers proxy
function convertSingleUrl(url) {
    if (ConvertPage.proxyPath == "") {
        return url;
    }
    return convertUrl(url);
}

//convert url - don't use directly.
// Use convertUrlsToProxy or convertSingleUrl instead
function convertUrl(url) {
    var domainRegExp = /https?:\/\/.*?\//;
    var proxyUrl = url.replace(domainRegExp, ConvertPage.server + ConvertPage.proxyPath);
    return proxyUrl;
}

function fillGroupsData(params) {
    var metaData = document.getElementsByTagName("meta");
    var convertGroups = "";

	convertGroups = window["convert_group"] || "";
    for (var i = 0; i < metaData.length; i++) {
        if (metaData[i].name == "WT.cg_n"
               || metaData[i].name == "WT.cg_s") {
            if (convertGroups.length > 0) {
                convertGroups = convertGroups + ",";
            }
            convertGroups = convertGroups + metaData[i].content;
        }
    }
    params["groups"] = convertGroups;
}

function fillBookingData(params) {
    var metaData = document.getElementsByTagName("meta");
    var findFirst = false;
    var findSecond = false;

    for (var i = 0; i < metaData.length; i++) {
        if (metaData[i].name == "WT.cg_n") {
            findFirst = (metaData[i].content == "booking");
        } else if (metaData[i].name == "WT.cg_s") {
            findSecond = (metaData[i].content == "book");
        }
    }
    //if meta is set - get params
    if (findFirst && findSecond) {
        var bookingVars = new Array("DCSext.ORG", "DCSext.DST", "DCSext.PTP", "DCSext.FPC", "DCSext.DDT", "DCSext.RF", "DCSext.AF", "DCSext.TF", "DCSext.NBR", "DCSext.RDT", "DCSext.PAX", "DCSext.ACCOM",
                  "DCSext.PRO", "DCSext.CC", "DCSext.DF", "DCSext.TC", "DCSext.DLV", "DCSext.Miles", "DCSext.BookingChannel");
        for (var i = 0; i < metaData.length; i++) {
            for (var j = 0; j < bookingVars.length; j++) {
                if (metaData[i].name == bookingVars[j]) {
                	var val = metaData[i].content;
                	if (val.indexOf("preencoded") == 0) {
                		//remove "preencoded:" prefix
                		val = val.substring(11, val.length);
                	}
                	params['bp_' + bookingVars[j]] = val;
                	break;
                }
            }
        }
    }
}

//This function gets all visitor profile field values - from
// meta-tags or from convertVisitorParams variable - and sends them to
// server. Some values can correspond to existing fields,
// for some values there is no fields. In first case, values will
// be assigned to fields. In second case, values will be ignored.
function fillProfileData(params) {
    var metaData = document.getElementsByTagName("meta");

    var paramsCount = 0;
    for (var i = 0; i < metaData.length; i++) {
        //get start of meta tag name
        var startStr = metaData[i].name.substring(4, 0);
        if (startStr == "CVT.") {
            var fieldName = metaData[i].name.substring(4, metaData[i].name.length);
			if(fieldName == "userID") {
				params['cvp_login'] = metaData[i].content;
			} else {
				params['cvp_' + fieldName] = metaData[i].content;
			}
            paramsCount++;
        }
    }

    //if meta tags didn't provide field values - use old
    // way to get values - through var convertVisitorParams
    if (paramsCount == 0) {
        var convertVisitorParams = window["convertVisitorParams"];
        if (convertVisitorParams) {
            for (var name in convertVisitorParams) {
                params['cvp_' + name] = convertVisitorParams[name];
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////
// General purpose methods.
// /////////////////////////////////////////////////////////////////////////

/**
 * Convert general purpose functionality
 */
var ConvertEngine = {

    nextSerialId: 0,
    elementIdPrefix: "convert_element_",
    visitorIdCookieName: "Engage1UID",
    chatDetached: false,

// Creates unique id for convert DOM element.
    createElementId: function() {
        var elementId;
        do {
            elementId = ConvertEngine.elementIdPrefix + (ConvertEngine.nextSerialId++);
        } while (document.getElementById(elementId))
        return elementId;
    },

// Extracts cookie value
    getCookie: function(name) {
        var result = null;
        var myCookie = " " + document.cookie + ";";
        var searchName = " " + name + "=";
        var startOfCookie = myCookie.indexOf(searchName);
        var endOfCookie;
        if (startOfCookie != -1) {
            startOfCookie += searchName.length;
            endOfCookie = myCookie.indexOf(";", startOfCookie);
            result = unescape(myCookie.substring(startOfCookie, endOfCookie));
        }
        return result;
    },

// Detaches the floating chat into a separate browser window
    detachChat: function() {
        ConvertEngine.chatDetached = true;

        var detachContainer = document.getElementById("detach");
        var detach = detachContainer.getElementsByTagName("div");
        EchoClientMessage.setActionValue(detach[0].id, "click");
        EchoServerTransaction.connect();

        ConvertEngine.openDetachedChat();
    },

    openDetachedChat: function() {
        window.open(ConvertPage.chatServletPath + "?detached=true&init=true&chatTemplate=" + ConvertPage.chatTemplateDetached, "detachedChat",
            "width=400,height=400,toolbar=no,location=no,directories=no," +
            "status=no,menubar=no,scrollbars=no,resizable=yes");
    },

// forces a repaint of the chat window in Safari
    repaintSafariScroll: function() {
        var tmp = document.createElement("div");
        document.body.appendChild(tmp);
    },

// Returns convert visitor id.
    getVisitorId: function() {
        return ConvertEngine.getCookie(ConvertEngine.visitorIdCookieName) || -1;
    },

// Adds event listener to the element.
    addEventListener: function(type, target, listener) {
        if (target.addEventListener) {
            target.addEventListener(type, listener, false);
        } else {
            target.attachEvent("on" + type, listener);
        }
    },

// Removes event listener to the element.
    removeEventListener: function(type, target, listener) {
        if (target.removeEventListener) {
            target.removeEventListener(type, listener, false);
        } else {
            target.detachEvent("on" + type, listener);
        }
    },

// Opens document in the specified window.
    openWindow: function(url, target) {
        window.open(url, target ? target : "_blank",
                "width=700,height=500,toolbar=yes,location=yes,directories=yes," +
                "status=yes,menubar=yes,scrollbars=yes,copyhistory=yes,resizable=yes");
    },

// Processes recursively all elements inside given container.
    processRecursive: function(element, processor) {
        for (var target = element.firstChild; target; target = target.nextSibling) {
            processor(target);
            if (target.nodeType == 1) {
                ConvertEngine.processRecursive(target, processor);
            }
        }
    },

// Makes convert client initialization.
    init: function() {
        ConvertLogger.init();
        ConvertImagePreload.init();
        ConvertChatInvitation.init();
        ConvertSurvey.init();
        ConvertMO.init();
        ConvertHC.init();
        ConvertAnimation.init();
    }
}

///////////////////////////////////////////////////////////////////////////
// Code responsible for 'DOM loaded' event listening
// /////////////////////////////////////////////////////////////////////////
// Based on ELO - Encapsulated Load Object, by Robert Nyman,
// http://www.robertnyman.com

var ConvertELO = {
    listeners: new Array(),

// (public) adds DOM content load listener
    addDOMLoadListener: function(listener) {
        ConvertELO.listeners[ConvertELO.listeners.length] = listener;
    },

// (private) invoked on DOM content load, probably multiple times
    fireDOMLoaded: function() {
        if (!ConvertELO.loaded) {
            ConvertELO.loaded = true;
            for (var i = 0; i < ConvertELO.listeners.length; i++) {
                try {
                    ConvertELO.listeners[i].call(window);
                } catch (e) {
                }
            }
        }
    }
};



// Mozilla/Opera 9
if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", ConvertELO.fireDOMLoaded, false);
}

// Safari
if (navigator.userAgent.search(/WebKit/i) != -1) {
    ConvertELO.timer = setInterval(function() {
        if (document.readyState.search(/loaded|complete/i) != -1) {
            clearInterval(ConvertELO.timer);
            ConvertELO.fireDOMLoaded();
        }
    }, 100);
}

// Others
// ConvertEngine.addEventListener("load", window, ConvertELO.fireDOMLoaded);
addDOMLoadEvent(ConvertELO.fireDOMLoaded);

// /////////////////////////////////////////////////////////////////////////
// Convert page lifecycle
// /////////////////////////////////////////////////////////////////////////

// Handles page load/unload focus/blur page lifecycle events.
var ConvertPage = {
    echoForm: null,
    pageId: new Date().getTime(),
//    siteId: 1000, //TODO: DO NOT Commit!
    siteId: "1006",
    server: httpToHttpsIfNeed("http://cvt2.convertglobal.com/"+
			      ("http://cvt2.convertglobal.com/".charAt("http://cvt2.convertglobal.com/".length -1) == "/" ? "" :"/")),
    appContextRoot: null,
    appServletPath: null,
    chatServletPath: null,
    chatTemplateFloating: null,
    chatTemplateDetached: null,
    inlineRedirectServletPath: null,
    reactiveChatType: null,
    embeddedChat: null,
    blurred: false,
    isActivePage: true,
    pageInfoTimerId: null,
    proxyPath: "",
    remoteServer: httpToHttpsIfNeed("http://cvt2.convertglobal.com/"),
    sendMailAlloved : false,

// Includes Echo2 js, adds listeners to the window
    init: function() {
	ConvertPage.setCookie();
        ConvertPage.webAppContextRoot = ConvertPage.server + ConvertPage.proxyPath + "1006_Convert";
        ConvertPage.appContextRoot = ConvertPage.server + ConvertPage.proxyPath + "Convert_client_1006";
        ConvertPage.appServletPath = ConvertPage.appContextRoot + "/app";
        ConvertPage.chatServletPath = ConvertPage.appContextRoot + "/chat";
        ConvertPage.detachedchatServletPath = ConvertPage.appContextRoot + "/d_chat";
        ConvertPage.inlineRedirectServletPath = ConvertPage.remoteServer + "1006_Convert" + "/inline/Accept";
        ConvertPage.getIpServletPath = httpToHttpsIfNeed(ConvertPage.remoteServer + "1006_Convert" + "/OnlineInfo/");
        ConvertPage.bulkEmailServletPath = httpToHttpsIfNeed(ConvertPage.webAppContextRoot + "/BulkEmailVerifier");
        ConvertPage.chatTemplateFloating = httpToHttpsIfNeed("http://cvt2.convert/houseofnutrition/chatTemplate.html");
        ConvertPage.chatTemplateDetached = httpToHttpsIfNeed("http://cvt2.convertglobal.com/houseofnutrition/chatTemplate.html");
        ConvertPage.reactiveChatType = "float"; // "float", "frame", or "detach"
        ConvertPage.embeddedChat = ConvertPage.server + ConvertPage.proxyPath + "Convert_client_1006" + "/embeddedChat.html";

	ConvertPage.getIp(function() {
            addDOMLoadEvent(function() {
        	ConvertPage.sendPageInfo(function() {
        	    ConvertPage.initActive();
        	});
		ConvertPage.replaceSubmitAction();
            });
        });
    },



    replaceSubmitAction: function(){
      var formarray = document.getElementsByTagName("form");
      for (var i=0; i<formarray.length; i++){
	     
					
	  var form = formarray[i];

	  if (form.action.match(/.*stores\.yahoo\..*/) )
	      {
		  ConvertPage.linkPost(form);
	      }

	  }
    },

    setCookie: function() {
	CVTSessionCookie = window.location.href.match(/CVTSESSION=.*;/);
	Engage1UIDCookie = window.location.href.match(/Engage1UID=.*/);

	if(CVTSessionCookie) {
	    document.cookie = CVTSessionCookie.toString();
	}

	if(Engage1UIDCookie) {
	    document.cookie = Engage1UIDCookie.toString();
	}	
    },
    

    linkPost: function(formObject) {
	formObject.action += "#" + getConvertCookieWithoutSpaces();
	return false;
    },



// Initialization for standard mode
    initActive: function() {
    	//send page info after DOM loading, because in case of proxy,
    	//ConvertPage.ip is initialized with DOM (see getIp() method)
        // ConvertPage.sendPageInfo(true, callback);

        var form = document.createElement("form");
        form.id = "c_root";
        form.action = "#";
        form.onsubmit = "return false";
        ConvertPage.echoForm = form;

        var windowComponent = document.getElementById("window");
        if (!windowComponent) {
            windowComponent = document.body;
            windowComponent.id = "window";
        }
        windowComponent.appendChild(form);

        var div = document.createElement("div");
        div.innerHTML =  httpToHttpsIfNeed('\n<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '
	               + 'codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" '
	               + 'width="1" height="1" id="convertFlashPlayer" align="middle"> '
	               + '<param name="allowScriptAccess" value="sameDomain" /> '
	               + '<param name="movie" value="' + ConvertPage.appContextRoot + '/convert.swf" /> '
	               + '<param name="quality" value="high" /> '
	               + '<param name="bgcolor" value="#ffffff" /> '
	               + '<embed src="' + ConvertPage.appContextRoot + '/convert.swf" quality="high" bgcolor="#ffffff" width="1" '
		       + 'height="1" name="convertFlashPlayer" align="middle" '
		       + 'allowScriptAccess="sameDomain" type="application/x-shockwave-flash" '
		       + 'pluginspage="http://www.macromedia.com/go/getflashplayer" '
		       + 'name="flashPlayer" /></object>\n ');
        document.body.appendChild(div);


// ConvertEngine.addEventListener("unload", window, ConvertPage.onUnload);
        ConvertEngine.addEventListener("focus", window, ConvertPage.onFocus);
        ConvertEngine.addEventListener("blur", window, ConvertPage.onBlur);

        /*
		 * Following lines causes troubles on Ridegear: NOTE if you uncomment
		 * this line, do not forgot about modify
		 * EchoTextComponent.prototype.processFocus and
		 * EchoTextComponent.prototype.processBlur methods in
		 * WebContainer\nextapp\echo2\webcontainer\resource\js\TextComponent.js
		 * file
		 * 
		 * if (window.attachEvent) { //only do this for IE
		 * //window.attachEvent("onclick", handleFocus); var inputs =
		 * document.getElementsByTagName("input"); for (var i = 0; i <
		 * inputs.length; i++) { inputs[i].attachEvent("onfocus", handleFocus);
		 * inputs[i].attachEvent("onblur", handleBlur); } var textareas =
		 * document.getElementsByTagName("textarea"); for (var i = 0; i <
		 * textareas.length; i++) { textareas[i].attachEvent("onfocus",
		 * handleFocus); textareas[i].attachEvent("onblur", handleBlur); } }
		 */
	ConvertLogger.log("////////////////////////////////");
        EchoClientEngine.init(ConvertPage.appServletPath, true); // boolean
																	// is for
																	// Echo2
																	// debug
																	// mode
	ConvertLogger.log("________________________________");
        EchoAsyncMonitor.pollServiceRequest = "?serviceId=Echo.AsyncMonitor&pageId=" + ConvertPage.pageId;
        EchoServerTransaction.synchronizeServiceRequest = "?serviceId=Echo.Synchronize&pageId=" + ConvertPage.pageId;
        // Replace default Echo response handler to avoid exception if
		// documentElement is null
        EchoAsyncMonitor.responseHandler = function(conn) {
            if (conn.getResponseXml().documentElement != null && "true" == conn.getResponseXml().documentElement.getAttribute("request-sync")) {
                // Server is requesting synchronization: Initiate server transaction.
                EchoServerTransaction.connect();
            } else {
                // Server does not require synchronization: restart countdown to next poll request.
               EchoAsyncMonitor.start();
            }
        };
        ConvertEngine.init();
    },

// Initialization for the case when embedded chat presented on the page
    initPassive: function() {
        var links = document.getElementsByTagName("a");
        for (var i = 0; i < links.length; i++) {
            var href = links[i].href;
            if (href && href.indexOf("http") == 0 && href.indexOf(ConvertPage.server) == -1) {
                links[i].target = "_blank";
            }
        }
    },


// Sends page info to the server. Used for initial requests and Echo2 app recovery
    sendPageInfo: function(callback) {
        ConvertPage.pageInfoTimerId = null;
        var params = {
            htmlservice: false,
            //groups: convertGroups,
            site: ConvertPage.siteId,
            referrer: document.referrer,
            reactiveChatType: ConvertPage.reactiveChatType,
            proactiveChatEmbedded: false,
            embeddedChatPage: ConvertPage.embeddedChat,
            chatTemplate: ConvertPage.chatTemplateFloating,
            placeOnHoldMsg: "Please stand by, a site representative has been paged.",

            pageId: ConvertPage.pageId,
            pageURL: document.location.href,
            title: document.title,
            chatButton: window.chatButton,
            visitorEmail: window.visitorEmail,
            visitorLogin: window.visitorLogin,

            // Shopping cart data. Values will be fetched by processShoppingCart in E1AjaxProxy
            purchased: window["convert_shopping_cart_purchased"],
            promoCode: window["convert_shopping_cart_promo_code"],
            totalCost: window["convert_shopping_cart_total_value"],
            orderId: window["convert_shopping_cart_order_id"],
            count: window["convert_shopping_cart_items_count"],
            taxes: window["convert_shopping_cart_taxes"],
            discount: window["convert_shopping_cart_discount"],
            shipping: window["convert_shopping_cart_shipping"],
            shoppingCartSize: window["convert_shopping_cart_items_count"]
        };
        if (ConvertPage.ip) {
            params.ip = ConvertPage.ip;
        }

        if ('string' == typeof(params.totalCost)) {
            //remove unneeded '$' sign
            params.totalCost = params.totalCost.replace('$', '');
        }

        var items = window["convert_shopping_cart_items"];
        if (params.count && items && items.length > 0) {
            for (var i = 0; i < params.count; i++) {
                if ('string' == typeof(items[i][price])) {
                    //remove unneeded '$' sign
                    items[i][price] = items[i][price].replace('$', '');
                }
                params['s' + i] = items[i][sku];
                params['p' + i] = items[i][price];
                params['q' + i] = items[i][quantity];
            }
        }

        fillProfileData(params);
        fillGroupsData(params);

        ConvertLogger.log("Sending page info. PageId is " + params.pageId);

        // there is no handler for "pageLoad"
        // new ConvertClientMessage("pageLoad", params).send(true);
        new ConvertRequest(ConvertPage.appServletPath, null, null, callback).send(params, true);
    },

// Notifies server about page has been unloaded
    onUnload: function() {
        new ConvertClientMessage("pageUnload", {
            pageUrl: document.location.href,
            pageId: ConvertPage.pageId
        }).send(true);
        // force reload frames
        for (var i=0; i<frames.length; i++) {
            frames[i].history.go(0);
        }
    },

// Handles window focus gain events. Restores sync with server if necessary.
    onFocus: function(evt) {
        ConvertLogger.log("Focus gained. Timer: " + EchoAsyncMonitor.timeoutId + "; blurred: " + ConvertPage.blurred);
        var target = window.event ? window.event.srcElement : evt.target;
        if (target && target.nodeType == 1) {
            ConvertLogger.log("Event source element: " + target.tagName);
        }

        ConvertPage.blurred = false;
        if (!ConvertPage.isActivePage) {
            ConvertLogger.log("Setting as active page: " + ConvertPage.pageId);
            ConvertPage.isActivePage = true;
            new ConvertRequest(ConvertPage.appServletPath).send({setActivePageId: ConvertPage.pageId});
        }
    },

// Handles window focus lost events. Restores sync with server if necessary.
    onBlur: function(evt) {
        ConvertLogger.log("Focus lost. Timer: " + EchoAsyncMonitor.timeoutId + "; blurred: " + ConvertPage.blurred);
        var target = window.event ? window.event.srcElement : evt.target;
        if (target && target.nodeType == 1) {
            ConvertLogger.log("Event source element: " + target.tagName);
        }
        ConvertPage.blurred = true;
        setTimeout(function() {
            if (ConvertPage.isActivePage) {
                ConvertLogger.log("Setting as inactive page: " + ConvertPage.pageId)
                // EchoAsyncMonitor.stop();
                ConvertPage.isActivePage = false;
            }
        }, 1000);
    },

// Dynamically includes a CSS file into the web page
    includeCSS: function(cssUrl) {
	//css should be download from local site, not remote through proxy
        // var proxyCssUrl = convertSingleUrl(cssUrl);
        var head = document.getElementsByTagName("head")[0];
        var node = document.createElement("link");
        node.type = "text/css";
        // if page is https - modify css url also to https
        node.href = httpToHttpsIfNeed(cssUrl);
        node.media = "all";
        node.rel = "Stylesheet";
        head.appendChild(node);
    },

// Dynamically includes a CSS style into the web page
    includeCssStyle: function(cssCode) {
        var styleElement = document.createElement("style");
        styleElement.type = "text/css";
        if (styleElement.styleSheet) {
            styleElement.styleSheet.cssText = cssCode;
        } else {
            styleElement.appendChild(document.createTextNode(cssCode));
        }
        document.getElementsByTagName("head")[0].appendChild(styleElement);
    },

// Recovers Echo2 application on server side.
    recoverEcho: function() {
        if (!ConvertPage.pageInfoTimerId) {
            ConvertLogger.log("Recovering Echo2 application");
            var root = document.getElementById("window");
            var toDelete = new Array();
            for (var element = root.firstChild; element; element = element.nextSibling) {
                if (EchoDomPropertyStore.getPropertyValue(element.firstChild, "component")) {
                    toDelete[toDelete.length] = element;
                }
            }
            for (var i = 0; i < toDelete.length; i++) {
                root.removeChild(toDelete[i]);
            }

            EchoClientMessage.reset();
            EchoClientMessage.setInitialize();
            EchoClientAnalyzer.analyze();
            EchoServerTransaction.postProcess();
            EchoServerTransaction.connect();
            ConvertPage.pageInfoTimerId = setTimeout("ConvertPage.sendPageInfo()", 500);
        }
    },

    getIp: function(callback) {
	var request = new AJAST.JsHttpRequest();
	request.onreadystatechange = function() {
	    if(request.readyState == 4) {
		if(request.status=200) {
		    addScriptObj(request.responseText);
		    if(callback) {
			callback();
		    }
		}
	    }
	}
	var url =  ajastServer + "?get_my_ip=";
	request.open("GET", url);
	request.send();
    },

    checkFramedChatOpened: function(redirectUrl) {
        if (!parent.ConvertEngineConfig) {
             window.location = redirectUrl;
        }
    }
}

///////////////////////////////////////////////////////////////////////////
// Convert image preload
// /////////////////////////////////////////////////////////////////////////

// Loads images used for 'hover' buttons state rendering to prevent
// buttons disappearing on first 'mouse over' event
var ConvertImagePreload = {
    imageList: ["/images/chat/send_hover.jpg", "/images/chat/close_hover.jpg"],

    init: function() {
        var imageList = ConvertImagePreload.imageList;
        for (var i = 0; i < imageList.length; i++) {
            var image = document.createElement("img");
            var src = ConvertPage.appContextRoot + imageList[i];
            image.src = src;
            imageList[i] = image;
            ConvertLogger.log(src + " cached");
        }
    }
};

// /////////////////////////////////////////////////////////////////////////
// Convert transport
// /////////////////////////////////////////////////////////////////////////

// Request constructor
function ConvertRequest(url, resultHandler, errorHandler, callback) {
    this.url = url;
    this.resultHandler = resultHandler;
    this.errorHandler = errorHandler;
    this.proxy = ajastServer + Math.random();
    this.callback = callback;
    ConvertLogger.log("ConvertRequest object created for url: " + url);
}

// XMLHttpRequest object's ready state value when interactions completed
ConvertRequest.READY_STATE_COMPLETE = 4;

// Request methods
ConvertRequest.prototype = {

// (public) Sends parameters to server
    send: function(parameters, blocking, method) {
        var request = this.createRequest();
        if (request) {
            request.open("POST", this.proxy, !blocking);
            request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	    var headers = {Content_Type: "application/x-www-form-urlencoded"};
            var thisInstance = this;
	    request.prototype ={callback: this.callback};
            request.onreadystatechange = function() {
            	if (request.readyState==4) // 4 = "loaded"
            	{
            		if (request.status == 200) {
            			thisInstance.processAjaxResponse(request);
            			if(this.prototype.callback) {
            				this.prototype.callback();
            			}
            		}
            	}
            }
            var queryString = this.createQueryString(parameters);
            ConvertLogger.log("Sending " + (blocking ? "" : "non-") + "blocking request: " + queryString);
            request.send(new PostObject(this.url, headers, queryString, getConvertCookie(), null));
            ConvertLogger.log("Sent")
        } else {
            ConvertLogger.log("Fatal error: Unable to create XMLHttpRequest object");
        }
    },

// (private) creates XmlHttpRequest object
     createRequest: function() {
        var request = null;
        if (window.XMLHttpRequest) {
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            var aVersions = [ "MSXML2.XMLHTTP.7.0", "MSXML2.XMLHTTP.6.0", "MSXML2.XMLHttp.5.0",
	        "MSXML2.XMLHttp.4.0","MSXML2.XMLHttp.3.0",
	        "MSXML2.XMLHttp","Microsoft.XMLHttp"
    		];
	    	for (var i = 0; i < aVersions.length; i++) {
		    	try {
	         	    request = new ActiveXObject(aVersions[i]);
					break;
	            } catch (oError) {
			    }
            }
        }
        return new AJAST.JsHttpRequest();
;
    },


// (private) joins parameters associative array into urlencoded string)
    createQueryString: function(parameters) {
        var result = "";
        if (parameters) {
            for (var name in parameters) {
                var value = parameters[name];
                if (typeof value != "undefined") {
                    result += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(value);
                }
            }
        }
        return result.substring(1);
    },

// (private) transport callback method
    processAjaxResponse: function(request) {
        ConvertLogger.log("Request object callback called with " + request.readyState + " ready state");
        if (request.readyState == ConvertRequest.READY_STATE_COMPLETE) {
            var status = request.status;
            if (!status || status == 200) {
                ConvertLogger.log("Request is succeeded");
		if(request.responseJSON.cookie) {
		    document.cookie = request.responseJSON.cookie;
		}
                if (this.resultHandler) {
                    this.resultHandler(request);
                }
            } else {
                ConvertLogger.log("Request failed: " + request.statusText + " (HTTP error code: " + status + ")");
                if (this.errorHandler) {
                    this.errorHandler(request);
                }
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////
// Object responsible for elements positioning and animation
// /////////////////////////////////////////////////////////////////////////

// Creates object responsible for target moving across the screen.
// To position element [and start movement] you must instantiate this object and
// invoke it's start() method.
// Parameters (corner, x and y are required):
// target: HTML element to move (if element is Echo2 WindowPane container,
// appropriate handling will be used).
// corner: one of "Upper Left", "Upper Right", "Lower Right" or "Lower Left";
// x, y: Original target coordinates;
// direction: movement direction, one of "None", "Up - Down" or "Left - Right";
// speed: movement speed (pixels per second)
// tripCount: full trips count before movement stops;
function ConvertAnimation(target, corner, x, y, direction, speed, tripCount) {
    if (target.animation) {
        target.animation.dispose();
    }
    target.animation = this;
    this.target = target;

    corner = corner.split(" ");
    this.xPosition = new ConvertAnimation.Position(corner[1] == "Left", x, "scrollWidth", "scrollLeft");
    this.yPosition = new ConvertAnimation.Position(corner[0] == "Upper", y, "scrollHeight", "scrollTop");

    this.turnCount = ConvertAnimation.abs(tripCount) * 2;
    var step = ConvertAnimation.abs(speed) * ConvertAnimation.period * .001;
    if (direction == "Left - Right") {
        this.xPosition.step = step;
    } else if (direction == "Up - Down") {
        this.yPosition.step = step;
    }
}

ConvertAnimation.period = 40;
ConvertAnimation.instances = new Array();

ConvertAnimation.abs = function(value) {
    value = parseInt(value);
    return isNaN(value) ? 0 : Math.abs(value);
}

// (public) starts animation system timer
ConvertAnimation.init = function() {
    setInterval("ConvertAnimation.process()", ConvertAnimation.period);
}

// (private) processes elements on timeout
ConvertAnimation.process = function() {
    var animations = ConvertAnimation.instances;
    for (var i = 0, size = animations.length; i < size; i++) {
        var animation = animations[i];
        if (animation) {
            animation.process();
        }
    }
}

ConvertAnimation.prototype = {
// (public) use this to start element moving
// you must dispose the animation when it doesn't make a sense any more
    start: function() {
        var index = 0;
        var animations = ConvertAnimation.instances;
        for (var size = animations.length; index < size; index++) {
            if (!animations[index]) {
                break;
            }
        }
        ConvertAnimation.instances[index] = this;
    },

// (private) arranges target on the screen
    process: function() {
        this.setPosition();
        if (this.xPosition.step == 0 && this.yPosition.step == 0) {
            this.dispose();
        }
        if (this.xPosition.hasBeenTurned || this.yPosition.hasBeenTurned) {
            this.turnCount--;
            if (this.turnCount == 0) {
                this.xPosition.step = 0;
                this.yPosition.step = 0;
            }
        }
    },

// (public) use this if you need to set element position only once
// You haven't to dispose animation after this method usage
    setPosition: function() {
        var target = this.target;
        var x = this.xPosition.next(target);
        var y = this.yPosition.next(target);
        var component = EchoDomPropertyStore.getPropertyValue(target, "component");
        if (component) {
            component.setPosition(x + this.xPosition.scroll.scrollLeft, y + this.yPosition.scroll.scrollTop);
            // component.redraw();
        }
        target.style.left = x + "px";
        target.style.top = y + "px";
    },

// (public) use this when animated element removed from screen
    dispose: function() {
        this.target.animation = null;
        var animations = ConvertAnimation.instances;
        for (var i = 0, size = animations.length; i < size; i++) {
            if (animations[i] == this) {
                animations[i] = null;
            }
        }
    }
}

// Class that controls target position along axis
ConvertAnimation.Position = function(strict, offset, sizeProperty, scrollProperty) {
    this.step = 0;
    this.position = 0;
    this.strict = strict;
    this.offset = ConvertAnimation.abs(offset);
    this.sizeProperty = sizeProperty;
    this.scrollProperty = scrollProperty;
    this.size = {scrollWidth : 0, scrollHeight : 0};
    this.scroll = {scrollLeft : 0, scrollTop : 0};
}

// Calculates new target position, sets hasBeenTurned flag
ConvertAnimation.Position.prototype.next = function(target) {
    this.hasBeenTurned = false;
    this.calculateViewportSize();
    this.calculateScrollingOffset();
    var viewPortSize = this.size[this.sizeProperty];
    var viewPortOffset = this.scroll[this.scrollProperty];
    var targetSize = target[this.sizeProperty];
    var maxPosition = viewPortSize - targetSize - (2 * this.offset);
    var newPosition = 0;
    if (maxPosition > this.step) {
        newPosition = this.position + this.step;
        if (newPosition > maxPosition) {
            newPosition = 0;
            this.hasBeenTurned = true;
            this.strict = !this.strict;
        }
    }
    this.position = newPosition;
    if (this.strict) {
        return viewPortOffset + this.offset + newPosition;
    } else {
        return viewPortSize + viewPortOffset - this.offset - targetSize - newPosition;
    }
}

//Calculate current viewport size
ConvertAnimation.Position.prototype.calculateViewportSize = function() {
    if (typeof window.innerWidth != 'undefined') {
        this.size.scrollWidth = window.innerWidth;
        this.size.scrollHeight = window.innerHeight;
    } else if (typeof document.documentElement != 'undefined'
            && typeof document.documentElement.clientWidth !=
               'undefined' && document.documentElement.clientWidth != 0) {
        this.size.scrollWidth = document.documentElement.clientWidth;
        this.size.scrollHeight = document.documentElement.clientHeight;
    } else {
        this.size.scrollWidth = document.getElementsByTagName('body')[0].clientWidth;
        this.size.scrollHeight = document.getElementsByTagName('body')[0].clientHeight;
    }
}
//Calculate current viewport scrolling offset
ConvertAnimation.Position.prototype.calculateScrollingOffset = function () {
    if (self.pageYOffset) {
        // all except Explorer
        this.scroll.scrollLeft = self.pageXOffset;
        this.scroll.scrollTop = self.pageYOffset;
    } else if (document.documentElement && document.documentElement.scrollTop) {
        // Explorer 6 Strict
        this.scroll.scrollLeft = document.documentElement.scrollLeft;
        this.scroll.scrollTop = document.documentElement.scrollTop;
    }
    else if (document.body) {
        // all other Explorers
        this.scroll.scrollLeft = document.body.scrollLeft;
        this.scroll.scrollTop = document.body.scrollTop;
    }
}


///////////////////////////////////////////////////////////////////////////
// Logger
// /////////////////////////////////////////////////////////////////////////

// Logging subsystem
var ConvertLogger = {

// keyphrase to type to open log pane: "convertdebug"
    key: [67, 79, 78, 86, 69, 82, 84, 68, 69, 66, 85, 71],
    typePosition: 0,

    messages: new Array(),
    logElement: null,

// (public) Use this method to append log messages to display
    log: function(message) {
        if (ConvertLogger.logElement) {
            ConvertLogger.append(message);
        } else {
            ConvertLogger.messages[ConvertLogger.messages.length] = message;
        }
    },

// (private) listens visitor key typing to open debug pane on keyphrase
    onKeyDown: function(evt) {
        var event = window.event || evt;
        if (event.keyCode == ConvertLogger.key[ConvertLogger.typePosition++]) {
            if (ConvertLogger.typePosition == ConvertLogger.key.length) {
                ConvertLogger.flush();
            }
        } else {
            ConvertLogger.typePosition = 0;
        }
    },

// (private) prepares logger layout, flushes collected messages
    flush: function() {
        var logElement = document.createElement("div");
        ConvertLogger.logElement = logElement;
        logElement.style.overflow = "scroll";
        logElement.style.border = "1px solid Black";
        logElement.style.backgroundColor = "#ccffcc";
        logElement.style.position = "absolute"
        logElement.style.right = "50px";
        logElement.style.bottom = "50px";
        logElement.style.width = "300px";
        logElement.style.height = "450px";
        document.body.appendChild(logElement);

        var messages = ConvertLogger.messages;
        ConvertLogger.messages = undefined;
        for (var i = 0; i < messages.length; i++) {
            ConvertLogger.append(messages[i], logElement);
        }
    },

// (private) appends message to the logElement, scrolls logElement to the bottom
    append: function(message) {
        var now = new Date();
        var time = now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds()
        var messageDiv = document.createElement("div");
        messageDiv.innerHTML = "<b>" + time + "</b>&nbsp;" + message;
        ConvertLogger.logElement.appendChild(messageDiv);
        ConvertLogger.logElement.scrollTop = 1000000;
    },

    init: function() {
        ConvertEngine.addEventListener("keydown", document, ConvertLogger.onKeyDown);
    }
}

///////////////////////////////////////////////////////////////////////////
// Popup engagement container
// /////////////////////////////////////////////////////////////////////////

// todo take implementation based on Echo2 WindowPane

// /////////////////////////////////////////////////////////////////////////
// Inline engagement container
// /////////////////////////////////////////////////////////////////////////
/**
 * Inline engagements wrapper class.
 */
function ConvertEngagementWrapper(elementId, engagementId, noResponseTimeout, keepContentAfterResponse, isManual) {
    ConvertLogger.log("New engagement wrapper creation. Element id: " + elementId);
    // bad realisation. this is done to be consistent with applet.
    var element = document.getElementById(elementId);
    if (!element) {
        ConvertLogger.log("Container not found for engagement, sing dummy container");
        new ConvertEngagementResult(engagementId, ConvertEngagementResult.noResponse).send();
        element = ConvertEngagementWrapper.dummyContainer;
        noResponseTimeout = 0;
    }
    // if element contains engagement, leave the old one
    this.containedEngagement = false;
    if (element.engagement != undefined) {
        ConvertLogger.log("Element already contains engagement");
        this.containedEngagement = true;
        return;
    }
    // references
    this.element = element;
    this.isManual = isManual;
    element.engagement = this;
    element.engagementId = engagementId;
    // protecting original content
    element.originalDisplay = element.style.display;
    element.originalContent = element.innerHTML;
    // no reponse timeout
    if (noResponseTimeout) {
        ConvertLogger.log("'No response' timer enabled, timeout: " + noResponseTimeout);
        element.noResponseTimerId = window.setTimeout(
                "ConvertEngagementWrapper.processNoResponse('" + elementId + "')",
                noResponseTimeout * 1000);
    }
    this.keepContentAfterResponse = keepContentAfterResponse;
}

ConvertEngagementWrapper.dummyContainer = document.createElement("div");

// Finds element by id and disposes attached engagement.
// Sends 'No response' engagement result to the server.
ConvertEngagementWrapper.processNoResponse = function(elementId) {
    var element = document.getElementById(elementId);
    new ConvertEngagementResult(element.engagementId, ConvertEngagementResult.noResponse).send();
    element.engagement.dispose();
}

ConvertEngagementWrapper.prototype = {
// Sets engagement content.
    setHtmlContent: function(content) {
        this.element.innerHTML = (content ? content : "");
    },

// Replaces current element content by given dom elements.
    setDomContent: function(elements) {
        ConvertLogger.log("Setting DOM content for eng. wrapper: " + elements.length + " fetched");
        var baseElement = this.element;
        while (baseElement.hasChildNodes()) {
            baseElement.removeChild(baseElement.firstChild);
        }
        for (var i = 0; i < elements.length; i++) {
            baseElement.appendChild(elements[i]);
        }
    },

// Replaces element engagement content with original one.
    dispose: function() {
        var element = this.element;
        if (element && element.engagementId != undefined) {
            ConvertLogger.log("Disposing engagement " + element.engagementId);
            window.clearTimeout(element.noResponseTimerId);

            // remove reference to engagement
            element.engagementId = undefined;
            element.engagement = undefined;

            // restore element content/visibility
            if (!this.keepContentAfterResponse) {
                element.style.display = element.originalDisplay;
                element.innerHTML = element.originalContent;
            }

            // unregister old listeners
            var allEventListeners = this.eventListeners;
            if (allEventListeners) {
                for (var type in allEventListeners) {
                    var eventListeners = allEventListeners[type];
                    for (var i = 0; i < eventListeners.length; i++) {
                        ConvertEngine.removeEventListener(type, element, eventListeners[i]);
                    }
                }
            }
        }
        this.element = undefined;
    },

// Adds event listener to representative component.
// Listener will be removed by dispose() method.
    addEventListener: function(type, listener) {
        ConvertLogger.log("Adding " + type + " events listener to engagement " + this.engagementId + " wrapper");
        var allEventListeners = this.eventListeners;
        if (allEventListeners == undefined) {
            this.eventListeners = allEventListeners = new Array();
        }
        var eventListeners = allEventListeners[type];
        if (eventListeners == undefined) {
            allEventListeners[type] = eventListeners = new Array();
        }
        ConvertEngine.addEventListener(type, this.element, listener);
        eventListeners[eventListeners.length] = listener;
    }
}

///////////////////////////////////////////////////////////////////////////
// Marketing offer handler functions.
// /////////////////////////////////////////////////////////////////////////

/**
 * Marketing offer engagement.
 */
var ConvertMO = {

    popupContainer: null,
    popupContainerId: "marketing_offer_popup",

    init: function() {
        ConvertLogger.log("Marketing offer engine initialization");
        var div = document.createElement("div");
        ConvertMO.popupContainer = div;
        div.id = ConvertMO.popupContainerId;
        div.style.position = "absolute";
        div.style.display = "none";
        document.body.appendChild(div);
    },

// (public) Embeds inline MO
    showInlineMO: function(elementId, engagementId, imageSrc, targetUrl, timeout) {

        ConvertLogger.log("Inline marketing offer fetched for element " + elementId);

        var engagement = new ConvertEngagementWrapper(elementId, engagementId, timeout, true);
        if (!engagement.containedEngagement) {
            engagement.setDomContent([ConvertMO.createMOImage(convertSingleUrl(imageSrc), convertSingleUrl(targetUrl))]);
        } else {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Div for inline is not empty").send();
            return;
        }
        ConvertEngagementResult.registerStartTime(engagementId);
    },

// (public) Opens popup MO
    showPopupMO: function(engagementId, imageSrc, targetUrl, timeout, x, y, corner, movement, speed, tripCount) {

        ConvertLogger.log("Popup marketing offer fetched for target url: " + targetUrl);

        var engagement = new ConvertEngagementWrapper(ConvertMO.popupContainerId, engagementId, timeout);
        var image = ConvertMO.createMOImage(convertSingleUrl(imageSrc), convertSingleUrl(targetUrl));
        engagement.setDomContent([image]);

        new ConvertAnimation(ConvertMO.popupContainer, corner, x, y, movement, speed, tripCount).start();
        ConvertMO.popupContainer.style.display = "inline";
        ConvertEngagementResult.registerStartTime(engagementId);
    },

// Creates marketing offer element.
// Result is hyperlink element or just image.
    createMOImage: function(imageSrc, targetUrl) {
        ConvertLogger.log("Marketing offer image creation, image URL: " + imageSrc);
        var image = document.createElement("img");
        image.onclick = ConvertMO.acceptMO;
        image.src = imageSrc;
        image.targetUrl = targetUrl;
        image.style.cursor = "pointer";
        image.style.border = "0px none Black";
        return image;
    },

// Sends 'accept' engagement result. Disposes engagement.
    acceptMO: function() {
        ConvertLogger.log("Marketing offer accepted");
        var element = this;
        while (element && !element.engagement) {
            element = element.parentNode;
        }
        if (element) {
            if (this.targetUrl) {
                new ConvertEngagementResult(element.engagementId, ConvertEngagementResult.accept, this.targetUrl).send();
            } else {
                new ConvertEngagementResult(element.engagementId, ConvertEngagementResult.accept).send();
            }
            element.engagement.dispose();
            if (element.animation) {
                element.animation.dispose();
            }
        }
        if (this.targetUrl) {
            ConvertEngine.openWindow(this.targetUrl);
        }
    }
}

///////////////////////////////////////////////////////////////////////////
// Html Creative handler functions.
// /////////////////////////////////////////////////////////////////////////

/**
 * Html Creative engagement.
 */
var ConvertHC = {

    popupContainer: null,
    popupContainerId: "html_creative_popup",

    init: function() {
        ConvertLogger.log("HTML Creative engine initialization");
        var div = document.createElement("div");
        ConvertHC.popupContainer = div;
        div.id = ConvertHC.popupContainerId;
        div.style.position = "absolute";
        div.style.display = "none";
        div.style.overflow = "auto";
        div.style.backgroundColor = "#FFF";
        document.body.appendChild(div);
    },

// (public) Embeds inline HTML Creative
    showInlineHC: function(divId, engagementId, htmlCode, url, timeout) {
        ConvertLogger.log("Inline HTML Creative fetched for element " + divId);

        var element = document.getElementById(divId);
        if (htmlCode == null) 
	{
            //if htmlCode is empty - code to inline is specified using url
	    this.getHtmlFromURL(divId, engagementId, url, timeout, inlineHcClosure);
	    return;
        }
        if (htmlCode == null) {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "HTML content for inline is empty").send();
            return;
        }

        var htmlCodeWithHandler = this.addUrlRedirect(htmlCode, engagementId);
        if (element) {
            var engagement = new ConvertEngagementWrapper(divId, engagementId, timeout);
            if (!engagement.containedEngagement && !backupDelivered) {
                element.innerHTML = htmlCodeWithHandler;
            } else {
                new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Div for inline is not empty").send();
            }
        } else {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "No div for inline").send();
        }
        ConvertEngagementResult.registerStartTime(engagementId);
    },

// (public) Opens popup HTML Creative
    showPopupHC: function(engagementId, htmlCode, url, timeout, x, y, corner, movement, speed, tripCount, width, height) {

        ConvertLogger.log("Popup HTML Creative fetched for target url: " + url);

        var element = document.getElementById(ConvertHC.popupContainerId);
        if (htmlCode == null) {
            //if htmlCode is empty - code to inline is specified using 
	    this.getHtmlFromURL(divId, engagementId, url, timeout, popupHcClosure);
	    return
        }
        if (htmlCode == null) {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "HTML content for inline is empty").send();
            return;
        }
        var htmlCodeWithHandler = this.addUrlRedirect(htmlCode, engagementId);

        ConvertHC.popupContainer.style.maxWidth = width + "px";
        ConvertHC.popupContainer.style.maxHeight = height + "px";
        var engagement = new ConvertEngagementWrapper(ConvertHC.popupContainerId, engagementId, timeout);
        if (!engagement.containedEngagement) {
            element.innerHTML = htmlCodeWithHandler;
        } else {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Div for inline is not empty").send();
        }

        new ConvertAnimation(ConvertHC.popupContainer, corner, x, y, movement, speed, tripCount).start();
        ConvertHC.popupContainer.style.display = "inline";
        var hcLinks = document.getElementById(ConvertHC.popupContainerId).getElementsByTagName('a');
        for (var i = 0; i < hcLinks.length; i++) {
            ConvertEngine.addEventListener('click', hcLinks[i], this.acceptHC);
        }
        ConvertEngagementResult.registerStartTime(engagementId);
    },

    /**
     * Adds redirect to each "<a href" element in html code
     * or, if there is javascript in href, rewrites url in it too
     */
    addUrlRedirect: function(htmlCode, engagementId) {
        var servletParams = "visitorId=" + ConvertEngine.getVisitorId() + "&engagementId=" + engagementId + "&pageId="
                + ConvertPage.pageId;
        var hrefRegExp = /href=".+?"/gi;
        var m = htmlCode.match(hrefRegExp);
        if (m) {
            for (var i = 0; i < m.length; i++) {
                //extract original URL from "href"
                var redirURL = m[i].substring(6, m[i].length - 1);
                var jsHrefRegExp = /\('.+?'/gi;
                var m2 = redirURL.match(jsHrefRegExp);
                if (m2) {
                    for (var j = 0; j < m2.length; j++) {
                        redirURL = m2[j].substring(2, m2[j].length - 1);
                        // do double encode because of window.open()
                        var params = encodeURIComponent(servletParams.concat("&redirectURL="
                                + encodeURIComponent(redirURL)));
                        htmlCode =
                        htmlCode.replace(m2[j], "('" + ConvertPage.inlineRedirectServletPath + "?" + params + "'");
                    }
                } else {
                    //add redirectURL param
                    var params = servletParams.concat("&redirectURL=" + encodeURIComponent(redirURL));
                    htmlCode =
                    htmlCode.replace(m[i], "href=\"" + ConvertPage.inlineRedirectServletPath + "?" + params + "\"");
                }
            }
        }
        return htmlCode;
    },

    //replaces host in arbitrary URL with
    // host of current AJAX server (always without proxy)
    replaceHostWithCurrentHost: function (url) {
        var prefix = url.substring(4, 0);
        if (prefix != "http") {
            url = "http://" + url;
        }
        var domainRegExp = new RegExp("https?:\\/\\/.*?\\/");
        return url.replace(domainRegExp, ConvertPage.server);
    },
    getHtmlFromURL: function(divId, engagementId, url, timeout, closure) {
        //substitute host from full url by ConvertPage.server
        url = this.replaceHostWithCurrentHost(url);
        var request = new ConvertRequest("").createRequest();
        try {	    
	    request.onreadystatechange = inlineHcClosure(divId, engagementId, url, timeout, request);
            request.open("POST", ajastServer + Math.random(), false);
            request.setRequestHeader("Content-Type", "text/html");
            request.setRequestHeader("Cache-Control", "no-cache");
	    var headers = {Content_Type: "text/html",
			   Cache_Control: "no-cache"};
            request.send(new PostObject(url, headers, null, getConvertCookie(), null));
        } catch (err) {
	    alert(err);
            // failed to send AJAX request - return error to server
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Failed to load HTML content; error="
                    + err).send();
            return null;
        }
        if (!request.status || request.status == 200) {
            ConvertLogger.log("Request for HTML Creative has succeeded");
            return request.responseText;
        } else {
            return null;
        }
    },

    // Sends 'accept' engagement result. Disposes engagement.
    acceptHC: function() {
        ConvertLogger.log("HTML Creative accepted");
        var element = this;
        while (element && !element.engagement) {
            element = element.parentNode;
        }
        if (element) {
//      do not send accept as Html Creative is accepted via servlet
// if (this.targetUrl) {
// new ConvertEngagementResult(element.engagementId,
// ConvertEngagementResult.accept, this.targetUrl).send();
// } else {
// new ConvertEngagementResult(element.engagementId,
// ConvertEngagementResult.accept).send();
// }
            setTimeout(function() {
                element.engagement.dispose();
                if (element.animation) {
                    element.animation.dispose();
                }
            }, 500);
        }
        if (this.targetUrl) {
            ConvertEngine.openWindow(this.targetUrl);
        }
        return true;
    }
}

///////////////////////////////////////////////////////////////////////////
// Chat button handler functions.
// /////////////////////////////////////////////////////////////////////////

/**
 * Convert chat invitation engagement.
 */
var ConvertChatInvitation = {

// handler names
    inlineHandlerName: "inlineChatInvitation",
    dhtmlHandlerName: "dhtmlChatInvitation",
    mailHandlerName: "mailChatInvitation",

// container for dhtml engagement
    dhtmlContainer: null,
// dhtml engagement container Id
    dhtmlContainerId: "dhtml_invite",

// possible operator states
    awayOpState: "awayOpState",
    availOpState: "availOpState",
    pagedOpState: "pagedOpState",
// current operator state
    currentOperatorState: "awayOpState",
// flag indicating visitor can make chat request
    engagementsAreActive: false,
// flag indicating that chat is opened
    isChatOpened: false,

    init: function() {
        ConvertLogger.log("Chat invitation engine initialization");
        // create dhtml chat invitation container
        var div = document.createElement("div");
        ConvertChatInvitation.dhtmlContainer = div;
        div.id = ConvertChatInvitation.dhtmlContainerId;
        div.style.position = "absolute";
        div.style.display = "none";
        document.body.appendChild(div);
        this.operatorStateImageUrls = new Array();
        this.chatInvitationImages = new Array();
        this.engagementIds = new Array();
    },

// Updates images (inline chat invitation buttons) to reflect operator state.
    setOperatorState : function(operatorState) {
        ConvertChatInvitation.currentOperatorState = operatorState;
        ConvertChatInvitation.engagementsAreActive = (operatorState == ConvertChatInvitation.availOpState);
        var cursor = (ConvertChatInvitation.engagementsAreActive || ConvertPage.sendMailAlloved) ? "pointer" : "default";
        for (var i = 0, length = this.engagementIds.length; i < length; i++) {
        	var engId = this.engagementIds[i];
            var image = this.chatInvitationImages[engId];
	        var src = this.operatorStateImageUrls[engId][operatorState];
	        if (src) {
        	    image.style.display = "inline";
    	        image.style.cursor = cursor;
	            image.src = src;
	        } else {
        	    image.style.display = "none";
	        }
        }
        if (!ConvertChatInvitation.engagementsAreActive) {
            var dhtmlEngagement = ConvertChatInvitation.dhtmlContainer;
            if (dhtmlEngagement.engagement && !dhtmlEngagement.engagement.isManual) {
                dhtmlEngagement.engagement.dispose();
            }
        }
    },

    /**
     * Updates state of chat invite engagements buttons
     */
    updateState : function () {
        this.setOperatorState(ConvertChatInvitation.currentOperatorState);
    },

// Invoked when user clicks on chat invitation image.
// Method ensures that chat isn't opened and operator is available,
// then sends chat request to the server and set operator status to 'paged'
// Method parameters are optional.
    sendChatRequest: function(engagementId, handlerName, isManual) {
        if (!ConvertChatInvitation.isChatOpened && (ConvertChatInvitation.engagementsAreActive || isManual)) {
            if (engagementId) {
                new ConvertEngagementResult(engagementId, ConvertEngagementResult.accept).send();
            }
            if (handlerName != ConvertChatInvitation.dhtmlHandlerName) {
                handlerName = ConvertChatInvitation.inlineHandlerName;
            }
            var params = new Object();
            params.engagementId = engagementId;
            new ConvertClientMessage(handlerName, params).send();
            ConvertChatInvitation.setOperatorState(ConvertChatInvitation.pagedOpState);
        } else {
            ConvertLogger.log("Operator is paged, unavailable or already talking to you");
        }
    },

// Sets flag, that indicates if chat is opened
    setChatOpened: function(opened) {
        ConvertChatInvitation.isChatOpened = opened;
    },

//////// Inline invitations

// image name for inline invitation images
    operatorImageName: "convertChatInvitationImage",
// operator state image urls
    operatorStateImageUrls: null,
    chatInvitationImages: null,
    engagementIds:null,

// Inserts chat invitation button into specified element.
// If operator state image url is known, uses this url.
// If operator image url isn't known (default chat invitation
// for instance) image 'display' style property will be set to 'none'.
    addInlineChatInvitation: function(elementId, engagementId, timeout, urls) {
        ConvertLogger.log("Adding inline chat invitation, engagement Id:" + engagementId);
        // create image
        var proxyUrls = convertUrlsToProxy(urls);
        this.operatorStateImageUrls[engagementId] = proxyUrls;
        this.engagementIds[this.engagementIds.length] = engagementId;
        var image = document.createElement("img");
        image.style.border = "0px none White";
        image.name = ConvertChatInvitation.operatorImageName;
        if (proxyUrls[ConvertChatInvitation.currentOperatorState]) {
            image.src = proxyUrls[ConvertChatInvitation.currentOperatorState];
            if (ConvertChatInvitation.engagementsAreActive || ConvertPage.sendMailAlloved) {
                image.style.cursor = "pointer";
            }
        } else {
            image.style.display = "none";
        }
        this.chatInvitationImages[engagementId] = image;
        // show engagement
        var engagement = new ConvertEngagementWrapper(elementId, engagementId);
        if (!engagement.containedEngagement) {
            engagement.setDomContent([image]);
            engagement.addEventListener("click", ConvertChatInvitation.onClickInline);
        } else {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Div for inline is not empty").send();
            return;
        }
        ConvertEngagementResult.registerStartTime(engagementId);
    },

// Mouse clicks listener. Fires 'invitation accepted' event to server.
    onClickInline: function(event) {

        //Operator mailing form processing
        if(!ConvertChatInvitation.engagementsAreActive && !ConvertChatInvitation.isChatOpened) {
            //There are no engagements active -> visitor can try send offline email to operator
                var params = new Object();
                new ConvertClientMessage(ConvertChatInvitation.mailHandlerName, params).send();
	        return;
        }

        ConvertLogger.log("Inline chat invitation clicked");
        var element = window.event ? window.event.srcElement : event.target;
        while (element && !element.engagement) {
            element = element.parentNode;
        }
        if (element) {
            ConvertChatInvitation.sendChatRequest(element.engagementId, ConvertChatInvitation.inlineHandlerName);
        }

        if (ConvertPage.reactiveChatType == "detach") {
            ConvertEngine.openDetachedChat();
        } else {
            window.focus();  // make sure browser window stays focused for
								// polling
        }
    },

//////// DHTML invitations

// Opens dhtml chat invitation
    showDhtmlChatInvitation: function(
            engagementId, imageUrl, timeout,
            closeX, closeY, closeWidth, closeHeight,
            x, y, corner, movement, speed, tripCount, isManual)
    {
        ConvertLogger.log("DHTML engagement fetched");
        var engagement = new ConvertEngagementWrapper(ConvertChatInvitation.dhtmlContainerId, engagementId, timeout, null, isManual);
        if (engagement.containedEngagement) {
            new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Div for inline is not empty").send();
            return;
        }
        var element = engagement.element;
        element.engagementId = engagementId;

        new ConvertAnimation(ConvertChatInvitation.dhtmlContainer, corner, x, y, movement, speed, tripCount);

        var imageMapDiv = document.createElement("div");

        var mapName = ConvertEngine.createElementId();
        var imageMapCode = '<map name="' + mapName + '"><area shape="rect" coords="' +
                           closeX + "," + closeY + "," + (closeX + closeWidth) + "," + (closeY + closeHeight) +
                           '" href="javascript:ConvertChatInvitation.killDhtmlInvitation()">'
        imageMapCode += '<area shape="rect" coords="0,0,10000,10000' +
                        '" href="javascript:ConvertChatInvitation.acceptDhtmlInvitation()">';
        imageMapDiv.innerHTML = imageMapCode;

        var image = document.createElement("img");
        engagement.setDomContent([imageMapDiv, image]);

        image.style.border = "0px none White";
        image.useMap = "#" + mapName;
        image.onload = ConvertChatInvitation.onImageLoad;
        image.src = convertSingleUrl(imageUrl);

        element.style.display = "inline";
        ConvertEngagementResult.registerStartTime(engagementId);
    },

    onImageLoad: function() {
        this.onload = null;
        ConvertLogger.log("DHTML engagement image loaded");
        ConvertChatInvitation.dhtmlContainer.animation.start();
    },

// Sends 'accept' result for dhtml invitation.
// Removes invitation element from screen.
    acceptDhtmlInvitation: function() {
        ConvertLogger.log("DHTML engagement accepted");
        var element = ConvertChatInvitation.dhtmlContainer;
        var engagementId = element.engagementId;
        if (engagementId) {
            ConvertChatInvitation.sendChatRequest(engagementId, ConvertChatInvitation.dhtmlHandlerName, element.engagement.isManual);
            if (element.engagement) {
                element.engagement.dispose();
            }
            element.animation.dispose();
        }
    },

// Sends 'kill' result for dhtml invitation.
// Removes invitation element from screen.
    killDhtmlInvitation: function() {
        ConvertLogger.log("DHTML engagement killed");
        var element = ConvertChatInvitation.dhtmlContainer;
        if (element.engagement) {
            new ConvertEngagementResult(element.engagementId, ConvertEngagementResult.kill).send();
            element.engagement.dispose();
            element.animation.dispose();
        }
    }
}

///////////////////////////////////////////////////////////////////////////
// Convert form/survey engine.
// /////////////////////////////////////////////////////////////////////////

var ConvertSurvey = {

    popupContainerId: "survey_popup",

// hidden input element name, that value is real form target
    realFormTarget: "convertFormTaget",

// form validator question element attribute names
    questionCaption: "convertQuestionCaption",
    textContentType: "contentType",
    requiredQuestion: "convertRequiredQuestion",

// Creates div for popup survey
    init: function() {
        ConvertLogger.log("Survey engine initialization");
        var div = document.createElement("div");
        div.id = ConvertSurvey.popupContainerId;
        div.style.border = "1px solid Black";
        div.style.zIndex = "10";
        div.style.background = "White";
        div.style.padding = "3px";
        div.style.position = "absolute";
        div.style.display = "none";
        // div.style.maxWidth = "300px";
        // div.style.maxHeight = "500px";
        div.style.display = "none";
        document.body.appendChild(div);
    },

// Places survey form into the document.
    showSurvey: function(engagementId, message, timeout, location, xOffset, yOffset) {
        try {
            var isPopup = (xOffset != undefined);
            ConvertLogger.log("Opening Form engagement" +
                              "; engagement Id: " + engagementId +
                              "; form type: " + (isPopup ? "popup" : "inline") +
                              "; target location: " + location +
                              (isPopup ? " (" + xOffset + "," + yOffset + ")" : ""));
            var elementId = isPopup ? ConvertSurvey.popupContainerId : location;
            var engagement = new ConvertEngagementWrapper(elementId, engagementId, 0);
            if (engagement.containedEngagement) {
                new ConvertEngagementResult(engagementId, ConvertEngagementResult.error, "Div for inline is not empty").send();
                return;
            }
            var element = engagement.element;
            var text = "";
            for (i = 0; i < message.childNodes.length; i++) {
     			text += message.childNodes[i].nodeValue;
   			}
            engagement.setHtmlContent(text);
            engagement.isInlineForm = !isPopup;

            if (isPopup) {
                element.style.display = "inline";
                new ConvertAnimation(element, location, xOffset, yOffset).setPosition();
            }

            // find form element
            var form;
            ConvertEngine.processRecursive(element, function(target) {
                if (target.nodeType == 1 && target.tagName.toLowerCase() == "form") {
                    form = target;
                }
            })
            if (!form) {
                ConvertLogger.log("No Form tag found")
                return;
            }

            // no response
            if (timeout > 0) {
                engagement.noResponseTimerId = setTimeout(
                        "ConvertSurvey.noResponse('" + form.name + "')", 1000 * timeout);
                engagement.addEventListener("click", ConvertSurvey.onClick);
            }

            // form target setup
            var realFormTargetElement = form.elements[ConvertSurvey.realFormTarget];
            if (realFormTargetElement && realFormTargetElement.value) {
                ConvertLogger.log("Form target is " + realFormTargetElement.value);
                form.target = realFormTargetElement.value;
                form.removeChild(realFormTargetElement);
            } else {
                ConvertLogger.log("Form target element not found");
            }

            ConvertEngagementResult.registerStartTime(engagementId);
        } catch (error) {
            ConvertLogger.log("Error occurred: " + error);
        }
        if (window.DateChooser) {
            window.DateChooser.init();
        }
        if (window.CheckBoxChooser) {
            window.CheckBoxChooser.init();
        }
    },

// Disables no-response timer
    onClick: function(evt) {
        var element = window.event ? window.event.srcElement : evt.target;
        while (element) {
            if (element.engagement && element.engagement.noResponseTimerId) {
                ConvertLogger.log("Disabling no-response timer");
                clearTimeout(element.engagement.noResponseTimerId);
                element.engagement.noResponseTimerId = null;
                return;
            }
            element = element.parentNode;
        }
    },

// No-response handler
    noResponse: function(formName) {
        ConvertSurvey.dispose(formName, ConvertEngagementResult.noResponse);
    },

// Removes form from window.
// Fires 'kill' visitor response.
// Since form may be placed into a page directly
    kill: function(formName, engagementId) {
        ConvertLogger.log("Form was closed");
        ConvertSurvey.dispose(formName, ConvertEngagementResult.kill, engagementId);
    },

// Submits form if it has valid answers.
// NOTE! isValid method also sets timer to remove
// form from the document if answers are valid.
    submit: function(formName) {
        ConvertLogger.log("Form attempted to submit");
        if (ConvertSurvey.isValid(formName)) {
            document.forms[formName].submit();
        }
    },

// Disposes form.
    dispose: function(formName, response, engagementId) {
        ConvertLogger.log("Disposing form");
        var form = document.forms[formName];
        if (form) {
            var element = form;
            while (element && !element.engagement) {
                element = element.parentNode;
            }
            if (element) {
                if (element.engagement.isInlineForm) {
                    if (ConvertEngagementResult.kill == response) {
                        form.reset();
                    } else if (response == undefined && (form.target != "_top" || form.target != "_self")) {
                        element.engagement.dispose();
                    }
                } else {
                    if (response) {
                        new ConvertEngagementResult(element.engagementId, response).send();
                    }
                    element.engagement.dispose();
                }
            } else {
                if( response && engagementId ) {
                    new ConvertEngagementResult(engagementId, response).send();
                }
                form.parentNode.removeChild(form);
            }
        }
    },

// Check answers. If all required answers are given and
// e-mail fields are properly filled, returns true.
// Otherwise shows information alert about required
// or invalid data and returns false.
// If data is valid, sets timer to dispose form after some time.
// WARNING! isValid method also sets timer to remove
// form from the document if answers are valid.
    isValid: function(formName) {
        var form = document.forms[formName];
        var formElements = form.elements;
        if (ConvertSurvey.testRequiredFields(formElements) && ConvertSurvey.testTextFields(formElements)) {
            var visitorId = formElements["visitorId"];
            if (!visitorId) {
                visitorId = document.createElement("input");
                visitorId.type = "hidden";
                visitorId.name = "visitorId";
                form.appendChild(visitorId);
            }
            visitorId.value = ConvertEngine.getVisitorId();
            ConvertLogger.log("Form is valid; Setting timer to dispose form");
            
            // post form via ajax
            processForm(form,form.action,formName+"container");
        }
        return false;
    },

// Method checks if user gave some answer to question represented by questionElement.
// Returns true if can't recognize question element type.
    isAnswered: function(questionElement) {
        switch (questionElement.tagName.toLowerCase()) {
            case "select":
                return questionElement.selectedIndex > 0;
            case "textarea":
                return questionElement.value.match(/\S+/) != null;
            case "input":
                switch (questionElement.type.toLowerCase()) {
                    case "password":
                    case "text":
                        return questionElement.value.match(/\S+/) != null;
                    case "radio":
                        var radioButtons = questionElement.form.elements[questionElement.name];
                        for (var i = 0; i < radioButtons.length; i++) {
                            if (radioButtons[i].checked) {
                                return true;
                            }
                        }
                        return false;
                }
        }
        return true;
    },

// Method checks if all mandatory (required) questions answered.
    testRequiredFields: function(questionElements) {
        var invalid = false;
        var errorMessage = "Fill required fields, please:\n";
        var unanswered = new Array();
        for (var i = 0; i < questionElements.length; i++) {
            var questionElement = questionElements[i];
            if (questionElement.getAttribute(ConvertSurvey.requiredQuestion)) {
                var caption = questionElement.getAttribute(ConvertSurvey.questionCaption);
                if (!(unanswered[caption] || ConvertSurvey.isAnswered(questionElement))) {
                    unanswered[caption] = invalid = true;
                    errorMessage += "\n - " + caption;
                }
            }
        }
        if (invalid) {
            alert(errorMessage);
        }
        return !invalid;
    },

// Check if all text fields are filled properly (or empty).
// If founds invalid string in input component,
// alerts error and returns false. Return true otherwise.
    testTextFields: function(questionElements) {
        for (var i = 0; i < questionElements.length; i++) {
            var element = questionElements[i];
            if (element.tagName.toLowerCase() == "input"
                    && element.type.toLowerCase() == "text") {
                var contentType = element.getAttribute(ConvertSurvey.textContentType);
                if (!contentType)
                    continue;
                var checkPattern;
                switch (contentType) {
                    case "email":
                        checkPattern = /^\S+@\S+$/;
                        break;
                    case "number":
                        checkPattern = /^\s*(\+|-)?\d+\s*$/;
                        break;
                    case "decimal":
                    case "currency":
                        checkPattern = /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/;
                        break;
                }
                if (checkPattern && element.value && element.value.match(checkPattern) == null) {
                }
                if (contentType == "email" && element.value) {
                    var isBulkRestricted = element.getAttribute(ConvertSurvey.bulkEmailRestricted);
                    if (isBulkRestricted && !ConvertSurvey.isBulkValidEmail(element.value)) {
                        alert(element.getAttribute(ConvertSurvey.questionCaption) +
                              " is invalid. Please supply a valid business email address (No bulk providers permitted).");
                        return false;
                    }
                }
            }
        }
        return true;
    },

    // Check if email is not in bulk email providers list
    isBulkValidEmail: function(mail) {
        var params = {
            email: mail
        };
        ConvertLogger.log("Checking for bulk email. Email is " + params.email);

        var request = new ConvertRequest("").createRequest();
        try {
            request.open("GET", ConvertPage.bulkEmailServletPath + "?"
                    + new ConvertRequest("").createQueryString(params), false);
            request.send(null);
        } catch (err) {
            return true;
        }
        if (!request.status || request.status == 200) {
            var response = request.responseText;
            ConvertLogger.log("Bulk email response is " + response);
            return response == "ok";
        } else {
            return true;
        }
    }
}

///////////////////////////////////////////////////////////////////////////
// Client message class
// /////////////////////////////////////////////////////////////////////////

/**
 * This is client message class. handlerName is handler name as defined in
 * client message handler binding parameters is associative array, optional
 * parameter. Be careful: constructor appends handlerName property to
 * parameters.
 */
function ConvertClientMessage(handlerName, parameters) {
    if (!parameters) {
        parameters = new Object();
    }
    parameters.handlerName = handlerName;
    this.parameters = parameters;
}

/**
 * Method sends request to the server.
 */
ConvertClientMessage.prototype.send = function(blocking) {
    ConvertLogger.log("Sending action request: " + this.parameters.handlerName);
    new ConvertRequest(ConvertPage.appServletPath).send(this.parameters, blocking);
}

///////////////////////////////////////////////////////////////////////////
// Engagement result class
// /////////////////////////////////////////////////////////////////////////
/**
 * Engagement result handlers.
 */
function ConvertEngagementResult(engagementId, result) {
    this.parameters = {engagementId: engagementId, engagementResult: result};
}

function ConvertEngagementResult(engagementId, result, resultParam) {
    this.parameters = {engagementId: engagementId,
        engagementResult: result,
        resultParam: resultParam
    };
}

// Engagement result constants (responses)
ConvertEngagementResult.accept = "Accept";
ConvertEngagementResult.kill = "Kill";
ConvertEngagementResult.noResponse = "No response";
ConvertEngagementResult.error = "Error";

// core custom method for writing text backwards
function outputbackwards(){
for (i=this.length-1;i>=0;i--)
document.write(this.charAt(i))
}

// Map from engagement id to engagement appearance time
ConvertEngagementResult.engagementStartTimes = new Array();

/**
 * This method registers engagement arrival time.
 */
ConvertEngagementResult.registerStartTime = function(engagementId) {
    ConvertEngagementResult.engagementStartTimes[engagementId] = new Date().getTime();
}

/**
 * Sends engagement result back to server.
 * Response is one of 'Accept', 'Kill' of 'No response' values.
 */
ConvertEngagementResult.prototype.send = function() {
    var engagementId = this.parameters.engagementId;
    var startTime = ConvertEngagementResult.engagementStartTimes[engagementId];
    if (startTime) {
        ConvertEngagementResult.engagementStartTimes[engagementId] = undefined;
        this.parameters.timeToAction = parseInt((new Date().getTime() - startTime) / 1000);
    }
    this.parameters.pageId = ConvertPage.pageId; 
    ConvertLogger.log("Engagement result sending. Engagement: " + engagementId +
                      ", result: " + this.parameters.engagementResult);
    new ConvertRequest(ConvertPage.appServletPath).send(this.parameters, true);
}

///////////////////////////////////////////////////////////////////////////
// Convert sound manager class
// /////////////////////////////////////////////////////////////////////////
/**
 * Sound manager
 */
var ConvertSoundManager = new Object();

ConvertSoundManager.flashAvailable = false;

ConvertSoundManager.isAvailable = function(){
   return ConvertSoundManager.flashAvailable;
}

ConvertSoundManager.setIsAvailable = function(){
   ConvertSoundManager.flashAvailable = true;
}

ConvertSoundManager.playSound = function(soundUrl){
   window.document.convertFlashPlayer.SetVariable('playSound', 'true');
   window.document.convertFlashPlayer.SetVariable('soundUrl', ConvertPage.appContextRoot + soundUrl);
}
/////////////////////////////////////////////////////////

ConvertPage.init();

