﻿//Global variables defining the form steps, and which step we are on, and session timer and logout timer IDs.
//Also a variable that tells us whether we are doing a final form submit.
//Note that curr_step is defined in a script in the head section of applyonline.aspx
var isSubmit = false;
var sessionTimer = 0;
var logoutTimer = 0;
var steps = new Array();
var is_printable_version = false;
var i = 0;
steps[0] = {
  num       : 1,
  name      : "Your Personal Details",
  desc      : "",
  contents  : {fs_contact:0, fs_health:0, fs_nationality:0, fs_loc_avail:0}
}
if (AppFormType == 1) {
  //The AppFormType global is defined in a script in the head section of applyonline.aspx
  steps[1] = {
    num       : 2,
    name      : "Your Education",
    desc      : "",
    contents  : {p_edu_preuni:0, fs_gcses:0, fs_alevels:0, fs_other_qualifications:0, fs_preuni_education_rating:0}
  }
  steps[2] = {
      num       : 3,
      name      : "Your University Plans",
      desc      : "",
      contents  : {fs_uni_plans:0}
  }
} else if ((AppFormType == 2) ||  (AppFormType == 3)) {
  steps[1] = {
    num       : 2,
    name      : "Your Pre-University Education",
    desc      : "",
    contents : {p_edu:0, fs_gcses:0, fs_alevels:0}
  }
  steps[2] = {
      num       : 3,
      name      : "Your University Education",
      desc      : "",
      contents  : {fs_undergrad_degree:0, fs_undergrad_degree_additional:0, fs_postgrad_degree:0, fs_other_qualifications:0}
  }
} else {
  alert("AppFormType out of range!");
}
steps[3] = {
  num       : 4,
  name      : "Your Employment Experience",
  desc      : "",
  contents  : {fs_employment:0, fs_vacation:0}
}
steps[4] = {
  num       : 5,
  name      : "Your Interests, Achievements and Ambitions",
  desc      : "",
  contents  : {fs_interests:0, fs_acheivements:0, fs_ambitions:0}
}
steps[5] = {
  num       : 6,
  name      : "Your Personal Characteristics",
  desc      : "",
  contents  : {fs_strengths:0, fs_weaknesses:0}
}
steps[6] = {
  num       : 7,
  name      : "Further Information",
  desc      : "",
  contents  : {fs_additional:0, fs_references:0, fs_sources:0}
}
steps[7] = {
  num       : 8,
  name      : "Declaration",
  desc      : "",
  contents  : {fs_declaration:0}
}

//Trim functions
function trim(str, chars) {
	return ltrim(rtrim(str, chars), chars);
}
function ltrim(str, chars) {
	chars = chars || "\\s";
	return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}
function rtrim(str, chars) {
	chars = chars || "\\s";
	return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

//Called to switch required rules on/off
function requiredFields(bIsRequired) {
  if (bIsRequired) {
    //Set isSubmit, used by the postgradinput section for validation
    isSubmit = true;
    //Set "required" class based on "prerequired" and the fieldsets actually
    //in the steps array.
    //Exclude elements in an expander row, since these are not expected to be 
    //filled in, unless this is the ONLY row in the expander, in which case we
    //do want to trigger the 'required' validation rule.  Note that we restrict
    //the attribute selection to the containing fieldset, since otherwise this
    //call runs very slowly on older machines.
    var i = 0;
    for (i = 0; i < steps.length; i++) {
      for(x in steps[i].contents) {
        jQuery('#' + x + ' .prerequired')
          .filter(function() {
            var fieldset = jQuery(this).parents('fieldset:eq(0)');
            var fieldname = jQuery(this).attr('name');
            if (jQuery('[name=' + fieldname + ']', fieldset).length < 2) {
              return true;
            } else {
              return !(jQuery(this).parent().parent().hasClass('expander'));
            }
          }).removeClass('prerequired').addClass('required');
      }
    }
  } else {
    isSubmit = false;
    jQuery('.required').removeClass('required').addClass('prerequired');
  }
}

//Called before saving, whether MetaAjaxSubmit or from an ASP.NET button.
//Note that the logout timer pop function does not call this validation routine,
//but that's OK because the form is not saved in that case.
function validateAndConfirm(bIsSubmit, bDoConfirm) {
  closeSpecifyOthers();
  requiredFields(false);
  if (bIsSubmit) {
    //Enforce required fields for final submission
    requiredFields(true)
    //Now validate the form
    if (jQuery('#aspnetForm').valid()) {      
      if (bDoConfirm) {
        return confirm('Once you have submitted your application, you will not be able to make any changes online (please contact us by phone or email if you need to make any corrections after submitting the form).  If you want to make further changes before submitting your application, please click *cancel*.  Use the *save* button on the form to save your application without submitting it.');
      } else {
        return true;
      }
    } else {
      alert("Sorry, this form is not ready to be submitted.  Please check all the steps and complete all the required details marked in red.  Thank you.");
      return false;
    }
  } else {
    //Validate the form, but not enforcing required fields    
    if (jQuery('#aspnetForm').valid()) {
      if (bDoConfirm) { 
        return confirm('Save your application so far (without submitting it yet to our recruitment team)?');
      } else {
        return true;
      }
    } else {
      alert("Sorry, this form contains invalid values for some fields.  Please check all the steps and remove the invalid content from fields marked in red.  Thank you.");
      return false;
    }
  }   
}

//Disable and restyle all inputs, except for the buttons.
//The buttons are hidden in the printable view anyway, and
//are needed in the historical view, and in the preview mode
//the asp.net page does not render them.
//Note that we filter out inputs starting with '__', so that
//we don't interfere with ASP.NET viewstate etc.
function disableInputs() {
  jQuery(':input').filter(function() { return (jQuery(this).attr('id').substring(0,2) != '__');}).attr('disabled', 'disabled');
  jQuery(':input').filter(function() { return (jQuery(this).attr('id').substring(0,2) != '__');}).addClass('disabled');
  jQuery('.button_left, .button_right').removeAttr('disabled');
  jQuery('.button_left, .button_right').removeClass('disabled');
}
function enableInputs() {
  jQuery(':input').removeAttr('disabled');
  jQuery(':input').removeClass('disabled');
}

function show_printable_version(bPrintable) {
  if (bPrintable) {
    if ((AppFormMode != 6) &&  (AppFormMode != 4)) { //Historical and preview modes always have inputs disabled
      disableInputs();
    }
    jQuery('#step_num').text("(All)");
    jQuery('#step_name').text("Printable Form");
    jQuery('#step_desc').text("");
    var i = 0;
    for (i = 0; i < steps.length; i++) {
      for(x in steps[i].contents) jQuery('#' + x).show();
    }  
    jQuery('div.nav').hide();
    jQuery('div.formcontrols').hide();
    jQuery('a.printlink').text("Normal Version");
  } else {
    if ((AppFormMode != 6) &&  (AppFormMode != 4)) { //Historical and preview modes always have inputs disabled
      enableInputs();
    }  
    switch_step(curr_step, false);
    jQuery('div.nav').show();
    jQuery('div.formcontrols').show();
    jQuery('a.printlink').text("Printable Version");
  }
  is_printable_version = bPrintable;
}

//Switches to the numbered step, unless the form
//can't be saved due to validation errors
function switch_step(i, bSave) {
  var prev_step;
  var next_step;
  var valid = true;
  
  //Post the form using ajax, but only when in apply mode
  if ((bSave) && (AppFormMode == 7)) {
    if (!metaAjaxSubmit()) valid = false;
    else {
      //Set a timer to warn the user about impending session timeout
      //Note that the timeout variable comes from a script in the head
      //section of applyonline.aspx.
      clearTimeout(sessionTimer);
      sessionTimer = setTimeout("raiseSessionWarning()", timeout);
    }
  }
  
  //Exit now if the form was not valid for saving
  if (!valid) return false;
  
  //Hide everything first
  jQuery('.stepcontent').hide();
  
  //Make sure step is in range
  if (i <= 0) {
    i = 0;
    prev_step = 0;
  } else {
    prev_step = parseInt(i) - 1;
  }
  if (i >= steps.length - 1) {
    i = steps.length - 1;
    next_step = steps.length - 1;
  } else {
    next_step = parseInt(i) + 1;
  }
  curr_step = i;
  
  //Change the step description at the top
  jQuery('#step_num').text(steps[i].num);
  jQuery('#step_name').text(steps[i].name);
  jQuery('#step_desc').text(steps[i].desc);
  
  //Show the right contents
  for(x in steps[i].contents) jQuery('#' + x).show();
  
  //Change the navbar current step
  jQuery('a.current').removeClass('current');
  jQuery('.step[name="step' + i + '"]').children('a').addClass('current');
  
  //Set up the next and prev links to point to the right steps
  jQuery('li.next > a').attr('href', '#' + next_step);
  jQuery('li.prev > a').attr('href', '#' + prev_step);
  
  //Store the current step so it can be posted in the form
  jQuery('#current_step_hidden').val(curr_step);
  
  return true;
}

function switch_step_save(i) {
  switch_step(i, true);
}

//Utility function to remove viewstate, eventvalidation and other
//ASP.NET form contents (anything with id starting "__") from
//our form before serializing it ready for posting.
jQuery.fn.serializeNoViewState = function()
{
    return this.find("input,textarea,select,hidden")
               .not("[type=hidden][name^=__]")
               .serialize();    
}

//A homegrown version of ajax form submission.
function metaAjaxSubmit() {
  if (validateAndConfirm(false, false))  {
    var res = jQuery("#aspnetForm").serializeNoViewState();
    jQuery.ajax({
      type: "POST",
      data: res,
      cache: false,
      beforeSend: function(req) {
        req.setRequestHeader("MetaAjaxSubmit", "true");
      },
      complete: function(res) {
        //This is to handle the logout case, and also a general unhandled failure case
        if (res.status == 277)  { 
          window.location = res.getResponseHeader('MetaRelocation')
        } else if (res.status == 500) {
          window.location = "/"
        } else {
          //Normally this will have no effect, since the page is already visible.
          //The purpose of this is to reverse the hide() called in the session timeout
          //function.
          jQuery('#maincontent').show();
        }
      }
    });
    return true;
  } else {
    return false;
  }
}

//Just refresh the session
function metaAjaxRefresh() {
  jQuery.ajax({
    type: "POST",
    data: "",
    cache: false,
    beforeSend: function(req) {
      req.setRequestHeader("MetaAjaxRefresh", "true");
    },
    complete: function(res) {
      //This is to handle the logout case, and also a general unhandled failure case
      if (res.status == 277)  { 
        window.location = res.getResponseHeader('MetaRelocation')
      } else if (res.status == 500) {
        window.location = "/"
      } else {
        //The purpose of this is to reverse the hide() called in the session timeout
        //function.
        jQuery('#maincontent').show();
      }
    }
  });
}

//Logout without saving.  Used only when the user does not respond to a session timeout within 60s.
function metaAjaxLogout() {
  jQuery.ajax({
    type: "POST",
    data: "",
    cache: false,
    beforeSend: function(req) {
      req.setRequestHeader("MetaAjaxLogout", "true");
    },
    complete: function(res) {
      if (res.status == 277)  { 
        window.location = res.getResponseHeader('MetaRelocation')
      } else {
        window.location = "/"
      }
    }
  });
}

//Sets up a div to clone itself when specified 'child' element triggers
//receive enter or tab keypresses, clicks, or blurs.
//Note that this function assumes that the expander trigger is nested 2 levels below the expander.
//This restriction could be removed, by using the ancestor descendant selector to find the children.
//When finding the parent, the behavior would have to be to look for an expander in the parent() array
//first, and then search up to the next nesting level(s) only if not found.  Use the parents() function to get
//an array of the parent chain.
//Note that this parent-finding functionality would need to be implemented in the requiredField() function too.
function prepareExpander(domEle) {
  jQuery(domEle).children().children(".expanderTrigger").each(function() {
    jQuery(this).keypress(function(e) {
      if (e.which == 13 || e.which == 0) {
        if (e.which == 13) e.preventDefault();
        e.stopImmediatePropagation();
        if ((jQuery(this).val() != "") && (jQuery(this).val() != 0)) doExpansion(this);
      }      
    });
    jQuery(this).click(function(e) {
      e.stopImmediatePropagation();
      if ((jQuery(this).val() != "") && (jQuery(this).val() != 0)) doExpansion(this);
    });
    jQuery(this).blur(function(e) {
      e.stopImmediatePropagation();
      if ((jQuery(this).val() != "") && (jQuery(this).val() != 0)) doExpansion(this);      
    });
  });
}

//The function that actually does the expansion.
//Note that this function assumes that the expander trigger is nested 2 levels below the expander.
//This restriction could be removed, by using the ancestor descendant selector to find the children.
//When finding the parent, the behavior would have to be to look for an expander in the parent() array
//first, and then search up to the next nesting level(s) only if not found.
function doExpansion(domEle) {
  //Remove any open instances of the specify other control before continuing (of which there should be at most one).
  //We do this here to prevent copying of open instances on expansion.
  jQuery(".enteroption").remove();
  jQuery(".tempHidden").selectOptions("").removeClass("tempHidden").show();
  
  //Find the element to be cloned and clone it.
  var expander = jQuery(domEle).parent().parent().addClass('prevexpander');
  var newExpander = expander.clone();
  
  //Different browsers interpret the W3C differently and some clone the selected value of select inputs (IE) and
  //some do not (FF).  The jQuery bug raised for this was closed as "wontfix" so we need to ensure consistency ourselves.
  newExpander.children().children("select").each(function() {
    var colindex = jQuery(this).parent().prevAll().length;
    var prevexpander = jQuery('.prevexpander');
    jQuery(this).val(prevexpander.children().eq(colindex).children('select').val());
  });
  expander.removeClass('prevexpander');
  
  //For elements that are specified as "copy empty", clear the cloned value.
  newExpander.children().children(".expanderCopyEmpty").val("");
  
  //Set up event handlers on the new elements (these are not cloned).
  newExpander.children().children("input.hiddencheckboxvalue").each(function() {
    prepareHiddenCheckBoxValue(this, true);
  });
  newExpander.children().children("input.hiddeninversecheckboxvalue").each(function() {
    prepareHiddenCheckBoxValue(this, false);
  });
  newExpander.children().children("select.specifyOther").each(function() {
    prepareSpecifyOther(this);
  });
  newExpander.children().children("select.specifyOtherNoFkey").each(function() {
    prepareSpecifyOtherNoFkey(this);
  });
  
  //Set up the new expander.
  prepareExpander(newExpander);
  
  //Switch off expander behavior on the old expander.  Note that an enhancement could be to allow the user to decide
  //whether to do this when creating the original expander.
  expander.removeClass("expander");  
  expander.children().children(".expanderTrigger").removeClass("expanderTrigger").unbind("keypress").unbind("click").unbind("blur").keypress(function(e) {
    if (e.which == 13) e.preventDefault();
  });
  
  //Finally, insert the new expander into the DOM.
  expander.after(newExpander);
    
  //Add validation rules to newly created inputs
  //Note that we have to do this after the element is added to
  //the DOM, since otherwise the validation plugin cannot
  //refer back to the parent form element, which is where the
  //main validator object is stored.
  newExpander.find(":input").each(function(){
    jQuery(this).rules("add", { noarrows: true });
    jQuery(this).keyup(function() {
      jQuery(this).valid();
    });
  });
  newExpander.find(".nocommas").each(function(){
    jQuery(this).rules("add", { nocommas: true });
  });
}

//Function to close any open instances of the specify other box,
//useful because the form inputs are invalid in this state
function closeSpecifyOthers() {
  jQuery(".enteroption").remove();
  jQuery(".tempHidden").selectOptions("").removeClass("tempHidden").show();
}

//Sets up a select box to enable the user to specify an option not
//already in the list.
function prepareSpecifyOther(domEle) {
  jQuery(domEle).change(function(e) {
    e.stopImmediatePropagation();
    var select = jQuery(this);
    if ((select.val() != "") && (select.val() == 0)) {
      //Remove any other open instances of the specify other control before continuing (of which there should be at most one).
      closeSpecifyOthers();      
      //Now manipulate the new instance
      select.hide();
      select.addClass("tempHidden");
      select.after("<div class='enteroption'><input type='text' class='text' id='optionentry' maxlength='128'/></div>");
      var optionentry = jQuery("#optionentry");
      optionentry.watermark("specify here", {className: "watermark"});
      optionentry.keypress(function (e) {
        if (e.which == 13 || e.which == 0) {
          if (e.which == 13) e.preventDefault();
          e.stopImmediatePropagation();
          jQuery(this).blur();
        }  
      });
      optionentry.blur(function () {
        //Take out commas, so that we can rely on fn_dc_split (in the database)
        //to separate a form array (which is passed as a comma-separated list)
        //into the right number of elements.
        var otherval = jQuery(this).val().replace(/,/g,'');
        if ((otherval != "") &&  (otherval != "specify here")) {
          jQuery(".tempHidden").removeOption("otherval");
          jQuery(".tempHidden").addOption("otherval", otherval, true);
          jQuery(this).parent().next("input").val(otherval);
        } else {
          jQuery(".tempHidden").selectOptions("");
        }
        jQuery(".enteroption").remove();
        jQuery(".tempHidden").removeClass("tempHidden").show().blur();
      });
    }
  });
}
//Similar to the previous function, but to be used when the field
//being specified in the drop down is not of the "fkey or other" type, 
//but is in fact just free text (so the select element is just provided
//to give some pre-canned options for that text).
function prepareSpecifyOtherNoFkey(domEle) {
  jQuery(domEle).change(function(e) {
    e.stopImmediatePropagation();
    var select = jQuery(this);
    if ((select.val() != "") && (select.val() == 0)) {
      //Remove any other open instances of the specify other control before continuing (of which there should be at most one).
      jQuery(".enteroption").remove();
      jQuery(".tempHidden").selectOptions("").removeClass("tempHidden").show();
      //Now manipulate the new instance
      select.hide();
      select.addClass("tempHidden");
      select.after("<div class='enteroption'><input type='text' class='text' id='optionentry' maxlength='128'/></div>");
      var optionentry = jQuery("#optionentry");
      optionentry.watermark("specify here", {className: "watermark"});
      optionentry.keypress(function (e) {
        if (e.which == 13 || e.which == 0) {
          if (e.which == 13) e.preventDefault();
          e.stopImmediatePropagation();
          jQuery(this).blur();
        }  
      });
      optionentry.blur(function () {
        //Take out commas, so that we can rely on fn_dc_split (in the database)
        //to separate a form array (which is passed as a comma-separated list)
        //into the right number of elements.
        var otherval = jQuery(this).val().replace(/,/g,'');
        if ((otherval != "") &&  (otherval != "specify here")) {
          jQuery(".tempHidden").addOption(otherval, otherval, true);
        } else {
          jQuery(".tempHidden").selectOptions("");
        }
        jQuery(".enteroption").remove();
        jQuery(".tempHidden").removeClass("tempHidden").show().blur();
      });
    }
  });
}

//Sets up a hidden checkbox value to set up a checkbox
//and to watch for changes in the checkbox value and be updated
//accordingly.  Note the possibility of inverse mode.
function prepareHiddenCheckBoxValue(domEle, bInverse) {
  var hidden = jQuery(domEle);
  var hiddenval = jQuery(domEle).val(); //Note that this is a string, not a boolean value
  var checkbox = hidden.next('input');
  checkbox.attr("checked", (bInverse == (hiddenval == 'True')));
  checkbox.click(function() {
    var hiddentoset = jQuery(this).prev('input');
    hiddentoset.val((bInverse == (jQuery(this).is(":checked"))));
  });
}

function showHideCorrespondence() {  
  if (jQuery("#corr_post").is(":checked")) {
    jQuery("#email_addr").hide();
    jQuery("#perm_or_term_addr").show();
  } else {
    jQuery("#email_addr").show();
    jQuery("#perm_or_term_addr").hide();
  }
}

//Called when the DOM is ready.
jQuery(document).ready(function(){
  
  //Hide everything first so the user does not see elements
  //changing during load
  //NOT NEEDED WHILE WE ARE ENFORCING JS - SEE THE END OF THIS FUNCTION
  //jQuery('.nav').hide();
  //jQuery('#stepcontents').hide();
  
  //Some variables needed in this function
  var optionsSource = "";
  var optionsArray = "";
  var selectedSource = "";
  var selectedArray = "";

  //Make our textareas autogrow
  jQuery('textarea').elastic();

  //Set up select boxes that allow you to add options
  jQuery("select.specifyOther").each(function() {
    prepareSpecifyOther(this);
  });
  jQuery("select.specifyOtherNoFkey").each(function() {
    prepareSpecifyOtherNoFkey(this);
  });

  //Set up expandable sections for qualifications
  jQuery("div.expander").each(function() {
    prepareExpander(this);
  });

  //Show/hide the correspondence address sections, and set up event handlers
  //for them.
  showHideCorrespondence();
  jQuery("#corr_post").click(showHideCorrespondence);
  jQuery("#corr_email").click(showHideCorrespondence);

  //Set up source checkboxes to add/remove from the primary source drop-down
  //Checkbox ID field is prefixed by chk_source_ (11 chars), plus the select option value
  //The label text is the select option text.
  jQuery(".chksource").click(function() {
    var box = jQuery(this);
    var box_id = box.attr("id");
    var option_id = box_id.substring(11);
    var lbl = jQuery("#lbl_" + box_id);
    var option_text = lbl.text();
    if (box.is(":checked")) {
      jQuery("#primary_source").addOption(option_id, option_text, false);
      if (jQuery("#primary_source").val() == "") {
        jQuery("#primary_source").selectOptions(option_id);
      }
    } else {
      jQuery("#primary_source").removeOption(option_id);
    }    
  });

  //Set up the single select drop-down elements whose values are provided in a hidden input
  //NOTE THAT THE HIDDEN VALUE ELEMENT MUST BE THE IMMEDIATELY PRECEDING SIBLING OF THE
  //SELECT DROP-DOWN  
  optionsSource = jQuery(".selectedSourceOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlselectedsources').addOption(optionsArray);
  }
  optionsSource = jQuery(".yntOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlynt').addOption(optionsArray);
  }
  optionsSource = jQuery(".nationalityOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlnationality').addOption(optionsArray);
  }
  optionsSource = jQuery(".gcseTypeOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlgcsequaltype').addOption(optionsArray);
  }
  optionsSource = jQuery(".gcseResultOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlgcsegrade').addOption(optionsArray);
  }
  optionsSource = jQuery(".aLevelTypeOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlalevelqualtype').addOption(optionsArray);
  }
  optionsSource = jQuery(".aLevelResultOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlalevelgrade').addOption(optionsArray);
  }
  optionsSource = jQuery(".undergradTypeOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlundergradqualtype').addOption(optionsArray);
  }
  optionsSource = jQuery(".undergradResultOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlundergradgrade').addOption(optionsArray);
  }
  optionsSource = jQuery(".postgradTypeOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlpostgradqualtype').addOption(optionsArray);
  }
  optionsSource = jQuery(".postgradResultOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlpostgradgrade').addOption(optionsArray);
  }
  optionsSource = jQuery(".schoolDisciplineOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlschooldiscipline').addOption(optionsArray);
    jQuery('.ddldiscipline').addOption(optionsArray);
  }
  optionsSource = jQuery(".uniDisciplineOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlunidiscipline').addOption(optionsArray);
    jQuery('.ddldiscipline').addOption(optionsArray);
  }
  optionsSource = jQuery(".institutionOptions").text();
  if (optionsSource.charAt(0) == "{") {
    optionsArray = eval("(" + optionsSource + ")");
    jQuery('.ddlinstitution').addOption(optionsArray);
  }
  
  //Set up select boxes, where the initial value is preloaded in a hidden field
  jQuery('.hiddenselectvalue').each(function() {
    var hidden = jQuery(this);
    var hiddenval = jQuery(this).val();
    var select = hidden.next('select');  
    if (hiddenval == "otherval") {
      var hiddenvalother = select.next('input').val();
      select.addOption(hiddenval, hiddenvalother, true);
    } else {
      select.selectOptions(hiddenval);
    }      
  });
  
  //Similar to the previous function, but to be used when the field
  //being specified in the drop down is not of the "fkey or other" type, 
  //but is in fact just free text (so the select element is just provided
  //to give some pre-canned options for that text).
  jQuery('.hiddenselectvaluenofkey').each(function() {
    var hidden = jQuery(this);
    var hiddenval = jQuery(this).val();
    var select = hidden.next('select');
    if ((hiddenval == "") || (select.containsOption(hiddenval))) {
      select.selectOptions(hiddenval);
    } else {
      select.addOption(hiddenval, hiddenval, true);
    }      
  });
  
  //Hidden checkbox values (regular and inverse).  Note that by contrast
  //with the select elements, we also set up event handlers for the checkbox so that
  //we keep the hidden value up to date with the value of the checkbox.  This is so
  //that the code that consumes the POSTed form gets a full-length array of values
  //rather than having to deal with a short array containing just the checked values.
  //Because we need to set up event handlers, we need to do this after an expander has
  //created a new row, so we move the setup code to a subfunction.
  //NOTE THAT THE HIDDEN VALUE ELEMENT MUST BE THE IMMEDIATELY PRECEDING SIBLING OF THE
  //CHECKBOX
  jQuery("input.hiddencheckboxvalue").each(function() {
    prepareHiddenCheckBoxValue(this, true);
  });
  jQuery('.hiddeninversecheckboxvalue').each(function() {
    prepareHiddenCheckBoxValue(this, false);
  });
  
  //Add a navbar item for each step
  for (i = 0; i < steps.length; i++) {
    jQuery('li.next').before("<li class='step' name='step" + i + "'><a href='#" + i + "'> Step " + steps[i].num + "</a></li>");
  }
  
  //Set up event handlers for the navigation controls
  jQuery('li.next > a, li.prev > a, li.step > a').click(function(){
    var hash = this.href;
	  hash = hash.replace(/^.*#/, '');
    jQuery.historyLoad(hash);
    return false;
  });
  jQuery('a.printlink').click(function(){
    show_printable_version(!is_printable_version);
  });
  
  //VALIDATION SETUP
  //Set up form validation.  Note that this only fires on "submit", 
  //not on "save", by means of an explicit call to valid() on the submit button.
  //Only used in "Apply" mode.
  if (AppFormMode == 7) {
    jQuery('#aspnetForm').validate({
      onsubmit: false,
      ignore: '.nojqval',
      focusInvalid: false,
      invalidHandler: function(form, validator) {
        if (isSubmit) {
          var firsterror = jQuery(validator.errorList[0].element);
          var fieldset = firsterror.parents("fieldset").attr("id");
          var i = 0;
        loop1:
          for (i = 0; i < steps.length; i++) {
        loop2:
            for(x in steps[i].contents) if (x == fieldset) break loop1;
          }
          switch_step(i, false);
          firsterror.focus();
        }
        return false;
      },
      rules: {
        term_dates: {
          required: function(element) {
            return (jQuery('#postaladdress_term').val().length > 0);
          },
          messages: {
            required: "Since you specified a term address, please let us know when you'll be there."
          }
        },
        dob: {
          required: false, date: true, dob: true
        }
      }
    });
    
    //Generic regular expression validation
    jQuery.validator.addMethod(
          "regex",
          function(value, element, regexp) {
              if (regexp.constructor != RegExp)
                  regexp = new RegExp(regexp);
              else if (regexp.global)
                  regexp.lastIndex = 0;
              return this.optional(element) || regexp.test(value);
          },
          "Please check your input."
    );
    
    //No < or > allowed in any input fields.  Note that ASP.NET will disallow this
    //too, but with an ugly server error message, so we prevent users entering them.    
    //Note that we need to reimplement this rule on any new expander row (see doExpansion).
    jQuery.validator.addMethod(
      'noarrows', 
      function (value) { 
        return !(/(<|>)/.test(value)); 
      }, 
      "No '<' or '>' characters allowed."
    );
    jQuery(":input").each(function(){
      jQuery(this).rules("add", { noarrows: true });
      jQuery(this).keyup(function() {
        jQuery(this).valid();
      });
    });
    
    //No commas allowed in certain input fields (those that are in arrays, generally).
    //Note that we need to reimplement this rule on any new expander row (see doExpansion).
    jQuery.validator.addMethod(
      'nocommas', 
      function (value) { 
        return !(/,/.test(value)); 
      }, 
      "No commas allowed in this field."
    );
    jQuery(".nocommas").each(function(){
      jQuery(this).rules("add", { nocommas: true });
    });
    
    //Dates of birth must be less than 80 years ago and more than 10.
    jQuery.validator.addMethod(
      'dob',
      function (value) {
        if (value == null) return true;
        else if (value == '') return true;
        else return ((((new Date()).getFullYear() - (new Date(value)).getFullYear()) >= 10) && (((new Date()).getFullYear() - (new Date(value)).getFullYear()) <= 80))
      },
      "DoB provided is out of range (please check the year)."
    );
    
    //Add the email rule to both the reference email address fields.
    //We're using our own email rule because the jquery.validate.js
    //version seems to be over-zealous!
    if ((AppFormMode == 6) || (AppFormMode == 7)) {
      jQuery('#reference_email_1').rules("add", {
        regex: / *[A-Za-z0-9._'%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4} */ ,
        messages: {
          regex: "Please enter a valid email address."
        }
      });
      jQuery('#reference_email_2').rules("add", {
        regex: / *[A-Za-z0-9._'%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4} */ ,
        messages: {
          regex: "Please enter a valid email address."
        }
      });
    }
    
    //Add the same required rule for everything in the postgrad section - this 
    //is based on class rather than name so it can't be done in the validate() call.
    //Only add this if fs_postgrad is in the steps array, which we know is just
    //grad and grad intern forms.
    if ((AppFormType == 2) ||  (AppFormType == 3)) {
      jQuery(".postgradinput").each(function(){
        jQuery(this).rules("add", {
          required: function(element) {
            //Not required if the "not applicable" box is checked.
            if (jQuery('#no_postgrad').is(':checked')) return false;
            //This is so that we can avoid triggering this required rule when
            //we do a non-submit validation (see validateAndConfirm function).
            else if (!isSubmit) return false;
            //Field is OK if it has a value.
            else if (trim(jQuery(this).val(), null) != "") return false;
            //Validation fails.
            else return true;
          },
          messages: {
            required: "This field is required unless you check 'Not applicable', above."
          }
        });                    
      });
    }
    
    //If someone checks the "no_postgrad" input, revalidate the ".postgradinput" elements
    //so we can remove the red error messages.
    jQuery('#no_postgrad').click(function() {
      if (jQuery('#no_postgrad').is(':checked')) {
        jQuery(".postgradinput").valid();
      }
    });
  }
  //END OF VALIDATION SETUP
  
  //Set a timer to warn the user about impending session timeout
  //Note that the timeout and isLoggedIn variables come from a script
  //in the head section of applyonline.aspx.
  if (isLoggedIn == "True") {
    clearTimeout(sessionTimer);
    sessionTimer = setTimeout("raiseSessionWarning()", timeout);
  }
  
  //Historical and preview mode always have inputs disabled
  if ((AppFormMode == 6) || (AppFormMode == 4)) {
    disableInputs();
  }
  
  //Show the step that the user was last on (will default to 0 for new applications and previews)
  switch_step(curr_step, false);
  jQuery('.nav').show();
  jQuery('#stepcontents').show();
  
  //Show the content and insert the hidden element that tells us we have JS enabled.
  jQuery('#maincontent').show().prepend('<input type="hidden" name="bJSActive" value="true" />');
  
  jQuery.historyInit(switch_step_save)

});

//We define this as a named function so that we can unbind it from the click handler
//without also unbinding the ASP.NET postback script (also registered to the click event).
function validateForLogout() {
  if(!validateAndConfirm(false, false)) return false;
}

function raiseSessionWarning() {
  jQuery('#maincontent').hide();
  logoutTimer = setTimeout("metaAjaxLogout()", 61000);
  jQuery.prompt(
    "Your session is about to time out (in 60 seconds).  Please hit OK to continue working.  If the session times out any unsaved progress will be lost.",
    {
      timeout: 60000,
      callback: function() {
        clearTimeout(logoutTimer);
        if (!metaAjaxSubmit()) metaAjaxRefresh();
        clearTimeout(sessionTimer);
        sessionTimer = setTimeout("raiseSessionWarning()", timeout);      
      }
    }
  );
}

//JAF A handy debugging tool, that lists event handlers.
//Use it like this:

// List all onclick handlers of all anchor elements:
//$('a').listHandlers('onclick', console.info);

// List all handlers for all events of all elements:
//$('*').listHandlers('*', console.info);

// Write a custom output function:
//$('#whatever').listHandlers('click',function(element,data){
//    $('body').prepend('<br />' + element.nodeName + ': <br /><pre>' + data + '<\/pre>');
//});
jQuery.fn.listHandlers = function(events, outputFunction) {
    return this.each(function(i){
        var elem = this,
            dEvents = jQuery(this).data('events');
        if (!dEvents) {return;}
        jQuery.each(dEvents, function(name, handler){
            if((new RegExp('^(' + (events === '*' ? '.+' : events.replace(',','|').replace(/^on/i,'')) + ')$' ,'i')).test(name)) {
               jQuery.each(handler, function(i,handler){
                   outputFunction(elem, '\n' + i + ': [' + name + '] : ' + handler );
               });
           }
        });
    });
};