
// The global array of objects that have been instanciated
if (!Bs_Objects) {var Bs_Objects = [];};

function Bs_Mls_updateMe(e) {
	if (!e) e = window.event;
	e.srcElement.Bs_Mls_Obj.updateMe(e.srcElement.Bs_Mls_Level);
}


function bs_isEmpty(o) {
    var i, v;
    if (typeOf(o) === 'object') {
        for (i in o) {
            v = o[i];
            if (v !== undefined && typeOf(v) !== 'function') {
                return false;
            }
        }
    }
    return true;
}

/**
* JavaScript MultiLevelSelector Component.
* 
* 
* <b>Features:</b> 
* - use any number of levels
* - use your custom design and css. just place your select fields into your html document 
*   like in the examples, give them an id, and init your Bs_MultiLevelSelector instance 
*   with their id's.
* - options can be loaded on demand from the server (see example 3).
* 
* <b>Includes (+Dependences):</b>
* <code>
*   <script type="text/javascript" src="/_bsJavascript/core/lang/Bs_Misc.lib.js"></script>
*   <script type="text/javascript" src="/_bsJavascript/core/form/Bs_FormFieldSelect.class.js"></script>
*   
*   if the load-on-demand feature is used like in example 3:
*   <script type="text/javascript" src="/_bsJavascript/plugins/jsrs/JsrsCore.lib.js"></script>
* </code>
* 
* <b>How to use:</b>
* - Have a look at the examples (see example link below)
* 
* <b>What is returned to the server:</b>
* - the selected elements of all select fields (of all levels). it's all standard html.
* 
* 
* @example components/multilevelselector/examples/example1.php Simple example with 2 levels.
* @example components/multilevelselector/examples/example2.php Simple example with 3 levels.
* @example components/multilevelselector/examples/example3.php Example using load on demand (jsrs).
* 
* @author     andrej arn <andrej-at-blueshoes-dot-org>
* @package    javascript_components
* @subpackage multilevelselector
* @copyright  blueshoes.org
* @since      bs-4.6
*/
function Bs_MultiLevelSelector() {
	
	/**
  * Unique Object/Tag ID is initialized in the constuctor.
  * Based on this._id. Can be used in genarated JS-code as ID. Is set together 
  * from the classname + this._id (see _constructor() code).
  *
  * @access private
  * @var    string 
	* @see    _constructor()
  */
  this._objectId;
	
	/**
	* unique id.
  * @access private
  * @var  int 
	* @see    _constructor()
  */
	this._id;
	
	
	/**
	* how many levels we have.
	* @access private
	* @var    int _levels
	*/
	this._levels = 0;
	
	
	/**
	* the data.
	* @access private
	* @var    array _data
	* @see    setData()
	*/
	this._data = new Array();
	
	
	/**
	* @access private
	* @var    array _fieldElements
	* @see    getFieldElement()
	*/
	this._fieldElements = new Array();
	
	
	/**
	* full ajax url on the server.
	* check example3 to see how this works.
	* @access public
	* @var    string jsrsUrl
	* @see    var jsrsFunction
	*/
	this.jsrsUrl;
	
	/**
	* general jsrs function.
	* can be overridden for some levels in initLevelByExistingField(), see third parameter.
	* @access public
	* @var    ? jsrsFunction
	* @see    var jsrsUrl
	*/
	this.jsrsFunction;
	
	/**
	* JavaScript function to execute on double click.
	* @access public
	* @var    mixed onDblClick (js function or string of js function name in global scope)
	*/
	this.onDblClick;
	
	/**
	* used for jsrs calls.
	* @access private
	* @var    array _jsrsIssuedCalls
	*/
	this._jsrsIssuedCalls = new Array();
	
	this._dynamicRendering = false;
	this._renderElm;
	
	
	/**
	* sets the data for all the options of the select fields.
	* @access public
	* @param  array data
	* @return void
	*/
	this.setData = function(data) {
		if (typeof(data.children) == 'undefined') {
			data = {children:data}
		}
		//this._levels = this._countLevels(data);
		this._data = data;
	}
	
	/**
	* counts the deepness of the data. how many levels down it goes. 
	* how many select fields there need to be.
	* @access private
	* @param  array data
	* @param  int i (used internally only)
	* @return int
	*/
	this._countLevels = function(data, i) {
		if (typeof(i) == 'undefined') i = 0;
		i++;
		var oldI = i;
		
		for (key in data.children) {
			if ((typeof(data.children[key]['children']) != 'undefined') && (typeof(data.children[key]['children']) == 'object')) {
 				var newI = this._countLevels(data.children[key]['children'], oldI);
				if (newI > i) i = newI;
			}
		}
		
		return i;
	}
	
	/**
	* Returns a reference to the field element specified by number.
	* @access public
	* @param  int i
	* @return element
	* @throws bool false (no such element)
	* @see    var _fieldElements
	*/
	this.getFieldElement = function(i) {
		if (typeof(this._fieldElements[i]) == 'undefined') return false;
		return this._fieldElements[i]['elm'];
	}
	
	
	/**
	* 
	* @access public
	* @param  int level (1-n)
	* @param  string fieldElementId
	* @param  string loadOptionsJsrsFunction
	* @return bool
	*/
	this.initLevelByExistingField = function(level, fieldElementId, loadOptionsJsrsFunction) {
		var elm = document.getElementById(fieldElementId);
		var s = new Bs_FormFieldSelect();
		s.init(elm);
		
		this._fieldElements[level] = new Array();
		this._fieldElements[level]['elm'] = elm;
		if (typeof(loadOptionsJsrsFunction) != 'undefined') this._fieldElements[level]['loadOptionsJsrsFunction'] = loadOptionsJsrsFunction;
		
		if (level > this._levels) this._levels = level;
	}
	
	
	/**
	* starts the whole thing after everything is set and ready.
	* @access public
	* @return bool
	*/
	this.render = function(elmId) {
		//if ((typeof(this._data.length) != 'object')          || (this._data.length == 0)) return false;          //no data
		//if ((typeof(this._fieldElements.length) != 'object') || (this._fieldElements.length == 0)) return false; //no fields
		
		if (elmId) {
			this._renderElm = document.getElementById(elmId);
			this._dynamicRendering = true;
			this._renderElement(1, this._data);
		} else {
			for (var i=1; i<this._fieldElements.length; i++) {
				elm = this._fieldElements[i]['elm'];
				elm.Bs_Mls_Obj   = this;
				elm.Bs_Mls_Level = i;
				//elm.attachEvent('onclick', 'Bs_Objects['+this._id+'].updateMe(2);');
				//elm.attachEvent('onclick', Bs_Mls_updateMe);
				elm.attachEvent('onchange', Bs_Mls_updateMe);
			}
		}
		
		var elm = this._fieldElements[1]['elm'];
		elm.Bs_Mls_data = this._data;
		
		elm.prune();
		
		var hasSelected = false;
		for (key in this._data.children) {
			//elm.addElementsByHash();
			/*
			var selected = ((typeof(this._data[key]['selected']) != 'undefined') && (this._data[key]['selected']));
			var caption  = (typeof(this._data[key]['caption']) != 'undefined') ? this._data[key]['caption'] : key;
			var newOption = new Option(caption, key, false, selected);
			elm.options[elm.length] = newOption;
			*/
			//var data = this._data;
			if (typeof(this._data.children[key]) == 'object') {
				var selected = ((typeof(this._data.children[key]['selected']) != 'undefined') && (this._data.children[key]['selected']));
				var caption  = (typeof(this._data.children[key]['caption']) != 'undefined') ? this._data.children[key]['caption'] : key;
			} else if (typeof(this._data.children[key]) == 'string') {
				var selected = false;
				var caption  = this._data.children[key];
			} else {
				//murphy
				var selected = false;
				var caption  = key;
			}
			var newOption = new Option(caption, key, false, selected);
			elm.options[elm.length] = newOption;
			if (selected) hasSelected = true;
		}
		if (!hasSelected) {
			//ie bug :-(
			elm.selectedIndex = -1;
		}
		
		this.updateMe(1);
	}
	
	/**
	* (re)renders the element. 
	* note: if the data is empty and we use dynamic rendering then the element will be removed.
	* @access private
	* @param  int level
	* @param  array data
	* @return void
	*/
	this._renderElement = function(level, data) {
		var newField = document.createElement('select'); //<select name="foo" id="foo" style="" class=""></select>')
		var fldId = this._objectId + '_' + level;
		newField.setAttribute('name', fldId);
		newField.setAttribute('id', fldId);
		//newField.setAttribute('multiple', true);
		newField.setAttribute('size', 8);
		// onDblClick:"xxx", cssClass:"europe",
		if (this.onDblClick) {
			if (typeof(this.onDblClick) == 'string') {
				newField.ondblclick = window[this.onDblClick];
			} else {
				newField.ondblclick = this.onDblClick;
			}
		}
		if (data) {
			if (data.cssClass)   newField.className  = data.cssClass; //'europe';
			if (data.onDblClick) {
				if (typeof(data.onDblClick) == 'string') { //happens when coming from serverside scripting. could check for () and ;
					newField.ondblclick = window[data.onDblClick];
				} else {
					newField.ondblclick = data.onDblClick;
				}
			}
		}
		
		this._renderElm.appendChild(newField); //insertBefore(newRadioButton);
		this.initLevelByExistingField(level, fldId); //, loadOptionsJsrsFunction
		/*
		class
		style
		onclick
		ondblclick
		*/
		this._fieldElements[level]['elm'].Bs_Mls_Obj   = this;
		this._fieldElements[level]['elm'].Bs_Mls_Level = level;
		this._fieldElements[level]['elm'].attachEvent('onchange', Bs_Mls_updateMe);
	}
	
	/**
	* removes the element of the level specified, happens for example 
	* when going to another area of the tree with lesser children.
	* @access private
	* @param  int level
	* @return bool (success)
	*/
	this._removeElement = function(level) {
		var fldId = this._objectId + '_' + level;
		var fld = document.getElementById(fldId);
		if (fld) {
			this._renderElm.removeChild(fld);
			delete this._fieldElements[level];
			return true;
		} else {
			return false;
		}
	}
	
	/**
	* @access public
	* @param  int level
	* @return ?
	*/
	this.updateMe = function(level) {
		if (typeof(this._fieldElements[level]) == 'undefined') {
			//happens with dynamic rendering and different number of levels based on what's clicked.
			return;
		}
		
		var elmSelf = this._fieldElements[level]['elm'];
		var value   = elmSelf.getValue();
		
		if ((typeof(elmSelf.Bs_Mls_data) != 'undefined') && (typeof(elmSelf.Bs_Mls_data.children) != 'undefined')) {
			//dump(elmSelf.Bs_Mls_data.children);
			for (key in elmSelf.Bs_Mls_data.children) {
				if (typeof(elmSelf.Bs_Mls_data.children[key]) == 'string') {
					var tmp = elmSelf.Bs_Mls_data.children[key];
					elmSelf.Bs_Mls_data.children[key] = new Array();
					elmSelf.Bs_Mls_data.children[key]['caption'] = tmp;
				}
				elmSelf.Bs_Mls_data.children[key]['selected'] = false;
			}
			if (value != 'undefined') {
				elmSelf.Bs_Mls_data.children[value]['selected'] = true;
			}
		}
		
		var loopI = level;
		do {
			loopI++;
			if (typeof(this._fieldElements[loopI]) != 'undefined') {
				this._fieldElements[loopI]['elm'].prune();
			} else {
				break;
			}
		} while (true);
		
		if (value != 'undefined') {
			if (typeof(elmSelf.Bs_Mls_data.children[value].children) == 'undefined') {
				//have to load that first, and then will call updateMe() again.
				if (this.jsrsUrl && ((typeof(this._fieldElements[level+1]) != 'undefined')) || this._dynamicRendering) {
					if (typeof(jsrsCall) == 'undefined') {
						//webmaster error:
						alert('Webmaster: you need to include the file plugins/jsrs/JsrsCore.lib.js in your page.');
					} else {
						this._loadOptionsFromServer(level +1, value, elmSelf);
					}
				}
			} else {
				var data = elmSelf.Bs_Mls_data.children[value]; //this._data[value].children;
				
				if (this._dynamicRendering) {
					if (value != 'undefined') {
						//have to remove all lower levels, example: if up to level 5 is open and 1 is clicked then 2-5 have to be hidden.
						for (var j=level+1; j<level+100; j++) { //prevent endless loop
							if (!this._removeElement(j)) {
								break;
							}
						}
						if (typeof(this._fieldElements[level +1]) == 'undefined') {
							//note: we either need such data available or use jsrs. so if it remains empty we have to remove it again.
							this._renderElement(level+1, elmSelf.Bs_Mls_data.children[value]);
						}
					}
				}
				if (level >= this._levels) {
					//return;
				} else {
					var elmChild   = this._fieldElements[level +1]['elm'];
	
					var hasSelected = false;
					elmChild.Bs_Mls_data = data; //set a reference so we can modify that later on.
					for (key in data.children) {
						if (typeof(data.children[key]) == 'object') {
							var selected = ((typeof(data.children[key]['selected']) != 'undefined') && (data.children[key]['selected']));
							var caption  = (typeof(data.children[key]['caption']) != 'undefined') ? data.children[key]['caption'] : key;
						} else if (typeof(data.children[key]) == 'string') {
							var selected = false;
							var caption  = data.children[key];
						} else {
							//murphy
							var selected = false;
							var caption  = key;
						}
						if (selected) hasSelected = true;
						var newOption = new Option(caption, key, false, selected);
						elmChild.options[elmChild.length] = newOption;
					}
					if (!hasSelected) {
						//ie bug :-(
						elmChild.selectedIndex = 0;
					}
				}
			}
		}
		
		/*
		if (this._dynamicRendering && (elmChild.options.length == 0)) {
			this._removeElement(level+1);
		}*/
		
		//if (this._dynamicRendering && (level < this._levels)) this.updateMe(level +1);
		//if (!this._dynamicRendering && (level < this._levels)) this.updateMe(level +1);
		if (level < this._levels) this.updateMe(level +1);
	}
	
	/**
	* @access private
	* @param  ? level
	* @param  ? key
	* @param  element elm
	* @return void
	*/
	this._loadOptionsFromServer = function(level, key, elm) {
		//jsrsContextProp.useGet = false; //otherwise äöü etc are fucked. hrm they still are.
		if ((typeof(this._fieldElements[level]) != 'undefined') && (typeof(this._fieldElements[level]['loadOptionsJsrsFunction']) != 'undefined')) {
			var functionName = this._fieldElements[level]['loadOptionsJsrsFunction'];
		} else {
			var functionName = this.jsrsFunction;
		}
 	  var callId    = jsrsCall(this.jsrsUrl, 'Bs_Objects['+this._id+']' + "._callbackLoadOptionsFromServer", functionName, key);
   	this._jsrsIssuedCalls[callId] = new Array(level, key, elm);
	}
	
  /**
  * is called automatically once the jsrs request to the server returns.
	* @access private
	* @param  ? value
	* @param  ? callId
	* @return void
  * @see this._loadOptionsFromServer()
  */
  this._callbackLoadOptionsFromServer = function(value, callId) {
		var level = this._jsrsIssuedCalls[callId][0];
		var key   = this._jsrsIssuedCalls[callId][1];
		var elm   = this._jsrsIssuedCalls[callId][2];
		//elm.Bs_Mls_data = value;
		elm.Bs_Mls_data.children[key].children = value;
		if (this._dynamicRendering && bs_isEmpty(value)) {
			//dump(value);
			this._removeElement(level);
		} else {
			this.updateMe(level -1);
		}
  }
	
	
	/**
	* the pseudo constructor.
	* @access private
	* @return void
	*/
	this._constructor = function() {
  	// Put this instance into the global object instance list
    this._id = Bs_Objects.length;
    Bs_Objects[this._id] = this; 
    this._objectId = "Bs_Mls_"+this._id;
	}
	
	
	this._constructor(); //call the constructor. needs to be at the end.
	
	
}

