var RF_ADMIN = 1;
var RF_BASIC_ADMIN = 2;

var ONE_DAY = 86400;

// Document Rights
var DEPT_R = 1;
var DEPT_W = 2;
var DISTRICT_R = 16;
var DISTRICT_W = 32;
var LI_R   = 256;
var LI_W   = 512;
var NLI_R  = 4096;
var NLI_W  = 8192;
var DEF_RIGHTS = (NLI_R | LI_R | DISTRICT_R | DEPT_R | DEPT_W);

// User Rights
var ADMIN_FG  = 1;
var EDITOR_FG = 2;

// Used with jsMoodle to login to moodle and update user account
var UpdateMoodleReq = getXMLHttpObject();

// Debugging tool, converts string into a string of decimal values for each char 
// separated by spaces
function String2Nums(Str)
{
  var A = new Array();

  A = Str.split(/|/);

  var S = "";

  for (var i = 0; i < A.length; i++)
  { S += A[i].charCodeAt() + " "; }

  return (S);
}

function getDate()
{
  var D = new Date();

  return (D.getMonth() + 1) + "-" + D.getDate() + "-" + D.getFullYear();
}

function Trim(s)
{
  return s.replace(/^\s*/, "").replace(/\s*$/, "");
}

String.prototype.trim = function () 
{
  return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

function clearListBox(lb)
{
  lb.length = 0;

//  for (var i = lb.length - 1; i >= 0; i--)
//  {
//    lb.options[i] = null;
//  }
}

// Function frame for sending XMLHTTPREQUESTS
//   url is utl of of php handler on server
//   Request is valid XMLHTTPRequest object
//   Datahandler is pointer to callback function that handles returning data
//   Snnd is string of data to send in label1=data1&label2=data2 format, which appears in POST format in the PHP handler
function xmlhttpCall(url,Request,Datahandler,Send)
{
  // Set up the call to the server if Request is a valid object
  if (Request)  
  {
    // Open the line to the url in asynchronous mode
    Request.open("POST",url,true);

    // Add the header required for the 'POST' method
    Request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

    // Declare the function that will handle the returning data
    Request.onreadystatechange = function()
    { 
      // If complete with no errors
      if (Request.readyState == 4 && Request.status == 200)
      {
        // If the our SQL handler did not return an error
        if (Request.responseText.match(/ERROR:/)) { alert (Request.responseText); }
        else
        // Process the data with our predefined Callback handler function
        {
          Datahandler(Request.responseText.replace(/(^[\n\r]+|[\n\r]+$)/g,"")); 
        }
      }
    }

    // Place the call to the server
    Request.send(Send);
  }
}

// Function frame for sending XMLHTTPREQUESTS
//   url is utl of of php handler on server
//   Request is valid XMLHTTPRequest object
//   Datahandler is pointer to callback function that handles returning data
//   Snnd is string of data to send in label1=data1&label2=data2 format, which appears in POST format in the PHP handler
function xmlhttpCallOop(url,Request,Objecthandle,Send)
{
  // Set up the call to the server if Request is a valid object
  if (Request)  
  {
    // Open the line to the url in asynchronous mode
    Request.open("POST",url,true);

    // Add the header required for the 'POST' method
    Request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

    // Declare the function that will handle the returning data
    Request.onreadystatechange = function()
    { 
      // If complete with no errors
      if (Request.readyState == 4 && Request.status == 200)
      {
        // If the our SQL handler did not return an error
        if (Request.responseText.match(/ERROR:/)) { alert (Request.responseText); }
        else
        // Process the data with our predefined Callback handler function
        { Objecthandle.callBack(Request.responseText.replace(/[\n\r]+$/,"")); }
      }
    }

    // Place the call to the server
    Request.send(Send);
  }
}

// Swap unsafe chars to web friendly hex codes for POSTing
function fixPost(S)
{
  S = S.replace(/=/g,"%3d");
  S = S.replace(/&/g,"%26");
  S = S.replace(/\+/g,"%2b");
  S = S.replace(/\'/g,"%27");

  return S;
}

// Get new XMLHttpRequest Object
function getXMLHttpObject()
{
  var Request = null;

  if (window.XMLHttpRequest)          // Everybody else
  { Request = new XMLHttpRequest(); }
  else if (window.ActiveXObject)      // Microsoft
  { Request = new ActiveXObject("Microsoft.XMLHTTP"); }

  return Request;
}

function countBits(Num,Start,Len)
{
  if (typeof(Num)   == "undefined") { Num   = 0; }
  if (typeof(Start) == "undefined") { Start = 0; }
  if (typeof(Len)   == "undefined") { Len   = 8; }

  for(var c = 0,i = Start; i < Len; i++)
  { (Num & (1 << i)) && c++; }

  return c;
}

// Clean up dollar sign, percent and commas to leave valid number
function format2num (S)
{
  var Str = (typeof S != 'string') ? "0" : S;

  if (Str.match(/\%/))
  {
    Str = Str.replace(/\%/,"");
    Str = isNaN(Str) ? Str : (Str / 100);
  }
  else
  {
    Str = +Str.replace(/[,\$]/g,"");
  }

  return (Str);
}

// Cannot use because of Microsoft's defective implementation of prototypes
if (!Array.indexOf)
{
  Array.prototype.indexOf = function(obj)
  {
    for (var i = 0; i < this.length; i++)
    {
      if (this[i] == obj) { return i; }
    }
    return -1;
  }
}

// Deprecated - use array object fix above
function IndexOf(a,item)
{
  var len = a.length;

  for (var i = 0; i < len; i++) 
  { 
    if (a[i] === item) { return i; }
  } 

  return -1;
}

function getLBValue(lb,Text)
{
  for (var i = 0; i < lb.options.length; i++)
  {
    if (lb.options[i].text == Text)
    { return lb.options[i].value; }
  }
  
  // if failed, return -1
  return -1;
}

function getLBText(lb,Value)
{
  for (var i = 0; i < lb.options.length; i++)
  {
    if (lb.options[i].value == Value)
    { return lb.options[i].text; }
  }
  
  // if failed, return -1
  return -1;
}

// lb is listbox handle, a is associative array of data
function loadLB(lb,a)
{
  var newOption; 
  var Key;   

  clearListBox(lb);
  for (Key in a)
  { 
    if (typeof a[Key] == 'function') { continue; }
    newOption = document.createElement("option");
    newOption.text  = a[Key];
    newOption.value = Key;
    lb.options.add(newOption);
  }
}

// Add a single element to a listbox
function loadLBElement(lb,Value,Text)
{
  var newOption; 

  newOption = document.createElement("option");
  newOption.text  = Text;
  newOption.value = Value;
  lb.options.add(newOption);
}

// Add a single element to a listbox
function loadSLBElement(lb,Value,Text,Selected,Status)
{
  var newOption; 

  if (typeof(Status) == "undefined") { Status = "All"; }

  if ((Status == "All") || (Status == "Selected" && Selected) || (Status == "Unselected" && !Selected))
  {
    newOption = document.createElement("option");

    // Set background for new item to yellow if selected
    if (Selected) { newOption.style.background = "yellow"; Text += "\xa0*"}
    else { Text += "\xa0\xa0"; }

    newOption.text  = Text;
    newOption.value = Value;
    lb.options.add(newOption);
  }
}

// lb is listbox, a is associative array, i is index value to filter on.
// Assumes array records have leading index value to match against i.
// '|' is the record delimiter
function loadSubLB(lb,a,i)
{
  var newOption;    // Used to create options to add
  var Key;          // used to step through associative array
  var FID;          // id at front of assoc array record to filter on
  var Text;         // test of assoc array record to use in object
  var Fields;       // array for splitting assoc array record

  clearListBox(lb); // empty the listbox for refilling

  for (Key in a)    // step through keys of associative array a
  { 
    if (typeof a[Key] == 'function') { continue; }
    Fields = new Array();        // Set up Fields array
    Fields = a[+Key].split("|"); // split assoc array record
    FID    = Fields[0];          // first field is id to filter on
    Text   = Fields[1];          // second field is text to insert into obj

    if (+FID == +i) // test if this is the type of record we want
    {
      // Set up the new option
      newOption = document.createElement("option");
      newOption.text  = Text;
      newOption.value = Key;

      // and add to the listbox
      lb.options.add(newOption);
    }
  }
}  

// Format number N as as string of format F with precision P for display
// F types = [M,N,P] (Money, Number, Percent)
function num2format(N,F,P)
{
  // Check for 'NA' not apply
  if (N == 'NA') { return 'NA'; }
  if (!isFinite(N)) { return 'NA'; }

  // clean up and set defaults as needed
  N = (typeof N == 'undefined') ? 0  : N;
  F = (typeof F == 'undefined') ? '' : F;
  P = (typeof P == 'undefined') ? 0  : P;

  // If it is not a number, bail on format and return
  // Commented out alert 07/16/10 as per Jason Nicholas
  if (isNaN(N)) { /*alert ("N [" + N + "] is not a number");*/ return (N); }

  var i;                 // Counter
  var A = new Array();   // inputed array of chars
  var B = new Array();   // formatted array of chars

  switch(F)
  {
    case 'M': // Money?

      // Make N have cents and convert to array of chars
      A = (N.toFixed(2)).split("");

      // Move cents over first (to decimal point from right)
      for (i = 0; i < 3; i++) { B.unshift(A.pop()); }

      // Set any needed commas
      while (A.length > 3)
      {
        for(i = 0; i < 3; i++) { B.unshift(A.pop()); }
        B.unshift(',');
      }

      // Tidy up remaining numbers from A
      while (A.length) { B.unshift(A.pop()); }
  
      // Add $ to front
      B.unshift('$');

      break;

    case 'N':  // Straight up number with commas?
    case 'U':

      // Make N array of chars with precision P
      A = (N.toFixed(P) + "").split("");

      // Move chars from right of A to decimal point
      if (P > 0)
      { 
        while (B[0] != '.') { B.unshift(A.pop()); } 
      }

      // Set any needed commas
      while (A.length > 3)
      {
        for(i = 0; i < 3; i++) { B.unshift(A.pop()); }
        B.unshift(',');
      }

      // Tidy up remaining numbers from A
      while (A.length) { B.unshift(A.pop()); }

      break;

    case 'P': // Percentage?

      // Return N X 100, precision 1, plus % sign on end
      // If char to right of . is 0, remove decimal and last 0
      return (((N * 100).toFixed(1)).replace(/\.0/,"") + "%");
  }

  //alert('Leaving num2format');
  // Return the string if B array used
  return (B.join(""));
}

// Pad string S with C up to length N.  Return left N chars of padded S.
var PadSpaces = "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0" +    
                "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0";

function PadString(S,N)
{
  if (typeof S == "undefined") { alert ("PadString called with no arguments"); return; }

  S = S.replace(/\s+/," ");
  return (S + PadSpaces).substr(0,N);
}

function ROUND(N,P)
{
//  alert ("N is " + N + " and P is " + P);

  if (typeof N != "number") { alert("N is not a number: " + N); return (0); }
  else { return (+N.toFixed(P)); }
}

function selectLBText(lb,t)
{
  for (var i=0; i<lb.length && (lb.options[i].text != t); i++) {}
  lb.selectedIndex = (i == lb.length) ? -1 : i;
}

function selectLBValue(lb,v)
{
  if (v == -1) { lb.selectedIndex = -1; return }

  for (var i=0; i<lb.length && (lb.options[i].value != v); i++) {}
  lb.selectedIndex = (i == lb.length) ? -1 : i;
}

// Unselect all items of multiple select listbox
function unselectMLBoxAll(lb)
{
  for (var i = 0; i < lb.options.length; i++)
  {
    lb.options[i].selected = false;
  }
}

// lb is listbox object, v is value, selected = true or false
function selectMLBText(lb,t,selected)
{
  for (var i=0; i<lb.length && (lb.options[i].text != t); i++) {}

  if (i < lb.length)
  { lb.options[i].selected = selected; }
}

// lb is listbox object, v is value, selected = true or false
function selectMLBValue(lb,v,selected)
{
  for (var i=0; i<lb.length && (lb.options[i].value != v); i++) {}

  if (i < lb.length)
  { lb.options[i].selected = selected; }
}

// Update Text of lb item based on value
function updateLBVText(lb,V,T)
{
  for (var i = 0; i < lb.length; i++)
  {
    if (lb.options[i].value == V)
    {
      lb.options[i].text = T;
      return;
    }
  }
}

// Get number of selected item in Multiple Select Listbox
function getMLBoxNumSelected(lb)
{
  for(var Count = 0, i = 0; i < lb.options.length; i++)
  {
    if (lb.options[i].selected == true) { Count++; }
  }
  return Count;
}

// Check for RETURN key and return 1 if true and 0 if false
function isReturn(e)
{
  return ((e.keyCode && e.keyCode==13) || (e.which && e.which==13));
}

// Convert data to table - first row is header, rest is data
function Data2Table(Data)
{
  // Split records on <record>
  var Records = Data.split('<record>');

  // Get first record as header
  var Header =  Records.shift();

  // Massage header into table format
  Header = Header.replace(/<field>/g,"</th><th style='background-color:silver;'>");
  
  // Sew records back together with table code
  Data = Records.join("</td></tr><tr><td>");

  // Finish massaging data with table code
  Data = Data.replace(/<field>/g,"</td><td>");

  // Give empty cells hard space for formatting purposes
  Data = Data.replace(/<td><\/td>/g,"<td>&nbsp;</td>");             

  // Bring it all together with table start and finish code
  Data = "<table border=1 cellpadding=\"10px\" id='CommonTable'><tr><th style='background-color:silver;'>" + Header + "</th></tr><tr><td>" + Data + "</td></tr></table>";

  return (Data);
}

// START: Object code for Multiple Select Listbox Object
// Constructor
// ControlID is id of HTML Control, e.g. Department or District multile select listbox
// Send is the get formated data for sending to the server
// PHPFile is the name of the php file on the server that handles requests
function MSListBox(ControlID,PHPFile,Headers,ColumnWidths)
{ 
  this.ControlID = ControlID;                        // The control id from html
  this.PHPFile = PHPFile;                            // Path to php file handling server calls
  this.Headers = Headers;                            // Listbox column headers
  this.ColumnWidths = ColumnWidths;                  // Width of columns in spaces
  this.onLoad = 0;                                   // Function to call whenever done loading
  this.Selected = -1;                                // Tracks currently selected item
  this.LoadReq = getXMLHttpObject();                 // Load data from server request object
  this.setHeader = setHeaderMSListBox;               // Set up the header
  this.load = loadMSListBox;                         // Function initiating load
  this.callBack = loadMSListBoxHandler;              // Call back function to handle returning data
  this.show = showMSListBox;                         // Displays data in array OA in the listbox
  this.change = changeMSListBox;                     // Function called when listbox changes
  this.get = getMSListBox;                           // Function to retrieve data from OA and pack with <record> delimiter
  this.setAll = setAllMSListBox;                     // Sets display status to show all 
  this.setSelected = setSelectedMSListBox;           // Sets display status to show Selected elements only 
  this.setUnselected = setUnselectedMSListBox;       // Sets display status to show Unselected elements only
  this.selectAll = selectAllMSListBox;		     // Select all items in MSListBox - Added 12/21/2009 by Joe Melville
  this.clearAll = clearAllMSListBox;		     // Clears all items in MSListBox - Added 12/21/2009 by Joe Melville
  this.OA = new Array();                             // Array to hold actual data to be reflected in the HTML Control
  this.LB = document.getElementById(this.ControlID); // Listbox handle for this Control
  this.Control = this.LB;                            // Generic name of Control's handle for this object for 
                                                     //   accessing focus() etc.
  this.sqlText = getMSListBoxIn;
  this.Status = "All";                               // other options are 'Selected' and 'Unselected' 
                                                     // - changed directly in the HTML code

  this.setHeader();
}

// Sets the header on the listbox
function setHeaderMSListBox()
{
  // Get id of span where header goes
  var Text = '';

  if (this.Headers.length == 0) { return; }

  // If there is one header column, just center it
  if (this.Headers.length < 2)
  {
    Text = this.Headers[0].trim(); 

    // If the padding required is not an even number, add a nbsp to the header
    if ((this.Headers[0].length - this.ColumnWidths[0]) % 2 != 0)
    { this.Headers[0] += "\xa0"; }
    
    // Pad either side with spaces to make the listbox the right size even if empty
    var Pad = Math.round(((this.ColumnWidths[0] - this.Headers[0].length) + 1) / 2);
    Text = PadString("",Pad) + Text + PadString("",Pad); 
  }
  // otherwise, format column headers with ColumnWidth
  else
  {
    for (var i = 0,Text = ''; i < this.Headers.length; i++)
    {
      // by convention, put spaces in between columns
      if (i > 0) { Text += PadString("",2); }
      Text += PadString(this.Headers[i],this.ColumnWidths[i]);
    }
  }   

  // By convention, the ID of the title span is the ID of the control plus 'Title'
  // Padding with \xA0 also happen in listbox to cover for brackets when selected
  if (this.ControlID.match(/CID$/)) { var HeaderID = this.ControlID.replace(/CID$/,"TitleCID"); }
  else { var HeaderID = this.ControlID + "Title"; }
  document.getElementById(HeaderID).innerHTML = "\xa0\xa0" + Text + "\xa0\xa0";           
}

// Performs load request to server
// Send contains GET formated data string that will show up on server as POST
function loadMSListBox(Send)
{
  clearListBox(this.Control);  
  // Make the AJAX call
  xmlhttpCallOop(this.PHPFile,this.LoadReq,this,Send);
}

// We use the display text as the key for the interal object array of data
// supporting this Multiple Select listbox because it keeps things sorted correctly
function loadMSListBoxHandler(Data)
{
  // If no data, just return
  if (Data == "") 
  { 
    if (this.onLoad != 0) { this.onLoad(); }
    return; 
  }

  this.OA = new Array();

  // Get Data from the server and break into array of records
  var Records = new Array();
  Records = Data.split('<record>');

  // Load the supporing arrays
  // Fields 0 = DID, 1 = Name, 2 = Selected (<NONE> means not selected)
  for (var R in Records)
  {
    if (typeof Records[R] == 'function') { continue; }

    var Text = '';

    var Fields = Records[R].split('<field>');

    // Gather other fields and format according to width
    var i;
    for (i = 1; i <= this.ColumnWidths.length; i++)
    {
      if (i > 1) { Text += PadString("",2); }

      // Note, ColumnWidths is 0 based and Fields is 1 based because non displayed id is in column 0
      Text += PadString(Fields[i],this.ColumnWidths[i - 1]);
    }  

    // Create new object on the arrat
    this.OA[Text] = new Object();

    // Save the id
    this.OA[Text].id = Fields[0];

    // Save the display text
    this.OA[Text].text = Text;

    // Indicate if selected
    this.OA[Text].selected = (Fields[i] == '<NONE>') ? 0 : 1;

    // Save the sql text
    Fields.shift();
    Fields.pop();
    this.OA[Text].sqlText = Fields.join(' ');
  }          

  // Refresh the listbox
  this.show();
}

// Shows array of data in listbox
function showMSListBox()
{
  // keep track of where we are in the listbox
  this.Selected = this.LB.selectedIndex;

  // blow listbox contents
  clearListBox(this.LB);

  // refill based on array OA
  for (var Key in this.OA)
  {
    if (typeof this.OA[Key] == 'function') { continue; } 
    loadSLBElement(this.LB,Key,this.OA[Key].text,this.OA[Key].selected,this.Status); 
  }

  // go back to previous location
  this.LB.selectedIndex = this.Selected;

  if (this.onLoad != 0) { this.onLoad(); }
}


// Selects all items array of data in listbox
// Added by Joe Melville 12/21/2009
function selectAllMSListBox()
{
  // keep track of where we are in the listbox
  this.Selected = this.LB.selectedIndex;

  // mark all itmes in OA as selected
  for (var Key in this.OA)
  {
    if (typeof this.OA[Key] == 'function') { continue; } 
    this.OA[Key].selected = true;
  }
}

// Clears all items array of data in listbox
// Added by Joe Melville 12/21/2009
function clearAllMSListBox()
{
  // keep track of where we are in the listbox
  this.Selected = this.LB.selectedIndex;

  // mark all itmes in OA as selected
  for (var Key in this.OA)
  {
    if (typeof this.OA[Key] == 'function') { continue; } 
    this.OA[Key].selected = false;
  }
}


// Handle double clicks to change an element's selected status
function changeMSListBox()
{
  // Toggle the selected value by xoring against 1
  this.OA[this.LB.options[this.LB.selectedIndex].value].selected ^= 1;

  // Redisplay
  this.show(); 
}

function getMSListBoxIn()
{
  return this.get(1);
}

// Gather data from array for passing to server with <record> delimiter
function getMSListBox(FormatArg)
{
  var Format = (typeof FormatArg == 'undefined') ? 0 : FormatArg;

  // Initialize Records - will be comma delimited text of record IDs
  var Records = "";
  
  // If department listbox has length, look for selected items
  if (this.LB.length > 0)
  {
    // Create an array to collect selected departments
    var A = new Array();

    // Get the selected records from OA
    for (var Key in this.OA)
    { 
      if (typeof this.OA[Key] == 'function') { continue; }
      if (Format > 0)
      { 
        if (this.OA[Key].selected) { A.push(this.OA[Key].sqlText); }
      }
      else
      {
        if (this.OA[Key].selected) { A.push(this.OA[Key].id); } 
      }
    }

    // if some selected departments showed up, pump them into string as <record> delimited
    if (A.length > 0) 
    {
      switch(Format)
      {
        case 0:
          Records = A.join("<record>"); 
          break;

        case 1:
          Records = "(\"" + A.join("\",\"") + "\")";
          break;

        default:
          Records = "";
          alert("Undefined data format in getMSListBox()");
      }
    }
  }

  return (Records);
}

function setAllMSListBox()
{
  this.Status = 'All';
  this.show();
}

function setSelectedMSListBox()
{
  this.Status = 'Selected';
  this.show();
}

function setUnselectedMSListBox()
{
  this.Status = 'Unselected';
  this.show();
}

// END: Object code for Multiple Select Listbox Object

// START: Object code for Single Select Listbox Object
// Constructor
// ControlID is id of HTML Control, e.g. Department or District single select listbox
// Send is the get formated data for sending to the server
// PHPFile is the name of the php file on the server that handles requests
function SSListBox(ControlID,PHPFile,Headers,ColumnWidths,Initial_ID)
{
  this.ID = -1;                                      // ID/Value of currently selected item
  if (typeof Initial_ID != "undefined") this.ID = Initial_ID;
  this.ControlID = ControlID;                        // The control id (i.e. String Identifier/Name) from html
  this.Name = this.ControlID.replace(/CID$/,"");     // Cleaned up name of this control for feedback purposes
  this.PHPFile = PHPFile;                            // Path to php file handling server calls
  this.Headers = Headers;                            // Listbox column headers
  this.ColumnWidths = ColumnWidths;                  // Width of columns in spaces
  this.onLoad = 0;                                   // Routine to run whenever done loading listbox
  this.LoadReq = getXMLHttpObject();                 // Load data from server request object
  this.setID = setIDSSListBox;                       // Function to set the currently selected item
  this.setHeader = setHeaderSSListBox;               // Set up the header
  this.load = loadSSListBox;                         // Function initiating load
  this.callBack = loadSSListBoxHandler;              // Call back function to handle returning data
  this.LB = document.getElementById(this.ControlID); // Listbox handle for this Control
  this.Control = this.LB;                            // Generic name of Control's handle for this object for 
                                                     // accessing focus() etc.
  this.selected = function() { return (this.Control.selectedIndex == -1) ? 0 : 1; }
  // 10/15/09 Code commented out and modified to eliminate subscript out of range error.  --  Joe Melville
  // this.value = function() { return this.Control.options[this.Control.selectedIndex].value; }
  // 02/26/10 Code commented out and modified to eliminate cannot be null error.  -- Chris
  //  this.value = function() { return ((this.Control.selectedIndex == -1) ? null : 
  //    this.Control.options[this.Control.selectedIndex].value); }
  this.value = function() 
  { 
    return ((this.Control.selectedIndex == -1) ? -1 : this.Control.options[this.Control.selectedIndex].value); 
  }
  this.text = function() { return this.Control.options[this.Control.selectedIndex].text; }
  this.sqltexts = new Array();
  this.sqlText = sqlText;
  this.check = checkSSListBox;      // Make sure selected or abort, give message and refocus

  // If we are using a title header, then the first header will not be blank.
  if (this.Headers[0] != "") { this.setHeader(); }

  this.unselect = function() { this.Control.selectedIndex = -1; this.ID = -1; }
}

// if ID not passed in, defaults to the this.ID
function setIDSSListBox(ID)
{
  // Reset ID if we passed it in
  if (typeof(ID) != "undefined") { this.ID = ID; }

  selectLBValue(this.LB,this.ID);  
}

// Sets the header on the listbox
function setHeaderSSListBox()
{
  // Get id of span where header goes
  var Text = '';

  if (this.Headers.length == 0) { return; }

  // If there is one header column, just center it 
  if (this.Headers.length < 2) 
  {
    Text = this.Headers[0].trim(); 
   
    // If the padding required is not an even number, add a nbsp to the header
    if ((this.Headers[0].length - this.ColumnWidths[0]) % 2 != 0)
    { this.Headers[0] += "\xa0"; }
    
    // Pad either side with spaces to make the listbox the right size even if empty
    var Pad = Math.round(((this.ColumnWidths[0] - this.Headers[0].length) + 1) / 2);
    Text = PadString("",Pad) + Text + PadString("",Pad); 
  }
  // otherwise, format column headers with ColumnWidth
  else
  {
    for (var i = 0,Text = ''; i < this.Headers.length; i++)
    {
      // by convention, put spaces in between columns
      if (i > 0) { Text += PadString("",2); }
      Text += PadString(this.Headers[i],this.ColumnWidths[i]);
    }
  }   

  // By convention, the ID of the title span is the ID of the control plus 'Title'
  // Padding with \xA0 also happen in listbox to cover for brackets when selected
  if (this.ControlID.match(/CID$/)) { var HeaderID = this.ControlID.replace(/CID$/,"TitleCID"); }
  else { var HeaderID = this.ControlID + "Title"; }
  document.getElementById(HeaderID).innerHTML = "\xa0" + Text + "\xa0";           
}

function checkSSListBox()
{
  if (!this.selected()) 
  { 
    alert(this.Name + " must be selected"); 
    FixMe = this.Control; 
    setTimeout("o" + this.Name + ".Control.focus()",500); 
    return 0;
  }
  else { return 1; }
}

// Performs load request to server
// Send contains GET formated data string that will show up on server as POST
function loadSSListBox(Send)
{  
  clearListBox(this.Control);
  // Make the AJAX call
  xmlhttpCallOop(this.PHPFile,this.LoadReq,this,Send);
}

function sqlText()
{
  return this.sqltexts[this.value()];
}

// Callback function for load request
function loadSSListBoxHandler(Data)
{
  // If no data, just return
  if (Data == "") 
  { 
    if (this.onLoad != 0) { this.onLoad(); }
    return; 
  }

  // Get Data from the server and break into array of records
  var Records = Data.split('<record>');

  this.sqltexts = new Array();

  // Load the supporing arrays
  // Fields 0 = DID, 1 = Name, 2 = Selected (<NONE> means not selected)
  for (var R in Records)
  {
    if (typeof Records[R] == 'function') { continue; }

    var Text = '';

    var Fields = Records[R].split('<field>');

    var Value  = Fields[0];         // Place the ID in Value

    // Keep track of core sql string (2nd field)in
    this.sqltexts[Value] = Fields[1];
    
    // Gather other fields and format according to width
    for (var i = 1; i <= this.ColumnWidths.length; i++)
    {
      if (i > 1) { Text += PadString("",2); }

      // Note, ColumnWidths is 0 based and Fields is 1 based because 
      // non displayed id is in column 0
      Text += PadString(Fields[i],this.ColumnWidths[i - 1]);
    }  
    loadLBElement(this.LB,Value,Text);
  }          

  selectLBValue(this.LB,this.ID);

  // if other processes are dependent on this load finishing, chain onto them
  if (this.onLoad != 0) { this.onLoad(); }
}

// END: Object code for Single Select Listbox Object

// START: Input box object
// Type Phone,Email,Money,Number,NonBlank,Unique
// Date and time to be handled by specific controls
// Record_ID_Value is the ID of the record being edited - used for unique test
// Record_ID_Name is the name of the record id, ex. Role_ID
function InputBox(ControlID,PHPFile,Type,Text,Required,Record_ID_Value,Record_ID_Name)
{
  this.Name = "o" + ControlID.replace(/CID$/,"");
  this.ControlID = ControlID;
  this.ControlName = ControlID.replace(/CID$/,"");
  this.PHPFile = PHPFile;
  this.Type = Type;
  this.Text = "";
  this.Required = 1;
  if (typeof(Text) != "undefined") { this.Text = Text; }
  if (typeof(Required) != "undefined") { this.Required = Required; }
  this.set = setInputBox;
  this.get = getInputBox;
  this.check = checkInputBox;
  this.unique = uniqueInputBox;
  this.uniqueHandler = uniqueHandlerInputBox;
  this.stat = statInputBox;
  this.statHandler = statHandlerInputBox;
  this.callBack = uniqueHandlerInputBox;               // default callBack handler
  this.Control = document.getElementById(this.ControlID);
  this.UniqueReq = getXMLHttpObject();                 // Perform unique check
  this.StatReq = getXMLHttpObject();                   // Perform stat() call to see if file/directory exists
  this.selected = function() { return this.Control.selectedIndex; }
  this.Record_ID_Value = (typeof Record_ID_Value != 'undefined') ? Record_ID_Value : -1;
  this.Record_ID_Name = (typeof Record_ID_Name != 'undefined')   ? Record_ID_Name  : "";

  // if (this.Text != "") { this.set(); }
  // Changed this code 10/14/09 to fix initialization bug.  --  Joe Melville
  this.set();
}

function setInputBox(Text)
{
  if (typeof(Text) != "undefined") { this.Text = Text; }

  this.Control.value = this.Text;
}

function getInputBox()
{
  return fixPost(this.Control.value);
}

// SkipUnique being set to 1 will cause the server side unique check, which is now depricated, to be skipped
// Unique checks should be called independently with a chained callback function
// Otherwise all other checks are client side and the function can return 1 for success and 0 for failure
function checkInputBox(SkipUnique)
{
  var Text = this.Control.value;
  var Success = 1;

  if (Text == "" && this.Required == 0) { return Success; }

  if (Text == "" && this.Required == 1) { return 0; }

  switch(this.Type)
  {
    case "Unique":
      if (typeof SkipUnique != 'undefined' && SkipUnique == 1) { break; }
      // Unique fields cannot be blank
      if (Text == "") 
      { 
        alert ("This field cannot be blank"); 
        FixMe = this.Control; 
        setTimeout(this.Name + ".Control.focus()",500); 
        break; 
      }
      // Branch to the unique check on the server which will be handled 
      // by uniqueHandlerInputBox (callBack) below
      this.unique();
      break;
    case "Phone":
      var PhoneNum = ""
      var Extension = "";
      var A = Text.split(/x/i);
      PhoneNum = A[0];
      if (A.length == 2) { Extension = A[1]; }
      var Test = PhoneNum.replace(/[\(\)\s\-a-zA-Z]/g,"");
      switch (Test.length)
      {
        case 7:
          this.Control.value = Test.substr(0,3) + " - " + Test.substr(3,4);
          break;
        case 10:
          this.Control.value = 
            "(" + Test.substr(0,3) + ") " + Test.substr(3,3) + "-" + Test.substr(6,4);
          break;
        default:
          alert("Invalid phone number: " + Text); 
          setTimeout(this.Name + ".Control.focus()",500);
          Success = 0;
          break;
      }

      if ((Extension = Extension.trim()) != "" && Extension.match(/^\d+$/)) 
      { this.Control.value += " x " + Extension; }

      break;
    case "Email":
      if (!Text.match(/.+?\@.+?\..+/)) 
      { 
        alert("Invalid email address: " + Text); 
        setTimeout(this.Name + ".Control.focus()",500); 
        Success = 0;
      }
      break;
    case "Money":
      var Test = Text.replace(/[\$,]/g,"");
      if (!Test.match(/^\d+\.\d\d$/) && !Test.match(/^\d+$/)) 
      { 
        alert("Invalid money: " + Text); 
        setTimeout(this.Name + ".Control.focus()",500); 
        Success = 0;
      } 
      break;
    case "Number":
      var Test = Text.replace(/[\s,]/g,"");
      if (!Test.match(/^[\+\-]{0,1}[\d,]+\.{0,1}\d{0,}$/)) 
      { 
        alert("Invalid number: " + Text); 
        setTimeout(this.Name + ".Control.focus()",500); 
        Success = 0;
      }        
      break;
    case "Zip":
      var Test = Text.replace(/[\s\-]/g,"");
      switch (Test.length)
      {
        case 5:
          this.Control.value = Test;
          break;
        case 9:
          this.Control.value = Test.substr(0,5) + "-" + Test.substr(5,5);
          break;
        default:
          alert("Invalid zipcode: " + Text); 
          Success = 0;
      }
      break;
    case 'FileSystem':
      this.stat();
      break;
    case "Filename":
      var Test = Text.replace(/[a-zA-Z0-9-_\/]/g,"");
      if (Test != "")
      {
        alert("Invalid filename characters found <" + Test + 
              ">. Replace and try again.");
        Success = 0;
      }
      break;
    case "NonBlank":
      // Unique fields cannot be blank
      if (Text == "") 
      { 
        alert ("This field cannot be blank"); 
        setTimeout("FixMe.focus()",500); 
        Success = 0;
      }
    default:
      break;
  }
  return Success;
}

// This has become more than just a unique check.  It is essentially the primary
// Key checker.  Current standard is to setup the Record_ID_Value and the
// Record_ID_Name so that calls to the unique check routine can be done.
// Compound keys will still need to have their own unique check routine
// defined along the lines of this function
// and inserted into the object on the 'unique' attribute
// The current function is designed to continue to support the deprecated
// simple/generalized ID method, where a global ID variable existed for
// the record, and this routine used it.
function uniqueInputBox(callBack)
{
  // If Call back function presented by caller, use it
  if (typeof callBack != "undefined") { this.callBack = callBack; }

  // the use of a generic ID is deprecated, but temporarily supported til
  // it is verified to be all gone
  if (typeof ID == 'undefined') { ID = this.Record_ID_Value; }

  var Send = "";
 
  if (this.Record_ID_Name != "") // use new method
  {
    Send = "Task=" + this.ControlName + 
           "&" + this.ControlName + "=" + this.Control.value + 
           "&" + this.Record_ID_Name + "=" + this.Record_ID_Value;
  }
  else                           // use deprecated method
  {
    Send = "Task=" + this.ControlName + 
           "&" + this.ControlName + "=" + this.Control.value + 
           "&ID=" + ID;
  }

  // Make the AJAX call
  xmlhttpCallOop(this.PHPFile,this.UniqueReq,this,Send);
}

function uniqueHandlerInputBox(Data)
{
  if (Data != "") 
  { 
    alert(this.ControlID + " '" + Data + "' already exists"); 
    this.Control.focus(); 
  }
}

function statInputBox()
{
  // Get the string value of the control
  var Text = this.Control.value;
  
  // Set up stat check
  var Send = "Task=Stat&File=" + Text;

  this.callBack = this.statHandler;

  // Make the AJAX call
  xmlhttpCallOop(this.PHPFile,this.StatReq,this,Send);
}

function statHandlerInputBox(Data)
{
  var Text = this.Control.value;

  if (Data == 0) { alert("Invalid file or directory name: " + Text); this.Control.focus(); }
}

// Interfaces between checkboxes and an integer that stores their bitmapped values
// ControlIDs = list of IDs from HTML, ControlValues = corresponding values for controls, Value bitmapped startup value
function CheckBox(ControlIDs,ControlValues,Value)
{
  this.ControlIDs = ControlIDs;
  this.ControlValues = ControlValues;
  this.Value = 0;
  if (typeof Value != "undefined") { this.Value = Value; }
  this.load = loadCheckBox;
  this.get = getCheckBox;
  this.set = setCheckBox;
  // for compatibility with other control objects
  this.check = function() { return 1; }

  // if Value > 0, load the checkboxes
  this.load(Value);
}

// Value is a bitmap.  Just set to 1 or 0 for singled checkbox implementations
function loadCheckBox(Value)
{
  if (typeof Value != "undefined") { this.Value = Value; }

  for (var i = 0; i < this.ControlIDs.length; i++)
  {
    document.getElementById(this.ControlIDs[i]).checked = (this.ControlValues[i] & this.Value) ? true : false;
  }
}

// If a checkbox ID passed in, checks on that box only, otherwise returns bitmap of all boxes
function getCheckBox(ID)
{
  // Are we looking for a specific checkbox
  if (typeof ID != "undefined") 
  { 
    // Make sure checkbox ID exits or return 0
    if (this.ControlIDs.indexOf(ID) == -1) { return 0; }

    // Then return the corresponding value
    return (document.getElementById(ID).checked); 
  }

  // Otherwise return bitmapped integer of all the checkboxes
  this.Value = 0;
  for (var i = 0; i < this.ControlIDs.length; i++)
  {
    this.Value |= (document.getElementById(this.ControlIDs[i]).checked ? this.ControlValues[i] : 0);
  }
  return this.Value;
}

function setCheckBox(ID,Value)
{
  // default to unchecked
  var Checked = 0;

  // if a value for checked passed in evaluate and set it's boolean value
  if (typeof Value != "undefined") { Checked = (Value == 0) ? 0 : 1; }

  // Make sure the ID exists, otherwise do nothing and return
  if (this.ControlIDs.indexOf(ID) == -1) { return; }

  // Set the checkbox
  document.getElementById(ID).checked = Checked;
}

// End Checkbox Object

// Time Object Constructor
function Clock(HourID,MinID,TStamp)
{
  this.HourID = HourID;
  this.MinID = MinID;
  this.HourLB = document.getElementById('HourID');
  this.MinLB = document.getElementById('MinID');
  this.TStamp = "1970-01-01 00:00";
  if (typeof TStamp != "undefined") { this.TStamp = TStamp; }
  this.load = loadClock;
  this.get = getClock;
  this.init = initClock;

  // Load the list boxes with hours and minutes
  this.init();

  // Load the clock
  this.load();
}

// Stuffs the hour and minute lb controls with hours and minute
function initClock()
{
  var hlb = document.getElementById(this.HourID);
  var mlb = document.getElementById(this.MinID);
  var v;
  var t;

  for (v = 0; v < 24; v++)
  {
    t = (("00" + v).match(/(..)$/))[1];
    loadLBElement(hlb,t,t);        
  }
  for (v = 0; v < 60; v++)
  {
    t = (("00" + v).match(/(..)$/))[1];
    loadLBElement(mlb,t,t);        
  }
}

// Load hour and minute controls based on unix_timestamp
function loadClock(TStamp)
{
  var hlb = document.getElementById(this.HourID);
  var mlb = document.getElementById(this.MinID);

  if (typeof TStamp != "undefined") { this.TStamp = TStamp; }

  if (this.TStamp == '') { return; }

  var HM    = this.TStamp.match(/\s([0-9]+):([0-9]+)$/);
  this.Hour = HM[1];
  this.Min  = HM[2];

//  alert ("Loading - " + this.Hour + " : " + this.Min);

  selectLBValue(hlb,this.Hour);
  selectLBValue(mlb,this.Min);
}

function getClock()
{
  var hlb = document.getElementById(this.HourID);
  var mlb = document.getElementById(this.MinID);

  if (hlb.selectedIndex == -1) { return ""; }
  if (mlb.selectedIndex == -1) { return ""; }

  this.Hour = hlb.options[hlb.selectedIndex].value;  
  this.Min  = mlb.options[mlb.selectedIndex].value;  

//  alert("Saving = " + this.Hour + " : " + this.Min);

  return ((this.Hour) + ':' + (this.Min));
}

function myDebug()
{
  var debug = document.getElementById('Debug');
  var ret = getCookie("MGRANTTRACKINGSRET");
  
  debug.innerHTML = 'MGRANTTRACKINGSRET: ' + ret;
}


function getCookie(NameOfCookie)
{

// First we check to see if there is a cookie stored.
// Otherwise the length of document.cookie would be zero.

  if (document.cookie.length > 0)
  {
  
    // Second we check to see if the cookie's name is stored in the
    // "document.cookie" object for the page.
    
    // Since more than one cookie can be set on a
    // single page it is possible that our cookie
    // is not present, even though the "document.cookie" object
    // is not just an empty text.
    // If our cookie name is not present the value -1 is stored
    // in the variable called "begin".
  
    begin = document.cookie.indexOf(NameOfCookie+"=");
    if (begin != -1)
    {
  
      // Our cookie was set.
      // The value stored in the cookie is returned from the function.
    
      begin += NameOfCookie.length+1;
      end = document.cookie.indexOf(";", begin);
      if (end == -1) 
      {
        end = document.cookie.length;
        return unescape(document.cookie.substring(begin, end));
      }
    }
  }

  return null;
  
  // Our cookie was not set.
  // The value "null" is returned from the function.
}

// Check to make sure string S is not blank, otherwise warn and return '' as error and set focus to obj
// On success return concatonated regexp string for providing extensive lookup in the query
// NOTE: in current form, requires /\\\/,"" substitution in php handler on server side
function lookupUser(S,Obj)
{
  // sql search template
  var Template = "(username regexp '<word>' or firstname regexp '<word>' or lastname regexp '<word>')";

  // Get the words
  var S = S.trim();

  if (S == "") { alert ("You must enter some search text"); Obj.focus(); return(''); }

  var Words = S.split(/\s+/);

  // Setup an array to hold search components
  var SArray = new Array();

  // Step through the Words array and build a seach component for each and store in SArray
  for (var W in Words)
  {
    if (typeof Words[W] == 'function') { continue; }  // Cover Microsoft booboo
    var T = Template.replace(/<word>/g,Words[W]);
    SArray.push(T);    
  }

  // Turn SArray into string SString for shipping on to searchr.php
  var SString = SArray.join(" AND ");
  
  // Build send Request - Differs from document search because of Task=SearchUser. Otherwise same call
  return(SString); 
}

function pow2caret(F)
{
  // Remove pesky spaces from formula
  F = F.replace(/\s/g,'');

  var FBuff = F.split('');  // Split formula string into array
  var SBuff = new Array();  // Declare general output buffer
  var VBuff = new Array();  // Declare buffer to hold char while verifying Math.pow
  var Buff1 = new Array();  // Declare buffer to hold first arg
  var Buff2 = new Array();  // Declare buffer to hold second arg
  var PCount = 0;           // Declare var to track left paren count
  var State = 'S';          // S - search for Math.pow(
                            // V - verify Math.pow(
                            // 1 - collect first arg
                            // 2 - collect second arg

  // Step through input buffer FBuff and process to output buffer SBuff
  for(var i = 0; i < FBuff.length; i++)
  {
    switch(State)
    {
      case 'S': // Search for Math.pow and just tack stuff onto output buffer SBuff

        if (FBuff[i] == 'M')     // start verifying we are in Math.pow( sequence
        {
          VBuff = new Array();   // reinitialize VBuff to hold chars while checking
          VBuff.push(FBuff[i]);  // shove M onto it
          State = 'V';           // switch to 'V' State
        }
        else { SBuff.push(FBuff[i]); }  // Otherwise just shove char onto output buffer
      
        break;

      case 'V': // Verify Math.pow, keep stuff in VBuff so abort is easy
                // Abort by switching state back to 'S'

        switch(VBuff[VBuff.length - 1])  // Check for expected chars based on last char
        {
          case 'M':
            if (FBuff[i] != 'a') { State = 'S'; }
            break;              
          case 'a':
            if (FBuff[i] != 't') { State = 'S'; }
            break;              
          case 't':
            if (FBuff[i] != 'h') { State = 'S'; }
            break;              
          case 'h':
            if (FBuff[i] != '.') { State = 'S'; }
            break;              
          case '.':
            if (FBuff[i] != 'p') { State = 'S'; }
            break;              
          case 'p':
            if (FBuff[i] != 'o') { State = 'S'; }
            break;              
          case 'o':
            if (FBuff[i] != 'w') { State = 'S'; }
            break;              
          case 'w':
            if (FBuff[i] != '(') { State = 'S'; }
            else                 { State = '1'; Buff1 = new Array(); }
            break;               // Success, setup State 1
        }      

        if (State == 'S')        // We aborted because verify failed
        {
          SBuff = SBuff.concat(VBuff);   // Shove collected VBuff stuff onto SBuff
          SBuff.push(FBuff[i]);          // Add this latest char and continue in S mode
        }
        else if (State == 'V') { VBuff.push(FBuff[i]); }
                                         // Store input char to VBuff while checking
        break;

      case '1':  // look for comma to end first arg

        if (FBuff[i] == ',')     // Found the comma
        { 
          SBuff = SBuff.concat(Buff1);   // Push first arg in Buff1 onto output buffer
          SBuff.push('^');               // Add ^ operator for XLS
          State = '2';                   // Change state to 2
          Buff2 = new Array();           // Setup Buff2
          PCount = 0;                    // Initialize PCount which is used in State 2
        }
        else                 
        { Buff1.push(FBuff[i]); }        // Keep adding chars to Buff1
      
        break;

      case '2':  // look for ending paren with PCount == 0

        switch (FBuff[i])
        {
          case ')': // If PCount is 0, this is the matching Paren - finish up
                    // otherwise dec PCount and add char to Buff2

            if (PCount == 0) { State = 'S'; SBuff = SBuff.concat(Buff2); }
            else             { PCount--; Buff2.push(FBuff[i]); }

            break;

          case '(':               // Inc PCount and add char to Buff2 below
            PCount++;

          default:

            Buff2.push(FBuff[i]); // Keep building Buff2 
        }
      
        break;  
    }
  }

  return SBuff.join('');          // Return SBuff as a string
}

// Edit Window (Edwin) - Simple popup window for editing variables in the parent window
// Accepts Title: Title of page
//      VarNames: Array of names of vars in code on parent page that are to be edited
//      VarAlias: Array of names of controls in edwin.php corresponding to VarNames 
//      FormText: HTML code for the form section of edwin.php
function EditWindow(Title, VarNames, VarAlias, FormText)
{
  // Title of window
  this.Title    = (typeof Title != "undefined") ? Title : "";
  // Array of javascript var names to edit, default to Note
  this.VarNames = (typeof VarNames != "undefined") ? VarNames : new Array('Note');
  // The corresponding HTML control names for the javascript VarNames, default to Note
  this.VarAlias = (typeof VarAlias != "undefined") ? VarAlias : new Array('Note');
  this.height     = 250;
  this.width      = 450;
  this.top        = (screen.height - this.height) / 2;
  this.left       = (screen.width - this.width) / 2;
  this.location   = "no";
  this.status     = "no";
  this.toolbar    = "no";
  this.scrollbars = "yes";
  this.resizable  = "yes";
  this.handle     = null;  // Handle for the Window
  this.open       = openEditWindow;
  this.close      = closeEditWindow;
  this.onsave     = null; // called for additional action on save
  this.onclose    = null; // called from this.close if not null
  this.validate   = null; // sample validation function below, called from edwin.php if not null
  this.SaveText   = 'Ok'; // Caption for Save button
  this.CloseText  = 'Cancel'; // Caption for Close button

  // Text of Form containing the HTML with the input/textarea boxes for the VarNames array
  // The default setup follows
  this.FormText = (typeof FormText != "undefined") ? FormText : "" +
    "<center>\n" +
    "<table border=1 'style=padding:2px'>\n" +
    "<tr>\n" +
    "  <th style='background:silver'>\n" +
    "    <script type='text/javascript'>document.write(Title);</script>\n" +
    "  </th>\n" +
    "</tr>\n" +
    "<tr>\n" +
    "  <th align='center'>\n" +
    "    <textarea name='Note' style='height:150px;width:400px;' onChange='Changed=1;'>\n" +
    "    </textarea>\n" +
    "  </th>\n" +
    "</tr>\n" +
    "</table>\n" +
    "</center>\n";
}

// Accepts Title and VarName, otherwise uses Object's Title and VarName
function openEditWindow(Title, VarNames, VarAlias, FormText)
{
  this.Title    = (typeof Title != "undefined")    ? Title    : this.Title;
  this.VarNames = (typeof VarNames != "undefined") ? VarNames : this.VarNames;
  this.VarAlias = (typeof VarAlias != "undefined") ? VarAlias : this.VarAlias;
  this.FormText = (typeof FormText != "undefined") ? FormText : this.FormText;

  // Make sure top and left current vis-a-vis the current height and width
  this.top  = (screen.height - this.height) / 2;
  this.left = (screen.width - this.width) / 2;

  // Make sure we have what we need
  if (this.Title  == "")
  { alert("Title empty in cesalib.js"); return; }
  if (this.VarNames.length == 0)
  { alert("VarAlias empty in cesalib.js"); return; }
  if (this.VarAlias.length != this.VarNames.length)
  { alert("VarAlias size does not match VarNames in cesalib.js"); return; }
  if (this.FormText == "")
  { alert("FormText empty in cesalib.js"); return; }

  // Set up the parameters part of the arg
  var param = "width="       + this.width      +
              ",height="     + this.height     +
              ",top="        + this.top        +
              ",left="       + this.left       +
              ",location="   + this.location   +
              ",status="     + this.status     +
              ",toolbar="    + this.toolbar    +
              ",scrollbars=" + this.scrollbars +
              ",resizable="  + this.resizable;

  // Set up the URL part of the art
  var URL = "edwin.php";

  // If the handle is null or closed, open a window
  if (!this.handle || this.handle.closed)
  { this.handle = window.open(URL,"subWindow",param); }
  else
  // otherwise, just go to it
  { this.handle.focus(); }
}

// This allows us to close the windows and run any onclose functions that might be set up
function closeEditWindow()
{
  if (this.handle != null) { this.handle.close(); }
  if (this.onclose != null) { this.onclose(); }
}

// Unused sample validation routine for Edwin
// Fields is an array of values automatically taken from the VarAlias list of controls.
// On failure, appropriate error message returned.
// On success, returns "".
// Copy this function to a new name in your code, modify and
// change Edwin to point to it with Edwin.validate = myvalidation;
function validateEditWindow(Fields)
{
  var MailTo = Fields[0];
  var MailFrom = Fields[1];
  var Subject = Fields[2];
  var Message = Fields[3];

  if (MailTo == "") { return ("MailTo cannot be blank"); }
  if (MailFrom == "") { return ("MailFrom cannot be blank"); }
  if (Subject == "") { return ("Subject cannot be blank"); }
  if (Message == "") { return ("Message cannot be blank"); }

  // Return empty string for no error
  return "";
}

// Code to update moodle credentials and log in to moodle
function jsMoodle(LUID)
{
  alert("LUID = " + LUID);

  // Build send Request
  var Send = "Task=UpdateMoodle&LUID=" + LUID; 

  // Call Ajax
  xmlhttpCall('cesamoodle.php',UpdateMoodleReq,updateMoodleHandler,Send);
}

function updateMoodleHandler(Data)
{
  var Credentials = Data.split('<field>');
  var F = document.Moodle;

  F.target = "_blank";  
  F.username.value = Credentials[0];
  F.password.value = Credentials[1];

  alert("username is " + F.username.value + " and password is " + F.password.value);  

  F.submit();
}

function jsFilename(e)
{
  var code,keychar,numcheck,browser,isMSIE;

  isMSIE = (navigator.userAgent.match(/MSIE/)) ? true : false;

  if (isMSIE == true) { code = e.keyCode; }
  else                { code = e.charCode; }

  if ((code == 8) || (code == 13)) { return true; }

  if ((code < 32) || (code > 126)) { return true; }

  keychar = String.fromCharCode(code);
  numcheck = /[a-zA-Z0-9-_\/]/;
  return numcheck.test(keychar);
}




