/* Ajax.DoubleCombo - based on Prototype and Scriptaculous
 * Author: Colin Mollenhour
 * Date: March 4, 2006
 * License: Free to use, modify, distribute as long as proper credit is given where due.
 * Usage:
 * new Ajax.DoubleCombo(
 * 	master select id or element,
 * 	slave select id or element,
 * 	Ajax.Request page url,
 * 	{
 * 		paramName: 'selectedIndex' The name of the parameter to pass when making the Ajax.Request call
 * 		parameters: '' Additional parameters to pass to the Ajax.Request call
 * 		sendValue: false (boolean) Send the "value" of the selected option in the Ajax.Request call
 * 		sendInnerHTML: false (boolean) Send the innerHTML of the selected option in the Ajax.Request call
 * 		voidValue: null (literal or array) value(s) to consider void when selected in the master select.
 * 			void values cause onVoid to be called and clearSlaves	if enabled (see "clearSlaves")
 * 		voidIndex: null (literal or array) Similar to voidValues, only using the selectedIndex of the master select
 * 		clearSlaves: true (boolean) Clear contents of the slave and show only "disabledText" on void values. Recurses through
 * 			slaves if specified as master in another Ajax.DoubleCombo object.
 * 		highlightcolor: '#FFFF99' Used by this.doHighlight, called by default in onComplete (can be overridden)
 * 		highlightendcolor: '#FFFFFF' Used by this.doHighlight, called by default in onComplete (can be overridden)
 * 		disabledText: '---' Text displayed in first and only option in the slave when a void value is selected in the master
 * 		fetchingText: 'Fetching options...' Text displayed in slave during Ajax.Request call
 * 		ajaxOptions: {} Additional option to pass to the Ajax.Request call
 * 		callback: function(master, options) Alternate function used to build parameters. If returns true, default parameters are also applied
 * 		onComplete: function(xhr,obj) Additional onComplete actions for the Ajax.Request call. Called last. Default calls doHighlight and slave.focus()
 * 		onLoading: function() Additional onLoading actions. Called right before the Ajax.Request call.
 * 		onFailure: function(xhr) Called by the Ajax.Request on failure
 * 		onVoid: function() Additional actions to take if a void value is selected
 * 	});
 */

function json_decode(txt){
	try{ return eval('('+txt+')'); }catch(ex){}
}

Ajax.DoubleCombo = Class.create();
Ajax.DoubleCombo.prototype = {
	initialize: function(master, slave, url, options) {
		this.master = $(master);
		this.slave = $(slave);
		this.url = url;
	 	this.master.mySlave = this.slave;
		this.slave.myMaster = this.master;
		//get original disabled bg color
		var dis = this.slave.disabled;
		this.slave.disabled = true;
		this.originalBackground = Element.getStyle(this.slave,'background-color');
		if(!dis){ this.slave.disabled = false; }
		this.options = Object.extend({
			paramNameIndex: 'selectedIndex',
            paramNameValue: 'value',
            paramNameId: 'id',
            paramNameInnerHTML: 'text',
            parameters: '',
            sendIndex: true,
            sendId: true,
            sendValue: true,
			sendInnerHTML: false,
			voidValue: null,
			voidIndex: null,
			clearSlaves: true,
			highlightcolor: '#FFFF99',
			highlightendcolor: '#FFFFFF',
			disabledText: '---',
			fetchingText: 'Fetching options...',
			ajaxOptions: {},
			callback: function(){ return false; },
			onComplete: function(xhr,obj){
				this.doHighlight(this.slave);
				this.slave.focus();
			},
			onLoading: false,
			onFailure: function(transport) {
				alert("Error while fetching options from server.");
			},
			onVoid: false
		}, options || {} );

		this.slave.disabledText = this.options.disabledText;
		if(this.options.clearSlaves){ this.clearSlaves(); }

		this.onchangeListener = this.onChange.bindAsEventListener(this);
		Event.observe(this.master, 'change', this.onchangeListener);
	},
	buildParams: function() {
		var idx = this.master.selectedIndex;
		var params = this.options.parameters;
        if(this.options.sendId){ params += (this.master.id && this.master.id !== "" ? "&"+this.options.paramNameId+"="+this.master.id:""); }
        if(this.options.sendIndex){ params += "&"+this.options.paramNameIndex+"="+idx; }
		if(this.options.sendValue){ params += "&"+this.options.paramNameValue+"="+this.master.options[idx].value; }
		if(this.options.sendInnerHTML){ params += "&"+this.options.paramNameInnerHTML+"="+this.master.options[idx].innerHTML; }
		return params;
	},
	clearSlaves: function() {
		var nextSlave = this.slave || false;
		while(nextSlave){
			nextSlave.options.length = 0;
			nextSlave.disabled = true;
			nextSlave.options[0] = new Option(nextSlave.disabledText);
			nextSlave.style.backgroundColor = this.originalBackground;
			nextSlave = nextSlave.mySlave || false;
		}
	},
	dispose: function(){
		Event.stopObserving(this.master, 'change', this.onchangeListener);
	},
	doHighlight: function(element){
		var newEffect = new Effect.Highlight(element, {startcolor: this.options.highlightcolor, endcolor: this.options.highlightendcolor, restorecolor: this.options.highlightendcolor });
	},
	onChange: function() {
		if(this.fetching){ return; }
		var idx = this.master.selectedIndex;
		var val = this.master.options[idx].value;

		//check for either string, int or array for voidValue and voidIndex
		if( this.options.voidValue !== null &&
		((typeof this.options.voidValue == 'object' &&
			this.options.voidValue.indexOf(val) != -1 ) ||
			(this.options.voidValue == val) )
		){
			this.onVoid();
			return;
		}
		if( this.options.voidIndex !== null &&
		((typeof this.options.voidIndex == 'object' &&
			this.options.voidIndex.indexOf(idx) != -1 ) ||
			(this.options.voidIndex == idx) )
		){
			this.onVoid();
			return;
		}
		this.fetching = true;
		var params = this.options.callback.call(this, this.master, this.options) || this.buildParams();
		this.onLoading();
		if(this.options.cache){
			fillSelectCached(this.slave,params,this.url,Object.extend({
				parameters: params,
				onComplete:this.onComplete.bind(this),
				onFailure: this.onFailure.bind(this)},
				this.options.ajaxOptions ));
		}else{
			var newAjax = new Ajax.Request( this.url, Object.extend({
				parameters: params,
				onComplete: this.onComplete.bind(this),
				onFailure: this.onFailure.bind(this)},
				this.options.ajaxOptions ));
		}
	},
	onComplete: function(xhr,json) {        
        this.fetching = false;
        var obj = (json && json.options ? json : json_decode(xhr.responseText));

        if(!obj.options){ return false; }

        this.slave.disabled = false;
		this.slave.options.length = 0;
		var sel = obj.selected || false;
		var opts = obj.options;        	
        for( var i=0; i<opts.length; i++ ){
            this.slave.options[i] = new Option(opts[i].text,opts[i].value,null,(sel&&sel==opts[i].value?true:false));
		}
		if( obj.selectedIndex ){ this.slave.selectedIndex = obj.selectedIndex; }
		this.options.onComplete.call(this, xhr, json);
	},
	onFailure: function(transport) {
		this.options.onFailure.call(this, transport);
	},
	onLoading: function() {
		this.clearSlaves();
		this.slave.options[0] = new Option(this.options.fetchingText);
		if(this.options.onLoading){ this.options.onLoading.call(this); }
	},
	onVoid: function() {
		if(this.options.clearSlaves){ this.clearSlaves(); }
		if(this.options.onVoid){ this.options.onVoid.call(this); }
	}
};