//  scrltabl.js
//  Written:  2009-03-21 by James Alarie <jalarie@umich.edu>
//    http://spruce.flint.umich.edu/~jalarie/
//
//  Set up table data similar to example 1 or 2 below and then create the 
//  table with a call like this:
//
//    ScrollTableCreate('Table1');
//
//  or create your table in standard HTML, wrap it (see example 3 below) in a 
//  table with a td having an id, and then make it scrollable with a call like 
//  this:
//
//    ScrollTable('ID',cols,rows[,hjump,vjump]);    // use your own table id
//
//  Example 1:  one-dimentional:  requires "separator" parameter.
//    Table2=new Array(
//      ' ,Column 1 Header,Col 2,C3,C4,C5,C6,C7,C8,Column 9 Header',
//      'Row 1 Label,Data 1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9',
//      'Row 2 Label,Data 2.1,2.2,2.3,2.4,2.5,2.6,2.7,2.8,2.9',
//      'Row 3 Label,Data 3.1,3.2,3.3,3.4,3.5,3.6,3.7,3.8,3.9',
//      'Row 4 Label,Data 4.1,4.2,4.3,4.4,4.5,4.6,4.7,4.8,4.9',
//      'Row 5 Label,Data 5.1,5.2,5.3,5.4,5.5,5.6,5.7,5.8,5.9'
//    );
//    Table2['separator']=",";                      // how to split row data
//    Table2['ParName']='ParValue';                 // parameters list below
//
//  Example 2:  two-dimentional:
//    Table1=new Array();
//    Table1['ParName']='ParValue';                 // parameters list below
//    Table1[0]=new Array('','Column 1 Header','Col 2','C3','C4','C5','C6',
//      'C7','C8','Column 9 Header');
//    Table1[1]=new Array('Row 1 Label','Data 1.1', // row 1 data
//      '1.2','1.3','1.4','1.5','1.6','1.7','1.8','1.9');
//    Table1[2]=new Array('Row 2 Label','Data 2.1', // ...row 2
//      '2.2','2.3','2.4','2.5','2.6','2.7','2.8','2.9');
//    Table1[3]=new Array('Row 3 Label','3.1','3.2','3.3','3.4','3.5','3.6',
//      '3.7','3.8','3.9');
//    Table1[4]=new Array('Row 4 Label','4.1','4.2','4.3','4.4','4.5','4.6',
//      '4.7','4.8','4.9');
//    Table1[5]=new Array('Row 5 Label','5.1','5.2','5.3','5.4','5.5','5.6',
//      '5.7','5.8','5.9');
//
//  Example 3:  pre-existing table:
//    <table border="0" cellspacing="0" cellpadding="0" summary="scroll">
//    <tbody><tr><td id="TableID">                  // new surrounding table
//      <table>...</table>                          // your pre-existing table
//    </td></tr></tbody></table>                    // end of new table
//
//  Parameters:  All are optional except "separator" for 1D tables.
//    class           example: zebra1        default: -none-
//      The table will use this CSS class.
//    scrollclass     example: null          default: -none-
//      The left/right/up/down/reset bar will be of this CSS class.
//    scrollcolor     example: pink          default: silver
//      The scroll/left/right/up/down/reset bar will be of this color.  It is 
//      not compatable with the "scrollclass" parameter.
//    background      example: champagn.jpg  default: -none-
//      Use this background for the table.
//    bgcolor         example: aqua          default: -none-
//      This will be the general table color.  It is not compatable with 
//      the "background" parameter.
//    header          example: Table Header  default: -none-
//      This line will appear across the top of the table.
//    summary         example: Congressional pork by State and amount.
//      People using screen readers will get this information.
//    caption         example: Government Pork Over the Years
//      This will appear as a caption for the table.
//    cols            example: 5
//      The number of data columns to display at once.  The default is the 
//      full width of the table and no left/right scrolling can happen.
//    rows            example: 2
//      The number of data rows to display at once.  The default is the full 
//      height of the table and no up/down scrolling can happen.
//    hjump           example: 2             default: 1
//      Clicking on "left" or "right" will cause the table to scroll
//      horizontally by this number of columns.
//    vjump           example: 3             default: 1
//      When the user clicks on "up" or "down" the table will scroll 
//      vertically by this number of rows.
//    nulls           example: n/a           default: - -
//      Any table values which are empty will display with this value.
//    separator       example: ,             default: -none-
//      This character will be used to indicate that this is a 1-dimensional 
//      table and to split the row data.
//
//  Functions:
//    ScrollTable(ID,cols,rows[,hjump,vjump])
//      You may call this function with the ID of an existing table to make 
//      it scrollable.  Be sure to tell it how many data columns (cols) and 
//      data rows (rows) to display at once.  You may also include hjump 
//      (horizontal jump) and vjump (vertical jump) parameters to tell it how 
//      much to jump when the "left", "right", "up", and "down" items are 
//      clicked.
//    ScrollTableCreate(Which)
//      Call this function with the name of an array to create the table.
//    ScrollTableMove(Which,How)
//      This function is totally internal to this package and would not be 
//      called from the main web page.  Its parameters tell which table to 
//      scroll and how to scroll it.  If you try to scroll beyond the edges 
//      of the table, it will say so.  Select the "scroll table" item and it 
//      will allow you to change the hjump or vjump parameters for the current 
//      table.

        function ScrollTable(TableID,TCols,TRows,HJump,VJump) {
          var TArea, Out, Target, Head, HeadL, THead, TCaption, RowNum, RowData;
          var TName, TTag, ColData, ColDatas, Work, WorkL, ix1, ix2, ix3, ix4;
          if (!TCols) { TCols=1; }
          if (!TRows) { TRows=1; }
          TArea=TableID;
          TName=TArea+'Data';
          Target=document.getElementById(TArea);
          Work=Target.innerHTML;
          Work=Work.replace(/\r/g,'\n');            // returns to new-lines
          for (ix1=0; ix1<40; ix1++) {
            Work=Work.replace(/\n /g, '\n');        // drop leading blanks
            Work=Work.replace(/ \n/g, '\n');        // ...trailing
          }
          Work=Work.replace(/\n/g, '');             // drop new-lines
          WorkL=Work.toLowerCase();
          
          ix1=WorkL.indexOf('<table');              // begin table tag
          ix2=WorkL.indexOf('>');                   // ...end
          TTag=Work.substring(ix1,ix2+1);
          Work=Work.substring(ix2+1);
          WorkL=WorkL.substring(ix2+1);
          if ((ix1=WorkL.indexOf('<caption')) > -1) {
            ix1=WorkL.indexOf('>');                 // end of caption tag
            ix2=WorkL.indexOf('<\/caption>',ix1);   // end-caption tag
            TCaption=Work.substring(ix1+1,ix2);
            TCaption=TCaption.replace(/\//g,'\\/');
            TCaption=TCaption.replace(/\"/g,'\\"');
            Work=Work.substring(ix2+10);
            WorkL=WorkL.substring(ix2+10);
          }
          
          RowNum=0;
          Out ='';
          Out+=TName+'=new Array();';
          ColDatas='';
          if ((ix1=WorkL.indexOf('<\/thead>')) > -1) {
            Head=Work.substring(7,ix1);             // the <thead> area
            HeadL=WorkL.substring(7,ix1);
            Work=Work.substring(ix1+8);             // ...everything else
            WorkL=WorkL.substring(ix1+8);
            ix2=HeadL.indexOf('<tr');               // first row begins here
            Head=Head.substring(ix2);
            HeadL=HeadL.substring(ix2);
            ix3=HeadL.indexOf('<tr',1);             // second row?
            if (ix3 > -1) {
              ix2=HeadL.indexOf('<',1);             // start of tag after tr
              ix2=HeadL.indexOf('>',ix2+1);         // ...end of tag
              ix4=HeadL.indexOf('<',ix2+1);         // start next tag
              THead=Head.substring(ix2+1,ix4);      // header
              Head=Head.substring(ix3);
              HeadL=HeadL.substring(ix3);
            }
            Out+=TName+'['+RowNum+']=new Array(';
            while(Head.length > 0) {
              ix2=HeadL.indexOf('<',1);             // start of th or td tag
              ix2=HeadL.indexOf('>',ix2+1);         // ...end of tag
              ix4=HeadL.indexOf('<',ix2+1);         // start next tag
              if (ix4 > -1) {
                ColData=Head.substring(ix2+1,ix4);  // a column header
                Head=Head.substring(ix4+1);
                HeadL=HeadL.substring(ix4+1);
                if (ColDatas == '') {
                  ColDatas='"'+ColData+' "';
                } else {
                  ColDatas+=',"'+ColData+' "';
                }
              } else {
                Head='';
                HeadL='';
              }
            }
            Out+=ColDatas+');';
            RowNum=1;
          } // end of head section
          
          while(Work.length > 0) {        
            ix1=WorkL.indexOf('<tr')                // another row
            ix1=WorkL.indexOf('>',ix1+1);           // ...end of tag
            ix2=WorkL.indexOf('</tr>',ix1);         // end of row
            RowData=Work.substring(ix1+1,ix2);
            Work=Work.substring(ix2+5);
            WorkL=WorkL.substring(ix2+5);
            ColDatas='';
            Out+=TName+'['+RowNum+']=new Array(';
            while (RowData.length > 0) {
              ix2=RowData.indexOf('<',0);           // start of th or td tag
              ix2=RowData.indexOf('>',ix2+1);       // ...end of tag
              ix4=RowData.indexOf('<',ix2+1);       // start next tag
              if (ix4 > -1) {
                ColData=RowData.substring(ix2+1,ix4);
                RowData=RowData.substring(ix4+1);
                if (ColDatas == '') {
                  ColDatas='"'+ColData+ ' "';
                } else {
                  ColDatas+=',"'+ColData+' "';
                }
              } else {
                RowData='';
              }
            }
            Out+=ColDatas+');';
            RowNum++;
            if (WorkL.indexOf('<tr') == -1) {
              Work='';
            }
          }
          
          Out+=TName+'["TableID"]="'+TArea+'";';
          Out+=TName+'["cols"]="'+TCols+'";';
          Out+=TName+'["rows"]="'+TRows+'";';
          if (HJump)    { Out+=TName+'["hjump"]="'+HJump+'";'; }
          if (VJump)    { Out+=TName+'["vjump"]="'+VJump+'";'; }
          if (THead)    { Out+=TName+'["header"]="'+THead+'";'; }
          if (TCaption) { Out+=TName+'["caption"]="'+TCaption+'";'; }
          eval(Out);
          
          ScrollTableCreate(TName);
          return true;
        } // ScrollTable
        
        function ScrollTableCreate(Which) {         // initial table creation
          var TName, TArea, TCols, TRows, THead, TClass, TBackground, BGColor;
          var ScrollColor, ScrollClass, wk1, Separator, RowData, RowItems;
          var Target, JSTable, TableID, TCaption, TSummary, TWidth, THeight;
          var Out, Work, WorkL, ixc, ixr;
          TName=Which;
          JSTable=eval(TName);
          TableID=JSTable["TableID"];
          THead=JSTable["header"];
          TCols=JSTable["cols"];
          TRows=JSTable["rows"];
          TCaption=JSTable["caption"];
          TSummary=JSTable["summary"];
          TClass=JSTable["class"];
          TBackground=JSTable["background"];
          BGColor=JSTable["bgcolor"];
          ScrollColor=JSTable["scrollcolor"];
          ScrollClass=JSTable["scrollclass"];
          Separator=JSTable["separator"];
          if (Separator) {
            RowData=JSTable[0];
            eval('RowItems=RowData.split(/\\'+Separator+'/);');
            TWidth=RowItems.length;
          } else {
            TWidth =JSTable[0].length;
          }
          THeight=JSTable.length;
          JSTable["OffsetC"]=0;
          JSTable["OffsetR"]=0;
          if (!TCols) { TCols=TWidth-1; }
          if (!TRows) { TRows=THeight-1; }
          if (!ScrollColor) { ScrollColor='silver'; }
          
          Out ='';
          if (TableID) {
            Target=document.getElementById(TableID);
            Work=Target.innerHTML;
            Work=Work.replace(/\r/g,'\n');          // returns to new-lines
            for (ix1=0; ix1<40; ix1++) {
              Work=Work.replace(/\n /g, '\n');      // drop leading blanks
              Work=Work.replace(/ \n/g, '\n');      // ...trailing
            }
            Work=Work.replace(/\n/g, '');           // drop new-lines
            WorkL=Work.toLowerCase();
            ix1=WorkL.indexOf('<table');            // start of table tag
            ix2=WorkL.indexOf('>',ix1);             // ...end
            Out=Work.substring(ix1,ix2+1);
          } else {
            TableID=TName+'ID';
            Out+='<div id="'+TableID+'">\n';
            Out+='<\/div>\n';
              document.write(Out);
            Out ='';
            Out+='<table border="1" cellspacing="2" cellpadding="2"';
            if (TClass) {
              Out+=' class="'+TClass+'"';
            }
            if (TBackground) {
              Out+=' style="background-image: url('+TBackground+');"';
            }
            if (BGColor) {
              Out+=' bgcolor="'+BGColor+'"';
            }
            if (TSummary) {
              Out+=' summary="'+TSummary+'"';
            }
            Out+='>';
          }
          
// table head section
          if (TCaption) {
            Out+='<caption>'+TCaption+'<\/caption>';
          }
          Out+='  <thead>\n';
          wk1=TCols+1;
          if (THead) {                              // a header line exists
            Out+='    <tr>\n';
            Out+='      <th colspan="'+wk1+'">\n';
            Out+=THead;
            Out+='      <\/th>\n';
            Out+='    <\/tr>\n';
          }
          Out+='    <tr>\n';                        // column labels
          Out+='      <th>'
          Out+='        <span id="'+TName+'R0C0">';
          if (Separator) {                          // 1-dimentional table
            RowData=JSTable[0];
            RowItems=RowData.split(Separator);
            Out+=RowItems[0];
          } else {                                  // 2D table
            Out+=JSTable[0][0];
          }
          Out+='&nbsp;<\/span>';
          Out+='      <\/th>\n';
          for (ixc=1; ixc<=TCols; ixc++) {
            Out+='      <th>';
            Out+='        <span id="'+TName+'R0C'+ixc+'">';
            if (Separator) {
              Out+=RowItems[ixc];
            } else {
              Out+=JSTable[0][ixc];
            }
            Out+='&nbsp;<\/span>';
            Out+='      <\/th>\n';
          }
          Out+='    <\/tr>\n';
          Out+='  <\/thead>\n';
          
// table body section
          Out+='  <tbody>\n';
          for (ixr=1; ixr<=TRows; ixr++) {          // down all rows
            Out+='    <tr>\n';
            Out+='      <th style="text-align: left;">';
            Out+='        <span id="'+TName+'R'+ixr+'C0">';
            if (Separator) {
              RowData=JSTable[ixr];
              RowItems=RowData.split(Separator);
              Out+=RowItems[0];
            } else {
              Out+=JSTable[ixr][0];
            }
            Out+='&nbsp;<\/span>';
            Out+='      <\/th>\n';
            for (ixc=1; ixc<=TCols; ixc++) {        // across all columns
              Out+='      <td>';
              Out+='        <span id="'+TName+'R'+ixr+'C'+ixc+'">';
              if (Separator) {
                Out+=RowItems[ixc];
              } else {
                Out+=JSTable[ixr][ixc];
              }
              Out+='&nbsp;<\/span>';
              Out+='      <\/td>\n';
            }
            Out+='    <\/tr>\n';
          }
          Out+='    <tr>\n';
// the scroll control line
          Out+='      <td style="font-size: smaller;';
          if ((!ScrollClass) || (ScrollClass == '')) {
            Out+=' background-color: '+ScrollColor+';"';
          } else {
            Out+='" class="'+ScrollClass+'"';
          }
          Out+=' colspan="'+wk1+'">\n';
          Out+='        ';
          Out+='<span id="'+TName+'S" onclick="ScrollTableMove(\''+Which+'\',\'scroll\');">scroll table<\/span>\n';
          Out+=':&nbsp; ';
          Out+='<span id="'+TName+'L" onclick="ScrollTableMove(\''+Which+'\',\'left\');">left<\/span>\n';
          Out+='&nbsp;|&nbsp;';
          Out+='<span id="'+TName+'R" onclick="ScrollTableMove(\''+Which+'\',\'right\');">right<\/span>\n';
          Out+='&nbsp;|&nbsp;';
          Out+='<span id="'+TName+'U" onclick="ScrollTableMove(\''+Which+'\',\'up\');">up<\/span>\n';
          Out+='&nbsp;|&nbsp;';
          Out+='<span id="'+TName+'D" onclick="ScrollTableMove(\''+Which+'\',\'down\');">down<\/span>\n';
          Out+='&nbsp;|&nbsp;';
          Out+='<span onclick="ScrollTableMove(\''+Which+'\',\'init\');">reset<\/span>\n';
          Out+='      <\/td>\n';
          Out+='    <\/tr>\n';
          Out+='  <\/tbody>\n';
          Out+='<\/table>\n';
          
          Target=document.getElementById(TableID);
          Target.innerHTML=Out;
          
          Out=navigator.appName.toLowerCase();
          if (Out != 'microsoft internet explorer') {
            Target=TName+'L';   Target=document.getElementById(Target);
            Target.style.color='grey';
            Target=TName+'R';   Target=document.getElementById(Target);
            Target.style.color='black';
            Target=TName+'U';   Target=document.getElementById(Target);
            Target.style.color='grey';
            Target=TName+'D';   Target=document.getElementById(Target);
            Target.style.color='black';
          }
          
          return true;
        } // ScrollTableCreate
        
        function ScrollTableMove(Which,How) {       // table scrolling
          var TName, TCols, TRows, THead, OffsetC, OffsetR, VJump, HJump, Nulls;
          var Out, ixc, ixc2, ixr, ixr2, Target, Value, TWidth, THeight, Hold;
          var Separator, RowData, RowItems, JSTable;
          TName=Which;
          JSTable=eval(TName);
          TCols=JSTable["cols"];
          TRows=JSTable["rows"];
          OffsetC=JSTable["OffsetC"];
          OffsetR=JSTable["OffsetR"];
          Separator=JSTable["separator"];
          VJump=JSTable["vjump"];
          HJump=JSTable["hjump"];
          Nulls=JSTable["nulls"];
          if (Separator) {
            RowData=JSTable[0];
            RowItems=RowData.split(Separator);
            TWidth=RowItems.length;
          } else {
            TWidth=JSTable[0].length;
          }
          THeight=JSTable.length;
          if (!VJump) { VJump=1; }                  // default vertical jump
          if (!HJump) { HJump=1; }                  // ...horizontal jump
          if (!Nulls) { Nulls='- -'; }              // ...null value
          
          if (How == 'init') {                      // back to initial table
            OffsetC=0;
            OffsetR=0;
          }
          if (How == 'scroll') {                    // reset scroll options
            Hold=prompt('Reset the Column Jump value to: ',HJump);
            if (Hold) { 
              HJump=Hold; 
              OffsetC=0;
            }
            Hold=prompt('Reset the Row Jump value to: ',VJump);
            if (Hold) { 
              VJump=Hold; 
              OffsetR=0;
            }
            JSTable["hjump"]=HJump;                 // save new jump values
            JSTable["vjump"]=VJump;
          }
          if (How == 'left') {                      // scroll it left
            if (OffsetC < 1) {
              alert('*** The table is already at the left edge.');
            } else {
              OffsetC-=HJump;
            }
          }
          if (How == 'right') {                     // scroll it right
            if (OffsetC >= (TWidth-TCols-1)) {
              alert('*** The table is already at the right edge.');
            } else {
              OffsetC=OffsetC*1+HJump*1;
            }
          }
          if (How == 'up') {                        // scroll it upward
            if (OffsetR < 1) {
              alert('*** The table is already at the top.');
            } else {
              OffsetR-=VJump;
            }
          }
          if (How == 'down') {                      // scroll downward
            if (OffsetR >= (THeight-TRows-1)) {
              alert('*** The table is already at the bottom.');
            } else {
              OffsetR=OffsetR*1+VJump*1;
            }
          }
          JSTable["OffsetC"]=OffsetC;               // save new offset values
          JSTable["OffsetR"]=OffsetR;
          
          for (ixc=1; ixc<=TCols; ixc++) {          // do the headers
            Out ='';
            Target=document.getElementById(TName+'R0C'+ixc);
            ixc2=ixc*1+OffsetC*1;
            if (Separator) {                        // 1-dimentional table
              RowData=JSTable[0];
              RowItems=RowData.split(Separator);
              Value=RowItems[ixc2];
            } else {                                // 2D table
              Value=JSTable[0][ixc2];
            }
            if (!Value) { Value=Nulls; }
            Out+=Value+'&nbsp;';
            Target.innerHTML=Out;
          }
          for (ixr=1; ixr<=TRows; ixr++) {          // down all rows
            ixr2=ixr*1+OffsetR*1;
            Out ='';
            Target=document.getElementById(TName+'R'+ixr+'C0');
            RowData=JSTable[ixr2];
            if (RowData) {                          // this row exists
              if (Separator) {
                RowItems=RowData.split(Separator);
                Value=RowItems[0];
              } else {
                Value=RowData[0];
              }
            } else {                                // ...no, it doesn't
              Value=Nulls;
            }
            if (!Value) { Value=Nulls; }            // no value in this cell
            Out+=Value+'&nbsp;';
            Target.innerHTML=Out;                   // row label
            for (ixc=1; ixc<=TCols; ixc++) {        // across all columns
              Out ='';
              Target=document.getElementById(TName+'R'+ixr+'C'+ixc);
              ixc2=ixc*1+OffsetC*1;
              if (RowData) {                        // this row exists
                if (Separator) {
                  Value=RowItems[ixc2];
                } else {
                  Value=RowData[ixc2];
                }
              } else {                              // ...no, it doesn't
                Value=Nulls;
              }
              if (!Value) { Value=Nulls; }          // no value in this cell
              Out+=Value+'&nbsp;';
              Target.innerHTML=Out;
            }
          }
          
          Out=navigator.appName.toLowerCase();
          if (Out != 'microsoft internet explorer') {
            Target=TName+'S';   Target=document.getElementById(Target);
            Target.style.color='black';
            Target=TName+'L';   Target=document.getElementById(Target);
            if (OffsetC < 1)                  { Target.style.color='grey'; }
            else                              { Target.style.color='black'; }
            Target=TName+'R';   Target=document.getElementById(Target);
            if (OffsetC >= (TWidth-TCols-1))  { Target.style.color='grey'; }
            else                              { Target.style.color='black'; }
            Target=TName+'U';   Target=document.getElementById(Target);
            if (OffsetR < 1)                  { Target.style.color='grey'; }
            else                              { Target.style.color='black'; }
            Target=TName+'D';   Target=document.getElementById(Target);
            if (OffsetR >= (THeight-TRows-1)) { Target.style.color='grey';}
            else                              { Target.style.color='black'; }
          }
          
          return true;
        } // ScrollTableMove