/* 

		INFORMATION

		Name:			Stuffed Ajax Request & Response classes
		Version:  1.7
		Author:		Sergey Smirnov / Stuffed Guys
		Site:			www.stuffedguys.com
		Desc:			Provides an abstraction class for Ajax request, response and
							most common DOM manupulation methods, only supports async
							requests (on purpose).
		CVS:			$Id: stuffed.ajax.js,v 1.64.2.5 2007/05/15 11:31:45 dmitry Exp $
		
*/

/*
		========================================================================
		StuffedAjax class (general settings)
		======================================================================== 
*/

var StuffedAjaxRedirectPerformed = false;

function StuffedAjax() {
  this.runningRequests = 0;
}

StuffedAjax.prototype.isAjaxAvailable = function () {
	var xmlhttp = this.newXMLHttp();
	return (xmlhttp ? true : false);
}

StuffedAjax.prototype.newXMLHttp = function () {
  var xmlhttp;
  
  // branch for native XMLHttpRequest object
  if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
  
  // branch for IE/Windows ActiveX version
  } else if (window.ActiveXObject) {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  }	
  
  return xmlhttp;
}

StuffedAjax.prototype.setLoadingImage = function (img_url) {
  if (img_url == null) return false;
    
  // preloading the loading image
  this.loadingImage = document.createElement('img');
  this.loadingImage.src = img_url;    
  
  return this;
}

StuffedAjax.prototype.positionLoading = function () {
	if (!this.loadingImage || !this.loadingDiv) return false;
	if (!this.loadingImage.width) return false;
	
	var imgHalfWidth = this.loadingImage.width / 2;
	var imgHalfHeight = this.loadingImage.height / 2;

	var x,y;
	if (self.pageYOffset) // all except Explorer
	{
		x = self.pageXOffset;
		y = self.pageYOffset;
	}
	else if (document.documentElement && document.documentElement.scrollTop)
		// Explorer 6 Strict
	{
		x = document.documentElement.scrollLeft;
		y = document.documentElement.scrollTop;
	}
	else if (document.body) // all other Explorers
	{
		x = document.body.scrollLeft;
		y = document.body.scrollTop;
	}
	
  if (self.innerWidth) {
		this.loadingDiv.style.left = ((self.innerWidth/2) + x - imgHalfWidth)+'px';
		this.loadingDiv.style.top = ((self.innerHeight/2) + y - imgHalfHeight)+'px';	
  }
  else {
		this.loadingDiv.style.left = ((document.body.clientWidth/2) + x - imgHalfWidth)+'px';
		this.loadingDiv.style.top = ((document.body.clientHeight/2) + y - imgHalfHeight)+'px';	
  }
}

StuffedAjax.prototype.showLoading = function () {
	if (!this.loadingImage) return false;

	// if a "loading" div is not already present on the page we add it
	if (!this.loadingDiv) {
		div = document.createElement('div');
		div.style.display = 'none';
		div.style.position = 'absolute';
		div.style.zIndex = '100';
		
		document.body.appendChild(div);
		div.appendChild(this.loadingImage);
		
		this.loadingDiv = div;
	} 
	
	// position loading should work only once, as soon as the image dimensions
	// become available (in IE they only become available after the div with the
	// image is displayed
	this.positionLoading();
	this.loadingDiv.style.display = '';
	this.positionLoading();
}

StuffedAjax.prototype.hideLoading = function () {
	if (!this.loadingDiv) return false;
	// if some requests are still running, don't swtich off loading (good for
	// several concurrent requests)
	if (this.runningRequests) return false;
	
	this.loadingDiv.style.display = 'none';
}

StuffedAjax.prototype.request = function () {
	return new StuffedAjaxRequest(this);
}

/*
		========================================================================
		StuffedAjaxRequest class (inititalized from StuffedAjax)
		======================================================================== 
*/

function StuffedAjaxRequest(oAjax) {
	if (!oAjax) return false;
	
	this.ajax = oAjax;
	
  this.uniqueRequestParam = '__SARandom='+Math.random()*1000;

  this.charset = false;
  this.charsetParamId = '__SACharset'	  
}

StuffedAjaxRequest.prototype.setCharset = function (charset) {
	if (charset == null) return false;
	this.charset = charset;
	
	return this;
}

StuffedAjaxRequest.prototype.process = function (url, method) {
	if (url == null) return false;

	// GET is the default method
	if (!method || !method.match(/^(GET|POST)$/i)) method = 'GET';
	method = method.toUpperCase();

	// initializing new xml http request object
	this.xmlhttp = this.ajax.newXMLHttp();

	var oThis = this;
	var oResponse = new StuffedAjaxResponse();

	this.ajax.runningRequests += 1;

	// tell the user the the request is commencing
	this.ajax.showLoading();
	
	// assign the handler for the request
	this.xmlhttp.onreadystatechange = function() {
		if (oThis.xmlhttp.readyState != 4) return;

		oThis.ajax.runningRequests -= 1;

		// tell the user the the request is complete
		oThis.ajax.hideLoading();
		
		if (oThis.xmlhttp.status == 200) {
			oResponse.handler(oThis.xmlhttp);
		} 
		// error occurred
		else {
			oResponse.error(oThis);
		}	
	}
	

	var sendData = null;
	
	// adding internal charset parameter to the request
	if (this.charset) this.setParam(this.charsetParamId, this.charset)

	// preparing URL parameters if they were specified
	if (method == 'GET') {
		var allParams = this.getParamsString();
		if (allParams) {
			url += (url.indexOf('?') == -1 ? '?' : '&') + allParams;
		}
	} 
	else if (method == 'POST') {
		sendData = this.getParamsString();
	} 
	
	// adding random number to the URL to prevent IE from caching the same request
	url += (url.indexOf('?') == -1 ? '?' : '&') + this.uniqueRequestParam;	
		
	this.xmlhttp.open(method, url);		

	// setting correct content-type header for the POST request
	if (method == 'POST') {
		var contentType = 'application/x-www-form-urlencoded';
		if (this.charset) contentType += '; charset='+this.charset;
		this.xmlhttp.setRequestHeader('Content-Type', contentType);	
	}
	
	this.xmlhttp.send(sendData);
	
	return oResponse;
}

StuffedAjaxRequest.prototype.setParam = function (key, value) {
	if (key == null) return false;	
	if (!this.params) this.params = new Object();
	if (!this.params[key]) this.params[key] = new Array();
	
	if (value == null) {
		this.params[key] = null;
	} else {
		this.params[key].push(encodeURIComponent(value));
	}

	return this;
}

StuffedAjaxRequest.prototype.urlEncode = function (string) {
	if (string == null) return '';
 	string = string.toString();
  
	string = string.replace(/([\x90-\xFF])/g, function (chr) {
		return '%u00' + (chr.charCodeAt(0) & 0xFF).toString(16).toUpperCase();
	});

  return escape(string).replace(/\+/g, "%2B");
}

StuffedAjaxRequest.prototype.getParamsString = function (key, value) {
	if (!this.params) return null;
	
	var allParams = new Array();
	
	for (key in this.params) {
		if (this.params[key] == null) continue;
		
		for (i = 0; i < this.params[key].length; i++) {
			allParams.push(key + '=' + this.params[key][i]);
		}
	}
	
	if (allParams.length) {
		return allParams.join('&');
	} else {
		return null;
	}
}

StuffedAjaxRequest.prototype.setErrorURL = function (error_url, error_param) {
	if (error_url == null) return false;
	this.error_url = error_url;
	this.error_param = error_param;
}

StuffedAjaxRequest.prototype.setErrorHandler = function (handlerFunc) {
	if (handlerFunc == null) return false;

	var args = false;
	
	// removing the first argument from the list as it is always equal to
	// the function that should be executed
	if (this.setErrorHandler.arguments.length > 1) {
		args = new Object();
		for (var i = 1; i < this.setErrorHandler.arguments.length; i++) {
			args[i-1] = this.setErrorHandler.arguments[i];
		}
	}

	// notify addToCache that we are adding a custom handler function
	
	var obj = new Object();
	obj['handlerName'] = handlerFunc;	
	obj['args'] = args;
	
	this.customErrorHandler = obj;
	
	return this;
}

/*
		========================================================================
		StuffedAjaxResponse class (inititalized from StuffedAjaxRequest)
		======================================================================== 
*/

function StuffedAjaxResponse() {
	this.complete = false;
}

StuffedAjaxResponse.prototype.error = function (request) {
	if (!request || !request.xmlhttp || !request.xmlhttp.status) return false;

	var xmlhttp = request.xmlhttp;
	
	if (request.customErrorHandler) {
		var handler = request.customErrorHandler;
		var cont = eval(handler.handlerName)(this, handler.args);
		if (!cont) return this;
	}

	if (request.error_url && StuffedAjaxRedirectPerformed == false) {
		var error_param = request.error_param;
		if (error_param == null) error_param = 'ajax_error';
		var url = request.error_url+(request.error_url.indexOf('?') == -1 ? '?' : '&');
		url += error_param + '=' + request.urlEncode(xmlhttp.responseText);
		StuffedAjaxRedirectPerformed = true;
		window.location.href = url;
		return this;
	}
	
	if (xmlhttp.status == 500) {
		alert("Error!\n\n" + xmlhttp.responseText);
	} else {
		alert("Unknown error!\n\n"+ xmlhttp.responseText);
	}
	
	return this;
}

StuffedAjaxResponse.prototype.GetElementAttribute = function(oObj, AttrName)
{
    if (oObj.getAttribute) return oObj.getAttribute(AttrName);
    if (oObj.attributes[AttrName]) return oObj.attributes[AttrName].value;    
    if (oObj.attributes.item(AttrName)) return oObj.attributes.item(AttrName).value;
    return false;
}

StuffedAjaxResponse.prototype.handler = function (xmlhttp) {
	this.complete = true;
	this.xmlhttp = xmlhttp;
	this.responseText = xmlhttp.responseText;
	this.responseXML = xmlhttp.responseXML;
	this.params = new StuffedAjaxResponseParams();
	
	// We should parse the responseText here and extract variables from it,
	// which should be saved somewhere in the response object for later use
	// by all handlers who will need them.
	var oDiv = document.createElement('div');
	oDiv.innerHTML = this.responseText;
	
	var allDivs = oDiv.getElementsByTagName('div');
	if (allDivs) {
		for (var i = 0; i < allDivs.length; i++) {
			var oneDiv = allDivs[i];
			if (!oneDiv.attributes) continue;

			var ajaxType=this.GetElementAttribute(oneDiv,'ajaxType');
			var ajaxValue=this.GetElementAttribute(oneDiv,'ajaxValue');
			var ajaxName=this.GetElementAttribute(oneDiv,'ajaxName');

			if (ajaxType == 'html' && oneDiv.attributes.ajaxName) {
				this.params.__setParam(oneDiv.attributes.ajaxName.value, oneDiv.innerHTML);
			} 
			else if (ajaxType == 'value' && oneDiv.attributes.ajaxName) {
				this.params.__setParam(oneDiv.attributes.ajaxName.value, oneDiv.attributes.ajaxValue.value);
			}
			// special type of param of type "script", it doesn't have a name and
			// contains javascript that would be evaluated inside runScripts method
			// if it will be run (this is a workaround for IE bug that cuts out 
			// scripts from the response if they are not preceded with a TextNode).
			else if (ajaxType == 'script') {
			  if (!this.scripts) this.scripts = new Array();
			  var thisScript = document.createElement('script');
			  thisScript.text = oneDiv.innerHTML;
			  this.scripts.push(thisScript);
			}
		}		
	}
	
	this.releaseCache();
	
	return this;
}

// Specifies the function or its name that should be executed after the
// request will successfully finish, any passed arguments besides the
// function itself will be returned to the function as an *object*, where
// they could be accessed with args[0], args[1], etc. 
// So a function declartion could look like this:
// function handler (args) {
// }

StuffedAjaxResponse.prototype.execFunction = function (handlerFunc) {
	if (handlerFunc == null) return false;

	var args = false;
	
	// removing the first argument from the list as it is always equal to
	// the function that should be executed
	if (this.execFunction.arguments.length > 1) {
		var args = new Object();
		for (var i = 1; i < this.execFunction.arguments.length; i++) {
			args[i-1] = this.execFunction.arguments[i];
		}
	}

	// notify addToCache that we are adding a custom handler function
	this.addToCache(handlerFunc, true, args);
	return this;
}

StuffedAjaxResponse.prototype.addToCache = function (funcName, execFunc, args) {
	if (funcName == null) return false;
	if (!this.cache) this.cache = new Array();
	
	var obj = new Object();
	obj['funcName'] = funcName;	
	obj['execFunc'] = execFunc;
	obj['args'] = args;
	
	this.cache.push(obj);
	if (this.complete) this.releaseCache();
	
	return this;
}	

StuffedAjaxResponse.prototype.releaseCache = function () {
	if (!this.cache) return false;

	// first in first out
	var entry;
	while (entry = this.cache.shift()) {
		// only specify the arguments if the function was inititally added with 
		// execFunction() (it is a custom handler outside the response object)
		if (entry.execFunc) {
			var cont = eval(entry.funcName)(entry.args);
			// if the function explicitly returned false then we stop processing
			if (cont == false) return this;
		} else {
			eval(entry.funcName);
		}
	}
	
	return this;
}

StuffedAjaxResponse.prototype.isComplete = function () {
	return this.complete;
}

StuffedAjaxResponse.prototype.runScripts = function () {
	if (!this.complete) { 
		this.addToCache('this.runScripts()');
		return this;
	}

	// creating a sandbox div
	var sandbox = document.createElement("div");
	sandbox.innerHTML = this.responseText;

	if (this.scripts) {
		for (var i = 0; i < this.scripts.length; i++) {
			this.__execScript(this.scripts[i]);
		}
	}

	var scripts = sandbox.getElementsByTagName('script');
	if (!scripts) return this;
	
	for (i = 0; i < scripts.length; i++) {
		this.__execScript(scripts[i]);
	}
	
	return this;
}

StuffedAjaxResponse.prototype.__execScript = function (thisScript) {
	if (!thisScript) return false;

	var reFunc = /function\s+([^\s\(]+)/gi;
	
	if (thisScript.src) {
		var newScript = document.createElement("script");
		newScript.type = thisScript.type;		
		newScript.src = thisScript.src;				
		document.body.appendChild(newScript);			
	} else if (thisScript.text) {
		var text = thisScript.text.replace(/^\s*<!\-\-/, '').replace(/(\/\/)?\-\->\s*$/, '');
		eval(text);

		// finding all possible function names declared in the script
		var rawNames = true;
		var funcNames = new Array();
		while (rawNames) {
			var rawNames = reFunc.exec(text);
			if (!rawNames || !rawNames[1]) break;
			funcNames.push(rawNames[1]);
		}

		// putting all found functions in the main "window" object/namespace			
		for (var i = 0; i < funcNames.length; i++) {
			window[funcNames[i]] = eval(funcNames[i]);
		}			
	}		
}

// inserts the response in the specified element on the page

StuffedAjaxResponse.prototype.insertIn = function (id, mark) {
	if (id == null) return false;
	if (!this.complete) { 
		this.addToCache('this.insertIn("'+id+'", '+(mark ? 'true' : 'false') +')');
		return this;
	}
	if (this.responseText == '') return this;

	var el = document.getElementById(id);
	if (!el) return this;
	
	el.innerHTML = this.responseText;

	if (mark) this.markElement(id);	
	
	return this;
}

// first shows the specified element on the page and then inserts the response 
// in it, if the second element is specified then it is hidden before the first
// element is displayed, if "mark" is true then the just update element is
// marked with a fading yellow background

StuffedAjaxResponse.prototype.showAndInsertIn = function (idShow, idHide, mark) {
	if (idShow == null) return false;
	if (!this.complete) { 
		var params = '"'+idShow+'"'+(idHide ? ', "'+idHide+'"' : ', false') + (mark ? ', true' : ', false');
		this.addToCache('this.showAndInsertIn('+params+')');
		return this;
	}

	var elShow = document.getElementById(idShow);
	if (!elShow) return this;

	var elHide = document.getElementById(idHide);
	if (elHide) elHide.style.display = 'none';
	
	elShow.style.display = '';
	elShow.innerHTML = this.responseText;
	
	if (mark) this.markElement(idShow);
	
	return this;
}

var Color = new Array('transparent','#ffe','#ffd','#ffc','#ffb','#ffa','#ff9');

StuffedAjaxResponse.prototype.markElement = function (id, bg_color, firstDelay) {
	if (id == null) return false;
	if (!this.complete) { 
		var params = '"'+id+'"'+(bg_color ? ', "'+bg_color+'"' : '');
		this.addToCache('this.markElement('+params+')');
		return this;
	}
		
  var el = document.getElementById(id);
  if (!el) return this;
  
  if (firstDelay == null) firstDelay = 300;

  if (bg_color) Color[0] = bg_color;  

	var idx = Color.length-1;
  setTimeout("markElementDoFade('"+id+"', "+idx+")", firstDelay);
  
  return this;
}

/*
		========================================================================
		StuffedAjaxResponseParams class (inititalized from StuffedAjaxResponse)
		======================================================================== 
*/

function StuffedAjaxResponseParams() {
}
StuffedAjaxResponseParams.prototype.__setParam = function (key, value) {
	if (key == null) return false;	
	if (!this.params) this.params = new Array();
	if (!this.params[key]) this.params[key] = new Array();
	
	this.params[key].push(value);
	
	return this;
}

StuffedAjaxResponseParams.prototype.getParam = function (key) {
	if (key == null || !this.params || this.params[key] == null) return false;
	
	// if the key has only one value then return just the value
	if (this.params[key].length == 1) {
		return this.params[key][0];
	} 
	// otherwise return an array of values
	else {
		this.params[key]
	}
	
}

/*
	========================================================================
	StuffedAjax service functions
	======================================================================== 
*/

function markElementDoFade (id, idx) {
	if (id == null) return false;

  var el = document.getElementById(id);
  if (!el || idx == -1) return false;

  el.style.backgroundColor = Color[idx];
  idx -= 1;
  setTimeout("markElementDoFade('"+id+"', "+idx+")", 150);
}