	function ajaxLink(ref_form)
	{
		if(typeof ref_form == 'string') this.parseRef(ref_form);
		if(typeof ref_form == 'object') this.assignForm(ref_form);
	}
	
	ajaxLink.prototype.protocol;
	ajaxLink.prototype.host;
	ajaxLink.prototype.path;
	ajaxLink.prototype.parameters;
	ajaxLink.prototype.anchor;
	
	ajaxLink.prototype.a;
	ajaxLink.prototype.form;
	ajaxLink.prototype.submit;

	ajaxLink.prototype.loaders = [];

	ajaxLink.prototype.parseRef = function(ref)
	{
		this.protocol = window.location.protocol;
		this.host = window.location.host;
		this.path = window.location.pathname
		this.parameters = {};
		
		var pos;

		if((pos = ref.search(/\:/)) >= 0)
		{
			this.protocol = ref.substring(0, pos + 1);
			ref = ref.substring(pos + 1);
		}

		if((pos = ref.search(/\#/)) >= 0)
		{
			this.anchor = ref.substring(pos + 1);
			ref = ref.substring(0, pos);
		}

		if((pos = ref.search(/\?/)) >= 0)
		{
			var paramsStr = ref.substring(pos + 1) + '&';
			ref = ref.substring(0, pos);
			while((pos = paramsStr.search(/\&/)) >= 0)
			{
				var paramStr = paramsStr.substring(0, pos);
				paramsStr = paramsStr.substring(pos + 1);

				if(paramStr.length)
				{
					var equPos = paramStr.search(/\=/);
					if(equPos < 0) this.parameters[paramStr] = '';
					else this.parameters[paramStr.substring(0, equPos)] = decodeURIComponent(paramStr.substring(equPos + 1));
				}
			}
		}

		if(ref.search(/\/\//) == 0)
		{
			ref = ref.substring(2);
			if((pos = ref.search(/\//)) >= 0)
			{
				this.host = ref.substring(0, pos);
				this.path = ref.substring(pos);
			}
			else
			{
				this.host = ref
				this.path = '/';
			}
		}
		else if(ref.search(/\//) == 0)
		{
			this.path = ref;
		}
		else this.path = this.path + (this.path.substring(this.path.length - 1) == '/' ? '' : '/') + ref;

		return this;
	}

	ajaxLink.prototype.assignA = function(a)
	{
		this.parseRef(a.href);
		this.a = a;
	}

	ajaxLink.prototype.assignForm = function(form, submit)
	{
		this.parseRef(form.action);
		this.form = form;
		this.submit = submit;
	}

	ajaxLink.prototype.assembleRef = function()
	{
		var ref = this.protocol + '//' + this.host + this.path;
		var div = '?';
		for(var key in this.parameters)
		{
			ref += div + key + '=' + encodeURIComponent(this.parameters[key]);
			div = '&';
		}
		return ref;
	}
	
	ajaxLink.prototype.setParameter = function(name, value, overwrite)
	{
		if(overwrite == undefined) overwrite = true;
		
		if(overwrite || !this.hasParameter(name))
		{
			if(this.form)
			{
				var input = this.form.ownerDocument.createElement('input');
				input.name = name;
				input.value = value;
				input.type = 'hidden';
				this.form.appendChild(input);
			}
			else
				this.parameters[name] = value;
		}
	}

	ajaxLink.prototype.getParameter = function(name)
	{
		if(this.form)
		{
			var field = selectNode("descendant::input[@name='"+ name +"']|descendant::textarea[@name='"+ name +"']|descendant::select[@name='"+ name +"']", this.form);
			if(field) return this.getFormFieldValue(field);
		}
		else return this.parameters[name];
	}

	ajaxLink.prototype.hasParameter = function(name)
	{
		return this.form ? selectNode("descendant::input[@name='"+ name +"']|descendant::textarea[@name='"+ name +"']|descendant::select[@name='"+ name +"']", this.form) : this.parameters[name];
	}
	
	ajaxLink.prototype.getFormFieldValue = function(field)
	{
		switch(field.type)
		{
			case 'radio': return field.checked ? encodeURIComponent(field.value) : null;
			case 'checkbox': return field.checked ? encodeURIComponent(field.value) : null;
			case 'select': return field.selectedIndex >= 0 ? field.options[field.selectedIndex].value : '';
			case 'textarea': return encodeURIComponent(field.value);
			default: return encodeURIComponent(field.value);
		}
	}

	ajaxLink.prototype.getFormData = function()
	{
		var formData = '';
		var fields = selectNodes("descendant::input[@type != 'submit' and @type != 'image']|descendant::textarea|descendant::select", this.form);
		for(var i = 0; i < fields.length; i++)
		{
			var fieldValue = this.getFormFieldValue(fields[i]);
			if(fieldValue != null) formData += fields[i].name + '=' + fieldValue + '&';
		}

		if(this.submit) formData += this.submit.name + '=' + encodeURIComponent(this.submit.value);

		return formData;
	}

	ajaxLink.prototype.getFreeLoader = function()
	{
		for(var i = 0; i < this.loaders.length; i++)
			if(this.loaders[i].className == 'free loader') return this.loaders[i];

		var loader = document.createElement('iframe');
		loader.style.display = 'none';
		loader.name = 'loader' + this.loaders.length;
		loader.className = 'loader';
		document.documentElement.appendChild(loader);
		this.loaders.push(loader);
		return loader;
	}
	
	ajaxLink.prototype.loadXml = function(callback)
	{
		if(this.form != undefined)
		{
			if(this.form.enctype == 'multipart/form-data')
			{
				var loader = this.getFreeLoader();
				this.form.target = loader.name;
				if(this.submit)
				{
					var handler = this.submit.onclick;
					this.submit.onclick = undefined;
					this.submit.click();
					this.submit.onclick = handler;
				}
				else this.form.submit();
				if(callback == undefined)
				{
					var result;
					do{result = getFrameDocument(loader)} while(!result);
					result = cloneDocument(result);
					loader.className = 'free loader';
					return result;
				}
				else loader.onload = function() {var result = cloneDocument(getFrameDocument(loader)); loader.className = 'free loader'; callback(result)};
			}
			else
			{
				if(callback == undefined) return loadXml(this.assembleRef(), this.getFormData());
				else loadXml(this.assembleRef(), this.getFormData(), callback);
			}
		}
		else
		{
			if(callback == undefined) return loadXml(this.assembleRef());
			else loadXml(this.assembleRef(), undefined, callback);
		}
	}
