function ListBox() {
	var dublicate = this;
	this.createContainer = function(boxType, itemType) {
		return dublicate.fabricContainer[boxType].apply(dublicate, arguments);
	};
	this.createItem = function(itemType) {
		return dublicate.fabricItems[itemType].apply(dublicate, arguments);
	};
}

// ---- default ----

ListBox.prototype._events = function(dublicate) {
	var subscribers = [];
	this.events = {};
	this.events.getAllSubscribers = function() {
		return subscribers;
	};
	this.events.getById = function(strId) {
		if (!strId || strId=="") return false;
		for (var i=0, l=subscribers.length; i<l; i++) {
			if (subscribers[i].id==strId) return subscribers[i];
		}
		return false;
	};
	this.events.remById = function(strId) {
		if (!strId || strId=="") return false;
		for (var i=0, l=subscribers.length; i<l; i++) {
			if (subscribers[i].id==strId) return subscribers.splice(i, 1)[0];
		}
		return false;
	};
	this.events.addSubscriber = function(eventType, mixPath, fnCallBack, objCallBackContext, arrCallBackArguments, strId) {
		if (typeof(fnCallBack)!="function") return false;
		var eventType = eventType || "change";
		var mixPath = mixPath || "";
		if (typeof(mixPath)=="string") {
			if (mixPath=="") {
				mixPath = [""];
			} else {
				mixPath += ".";
				mixPath = mixPath.split(".");
				mixPath.pop();
			}
		}
		if (mixPath.constructor!=Array) return false;
		for (var i=0, l=mixPath.length; i<l; i++) {
			if (mixPath[i]=="") mixPath[i] = "*";
		}
		var objCallBackContext = objCallBackContext || window;
		if (typeof(arrCallBackArguments)=="undefined") {
			var arrCallBackArguments = [];
		} else if (arrCallBackArguments.constructor!=Array) {
			var arrCallBackArguments = [arrCallBackArguments];
		}
		while( typeof(strId)!="string" || dublicate.events.getById(strId) ) {
			var strId = Math.round(Math.random() * 100000).toString();
		}
		subscribers.push({
			id: strId,
			type: eventType,
			path: mixPath,
			fn: fnCallBack,
			context: objCallBackContext,
			args: arrCallBackArguments
		});
		return strId;
	};
	this.events.fireEvent = function(eventType, evtPath, target, oldValue) {
		var evtPath = typeof(evtPath)=="string" ? evtPath.split(".") : evtPath;
		var eventObj = [{ type: eventType, path: evtPath, target: target, oldValue: oldValue }];
		var subs = dublicate.events.getAllSubscribers();
		var res = 0;
		for (var i=0, l=subs.length, path, match; i<l; i++) {
			if (eventType!=subs[i].type) continue;
			path = subs[i].path;
			match = 0;
			while( match<path.length && ( path[match]==evtPath[match] || path[match]=="*" || evtPath[match]=="*" ) ) {
				match++;
			}
			if (match==path.length) {
				subs[i].fn.apply( subs[i].context, eventObj.concat(subs[i].args) );
				res++;
			}
		}
		return res;
	};
}

ListBox.prototype._getters = function(dublicate) {
	var settings = {};

	this.get = function(mix) { return this._getSettings(mix, settings); };

	this._getSettings = function(mix, settings) {
		if (typeof(mix)=="undefined") {
			return settings;
		}
		if (typeof(mix)=="string") {
			if (mix.indexOf(".")>-1) {
				mix = mix.split(".");
			} else {
				return settings[mix];
			}
		}
		if (mix.constructor==Array) {
			var cur = settings;
			for (var i=0, l=mix.length; i<l; i++) {
				cur = cur[mix[i]];
				if (typeof(cur)=="undefined") return;
			}
			return cur;
		}
		return settings;
	};
};

ListBox.prototype._setters = function(dublicate) {
	this.set = function(mix, value) {
		var settings = dublicate.get();
		if (typeof(mix)=="string") {
			if (mix.indexOf(".")>-1) {
				mix = mix.split(".");
			} else {
				settings[mix] = value;
			}
		}
		if (mix.constructor==Array) {
			var cur = settings;
			for (var i=0, l=mix.length-1; i<l; i++) {
				cur = cur[mix[i]];
				if (typeof(cur)=="undefined") return;
			}
			var oldValue = cur[mix[mix.length-1]];
			cur[mix[mix.length-1]] = value;
			dublicate.events.fireEvent("change", mix, dublicate, oldValue);
		}
		return dublicate;
	};
};

ListBox.prototype._container = function(dublicate) {
	ListBox.prototype._getters.call(this, dublicate);
	ListBox.prototype._setters.call(this, dublicate);
	ListBox.prototype._events.call(this, dublicate);

	this.getDefaultItemType = function() {
		return dublicate.get("defaultItemType");
	};
	this.setDefaultItemType = function(str) {
		var settings = this.get();
		settings.defaultItemType = str;
		return dublicate;
	};
	this.getItem = function(position) {
		return dublicate.get("items")[position];
	};
	this.getItemPosition = function(item) {
		var ret = false;
		var arr = dublicate.get("items");
		for (var i=0, l=arr.length; i<l; i++) {
			if (arr[i]==item) return i;
		}
		return;
	};
	this.getAllItems = function() {
		return dublicate.get("items");
	};
	this.addItem = function(item, position) {
		var pos = typeof(position)=="undefined" ? "last" : position;
		var arr = dublicate.get("items");
		if (pos=="first") pos = 0;
		if (pos=="last") pos = dublicate.get("items").length;
		if (dublicate.getItem(pos)) {
			// уже есть Item'ы
			var refNode = dublicate.getItem(pos).getHTML();
			arr.splice(pos, 0, item);
			if (refNode) {
				if (refNode.nextSibling) {
					refNode.parentNode.insertBefore(item.createHTML(true), refNode.nextSibling);
				} else {
					refNode.parentNode.appendChild(item.createHTML(true));
				}
			}
		} else {
			arr.push(item);
			var refNode = dublicate.get("nodes.container");
			if (refNode) {
				refNode.appendChild(item.createHTML(true));
			}
		}
		item.events.addSubscriber( "change", "nodes.item", onItemsContainerChange, dublicate, [item] )
		return dublicate;
	};
	this.remItem = function(position) {
		var item = dublicate.get("items").splice(position, 1);
		item[0].clearHTML();
		return dublicate;
	};
	
	function onItemsContainerChange(evt) {
		if (!evt.oldValue) {
			var pos = dublicate.getItemPosition(evt.target);
			var nodeItem = evt.target.getHTML();
			var cur = pos;
			var refNode;
			while( cur>0 && (!refNode || refNode && !refNode.parentNode) ) refNode = dublicate.getItem(--cur).getHTML();
			if (cur>0 && refNode.nextSibling) {
				refNode = dublicate.getItem(cur).getHTML();
			} else if (c1.get("nodes.container")) {
				c1.get("nodes.container").appendChild(nodeItem);
			}
		}
	}
	
};

ListBox.prototype._item = function(dublicate) {
	ListBox.prototype._getters.call(this, dublicate);
	ListBox.prototype._setters.call(this, dublicate);
	ListBox.prototype._events.call(this, dublicate);
	
	this.getHTML = function(part) {
		return this.get( "nodes." + (part || "item") );
	};
	this.setHTML = function(value, part) {
		dublicate.set( "nodes." + (part || "item"), value );
	};
	this.clearHTML = function() {
		var fn = dublicate.get("misc.DOM");
		fn.rem(dublicate.getHTML());
	};
};

// ---- / default ----

// ---- containers ----

ListBox.prototype.containers = {};

ListBox.prototype.containers.defaultBox = function() {
	var dublicate = this;
	
	ListBox.prototype._container.call(this, dublicate);

	this.set("items", []);
	this.set("nodes", { container: null });

	this.createHTML = function(setToNodes) {
		var fn = dublicate.get("misc.DOM");
		var nodeContainer = fn.cr( "div", "list-box default-list-box list-box-container" );
		if (setToNodes) {
			dublicate.set("nodes.container", nodeContainer);
		};
		return nodeContainer;
	};
	
	this.hide = function() {
		var node = dublicate.get("nodes.container");
		if (node) node.style.display = "none";
	};

	this.show = function() {
		var node = dublicate.get("nodes.container");
		if (node) node.style.display = "block";
	};
};

// ---- / containers ----

// ---- items ----

ListBox.prototype.items = {};

ListBox.prototype.items.textItem = function() {
	var dublicate = this;
	
	ListBox.prototype._item.call(this, dublicate);
	
	this.set("nodes", {item: null});
	this.set("values", {});

	this.createHTML = function(setToNodes) {
		var fn = dublicate.get("misc.DOM");
		var nodeText = fn.cr( "div", "text", dublicate.get("values.text") );
		var nodeItem = fn.cr( "div", "item", nodeText );
		if (setToNodes) {
			dublicate.set("nodes.item", nodeItem);
			dublicate.set("nodes.text", nodeText);
		};
		return nodeItem;
	};
	
	this.events.addSubscriber("change", "values.text", autoupdate, dublicate);
	// (eventType, mixPath, fnCallBack, objCallBackContext, arrCallBackArguments, strId)
	
	function autoupdate() {
		var nodeText = dublicate.get("nodes.text");
		if (!nodeText) {
			dublicate.createHTML(true);
			nodeText = dublicate.get("nodes.text");
		}
		if (nodeText && nodeText.nodeType==1) {
			while(nodeText.firstChild) nodeText.removeChild(nodeText.firstChild);
			nodeText.appendChild( document.createTextNode( dublicate.get("values.text") ) );
		}
	}
};

// ---- / items ----

// ---- fabric ----

ListBox.prototype.fabricContainer = {};
ListBox.prototype.fabricContainer.defaultBox = function(boxType, itemType) {
	// this==listBox
	var ret = new this.containers.defaultBox();
	ret.setDefaultItemType(itemType);
	ret.set("misc", { DOM: this.miscDOM });
	return ret;
};

ListBox.prototype.fabricItems = {};
ListBox.prototype.fabricItems.textItem = function(itemType) {
	// this==listBox
	var ret = new this.items.textItem();
	ret.set("misc", { DOM: this.miscDOM });
	return ret;
};

// ---- / fabric ----

// ---- mics DOM ----
ListBox.prototype.miscDOM = {};
ListBox.prototype.miscDOM.hasClass = function(node, str) {
	return node.className ? (new RegExp("(^|\\s)"+ str +"($|\\s)")).test( node.className ) : false;
};
ListBox.prototype.miscDOM.removeClass = function(node, str, noChangeClassName) {
	var className = node.className || "";
	className = className.replace( new RegExp("(^|\\s)"+ str +"($|\\s)", "g"), " ");
	while( /\s\s/.test(className) ) className = className.replace(/\s\s/g, " ");
	while( /(^)\s/.test(className) ) className = className.replace(/(^)\s/g, "");
	while( /\s($)/.test(className) ) className = className.replace(/\s($)/g, "");
	if (!noChangeClassName) node.className = className;
	return noChangeClassName ? className : node.className;
};
ListBox.prototype.miscDOM.changeClass = function(node, strFrom, strTo, fnRemClass) {
	var className = fnRemClass(node, strFrom, true);
	if (className=="") {
		className = strTo;
	} else {
		className += " "+ strTo;
	}
	node.className = className;
};
ListBox.prototype.miscDOM.getByClass = function(container, strClassName, fnHasClass, RegExp_indexOf_equal) {
	if (!RegExp_indexOf_equal) RegExp_indexOf_equal = "RegExp";
	switch(RegExp_indexOf_equal) {
		case "RegExp":
			var fn = function(node) { return fnHasClass(node, strClassName); };
		break;
		case "indexOf":
			var fn = function(node) { return (node.className || "").indexOf(strClassName)>-1; };
		break;
		case "eq": case "equal": case "==":
			var fn = function(node) { return (node.className || "")==strClassName; };
		break;
	}
	var ret = [];
	var nodes = container.getElementsByTagName("*");
	for (var i=0, l=nodes.length; i<l; i++) {
		if ( fn(nodes[i]) ) ret.push(nodes[i]);
	}
	delete fn;
	delete RegExp_indexOf_equal;
	return ret;
}
ListBox.prototype.miscDOM.rem = function(node) {
	if (!node || node && !node.parentNode) return false;
	return node.parentNode.removeChild(node);
};
ListBox.prototype.miscDOM.cr = function(tagName, className, inner, attrs) {
	var ret = document.createElement(tagName);
	ret.className = className || "";
	if (typeof(inner)!="undefined") {
		if (typeof(inner)=="string") {
			ret.innerHTML = inner;
		} else if (inner.constructor==Array) {
			for (var i=0, l=inner.length; i<l; i++) {
				ret.appendChild(inner[i]);
			}
		} else if (inner.nodeType) {
			ret.appendChild(inner);
		}
	}
	if (typeof(attrs)=="object") {
		for (var name in attrs) {
			ret[name] = attrs[name];
		}
	}
	return ret;
};
ListBox.prototype.miscDOM.event = function(node, type, fn) {
	if (node.addEventListener) {
		node.addEventListener(type, fn, false);
	} else {
		node.attachEvent("on"+type, fn);
	}
}
// ---- / mics DOM ----