function AjaxForm(options){
	this.url = document.location.href;
	this.form = null;
	this.inputs = {};
	this.hash = {};
	this.need_save = false;
	this.global_section = false;
	this.requests = {};
	this.serviceFunction = function(){};

	this.init = function(options){
		$H(options).each(function(v, k){
			this[k] = v;
		}.bind(this));
		this.url  = this.url.replace(/(^.*\/?[^\/]+)\.v[^\/]*$/, '$1');
		this.inputs = $(this.form).getElements('input[type=text], input[type=password], input[type=hidden], textarea');
		this.inputs = this.inputs.filter(function(input){
			var cl = input.className.split(" ")[0].split("-");
			if (cl[0] == "js"){return true};
			return false;
		});
		this.inputs.each(function(input){
			if (this.addInputToHash($(input))){
				this.bindEventsAndFunctions($(input));
			}
		}.bind(this));
		this.checkAllFields();
	}
	this.addInputToHash = function(input){
		var cl = input.className.split(" ")[0].split("-");
		if (cl[0] == "js"){
			if (!this.hash[cl[1]]){
				if (cl.length < 3){
					this.hash[cl[1]] = input.get('value');
				}
				else{
					this.hash[cl[1]] = {}
				}
			}
			if (typeof this.hash[cl[1]] == 'string'){
				input.section = '';
			}
			else{
				this.hash[cl[1]][cl[2]] = input.get('value');
				input.section = cl[1];
			}
			return true;
		}
		return false;
	}
	this.bindEventsAndFunctions = function(input){
		input.addEvent('focus', function(){
			if (!input.get('value')){
				this.showMessage(input, 'tip');
				input.getParent().addClass('igray');
			}
		}.bind(this))
		.addEvent('blur', function(){
			if (input.getParent().hasClass('igray')){
				input.getParent().removeClass('igray');
				this.getSpan(input).set('text', '');
			}
		}.bind(this))
		.addEvent('keyup', function(e){
			if ($chk(e) && e.code == 9){
				return false;
			}
			if (input.t){
				clearTimeout(input.t);
				input.processing = false;
			}
			this.cancelAjax(input);
			input.updateValue();
			if (input.get('value')){
				if (!(input.hasClass('not_checked'))){
					input.processing = true;
					input.t = setTimeout(
						function(){this.check(input)}.bind(this),
						800
					);
				}
			}
			else{
				this.showMessage(input, 'tip');
				this.checkFormOk();
			}
			this.serviceFunction(input);
		}.bind(this))
		input.processing = false;
		input.updateValue = function(){
			if (input.section){
				this.hash[input.section][input.get('name')] = (input.getProperty('prefix') ? input.getProperty('prefix') : '') + input.get('value') + (input.get('postfix') ? input.get('postfix') : '');
			}
			else{
				this.hash[input.get('name')] = (input.getProperty('prefix') ? input.getProperty('prefix') : '') + input.get('value') + (input.get('postfix') ? input.get('postfix') : '');
			}
			return input;
		}.bind(this)
		input.check = function(){this.check(input); return input}.bind(this);
		input.save = function(){this.save(input); return input}.bind(this);
	}
	this.getRequestName = function(input){
		if (input.section){
			return input.section;
		}
		else{
			return input.get('name');
		}
	}
	this.getSpan = function(input){
		var _class = '.' + ((input.section) ? input.section + '-' : '') + input.get('name');
		return $(this.form).getElement(_class);
	}
	this.checkAllFields = function(){
		this.inputs.each(function(input){
			if (input.get('value') != ''){
				input.only_check = true;
				this.cancelAjax(input);
				this.check(input);
			}
		}.bind(this));
	}
	this.check = function(input){
		var send_obj = {};
		if (input.section){
			send_obj[input.section] = this.hash[input.section];
		}
		else{
			send_obj[input.get('name')] = this.hash[input.get('name')];
		}
		send_obj = JSON.encode(send_obj);
		var req_name = this.getRequestName(input);
		this.requests[req_name] = new Ajax(send_obj, {
			url: this.url + '.x',
			onSuccess: function(response){
				var errors = {};
				var f = function(obj, field){
					for (var i in obj) {
						if (!obj.hasOwnProperty(i)) continue;
						if (i == '@error') {
							if (field) errors[field] = obj[i];
						} else {
							if (typeof obj[i] == 'object') {
								f(obj[i], ($chk(field)) ? field + '-' + i : i);
							}
							else{
								if (i != '#text'){
									if (i.substring(0, 1) == '@'){
										i = '';
									};
									var input = $(this.form).getElement('.js-' + ($chk(field) ? field + (i ? '-' + i : '') : i));
									this.showMessage(input , 'field_ok', 'ok_message');
									if ($chk(input.only_check)){
										input.only_check = false;
									}
									else{
										this.save(input);
									}
								}
							}
						}
					}
				}.bind(this);
				f(response);
				for (var _class in errors){
					this.showMessage($(this.form).getElement('.js-' + _class), 'error', errors[_class]);
				}
				input.processing = false;
				this.checkFormOk();
				this.serviceFunction(input);
			}.bind(this),
			onFailure: function(){
				input.processing = false;
				this.serviceFunction(input);
				this.requests[req_name].onFailure = function(){
					this.serviceFunction(input);
				}.bind(this);
				this.requests[req_name].send(send_obj);
			}.bind(this),
			onCancel: function(){
				input.processing = false;
			}
		});
	}
	this.save = function(input){
		if (!this.need_save){
			return;
		}
		var send_obj = {};
		if (typeof input.getParent != 'function' || !input.getParent().hasClass('ired')){
			if (input == 'all'){
				send_obj = this.hash;
			}
			else{
				if (input.section){
					send_obj[input.section] = {};
					send_obj[input.section][input.get('name')] = this.hash[input.section][input.get('name')];
				}
				else{
					send_obj[input.get('name')] = this.hash[input.get('name')];
				}
			}
			new Request.JSON({
				url: this.url + '.s',
				method: 'post',
				onSuccess: this.checkFormOk.bind(this)
			}).send(JSON.encode(send_obj));
		}
	}
	this.clearFields = function(input){
		if (input == 'all'){
			this.inputs.each(function(input){
				if (!(input.get('readonly') || input.get('disabled'))){
					this.clearInput(input);
				}
			}.bind(this));
			this.save('all');
		}
		else{
			if (!(input.get('readonly') || input.get('disabled'))){
				input.set('value', '').updateValue().save();
			}
		}
	}
	this.cancelAjax = function(input){
		var req_name = this.getRequestName(input);
		if (this.requests[req_name]){
			this.requests[req_name].cancel();
		}
	}
	this.clearInput = function(input){
		this.cancelAjax(input);
		input.getParent().removeClass('ired').removeClass('ilightgreen');
		input.set('value', '').updateValue();
		var span = this.getSpan(input);
		span.set('text', '');
	}
	this.showMessage = function(input, type, message){
		var span = this.getSpan(input);
		switch(type){
			case 'tip':
				input.getParent().removeClass('ired').removeClass('ilightgreen').addClass('igray');
				span.removeClass('ired').removeClass('ilightgreen').set('html', tips[input.get('name')]);
				break;
			case 'error':
				input.getParent().removeClass('ired').removeClass('ilightgreen').removeClass('igray');
				span.removeClass('ired').removeClass('ilightgreen');
				if (input.get('value') != ''){
					input.getParent().addClass('ired');
					span.addClass('ired').set('html', tips[message]);
				}
				break;
			case 'field_ok':
				input.getParent().removeClass('ired').removeClass('igray').addClass('ilightgreen');
				span.removeClass('ired').addClass('ilightgreen').set('html', tips[message]);
				break;
			default: break;
		}
	}
	this.checkFormOk = function(){
		var state = $(this.form).getElements('.required').filter(function(input, i){return input.getParent().hasClass('ilightgreen')}).length == $(this.form).getElements('.required').length
			&& $(this.form).getElements('.ired').length == 0;
		if (state){
			this.form_ok = true;
			if (typeof this.onFormOk == 'function') this.onFormOk();
		}
		else{
			this.form_ok = false;
		}
	}
	this.init(options);
}