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

/**
* A Checkbox Component.
* 
* <b>Features:</b> 
* - IE6 and NS(Mozilla7) compliant.
* - Simulates a normal HTML-form checkbox; so your form-hanling will not change.
* - Uses images for the checkbox. (You may use your customized images).
* - Able to deactivate a checkbox, thus visible but not changeable (by the user).
* - Can have 3 states: 'Checked', 'Not Checked' AND 'Partly Checked' 
* - Able to attach onClick- and onChange-events to execute your JS-code when chicked/changed.
* - Able to have rollover effects.
* 
* <b>Includes (+Dependences):</b>
* <code>
*   <script type="text/javascript" src="/_bsJavascript/core/lang/Bs_Misc.lib.js"></script>
*   <script type="text/javascript" src="/_bsJavascript/components/checkbox/Bs_Checkbox.class.js"></script>
* </code>
* 
* <b>How to use:</b>
* 1. Have a look at the example (see example link below)
* 2. Create a function in the HTML-header called init(). Place the javascript code 
*    that instanciates and inits this component into init().
* 3. Place an 'onLoad' in your body-tag: e.g. <body onLoad="init();">
* 4. In the HTML body: Place a div- or span-tag with an unique ID where you want the componet to be.
* 
* <b>How it works:</b>
* - [After instanciating and initializing the object]. Call the drawInto([tag id]) methode.
*   This will search the HTML code for a tag with the given id and insert HTML- and JS- code
*   dynamically (using DOM) to display the component and handle it.
* - This class generates a normal HTML checkbox input field and saves it's state in there.
*   It's not visible because it is set to display:none (invisible) but (when used in a form) will 
*   be submitted like any other HTML-form-field.
* - Note: HTML checkboxs that have a value of 0 are not returned to the browser.
* 
* <b>What is returned to the server:</b>
*<pre>
*  +----------------------+----------------------------------------------------+
*  | checkboxName         | int, 1 or 2 (0 is not returned, assume not checked)|
*  +----------------------+----------------------------------------------------+
*</pre>
*
* <b>Event-Handling</b>: (see {@link attachOnClick()}, {@link attachOnChange()})
* - You can attach code or a callback function for certain events onto this component
*   (events are things like onClick, onChange ... aso) 
*
* Snippet 1) Attaching a callback function:
* <code>
*     var checkBox = new Bs_Checkbox();
*     checkBox.attachOnClick(yourCallbackFunctionName);
* </code>
* The function you pass should be able to receive a object as parameter.
* The object is an instance of this class and allows you to access it the 
* component that triggered the event:
* <code>
*     function yourCallbackFunctionName(componentObj){
*       // do some stuff.
*     }
* </code>
* = = = =<br>
* Snippet 2) Attaching JS code:
* <code>
*     var checkBox = new Bs_Checkbox();
*     checkBox.attachOnClick("yourFunctionName('foo', 'bar'); alert('hello');");
* </code>
*
* <b>Customized images:</b>
* - Set the path to your image-dir {@link imgDir}
* - Following images must be created.
*   The numbers stand for 0(= not checked), 1(=partly) or 2(=completely).
* <pre>
*   Disabled GIFs:
*     disable_0.gif,disable_1.gif, disable_2.gif  ()
*   Enabled GIFs:
*     enable_0.gif,enable_1.gif, enable_2.gif  ()
*   Rollover GIFs:
*     enable_0_over.gif,enable_1_over.gif, enable_2_over.gif
* </pre>
*
* @example components/checkbox/examples/example1.php
* 
* @author     andrej arn <andrej-at-blueshoes-dot-org>
* @package    javascript_components
* @subpackage checkbox
* @copyright  blueshoes.org
*/
function Bs_Checkbox() {

  /**
  * ID is initialized in the constuctor. Represents the possition of 'this' in the global array 'Bs_Objects'
  * Use: This is the only way we can reference ourselfs in an evaluation string 
  *  E.g. str = "var me = Bs_Objects["+this._id+"];";
  *       str += "me.foo();" 
  *       eval(str); 
  *
  * @access private
  * @var  integer 
  */
  this._id;
  
  /**
  * 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._tagId;

  /**#@+
  * @access public
  */
  
  /**
  * When submitting the form, you'll receive the checkbox value under this name.
  * In other words you'll receive the data back to the server as if you had placed <br>
  * <code><input type=checkbox name="[the checkboxName]" id="[the checkboxName]"  value="[the value]"></code><br>
  * into your HTML-form. If you omit setting the checkboxName it will be genarated as follows:<br><br>
  * <i>checkboxName = "checkbox" + tag-Id</i>
	* 
	* if you use the convertField() method then this setting will be ignored; the existing 
	* field name will be used instead.
	* 
	* @access public
  * @var    string
  */
  this.checkboxName;
  
  /**
  * checkbox checked?
  * <pre>
  * 0 = no
  * 1 = partly
  * 2 = completely
  * </pre>
	* @access public
  * @var    int (default is 0)
  */
  this.value = 0;
  
  /**
  * if set to true then the 'partly' thing won't be used, thus a value of 1 is treated like 2.
  * @access public
  * @see    value
  * @since  bs4.3
  */
  this.noPartly = false;
  
  /**
  * checkbox can be disabled. then it's value cannot be changed.
  * @var    bool disabled (default is false)
  * @see    guiNochange
  */
  this.disabled = false;
  
	
	/**
	* @access public
	* @param  bool b (true = disabled, false = enabled, not specified or null = toggle current state.)
	* @return void
	* @since  bs-4.6
	*/
	this.setDisabled = function(b) {
		if (typeof(b) == 'undefined') b = !this.disabled;
		this.disabled = b;
		this.drawInto(this._tagId);
	}
	
	
  /**
  * if set to true then the user cannot change things by clicking. 
  * changes through the api functions (for the coder, you) are still possible.
  * @var    bool guiNochange
  * @see    disabled
  */
  this.guiNochange = false;
  
  /**
  * if a caption is used then it will be clickable (if the checkbox 
  * is not disabled).
  * 
  * currently you can assign any html value, examples:
  * <pre>
  *   yourObj.caption = "foobar";
  *   yourObj.caption = "&lt;span id='foobar' class='foobar'&gt;foobar&lt;/span&gt;";
  * </pre>
  * so you can hack in styles etc. this may change in the future, a text string 
  * only would be cleaner.
  * 
  * @var string
  */
  this.caption;
  
  /**
  * Directory where the images are. 
  * Feel free to create your own images, put them into another directory (by using the same file names, etc.)
  * See header for more info.
  * @access public
  * @var string 
  */
  this.imgDir = '/_bsJavascript/components/checkbox/img/win2k/';
  
  /**
  * @access public
  * @var    string  image width
  */
  this.imgWidth  = '20';
  
  /**
  * @access public
  * @var    string  image hight
  */
  this.imgHeight = '20';
  
  /**
  * @access public
  * @var    string image style (css style string)
  */
  this.imgStyle = '';
  
  /**
  * 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 (checkbox status change), to see 
  *       if they're now available, even if never used. 
  *       btw, i'm speaking of the "partly checked" icons.
  * 
  * @var  bool 
  */
  this.useMouseover = false;
  /**#@-*/
  
  /**
  * @access private
  * @var    string eventOnClick (currently string is used)
  * @see    attachOnClick();
  * @see    var Bs_Checkbox::eventOnChange
  */
  this.eventOnClick;
  
  /**
  * @access private
  * @var    string eventOnChange (currently string is used) 
  * @see    this.attachOnChange();
  * @see    var Bs_Checkbox::eventOnClick
  */
  this.eventOnChange;  
    
	/**
	* 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._tagId = "Bs_Checkbox_"+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/checkbox/img/win2k/';
		  	this.imgWidth     = '20';
		  	this.imgHeight    = '20';
				break;
			case 'osx':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/osx/';
		  	this.imgWidth     = '14';
		  	this.imgHeight    = '16';
				break;
			case 'opera7':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/opera7/';
		  	this.imgWidth     = '14';
		  	this.imgHeight    = '14';
				break;
			case 'bobby':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/bobby/';
		  	this.imgWidth     = '11';
		  	this.imgHeight    = '11';
				this.imgStyle     = 'margin:3px; vertical-align:middle;';
				this.useMouseover = true;
				break;
			case 'yattacier3':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/yattacier3/';
		  	this.imgWidth     = '13';
		  	this.imgHeight    = '13';
				break;
			case 'h2ogtk2':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/h2ogtk2/';
		  	this.imgWidth     = '13';
		  	this.imgHeight    = '13';
				break;
			case 'aluminumalloyvolcanic':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/aluminumalloyvolcanic/';
		  	this.imgWidth     = '14';
		  	this.imgHeight    = '16';
				break;
			case 'smoothstreak':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/smoothstreak/';
		  	this.imgWidth     = '13';
		  	this.imgHeight    = '13';
				break;
			case 'ximian-industrial':
			case 'ximian_industrial':
		  	this.imgDir       = '/_bsJavascript/components/checkbox/img/ximian_industrial/';
		  	this.imgWidth     = '13';
		  	this.imgHeight    = '13';
				break;
				
			default:
				return false;
		}
		return true;
	}
	
	
  /**
  * Renders this compnent (generates html code).
  * We added this method to the public API for the advanced user. It's half-private :).
  * This is a call for the advanced user, who would like to fetch the html- / js-
  * that is rendered to make this component running.<br>
  * The average user will normally use {@link drawInto()}, that will render and place the 
  * code into the site in one simple step.
  *
  * @access public
  * @param  string tagId (id of the tag.)
  * @return string
  * @see drawInto()
  */
  this.render = function(tagId) {
    if (this.noPartly && (this.value == 1)) this.value = 2;
    
    if (!bs_isEmpty(tagId)) {
      this._tagId = tagId;
    }
    
    var out  = new Array();
    var outI = 0;
    
    var img = '';
    img += (this.disabled) ? 'disabled' : 'enabled';
    img += '_' + this.value;
    
    if (!this.disabled) { //open onclick event
      out[outI++] = '<span';
      if (!this.guiNochange) {
        out[outI++] = ' onClick="Bs_Objects['+this._id+'].onClick(\'' + this._tagId + '\');"';
      }
      out[outI++] = ' style="cursor:hand;"';
      if (this.useMouseover && !this.guiNochange) {
        out[outI++] = ' onMouseOver="Bs_Objects['+this._id+'].onMouseOver(\'' + this._tagId + '\');"';
        out[outI++] = ' onMouseOut="Bs_Objects['+this._id+'].onMouseOut(\'' + this._tagId + '\');"';
      }
      out[outI++] = '>';
    }
    
    // this.imgDir
    out[outI++] = '<img id="' + this._tagId + 'icon" src="' + this.imgDir + img + '.gif" border="0" width="' + this.imgWidth + '" height="' + this.imgHeight + '"';
    if (!bs_isEmpty(this.imgStyle)) out[outI++] = ' style="' + this.imgStyle + '"';
    out[outI++] = '>';
    if (this.caption) {
      out[outI++] = '&nbsp;' + this.caption;
    }
    
    if (!this.disabled) { //close onclick event
      out[outI++] = '</span>';
    }
    
    if (!this.checkboxName) {
      this.checkboxName = 'checkbox' + this._tagId;
    }
    
    out[outI++] = '<input value="' + this.value + '" type=checkbox name="' + this.checkboxName + '" id="' + this.checkboxName + '" style="display:none; visibility:hidden;"';
    if (this.value) out[outI++] = ' checked';
    out[outI++] = '>';

    return out.join('');
  }
  
  /**
  * Renders the checkbox component and places it into the tag specified.
  * @access public
  * @param  string tagId id of the tag. (Hint: use a <div> or <span>.)
  */
  this.drawInto = function(tagId) {
    if (!bs_isEmpty(tagId)) {
      this._tagId = tagId;
    }
    document.getElementById(this._tagId).innerHTML = this.render(this._tagId);
  }

  /**
  * DEPRECATED.
  * Use drawInto()
  * @deprecated use drawInto()
  * @param string tagId (id of the span tag.)
  * @see drawInto()
  */
  this.draw = function(tagId) {
    this.drawInto(tagId);
  }
  
  /**
  * renders the checkbox object and document.write()'s it immediatly.
  * 
  * WARNING:
  * - that currently won't work, since any change to the checkbox calls 
  *   drawInto() to redraw the box. all internal calls to drawInto() would need to be 
  *   rewritten in that only the checkbox image is replaced. would be cooler 
  *   anyway. so use drawInto() until this changes.
  * 
  * @deprecated
  * @access private
  * @return void
  */
  this.write = function() {
    document.write(this.render(this._tagId));
  }
  
  /**
  * converts an existing standard checkbox field into a Bs_Checkbox field on the fly.
	* uses the existing field name as checkboxName.
  * @access public
  * @param  string fieldId (the id of the checkbox field)
  * @return bool (true on success, false on failure)
  * @see    see example 2 at http://www.blueshoes.org/_bsJavascript/components/checkbox/examples/example2.html
  */
  this.convertField = function(fieldId) {
		var fldElm = document.getElementById(fieldId);
		if (!fldElm) return false;
		this.checkboxName = fldElm.name;
 	  fldElm.outerHTML = this.render(this._tagId);
		return true;
  }
  
  /**
  * built in mouseover effect to switch icons also preloads the icons if they are not already.
  * @access private
  * @return void
  */
  this.onMouseOver = function() {
    var img = document.getElementById(this._tagId + '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;
  }
  
  /**
  * built in mouseover effect to switch icons
  * @access private
  * @return void
  */
  this.onMouseOut = function() {
    var img = document.getElementById(this._tagId + 'icon');
    img.src = img['swapOut' + this.value].src;
  }
  
  /**
  * Fires on an onclick event you'd better use {@link setTo()}. 
  * We added this method to the public API for the advanced user. It's half-private :).
  * @access public
  * @return void
  * @see    setTo()
  */
  this.onClick = function() {
    switch (this.value) {
      case 0:
        this.value = 2;
        break;
      case 1:
      case 2:
        this.value = 0;
        this.value = 0;
        break;
      default:
        //huh? murphy.
        this.value = 0;
    }
    this._updateHiddenField();
    this._updateIcon();
    
    if (this.eventOnClick)  this._fireEvent(this.eventOnClick);
    if (this.eventOnChange) this._fireEvent(this.eventOnChange);
  }
  
  
  /**
  * If you wish to change the value of this component, call this method.
  * The value will be changed AND redrawn, showing the new state/data.
  * Usfull for other JS-code (tree-like structure of checkboxes, ya know...)
  * 
  * NOTE: 
  * - attached attachOnChange() event will fire but *not* the attachOnClick() event.
  * 
  * @access public
  * @param  int  value (0, 1 or 2)
  * @param  bool cancelEventOnChange (set this to true if you don't want the eventOnChange to fire.)
  * @return void
  * @see    attachOnChange(), attachOnClick()
  */
  this.setTo = function(value, cancelEventOnChange) {
    this.value = value;
    this._updateHiddenField();
    this._updateIcon();
    if (!cancelEventOnChange) {
      if (this.eventOnChange) this._fireEvent(this.eventOnChange);
    }
  }
  
  /**
  * Attaches an onClick event. (currently overwrites any previosly attached function)
  * 
  * This one fires after onClick().
  * 
  * @access public
  * @param  mixed A globalFunctionName OR string of javascript to be evaled (using JS's eval())
  * @return void
  * @see    setTo(), attachOnChange()
  */
  this.attachOnClick = function(globalFunctionName) {
    this.eventOnClick = globalFunctionName;
  }

  /**
  * Attaches an onChange event. (currently overwrites any previosly attached function)
  * 
  * This one fires after onClick() AND setTo().
  * 
  * @access public
  * @param  mixed A globalFunctionName OR string of javascript to be evaled (using JS's eval())
  * @return void
  * @see    setTo(), attachOnClick()
  */
  this.attachOnChange = function(globalFunctionName) {
    this.eventOnChange = globalFunctionName;
  }
  
  /**
  * 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') {
          //alert(e[i]);
          eval(e[i]);
        } //else murphy
      }
    }
  }
  
  /**
  * updates the checkbox icon.
  * @access private
  * @return void
  */
  this._updateIcon = function() {
    //this.drawInto();
    var iconElm = document.getElementById(this._tagId + 'icon');
    if (iconElm != null) {
      var img = '';
      img += (this.disabled) ? 'disabled' : 'enabled';
      img += '_' + this.value;
      iconElm.src = this.imgDir + img + '.gif';
    }
  }
	
	/**
	* updates the hidden value.
	* @access private
	* @return void
	*/
  this._updateHiddenField = function() {
    var elm = document.getElementById(this.checkboxName);
    elm.value = this.value;
    elm.checked = (this.value);
  }

	this._constructor(); //call the constructor. needs to be at the end.

}

