/*
** File : db.page.js
**
** Copyright 2005 by Community College Workforce Alliance, 
**   Richmond, Virginia, USA. www.ccwasmallbusiness.com
**
** Contents : This file contains the code to read and write the data
**            associated with the various forms in the EntrePlan
**            plan software by CCWA.
**
** Original Version: George Flanagin 6 October 2005.
*/

//**********
// The use of client side JavaScript to read data from a database is a 
// little irregular. Here is an explanation of the techniques that 
// are used. They have been gleaned from the Rhino book by O'Reilly,
// the SQL2 Reference manual, and the article from the 15 July 2005
// issue of Kuro5hin: "Client-side ADO using Javascript," by "curien."
//**********
// The general procedure is:
//  [1] Create an SQL statement that does what you want done. A bit of
//      care must be taken when building the SQL statement because of
//      the embedded quotes. In our case, we are searching by business
//      plan name and page name. The found records will have all we
//      need, including the names of the fields on the page, and the
//      values to populate them.
//  [2] Submit the SQL statement to the database connection. There are
//      several ways to do this, but the clearest one is through the
//      Execute() method belonging to the database connection object.
//  [3] The result of the execution of the SQL is a possibly empty
//      record set. You never know, do you? The record set appears
//      as an object, and it is manipulated by the MoveNext() 
//      method. Another "gotcha" occurs because this type of operation is
//      often done in VB Script, in which it is not possible to tell
//      the difference between properties (eg: x.property) and 
//      methods that have no parameters (eg: x.method()).
//  [4] At long last, we take the appropriate key-value pairs 
//      and place them in an associative array. The key is the name of
//      the field, and the value is the content of that field. This
//      step allows the HTML to work with the JS associative array
//      since it cannot work directly with the record set object.
//**********
// A little bit about the naming conventions for fields:
//   strXXXX     <=> A string field.
//   numXXXX     <=> A numeric field.
//   bluXXXX     <=> A MEMO field (as MS Access calls it).
//   disXXXX     <=> A display only field whose value comes from some
//                   other page.
//   calXXXX     <=> A display only field whose value is derived by
//                   arithmetic operations on other fields on the
//                   same page.
//**********
// Contents (with Examples):
//--
// Utility functions
//   quotedString = AddSQLQuotes(string);
//   string = FormatArrayAsString(array);  
//   boolean = Ignoreable(fieldObjectFromPage);
//--
// Page loading and saving functions
//  arrayOfPlans = FindPlans(void);
//  arrayOfFieldsAndValues = LoadPage(PlanName, PageName);
//  SavePage(PageName, PlanName, arrayOfFieldsAndValues);
//  SavePlan(void);
//  arrayOfFieldsAndValues = ScrapePage(PageName, PlanName);
//--
// Global variable manipulators.
//  void StoreGlobalPlanName(SelectedPlanName);
//  SelectedPlanName = GetGlobalPlanName(void);
//**********
// This function applies SQL quotes when need it. Its use does nothing
// except increase readability.
//**********
// A few words about DebugLevel
//**
//  DebugLevel of -1 means that the Debug messages are "off."
//  A DebugLevel greater than zero causes the flow trace to execute.
//  A DebugLevel that is divisible by 2 shows the page scrape.
//  A DebugLevel that is divisible by 3 shows the SQL.
//  A DebugLevel that is divisible by 5 shows items of special interest.
//**
//  So ... setting DebugLevel to 30
//**********
var SHOWSCRAPE  = 2;
var SHOWSQL     = 3;
var SHOWSPECIAL = 5;

function AddSQLQuotes(myString)
{
  var c;
  var myEscapedString = "";
  var i;
  for (i=0; i<myString.length; i++)
  {
    c = myString.charAt(i);
    if (c!="'")
      myEscapedString += c;
    else 
      myEscapedString += "\xB4";
  }
  myEscapedString = "'" + myEscapedString + "'";
  return myEscapedString;
}

//**********
// This function is called from the main page only. It runs down
// the PlanMaster to find all the plans currently in the database.
// In addition, there is always a value in the returned array
// called "New", which allows the user to create a new plan.
//**********
function FindPlans()
{
  if (DebugLevel > 0) alert ("Entering FindPlans()");
  var Key = "";
  var Value = "";
  var DBarray = new Array();
  DBarray['New'] = 'New';

  var DBconnection = new ActiveXObject("ADODB.Connection");
  var RecordSet = new ActiveXObject("ADODB.Recordset");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");
  
  var SQLStatement = "SELECT PlanName FROM PlanMaster";
  if (DebugLevel % SHOWSQL == 0) alert ("Executing " + SQLStatement);
  RecordSet = DBconnection.Execute(SQLStatement);
  if (RecordSet.EOF && RecordSet.BOF) 
  {
    if (DebugLevel % SHOWSCRAPE == 0) 
      alert ("DBarray[] = " + FormatArrayAsString(DBarray));
    return DBarray;
  }

  RecordSet.MoveFirst();
  while (!RecordSet.EOF)
  {
    //**********
    // We set both members of the associative array to the same
    // value so that they will work properly in the selector box
    // in which they are displayed.
    //**********
    Key = RecordSet.fields("planName").value;
    Value = Key;
    //**********
    // Transfer the results to the JS array, and chug along.
    //**********
    DBarray[Key] = Value.toString();
    RecordSet.MoveNext();
  }
  if (DebugLevel & SHOWSCRAPE == 0)
    alert("Exiting FindPlans() with DBarray[] = " + FormatArrayAsString(DBarray));
  return DBarray;
}

//**********
// Take an array, and print it out in the format
//  {(a,b) (c,d) ... }
//**********
function FormatArrayAsString(myArray)
{
  var Key;
  var Value;
  var myString = "{";
 
  for (Key in myArray)
  {
    Value = myArray[Key];
    myString = myString + " (" + AddSQLQuotes(Key) + " , " + AddSQLQuotes(Value) + ") ";
  } 
  myString = myString + "}";
  return myString;
}


//**********
// We are only interested in taking a look at fields that are of
// type "text" or "textarea". There is also no need to look at the
// calculated values, or the buttons, or the things being displayed.
//**********

function Required(myField)
{
  //**********
  // If the field has the right sort of name, we definitely
  // want to store it.
  //**********
  if (myField.name.substr(0,3) == "num" || 
      myField.name.substr(0,3) == "str" || 
      myField.name.substr(0,3) == "blu")
    return true;
  else
    return false;
}

function Ignoreable(myField)
{
  return !Required(myField);
}

//**********
// This function is called to retrieve data from the database to load
// the form. This function uses the PlanName and the PageName to find
// the appropriate objects to populate the form.
//**********
// "Smart data; stupid code."
//                       -- James Coplien
//**********
function LoadPage (PlanName, PageName)
{
  if (DebugLevel > 0) alert("Entering LoadPage(" + PlanName + "," + PageName + ")");
  var DBconnection = new ActiveXObject("ADODB.Connection");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");
  //**********
  // This is the array into which the found items will be placed. It is
  // returned by this function.
  //**********
  var DBarray = new Array();
  var Key;
  var Value;

  var SQLStatements = new Array();
  SQLStatements[0] = 
    "SELECT fieldName, datum FROM BlurbMaster WHERE planName = " + 
    AddSQLQuotes(PlanName); 
  SQLStatements[1] = 
    "SELECT fieldName, datum FROM StringMaster WHERE planName = " + 
    AddSQLQuotes(PlanName); 
  SQLStatements[2] = 
    "SELECT fieldName, datum FROM NumberMaster WHERE planName = " + 
    AddSQLQuotes(PlanName); 
    
  var RecordSet = new ActiveXObject("ADODB.Recordset");
  
  //**********
  // Use each statement in the Array to get everything that matches
  // out of the appropriate table.
  //**********
  for (var i=0; i<3; i++)
  {
    if (DebugLevel % SHOWSQL == 0) 
      alert ("Loading page via SQL: " + SQLStatements[i]);
    RecordSet = DBconnection.Execute(SQLStatements[i]);
    //*********
    // See if we got anything at all. This is most likely to happen
    // when one is entering a business plan for the first time.
    //*********
    if (RecordSet.BOF && RecordSet.EOF)
    {
      continue;
    }
    RecordSet.MoveFirst();
    while (!RecordSet.EOF)
    {
      //**********
      // The hardcoding of the two data items that make up the key-value
      // pairs is a trade off. The drawback is that ... well ... they
      // are hardcoded. The gain is that the calling HTML remains 
      // 100% naive about the underlying data storage.
      //**********
      Key = RecordSet.fields("fieldName").value;
      Value = RecordSet.fields("datum").value;
      //**********
      // Transfer the results to the JS array, and chug along.
      //**********
      DBarray[Key] = Value.toString();
      RecordSet.MoveNext();
    }
    RecordSet.close();
  }
  if (DebugLevel % SHOWSCRAPE == 0)
    alert("Exiting LoadPage() with " + FormatArrayAsString(DBarray));
  return DBarray;
  
}

//**********
// FieldIDsByName(void)
//**********
function FieldIDsByName()
{
  var iNumFields = document.forms[0].length;
  var i;
  var fieldArray = Array();

  for (i=0; i<iNumFields; i++)
  {
    Key = document.forms[0].elements[i].name;
    Value = i;
    fieldArray[Key] = Value;
  }  
  if (DebugLevel % SHOWSCRAPE == 0) 
    alert("FieldIDsByName() with fieldArray = " + FormatArrayAsString(fieldArray));
  return fieldArray;
}

//**********
// GetMyID(this)
//**********
function GetMyID(myField)
{
  var fieldArray = FieldIDsByName();
  return fieldArray[myField.name];
}


//**********
// productPrevious(this)
//**********
function productPrevious(myField)
{
  var iMyID = GetMyID(myField);
  myField.value = OrZero(document.forms[0].elements[iMyID-2].value) * 
                  OrZero(document.forms[0].elements[iMyID-1].value);
}

//**********
// sumPrevious(this, numPrevious)
//**********
function sumPrevious(myField, numPrevious)
{
  var iMyID = GetMyID(myField);
  var myValue = 0;
  var i;
  for (i=1; i<=numPrevious; i++)
    myValue += OrZero(Number(document.forms[0].elements[iMyID-i].value));
  myField.value = myValue;
}


//**********
// diffPrevious(this)
//**********
function diffPrevious(myField, offset1, offset2)
{
  if (offset1 == undefined)
  {
    offset1 = -1;
    offset2 = -2;
  }
  var iMyID = GetMyID(myField);
  myField.value = document.forms[0].elements[iMyID+offset2].value - 
                  document.forms[0].elements[iMyID+offset1].value;
}



//**********

// SavePage() is quite a bit longer than the LoadPage() routine. 
// From a conceptual approach, it is just as simple. The decision to 
// DELETE the existing records before the INSERT statements is 
// simply that it is difficult to programmatically generate the
// appropriate UPDATE statements.
//**********
// In this particular application, it is important to realize that the
// usual constraints on database operations do not apply: Namely that 
// this is by definition a single user database. Lock? Update in place?
// Why worry? Be happy!
//**********
function SavePage (PlanName, PageName, DBarray)
{
  if (DebugLevel > 0) 
    alert("Entering SavePage("+PlanName+","+PageName+") = " + FormatArrayAsString(DBarray));
  var DBconnection = new ActiveXObject("ADODB.Connection");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");

  var SQLDeletes = new Array();
  var SQLInserts = new Array();
  var i = 0;
  var Key = "";
  var Value = "";
  var SQLString = "";
  var Tail = "";
  var DeleteTail = "";


  SQLDeletes[0] = 
    "DELETE FROM BlurbMaster WHERE planName = " + AddSQLQuotes(PlanName) + 
    " AND fieldName = " ;

  SQLDeletes[1] = 
    "DELETE FROM StringMaster WHERE planName = " + AddSQLQuotes(PlanName) +
    " AND fieldName = " ;

  SQLDeletes[2] = 
    "DELETE FROM NumberMaster WHERE planName = " + AddSQLQuotes(PlanName) +
    " AND fieldName = " ;

  SQLInserts[0] = 
    "INSERT INTO BlurbMaster (planName, pageName, fieldName, datum) VALUES ( " +
    AddSQLQuotes(PlanName) + ", " + AddSQLQuotes(PageName) + ", ";
    
  SQLInserts[1] = 
    "INSERT INTO StringMaster (planName, pageName, fieldName, datum) VALUES ( " +
    AddSQLQuotes(PlanName) + ", " + AddSQLQuotes(PageName) + ", ";

  SQLInserts[2] = 
    "INSERT INTO NumberMaster (planName, pageName, fieldName, datum) VALUES ( " +
    AddSQLQuotes(PlanName) + ", " + AddSQLQuotes(PageName) + ", ";

  var RecordSet = new ActiveXObject("ADODB.Recordset");
  

  //**********
  // Let's DELETE.
  //**********
  for (Key in DBarray)
  {
    Stub = Key.substr(0,3);
    DeleteTail = AddSQLQuotes(Key);
    if (Stub == "blu")
    {
      SQLStatement = SQLDeletes[0] + DeleteTail;
    }
    else if (Stub == "str")
    {
      SQLStatement = SQLDeletes[1] + DeleteTail;
    }
    else if (Stub == "num")
    {
      SQLStatement = SQLDeletes[2] + DeleteTail;
    }
    else
    {
       // do nothing.
    }
    RecordSet = DBconnection.Execute(SQLStatement);
  }


  //**********
  // And now for something slightly more complex, let's INSERT. The one
  // thing about this code that might be considered a "hack" is that
  // there is a tiny bit of information embedded in the names of the 
  // data.
  //**********
  for (Key in DBarray)
  {
    Stub = Key.substr(0,3);
    Value = DBarray[Key];

    if (Stub == "blu")
    {
      Tail = AddSQLQuotes(Key) + ", " + AddSQLQuotes(Value) + ")";
      SQLStatement = SQLInserts[0] + Tail;
    }
    else if (Stub == "str")
    {
      Tail = AddSQLQuotes(Key) + ", " + AddSQLQuotes(Value) + ")";
      SQLStatement = SQLInserts[1] + Tail;
    }
    else if (Stub == "num")
    {
      Tail = AddSQLQuotes(Key) + ", " + Value + ")";
      SQLStatement = SQLInserts[2] + Tail;
    }
    else
    {
      //**********
      // Bad field name, but what can we do??? At least we will not
      // write it to a place it does not belong.
      //**********
      continue;
    }
    if (DebugLevel % SHOWSQL == 0) 
      alert ("SavePage() storing new info via SQL: " + SQLStatement);
    DBconnection.Execute(SQLStatement);
  }
}

//*********
// SavePlan() is only called from the first page where the operative
// plan is selected. This function sets the GlobalPlanName variable,
// which is used throughout the application to determine what plan
// is currently active.
//*********
function SavePlan(PlanName)
{
  if (DebugLevel > 0)
    alert("Entering SavePlan() with " + PlanName);
  //********
  // Set the GlobalPlanName before we do anything else. Many other 
  // operations require this to be set. We may need to set it again ..
  // but still, let's set it now.
  //********  
  StoreGlobalPlanName(PlanName);
  PageName = "StartPage";
  var InvalidName = (PlanName.length < 1 ||
                     PlanName[0] == " " ||
                     PlanName[0] == "-" ); 
  if (InvalidName)
  {
    alert("That's not a valid plan name. You must either enter a new name, or select one from the list of previously entered plan names.");
    return false;
  }

  var DBconnection = new ActiveXObject("ADODB.Connection");
  var RecordSet = new ActiveXObject("ADODB.Recordset");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");
  
  var SQLStatement1 = "SELECT * FROM PlanMaster WHERE planName = " + 
                     AddSQLQuotes(GlobalPlanName);
  var  SQLStatement2 = "INSERT INTO PlanMaster (planName, " + 
         "creationDate, startYear, startMonth, version) VALUES (" +
         AddSQLQuotes(GlobalPlanName) + ", " +
         "NOW(), " +
         "YEAR(NOW()), " +
         "MONTH(NOW()), " +
         "2005.1012)";
  if (DebugLevel % SHOWSQL == 0)
    alert("Executing SQL: " + SQLStatement1);
  RecordSet = DBconnection.Execute(SQLStatement1);

  //**********
  // If we didn't get anything, then we need to write the data to
  // the database because this is new information.
  //**********
  if (RecordSet.BOF && RecordSet.EOF)
  {
    if (DebugLevel % SHOWSQL == 0)
      alert("Executing SQL: " + SQLStatement2);
    DBconnection.Execute(SQLStatement2);
  }
  return true;
}

//*********
// This function takes the fields off the page, and places them
// in a single associative array. No use is made of the arguments;
// they are just passed along without modification to the function
// SavePage().
//*********
function ScrapePage(PlanName, PageName)
{
  if (DebugLevel > 0)
    alert("Entering ScrapePage() with " + PlanName + " and " + PageName);
  var DBarray = new Array();
  var Key;
  var Value;

  var TheForm = document.forms[0];
  if (TheForm == undefined) 
    return (DBarray);

  var NumFields = TheForm.length;
  var myField = "";
  var myValue = "";
  for ( i=0; i<NumFields; i++ ) 
  {
     if (Ignoreable(TheForm[i])) 
     {
       continue;
     }
     myField = TheForm[i].name;
     myValue = TheForm[i].value;
     var Stub = myField.substr(0,3);
     if (myValue == undefined || myValue == "")
     {
       if (Stub == "num")
         myValue = 0;
       else 
         myValue = " ";
     }
     DBarray[myField] = myValue.toString();
  }  
  if (DebugLevel > 0)
    alert("ScrapePage() found " + FormatArrayAsString(DBarray));
  SavePage(PlanName, PageName, DBarray);
  return DBarray;
}

//**********
// JavaScript does not really have "global" scope variables in
// the sense that compiled languages have extern scope. Consequently,
// for the selected plan to persist between pages, it must be stored
// and retrieved from a well known location.
//--
// Note that the Store.. function also sets the "global" variable
// named "GlobalPlanName" so that it is valid as soon as the function
// is called.
//--
// These functions encapsulate this rather odd quirk so that future
// programmers can simply change these routines rather than look for
// inline code that performs the work.
//***********

function StoreGlobalPlanName(PlanName)
{
  if (DebugLevel > 0)
    alert ("StoreGlobalPlanName(" + PlanName + ")");
  GlobalPlanName = PlanName;
  
  var DBconnection = new ActiveXObject("ADODB.Connection");
  var RecordSet = new ActiveXObject("ADODB.Recordset");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");

  var SQLStatement1 = "DELETE FROM StringMaster WHERE planName = 'CCWASelectedPlan'";
  var SQLStatement2 = "INSERT INTO StringMaster (planName, datum) VALUES " +
        "('CCWASelectedPlan', " + AddSQLQuotes(PlanName) + ")";

  DBconnection.Execute(SQLStatement1);
  DBconnection.Execute(SQLStatement2);
}

function GetGlobalPlanName()
{
  
  var ReturnValue = "None";
  var DBconnection = new ActiveXObject("ADODB.Connection");
  var RecordSet = new ActiveXObject("ADODB.Recordset");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");

  SQLStatement = "SELECT datum from StringMaster WHERE planName = 'CCWASelectedPlan'";
  RecordSet = DBconnection.Execute(SQLStatement);
  if (RecordSet.BOF && RecordSet.EOF) 
  {
    alert ("Unable to locate the selected plan's name.");
    return ReturnValue;
  }
  RecordSet.MoveFirst();
  ReturnValue = RecordSet.fields("datum").value;
  if (DebugLevel > 0)
    alert ("GetGlobalPlanName() found " + ReturnValue);
  return ReturnValue;
}

function TransferRecordSetToArray(RecordSet)
{
  var DBarray = new Array();
  var Key;
  var Value;

  if (RecordSet.EOF && RecordSet.BOF)
  {
    return DBarray;
  }
  RecordSet.MoveFirst();
  while (!RecordSet.EOF)
  {
    Key = RecordSet.fields("fieldName").value;
    Value = RecordSet.fields("datum").value;
    DBarray[Key] = Value.toString();
    RecordSet.MoveNext();
  }  
  return DBarray;
}

//********
// This little function retrieves the value of a read-only field from
// the DB, using the second argument as a search criterion. The gist
// of this operation is a bit obtuse, so ....
//**
// [1] Use the "Me" argument to find out the name of the field.
//  [a] Strip off the "dis" from the front of the name.
//  [b] Then use the regular procedure for the "str", "blu", "num", etc.
// [2] This function is used when the value comes from some other page,
//  so the second argument gives you the name of the page to use in the
//  SQL statement.
//********
function LoadMe(Me, PageOfOrigin)
{
  if (DebugLevel > 0) 
    alert ("Entering LoadMe("+Me.name+","+PageOfOrigin+")");
  //*********
  // Keeping in mind that this function might be called inside a loop
  // for every field on a page, let's ensure that we only do the lookup
  // if the name of the field starts with "dis" (for "display").
  //*********
  var SQLStatement;
  var myName = Me.name;
  var stub = myName.substr(0,3);
  if (stub != "dis")
  {
    Me.value = "[bad call]";
    return;
  }

  //*********
  // OK, now chop off the "dis" from the front of the name to get the
  // thing we are searching for. The syntax means "start at position #3,
  // and gimme the rest of it."
  //*********
  myName = myName.substr(3);
  stub = myName.substr(0,3);
    
  //**********
  // A little bit of DB gibberish to get the value from the DB.
  //**********
  var DBconnection = new ActiveXObject("ADODB.Connection");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");
  var RecordSet = new ActiveXObject("ADODB.Recordset");

  SQLStatementHead = "SELECT datum FROM "; 
  SQLStatementTail = " WHERE planName = " + AddSQLQuotes(GlobalPlanName) +
//                     " AND pageName = " + AddSQLQuotes(PageOfOrigin) +
                     " AND fieldName = " + AddSQLQuotes(myName);
  
  //**********
  // Now, decide on the table name, and build the correct SQL statement.
  //**********
  if (stub == "blu")
  {
    SQLStatement = SQLStatementHead + "BlurbMaster" + SQLStatementTail;
  }
  else if (stub == "str")
  {
    SQLStatement = SQLStatementHead + "StringMaster" + SQLStatementTail;
  }
  else if (stub == "num")
  {
    SQLStatement = SQLStatementHead + "NumberMaster" + SQLStatementTail;
  } 
  else 
  {
    Me.value = "[bad name]";
    return;
  }  
  
  //**********
  // And execute it .. grabbing the one thing from the result should be
  // easy.
  //**********
  if (DebugLevel % SHOWSQL == 0)
    alert("Executing SQL: " + SQLStatement);
  RecordSet = DBconnection.Execute(SQLStatement);
  if (RecordSet.EOF && RecordSet.BOF)
  {
    Me.value = "[no value]";
    return;
  } 
  RecordSet.MoveFirst();
  Me.value = RecordSet.fields("datum").value;
  return Me.value;
}

//*********
// Delete a plan!
//*********

function DeletePlan(PlanName)
{
  var SQLStatements = new Array();
  SQLStatements[0] = "DELETE FROM StringMaster";
  SQLStatements[1] = "DELETE FROM BlurbMaster";
  SQLStatements[2] = "DELETE FROM NumberMaster";
  SQLStatements[3] = "DELETE FROM PlanMaster";

  var DBconnection = new ActiveXObject("ADODB.Connection");
  DBconnection.Provider = "Microsoft.Jet.OLEDB.4.0;"
  DBconnection.open("Data Source=C:/WINDOWS/entreplan.mdb", "", "");
  var RecordSet = new ActiveXObject("ADODB.Recordset");
  
  for (var i=0; i<4; i++)
    DBconnection.Execute(SQLStatements[i]);
}


//*********
// FocusAll() is a HACK. This function sends the focus message to every
// field on the page that has not been loaded as a part of LoadPage.
//*********
function FocusAll()
{
  if (DebugLevel > 0) alert ("Entering FocusAll()");
  var i;
  var Key;
  var Datum;
  var FieldNames = new Array();
  var FieldNumbers = new Array();
  FieldNames = FieldIDsByName();  

  //**********
  // Swap the Key and Datum in the array
  //**********  
  for (Key in FieldNames)
    FieldNumbers[FieldNames[Key]] = Key;

  //**********
  // Reverse the array
  //**********
  FieldNumbers.reverse();
  //**********
  // Send the onfocus message to each element that is either CALculated
  // or displayed. We have to do this twice so that all the data that is
  // loaded from the DB is on the page before we start doing calculations.
  //**********
  for (Key in FieldNumbers)
  {
    Datum = FieldNumbers[Key];
    if (DebugLevel % SHOWSPECIAL == 0) 
      alert("Sending focus to " + Key);
  }
  for (Key in FieldNumbers)
  {
    Datum = FieldNumbers[Key];
    if (DebugLevel % SHOWSPECIAL == 0) 
      alert("Sending focus to " + Key);
  }
}

function CalcAnnual(monthlyField)
{
  var Amount = 12 * OrZero(monthlyField.value);
  return Amount;
}

function SetYears(YearField, OtherField)
{
  OtherField.value = parseInt(YearField.value) + 1;
}

function FixNumericField(myField)
{
  myField.value = FixNumericValue(myField.value);
}

//*********
// Clean up strings that are construed to contain numbers.
//*********
function FixNumericValue(myValue)
{
  if (isNaN(Number(myValue)))
    return 0;
//  else
//    return myValue.toFixed(2);
}

function productForward(BlurredField)
{
  var fieldNum = GetMyID(BlurredField);
  productPrevious(document.forms[0].elements[fieldNum+1]);  
}

function productForward1(BlurredField)
{
  var fieldNum = GetMyID(BlurredField);
  productPrevious(document.forms[0].elements[fieldNum+2]);
}

//***********
// columnSum()
//   Parameters:
//     myField := the field whose value will take the result.
//     lastFieldOffset := the relative position of the last field
//                    used in the sum.
//     spacing := the n'th field spacing.
//     numFields := how many fields are used in the total.
//**
// This is a very general purpose kind of function. For examples,
// suppose you wanted to total up a years worth of fields in a 
// four column table whose months appear just above you:
//
//   xxxxx  xxxxxx  xxxxxx   xxxxxx
//   yyyyy  yyyyyy  yyyyyy   yyyyyy
//  -------------------------------
//   ttttt  tttttt  tttttt   tttttt
//
// Your formula for the "ttttt" fields might be something like this:
//  columnSum(this, -4, 4, 12);
// I.e., start four fields back, every fourth field, and 12 of them
//  to make the sum.
//
// If you just want to sum the two previous fields ... very common ...
//  columnSum(this, -1, 1, 2);
//
// The final parameter, multiplier, is optional. If it is something
// less than zero, the implication is that you want to do subtraction.
//***********

function DollarsAndCents(myField)
{
  var v = new Number(myField.value);
  myField.value = v.toFixed(2);
}

function ToCurrency(myForm, myStub)
{
  if (myStub == undefined) myStub = "*";
  if (myStub == "") myStub = "*";
  var DoThemAll = (myStub == "*");
  var DoThisField = false;
  var FieldName = "";
  var i=0;
  var StubLength = myStub.length;
  
  for (i=0; i<myForm.length; i++)
  {
    FieldName = myForm.elements[i].name;
    if (DoThemAll)
      DoThisField = true;
    else if (FieldName.substr(0,StubLength) == myStub)
      DoThisField = true;
    else
      DoThisField = false;
    if (DoThisField) DollarsAndCents(myForm.elements[i]);
  }
}

function columnSum(myField, lastFieldOffset, spacing, numFields, multiplier)
{
  if (multiplier == undefined) multiplier = 1;
  if (multiplier < 0) multiplier = -1;
  if (multiplier > 0) multiplier = 1;

  var i = GetMyID(myField);  
  var result = 0;
  var addend = 0;
  var lastFieldNum = i + lastFieldOffset;
  var firstFieldNum = lastFieldNum - spacing*(numFields-1);

/*
    alert("*******************\n" + 
        " lfn, lfo, nf, spacing : " + lastFieldNum + ", " + lastFieldOffset + ", " + numFields + ", " + spacing + "\n" +
        "*******************\n" +
        "Operational Fields:\n" + 
        "target fieldnum  : " + i + "\n" +
        "target fieldname : " + myField.name + "\n" +
        "firstFieldNum    : " + firstFieldNum + "\n" +
        "first FieldName  : " + document.forms[0].elements[firstFieldNum].name + "\n" +
        "lastFieldNum     : " + lastFieldNum + "\n" +
        "last FieldName   : " + document.forms[0].elements[lastFieldNum].name  + "\n" +
        "******************" );
*/
  for (i=0; i<numFields; i++)
  {
    addend = parseFloat(document.forms[0].elements[firstFieldNum + i*spacing].value);
    if (isNaN(addend)) addend = 0;
    result += addend*multiplier;
  }
  myField.value=result;
}

function arbitrarySum(targetField)
{
  var i = 0;
  var result = 0;
  var addend = 0;

  for (i=1; i<arguments.length; i++)
  {
    addend = parseFloat(arguments[i].value);
    result += addend;
  }
  targetField.value = result;
}

function OrZero(candidate)
{
  return isNaN(candidate) ? 0 : candidate;
}

function CopyProductNames(thisForm)
{
  thisForm.strProd1NameDup.value = thisForm.strProd1Name.value;
  thisForm.strProd2NameDup.value = thisForm.strProd2Name.value;
  thisForm.strProd3NameDup.value = thisForm.strProd3Name.value;
  thisForm.strProd4NameDup.value = thisForm.strProd4Name.value;
  thisForm.strProd5NameDup.value = thisForm.strProd5Name.value;
  thisForm.strProd6NameDup.value = thisForm.strProd6Name.value;
  thisForm.strProd7NameDup.value = thisForm.strProd7Name.value;
}

function MM_popupMsg(msg) { //v1.0
  alert(msg);
}

