/*
Code by Steffan A. Cline 
Version 1.5 - 9/5/2007
Row color highlighter and automatic price adjustment

usage:

<body onload="init( parameter1, parameter2, parameter3, parameter4 );">
parameter 1 = 	the name of the form
parameter 2 = 	the type of control to attach a listener to - this can be a single control or an array 
				rather than just radio you could do ..., new Array('radio','checkbox','select-one'), ... 
				or lasso escaped ..., ["['radio','checkbox','select-one']"], ... 
parameter 3 =	the target "id" of the price - this can be any thing that contain text 
				for example <span id="price"></span> <div id="price"></div> <td id="price"></td> <input ... id="price">
				all ids must be UNIQUE! multiple can be used. - ..., new Array('price1','price2'),...
parameter 4 = 	the BASE price of the item in a raw decimal or integer value. 

changes 9/8/2007 v1.7
1.  documentation update - included alternate to new Array() when initializing the script - [..,..,..] notation
2.  checkType changed to more logical controlTypes name
3.  added support for checkboxes 
4.  added support for selects - single and multiple
5.  more comments
6.  made it smarter so if not existent requested items are not there you do not get errors.
7.  universalized the event bubbling control
8.  monitored items that are not in a row will no longer throw an error.
9.  added &nbsp; to the [ Add.... ] so they wrap appropriately.
10. corrected some logic
11. removed line of code that would place a price for items of the same value.
12. added compatability with the w3c stopPropagation foe event handling on the row click


*/

// begin user configurable
var unselectedColor = "#ffffff";		// color of the rows when not checked/selected
var selectedColor = "#d2ebff";			// color of the rows when selected/checked
var rolloverOnColor = "#eaeaea";		// color of the rows when moused over
var rolloverOffColor = unselectedColor;	// color of the rows when moused out
var priceUnChangedClass = "grey12";		// class of a span div td etc where the output text is to be changed
var priceChangedClass =  "red12";		// class of a span div td etc where the output text is to be changed when form changed
var parentToColor = "TR";				// the parent node of a control to have it's background color changed
var selectedItemChangesOnRollover = false; // change to false if a selected item should change color
// end user configurable


// ***** script vars - do not change ******
var calcForm, controlTypes, totalTarget;
var basePrice = 0;
var formWasChanged = false;

function init( _formSeed, _supportedControls, _targets, _price )
{
	calcForm = document.forms[_formSeed];											// set the form to attach the event to
	controlTypes = (typeof(_supportedControls) == 'string' ? new Array(_supportedControls) : _supportedControls);	// set the type of element we will check in the form
	totalTarget = (typeof(_targets) == 'string' ? new Array(_targets) : _targets); // insure we are working with an array and set the target(s) for the total
	basePrice = parseFloat(_price);													// set the base price
	
	// init data integrity validation
	if( !calcForm )		alert("init() did not receive a form name to process!");
	if( !controlTypes ) alert("init() did not receive (a) form element type(s)!");
	if( !totalTarget )	alert("init() did not receive (a) target(s) for the total!");
	if( !basePrice ) 	alert("init() did not receive a base price!");
	
	for( var i = 0; i < calcForm.elements.length; i++ )
		for( var j in controlTypes )
			if( calcForm.elements[i].type == controlTypes[j] )
			{
				if( getValue(calcForm.elements[i]) && calcForm.elements[i].type.indexOf('select') == -1 ) // if radio or checkbox
					getParent(calcForm.elements[i]).cells[1].innerHTML += (" <b>[&nbsp;Add&nbsp;$" + getValue(calcForm.elements[i]) + "&nbsp;]</b>");

				if( calcForm.elements[i].type.indexOf('select') > -1 )					// selects
					for( var j = 0; j < calcForm.elements[i].length; j++ )
						if( getValue(calcForm.elements[i].options[j].value) )
							calcForm.elements[i].options[j].text += (" [ Add $" + getValue(calcForm.elements[i].options[j]) + " ]")	
					
				
				calcForm.elements[i].onclick = function (e) 
													{ 	var e = e || event;
														e.cancelBubble = true;	// stop this click from bubbling up to the row handler
														if (e.stopPropagation) e.stopPropagation(); // for browsers not supporting .cancelBubble
														formWasChanged = true;	// set the form as dirty so it can be processed
														setTotal(); 
													};		// attach the event to all types specified
				
				var row = getParent(calcForm.elements[i]);
				if( row )
				{
				row.onmouseover = function () { if( !selectedItemChangesOnRollover && (getControl(this).checked || getControl(this).selectedIndex) ) 
													return; // will control whether or not a row is affected by rollover when selected
												this.style.backgroundColor = rolloverOnColor; // highlight the row 
											  }
				row.onmouseout  = function () { var x = getControl(this); this.style.backgroundColor = ( x && ( x.selectedIndex || x.checked ) ?  selectedColor : rolloverOffColor ); }
				row.onclick 	= function (e) { var x = getControl(this);
												var e = e || event;
												e.cancelBubble = true;	// stop this click from bubbling up to the row handler
												if (e.stopPropagation) e.stopPropagation(); // for browsers not supporting .cancelBubble
												if( x ) // if there is a control in this row then process it
												{
													switch( x.type )
													{
														case( "radio" )			:	this.style.backgroundColor = selectedColor;
																					x.checked = true;
																					break;
														case( "checkbox" )		:	x.checked = !x.checked;
																					this.style.backgroundColor = ( x.checked ? selectedColor : unselectedColor );
																					break;
														case( "select" )		:	// catch alls for the selects 
														case( "select-multiple" ):	
														case( "select-one" )	:	this.style.backgroundColor = ( x.selectedIndex ? selectedColor : unselectedColor );
																					break;
													}
													formWasChanged = true;			// set the form as dirty so it can be processed
													setTotal();
												}
											  }
				}	
			}
	setTotal();												// setTotal will color the rows appropriately and then displayTotal()
}

function setTotal()
{
	var chosenConfigCost, current_group, current_amount, current_control;
	var total = basePrice;											// set a base point for our total

	if( controlTypes.contains("radio") )
	{
		var groups = [];
		var inputControls = calcForm.getElementsByTagName("input").length
								? calcForm.getElementsByTagName("input")
									: new Array(0);					// get all input elements handed off in an array

		for( var i = 0; i < inputControls.length; i++ )				// iterate thru all inputControls - find radio dials and get the groups
			if( inputControls[i].type == "radio" && !groups.find( inputControls[i].name ) )	
				groups = groups.concat([inputControls[i].name]);	// if this is a new group then add it
	
		for( var i = 0; i < groups.length; i++ ) 					// iterate thru groups
		{	
			current_group = calcForm.elements[groups[i]]; 			// set current group
			chosenConfigCost = 0;									// reset the chosenConfig cost for this iteration
			
			for( var j = 0; j < current_group.length; j++ )			// iterate thru current group's radio dials (bubble sort)
				if( current_group[j].type == "radio" && current_group[j].checked )
					chosenConfigCost = getValue(current_group[j]);	// if this is the higest then set it as the highest
	
			for( var k = 0; k < current_group.length; k++ )			// iterate thru current group
			{
				current_control = current_group[k];					// set the current control
				current_amount = getValue(current_control);			// set the current amount
				current_row = getParent(current_control);			// set the current row
				
				current_row.cells[1].innerHTML =					// clear out current dollars
					current_row.cells[1].innerHTML.replace(new RegExp("(\\s*<b>\\[.*?\\]</b>\\s*)+?", "gi" ),""); 
				current_row.style.backgroundColor = unselectedColor;
	
				if( current_amount < chosenConfigCost )				// if current item costs less than the chosen cost then show - subtract
					current_row.cells[1].innerHTML += " <b>[&nbsp;Subtract&nbsp;$" + (chosenConfigCost - current_amount).toFixed(2) + "&nbsp;]</b>";
				if( current_amount > chosenConfigCost )				// if current item costs more than the chosen cost then show - add more
					current_row.cells[1].innerHTML += " <b>[&nbsp;Add&nbsp;$" + (current_amount - chosenConfigCost).toFixed(2) + "&nbsp;More&nbsp;]</b>";
				if( current_control.checked && current_amount ) 	// if item is the one checked then show - add
					total += chosenConfigCost;
				if( current_control.checked )
					current_row.style.backgroundColor = selectedColor;
			}
		}
	}
	
	if( controlTypes.contains("select") )
	{
		var selects = calcForm.getElementsByTagName('select').length 		// catch all for the selects
							? calcForm.getElementsByTagName('select') 
							: calcForm.getElementsByTagName('select-one').length
								? calcForm.getElementsByTagName('select-one')
								: calcForm.getElementsByTagName('select-multiple').length
									? calcForm.getElementsByTagName('select-multiple')
									: new Array(0);
		for( var i = 0; i < selects.length; i++ )
		{
			var thisSelectMax = getValue(selects[i].options[selects[i].selectedIndex]);
			for( var j = 0; j < selects[i].length; j++ )
				{	
				selects[i].options[j].text = selects[i].options[j].text.replace(new RegExp("(\\s*\\[.*?\\]\\s*)+?", "gi" ),"");
				var thisOption = getValue(selects[i].options[j])
				if(  thisOption < thisSelectMax )
					selects[i].options[j].text += " [ Subtract $" + (thisSelectMax - thisOption).toFixed(2) + " ]";
				if( thisOption > thisSelectMax )
					selects[i].options[j].text += " [ Add $" + (thisOption - thisSelectMax).toFixed(2) + " More ]"
				}
			total += getValue(selects[i].options[selects[i].selectedIndex]);
		}
	}
	
	if( controlTypes.contains("checkbox") )
	{
		var inputControls = calcForm.getElementsByTagName("input").length
								? calcForm.getElementsByTagName("input")
									: new Array(0);									// get all input elements handed off in an array

		for( var i = 0; i < inputControls.length; i++ )
			if( inputControls[i].type == "checkbox" && inputControls[i].checked )	// if it is a checkbox and is checked
				total += getValue(inputControls[i]);								// if so add to total

	}

	displayTotal(total);															// display the total in assigned spaces
}

function displayTotal(total)
{

	for( var i = 0; i < totalTarget.length; i++ )				// loop thru the list of price locations.
	{
		var target = document.getElementById(totalTarget[i]);	// get a handle on the target
		if( target.type == "text" )								// determine if this is an input 
			target.value = total.toFixed(2);						// output the data in a fixed format
		else
			{
			target.innerHTML = (formWasChanged ? "Updated Price $" : "Our Price $" ) + total.toFixed(2);
			target.className = (formWasChanged ? priceChangedClass : priceUnChangedClass );
			}
	}
}

function getParent( obj )
{
	do {														// back out of the container node until we hit the 
		obj = obj.parentNode;									// targeted node
		}
	while( obj.nodeName != "HTML" && obj.nodeName != parentToColor );	// use HTML as the safety node
	return( obj.nodeName == "HTML" ? null : obj );
}

function getControl( obj )
{
	if( obj.getElementsByTagName("input").length )					// checkboxes and radio dials <or buttons>
		return( obj.getElementsByTagName("input")[0] );
	if( obj.getElementsByTagName("select").length )					// pop up selects - non standard getter
		return( obj.getElementsByTagName("select")[0] );
	if( obj.getElementsByTagName("select-one").length )				// pop up selects
		return( obj.getElementsByTagName("select-one")[0] );
	if( obj.getElementsByTagName("select-multiple").length )		// list box select
		return( obj.getElementsByTagName("select-multiple")[0] );
	return( null );
}


function getValue(obj)
{
	var delimiter = "||";			// the delimiter in your strings to get to the price for the added feature's price
	var valuePosition = 2;			// the position within the delimited string for the added feature's price
	
	var value = 0.0;					// set a default of 0
	if( typeof(obj) == 'string' )	// if we are passed a string in the event of a select item
		value = parseFloat(obj.split(delimiter)[valuePosition]);
	else if( obj.value )			// else there is a value attribute
		value = parseFloat(obj.value.split(delimiter)[valuePosition]);
	return( value ? value : 0.0 );
}

// add a new member to the array class
Array.prototype.find = function ( item ) 	
{											
	for( var i = 0; i < this.length; i++ ) 
		if( this[i] == item ) 
			return( true );
	return( false );
};

// add a new member to the array class
Array.prototype.contains = function ( item ) 	
{											
	for( var i = 0; i < this.length; i++ ) 
		if( this[i].indexOf(item) != -1 ) 
			return( true );
	return( false );
};

// the following functions will ensure that the dollars are rounded to the specified number of decimal places.
function Stretch(Q, L, c) 
{ 
	var S = Q;
	if (c.length>0) 
		while (S.length<L) 
			{ S = c+S } 
	return( S ); 
} 

function StrU(X, M, N) 
{ 
	// X>=0.0 
	var T, S=new String(Math.round(X*Number("1e"+N))); 
	if (S.search && S.search(/\D/)!=-1) { return ''+X } 
	with (new String(Stretch(S, M+N, '0'))) 
	return( substring(0, T=(length-N)) + '.' + substring(T) ); 
} 

function Sign(X) 
{ 	
	return( X < 0 ? '-' : '' ); 
} 

function StrS(X, M, N) 
{ 
	return( Sign(X)+StrU(Math.abs(X), M, N) ); 
} 
// add the .toFixed() to the number class.
Number.prototype.toFixed = function(n){ return StrS(this,1,n) }; 

