// /////////////////////////////////////////////////////////////////////////////////////////////////
// Application:  Website
//        File:  local/CSCC.Validation.js
//      Author:  Andrew Deans (deansa@clarkstate.edu)
//     Created:  08 October 2008
//     Updated:  22 June 2009
// Description:  Functions used to handle form validation.
//   Changelog:  22 June 2009
//                 - Copied from www.clarkstate.edu
// /////////////////////////////////////////////////////////////////////////////////////////////////

// If our namespace object does not exist go ahead and create it.
if (!CSCC) var CSCC = new Object();
CSCC.Validation = new Object();



// FormErrors //////////////////////////////////////////////////////////////////////////////////////
// This is an associative array containing an error message for any fields that fail validation.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.FormErrors = Object();



// Validator ///////////////////////////////////////////////////////////////////////////////////////
// Function pointer to the routine used to validate the contents of the fields on this form.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.Validator = function(FieldId) { CSCC.Validation.FormErrors[FieldId] = ''; };



// TaggedField /////////////////////////////////////////////////////////////////////////////////////
// Contains the id of the field our error bubble is currently pointing at.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.TaggedField = null;



// AttachValidationHandlers ////////////////////////////////////////////////////////////////////////
// Attaches validation event handlers to the referenced field.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.AttachValidationHandlers = function(Field)
{
    var currentEvent;
    
    if (!Field) return;
    
    var blurEvent = 'document.getElementById(\'%ID%\').attachEvent(\'onblur\', function() {' +
                    'document.getElementById(\'%ID%\').firstClick=false;' +
                    'CSCC.Validation.UpdateForm(\'%ID%\', true);' +
                    '});';
    var eventHandler = 'document.getElementById(\'%ID%\').attachEvent(\'%EVENT%\', function() {' +
                       'CSCC.Validation.UpdateForm(\'%ID%\', false);' +
                       '});';

    // Reset the error text for this field and attach the validation event handlers.
    CSCC.Validation.FormErrors[Field.id] = null;

    // Internet Explorer does not allow for attaching event handlers through setAttribute like other
    // browsers do. Go figure. So if we are running in IE we will need to pull a couple fancy tricks
    // to get the events to attach properly.
    if (!CSCC.IsInternetExplorer())
    {
        currentEvent = Field.getAttribute('onblur') ? Field.getAttribute('onblur') : '';
        Field.setAttribute('onblur',   'this.firstClick=false; CSCC.Validation.UpdateForm(\'' + Field.id + '\', true);');
        
        currentEvent = Field.getAttribute('onchange') ? Field.getAttribute('onchange') : '';
        Field.setAttribute('onchange', 'CSCC.Validation.UpdateForm(\'' + Field.id + '\', false);' + currentEvent);
        
        currentEvent = Field.getAttribute('onclick') ? Field.getAttribute('onclick') : '';
        Field.setAttribute('onclick',  'CSCC.Validation.UpdateForm(\'' + Field.id + '\', false);' + currentEvent);
        
        currentEvent = Field.getAttribute('onfocus') ? Field.getAttribute('onfocus') : '';
        Field.setAttribute('onfocus',  'CSCC.Validation.UpdateForm(\'' + Field.id + '\', false);' + currentEvent);
        
        currentEvent = Field.getAttribute('onkeyup') ? Field.getAttribute('onkeyup') : '';
        Field.setAttribute('onkeyup',  'CSCC.Validation.UpdateForm(\'' + Field.id + '\', false);' + currentEvent);
    }
    else
    {
        setTimeout(blurEvent.replace(/%ID%/g, Field.id), 1);
        setTimeout(eventHandler.replace(/%ID%/g, Field.id).replace(/%EVENT%/, 'onchange'), 1);
        setTimeout(eventHandler.replace(/%ID%/g, Field.id).replace(/%EVENT%/, 'onclick'), 1);
        setTimeout(eventHandler.replace(/%ID%/g, Field.id).replace(/%EVENT%/, 'onfocus'), 1);
        setTimeout(eventHandler.replace(/%ID%/g, Field.id).replace(/%EVENT%/, 'onkeyup'), 1);
    }
    
    // Ensure we do not validate a field until after the user has had a chance to enter something.
    Field.firstClick = true;
}



// InitializeForm //////////////////////////////////////////////////////////////////////////////////
// Takes care of any upfront work required for validation. Mostly this consists of attaching
// event handlers to the form fields.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.InitializeForm = function(Fields)
{
    var p, q;
    var field;
    
    CSCC.Validation.FormErrors = Object();
    
    // Loop through every field passed to us for validation.
    for (p = 0; p < Fields.length; p++)
    {
        field = document.getElementById(Fields[p]);
        if (!field) continue;

        // Reset the error text for this field and attach the validation event handlers.
        CSCC.Validation.FormErrors[field.id] = null;
        
        // Attach the validation event handlers to the field.
        CSCC.Validation.AttachValidationHandlers(field);
        
        // Certain groups of controls, such as radio buttons, have numeric index appended to their ids. We'll
        // want to loop through them and add event handlers to them as well.
        q = 0
        while (subfield = document.getElementById(field.id + '_' + q))
        {
            CSCC.Validation.AttachValidationHandlers(subfield);            
            q++;
        }
    }
}



// UpdateForm //////////////////////////////////////////////////////////////////////////////////////
// Update's the validation state of all forms registered for validation (those that were passed
// into InitializeForm.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.UpdateForm = function(CurrentField, Blur)
{
    var thisField = document.getElementById(CurrentField);
    var tag       = document.getElementById('FormTag');
    var message   = document.getElementById('FormTag_Message');
    var field;
    
    if (!CurrentField) return;
    
    // If this is the first time the user has clicked this field then we'll give them a
    // chance to do what they're supposed to before we yell at them.
    if (thisField.firstClick) return;

    CSCC.Validation.Validator(CurrentField);

    //tag.style.display = 'none';
    
    for(fieldId in CSCC.Validation.FormErrors)
    {
        field = document.getElementById(fieldId);
        if (!field) continue;

        if (CSCC.Validation.FormErrors[fieldId])
        {
            CSCC.AddCssClass(field, "FormError");
            field.firstClick = false;
            
            if (fieldId == CurrentField)
            {
                tag.style.top  = (CSCC.OffsetTop(field) + (field.tagName == 'SELECT' ? 10 : (field.offsetHeight / 2)) - 23) + 'px';
                tag.style.left = (CSCC.OffsetLeft(field) + field.offsetWidth + 11) + 'px';
                tag.style.display = Blur ? 'none' : '';
            
                message.innerHTML = CSCC.Validation.FormErrors[fieldId];
                CSCC.Validation.TaggedField = CurrentField;
            }
        }
        else
        {
            CSCC.RemoveCssClass(field, "FormError");
            if (fieldId == CSCC.Validation.TaggedField)
            {
                tag.style.display = 'none';
                CSCC.Validation.TaggedField = null;
            }
        }
    }
}



// SubmitForm //////////////////////////////////////////////////////////////////////////////////////
// Handles the final validation check before a form is submitted. The onsubmit event attribute on
// the form tag should contain a call to this function.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.SubmitForm = function()
{
    var field;
    var errorField;

    for (fieldId in CSCC.Validation.FormErrors)
    {
        field = document.getElementById(fieldId);
        if (!field) continue;
        
        field.firstClick = false;
        
        CSCC.Validation.Validator(fieldId);
        if (CSCC.Validation.FormErrors[fieldId])
        {
            CSCC.AddCssClass(field, "FormError");
        }
        
        if (!errorField && CSCC.Validation.FormErrors[fieldId])
            errorField = field;
    }
    
    if (errorField)
    {
        errorField.focus();
        CSCC.Validation.UpdateForm(errorField.id);
        alert('Please correct the highlighted fields before submitting');
        
        return false;
    }
    
    return true;
}



// VerifyCcNumber //////////////////////////////////////////////////////////////////////////////////
// Verifies that the given credit card account number is valid. We provide a test value
// (1234123412341234) that bypasses verification.
//
// Parameters:
//   Number             The identification number to be verified.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.VerifyCcNumber = function(Number)
{
    var parity;
    var total;
    var p;
    
    // Strip out any non-numeric characters.
    Number = Number.replace(/\D/g, '');
    
    // Always return true if we were given the test cc number. 
    if (Number == '1234123412341234') return true;
    
    // All the credit cards we accept have sixteen digit account numbers.
    if (Number.length != 16)          return false;
    
    // Each type of card has their own prefixes that their account numbers begin with. We can use
    // this to verify that the number looks like one we accept.
    if (!Number.match(/^4[0-9]+$/) &&       // Visa
        !Number.match(/^5[1-5][0-9]+$/) &&      // MasterCard
        !Number.match(/^(6011|6011[2-4]|601174|60117[7-9]|6011[8-9][0-9]|622[0-9]{3}|644|65)[0-9]+$/))  // Discover
    {
        return false;
    }

    // Next we'll run the number through the Luhn algorithm. This is a simple checksum algorithm that
    // can be used to verify many different types of identification numbers.
    parity = Number.length % 2;
    total = 0;
    
    for (p = Number.length - 1; p >= 0; p--)
    {
        // Grab the number at our current position. Note the useless looking '- 0'. That is there to
        // ensure that JS casts the string to an integer (Bloody stupid loosely typed languages...).
        digit = Number.charAt(p) - 0;

        // Multiply every other digit by two.
        if (p % 2 == parity)
        {
            digit *= 2;

            // If the result comes out to be greater than 10, add both digits together.
            if (digit > 9) digit -= 9;
        }

        // Add the digit to our total.
        total += digit;
    }

    // If the total mod 10 is not 0 then the number is invalid.
    if (total % 10 != 0) return false;

    return true;    
}



// AskAQuestion ////////////////////////////////////////////////////////////////////////////////////
// Validation handler for the Ask a Question form.
// /////////////////////////////////////////////////////////////////////////////////////////////////
CSCC.Validation.AskAQuestion = function(FieldId)
{
    var field = document.getElementById(FieldId);
    if (!field) return;

    switch (FieldId)
    {
    case 'Name':
        if (!field.value)
            CSCC.Validation.FormErrors[FieldId] = 'Please enter your name.';

        else
            CSCC.Validation.FormErrors[FieldId] = null;
        break;

    case 'Phone':
    case 'Email':
        if (!document.getElementById('Phone').value.match(/^(\(?\d{3}[\.\-\ \)]*)?\d{3}[\.\-\ ]?\d{4}$/) &&
            !document.getElementById('Email').value.match(/^.+@.+\..+$/))
        {
            CSCC.Validation.FormErrors['Phone'] = 'Please enter either your phone number or email address.';
            CSCC.Validation.FormErrors['Email'] = 'Please enter either your phone number or email address.';
        }
        else
        {
            CSCC.Validation.FormErrors['Phone'] = null;
            CSCC.Validation.FormErrors['Email'] = null;
        }
        break;

    case 'Question':
        if (!field.value)
            CSCC.Validation.FormErrors[FieldId] = 'Please type your question in the box.';
        else
            CSCC.Validation.FormErrors[FieldId] = null;
        break;
    }        
}

