function Checkbox(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.checked = this.node.checked;
	this.defaultChecked = this.node.defaultChecked;
	this.disabled = this.node.disabled;
	this.name = this.node.name;
	this.value = this.node.value;
}

Checkbox.prototype.check = function(trueOrFalse) {
	this.checked = trueOrFalse;
	if (this.checked) Innecto.styleClass.add(this.node, "checked");
	else Innecto.styleClass.remove(this.node, "checked");
};

Checkbox.prototype.disable = function() {
	this.disabled = true;
	this.node.tabIndex = -1;
	Innecto.styleClass.add(this.node, "disabled");
};

Checkbox.prototype.enable = function() {
	this.disabled = false;
	this.node.tabIndex = 0;
	Innecto.styleClass.remove(this.node, "disabled");
};

Checkbox.prototype.handleEvents = function(e) {
	if (this.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			break;
		case "click":
			this.check(!this.checked);
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
		case "keydown":
			if (e.keyCode == 32) {
				EventManager.fire(this.node, "click");
				e.preventDefault();
			}
			break;
	}
};

Checkbox.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-checkbox")) {
		this.node.insertAdjacentHTML("beforeBegin", [
			"<div class=\"control-checkbox",
			this.checked ? " checked" : "",
			this.disabled ? " disabled" : "",
			"\"",
			this.defaultChecked ? " defaultchecked" : "",
			" id=\"",
			this.id,
			"\" name=\"",
			this.name,
			"\" tabindex=\"",
			this.disabled ? -1 : 0,
			"\" value=\"",
			this.value,
			"\"></div>"].join(""));
		var node = this.node.previousSibling;
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		if (typeof this.node.hideFocus != "undefined") this.node.hideFocus = true;
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				EventManager.fire(node, "click");
				node.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(this.node, "blur", handleEvents);
		EventManager.add(this.node, "click", handleEvents);
		EventManager.add(this.node, "focus", handleEvents);
		EventManager.add(this.node, "keydown", handleEvents);
	}
};

Checkbox.prototype.type = "checkbox";

function Errors(Input) {
	this.parent = Input;
	this.errors = [];
}

Errors.prototype.add = function(error, id) {
	if (typeof this.errors[id] == "undefined") this.errors[id] = [error];
	else if (this.errors[id].indexOf(error) == -1) this.errors[id].push(error);
	this.show(id);
};

Errors.prototype.clear = function() {
	this.errors = [];
};

Errors.prototype.hide = function() {
	this.clear();
	
	function hideError(el) {
		el.parentNode.removeChild(el);
	}
	
	Sly.search(".errors", this.parent.parent.node).forEach(hideError);
};

Errors.prototype.remove = function(error, id) {
	if (typeof this.errors[id] != "undefined") {
		if (error) {
			var i = this.errors[id].indexOf(error);
			if (i != -1) {
				if (this.errors[id].length > 1) this.errors[id].splice(i, 1);
				else delete this.errors[id];
				this.show(id);
			}
		}
		else {
			delete this.errors[id];
			this.show(id);
		}
	}
};

Errors.prototype.show = function(id) {
	var el = Innecto.$("errors-" + id);
	if (el) {
		if (typeof this.errors[id] != "undefined") el.innerHTML = "<ul><li>" + this.errors[id]
				.join("</li><li>") + "</li></ul>";
		else el.parentNode.removeChild(el);
	}
	else {
		el = Innecto.$(id);
		if (Innecto.styleClass.exist(el, "control-hidden")) el = el.previousSibling;
		do {
			el = el.nextSibling;
			if (!el) {
				if (typeof this.errors[id] != "undefined") alert(this.errors[id]
						.join("\n"));
				return;
			}
		}
		while (el.nodeName != "BR");
		el
				.insertAdjacentHTML(
						"afterEnd",
						"<div class=\"errors\" id=\"errors-" + id + "\"><ul><li>" + this.errors[id]
								.join("</li><li>") + "</li></ul></div>");
	}
};

function File(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.constraint = this.node.getAttribute("constraint");
	this.disabled = this.node.disabled;
	this.name = this.node.name;
	this.value = this.node.value;
}

File.prototype.disable = function() {
	this.node.disabled = true;
	this.node.firstChild.disabled = true;
	this.disabled = true;
};

File.prototype.enable = function() {
	this.node.disabled = false;
	this.node.firstChild.disabled = false;
	this.disabled = false;
};

File.prototype.refresh = function() {
	this.value = this.node.value;
};

File.prototype.render = function() {
	// TODO Add keyboard navigation support!
	if (!Innecto.styleClass.exist(this.node.parentNode, "control-file")) {
		this.node.outerHTML = [
			"<div class=\"control-file\"><input class=\"short\" id=\"",
			this.id,
			"-file\" readonly type=\"text\"><div class=\"buttons\"><ul><li class=\"buttons-button enabled\" id=\"",
			this.id,
			"-browse\"><a href=\"#\" name=\"browse\">",
			$TXT_BROWSE,
			"</a></li></ul></div>",
			this.node.outerHTML,
			"</div>"].join("");
		this.node = Innecto.$(this.id);
		this.parent.initialize(this.node.parentNode);
		this.parent.parent.buttons.initialize(this.node.parentNode);
		if (this.disabled) {
			this.parent.controls[this.id + "-file"].disable();
			this.parent.parent.buttons.buttons[this.id + "-browse"].disable();
		}
		var parent = this;
		
		function change() {
			parent.parent.controls[this.id + "-file"].setValue(this.value
					.substr(this.value.lastIndexOf("\\") + 1));
		}
		
		EventManager.add(this.node, "change", change);
	}
};

File.prototype.type = "file";

function Hidden(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.constraint = this.node.getAttribute("constraint");
	this.defaultValue = this.node.value;
	this.disabled = this.node.disabled;
	this.name = this.node.name;
	this.required = this.node.attributes["required"];
	this.unique = this.node.getAttribute("unique");
	this.value = this.node.value;
}

Hidden.prototype.refresh = function() {
	this.value = this.node.innerHTML;
};

Hidden.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-hidden")) {
		this.node.insertAdjacentHTML("beforeBegin", [
			"<span class=\"control-hidden\"",
			this.required ? " required" : "",
			this.disabled ? " disabled" : "",
			"\"",
			this.constraint ? " constraint=\"" + this.constraint + "\"" : "",
			" defaultvalue=\"",
			this.defaultValue,
			"\" id=\"",
			this.id,
			"\" name=\"",
			this.name,
			"\"",
			this.unique ? " unique=\"" + this.unique + "\"" : "",
			"></span>"].join(""));
		var node = this.node.previousSibling;
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		this.setValue(this.value);
	}
};

Hidden.prototype.setDefaultValue = function(newValue) {
	this.defaultValue = newValue;
	this.node.setAttribute("defaultvalue", newValue);
};

Hidden.prototype.setValue = function(newValue) {
	this.value = newValue;
	this.node.innerHTML = newValue;
};

Hidden.prototype.type = "hidden";

function Input(parent) {
	this.parent = parent;
	this.controls = [];
	this.errors = new Errors(this);
}

Input.prototype.checkbox = function(id) {
	return new Checkbox(id, this);
};

Input.prototype.coalesce = function(value, returnValue) {
	if (String(value).match(/^(?:null|undefined|-1)*$/) == null) return value;
	return returnValue;
};

Input.prototype.disable = function(id) {
	this.getControlByElement(id).disable();
};

Input.prototype.enable = function(id) {
	this.getControlByElement(id).enable();
};

Input.prototype.eventify = function() {
	var parent = this;
	var events = [];
	var args = arguments;
	var len = args.length, i = 0;
	while (i < len) {
		var id = args[i];
		var type = args[i + 1];
		events[id] = args[i + 2];
		var Control = this.controls[id];
		var node = Control.node;
		switch (Control.type) {
			case "multilinetext":
				node = node.firstChild.firstChild.firstChild.firstChild;
				break;
			case "text":
				node = node.firstChild.firstChild;
				break;
		}
		EventManager.add(node, type, function(e) {
			var Control = parent.getControlByElement(this);
			events[Control.id].apply(Control, [e]);
		});
		i += 3;
	}
};

Input.prototype.file = function(id) {
	return new File(id, this);
};

Input.prototype.getControlByElement = function(el) {
	while (!el.id) {
		el = el.parentNode;
	}
	return this.controls[el.id];
};

Input.prototype.getControlsByName = function(name) {
	var controls = [];
	for ( var id in this.controls) {
		if (this.controls.hasOwnProperty(id)) {
			var Control = this.controls[id];
			if (Control.name.replace(/\[.*\]/, "") == name) controls
					.push(Control);
		}
	}
	return controls;
};

Input.prototype.hasErrors = function() {
	return this.errors.errors.count() > 0;
};

Input.prototype.hidden = function(id) {
	return new Hidden(id);
};

Input.prototype.initialize = function(el) {
	var parent = this;
	
	function validateConstraint() {
		parent.validateConstraint.apply(parent, [parent
				.getControlByElement(this)]);
	}
	
	function validateExistence() {
		parent.validateExistence.apply(parent, [parent
				.getControlByElement(this)]);
	}
	
	function validateLength() {
		parent.validateLength.apply(parent, [parent.getControlByElement(this)]);
	}
	
	function validateMimeTypes() {
		parent.validateMimeTypes.apply(parent, [parent
				.getControlByElement(this)]);
	}
	
	function validatePasswords() {
		parent.validatePasswords.apply(parent, [parent
				.getControlByElement(this)]);
	}
	
	function validateRange() {
		parent.validateRange.apply(parent, [parent.getControlByElement(this)]);
	}
	
	function validateUnique() {
		parent.validateUnique.apply(parent, [parent.getControlByElement(this)]);
	}
	
	var context = el || this.parent.node;
	var els = Sly.search("input, select, textarea", context);
	var len = els.length, i = 0;
	var Control;
	while (i < len) {
		var el = els[i];
		if (!el.id) el.id = Innecto.guid();
		switch (el.nodeName) {
			case "INPUT":
				switch (el.type) {
					case "checkbox":
						Control = this.checkbox(el.id);
						Control.render();
						break;
					case "file":
						Control = this.file(el.id);
						Control.render();
						if (!this.controls.hasOwnProperty(Control.id) && Control.constraint && Control.constraint
								.match(/^mimetypes/)) EventManager.add(
								Control.node,
								"change",
								validateMimeTypes);
						break;
					case "hidden":
						Control = this.hidden(el.id);
						Control.render();
						if (Control.unique) EventManager.add(
								Control.node,
								"keyup",
								validateUnique);
						break;
					case "password":
						Control = this.password(el.id);
						Control.render();
						el = Control.node.firstChild.firstChild;
						if (Control.minLength) {
							EventManager.add(el, "blur", validateLength);
							EventManager.add(el, "keyup", validateLength);
						}
						if (Control.constraint && Control.constraint
								.match(/^passwordmatch$/)) {
							EventManager.add(el, "blur", validatePasswords);
							EventManager.add(el, "keyup", validatePasswords);
						}
						break;
					case "radio":
						Control = this.radioButton(el.id);
						Control.render();
						break;
					case "text":
						Control = this.text(el.id);
						Control.render();
						el = Control.node.firstChild.firstChild;
						if (Control.constraint) {
							if (Control.constraint
									.match(/^email|emails|file|identifier|item|phone|string-basic|string-extended|url|zip$/)) {
								EventManager
										.add(el, "blur", validateConstraint);
								EventManager.add(
										el,
										"keyup",
										validateConstraint);
								if (Control.constraint.match(/^mimetypes/)) {
									EventManager.add(
											el,
											"blur",
											validateMimeTypes);
									EventManager.add(
											el,
											"keyup",
											validateMimeTypes);
								}
							}
							else if (Control.constraint.match(/^folder|path/)) {
								EventManager
										.add(el, "blur", validateConstraint);
								EventManager.add(
										el,
										"keyup",
										validateConstraint);
								if (!Control.constraint.match(/nocheck$/)) {
									EventManager.add(
											el,
											"blur",
											validateExistence);
									EventManager.add(
											el,
											"keyup",
											validateExistence);
								}
							}
							else if (Control.constraint.match(/^range/)) {
								EventManager.add(el, "blur", validateRange);
								EventManager.add(el, "keyup", validateRange);
							}
						}
						if (Control.unique) {
							EventManager.add(el, "blur", validateUnique);
							EventManager.add(el, "keyup", validateUnique);
						}
						break;
				}
				break;
			case "SELECT":
				Control = el.size > 1 ? this.selectList(el.id) : this
						.select(el.id);
				Control.render();
				if (Control.editable) {
					if (Control.constraint) {
						el = Control.node.firstChild.nextSibling;
						if (Control.constraint
								.match(/^email|emails|file|identifier|item|phone|string-basic|string-extended|url|zip$/)) {
							EventManager.add(el, "blur", validateConstraint);
							EventManager.add(el, "keyup", validateConstraint);
							if (Control.constraint.match(/^mimetypes/)) {
								EventManager.add(el, "blur", validateMimeTypes);
								EventManager
										.add(el, "keyup", validateMimeTypes);
							}
						}
						else if (Control.constraint.match(/^folder|path/)) {
							EventManager.add(el, "blur", validateConstraint);
							EventManager.add(el, "keyup", validateConstraint);
							if (!Control.constraint.match(/nocheck$/)) {
								EventManager.add(el, "blur", validateExistence);
								EventManager
										.add(el, "keyup", validateExistence);
							}
						}
						else if (Control.constraint.match(/^range/)) {
							EventManager.add(el, "blur", validateRange);
							EventManager.add(el, "keyup", validateRange);
						}
					}
				}
				break;
			case "TEXTAREA":
				Control = this.multilineText(el.id);
				Control.render();
				break;
			default:
				Control = undefined;
				break;
		}
		if (typeof Control != "undefined") this.controls[Control.id] = Control;
		i++;
	}
};

Input.prototype.multilineText = function(id) {
	return new MultilineText(id, this);
};

Input.prototype.password = function(id) {
	return new Password(id, this);
};

Input.prototype.radioButton = function(id) {
	return new RadioButton(id, this);
};

Input.prototype.regularExpression = function(regExp) {
	switch (regExp) {
		case "email":
			return /^[A-Za-z0-9_\-\.]+@[A-Za-z0-9\-\.]+$/;
			break;
		case "emails":
			return /^(([A-Za-z0-9_\-\.]+@[A-Za-z0-9\-\.]+)*\s*[,]{0,1}\s*)+$/;
			break;
		case "file":
			return /^[^\\\?\*"'<>:\|]+\.\w{2,6}$/;
			break;
		case "folder":
			return /^[^\\\/\?\*"'<>:\|]*$/;
			break;
		case "identifier":
			return /^[A-Za-z0-9_\-]*$/;
			break;
		case "item":
			return /^[A-Za-z0-9_\-&#\(\)<>\?\/' \.\+]*$/;
			break;
		case "path":
			return /^([A-Z]:\\[^\/:\*\?<>\|]+\.\w{2,6})|(\\{2}[^\/:\*\?<>\|]+\.\w{2,6})$/;
			break;
		case "phone":
			return /^[A-Za-z0-9]*$/;
			break;
		case "string-basic":
			return /^[A-Za-z\- ]*$/;
			break;
		case "string-extended":
			return /^[A-Za-z0-9_\-\,\*\(\)\+\$\.\/ \?&'#!:]*$/;
			break;
		case "url":
			return /^((http|ftp|https):\/\/w{3}[\d]*.|(http|ftp|https):\/\/|w{3}[\d]*.)([\w\d\._\-#\(\)\[\]\\,;:]+@[\w\d\._\-#\(\)\[\]\\,;:])?([a-z0-9]+.)*[a-z\-0-9]+.([a-z]{2,3})?[a-z]{2,6}(:[0-9]+)?(\/[\/a-z0-9\._\-,]+)*[a-z0-9\-_\.\s\%]+(\?[a-z0-9=%&\.\-,#]+)?$/;
			break;
		case "zip":
			return /^\d{5}$/;
			break;
	}
};

Input.prototype.render = function(el) {
	switch (el.nodeName) {
		case "INPUT":
			switch (el.type) {
				case "checkbox":
					this.checkbox(el.id).render();
					break;
				case "password":
				case "text":
					this.text(el.id).render();
					break;
				case "radio":
					this.radioButton(el.id).render();
					break;
			}
			break;
		case "SELECT":
			this[el.size > 1 ? "selectList" : "select"](el.id).render();
			break;
		case "TEXTAREA":
			this.multilineText(el.id).render();
			break;
	}
};

Input.prototype.reset = function(id) {
	var Control = this.controls[id];
	switch (Control.type) {
		case "checkbox":
			Control.check(Control.defaultChecked);
			break;
		case "radiobutton":
			Control.check(Control.defaultChecked);
			break;
		case "select":
			Control.options.selectDefault();
			break;
		case "text":
			Control.setValue(Control.defaultValue);
			break;
	}
};

Input.prototype.select = function(id) {
	return new Select(id, this);
};

Input.prototype.selectList = function(id) {
	return new SelectList(id, this);
};

Input.prototype.serialize = function() {
	var args = arguments;
	var len = args.length;
	var data = [];
	for ( var id in this.controls) {
		if (this.controls.hasOwnProperty(id)) {
			var Control = this.controls[id];
			if (Control.hasOwnProperty("refresh")) Control.refresh();
			if (len > 0 && Array.indexOf(args, Control.name.replace(
					/\[.*\]/,
					"")) == -1) continue;
			if (!Control.disabled && (!Control.hasOwnProperty("checked") || Control
					.hasOwnProperty("checked") && Control.checked)) {
				var value;
				switch (Control.type) {
					case "checkbox":
					case "hidden":
					case "multilinetext":
					case "password":
					case "radiobutton":
					case "text":
						value = Control.value;
						break;
					case "select":
					case "selectlist":
						value = Control.editable ? Control.value : Control.options
								.selected().toString();
						break;
				}
				data.push(Control.name + "=" + encodeURIComponent(value));
			}
		}
	}
	return data.join("&");
};

Input.prototype.text = function(id) {
	return new Text(id, this);
};

Input.prototype.validate = function() {
	for ( var id in this.controls) {
		if (this.controls.hasOwnProperty(id)) {
			var Control = this.controls[id];
			if (Control.hasOwnProperty("refresh")) Control.refresh();
			this.errors.remove($TXT_ERR_MISSING, Control.id);
			if (!Control.disabled && Control.required) {
				var parent = this;
				
				function validate() {
					var Control = parent.getControlByElement(this);
					Control.refresh();
					if (Control.value) parent.errors.remove(
							$TXT_ERR_MISSING,
							Control.id);
				}
				
				var el;
				switch (Control.type) {
					case "multilinetext":
						el = Control.node.firstChild.firstChild.firstChild.firstChild;
						break;
					case "select":
						el = Control.node.firstChild.nextSibling;
						break;
					case "text":
						el = Control.node.firstChild.firstChild;
						break;
				}
				if (!Control.value) {
					this.errors.add($TXT_ERR_MISSING, Control.id);
					EventManager.add(el, "keyup", validate);
				}
				else {
					this.errors.remove($TXT_ERR_MISSING, Control.id);
					EventManager.remove(el, "keyup", validate);
				}
			}
		}
	}
	if (this.hasErrors() && this.errors.onError) this.errors.onError();
	return !this.hasErrors();
};

Input.prototype.validateConstraint = function(Control) {
	Control.refresh();
	if (Control.value.trim() && !Control.value.match(this
			.regularExpression(Control.constraint.split(":")[0]))) this.errors
			.add($TXT_ERR_INVALID, Control.id);
	else this.errors.remove($TXT_ERR_INVALID, Control.id);
};

Input.prototype.validateExistence = function(Control) {
	Control.refresh();
	if (Control.value) {
		var matches = Control.constraint.match(/^(?:folder|path):(.+)$/);
		var directory = matches.length > 1 ? matches[1] : false;
		var directorySuffix = directory ? "\\" + Control.value : "";
		directory = directory ? directory : Control.value;
		if (Control.value.length > 0) Ajax.asyncRequest(
				"POST",
				"commands/validateexistence.php",
				Ajax.serialize(
						"directory",
						directory,
						"directorysuffix",
						directorySuffix),
				{
					scope: this,
					success: function(Obj) {
						var exists = Obj.response.toBoolean();
						if (Control.existenceRequired) {
							if (!exists) this.errors.add(
									$TXT_ERR_NOT_EXIST,
									Control.id);
							else this.errors.remove(
									$TXT_ERR_NOT_EXIST,
									Control.id);
						}
						else {
							if (exists) this.errors.add(
									$TXT_ERR_EXISTS,
									Control.id);
							else this.errors
									.remove($TXT_ERR_EXISTS, Control.id);
						}
					}
				},
				true);
	}
};

Input.prototype.validateLength = function(Control) {
	if (Control.hasOwnProperty("refresh")) Control.refresh();
	var error = $TXT_ERR_MIN_LENGTH(Control.minLength);
	if (Control.value && Control.value.length < Control.minLength) this.errors
			.add(error, Control.id);
	else this.errors.remove(error, Control.id);
};

Input.prototype.validateMimeTypes = function(Control) {
	Control.refresh();
	var ext = Control.value.substr(Control.value.lastIndexOf(".") + 1)
			.toLowerCase();
	var mimeTypes = Control.constraint.match(/^mimetypes:(.+)$/)[1].split(";");
	
	function mimeType(el) {
		return el.toUpperCase() + " (." + el + ")";
	}
	
	var supportedMimeTypes = mimeTypes.map(mimeType);
	var error = $TXT_ERR_INVALID_MIME_TYPE + " " + supportedMimeTypes
			.join(", ") + ".";
	if (mimeTypes.indexOf(ext) != -1 || !Control.value) this.errors.remove(
			error,
			Control.id);
	else if (Control.value) this.errors.add(error, Control.id);
};

Input.prototype.validatePasswords = function(Control) {
	var base = Control.id.slice(0, Control.id.lastIndexOf("-"));
	var Control1 = this.controls[base + "-password"];
	var Control2 = this.controls[base + "-confirmpassword"];
	if (Control1.value != "" && Control2.value != "" && Control1.value != Control2.value) this.errors
			.add($TXT_ERR_PASSWORD_MISMATCH, Control.id);
	else {
		this.errors.remove($TXT_ERR_PASSWORD_MISMATCH, Control1.id);
		this.errors.remove($TXT_ERR_PASSWORD_MISMATCH, Control2.id);
	}
};

Input.prototype.validateRange = function(Control) {
	Control.refresh();
	var matches = Control.constraint.match(/^range:(.+)-(.+)$/);
	var lbound = parseFloat(matches[1]);
	var ubound = parseFloat(matches[2]);
	var error = $TXT_ERR_OUT_OF_RANGE(lbound, ubound);
	if (parseFloat(Control.value) < lbound || parseFloat(Control.value) > ubound || isNaN(Control.value)) this.errors
			.add(error, Control.id);
	else this.errors.remove(error, Control.id);
};

Input.prototype.validateUnique = function(Control) {
	Control.refresh();
	var url = Control.unique;
	if (Control.value && Control.defaultValue != Control.value) Ajax
			.asyncRequest("POST", url, Ajax.serialize(
					Control.name,
					Control.value), {
				scope: this,
				success: function(Obj) {
					if (Obj.response.toBoolean()) this.errors.add(
							$TXT_ERR_NOT_UNIQUE,
							Control.id);
					else this.errors.remove($TXT_ERR_NOT_UNIQUE, Control.id);
				}
			});
	else this.errors.remove($TXT_ERR_NOT_UNIQUE, Control.id);
};

function MultilineText(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.defaultValue = this.node.value;
	this.disabled = this.node.disabled;
	this.maxLength = this.node.getAttribute("maxlength");
	this.name = this.node.name;
	this.readOnly = this.node.readOnly;
	this.required = this.node.attributes["required"];
	this.rows = this.node.rows;
	this.value = this.node.value;
}

MultilineText.prototype.disable = function() {
	this.disabled = true;
	var el = this.node.firstChild.firstChild.firstChild.firstChild;
	el.contentEditable = false;
	el.tabIndex = -1;
	Innecto.styleClass.add(this.node, "disabled");
};

MultilineText.prototype.enable = function() {
	this.disabled = false;
	var el = this.node.firstChild.firstChild.firstChild.firstChild;
	el.contentEditable = true;
	el.tabIndex = 0;
	Innecto.style.set(el, "overflow", "auto");
	Innecto.styleClass.remove(this.node, "disabled");
};

MultilineText.prototype.getValue = function() {
	return this.node.firstChild.firstChild.firstChild.firstChild.innerHTML
			.replace(/<div><br><\/div>|<br>|<div>|<\/p><p>/gi, "\n").replace(
					/<\/?[a-z]+>/gi,
					"");
};

MultilineText.prototype.handleEvents = function(e) {
	this.refresh();
	if (this.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			var el = this.node.firstChild.firstChild.firstChild.firstChild;
			var value = this.value;
			if (this.maxLength && value.length > this.maxLength) value = value
					.slice(0, this.maxLength);
			el.innerHTML = value.convertLineBreaks();
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
		case "keypress":
			var backspaceKey = e.keyCode == 8;
			var tabKey = e.keyCode == 9;
			var deleteKey = e.keyCode == 46;
			var noSelection;
			if (window.getSelection) noSelection = window.getSelection().isCollapsed;
			else if (document.selection) noSelection = document.selection.type == "None";
			if (noSelection && !backspaceKey && !tabKey && !deleteKey && this.maxLength && this.value.length == this.maxLength) e
					.preventDefault();
			break;
	}
};

MultilineText.prototype.refresh = function() {
	this.value = this.getValue();
};

MultilineText.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-multilinetext")) {
		var classes = Innecto.styleClass.get(this.node);
		this.node
				.insertAdjacentHTML(
						"beforeBegin",
						[
							"<div class=\"control-multilinetext",
							classes != "" ? " " + classes : "",
							this.required ? " required" : "",
							this.disabled ? " disabled" : "",
							"\" defaultvalue=\"",
							this.defaultValue,
							"\" id=\"",
							this.id,
							"\"",
							this.maxLength ? " maxlength=\"" + this.maxLength + "\"" : "",
							" name=\"",
							this.name,
							"\"",
							this.readOnly ? " readonly" : "",
							this.rows ? " rows=\"" + this.rows + "\"" : "",
							"><div><div><div><div class=\"control-multilinetext-value\" tabindex=\"",
							this.disabled ? -1 : 0,
							"\"></div></div></div></div></div>"].join(""));
		var node = this.node.previousSibling;
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		var el = this.node.firstChild.firstChild.firstChild.firstChild;
		if (typeof el.hideFocus != "undefined") el.hideFocus = true;
		if (this.rows) {
			el.innerHTML = "&nbsp;";
			Innecto.style.set(el, "height", el.clientHeight * this.rows + "px");
		}
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				this.nextSibling.firstChild.firstChild.firstChild.firstChild
						.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(el, "blur", handleEvents);
		EventManager.add(el, "focus", handleEvents);
		EventManager.add(el, "keypress", handleEvents);
		if (!this.disabled) {
			el.contentEditable = true;
			Innecto.style.set(el, "overflow", "auto");
		}
		this.setValue(this.value);
	}
};

MultilineText.prototype.setReadOnly = function(trueOrFalse) {
	this.readOnly = trueOrFalse;
	if (trueOrFalse) {
		this.node.setAttribute("readonly", trueOrFalse);
		this.node.firstChild.firstChild.firstChild.firstChild.contentEditable = false;
	}
	else {
		this.node.removeAttribute("readonly");
		this.node.firstChild.firstChild.firstChild.firstChild.contentEditable = true;
	}
};

MultilineText.prototype.setValue = function(newValue) {
	this.value = newValue;
	this.node.firstChild.firstChild.firstChild.firstChild.innerHTML = newValue
			.convertLineBreaks();
};

MultilineText.prototype.type = "multilinetext";

function Password(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.constraint = this.node.getAttribute("constraint");
	this.disabled = this.node.disabled;
	this.maxLength = this.node.getAttribute("maxlength");
	this.minLength = this.node.getAttribute("minlength");
	this.name = this.node.name;
	this.required = this.node.attributes["required"];
	this.size = this.node.getAttribute("size");
	this.value = this.node.value;
}

Password.prototype.disable = function() {
	this.disabled = true;
	this.parent.errors.remove(false, this.id);
	var el = this.node.firstChild.firstChild;
	el.contentEditable = false;
	el.tabIndex = -1;
	Innecto.styleClass.add(this.node, "disabled");
};

Password.prototype.enable = function() {
	this.disabled = false;
	var el = this.node.firstChild.firstChild;
	el.contentEditable = true;
	el.tabIndex = 0;
	Innecto.styleClass.remove(this.node, "disabled");
};

Password.prototype.handleEvents = function(e) {
	if (this.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
		case "keydown":
			var backspaceKey = e.keyCode == 8;
			var enterKey = e.keyCode == 13;
			var deleteKey = e.keyCode == 46;
			if (enterKey) e.preventDefault();
			else if (backspaceKey || deleteKey) {
				var startOffset, endOffset;
				if (window.getSelection) {
					var range = window.getSelection().getRangeAt(0);
					startOffset = range.startOffset;
					endOffset = range.endOffset;
				}
				else if (document.selection) {
					var range = document.selection.createRange();
					var len = range.text.length;
					range.execCommand("Delete");
					range.moveStart(
							"character",
							-this.node.firstChild.firstChild.innerText.length);
					startOffset = range.text.length;
					endOffset = startOffset + len;
					range.collapse(false);
				}
				var chars = this.value.split("");
				if (startOffset == endOffset) chars.splice(startOffset - 1, 1);
				else chars.splice(startOffset, endOffset - startOffset);
				this.value = chars.join("");
			}
			break;
		case "keypress":
			var charKey = typeof e.charCode != "undefined" ? e.charCode : e.keyCode;
			if (charKey != 0) {
				var noSelection;
				if (window.getSelection) noSelection = window.getSelection().isCollapsed;
				else if (document.selection) noSelection = document.selection.type == "None";
				if (noSelection && this.maxLength && this.value.length == this.maxLength) {
					e.preventDefault();
					return;
				}
				var char = String.fromCharCode(charKey);
				var el = this.node.firstChild.firstChild;
				if (window.getSelection) {
					var selection = window.getSelection();
					var range = selection.getRangeAt(0);
					var startOffset = range.startOffset;
					var endOffset = range.endOffset;
					if (el.textContent.length == 0) {
						this.value = char;
						el.textContent = "*";
					}
					else {
						var chars = this.value.split("");
						chars
								.splice(
										startOffset,
										endOffset - startOffset,
										char);
						this.value = chars.join("");
						el.textContent = this.value.replace(/./g, "*");
					}
					if (Innecto.agent.SAFARI) selection.setBaseAndExtent(
							el.firstChild,
							startOffset + 1,
							el.firstChild,
							startOffset + 1);
					else {
						range.setStart(el.firstChild, startOffset + 1);
						range.setEnd(el.firstChild, startOffset + 1);
					}
				}
				else if (document.selection) {
					var range = document.selection.createRange();
					var len = range.text.length;
					range.execCommand("Delete");
					range.moveStart("character", -el.innerText.length);
					var startOffset = range.text.length;
					var endOffset = startOffset + len;
					range.collapse(false);
					if (el.innerText.length == 0) this.value = char;
					else {
						var chars = this.value.split("");
						chars
								.splice(
										startOffset,
										endOffset - startOffset,
										char);
						this.value = chars.join("");
					}
					range.pasteHTML("*");
				}
				e.preventDefault();
			}
			break;
	}
};

Password.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-password")) {
		var classes = Innecto.styleClass.get(this.node);
		if (Innecto.agent.IE && this.node.outerHTML
				.indexOf("size=" + this.size) == -1) this.size = 0;
		this.node.insertAdjacentHTML("beforeBegin", [
			"<div class=\"control-password",
			classes != "" ? " " + classes : "",
			this.required ? " required" : "",
			this.disabled ? " disabled" : "",
			"\"",
			this.constraint ? " constraint=\"" + this.constraint + "\"" : "",
			"\" id=\"",
			this.id,
			"\"",
			this.maxLength ? " maxlength=\"" + this.maxLength + "\"" : "",
			this.minLength ? " minlength=\"" + this.minLength + "\"" : "",
			" name=\"",
			this.name,
			"\"",
			this.size ? " size=\"" + this.size + "\"" : "",
			"><div><div class=\"control-password-value\"></div></div></div>"]
				.join(""));
		var node = this.node.previousSibling;
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		var el = this.node.firstChild.firstChild;
		if (typeof el.hideFocus != "undefined") el.hideFocus = true;
		if (this.size) {
			el.innerHTML = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
			Innecto.style
					.set(
							el,
							"width",
							Math
									.round(this.node.clientWidth / el.innerHTML.length) * (parseInt(this.size) + 1) + "px");
		}
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				this.nextSibling.firstChild.firstChild.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(el, "blur", handleEvents);
		EventManager.add(el, "focus", handleEvents);
		EventManager.add(el, "keydown", handleEvents);
		EventManager.add(el, "keypress", handleEvents);
		if (!this.disabled) {
			el.tabIndex = 0;
			el.contentEditable = true;
		}
	}
};

Password.prototype.setValue = function(newValue) {
	this.value = newValue;
	var el = this.node.firstChild.firstChild;
	el.innerHTML = "";
	if (newValue != "") {
		var text = document.createTextNode(newValue);
		el.appendChild(text);
	}
};

Password.prototype.type = "password";

function RadioButton(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.checked = this.node.checked;
	this.defaultChecked = this.node.defaultChecked;
	this.disabled = this.node.disabled;
	this.name = this.node.name;
	this.value = this.node.value;
}

RadioButton.prototype.check = function(trueOrFalse) {
	this.checked = trueOrFalse;
	if (this.checked) Innecto.styleClass.add(this.node, "checked");
	else Innecto.styleClass.remove(this.node, "checked");
};

RadioButton.prototype.disable = function() {
	this.disabled = true;
	this.node.tabIndex = -1;
	Innecto.styleClass.add(this.node, "disabled");
};

RadioButton.prototype.enable = function() {
	this.disabled = false;
	this.node.tabIndex = 0;
	Innecto.styleClass.remove(this.node, "disabled");
};

RadioButton.prototype.handleEvents = function(e) {
	if (this.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			break;
		case "click":
			var controls = this.parent.getControlsByName(this.name);
			
			function uncheckRadiobutton(Control) {
				Control.check(false);
			}
			
			controls.forEach(uncheckRadiobutton);
			this.check(true);
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
		case "keydown":
			if (e.keyCode == 32) {
				EventManager.fire(this.node, "click");
				e.preventDefault();
			}
			break;
	}
};

RadioButton.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-radiobutton")) {
		this.node.insertAdjacentHTML("beforeBegin", [
			"<div class=\"control-radiobutton",
			this.checked ? " checked" : "",
			this.disabled ? " disabled" : "",
			"\"",
			this.defaultChecked ? " defaultchecked" : "",
			" id=\"",
			this.id,
			"\" name=\"",
			this.name,
			"\" tabindex=\"",
			this.disabled ? -1 : 0,
			"\" value=\"",
			this.value,
			"\"></div>"].join(""));
		var node = this.node.previousSibling;
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		if (typeof this.node.hideFocus != "undefined") this.node.hideFocus = true;
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				EventManager.fire(node, "click");
				node.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(this.node, "blur", handleEvents);
		EventManager.add(this.node, "click", handleEvents);
		EventManager.add(this.node, "focus", handleEvents);
		EventManager.add(this.node, "keydown", handleEvents);
	}
};

RadioButton.prototype.type = "radiobutton";

function Select(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.constraint = this.node.getAttribute("constraint");
	this.disabled = this.node.disabled;
	this.editable = this.node.attributes["editable"];
	this.maxLength = this.node.getAttribute("maxlength");
	this.name = this.node.name;
	this.options = new SelectOptions(this);
	this.required = this.node.attributes["required"];
	this.value = "";
}

Select.prototype.disable = function() {
	this.disabled = true;
	this.node.tabIndex = -1;
	Innecto.styleClass.add(this.node, "disabled");
};

Select.prototype.enable = function() {
	this.disabled = false;
	this.node.tabIndex = 0;
	Innecto.styleClass.remove(this.node, "disabled");
};

Select.prototype.handleEvents = function(e) {
	this.refresh();
	if (this.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
		case "keydown":
			if (this.editable) return;
			var upKey = e.keyCode == 38;
			var downKey = e.keyCode == 40;
			var charKey = e.keyCode >= 65 && e.keyCode <= 90;
			if (!downKey && !upKey && !charKey) return;
			var index = this.options.selected("index");
			if (upKey || downKey) {
				if (upKey) index--;
				else index++;
				if (index == -1 || index == this.options.count) {
					e.preventDefault();
					return;
				}
			}
			else if (charKey) {
				var i = 0;
				while (i < this.options.count) {
					if (String.fromCharCode(e.keyCode).toLowerCase() == this.options.options[i].text
							.slice(0, 1).toLowerCase()) {
						index = i;
						break;
					}
					i++;
				}
			}
			this.options.select(index, "index");
			if (this.hasOwnProperty("onSelect")) this.onSelect();
			break;
		case "keypress":
			var backspaceKey = e.keyCode == 8;
			var tabKey = e.keyCode == 9;
			var enterKey = e.keyCode == 13;
			var deleteKey = e.keyCode == 46;
			var noSelection = true;
			if (window.getSelection) noSelection = window.getSelection().isCollapsed;
			else if (document.selection) noSelection = document.selection.type == "None";
			if (enterKey || noSelection && !backspaceKey && !tabKey && !deleteKey && this.maxLength && this.value.length == this.maxLength) e
					.preventDefault();
			break;
		case "mousedown":
			var selEl = e.target;
			if (this.editable && !Innecto.styleClass.exist(
					selEl,
					"control-select-glyph")) return;
			if (typeof storage != "undefined") this.options.hide();
			else this.options.show();
			break;
	}
};

Select.prototype.refresh = function() {
	var el = this.node.firstChild.nextSibling;
	this.value = typeof el.innerText != "undefined" ? el.innerText : el.textContent;
};

Select.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-select")) {
		var classes = Innecto.styleClass.get(this.node);
		this.node
				.insertAdjacentHTML(
						"beforeBegin",
						[
							"<div class=\"control-select",
							classes != "" ? " " + classes : "",
							this.disabled ? " disabled" : "",
							this.editable ? " editable" : "",
							"\"",
							this.constraint ? " constraint=\"" + this.constraint + "\"" : "",
							" id=\"",
							this.id,
							"\"",
							this.maxLength ? " maxlength=\"" + this.maxLength + "\"" : "",
							" name=\"",
							this.name,
							"\"><div class=\"control-select-glyph\"></div><div class=\"control-select-selected\">",
							this.editable ? "" : "&nbsp;",
							"</div><div class=\"control-select-options\"></div></div>"]
								.join(""));
		var node = this.node.previousSibling;
		var options = [];
		if (this.node.options.length > 0) {
			function getOption(el) {
				return [el.text, el.value, el.selected];
			}
			
			options = Array.map(this.node.options, getOption);
		}
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		if (options.length > 0) this.options.populate(options);
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				this.nextSibling.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(this.node, "blur", handleEvents);
		EventManager.add(this.node, "focus", handleEvents);
		EventManager.add(this.node, "keydown", handleEvents);
		if (this.editable) EventManager
				.add(this.node, "keypress", handleEvents);
		EventManager.add(this.node, "mousedown", handleEvents);
		if (!this.disabled) {
			if (this.editable) {
				var el = this.node.firstChild.nextSibling;
				if (typeof el.hideFocus != "undefined") el.hideFocus = true;
				el.tabIndex = 0;
				el.contentEditable = true;
			}
			else {
				if (typeof this.node.hideFocus != "undefined") this.node.hideFocus = true;
				this.node.tabIndex = 0;
			}
		}
	}
};

Select.prototype.setValue = function(newValue) {
	this.value = newValue;
	var el = this.node.firstChild.nextSibling;
	el.innerHTML = "";
	var text = document.createTextNode(newValue);
	el.appendChild(text);
};

Select.prototype.type = "select";

function SelectList(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.disabled = this.node.disabled;
	this.max = this.node.getAttribute("max");
	this.multiple = this.node.multiple;
	this.name = this.node.name;
	this.options = new SelectListOptions(this);
	this.size = this.node.size;
}

SelectList.prototype.disable = function() {
	this.disabled = true;
	Innecto.styleClass.add(this.node, "disabled");
};

SelectList.prototype.enable = function() {
	this.disabled = false;
	Innecto.styleClass.remove(this.node, "disabled");
};

SelectList.prototype.handleEvents = function(e) {
	if (this.disabled) return;
	var selEl = e.target;
	var selIndex = this.options.getIndex(selEl);
	var tabKey = e.keyCode == 9;
	var enterKey = e.keyCode == 13;
	var upKey = e.keyCode == 38;
	var downKey = e.keyCode == 40;
	var shiftKey = e.shiftKey;
	var ctrlKey = e.ctrlKey;
	var Option;
	switch (e.type) {
		case "keydown":
			if (!downKey && !upKey && !enterKey) return;
			if (downKey || upKey) {
				if (downKey) selEl = selEl.nextSibling;
				else selEl = selEl.previousSibling;
				if (!selEl || selEl.nodeType == 3) return;
				selIndex = this.options.getIndex(selEl);
			}
			if (ctrlKey) return;
			break;
		case "mousedown":
			if (selEl.nodeName != "SPAN") return;
			break;
	}
	if (this.multiple) {
		var focalPointEl = Sly.search(
				".control-selectlist-options-option[focalpoint]",
				this.node)[0];
		if (shiftKey) {
			if (focalPointEl) {
				var focalPointIndex = this.options.getIndex(focalPointEl);
				
				function sortIndex(a, b) {
					if (a > b) return 1;
					if (a < b) return -1;
					return 0;
				}
				
				var indexOrder = [selIndex, focalPointIndex].sort(sortIndex);
				
				function selectOption(Option, index) {
					Option.selected = (index > indexOrder[0] && index < indexOrder[1]);
					Option.apply();
				}
				
				this.options.options.forEach(selectOption);
				Option = this.options.options[focalPointIndex];
				Option.selected = true;
				Option.apply();
			}
			else selEl.setAttribute("focalpoint", "yes");
			Option = this.options.options[selIndex];
			Option.selected = true;
			Option.apply();
		}
		else {
			if (focalPointEl) focalPointEl.removeAttribute("focalpoint");
			selEl.setAttribute("focalpoint", "yes");
			if (ctrlKey || enterKey) {
				Option = this.options.options[selIndex];
				Option.selected = !Option.selected;
				Option.apply();
			}
			else this.options.select( [selIndex], "index");
		}
	}
	else {
		this.options.select( [selIndex], "index");
		selEl.setAttribute("focalpoint", "yes");
	}
	
	function tabify(Option) {
		Option.node.tabIndex = -1;
	}
	
	this.options.options.forEach(tabify);
	selEl.tabIndex = 0;
	selEl.focus();
	if (this.hasOwnProperty("onSelect")) this.onSelect();
};

SelectList.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-selectlist")) {
		var classes = Innecto.styleClass.get(this.node);
		this.node
				.insertAdjacentHTML(
						"beforeBegin",
						[
							"<div class=\"control-selectlist",
							classes != "" ? " " + classes : "",
							this.disabled ? " disabled" : "",
							"\" id=\"",
							this.id,
							"\"",
							this.max ? " max=\"" + this.max + "\"" : "",
							this.multiple ? " multiple" : "",
							" name=\"",
							this.name,
							"\" size=\"",
							this.size,
							"\"><div><div><div><div class=\"control-selectlist-options\"><span class=\"control-selectlist-options-option\">&nbsp;</span></div></div></div></div></div>"]
								.join(""));
		var node = this.node.previousSibling;
		var el = node.firstChild.firstChild.firstChild.firstChild;
		Innecto.style.set(
				el,
				"height",
				el.firstChild.offsetHeight * this.size + "px");
		el.innerHTML = "";
		var options = [];
		if (this.node.options.length > 0) {
			function getOption(el) {
				return [el.text, el.value];
			}
			
			options = Array.map(this.node.options, getOption);
		}
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		if (options.length > 0) {
			this.options.add(options);
			this.options.selectDefault();
		}
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				this.nextSibling.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(this.node, "keydown", handleEvents);
		EventManager.add(this.node, "mousedown", handleEvents);
	}
};

SelectList.prototype.setMax = function(max) {
	this.max = max;
	this.node.setAttribute("max", max);
};

SelectList.prototype.type = "selectlist";

function SelectListOption(Obj, SelectListOptions) {
	this.parent = SelectListOptions;
	var SPAN = document.createElement("span");
	SPAN.setAttribute("title", Obj.text);
	SPAN.setAttribute("value", Obj.value);
	Innecto.styleClass.set(SPAN, "control-selectlist-options-option");
	SPAN.appendChild(document.createTextNode(Obj.text));
	var parent = this;
	
	function handleEvents(e) {
		parent.handleEvents.apply(parent, [e]);
	}
	
	EventManager.add(SPAN, "blur", handleEvents);
	EventManager.add(SPAN, "focus", handleEvents);
	if (this.parent.count == 0) SPAN.tabIndex = 0;
	if (typeof SPAN.hideFocus != "undefined") SPAN.hideFocus = true;
	this.node = SPAN;
	this.selected = false;
	this.text = Obj.text;
	this.value = Obj.value;
}

SelectListOption.prototype.apply = function() {
	this.node.firstChild.nodeValue = this.text;
	this.node.setAttribute("title", this.text);
	this.node.setAttribute("value", this.value);
	if (this.selected) Innecto.styleClass.add(this.node, "selected");
	else Innecto.styleClass.remove(this.node, "selected");
};

SelectListOption.prototype.handleEvents = function(e) {
	if (this.parent.parent.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
	}
};

function SelectListOptions(SelectList) {
	this.parent = SelectList;
	this.count = 0;
	this.options = [];
}

SelectListOptions.prototype.add = function(options, beforeIndex) {
	function generateOption(el, index) {
		var Option = new SelectListOption( {
			text: el.getPropertyAtIndex(0),
			value: typeof el.getPropertyAtIndex(1) != "undefined" ? el
					.getPropertyAtIndex(1) : el.getPropertyAtIndex(0)
		}, this);
		if (typeof beforeIndex != "undefined") {
			this.options.splice(beforeIndex + index, 0, Option);
			el = this.options[beforeIndex + index].node;
			el.parentNode.insertBefore(Option.node, el);
		}
		else {
			this.parent.node.firstChild.firstChild.firstChild.firstChild
					.appendChild(Option.node);
			this.options.push(Option);
		}
		this.count++;
	}
	
	options.forEach(generateOption, this);
};

SelectListOptions.prototype.all = function(what) {
	function getOption(Option, index) {
		if (what == "index") return index + 1;
		return what == "text" ? Option.text : Option.value;
	}
	
	var options = this.options.map(getOption);
	return options.length > 0 ? options : [];
};

SelectListOptions.prototype.change = function(text, value, index) {
	if (typeof index == "undefined") index = this.selected("index").toString();
	var Option = this.options[index];
	Option.text = text;
	Option.value = value;
	Option.apply();
};

SelectListOptions.prototype.copyTo = function(id) {
	var Control = this.parent.parent.controls[id];
	
	function copyOption(Option) {
		if (Option.selected && (Control.max == -1 || Control.options.count < Control.max || !Control.max)) Control.options
				.add( [[Option.text, Option.value]]);
	}
	
	this.options.forEach(copyOption);
};

SelectListOptions.prototype.getIndex = function(el) {
	var i = 0;
	while (el.previousSibling) {
		el = el.previousSibling;
		i++;
	}
	return i;
};

SelectListOptions.prototype.move = function(upOrDown) {
	var index = this.selected("index").toString();
	var newIndex = index;
	if (upOrDown == "up" && newIndex > 0) newIndex--;
	else if (upOrDown == "down" && newIndex < this.count) newIndex++;
	if (index != newIndex) {
		var Option1 = this.options[index];
		var Option2 = this.options[newIndex];
		var text1 = Option1.text;
		var text2 = Option2.text;
		var value1 = Option1.value;
		var value2 = Option2.value;
		Option1.selected = false;
		Option1.text = text2;
		Option1.value = value2;
		Option1.apply();
		Option2.selected = true;
		Option2.text = text1;
		Option2.value = value1;
		Option2.apply();
		Option2.node.focus();
	}
	return newIndex;
};

SelectListOptions.prototype.moveTo = function(id) {
	this.copyTo(id);
	this.synchronize(id);
};

SelectListOptions.prototype.populate = function(options) {
	this.count = 0;
	this.options = [];
	this.parent.node.firstChild.firstChild.firstChild.firstChild.innerHTML = "";
	this.add(options);
};

SelectListOptions.prototype.remove = function() {
	var i = 0;
	while (i < this.options.length) {
		var Option = this.options[i];
		if (Option.selected) {
			Option.node.parentNode.removeChild(Option.node);
			this.options.splice(i, 1);
			this.count--;
		}
		i++;
	}
	if (this.count > 0) this.options[0].node.tabIndex = 0;
};

SelectListOptions.prototype.select = function(values, what) {
	what = what || "value";
	var index = this.selected("index").toString();
	
	function deselectOption(Option) {
		Option.node.tabIndex = -1;
		Option.selected = false;
		Option.apply();
	}
	
	this.options.forEach(deselectOption);
	var indexes = [];
	switch (what) {
		case "first":
			index = 0;
			indexes.push(index);
			break;
		case "index":
			indexes = values;
			break;
		case "last":
			index = this.count - 1;
			indexes.push(index);
			break;
		case "next":
			if (index != this.count - 1) index++;
			indexes.push(index);
			break;
		case "previous":
			if (index > 0) index--;
			indexes.push(index);
			break;
		case "text":
		case "value":
			function getOption(Option, index) {
				function getOption(value) {
					if (value == ((what == "text") ? Option.text : Option.value)) indexes
							.push(index);
				}
				
				values.forEach(getOption);
			}
			
			this.options.forEach(getOption);
			break;
	}
	
	function selectOption(Option, index) {
		if (indexes.indexOf(index) != -1) {
			Option.selected = true;
			Option.apply();
		}
	}
	
	this.options.forEach(selectOption);
	this.options[indexes[0]].node.tabIndex = 0;
	return indexes;
};

SelectListOptions.prototype.selectDefault = function() {
	var indexes = [];
	
	function getOptionIndex(Option, index) {
		if (Option.defaultSelected) indexes.push(index);
	}
	
	this.options.forEach(getOptionIndex);
	this.select(indexes, "index");
};

SelectListOptions.prototype.selected = function(what) {
	var options = [];
	
	function getSelectedOption(Option, index) {
		if (Option.selected) {
			if (what == "index") options.push(index);
			else options.push((what == "text" ? Option.text : Option.value));
		}
	}
	
	Array.forEach(this.options, getSelectedOption);
	return options.length > 0 ? options : -1;
};

SelectListOptions.prototype.synchronize = function(id) {
	var Control = this.parent.parent.controls[id];
	var options = Control.options.all();
	var i = 0;
	while (i < this.options.length) {
		var Option = this.options[i];
		var index = options.indexOf(Option.value);
		if (index != -1) {
			Option.node.parentNode.removeChild(Option.node);
			options.splice(index, 1);
			this.options.splice(i, 1);
			this.count--;
		}
		i++;
	}
	if (this.count > 0) this.options[0].node.tabIndex = 0;
};

function SelectOption(Obj, SelectOptions) {
	this.parent = SelectOptions;
	var SPAN = document.createElement("span");
	Innecto.styleClass.set(SPAN, "control-select-options-option");
	SPAN.setAttribute("value", Obj.value);
	SPAN.appendChild(document.createTextNode(Obj.text));
	this.node = SPAN;
	this.defaultSelected = Obj.hasOwnProperty("defaultSelected") ? Obj.defaultSelected : false;
	this.selected = false;
	this.text = Obj.text;
	this.value = Obj.value;
}

SelectOption.prototype.apply = function() {
	this.node.firstChild.nodeValue = this.text;
	this.node.setAttribute("value", this.value);
	if (this.selected) {
		Innecto.styleClass.add(this.node, "selected");
		this.parent.parent.node.firstChild.nextSibling.innerHTML = this.node.innerHTML;
	}
	else Innecto.styleClass.remove(this.node, "selected");
};

function SelectOptions(Select) {
	this.parent = Select;
	this.count = 0;
	this.options = [];
}

SelectOptions.prototype.all = function(what) {
	function getOption(Option, index) {
		if (what == "index") return index + 1;
		return what == "text" ? Option.text : Option.value;
	}
	
	var options = this.options.map(getOption);
	return options.length > 0 ? options : -1;
};

SelectOptions.prototype.getIndex = function(el) {
	var i = 0;
	while (el.previousSibling) {
		el = el.previousSibling;
		i++;
	}
	return i;
};

SelectOptions.prototype.handleEvents = function(e) {
	var selEl = e.target;
	if (selEl.parentNode == storage) {
		this.select(this.getIndex(selEl), "index");
		if (this.parent.editable) EventManager.fire(
				this.parent.node.firstChild.nextSibling,
				"keyup");
		if (this.parent.hasOwnProperty("onSelect")) this.parent.onSelect
				.apply(this.parent);
		this.hide();
	}
};

SelectOptions.prototype.hide = function() {
	storage.parentNode.removeChild(storage);
	delete storage;
	var parent = this;
	window.setTimeout(function() {
		parent.parent.node.focus();
	}, 1);
};

SelectOptions.prototype.populate = function(options, defaultOptions) {
	defaultOptions = defaultOptions ? [defaultOptions] : [];
	this.options = [];
	this.parent.node.lastChild.innerHTML = "";
	
	function generateOption(el) {
		var Obj = {
			text: el.getPropertyAtIndex(0),
			value: typeof el.getPropertyAtIndex(1) != "undefined" ? el
					.getPropertyAtIndex(1) : el.getPropertyAtIndex(0)
		};
		if (typeof el.getPropertyAtIndex(2) != "undefined") Obj.defaultSelected = el
				.getPropertyAtIndex(2);
		var Option = new SelectOption(Obj, this);
		this.parent.node.lastChild.appendChild(Option.node);
		this.options.push(Option);
	}
	
	defaultOptions.concat(options).forEach(generateOption, this);
	this.count = this.options.length;
	this.selectDefault();
	if (this.selected("index") == -1) this.select("", "first");
};

SelectOptions.prototype.populateWithStates = function(defaultOptions) {
	var states = [
		["Alabama", "AL"],
		["Alaska", "AK"],
		["Arizona", "AZ"],
		["Arkansas", "AR"],
		["California", "CA"],
		["Colorado", "CO"],
		["Connecticut", "CT"],
		["Delaware", "DE"],
		["Florida", "FL"],
		["Georgia", "GA"],
		["Hawaii", "HI"],
		["Idaho", "id"],
		["Illinois", "IL"],
		["Indiana", "IN"],
		["Iowa", "IA"],
		["Kansas", "KS"],
		["Kentucky", "KY"],
		["Louisiana", "LA"],
		["Maine", "ME"],
		["Maryland", "MD"],
		["Massachusetts", "MA"],
		["Michigan", "MI"],
		["Minnesota", "MN"],
		["Mississippi", "MS"],
		["Missouri", "MO"],
		["Montana", "MT"],
		["Nebraska", "NE"],
		["Nevada", "NV"],
		["New Hampshire", "NH"],
		["New Jersey", "NJ"],
		["New Mexico", "NM"],
		["New York", "NY"],
		["Newfoundland", "NF"],
		["North Carolina", "NC"],
		["North Dakota", "ND"],
		["Ohio", "OH"],
		["Oklahoma", "OK"],
		["Oregon", "OR"],
		["Pennsylvania", "PA"],
		["Rhode Island", "RI"],
		["South Carolina", "SC"],
		["South Dakota", "SD"],
		["Tennessee", "TN"],
		["Texas", "TX"],
		["Utah", "UT"],
		["Vermont", "VT"],
		["Virginia", "VA"],
		["Washington", "WA"],
		["West Virginia", "WV"],
		["Wisconsin", "WI"],
		["Wyoming", "WY"]];
	this.populate(states, defaultOptions);
};

SelectOptions.prototype.populateWithTime = function(defaultOptions) {
	function option(el) {
		options
				.push( [(Katana.languages.militaryTime ? i.toString()
						.setLeadingZeros(2) + ":" + el.toString()
						.setLeadingZeros(2) : i.toMeridiemTime(el))]);
	}
	
	var options = [];
	var quarters = [0, 15, 30, 45];
	var i = 0;
	while (i < 24) {
		quarters.forEach(option);
		i++;
	}
	this.populate(options, defaultOptions);
};

SelectOptions.prototype.populateWithYears = function(defaultOptions) {
	var years = [];
	var year = 1900;
	while (year < 2079) {
		years.push( [year]);
		year++;
	}
	this.populate(years, defaultOptions);
};

SelectOptions.prototype.select = function(value, what) {
	what = what || "value";
	var index = this.selected("index");
	var Option;
	if (index != -1) {
		Option = this.options[index];
		Option.selected = false;
		Option.apply();
	}
	else what = "first";
	switch (what) {
		case "first":
			index = 0;
			break;
		case "index":
			index = value;
			break;
		case "last":
			index = this.count - 1;
			break;
		case "next":
			if (index != this.count - 1) index++;
			break;
		case "previous":
			if (index > 0) index--;
			break;
		case "text":
		case "value":
			var i = 0;
			while (i < this.count) {
				if (value == (what == "text" ? this.options[i].text : this.options[i].value)) {
					index = i;
					break;
				}
				i++;
			}
			break;
	}
	Option = this.options[index];
	Option.selected = true;
	Option.apply();
	if (this.parent.hasOwnProperty("onSelect")) this.parent.onSelect
			.apply(this.parent);
	return index;
};

SelectOptions.prototype.selectDefault = function() {
	function selectOption(Option, index) {
		if (Option.defaultSelected) {
			Option.selected = true;
			Option.apply();
		}
	}
	
	this.options.forEach(selectOption);
};

SelectOptions.prototype.selected = function(what) {
	var index = -1;
	var i = 0;
	while (i < this.count) {
		if (this.options[i].selected) {
			index = i;
			break;
		}
		i++;
	}
	if (index == -1) return -1;
	else if (what == "index") return index;
	else return what == "text" ? this.options[index].text : this.options[index].value;
};

SelectOptions.prototype.show = function() {
	var el = this.parent.node.lastChild;
	document.body.insertAdjacentHTML(
			"afterBegin",
			"<div id=\"control-select-options\">" + el.innerHTML + "</div>");
	storage = Innecto.$("control-select-options");
	if (typeof storage.hideFocus != "undefined") storage.hideFocus = true;
	storage.tabIndex = 0;
	var parent = this;
	
	function handleEvents(e) {
		parent.handleEvents.apply(parent, [e]);
	}
	
	EventManager.add(storage, "mousedown", handleEvents);
	var height = this.count > 10 ? el.lastChild.offsetHeight * 10 : "auto";
	if (this.count > 10) Innecto.style.set(storage, "height", height + "px");
	var widthAdjust = parseInt(Innecto.style.get(storage, "marginRight"));
	Innecto.style.set(
			storage,
			"width",
			this.parent.node.clientWidth - widthAdjust + "px");
	var BoundingRectangle = new Innecto.boundingRectangle(this.parent.node);
	Innecto.style.set(storage, "left", BoundingRectangle.left + "px");
	var bottomAdjust = parseInt(Innecto.style.get(storage, "marginBottom"));
	Innecto.style
			.set(
					storage,
					"top",
					(document.body.clientHeight - BoundingRectangle.bottom <= height ? BoundingRectangle.top - height - bottomAdjust : BoundingRectangle.top + this.parent.node.offsetHeight) + "px");
	Innecto.style.set(storage, "display", "block");
	if (this.count > 10) storage.scrollTop = storage
			.getElementsByTagName("span")[this.selected("index")].offsetTop - parseInt(storage.clientHeight / 2);
	window.setTimeout(function() {
		if (typeof storage != "undefined") {
			function hide() {
				parent.hide.apply(parent);
			}
			
			EventManager.add(storage, "blur", hide);
			storage.focus();
		}
	}, 1);
};

function Text(id, Input) {
	this.parent = Input;
	this.node = Innecto.$(id);
	this.id = id;
	this.constraint = this.node.getAttribute("constraint");
	this.defaultValue = this.node.value;
	this.disabled = this.node.disabled;
	this.maxLength = this.node.getAttribute("maxlength");
	this.minLength = this.node.getAttribute("minlength");
	this.name = this.node.name;
	this.readOnly = this.node.readOnly;
	this.required = this.node.attributes["required"];
	this.size = this.node.getAttribute("size");
	this.unique = this.node.getAttribute("unique");
	this.value = this.node.value;
}

Text.prototype.disable = function() {
	this.disabled = true;
	this.parent.errors.remove(false, this.id);
	var el = this.node.firstChild.firstChild;
	el.contentEditable = false;
	el.tabIndex = -1;
	Innecto.styleClass.add(this.node, "disabled");
};

Text.prototype.enable = function() {
	this.disabled = false;
	var el = this.node.firstChild.firstChild;
	if (!this.readOnly) {
		el.contentEditable = true;
		el.tabIndex = 0;
	}
	Innecto.styleClass.remove(this.node, "disabled");
};

Text.prototype.handleEvents = function(e) {
	this.refresh();
	if (this.disabled) return;
	switch (e.type) {
		case "blur":
			Innecto.styleClass.remove(this.node, "focus");
			var value = this.value;
			if (this.maxLength && value.length > this.maxLength) value = value
					.slice(0, this.maxLength);
			this.setValue(value);
			break;
		case "focus":
			Innecto.styleClass.add(this.node, "focus");
			break;
		case "keydown":
			var backspaceKey = e.keyCode == 8;
			var tabKey = e.keyCode == 9;
			var enterKey = e.keyCode == 13;
			var deleteKey = e.keyCode == 46;
			var noSelection;
			if (window.getSelection) noSelection = window.getSelection().isCollapsed;
			else if (document.selection) noSelection = document.selection.type == "None";
			if (enterKey || noSelection && !backspaceKey && !tabKey && !deleteKey && this.maxLength && this.value.length == this.maxLength) e
					.preventDefault();
			break;
	}
};

Text.prototype.refresh = function() {
	var el = this.node.firstChild.firstChild;
	this.value = typeof el.innerText != "undefined" ? el.innerText : el.textContent;
};

Text.prototype.render = function() {
	if (!Innecto.styleClass.exist(this.node, "control-text")) {
		var classes = Innecto.styleClass.get(this.node);
		if (Innecto.agent.IE && this.node.outerHTML
				.indexOf("size=" + this.size) == -1) this.size = 0;
		this.node.insertAdjacentHTML("beforeBegin", [
			"<div class=\"control-text",
			classes != "" ? " " + classes : "",
			this.required ? " required" : "",
			this.disabled ? " disabled" : "",
			"\"",
			this.constraint ? " constraint=\"" + this.constraint + "\"" : "",
			" defaultvalue=\"",
			this.defaultValue,
			"\" id=\"",
			this.id,
			"\"",
			this.maxLength ? " maxlength=\"" + this.maxLength + "\"" : "",
			this.minLength ? " minlength=\"" + this.minLength + "\"" : "",
			" name=\"",
			this.name,
			"\"",
			this.readOnly ? " readonly" : "",
			this.size ? " size=\"" + this.size + "\"" : "",
			this.unique ? " unique=\"" + this.unique + "\"" : "",
			"><div><div class=\"control-text-value\"></div></div></div>"]
				.join(""));
		var node = this.node.previousSibling;
		this.node.parentNode.removeChild(this.node);
		this.node = node;
		var el = this.node.firstChild.firstChild;
		if (typeof el.hideFocus != "undefined") el.hideFocus = true;
		if (this.size) {
			el.innerHTML = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
			Innecto.style
					.set(
							el,
							"width",
							Math
									.round(this.node.clientWidth / el.innerHTML.length) * (parseInt(this.size) + 1) + "px");
			el.innerHTML = "";
		}
		var LABEL = Sly.search("label[for=" + this.id + "]")[0];
		if (LABEL) {
			LABEL.removeAttribute("for");
			
			function click() {
				this.nextSibling.firstChild.firstChild.focus();
			}
			
			EventManager.add(LABEL, "click", click);
		}
		var parent = this;
		
		function handleEvents(e) {
			parent.handleEvents.apply(parent, [e]);
		}
		
		EventManager.add(el, "blur", handleEvents);
		EventManager.add(el, "focus", handleEvents);
		EventManager.add(el, "keydown", handleEvents);
		if (!this.disabled && !this.readOnly) {
			el.tabIndex = 0;
			el.contentEditable = true;
		}
		if (this.value != "") this.setValue(this.value);
	}
};

Text.prototype.setDefaultValue = function(newValue) {
	this.defaultValue = newValue;
	this.node.setAttribute("defaultvalue", newValue);
};

Text.prototype.setReadOnly = function(trueOrFalse) {
	this.readOnly = trueOrFalse;
	if (trueOrFalse) {
		this.node.setAttribute("readonly", trueOrFalse);
		this.node.firstChild.firstChild.contentEditable = false;
	}
	else {
		this.node.removeAttribute("readonly");
		this.node.firstChild.firstChild.contentEditable = true;
	}
};

Text.prototype.setValue = function(newValue) {
	this.value = newValue;
	var el = this.node.firstChild.firstChild;
	el.innerHTML = "";
	if (newValue != "") {
		var text = document.createTextNode(newValue);
		el.appendChild(text);
	}
};

Text.prototype.type = "text";