function validator()
{
	this.handler;
	this.Add = _add;
	this.Required = _required;
	this.Compare = _compare;
	this.Validate = _validate;
	this.Error = _error;
	this.Reset = _reset;
	
	var errs = [];
	var vals = [];
	
	function _add(v, p)
	{
		vals.push({val : v, params : p});
	}
	
	function _reset()
	{
		errs = [];	//reset the errors list
	}
	
	function _validate()
	{
		errs = [];
		$('label').removeClass('errortext');	//remove labels' error class
		$('input, select, textarea').removeClass('errorinput');	//remove inputs' error class
		for (i=0, c=vals.length; i<c; i++)
		{
			switch (vals[i].val)
			{
				case 'required':
					_required(vals[i].params);
					break;
				case 'compare':
					_compare(vals[i].params);
					break;
				case 'email':
					_email(vals[i].params);
					break;
			  case 'postal':
          _postal(vals[i].params);
          break;
			  case 'phone':
          _phone(vals[i].params);
          break;
        case 'dateBefore':
          _dateBefore(vals[i].params);
          break;
        case 'requiredGroup':
          _requiredGroup(vals[i].params);
          break;
			}
		}
		
		if (errs.length > 0)
		{
			alert('Please fix the following errors to proceed: \n - ' + errs.join('\n - '));
			return false;
		}
		return true;
	}
	
	function _required(p)
	{
		if ($('#' + p.id).length == 0) return;
		if ($('#' + p.id + ':visible').length == 0) return;
		if (p.type)
		{
		}
		if ((p.type == 'checkbox' || p.type == 'radio') && $('#' + p.id + ':checked').length == 0) _error(p.id, p.msg);
		else if ($('#' + p.id).val().length < p.length) _error(p.id, p.msg);
		
	}
	
	function _compare(p)
	{
		if ($('#' + p.id).length == 0) return;
		if ($('#' + p.id).val() && $('#' + p.id).val() != $('#' + p.compare_to).val()) _error(p.id, p.msg);
	}
	
	function _email(p)
	{
		if ($('#' + p.id).length == 0) return;
		if (!$('#' + p.id).val() && p.required == 0) return; //don't check email if not provided and not required
		if (!/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$/.test($('#' + p.id).val())) _error(p.id, p.msg);
	}
	
	function _postal(p) {
	  
	  if ($('#' + p.id).length == 0) return;
	  if (!$('#' + p.id).val() && p.required == 0) return; //don't check postal/zip if not provided and not required
	  if ($('#country').val() == 'CA') {
      // Check for valid Canadian postal code
	    if (!/^[a-zA-Z]{1}[0-9]{1}[a-zA-Z]{1}\s?[0-9]{1}[a-zA-Z]{1}[0-9]{1}$/.test($('#' + p.id).val())) _error(p.id, p.msg);	    
	  } else if ($('#country').val() == 'US') {
	    // Check for valid US zip code
	    if (!/^[0-9]{5,5}([-\s]?[0-9]{4,4})?$/.test($('#' + p.id).val())) _error(p.id, p.msg);
	  }
	}
	
	function _phone(p) {
	  
	  if ($('#' + p.id).length == 0) return;
		if (!$('#' + p.id).val() && p.required == 0) return; //don't check phone if not provided and not required
		if (/[^0-9-()\s]/.test($('#' + p.id).val())) _error(p.id, p.msg);
	}
	
	function _dateBefore(p) {
	  var fromDate;
	  var toDate;
	  
	  if ($('#' + p.fromDate).length == 0 || $('#' + p.toDate).length == 0) return;
	  if (!$('#' + p.fromDate).val() || !$('#' + p.fromTime).val()) return;
	  
	  // Ensure that if one of toDate or toTime is specified that the other is as well
	  if ($('#' + p.toDate).val() && !$('#' + p.toTime).val()) {
	    _error (p.toTime, "End time is required");
	    return;
	  } else if	(!$('#' + p.toDate).val() && $('#' + p.toTime).val()) {
	    _error (p.toDate, "End date is required");
	    return;
	  }
	  
	  // Convert strings to Javascript Dates, NOTE:  expected format it DATE_FORMAT (in common.js)
	  fromDate = new Date ($('#' + p.fromDate).val() + ' ' + _militaryTime($('#' + p.fromTime).val()));
	  toDate = new Date ($('#' + p.toDate).val() + ' ' + _militaryTime($('#' + p.toTime).val()));
	  
	  if (p.strict && fromDate > toDate) {
	    _error(p.fromDate, p.msg);
	  } else if (fromDate >= toDate) {
	    _error(p.fromDate, p.msg);
	  }
	}
	
	function _requiredGroup (p) {
	  
	  if ($(p.selector).length == 0) return;
	  
	  $(p.selector + ':visible').each(function () {
      
	    if (!$(this).val()) {
	    
        _error(this.id, p.msg);
        return;
      }
	  });
	}
	
	function _error(id, msg)
	{
		errs.push(msg);
		if ($('#lbl_' + id).length > 0) 
		{
			$('#lbl_' + id).addClass('errortext');
			$('#' + id).addClass('errorinput');
		}
	}
	
	function _militaryTime(time) {
	  
	  // Remove whitespace
	  time = time.replace(/^\s*|\s*$/g, '');  // NOTE:  g modifier matches all occurences of the regular expression
	  tod = time.substring (time.length - 2, time.length);
	  if (tod == 'PM') {
	    var hour = time.substring(0, 2);
	    
	    hour = hour.replace (/^0*/, '');
	    time = parseInt(hour) + 12 + time.substring (2);
	  }
	  return time.substring(0, time.length - 2);
	}
}
