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

/**
* unfortunately, this internal wrapper function needs to be in the global scope.
*/
function bs_radio_onClickLabel() {
  //this is cheap code. if the label has cascaded tags, for example images and divs, 
  //the event.srcElement won't be the label directly. have to walk up to the 
  //label tag. should implement a recursive call here. but works for 99% of the cases.
  var srcElm = event.srcElement;
  if (typeof(srcElm.forValue) == 'undefined') {
    srcElm = srcElm.parentElement;
    if (typeof(srcElm.forValue) == 'undefined') {
      srcElm = srcElm.parentElement;
      if (typeof(srcElm.forValue) == 'undefined') {
        return;
      }
    }
  }
	srcElm.bsRadioObj.onClick(srcElm.forValue);
}



/**
* Radio Field. 
* check the website at http://www.blueshoes.org/en/javascript/radio/
* 
* 
* <b>Event-Handling</b> (see {@link attachEvent()}):
* - You can attach code or a callback function for certain events onto this component
*   (events are things like onClick, onChange ... aso) See attachEvent() for a list 
*   of supported events and more details.
* 
* 
* @dependencies Bs_Misc.lib.js
* @author       andrej arn <andrej-at-blueshoes-dot-org>
* @package      javascript_components
* @subpackage   radio
* @copyright    blueshoes.org
* 
* @param string objectName
*/
function Bs_Radio() {
  
	/**
  * Unique Object/Tag ID is initialized in the constuctor.
  * Bassed on this._id. Can be used in genarate JS-code as ID. Is set together 
  * from the  classname + this._id (see _constructor() code ).
  *
  * @access private
  * @var  string 
  */
  this._objectId;

  /**
  * the options of this radio button.
  * you may directly overwrite this var with your own array, or 
  * use this.addOption() to add one option after another.
  * 
  * data structure:
  *   hash where the key is the value of the options, and the value 
  *   is another hash with the keys:
  *   caption
  *   hide
  *   activateable
  *   deactivateable
  *   furtherOptions
  * 
  * @access public
  * var     object options (hash)
  * @see    this.addOption()
  */
  this.options = new Object();
  
  /**
  * the value of the currently checked option.
	* null for no value.
	* 
  * @access public (setting it after rendering makes no sense.)
  * @var    mixed value (string, may be numeric if you like, just treated as string. null if no value.)
	* @see    getValue()
  */
  this.value = null;
  
  /**
  * no change can be made.
  * if set to true then this overwrites the settings of all options.
  * @access public
  * @var    bool disabled (default is false)
  * @see    var guiNochange
  * @todo   implement this completely
  */
  this.disabled = false;
  
  /**
  * if set to true then the user cannot change things by clicking. 
  * changes through the api functions (for the coder, you) are still possible.
  * if set to true then this overwrites the settings of all options.
  * @access public
  * @var    bool guiNochange
  * @see    var disabled
  * @todo   implement this completely
  */
  this.guiNochange = false;
  
  /**
  * should it be possible to remove the selection from all options?
  * if so, set this to true. then a click on an already selected option 
  * will remove the selection. 
  * @access public
  * @var    bool allowNoSelection
  * @since  bs4.4
  */
  this.allowNoSelection = false;
  
  /**
  * name of the radio form field. if not set until draw() is called then one 
  * will be made up.
  * @var string radioFieldName
  */
  this.radioFieldName;
  
  /**
  * directory where the images are. feel free to create your own 
  * images, put them into another directory (by using the same 
  * file names, etc.)
  * @access public
  * @var    string imgDir
  */
  this.imgDir = '/_bsJavascript/components/radio/img/bobby/';
  
  /**
  * the image type, 'gif' (default), 'jpg', 'png' or whatever.
  * @access public
  * @var    string imgType
  * @since  bs4.5
  */
  this.imgType = 'gif';
  
  /**
  * the width of the radio icon.
  * @access public
  * @var    int imgWidth
  * @see    vars this.imgDir, this.imgHeight
  */
  this.imgWidth  = '12';
  
  /**
  * the height of the radio icon.
  * @access public
  * @var    int imgHeight
  * @see    vars this.imgDir, this.imgWidth
  */
  this.imgHeight = '12';
  
  /**
  * if set to true then the caption will be used as title text only 
  * on the radio icons.
  * 
  * in bs4.5 this has been renamed from captionAsAltText, but it's backward compatible.
  * 
  * @access public
  * @var    bool captionAsTitleText (default is false)
  */
  this.captionAsTitleText = false;
  
  /**
  * the css class to use. not specified = none.
  * if you want different classes for different options, add some functionality 
  * to this code. :-)
  * @access public
  * @var    string cssClass
  * @see    var this.cssStyle
  */
  this.cssClass;
  
  /**
  * the css style to use. not specified = none.
  * if you want different styles for different options, add some functionality 
  * to this code. :-)
  * @access public
  * @var    string cssStyle
  * @see    var this.cssClass
  */
  this.cssStyle;
  
  
  /**
  * you may use the built in onMouseOver event to switch the icon.
  * 
  * note: if you use this one, it's wise to create all icons, even 
  *       those which are never used. consider just using spacer-gif's for them.
  *       because if an icon does not exist, the server returns a 404 not found. 
  *       and then the client (the users browser) goes and re-requests those 
  *       files again and again, on each click (radio status change), to see 
  *       if they're now available, even if never used. 
  *       btw, i'm speaking of the "inactive" icons.
  * 
  * this.iconType has to be 'image', otherwise it makes no sense.
  * 
  * @access public
  * @var    bool $useMouseover
  * @todo   implement this
  */
  this.useMouseover = false;
  
  /**
  * pixelate the available options that are not currently selected?
  * uses microsoft filters, thus for ie only.
  * set to a number, eg 10, play with the values.
  * 
  * see example 6: 
  * http://www.blueshoes.org/_bsJavascript/components/radio/examples/example6.html
  * 
  * this.iconType has to be 'css', otherwise it makes no sense.
  * 
  * @access public
  * @var    int pixelate
  * @status experimental
  * @since  bs4.5
  */
  this.pixelate = 0;
  
  /**
  * inverts the currently active icon.
  * uses microsoft filters, thus for ie only.
  * 
  * see example 6: 
  * http://www.blueshoes.org/_bsJavascript/components/radio/examples/example6.html
  * 
  * this.iconType has to be 'css', otherwise it makes no sense.
  * 
  * @access public
  * @var    bool invertActive
  * @status experimental
  * @since  bs4.5
  */
  this.invertActive = false;
  
  /**
  * how the different radio icons should be made. 
  * 
  *   'image' => default, old fashion: for every mode there is an image. 
  *   'css'   => styles: only one image, and 'active', disabled' etc are done using css.
  * 
  * when using css, the disabled mode only works (visually) in ie because moz does not 
  * support the 'filters'.
  * 
  * @access public
  * @var    string iconType
  * @since bs4.5
  */
  this.iconType = 'image';
  
  
  /**
  * array holding all the information about attached events. 
  * the structure can be like these:
  * <pre>
  * 1) attach a function directly
  *    syntax:  _attachedEvents['eventName'] = yourFunctionName;
  * 2) attach some javascript code
  *    syntax:  _attachedEvents['eventName'] = "yourCode();";
  *    example: _attachedEvents['eventName'] = "alert('hi'); callSomething('foo');";
  *    just keep in mind that you cannot use vars in that code, because when it 
  *    gets executed that will be another scope (unless the vars are global...)
  * 3) attach multiple things for the same event
  *    syntax:  _attachedEvents['eventName']    = new Array;
  *             _attachedEvents['eventName'][0] = yourFunctionName;
  *             _attachedEvents['eventName'][1] = "yourCode();";
  * </pre>
  * 
  * @access private
  * @var    array (hash, see above)
  * @see    attachEvent();
  */
  this._attachedEvents;
  
  
  /**
  * constructor
  */
  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_Radio_"+this._id;
  }
  
	
	/**
	* loads a skin by its name. 
	* 
	* you can do the same with manual calls to setSliderIcon() etc, but this is quick and easy.
	* 
	* available skins:
	*   
	* 
	* @access public
	* @param  string skinName
	* @return bool
	* @since  bs-4.6
	*/
	this.loadSkin = function(skinName) {
		switch (skinName) {
			case 'win2k':
				this.imgDir         = '/_bsJavascript/components/radio/img/win2k/';
				this.imgWidth       = 12;
				this.imgHeight      = 12;
				break;
			case 'osx':
				this.imgDir         = '/_bsJavascript/components/radio/img/osx/';
				this.imgWidth       = 16;
				this.imgHeight      = 16;
				break;
			case 'opera7':
				this.imgDir         = '/_bsJavascript/components/radio/img/opera7/';
				this.imgWidth       = 12;
				this.imgHeight      = 12;
				break;
			case 'yattacier3':
				this.imgDir         = '/_bsJavascript/components/radio/img/yattacier3/';
				this.imgWidth       = 13;
				this.imgHeight      = 13;
				break;
			case 'h2ogtk2':
				this.imgDir         = '/_bsJavascript/components/radio/img/h2ogtk2/';
				this.imgWidth       = 13;
				this.imgHeight      = 13;
				break;
			case 'aluminumalloyvolcanic':
				this.imgDir         = '/_bsJavascript/components/radio/img/aluminumalloyvolcanic/';
				this.imgWidth       = 14;
				this.imgHeight      = 14;
				break;
			case 'smoothstreak':
				this.imgDir         = '/_bsJavascript/components/radio/img/smoothstreak/';
				this.imgWidth       = 13;
				this.imgHeight      = 13;
				break;
			case 'ximian-industrial':
			case 'ximian_industrial':
				this.imgDir         = '/_bsJavascript/components/radio/img/ximian_industrial/';
				this.imgWidth       = 13;
				this.imgHeight      = 13;
				break;
			default:
				return false;
		}
		return true;
	}
	
	
	/**
	* inits this class using an existing html radio field.
	* 
	* the available options and current value is affected. 
	* the captions cannot be read in.
	* 
	* @access public
	* @param  string fieldName
	* @return void
	*/
	this.initByField = function(fieldName) {
		this.radioFieldName = fieldName;
		this.options        = new Object;
		
		var elms = document.getElementsByName(fieldName);
		for (var i=0; i<elms.length; i++) {
			var elm = elms[i];
			var activateable   = (elm.disabled) ? 'no' : 'yes';
			var deactivateable = (elm.disabled) ? 'no' : 'yes';
			this.addOption(elm.value, '', false, activateable, deactivateable);
			if (elm.checked) this.value = elm.value;
		}
	}
  
	/**
	* uses an existing standard radio field and 'upgrades' it to a bs_radio field.
  * 
  * a radio field (most always) has more than 1 radio option. all those options 
  * share the same field name, but may (and should!) have different id's. 
  * that's why you have to give the name, not the id as param to this method.
  * 
  * NOTE: all onSomething functions that are attached to your existing plain 
  *       html radio field options will be lost.
  * 
	* @access public
	* @param  string fieldName (name, not id!)
	* @return void
	*/
	this.convertField = function(fieldName) {
		this.initByField(fieldName);
		
		var allLabels = document.getElementsByTagName('label');
		
//<input type="radio" name="disciplineOrEvent" id="disciplineOrEvent_d" value="d" onClick="local_toggleEvent(false); eventDiscChanged();" style="background-color:transparent; border:none;"><label for="disciplineOrEvent_d"><img src="/images/icon_discipline.gif"> <b>Discipline</b></label><br>';

		var elms = document.getElementsByName(fieldName);
		for (var i=elms.length; i>0; i--) { //we absolutely have to do this in reverse order.
			var elm = elms[i -1];
			var elmValue = elm.value; //have to cache that.
      
      /*
      //maybe it would be possible to read out existing attached onSomething functions, 
      //like onclick etc. and reassign them automatically. 
      //problem: an onclick can be attached to only one of the radio options, in html.
      //but this class currently does not allow to only attach to some of the options.
      for (var prop in elm) {
        if ((typeof(elm[prop]) == 'function') && (prop.substr(0,2) == 'on')) {
          elm.attachEvent('onclick', elm[prop]);
        }
      }*/
      
			elm.outerHTML = this.render(elmValue);
			
			//keep the label clickable
			var forId = fieldName + '_' + elmValue;
			for (var j=0; j<allLabels.length; j++) {
				//outerHTML = <LABEL for="someRadio_one">
				if (allLabels[j]['htmlFor'] == forId) { //another great idea from browsers to call it 'for' in html and 'htmlFor' in the object.
					allLabels[j].forValue   = elmValue;
					allLabels[j].bsRadioObj = this;
					allLabels[j].attachEvent('onclick', bs_radio_onClickLabel);
					break;
				}
			}
		}
	}
  
	
  /**
  * this method renders the option specified into the tag specified.
  * 
  * example: you have a <div id="radioOptionMr"></div> and you want 
  *          the <option name="sex" value="mr"> Mr.
  *          option rendered into this tag. then use:
  *          yourRadioObj.drawInto('mr', 'radioOptionMr');
  * 
  * @access public
  * @param  string value (null to render the full table, see this.renderAsTable().)
  * @param  string tagId
  * @return bool (true on success, false on failure)
  */
  this.drawInto = function(value, tagId) {
		if (bs_isNull(value)) {
	    var out = this.renderAsTable();
		} else {
	    var out = this.render(value);
		}
    
    var tag = document.getElementById(tagId);
    if (tag != null) {
      tag.innerHTML = out;
      return true;
    }
    return false;
  }
  
	
  /**
	* DEPRECATED USE drawInto()
	* 
  * renders the radio option specified and document.write()'s it immediatly.
  * @access public
  * @param  string value
  * @return void
	* @deprecated use this.drawInto()
  */
  this.draw = function(value) {
    document.write(this.render(value));
  }
  
	
  /**
  * returns the whole select field with all options, rendered as html table.
  * @access public
  * @param  string direction ('vertical' (default) or 'horizontal')
  * @param  string tableTag (you can give your own <table> tag here, otherwise '<table border="0">' is used.
  * @return string
  */
  this.renderAsTable = function(direction, tableTag) {
    var ret = new Array();
    if (bs_isNull(tableTag)) {
      ret[ret.length] = '<table border="0">';
    } else {
      ret[ret.length] = tableTag;
    }
    if (direction == 'horizontal') {
      ret[ret.length] = '<tr>';
			var i = 0;
      for (var value in this.options) {
        ret[ret.length] = '<td>';
				if (i > 0) ret[ret.length] = '&nbsp;&nbsp;';
				ret[ret.length] = this.render(value) + '</td>';
				i++;
      }
      ret[ret.length] = '</tr>';
    } else {
      for (var value in this.options) {
        ret[ret.length] = '<tr><td>' + this.render(value) + '</td></tr>';
      }
    }
    ret[ret.length] = '</table>';
    return ret.join('');
  }
  
  
  /**
  * renders the radio option (generates html code).
  * 
  * a radio field in html looks like this:
  *   <input type="radio" name="name" value="value" checked>
  * 
  * @access public
  * @param  string value (of the option you want)
  * @return string (empty string if no such option)
  */
  this.render = function(value) {
		if (typeof(this.options[value]) == 'undefined') return ''; //no such option.
    if (this.options[value]['hide']) return ''; //hidden option
    
    var out  = new Array();
    var outI = 0;
    
    var valueId = this._objectId + value;
    
    if (!bs_isNull(this.options[value]['furtherOptions']) && !bs_isNull(this.options[value]['furtherOptions']['captionAsTitleText'])) {
      var captionAsTitleText = this.options[value]['furtherOptions']['captionAsTitleText'];
    } else {
      if (typeof(this.captionAsAltText) != 'undefined') {
        //backward compatibility for pre bs4.5
        var captionAsTitleText = this.captionAsAltText;
      } else {
        var captionAsTitleText = this.captionAsTitleText;
      }
    }
    
    //is this option selectable by the user?
    var userSelectable = !(this.disabled || this.guiNochange || (this.options[value]['activateable'] != 'yes'));
    
    //is this option selected?
    var isSelected = (this.value == value);
    
    if (userSelectable) { //open onclick event
      out[outI++] = '<span';
      if (typeof(this.cssClass) != 'undefined') {
        out[outI++] = ' class="' + this.cssClass + '"';
      }
      if (typeof(this.cssStyle) != 'undefined') {
        out[outI++] = ' style="' + this.cssStyle + '"';
      }
      
      if (!this.guiNochange) {
        out[outI++] = ' onClick="Bs_Objects['+this._id+'].onClick(\'' + value + '\');"';
      }
      out[outI++] = ' style="cursor:hand; cursor:pointer;"';
      out[outI++] = ' onMouseOver="Bs_Objects['+this._id+'].onMouseOver(\'' + value + '\');"';
      out[outI++] = ' onMouseOut="Bs_Objects['+this._id+'].onMouseOut(\'' + value + '\');"';
      out[outI++] = '>';
    }
    
    var img         = '';
    var imgCssStyle = '';
    img += this._getIconPrefix(value);
    if (this.iconType == 'css') {
      if (!userSelectable) {
        imgCssStyle += 'filter:progid:DXImageTransform.Microsoft.BasicImage(grayScale=1), progid:DXImageTransform.Microsoft.BasicImage(opacity=.5);';
      }
      if (isSelected) {
        if (this.invertActive) imgCssStyle += 'filter:progid:DXImageTransform.Microsoft.BasicImage(invert=1);';
        imgCssStyle += 'border:2px solid gray;';
      } else {
        if ((this.pixelate > 0) && userSelectable) {
          imgCssStyle += 'filter:progid:DXImageTransform.Microsoft.Pixelate(maxsquare=' + this.pixelate + ');';
        }
      }
    } else {
      img += (userSelectable) ? 'enabled' : 'disabled';
      img += '_';
      img += (isSelected) ? '1' : '0'; //is that option selected?
    }
    img += '.' + this.imgType;
    out[outI++] = '<img id="' + valueId + 'Icon" src="' + this.imgDir + img + '" border="0"';
    if (!bs_isNull(this.imgWidth))  out[outI++] = ' width="'  + this.imgWidth  + '"';
    if (!bs_isNull(this.imgHeight)) out[outI++] = ' height="' + this.imgHeight + '"';
    out[outI++] = ' style="' + imgCssStyle + '"';
		
		//alert(value);
    if (this.options[value]['caption'].indexOf('<') == -1) {
      //don't use something as alt if it has tags in it.
      out[outI++] = ' alt="' + this.options[value]['caption'] + '"';
    }
    if (captionAsTitleText) {
      out[outI++] = ' title="' + this.options[value]['caption'] + '"';
    }
    out[outI++] = '>';
    
    if (!captionAsTitleText && (this.options[value]['caption'] != '')) {
      out[outI++] = '&nbsp;' + this.options[value]['caption'];
    }
    
    if (userSelectable) { //close onclick event
      out[outI++] = '</span>';
    }
    
    if (typeof(this.radioFieldName) != 'undefined') {
      var radioFieldName = this.radioFieldName;
    } else {
      var radioFieldName = valueId + 'Field';
			this.radioFieldName = radioFieldName; //we may need this later... maybe create a getRadioFieldName() method.
    }
		
    out[outI++] = '<input type="radio" name="' + radioFieldName + '" value="' + value + '" style="display:none; visibility:hidden;"'; //id="' + radioFieldName + '" 
    if (isSelected) out[outI++] = ' checked';
    out[outI++] = '>';
		
    return out.join('');
  }
  
  
  /**
  * adds the given option to the radio options.
  * if there is already an option with such a value, it will be overwritten. (case sensitive!)
  * 
  * 
  * activateable and deactivateable:
  * may this option be activated or deactivated? there are 2 possible values:
  *   'yes'    => this is the default.
  *   'script' => the user may not do it, it's only possible via script.
  *   'no'     => no change is possible.
  * so as an example, it is possible to have a radio option that cannot be 
  * selected, but if it is already selected by default, the selection can be 
  * "taken away". 
  * 
  * param "furtherOptions":
  * if given, it can have some or all of the keys:
  * iconPrefix, cssClass, cssStyle, captionAsTitleText (more to come?)
  * these settings then overwrite the default settings of this class.
  * 
  * @access public
  * @param  string value
  * @param  string caption
  * @param  string hide (if set to true then this option won't be visible.)
  * @param  string activateable   (default is 'yes', see above)
  * @param  string deactivateable (default is 'yes', see above)
  * @param  array  furtherOptions (see above)
  * @return void
  * @see    var this.options
  * @todo   implement the activateable/deactivateable stuff
  */
  this.addOption = function(value, caption, hide, activateable, deactivateable, furtherOptions) {
    if (bs_isNull(hide))           hide             = false;
    if (bs_isNull(activateable))   activateable     = 'yes';
    if (bs_isNull(deactivateable)) deactivateable   = 'yes';
    
    /*
    if (typeof(activateable)   == 'undefined')  activateable   = 'yes';
    if (typeof(deactivateable) == 'deactivate') deactivateable = 'yes';
    if (typeof(hide)           == 'undefined')  hide           = false;
    */
    
    value += ''; //string conversion to avoid stupid conflicts because of user errors.
    this.options[value] = new Object;
    this.options[value]['caption']        = caption;
    this.options[value]['hide']           = hide;
    this.options[value]['activateable']   = activateable;
    this.options[value]['deactivateable'] = deactivateable;
    if (typeof(furtherOptions) == 'object') {
      //we could assign them all separate here, but i think it won't make things easier nor faster.
      this.options[value]['furtherOptions'] = furtherOptions;
    }
  }
  
	
	/**
	* returns the current value.
	* @access public
	* @return mixed (null if no value, string otherwise
	*/
	this.getValue = function() {
		return this.value;
	}
  
	
  /**
  * fires on an onclick event. you better use this.setTo(value) 
  * as API function cause this one is used half-internally.
  * @access public
  * @param  string value
  * @return void
  * @see    this.setTo()
  */
  this.onClick = function(value) {
		var newValueReal = value;
		
    if (this.value == value) {
			if (this.allowNoSelection) {
				//reset to no value at all.
				newValueReal = null;
			} else {
				return; //no change, nothing to do
			}
		}
    
    //update outrendered icons:
    if (typeof(this.value) != 'undefined') {
      var oldSelectedImg = document.getElementById(this._objectId + this.value + 'Icon');
      if (oldSelectedImg) {
        if (this.iconType == 'css') {
          //oldSelectedImg.style.margin = '2px';
          oldSelectedImg.style.border = 'none';
          if (this.pixelate > 0) {
            oldSelectedImg.style.filter = 'progid:DXImageTransform.Microsoft.Pixelate(maxsquare=' + this.pixelate + ')';
          } else {
            oldSelectedImg.style.filter = '';
          }
        } else {
          oldSelectedImg.src = this.imgDir + this._getIconPrefix(this.value) + 'enabled_0.' + this.imgType;
        }
      }
    }
		
		if (newValueReal != null) {
	    var newSelectedImg = document.getElementById(this._objectId  + value + 'Icon');
	    if (newSelectedImg) {
        if (this.iconType == 'css') {
          imgCssStyle  = '';
          if (this.invertActive) newSelectedImg.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(invert=1)';
          newSelectedImg.style.border = '2px solid gray';
          //oldSelectedImg.style.margin = '0px';
        } else {
  	      newSelectedImg.src = this.imgDir + this._getIconPrefix(value) + 'enabled_1.' + this.imgType;
        }
	    }
    }
		
    this.value = newValueReal;
		
		//set value of invisible radio field:
		var col = document.getElementsByName(this.radioFieldName);
		for (var i=0; i<col.length; i++) {
			if (col[i].value == this.value) {
				col[i].checked = true;
			} else {
				col[i].checked = false;
			}
		}
    
    if (this.eventOnClick)  this._fireEvent(this.eventOnClick);
    if (this.eventOnChange) this._fireEvent(this.eventOnChange);
    if (this.hasEventAttached('onChange')) this.fireEvent('onChange');
  }
  
  
  /**
  * built in mouseover effect to switch icons
  * also preloads the icons if they are not already.
  * @access private
  * @param  mixed value
  */
  this.onMouseOver = function(value) {
    if (this.useMouseover && (this.iconType == 'image')) {
      var img = document.getElementById(this._objectId  + value + 'Icon');
      if (!img.swapOver0) {
        //load it
        img.swapOver0 = new Image();
        img.swapOver0.src = this.imgDir + 'enabled_0_over.gif';
        img.swapOver1 = new Image();
        img.swapOver1.src = this.imgDir + 'enabled_1_over.gif';
        img.swapOver2 = new Image();
        img.swapOver2.src = this.imgDir + 'enabled_2_over.gif';
        img.swapOut0 = new Image();
        img.swapOut0.src = this.imgDir + 'enabled_0.gif';
        img.swapOut1 = new Image();
        img.swapOut1.src = this.imgDir + 'enabled_1.gif';
        img.swapOut2 = new Image();
        img.swapOut2.src = this.imgDir + 'enabled_2.gif';
      }
      img.src = img['swapOver' + this.value].src;
    }
    if (this.pixelate > 0) {
      var img = document.getElementById(this._objectId  + value + 'Icon');
      img.style.filter = '';
    }
  }
	
  
  /**
  * built in mouseover effect to switch icons
  * @access private
  * @param  mixed value
  */
  this.onMouseOut = function(value) {
    if (this.useMouseover && (this.iconType == 'image')) {
      var img = document.getElementById(this._objectId  + value + 'Icon');
      img.src = img['swapOut' + this.value].src;
    }
    if ((this.pixelate > 0) && (this.value != value)) {
      var img = document.getElementById(this._objectId  + value + 'Icon');
      img.style.filter = 'progid:DXImageTransform.Microsoft.Pixelate(maxsquare=' + this.pixelate + ')';
    }
  }
  
  
  /**
  * can be called without a direct onClick event to that checkbox. 
  * useful for inheritance (tree-like structure of checkboxes, ya know...)
  * value will be changed, checkbox will be redrawn.
  * 
  * attached onClick events won't fire. if you want something to fire, 
  * use attachOnChange().
  * 
  * @access public
  * @param  bool cancelEventOnChange (set this to true if you don't want the eventOnChange to fire.)
  * @return void
  * @see    attachOnChange()
  this.setTo = function(value, cancelEventOnChange) {
    this.value = value;
    this.draw();
    if (!cancelEventOnChange) {
      if (this.eventOnChange) this._fireEvent(this.eventOnChange);
    }
  }
  */
  
  /**
  * attaches an onclick event. (currently overwrites a prev attached function)
  * 
  * this one fires after this.onClick().
  * 
  * @param  string globalFunctionName
  * @return void
  * @see    this.attachOnChange()
  this.attachOnClick = function(globalFunctionName) {
    this.eventOnClick = globalFunctionName;
  }
  */

  /**
  * attaches an onChange event. (currently overwrites a prev attached function)
  * 
  * this one fires after this.onClick() AND this.setTo().
  * 
  * @param  string globalFunctionName
  * @return void
  * @see    this.attachOnClick()
  this.attachOnChange = function(globalFunctionName) {
    this.eventOnChange = globalFunctionName;
  }
  */
  
  
  /**
  * Attaches an event like onChange, onMouseOver, onClickCaption ... a.s.o.
  * Supported events are:
  * - 'onChange'
  * 
  * @access public
  * @param  string trigger (for example 'onChange')
  * @param  mixed A globalFunctionName OR string of javascript to be evaled (using JS's eval())
  * @return void
  * @see    _attachedEvents
  */
  this.attachEvent = function(trigger, yourEvent) {
    if (typeof(this._attachedEvents) == 'undefined') {
      this._attachedEvents = new Array();
    }
    
    if (typeof(this._attachedEvents[trigger]) == 'undefined') {
      this._attachedEvents[trigger] = new Array(yourEvent);
    } else {
      this._attachedEvents[trigger][this._attachedEvents[trigger].length] = yourEvent;
    }
  }
  
  /**
  * Tells if an event is attached for the trigger specified. 
  * @access public
  * @param  string trigger
  * @return bool
  */
  this.hasEventAttached = function(trigger) {
    return (this._attachedEvents && this._attachedEvents[trigger]);
  }
  
  /**
  * Fires the events for the trigger specified.
  * (used internally, but feel free to trigger events yourself...)
  * @access public
  * @param  string trigger (for example 'onClickCaption')
  * @return void
  */
  this.fireEvent = function(trigger) {
    if (this._attachedEvents && this._attachedEvents[trigger]) {
      var e = this._attachedEvents[trigger];
      if ((typeof(e) == 'string') || (typeof(e) == 'function')) {
        e = new Array(e);
      }
      for (var i=0; i<e.length; i++) {
        if (typeof(e[i]) == 'function') {
          e[i](this);
        } else if (typeof(e[i]) == 'string') {
          eval(e[i]);
        } //else murphy
      }
    }
  }
  
  /**
  * fires the event specified.
  * @access private
  * @param  mixed e (string or array)
  * @return void
  */
  this._fireEvent = function(e) {
    if (e) {
      if (typeof(e) != 'array') {
        e = new Array(e);
      }
      for (var i=0; i<e.length; i++) {
        if (typeof(e[i]) == 'function') {
          e[i](this);
        } else if (typeof(e[i]) == 'string') {
          eval(e[i]);
        } //else murphy
      }
    }
  }
  
  
  /**
  * returns the icon prefix for the option specified.
  * @access private
  * @param  string value
  * @return string (empty string '' if not prefix.)
  */
  this._getIconPrefix = function(value) {
    if (!bs_isNull(this.options[value]['furtherOptions']) && !bs_isNull(this.options[value]['furtherOptions']['iconPrefix'])) {
      return this.options[value]['furtherOptions']['iconPrefix'];
    }
    return '';
  }
  
  
  this.constructor();
  
}

