// The global array of objects that have been instanciated
if (!Bs_Objects) {var Bs_Objects = [];};



/**
* Editable List Box.
* see http://www.blueshoes.org/en/javascript/editablelistbox/
* 
* <b>Features:</b> 
* - Register events using attachEvent(): onBeforeAdd, onAfterAdd, onBeforeDelete, onAfterDelete
* - Language support (currently en/de, create your own, check the lang folder.)
* 
* <b>How to use:</b>
*  - Have a look at the examples (see examples subfolder)
* 
* <b>Includes (+Dependences):</b>
* <code>
*   <script type="text/javascript" src="/_bsJavascript/core/lang/Bs_Misc.lib.js"></script>
*   <script type="text/javascript" src="/_bsJavascript/lib/browscap.js"></script>
*   <script type="text/javascript" src='/_bsJavascript/core/lang/Bs_Array.class.js'></script>
*   <script type="text/javascript" src='/_bsJavascript/core/lang/Bs_Eventable.class.js'></script>
*   <script type="text/javascript" src='/_bsJavascript/core/lang/Bs_Languageable.class.js'></script>
*   <script type="text/javascript" src='/_bsJavascript/core/form/Bs_FormFieldSelect.class.js'></script>
*   <script type="text/javascript" src="/_bsJavascript/components/editablelistbox/Bs_EditableListBox.class.js"></script>
* </code>
* 
* <b>What is returned to the server:</b>
*  - an array with the string values as the field name specified in fieldName.
*  - more values might be returned with random field names - just ignore them.
* 
* @author     andrej arn <andrej-at-blueshoes-dot-org>
* @package    javascript_components
* @subpackage editablelistbox
* @copyright  blueshoes.org
* @since      bs-4.6
*/
function Bs_EditableListBox() {
	
	/**
	* the name of this class (since it cannot be easily found out).
	* @access public (read only please)
	* @var    string className
	*/
	this.className = 'Bs_EditableListBox';
	
	/**
  * 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 
  */
  this._objectId;
	
	/**
	* the fieldname to use. 
	* 
	* the values will be submitted along with the form using this field name.
	* if not specified then something unique will be made up.
	* 
	* @access public
	* @var    string fieldName
	*/
	this.fieldName;
	
	/**
	* the width of the text and select field.
	* ignored if a css style or class is defined.
	* @access public
	* @var    int width
	*/
	this.width = 200;
	
	/**
	* the number of lines to use in the select field.
	* so it defines the height.
	* ignored if a css style or class is defined.
	* @access public
	* @var    int lines
	*/
	this.lines = 5;
	
	/**
	* if added values should be trimmed.
	* default is true.
	* @access public
	* @var    bool trim
	*/
	this.trim = true;
	
	/**
	* the maximal number of characters of a value.
	* default is 255.
	* @access public
	* @var    int maxLength
	*/
	this.maxLength = 255;
	
	/**
	* the maximal number of elements that can be added.
	* before adding more others have to be deleted.
	* default is -1 which means no limit. 0 means 0.
	* @access public
	* @var    int maxElements
	*/
	this.maxElements = -1;
	
	/**
	* if duplicates should be prevented/removed.
	* 
	*   0 = allow duplicates
	*   1 = no duplicates, unless case is different ('AAA' != 'aaa'). this is the default.
	*   2 = no duplicates, not even if case is different ('AAA' is treated like 'aaa').
	* 
	* @access public
	* @var    int noDuplicates
	*/
	this.noDuplicates = 1;
	
	/**
	* how to sort the values.
	* 
	* possible values:
	*   'addEnd'           => add new values at the end. this is the default.
	*   'addTop'           => add new values at the top.
	*   'alphaDesc'        => alphabetically, descending (a to z).
	*   'alphaAsc'         => alphabetically, ascending (z to a).
	*   'alphaDescNatural' => not supported yet.
	*   'alphaAscNatural'  => not supported yet.
	*   'manual'           => allow manual ordering using up/down buttons. 
	*                         initially the elements are ordered like 'addEnd'. 
	*                         not supported yet.
	* 
	* @access public
	* @var    string sort
	*/
	this.sort = 'addEnd';
	
	/**
	* the values in an array.
	* @access private
	* @var    array _values
	* @see    addValue(), deleteValue()
	*/
	this._values = new Array();
	
	/**
	* state if the component is rendered to the browser yet.
	* @access private
	* @var    bool _isOutrendered
	*/
	this._isOutrendered = false;
	
	/**
	* reference to the text field element.
	* @access public
	* @var    element textField
	*/
	this.textField;
	
	/**
	* reference to the visible select field element.
	* @access public
	* @var    element selectField
	*/
	this.selectField;
	
	/**
	* reference to the hidden select field element.
	* @access public
	* @var    element hiddenField
	*/
	this.hiddenField;
	
	
	/**
	* constructor.
	*/
	this._constructor = function() {
    this._id = Bs_Objects.length;
    Bs_Objects[this._id] = this; 
    this._objectId = "Bs_Elb_"+this._id;
	}
	
	this._initFields = function() {
		this.textField   = document.getElementById(this._objectId+'_text');
		this.selectField = document.getElementById(this._objectId+'_select');
		this.hiddenField = document.getElementById(this._getFieldName());
		var s = new Bs_FormFieldSelect();
		s.init(this.selectField);
		s.init(this.hiddenField);
		//this.selectField.extend(Bs_FormFieldSelect);
		//this.hiddenField.extend(Bs_FormFieldSelect);
		/*
	  for (property in Bs_FormFieldSelect.prototype) {
	    this.selectField.prototype[property] = Bs_FormFieldSelect.prototype[property];
	  }
	  for (property in Bs_FormFieldSelect.prototype) {
	    this.hiddenField.prototype[property] = Bs_FormFieldSelect.prototype[property];
	  }
		*/
    //this.selectField.prototype = Bs_FormFieldSelect.prototype;
    //this.hiddenField.prototype = Bs_FormFieldSelect.prototype;
	}
	
	
	/**
	* adds the value specified.
	* @access public
	* @param  string value
	* @return bool
	*/
	this.addValue = function(value) {
		if (this.trim) {
			value = value.replace(/^\s*/, '');
			value = value.replace(/\s*$/, '');
		}
		if (value == '') return false;
		if (value.length > this.maxLength) {
			//todo: proper error handling.
			alert(this.getInterfaceText('error', 'maxLength', [this.maxLength, value.length]));
			return false;
		}
		
		if ((this.maxElements > -1) && (this._values.length >= this.maxElements)) {
			//todo: proper error handling.
			alert(this.getInterfaceText('error', 'maxElements', [this.maxElements]));
			return false;
		}
		
		if (this.noDuplicates) {
			if (this.noDuplicates == 2) { //case insensitive.
				var valueLower = value.toLowerCase();
				for (var i=0; i<this._values.length; i++) {
					if (this._values[i].toLowerCase() == valueLower) return false;
				}
			} else { //default, 1. case sensitive.
				if (this._values.has(value)) return false;
			}
		}
		
		var status = this.fireEvent('onBeforeAdd', value);
		if (status == false) return false;
		
		this._values[this._values.length] = value;
		if (this._isOutrendered) {
			var newOption = new Option(value, value, false, false);
			this.selectField.options[this.selectField.length] = newOption;
			
			var newOption = new Option(value, value, false, true);
			this.hiddenField.options[this.hiddenField.length] = newOption;
			
			this._sort();
		}
		
		this.fireEvent('onAfterAdd', value);
		
		return true;
	}
	
	
	/**
	* disables or enables the component.
	* call this after the component is rendered.
	* @access public
	* @param  bool bool (true=disable, false=enable, nothing=toggle)
	* @return void
	*/
	this.setDisabled = function(bool) {
		try {
			if (typeof(bool) == 'undefined') {
				bool = !this.selectField.disabled;
			}
			if (bool) {
				//this.toggleButton.setStatus(0);
				this.selectField.disabled = true;
				this.textField.disabled   = true;
			} else {
				//this.toggleButton.setStatus(1);
				this.selectField.disabled = false;
				this.textField.disabled   = false;
			}
		} catch (e) {
		}
	}
	
	
	this._sort = function() {
		switch (this.sort) {
			case 'alphaAsc':
				this.selectField._param1 = false;
				this.selectField.sortByText();
				this.hiddenField._param1 = false;
				this.hiddenField.sortByText();
				break;
			case 'alphaDesc':
				this.selectField._param1 = true;
				this.selectField.sortByText();
				this.hiddenField._param1 = true;
				this.hiddenField.sortByText();
				break;
			case 'addTop':
				//todo
				break;
			case 'alphaAscNatural':
				this.selectField._param1 = false;
				this.selectField._param2 = true;
				this.selectField.sortByText();
				this.hiddenField._param1 = false;
				this.hiddenField._param2 = true;
				this.hiddenField.sortByText();
				break;
			case 'alphaDescNatural':
				this.selectField._param1 = true;
				this.selectField._param2 = true;
				this.selectField.sortByText();
				this.hiddenField._param1 = true;
				this.hiddenField._param2 = true;
				this.hiddenField.sortByText();
				break;
			case 'addEnd':
			case 'manual':
			default:
				//do nothing
		}
		return false;
	}
	
	this.deleteValue = function(value) {
		var status = this.fireEvent('onBeforeDelete', value);
		if (status == false) return false;
		
		//alert(value);
		this.selectField._param1 = value;
		this.selectField.removeElement();
		this.hiddenField._param1 = value;
		this.hiddenField.removeElement();
		
		var killThis = this._values.indexOf(value);
		if (killThis > -1) this._values.deleteItem(killThis);
		
		this.fireEvent('onAfterDelete', value);
		
		return true;
	}
	
	
	this.renderInto = function(tagId) {
		if (is_opera || (is_win && (is_ie || is_gecko))) {
			var out = this.renderAsHtml();
			document.getElementById(tagId).innerHTML = out;
		} else {
			var div = this.renderAsNode();
			//document.getElementById(tagId).innerHTML = '';
			document.getElementById(tagId).appendChild(div);
			document.getElementById(tagId).appendChild(this.renderHiddenFieldAsNode()); //have to add this directly for mac ie5.5 to submit it along with the form.
		}
		
		this._isOutrendered = true;
		this._initFields();
		return true;
	}
	
	
	this.renderAsNode = function(tagId) {
		var _id = this._id;
		
		var div = document.createElement('span');
		
		//text field:
		//var fldText = document.createElement('input');
		var fldText = document.createElement('input');
		//fldText.type = 'text'; //fails in ie5.5 mac and is the default anyway.
		fldText.name = this._objectId + '_text';
		fldText.id   = this._objectId + '_text';
		fldText.value = '';
		fldText.style.width = this.width;
		//fldText.setAttribute('onkeypress', "return Bs_Objects['" + this._id + "'].typing(event);"); //works in moz but not ie.
		//fldText.onkeyup    = "return Bs_Objects['" +this._id  + "']._textKeyUp(event);"; //doesn't work
		//fldText.attachEvent('onkeypress', Bs_Elb_typing); //does not work on mac ie5.5
		var str = "return Bs_Objects['" + this._id + "'].typing(event);";
		//fldText.onkeypress = function() { return Bs_Objects[this._id].typing(event); }
		//fldText.onkeypress = function() { return eval(str); }
		fldText.onkeypress = function() { return Bs_Objects[_id].typing(event); }
		fldText.onkeyup    = function () { return Bs_Objects[_id]._textKeyUp(event); }
		div.appendChild(fldText);
		
		//select field:
		var selDiv = document.createElement('div');
		var fldSelect = document.createElement('select');
		fldSelect.name = this._objectId + '_select[]';
		fldSelect.id = this._objectId + '_select';
		fldSelect.multiple = true;
		fldSelect.size = this.lines;
		fldSelect.style.width = this.width;
		//fldSelect.onkeydown  = "return Bs_Objects['" +this._id+ "']._selectTyping();";
		//fldSelect.ondblclick = "return Bs_Objects['" +this._id+ "']._selectDblClick();";
		fldSelect.onkeydown  = function() { return Bs_Objects[_id]._selectTyping(); }
		fldSelect.ondblclick = function () { return Bs_Objects[_id]._selectDblClick(); }
		for (var i=0; i<this._values.length; i++) {
			var newOption = new Option(this._values[i], this._values[i], false, false);
			fldSelect.options[fldSelect.length] = newOption;
		}
		selDiv.appendChild(fldSelect);
		div.appendChild(selDiv);
		
		//hidden field: (now done differently for mac ie5.5
		//var fldHidden = this.renderHiddenFieldAsNode();
		//div.appendChild(fldHidden);
		
		return div;
	}
	
	
	/**
	* we have to add the hidden select form field directly to the container. 
	* otherwise mac ie5.5 does not submit the form field to the server.
	* seems to be an ie 5.5 mac bug. won't ever be fixed from ms.
	* @access private
	* @return elm (hidden select field as node)
	*/
	this.renderHiddenFieldAsNode = function() {
		var fldHidden = document.createElement('select');
		fldHidden.name = this._getFieldName() + '[]';
		fldHidden.id = this._getFieldName();
		fldHidden.multiple = true; //'true';
		fldHidden.size = 10; //whatever...
		fldHidden.style.display = 'none';
		for (var i=0; i<this._values.length; i++) {
			var newOption = new Option(this._values[i], this._values[i], false, true);
			fldHidden.options[fldHidden.length] = newOption;
		}
		return fldHidden;
	}
	
	
	this.renderAsHtml = function() {
		var out = new Array();
		
		var style = ' style="width:' + this.width + 'px;" ';
		
		//text field:
		out[out.length] = '<div><input type="text" name="'+this._objectId+'_text" id="'+this._objectId+'_text" value="" ' + style + ' onkeypress="return Bs_Objects['+this._id+'].typing(event);" onkeyup="return Bs_Objects['+this._id+']._textKeyUp(event);"></div>';
		
		//select field:
		out[out.length] = '<div><select name="'+this._objectId+'_select[]" id="'+this._objectId+'_select" multiple size="' + this.lines + '" ' + style + ' onkeydown="return Bs_Objects['+this._id+']._selectTyping();" ondblclick="return Bs_Objects['+this._id+']._selectDblClick();">';
		for (var i=0; i<this._values.length; i++) {
			out[out.length] = '<option value="' + this._values[i] + '">' + this._values[i] + '</option>';
		}
		out[out.length] = '</select></div>';
		
		//hidden field:
		var fieldName = this._getFieldName();
		out[out.length] = '<select name="'+fieldName+'[]" id="'+fieldName+'" multiple size="10" style="display:none;">'
		for (var i=0; i<this._values.length; i++) {
			out[out.length] = '<option value="' + this._values[i] + '" selected>' + this._values[i] + '</option>';
		}
		out[out.length] = '</select>';
		
		return out.join('');
	}
	
	
	/**
	* returns the name of the field name that is submitted to the server.
	* @access private
	* @return string
	*/
	this._getFieldName = function() {
		return (typeof(this.fieldName) != 'undefined') ? this.fieldName : this._objectId + '_field';
	}
	
	
	
	/**
	* fires when typing (onkeypress, not onkeydown!) in the text field.
	* @access private
	* @param  object evt (event)
	* @return bool
	*/
	this.typing = function(evt) {
		if (!evt && window.event) evt=window.event;
		
		switch (evt.keyCode) { // evt.which in Netscape 4, 
			case 13: //enter
				if (this.textField.value != '') {
					var status = this.addValue(this.textField.value);
					if (status) {
						this.textField.value = '';
					}
				}
				
				/*
				var keyTab=9,keyEnter = 13;
				if (evt && evt.keyCode==keyEnter) {
					try {
						//alert(evt.keyCode);
						evt.keyCode=keyTab;
						return false;
					} catch (e) {
					}
				}
				*/
				
				return false;
				break;
			default:
				if (this.textField.value.length > this.maxLength) {
					this.textField.value = this.textField.value.substr(0, this.maxLength);
					return false;
				}
		}
		return true;
	}
	
	/**
	* fires when onkeyup in the text field.
	* @access private
	* @param  object evt (event)
	* @return bool
	*/
	this._textKeyUp = function(evt) {
		if (!evt && window.event) evt=window.event;
		if (evt.keyCode == 13) return false; //enter
		
		if (this.textField.value.length > this.maxLength) {
			this.textField.value = this.textField.value.substr(0, this.maxLength);
		}
		return true;
	}
	
	
	this._selectTyping = function() {
		switch (window.event.keyCode) {
			case 13: //enter
				var current = this.selectField.getValueOrText();
				this.textField.value = current;
				this.deleteValue(current);
				return false;
				break;
			case 46: //delete
				var current = this.selectField.getValueOrText();
				this.deleteValue(current);
				return false;
				break;
			default:
		}
		return true;
	}
	
	this._selectDblClick = function() {
		var current = this.selectField.getValueOrText();
		this.textField.value = current;
		this.deleteValue(current);
		return false;
	}
	
	
	this._constructor(); //call the constructor. needs to be at the end.
	
}

Bs_EditableListBox.extend(Bs_Eventable);
Bs_EditableListBox.extend(Bs_Languageable);



