function SelectFeeder() {
	var watched = {};
	var cache = {};
	var fixed = [];
	var hook = function(object, event, callback, phase) {
		object.addEventListener(event, callback, phase);
	};
	if (typeof window.addEventListener == 'undefined') {
		hook = function(object, event, callback, phase) {
			object.attachEvent('on' + event, callback );
		};
	};
	
	function XHR () {
		if (window.XMLHttpRequest)     // Object of the current windows
			return xhr = new XMLHttpRequest();     // Firefox, Safari, ...
		else if (window.ActiveXObject)   // ActiveX version
			return new ActiveXObject("Microsoft.XMLHTTP");  // Internet Explorer
		return null;
	}
	
	// Call callback with JSON corresponding to selected values
	function broccoli(callback, preselect) {
		var xhr = XHR();
		var key = '';
		xhr.onreadystatechange = function() {
			if (xhr.readyState == 4) {
				var json = eval('(' + xhr.responseText + ')');
				cache[key] = json
				callback(json);
			};
		};
		if (!preselect) {
			key = fixed.join('_');
			for (vok in watched) {
				voc = watched[vok];
				if (voc.element.value) {
					if (key) key += '_';
					key += String(voc.element.value);
				}				
			}
		} else
			key = preselect.replace(/,/g, '_') 
			
		if (typeof cache[key] == 'undefined') {
			var req = key.replace(/_/g, ',') 
			xhr.open('GET', '/feed/broccoli.json?' + req, true);
			xhr.send(null);
		}
		else {
			callback(cache[key]);
		}
	}
	
	// Empty select box, insert 'any' item
	function select_prune() {
		while (this.element.length)
			this.element.remove(0);
		this.append({tid: 0, name: this.any});
	}
	
	// Add option to select
	function select_append(option) {
		var opt = document.createElement('option');
		opt.text = option.name;
		opt.value = option.tid; 
		try {
			this.element.add(opt, null);
		} catch (e) {
			this.element.add(opt);
		}
	}
	
	// BTW, it's not select method
	function select_preselect(preselect) {
		var ps = preselect.split(',');
		var voc;
		var i, j;
		for (vok in watched) {
			voc = watched[vok].element;
			try {
				for (i=0; i<voc.children.length; i++)
					for (j=0; j<ps.length; j++)
						if (ps[j] == voc.children[i].value) {
							voc.value = ps[j];
							throw new Exception();
						}
			} catch (e) {}
		}
	}
	
	// REFRESHED
	function refresh(preselect) {
		broccoli(function (json) {
			var i;
			var visible;
			var select;
			var v;
			for (voc in json) {
				if (typeof(watched[voc]) == 'undefined')
					continue
				visible = json[voc];
				select = watched[voc];
				v = select.element.value || 0;
				select.prune();
				for (i=0; i<visible.length; i++)
					select.append(visible[i]);
				select.element.value = v;
			}
			if (preselect)
				select_preselect(preselect);
			for (vok in watched)
				if (watched[vok].onupdate)
					watched[vok].onupdate();
		}, preselect);
	}
	
	/*function caller(x) {
		if (typeof x != undefined && x.tagName)
			return x;
		return window.event.srcElement
	}*/

	function watch(id, element, any, onupdate) {
		watched[id] = {element: element, any: any, prune: select_prune, append: select_append, onupdate: onupdate};
		hook(element, 'change', function () { /* avoid event object */ refresh(); }, false);
	}
	
	function addFixed(tid) {
		fixed.push(tid);
	}
	
	return {
		refresh: refresh,
		watch: watch,
		addFixed: addFixed
	};
}
