function initialise() {
	
	if (document.getElementById('membership')) 
	{
    	//Check for the membership selection table:
    	var membershipOptionsNode = document.getElementById('membershipOptions')
    	if (membershipOptionsNode) {
    	    
    		var choices = membershipOptionsNode.getElementsByTagName('input');
    		
    		for (var i = 0; i < choices.length; i++) {
    			if (choices[i].className.indexOf('choice') != -1) {
    				choices[i].onclick = function() { membershipTypeSelected(this)};
    				// If this is the option that is selected.
    				if (choices[i].checked)
    				{
    				    // Initialise the selection.
    				    membershipTypeSelected(choices[i]);
    				}
    			}
    		}
    	}
		
		happyError.init('membership');
		disables('membership');
	}
	else if (document.getElementById('donation')) 
	{
    	//Check for the membership selection table:
    	var donationOptionsNode = document.getElementById('donationOptions')

    	if (donationOptionsNode) {
    		var inputs = donationOptionsNode.getElementsByTagName('input');
    		
    		for (var i = 0; i < inputs.length; i++) {
    			if (inputs[i].className.indexOf('choice') != -1) {
    			    	        		
    				inputs[i].onclick = function() { donationTypeSelected(this)};
    				// If this is the option that is selected.
    				if (inputs[i].checked)
    				{
    				    // Initialise the selection.
    				    donationTypeSelected(inputs[i]);
    				}
    			}
    			else if (inputs[i].className.indexOf('amount') != -1) {
    			     inputs[i].onkeyup = function() { donationAmountChanged(this)};
    			}
    		}
    	}
		
		happyError.init('donation');
		disables('donation');
	}
		// PRODUCT SELECTION
	else if (document.getElementById('productSelection')) 
	{
		boxen.init({
			formID:'selectForm', 
			totalItemsID:'totalItems', 
			totalCartonsID:'totalCartons', 
			totalProductCostID:'productCost',
			totalPriceID:'totalPrice',
			highlightClass:'productLine',
			freightByWeight:true,
			cartonWeight:1000
		});
	}
	// SHIPPING
	else if (document.getElementById('orderDelivery')) {
		happyError.init('orderDelivery');
		happyShipping.init('freight', 'totalShipping', 'totalPrice');
	}
	// PAYMENT
	else if (document.getElementById('orderBilling')) {
		happyError.init('orderBilling');
		// happySwitch.init(['Customer_Payment_Type_Credit', 'Customer_Payment_Type_Cheque'], 'blockGroup');
		// happyCopy.init('copyDetails', 'shippingAddress');
	}	
}

/*
    Parses the class names of the each of the checkbox inputs in the form with the given 
    parentFormId looking for disables_<ID>, where ID is the html entity identifier that
    should be disabled when the associated element is unchecked. An onclick event is
    attached to the checkbox to control the enabled state of the nominated "disables"   
    nodes.
*/
function disables(parentFormId)
{
    form = document.getElementById(parentFormId);
    allInputs = form.getElementsByTagName('input');
    disableControllers = new Array();

	for (var i = 0; i < allInputs.length; i++) 
	{
	   if (allInputs[i].type == 'checkbox')
	   {
    	   if (allInputs[i].className.indexOf('disables_') != -1) 
    	   {
    	       var disableIds = new Array();
               var disableCount = 0;
               
    	       classes = allInputs[i].className.split(' ');
    		   for (var j = 0; j < classes.length; j++) 
    		   {
        	       if (classes[j].indexOf('disables_') != -1)
        	       {
            	       disableId = classes[j].split('disables_')[1];
            	       disableIds[disableCount] = disableId;
                       disableCount = disableCount + 1;
                   }
        	   }  
        	   
        	   // Store the ids to be controlled in an array keyed by the 
        	   // controlling nodes html identifier.
        	   this.disableControllers[allInputs[i].id] = {
						disableIds:disableIds, 
						node:allInputs[i]
			     };
        	   
        	   // Initialise the states.
        	   for (var disableId in disableIds) 
		      {
	              document.getElementById(disableIds[disableId]).disabled = !allInputs[i].checked;
	          } 
    
   	          allInputs[i].onclick = function()
    	      {     	   
    	          // Get the disable definition for this node.      	         	    
    	          disableDef = disableControllers[this.id];
    	          // For each of the nodes to be disabled.
    		      for (var disableId in disableDef.disableIds) 
    		      {    		          
    	              document.getElementById(disableDef.disableIds[disableId]).disabled = !this.checked;
    	          }
    	      };
    	   }
    	}
	}
}

/*	----------------------------------------------------------------------
	BOXEN 
	Fancy little script to drive our wine carton update interface.
	All packed into an object in order to avoid polluting the namespace. YAY!
	
	The following classes may be used:
		* unitprice_12-30		- Unit price.
		* unitweight_12.34		- Unit Weight, in grams.
		* maximum_12			- Maximum user can buy
		* minimum_2				- Minimum user can buy
		* modifier_1			- For items sold in cases only
		* caseprice_12-30		- Discounted price when user buys a case worth
		* casesize_12			- How large is a case then?
	---------------------------------------------------------------------- */
boxen = {
	inputs: [],
	totalCartons: 0,
	totalItems: 0,
	totalPrice: 0,
	totalPriceNode: 0,
	totalItemsNode: 0,
	totalCartonsNode: 0,
	totalWeight: 0,
	highlightClass: null,
	freightByWeight: false,
	cartonWeight: 1000,
	boxenNode: null,
	bonusNode: null,
	bonusSwitch: null,
	// INITALISE
	// Collect inputs, assign handlers, store config details
	init: function(options) {
		form = document.getElementById(options.formID);
		// Check for bonuses
		if (options.bonusNode) {
			this.bonusNode = document.getElementById(options.bonusNode);
			formClasses = form.className.split(' ');
			for (var h = 0; h < formClasses.length; h++) {
				if (formClasses[h].lastIndexOf('bonusunits') != -1) {
					bonus = formClasses[h].split('_');
					this.bonusSwitch = parseInt(bonus[1]);
				}
			}
		}
		// Init the quantity inputs
		inputs = form.getElementsByTagName('input');
		for (var i = 0; i < inputs.length; i++) {
			if (inputs[i].className.lastIndexOf('quantity') != -1) {
				// set up local vars to store extracted values
				var maximum 	= null;
				var minimum 	= null;
				var unitPrice 	= null;
				var modifier 	= null;
				var casePrice 	= null;
				var caseSize 	= null;
				var unitWeight = null;
				// loop through all the classnames
				classes = inputs[i].className.split(' ');
				for (var j = 0; j < classes.length; j++) {
					// get the modifier
					if (classes[j].lastIndexOf('modifier') != -1) {
						modifier = classes[j].split('_');
						modifier = parseInt(modifier[1]);
					}
					// get the price
					else if (classes[j].lastIndexOf('unitprice') != -1) {
						unitPrice = classes[j].split('_');
						unitPrice = parseFloat(unitPrice[1].replace(/-/, '.')); // Swap out the hypen for a decimal
					}
					// get the maximum
					else if (classes[j].lastIndexOf('maximum') != -1) {
						maximum = classes[j].split('_');
						maximum = parseInt(maximum[1]);
					}
					// get the minimum
					else if (classes[j].lastIndexOf('minimum') != -1) {
						minimum = classes[j].split('_');
						minimum = parseInt(minimum[1]);
					}
					// Case size
					else if (classes[j].lastIndexOf('casesize') != -1) {
						caseSize = classes[j].split('_');
						caseSize = parseInt(caseSize[1]);
					}
					// Case price
					else if (classes[j].lastIndexOf('unitweight') != -1) {
						unitWeight = classes[j].split('_');
						unitWeight = parseFloat(unitWeight[1]);
					}
					
				}
				// Check to see if the modifier is up to date, else set a default
				if (modifier == null) {
					modifier = 1;
				}
				// Store extracted values
				this.inputs[inputs[i].id] = {
						modifier:modifier, 
						node:inputs[i], 
						unitPrice:unitPrice,
						unitWeight:unitWeight,
						totalWeight:0, 
						maximum:maximum,
						minimum:minimum,
						caseSize:caseSize,
						casePrice:casePrice, 
						totalItems:0,
						totalPrice:0, 
						totalNode: document.getElementById(inputs[i].id + 'Total') // This is a bit brittle...
				};
				var boxObj = this;
				inputs[i].onkeyup = function(){boxObj.update(this)};
			}
		}
					  
		// Collect the totals (add tests to see if they are there)
		this.totalPriceNode = document.getElementById(options.totalPriceID);	
		this.totalProductCostNode = document.getElementById(options.totalProductCostID);	
		this.totalItemsNode = document.getElementById(options.totalItemsID);
		this.totalCartonsNode = document.getElementById(options.totalCartonsID);
		// Insert our flash cartons
		if (options.cartonID && options.cartonFlashParams) {			
			var fo = new FlashObject(
				options.cartonFlashParams.url,
				'boxen',
				options.cartonFlashParams.width,
				options.cartonFlashParams.height,
				options.cartonFlashParams.bgColor
				);
			fo.write(options.cartonID);
			this.boxenNode = document.getElementById('boxen');			
		}	
		// See the highlightClass. This is the class used to pick the parent we need to highlight
		if (options.highlightClass) {
			this.highlightClass = options.highlightClass;
		}
		// See how the cartons need to be updated
		if (options.freightByWeight) {
			this.freightByWeight = options.freightByWeight;
		}
		// Need to add code for the flash file later.
		// Update the display... this deals with inital values, and firefox caching values
		this.updateAll();
	},
	// UPDATE TOTALS
	// This is called when a single input has had it's value changed
	update: function(node) {
		this.updateInput(node);
		this.updateTotals();
	},
	// UPDATE TOTALS FOR ONE INPUT
	updateInput: function(node) {
		input = this.inputs[node.id];
				
		// Basic validation
		if (isNaN(node.value)) {
			happyError.showErrors(node, ['Please use whole numbers only']);
			var newValue = parseInt(node.value);
			if (isNaN(newValue)) {
				node.value = 0;
			}
		}
		// has some spoobie typed in a decimal?
    	else if (Math.round(node.value) != node.value) {
    		happyError.showErrors(node, ['Please use whole numbers only']); // Spit the dummy
    		node.value = Math.round(node.value); // Reset it to a nicer value
    	}
		else if (node.value < 0)
		{
		   node.value = Math.abs(node.value);
	       happyError.showErrors(node, ['Please use whole, positive numbers only']);
		}
						
			
		// Validate maximum
		if (input.node.value > input.maximum && input.maximum != null) {
			input.node.value = input.maximum;
			// Display an error stating the maximum
		}
		// Validate minimum
		if (input.node.value < input.minimum && input.minimum != null && input.node.value != 0) {
			input.node.value = input.minimum;
			// Display an error stating the minimum
		}
		// Check to for case modifiers
		if (input.caseSize != null) {
			if (input.node.value >= input.caseSize) {
				// Figure out how many cartons
				var remainder = input.node.value % input.caseSize;
				var cartons = (input.node.value - remainder) / input.caseSize;
				input.totalPrice = (cartons * input.casePrice) + (remainder * input.unitPrice);
			}
			else {
				input.totalPrice = input.unitPrice * input.node.value;
			}
		}
		else {
			input.totalPrice = input.unitPrice * input.node.value;
		}
		// Total weight. Case weight is accounted for using the modifier
		if (this.freightByWeight) {
			input.totalWeight = (input.node.value * input.modifier) * input.unitWeight;
		}
		// Then figure out the totals items
		input.totalItems = input.node.value * input.modifier;
		
		// Update the total node
		input.totalNode.innerHTML = currencyFormatter(input.totalPrice);
		// Check to see if we need to toggle the highlight too
		if (this.highlightClass != null) {
			if (node.value > 0) {
				this.toggleHighlight(node, true);
			}
			else {
				this.toggleHighlight(node, false);
			}
		}
		
	},
	toggleHighlight: function(node, state) {
		target = node;
		do {
			target = target.parentNode;
		} while(target.className.lastIndexOf(this.highlightClass) == -1)
		
		// If the state of the highlight is going to change.
		if (state != hasClass(target, 'highlight'))
		{
    		if (state == true) {
    			addClass(target, 'highlight');
    		}
    		else {
    			removeClass(target, 'highlight');
    		}
    		
    		// If this parent is not the first
    		if (hasClass(target, 'first') == false)
    		{
    		    // Find the first sibling for this product.
        		sibling = target;
        		do {
        			sibling = getPreviousSibling(sibling);
        		} while(sibling != null && hasClass(sibling, 'first') == false && hasClass(sibling, 'productLine') == true)
        		
        		if (sibling != null)
        		{
            		if (state == true) {
            			addClass(getFirstChild(sibling), 'highlight');
            		}
            		else {
            			removeClass(getFirstChild(sibling), 'highlight');
            		}
            	}
        	}
        }

	},
	// UPDATE TOTAL
	// This loops through all the stored inputs, builds the totals
	// and then updates the total display. Also calls the flash update
	updateTotals: function() {		
		this.totalItems 	= 0;
		this.totalPrice 	= 0;
		this.totalWeight 	= 0;
		// May need to change depending if we use a has or literal
		for (var i in this.inputs) {			
			// IE 5 add an additional property to the hash table. We need to exclude it
			if (this.inputs[i].totalItems) {
				this.totalItems 	+= this.inputs[i].totalItems;
				this.totalPrice 	+= this.inputs[i].totalPrice;
				this.totalWeight 	+= this.inputs[i].totalWeight;
			}
		}

		// Update displays
		this.totalItemsNode.innerHTML = this.totalItems;		
		this.totalProductCostNode.innerHTML = currencyFormatter(this.totalPrice);
		this.totalPriceNode.innerHTML = currencyFormatter(this.totalPrice);		
		// Do we need to display the bonus?
		if (this.bonusNode != null) {
			if (this.totalItems >= this.bonusSwitch) {
				this.bonusNode.style.visibility = 'visible';
			}
			else {
				this.bonusNode.style.visibility = 'hidden';
			}
		}
		this.updateCartonTotal();
	},
	// UPDATE CARTON TOTAL CARTONS
	// Work out total cartons based on the weight
	updateCartonTotal: function() {
	   if (this.totalCartonsNode != null)
		{
    		// Figure out total cartons
    		if (this.freightByWeight == true) {
    			remainder = this.totalWeight % this.cartonWeight
    			this.totalCartons = this.totalWeight / this.cartonWeight;
    			this.totalCartons = (this.totalWeight - remainder) / this.cartonWeight;
    			if (remainder != 0) {
    				this.totalCartons += 1;
    			}
    		}
    		else {
    			remainder = this.totalItems % 12;
    			this.totalCartons = (this.totalItems - remainder) / 12;
    			if (remainder != 0) {
    				this.totalCartons += 1;
    			}
    		}
    		this.totalCartonsNode.innerHTML = this.totalCartons;
    		// Update the flash file
    		if (this.boxenNode != null) {			
    			this.updateCarton(this.totalItems);
    		}
    	}
	},
	// UPDATE ALL THE INPUT TOTALS
	// Usually used when we first load up the page
	updateAll: function() {
		for (var i in this.inputs) {
			if (this.inputs[i].node) {
				this.updateInput(this.inputs[i].node);
			}
		}
		this.updateTotals();
	},
	// UPDATE CARTON
	// This updates the flash file. Pretty simple right now but
	// will get a bit more complicated when we change our flash file
	// For now it's not even checking if it's loaded
	updateCarton: function(items) {
		if (this.boxenNode.PercentLoaded() == 100) {
			this.boxenNode.GotoFrame(items);
		}
		else {
			box = this.boxenNode;
			window.setTimeout('box.GotoFrame(' + items + ')', 2000)
		}
	}
}

/*	----------------------------------------------------------------------
	VALIDATION 
	Some simple validations. These are packed into a literal object to
	prevent name collisions. The logic may improve in the future, but
	the interface will likely remain the same.
	
	May need something to deal with dependencies. IE, if we have a
	credit number, we need to know the card type to validate it.
	Same with checking the expiry on a card.
	
	Works with one form at a time.
	
	Handles the following validations
		* he-Required 		- Must not be empty
		* he-Integer		- Whole numbers only
		* he-Float			- Numbers only
		* he-Email			- Valid email address
		* he-Phone			- Valid australian phone number
		* he-State			- Valid country dependant on state
		* he-CreditNumber	- Valid credit card
		* he-CreditType	- Credit card vendor
		* he-CreditYear	- Valid expiry for credit card
		* he-CreditMonth	- Valid expiry for credit card
		* he-Depend_ID 	- A dependent field
	---------------------------------------------------------------------- */
happyError = {
	formNode: null,
	errorNode: null,
	errorTimeout: null,
	inputs: [],
	required: [],
	hasRequiredFields: false,
	// INITALISE
	// Figures out what the items need to be validated against, and
	// stores em for later. Attaches the events.
	init: function(formID) {
		this.formNode = document.getElementById(formID);
		// Get the inputs
		var inputs = this.formNode.getElementsByTagName('input');
		this.extractValidations(inputs);
		// Get the textareas
		var textareas = this.formNode.getElementsByTagName('textarea');
		this.extractValidations(textareas);
		// Get the selects
		var selects = this.formNode.getElementsByTagName('select');
		this.extractValidations(selects);
		// If it has required fields, attach this 'ere event
		if (this.hasRequiredFields) {
			var errorObj = this;
			this.formNode.onsubmit = function(){errorObj.checkRequiredFields();return false;}
		}
	},
	// EXTRACT VALIDATIONS
	// This extracts the validations, and attaches events
	extractValidations: function(nodes) {
		for (var i = 0; i < nodes.length; i++) {
			if (nodes[i].className) {
				classes = nodes[i].className.split(' ');
				var validations = [];
				var dependencies = null;
				for (var j = 0; j < classes.length; j++) {
					// See if this class name has the validation prefix
					if (classes[j].lastIndexOf('he-') != -1) {
						validation = classes[j].split('-');
						validation = validation[1];						
						// Check for required fields
						if (validation == 'required') {
							this.required.push(nodes[i]);
							this.hasRequiredFields = true;
						}
						// Check for dependency
						// Only supports single dependency for now
						else if (validation.lastIndexOf('depend') != -1) {
							dependencies = validation.split('_');
							dependencies = dependencies[1];
						}
						else {
							validations.push(validation);
						}
					}									
					
					// Only store it if we actually have any validations
					if (validations.length > 0) {
						this.inputs[nodes[i].id] = {
							node:nodes[i],
							validations:validations,
							dependencies:dependencies														
						}
						
						var errorObj = this;
        				nodes[i].onchange = function(){errorObj.validate(this);} 					  
					}
				}
			}
		}	
	},
	// VALIDATE AN INPUT
	// Node is required. Validations is option. Is an array of strings.
	// If it is provided, those validations are used instead of any cached.
	validate: function(node, optValidations) {
		// Check for optional validations (non-cached)
		var validations;
		if (optValidations) {
			validations = optValidations;
		}
		else {
			validations = this.inputs[node.id].validations;
		}
		// Add logic for checkboxes
		var value = node.value;
		var errors = [];
		// Validate
		for (var i = 0; i < validations.length; i++) {
			var check = this['valid_' + validations[i]](node);
			if (check != true) {
				errors.push(check);
			}
		}
		// Display errors if we have any
		// In the future have validations return an array.
		// true/false, message, node
		if (errors.length > 0) {
			this.showErrors(node, errors);
		}
	},
	// CHECK REQUIRED FIELDS
	checkRequiredFields: function() {
		var invalid = false;
		for (var i = 0; i < this.required.length; i++) {
			if (this.required[i].value == '' || this.required[i].value == ' ') {
				invalid = true;
				// highlight the field
				addClass(this.required[i], 'attention');
			}
			else
			{
                removeClass(this.required[i], 'attention');
			}		
		}
		if (invalid) {
			alert('There are required fields you need to fill out.\n These are highlighted.');
			return false;
		}
		else {
			this.formNode.submit();
		}
	},
	// ERROR CACHE
	addError: function() {
		
	},
	// SHOW ERRORS
	// Gets passed a node and an array of error messages. Displays em. Easy.
	// Optionally we may do some stuff to allow different displays.
	showErrors: function(node, messages) {
		// Collect the errors into a string
		var message = '';
		for (var i = 0; i < messages.length; i++) {
			message += messages[i] + '\n';
		}
		//alert(message);
		// Inialise the error display
		if (this.errorNode == null) {
			this.errorNode = document.createElement('div');
			this.errorNode.className = 'error';
		}
		else {
			// make sure it's not sitting in the document
			this.hideErrors();
		}
		this.errorNode.innerHTML = message;
		// Position it, stick it in the page
		node.parentNode.style.position = 'relative';
		this.errorNode.style.left = node.offsetLeft + node.offsetWidth + 'px';
		this.errorNode.style.top = node.offsetTop + 'px';
		node.parentNode.appendChild(this.errorNode);
		// Add events to hide the error 
		errorObj = this;
		node.onfocus = function() {errorObj.hideErrors();}
		// I HATE HAVING TO PASS A GOD_DAMN STRING!
		this.errorTimeout = window.setTimeout('happyError.hideErrors()', 4000);
	},
	hideErrors: function() {
		if (this.errorNode.parentNode) {
			parentNode = this.errorNode.parentNode;
			parentNode.removeChild(this.errorNode);
		}
		window.clearTimeout(this.errorTimeout);
	},
	// VALIDATIONS
	// Oh, lot's of 'em! Either return true, or a string, which is the error message
	valid_integer: function(node) {
		value = node.value
		var filter  = /^(\(?\+?[0-9]*\)?)?[0-9_\- \(\)]*$/;
		if (!filter.test(value.toString())) {
			var error = 'Whole numbers only.';
			return error;
		}
		else {
			return true;
		}
	},
	valid_phone: function(node) {
		value = node.value;
		var filter  = /^(\(?\+?[0-9]*\)?)?[0-9_\- \(\)]*$/;
		if (!filter.test(value.toString())) {
			var error = 'Numbers only for your phone or fax number.';
			return error;
		} 
		else {
			return true;
		}
	},
	valid_state: function(node) {	   
		value = node.value;
		dependentID = this.inputs[node.id].dependencies;
		if (value == "Outside Australia")
		{
		  document.getElementById(dependentID).value = "";
		}
		else
		{
		  document.getElementById(dependentID).value = "Australia";
		}
		return true;
	},
	valid_email: function(node) {
		value = node.value;
		var x = value;
		var filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
		if (!filter.test(x)) {
			var error = 'This is not a valid email address.';
			return error;
		}
		else {
			return true;
		}
	},
	valid_creditNumber: function(node) {	   
		value = node.value;
		dependentID = this.inputs[node.id].dependencies;
		cardType = document.getElementById(dependentID).value;
		return this.checkCreditNumber(value, cardType);
	},
	valid_creditType: function(node) {
		value = node.value;
		numberNode = document.getElementById(this.inputs[node.id].dependencies);
		cardNumber = numberNode.value;
		return this.checkCreditNumber(cardNumber, value);
		// can't reset the target node with this style
	},
	checkCreditNumber: function(value, cardType) {
		if (isNaN(value)) {
			error = 'Please use numbers only (no spaces)';
			return error;
		}
		// Check the card type and do the checks based on that
		var cardNumber = value.toString();
		var startTest;
		switch(cardType) {
			case 'Visa':
				// VISA
				startTest = parseInt(cardNumber.slice(0, 1));
				if (startTest != 4) {
					error = 'This is not a valid Visa number';
					return error;
				}
			break;
			case 'MasterCard':
				// MASTERCARD
				startTest = parseInt(cardNumber.slice(0, 1));
				if (startTest != 5) {
					error = 'This is not a valid Mastercard number';
					return error;
				}
			break;
			case 'American Express':
				// AMERICAN EXPRESS
				startTest = parseInt(cardNumber.slice(0, 2));
				if (startTest != 37) {
					error = 'This is not a valid American Express number';
					return error;					
				}
			break;
			case 'BankCard':
				// BANKCARD
				startTest = parseInt(cardNumber.slice(0, 4));
				if (startTest != 5610) {
					error = 'This is not a valid BankCard number';
					return error;					
				}
			break;
			case 'Diners':
				// DINERS CLUB
				startTest = parseInt(cardNumber.slice(0, 1));
				if (startTest != 3) {
					error = 'This is not a valid Diners Club number';
					return error;					
				}
			break;
		}
		return true;
	},
	valid_creditYear: function(node) {
		value = node.value;
		if (isNaN(value) || value == 0) {
			error = 'This is not a valid year';
			return error;
		}		
		else {
			today = new Date();
			// turn the test value into an oughtie number
			var testValue = value.toString();
			testValue = parseInt(20 + testValue);
			// Now lets check it out
			if (testValue <= today.getFullYear() - 1) {
				error = 'This expiry date is invalid';
				return error;
			}
			else {
				return true;
			}
		}
	},
	valid_creditMonth: function(node) {
		value = node.value;
		if (value > 12 || isNaN(value) || value == 0) {
			error = 'Please use a value between 1 and 12'
			return error;
		}
		else {
			return true;
		}
	},
	// UTILITY METHODS FOR VALIDATION
	getDependentNode: function(nodeID) {
		return document.getElementById(this.inputs[nodeID].dependencies);
	}
}

/*	----------------------------------------------------------------------
	SHIPPING
	Doesn't do too much. It just updates the totals when the user changes
	the shipping.
	---------------------------------------------------------------------- */
happyShipping = {
	inputs: [],
	shippingNode: null,
	totalPriceNode: null,
	totalPrice: 0,
	init: function(optionBoxID, shippingID, totalID) {
		// Collect inputs and prices
		var box = document.getElementById(optionBoxID);
		var inputs = box.getElementsByTagName('input');
		for (var i = 0; i < inputs.length; i++) {
			if (inputs[i].className.lastIndexOf('shippingDestination') != -1) {
				price = inputs[i].className.slice((inputs[i].className.lastIndexOf('price_')+6), inputs[i].className.length)
				price = parseFloat(price); // Dirty hack coz it returns a match and sub-match
				this.inputs[inputs[i].id] = {node:inputs[i], price:price};
				// Attach events
				shippingObj = this;
				inputs[i].onclick = function() {shippingObj.update(this);}
			}
		}
		// Get the total nodes and their prices
		this.shippingNode = document.getElementById(shippingID);
		this.totalPriceNode = document.getElementById(totalID);
		this.totalPrice = this.extractPrice(this.totalPriceNode.innerHTML);
	},
	extractPrice: function(oldPrice) {
		newPrice = parseFloat(oldPrice.slice(1, oldPrice.length));
		return newPrice;
	},
	update: function(node) {
		shipping = this.inputs[node.id].price;
		newPrice = currencyFormatter(this.totalPrice + shipping);
		this.shippingNode.innerHTML = currencyFormatter(shipping);
		this.totalPriceNode.innerHTML = newPrice;
	}
}

/* 
Invoked when a membership type has been selected. The fee and type nodes are 
updated to display the selected option.
*/
function membershipTypeSelected(node) {
    var membershipType;
    var membershipFee;

    // Get the nodes that will be updated.
    membershipTypeNode = document.getElementById('membershipType');
    membershipFeeNode = document.getElementById('membershipFee');

    // The class name can include a number of parameters separated by a space. 
    // Extract the individual parameters.
   classNameParts = node.className.split(" ");
   // For each of the parameters in the class name string.
   for (namePartIndex in classNameParts)
   {
        classNamePart = classNameParts[namePartIndex];
        // If the part is the fee.
        if (membershipFeeNode != null && classNamePart.indexOf('fee') == 0)
        {
            // Get the substring that is the fee. (It's 4 characters in cos we need to 
            // remove the 'fee_' prefix.)
            membershipFee = classNamePart.slice(4, classNamePart.length);
        }
        // If the part is the membership type.
        else if (membershipTypeNode != null && classNamePart.indexOf('type') == 0)
        {
            // Get the substring that is the membership type. (It's 5 characters in cos we need to 
            // remove the 'type_' prefix.)
            membershipType = classNamePart.slice(5, classNamePart.length);
            // Replace all the unserscores with spaces.
            membershipType = membershipType.replace(/_/g, " ");
        }	
    }
    
    membershipTypeNode.innerHTML = membershipType;
    membershipFeeNode.innerHTML = currencyFormatter(membershipFee);
}

/* 
Invoked when a donation type has been selected. The amount and type nodes are 
updated to display the selected option.
*/
function donationTypeSelected(node) {
    var donationType;

    // Get the nodes that will be updated.
    donationTypeNode = document.getElementById('donationType');
    donationAmountNode = document.getElementById('donationAmount');

    // The class name can include a number of parameters separated by a space. 
    // Extract the individual parameters.
   classNameParts = node.className.split(" ");
   // For each of the parameters in the class name string.
   for (namePartIndex in classNameParts)
   {
        classNamePart = classNameParts[namePartIndex];
        if (donationTypeNode != null && classNamePart.indexOf('type') == 0)
        {
            // Get the substring that is the donation type. (It's 5 characters in cos we need to 
            // remove the 'type_' prefix.)
            donationType = classNamePart.slice(5, classNamePart.length);
        }	
    }
    
    // Find the donation type's amount element.
    donationAmountSourceNode = document.getElementById(node.id + "_amount");
    
    // Replace all the unserscores with spaces.
    donationTypeNode.innerHTML = donationType.replace(/_/g, " ");
    donationAmountNode.innerHTML = currencyFormatter(parseInt(donationAmountSourceNode.value));
}

/* Invoked when one of the donation amounts is changed. If the amount is for the 
currently selected donation type, then the payment amount is updated.
*/
function donationAmountChanged(node)
{
    var donationType;
    var donationAmount;
    
    // Extract the type of donation from the amount node's class name.
    nodeId = node.id;
    donationType = nodeId.substring(0, nodeId.length - 7);

    // Find the donation type element.
    donationTypeNode = document.getElementById(donationType);
    // If the donation type node is checked.
    if (donationTypeNode.checked)
    {
        // Extract the value and update the payment amount node.
        donationAmount = node.value;
        donationAmountNode = document.getElementById('donationAmount');
        donationAmountNode.innerHTML = currencyFormatter(parseInt(donationAmount));
    }
    

}

/*	----------------------------------------------------------------------
	UTILITY FUNCTIONS
	Functions for some more common stuff. Mainly formatting, but also
	some short cuts to DOM methods.
	---------------------------------------------------------------------- */
// CURRENCY FORMATTER
function currencyFormatter(amount) {
	var i = parseFloat(amount);
	if(isNaN(i)) { i = 0.00; }
	var minus = '';
	if(i < 0) { minus = '-'; }
	i = Math.abs(i);
	i = parseInt((i + .005) * 100);
	i = i / 100;
	s = new String(i);
	if(s.indexOf('.') < 0) { s += '.00'; }
	if(s.indexOf('.') == (s.length - 2)) { s += '0'; }
	s = minus + s;
	return '$' + s;
}

// CREATE DOM NODE SHORTCUT
// ADD/REMOVE CLASSNAME
function addClass(element, className) {
	var classes = element.className.split('');
	// Check to see if it exists, If it does skip out
	for (var i = 0; i < classes.length; i++) {
		if (classes[i] == className) {
			return;
		}
	}
	// Otherwise add it
	element.className = element.className + ' ' + className;
}
function removeClass(element, className) {
	var newClass = '';
	var classes = element.className.split(' ');
	var classIndex = 0;
	for (; classIndex < classes.length; classIndex++) {
		if (classes[classIndex] != className) {
			newClass += classes[classIndex] + ' ';
		}
		else
		{
			// Stop now so we just remove the first occurrence.
			classIndex = classIndex + 1;
			break;
		}
	}
	// Add each of the remaining classes.
	for (; classIndex < classes.length; classIndex++) {
			newClass += classes[classIndex] + ' ';
	}
	// set the class
	element.className = rightTrim(newClass);
}
function hasClass(element, className) {
    elementClassName = element.className;
    if (elementClassName != null)
    {
    	var classes = elementClassName.split(' ');
    	// Check to see if it exists, If it does skip out
    	for (var i = 0; i < classes.length; i++) {
    		if (classes[i] == className) {
    			return true;
    		}
    	}
    }
	return false;
}
/* 
* Use this instead of node.previousSibling to protect against FireFox 
* returning newline and whitespace as the sibling.
*/
function getPreviousSibling(startBrother){
  endBrother=startBrother.previousSibling;
  while(endBrother != null && endBrother.nodeType!=1){
    endBrother = endBrother.previousSibling;

  }
  return endBrother;
}
/* 
* Use this instead of node.nextSibling to protect against FireFox 
* returning newline and whitespace as the sibling.
*/
function getNextSibling(startBrother){
  endBrother=startBrother.nextSibling;
  while(endBrother != null && endBrother.nodeType!=1){
    endBrother = endBrother.nextSibling;

  }
  return endBrother;
}
/* 
* Use this instead of node.firstChild to protect against FireFox 
* returning newline and whitespace as the child.
*/
function getFirstChild(parent){  
  child = parent.firstChild;
  
  while(child != null && child.nodeType!=1){
    child = getNextSibling(child);

  }
  return child;
}
// TRIM STRING
function leftTrim(sString) {
	while (sString.substring(0,1) == ' ') {
		sString = sString.substring(1, sString.length);
	}
	return sString;
}
function rightTrim(sString) {
	while (sString.substring(sString.length-1, sString.length) == ' ') {
		sString = sString.substring(0,sString.length-1);
	}
	return sString;
}

window.onload = initialise;