wsurvey.arrayToHtml.js  (Feb 2022)

A javascript library to create scrollable html tables.

I.    Introduction
II.a:     What is an "associativeArray"
II.b:     What is displayed?
II.    Usage:
II.a.:    Options in  wsurvey.arrayToTable.init
II.a.1:      List of options
II.b:     Scrolling within a table
II.c:     Using colLabels
III.   Other arrayToHtml functions
III.a:    wsurvey.arrayToHtml.clickHandler -- Adding a click handler for each cell
III.b:    wsurvey.arrayToHtml.display Change display of an existing table
IV:    Custom styling
V:     Contact and legal

                ------------------

I. Introduction: What's the point of wsurvey.arrayToHtml.js?

    wsurvey.arrayToHtml.js contains functions that will convert a javascript "associativeArray" into an html "table".
    Actually: into a set of <divs> that use floats to create a table-like format.

    A main feature is the abilty to "internally scroll" rows.
        A header (and perhaps a few "frozen rows") do not move, but you can scroll through  many lines below the header.

     Thus, the header row (with column specific names) is always visible. AND : when you scroll to the right, the header ALSO scrolls.

    IOW: The column headers are always above the correct columns, even when you scroll down or right.

                    ------------------

II.a:  What is an "associativeArray"

   Technically, wsurvey.arrayToHtml works with javascript arrays (with indices of 0,1,...)
   Each row is a simple object with 1 or more index:value pairs.
   These objects usually contain the same indices. But they do NOT have to.

   Values are 2 element arrays:
           [0] : the actual value
           [1] : the value displayed
       For example: [15612.1215,'15.6k']

      As a shortcut you can specify a string or a number. It will be converted into a 2 element array.

   Example:
       let myArray=[];
       myArray[0]={'height':69,'weight':180};
       myArray[1]={'height':61.6,'weight':'unknown'};
       myArray[2]={'height':47,'weight':96};
       myArray[3]={'height':247,'weight':396.12,'variety':'larger'};
       myArray[4]={'height':[1247,'jumbo version'],'weight':1396,'variety':'largest'};
       myArray[5]={'height':[24,'mini version'],'weight':36.41,'variety':'smaller'};

    Notes:
      *  Most of the values are NOT 2 element arrays!
         Basically: integers and strings are converted to [avalue,avalue] -- the "value to display" and the "actual value" are the same.
      Rows 0 to 3 do NOT use 2 element arrays.
      *  rows 3 to 5 has an index ('variety') not in the other rows.
      *  rows 4 and 5 use  a "2 element array" for the 'height' value

I.b:  What is displayed?

     Each cell of the "table" contains a <span>.

         * The  html of the span is the "value displayed" .
            For numbers: can be modified using the ndec and addComma options (see below)
            For strings: the value is displayed. It can contain HTML
            For 2 element arrays: the "value displayed" ([1]). It can contain HTML.
         * The "actual value" is displayed in a title (visible on a mouseover).
             If the actual value is not a number:
                 * html will be stripped.
                 * only the first 160 characters are displayed
                 * new lines will be displayed.
         * A data-arraytohtml attribute contains the actual value.
             This is meant for use by wsurvey.arrayToHtml.onclick.
             Thus: the value of data-arrayToHtml is base64 encoded.
          * A data-isValSpan=1 if this index is defined in this row, 0 if not
            (or one can check for the existence of a  data-arraytohtml attribute)

                ------------------

II. Usage

   wsurvey.arrayToHtml requires jQuery, and the wsurvey.utils1.js.
   For example:

      <script type="text/javascript" src="jquery-3.6.0.min"></script>
      <script type="text/javascript" src="wsurvey.utils1.js">  </script>
      <script type="text/javascript" src="wsurvey.arrayToHtml.js"></script>


   Assuming a variable, call it 'vv', that is an "associativeArray", wsurvey.arrayToHtml() is called as
      stuff=wsurvey.arrayToHtml(vv,obts)

   where:

      vv : the associative arrays (each row can have different indices)
      opts: an object with options. Defaults are used if opts is not specified

      stuff: is a two element array:
               vv[0] : the html "table" -- it can be written to the browser. For example, using:  $('#showTableHere').html(vv[0]) ;
               vv[1] : the id of this table. Unique, randomly generated ids are assigned (though you can specify an id the opts).

   For example:
          stuff= ws_arrayToHtml(vv,aopts) ;
          tableString=stuff[0];
          tableId=stuff[1];
          $('#myTable').html(tableString);

    The tableID can be used to assign a click handlers for cells . For example:
         ws_arrayToHtml(sayCellInfoFunction,tableid)    -- where sayCellInfoFunction is a custom function
     where sayCellInfoFunction is a custom function
     See below for details on wsurvey.arrayToHtml.onclick.

   Two other functions,  wsurvey.arrayToTable.clickHandler and  wsurvey.arrayToTable.display, can be used to
   change an arrayToHtml table after is has been created. These are discussed later in this document

                ------------------

II.a.: Options in  wsurvey.arrayToTable.init

   If specified, the 2nd argument (opts) should be an object with the following elements.
       You can specify as many as you wish: defaults are used for unspecified options
       Note that opts=0, or opts]{}, is the same as not specifying opts (defaults are used for all options).

                       ------------------

II.a.1:   List of options

         addComma: if 1, and a value is not a 2 element array: then add commas if the value is a number (do nothing if it is not a number)

         addWidth : ems  to add when computing row width. If not specified, 5 (5em) is used.
                    Larger numbers give space for cell width expansion, but pushes the vertical scroll bar to the right (perhaps off screen).

                    With

         cellWidth: width of cells (in em). Default is 5em   (cellWidths is a synonym for 'cellWidth')
                    Or, an object with elements 'colName':width (for column specific widths)
                    Width is in em. Thus: 8 means "8em".
                    If object is used, a special element --  '*':width  -- is the width used for non-matching columns
                    If "*" is not specified, 5em is used

                    Special value: '#' refers to the first column (the row number column). Its default width is 2 (2em).

         height: starting height of scrollable rows area. In em (fractional em allowed). Minimum value of 1.0. Default is 12em

         colLabels: an associative array of columnName:columnLabel.
             This is optional. If not specified, then all indices are displayed (in seperate columns).
             The columnName should match indices in the associative array  (mismatches are ignored).
             Each matching index is one of the columns of the table.
             And the "label" in the top row (the "column header row") will be "columnLabel"

             Reiterating:
                 If colLabels is specified, then ONLY the specified columnNames are displayed. And the columnLabel is used as the columnHeader.

             Special case: if one of the elements of the columnName is '*', then
                 *  all the "unspecified" columns (indices with no matching columnName) are displayed at  the end of the table.
                 *  in order of appearance
                 *   using its 'index value' as the column label
                 *  As described below, you can also specify a css class to use in these columnHeaders.

             Notes Important note:
                * skipVars overrides colLabels -- an index specified in skipVars is never displayed.
                * if colLabels is not specified, wsurvey.arrayToHtml will display columnHeaders across multiple rows.
                      Method: the columnName is split  on "_" or upperCase characters, numbers, or punctuation marks.
                      Example: 'myBestMovie' would be displayed  as 'my<br>Best</br>Movie'

             See below for details.

         colors: csv of colors. first is used in the "non scrolling rows" (and last row); after that is used for scrolling rows.. so there should be at least 3 colors
                   Default is 'tan,#abbaba,silver'

         freeze : # of rows (at start of vv) to freeze. These (along with the column header row are NOT scrollable.
                   All rows after freeze ARE scrollable.
                   Default value is 1

         hiliteClass: enable "hilite this row" if the left column (row #) button is clicked.
                      If not specified, or '0', this is NOT enabled.
                      If specified, should be a class to use to highlight the row
                      Or, use '', or '1', to use the default class (ws_arrayToHtml_rowHilite)
                      If enabled, the row# cell (the first column in each row) is displayed using  the ws_arrayToHtml_hiliteRowButton class.

          id: an id to use in the generated table. If not specified, or if 0, a random id is generated

         leftColLabels: object of "row id" labels.
            Format: {'top':'astring','*':'astring','width':'xem',n1=astring,n2=astring',...
              where:
                'top': a label for the first column in the column header row. If not  specified,'#' is used
                  '*'  : default label to used (for any row that does NOT match an "n1").
                        If not specified, the row number is used
                  'width': the width of the row# column (in em). The default is 2em
                  n1,n2... : a row number of the associativeArray.
                       if a rowNumber  matches a specified "n1", the  value of the leftColLabels will be used (in the left column of the row)

             Alternative: for simple strings (say, that do NOT contain HTML), a CSV can be used.
                          Example: 'top=Year,*=Vu,0=Final,5=Year 5'

         ndec:    if specified, and a value is not a 2 element array: the decimal precision IF the value is a number

         range : 'firstRow,lastRow' (inclusive) to display. If not specified, all rows are displayed (same as '0,'+vv.length-1)
                 Notes
                   * if lastRow is >= vv.length (or if it is NOT specified), vv.length-1 is used
                   * if freeze is specified, it refers to the rows from firstrow.
                     Example: if there are 10 rows in vv
                        range='4,9'
                        freeze=2
                        Then rows 4 and 5 are "frozen", and rows 6, 7, 8, and 9 are "scrollable"

         skipVars:  csvOfIndicesToSkip
             Example: 'maxYear,totalAmount' -- a maxYear or a totalAmount index (in a row of vv) is ignored (not included as a column in the display table)

             Note that skipVars overrides colLabels -- an index specified in skipVars is never displayed.

          titles: if specified, an associative array of 'header titles'. {'columnName1':'title1',...}
                  If a columnName matches a 'columnName1' in titles, the title is used as a mouseOver title in the column header.
                  These should NOT contain HTML (html will be removed).

                ------------------

II.b: Scrolling within a table

   When displayed using the default options, the top row contains the header. It does not move
   If you specified freeze, the next freeze rows do not move
   The remaining rows are scrollable.
   These scrollable rows are in a container with
     *  a dotted border (using a wsurvey_arrayToHtml_scrollRows css class)
     *  a vertical scroll bar on the right end
   On wide tables,
     *    the vertical scroll bar might be off of the browser window.
     *    there will be no horizontal scroll in the container. There should be one at the bottom of the browser


   One recommended approach is to insert the created table in nto an "outer container" with a fixed width,
   and overflow:auto. This adds a horizontal scroll bar under the table --  which tends to be a a nicer look.

  Example:

         stuff=wsurvey.arrayToHtml(vv,obts)
         $('#myDataView').html(stuff[0]);
         ...
         <div id="myDataView" style="width:95%; overflow:auto"></div<
         ...

   What to do if the vertical scroll bar iss off the browser window?
   Which means one has to scroll horizontallyto find it.  Or, use a mouse wheel.

     If this shortcoming is problemmatic, one can
        * Along with width and overflow, specify a "height" attribute in the container the table is inserted into ('myDataView' in the above  example).
        * Specify a large height parameter (say, 'height':1000) in the opts.

     Vertical scroll will now be visible! HOWEVER
        * It will scroll all the rows.
        * That is: the header row will NO LONGER be in a fixed location.

      Thus:
        * If you can depend on users having a mouse wheel, it is better NOT to specify a height in the  "outer container"

                ------------------

II.c: Using colLabels

   The  colLabels option (in wsurvey.arrayToHtml.init(vv,opts) is optional.
   If specified (as an object), it is used to:
       * select which columns (indicies of the associativeArray) to display,
       * in what order,
       * and what text to use in the column header.

    Example -- assume that there following indices are specified in the associativeArray to be displayed:
        myName,pets,cars,bikes
     And colLabels is specified as:
       colLabels={'myName':'User name','pets':'# of pets','cars':'# of cars'};

     ws_arrayToHtml willonly display the myName, pets, and cars indices (in that order), and use the specified column labels.
     Thus: elements in the associativeArray with an index of 'bikes' will NOT be displayed.

    You could  get something like
       User name     # of pets    # of cars
        joe          3            1
        nancy        6            0
        bill         0            2
        sam          0            0

    Again: notice that the "bikes" index is NOT displayed.

    The "label" can contain html! In fact, it can contain internal containers with class and style.
    For example:
        colLabels['bikes']='<span style="color:red"># of bikes</span>';

    There is a short cut to display all "non-specified" columns -- these will be written after the columns specified in colLabels.
       colLabels['*']=aClass
    For example:
         colLabels['*']='myOtherColsClass';

    The column label is the columnName (the index of the associativeArray), within a <span class="aClass">.
    If aClass='', then <span> is not used the columnName is displayed, after adding line breaks.

                ------------------

III.  Other arrayToHtml functions
  wsurvey.arrayToHtml.clickHandler and wsurvey.arrayToHtml.display give dynamic control over an existing htmlToArray table.

                ------------------

III.a: wsurvey.arrayToHtml.clickHandler -- Adding a click handler for each cell
   wsurvey.arrayToHtml.clickHandler can be added as a click handler for the entire table.
   It will capture all clicks on the table.
   If the click is on a cell (in either a frozen or scrolling rows), a custom function (that you create) will be called.

   For example, if wsurvey.arrayToHtml.init created a table with an id of 'mySampleTable'.
   After adding it to the document:

    wsurvey.arrayToHtml.clickHandler('mySampleTable',myCellHandler)

  where myCellHandler should be a function variable (not a string).

 The callback is called as:

     myCellHandler(irow,varname,origVal,evt)

  irow: the row number of the cell (starting at 0)
  varname: the "index value" of the cell
  origVal: the original value (not the "display value"). Any HTML is retained
  evt : the jQuery object of the <span> (of the cell that was clicked on)

                ------------------

III.b: wsurvey.arrayToHtml.display -- Change display of an existing table

  wsurvey.arrayToHtml.display is used to change several aspects of a currently displayed table.

     wsurvey.arrayToHtml.display(tableid,action,option)

  where
     tableid: the id, or jQuery object, of a araryToHtml table
     action: what to do. Actions are listed below
     options: an object, that can contain modifiers for an action.

   Actions:
       'rows' : increase or decrease the height of the 'scrollable rows' container.
               The option should be the number of px -- negative to decrease, positive to increase.
               The default is 12px (increase)
               The minimum size (after applying a change) is 20px.

       'cellWidth': change width of all cells by this amount. The option should be the number of px -- negative to decrease, positive to increase.
                  The default is 8px (increase)
                  The minimum size is 1px
           Caution: if you increase cell width, you might get wrapping of rows (multiple display lines per table row).
           This can be prevented by having larger values of the "addWidth" option -- though larger addWidth may cause the vertical scroll bar
           to be outside of the browser screen.


  Example. This uses the default of "rows". And finds the closest "sibling" (or cousin) that is an arrayToHtml table.

    stuff=wsurvey.arrayToHtml.init(vv,opts);
    $('#arrayToHtml_outer').html(stuff[0]);
    $('.changeTable').on('click',wsurvey.arrayToHtml.display);

     ....
    <div id="arrayToHtml_outer">...</div>
    <div style="background-color:cyan;width:15em">
       <input class="changeTable" type="button" value="&#8613;" title="view fewer rows "   option="-12"  >
       <input class="changeTable"  type="button" value="&#8615;" title="view more rows "   option="12"  >
    </div>


  Example: Increase all the cells by the default (6px)

       <input class="changeTable"  type="button" value="&#8614;" title="wider cells"  action="cellWidth"   >

  Example: decrease all the cells (in a table created with an id of myArrayTableHtmlId) by 3px

       <input class="changeTable" title="narrower cells"   type="button" value="&#8612;"  table="myArrayTableHtmlId"   action="cellWidth" option="-3"  >

  Example: direct call, increase all cell widths by 4

       wsurvey.arrayToHtml.display('myArrayTableHtmlId','cellwidth',4);

                ------------------

IV: Custom styling

   wsurvey.arrayToHtml uses several different css styles.
   You can override these by loading your own style sheet, before calling wsurey.arrayToHtml

    *  It MUST have a name of wsurveyArrayToHtml_defaults.
       Example:
          <style type="text/css" name="wsurveyArrayToHtml_defaults">
              ...
          </style>

     * It MUST specify ALL of the following styles.
       The coded values (listed below) can be used, but if you do NOT specify one of the the formatting will be flawed.

      The required css classes, and their coded values:


  .wsurvey_arrayToHtml_colHeaderRow {color:blue; background-color:#abbaab;}
  .wsurvey_arrayToHtml_topLeft { width:3em;  overflow:hidden;  font-style:oblique;  float:left;  background-color:#abbaab;}
  .wsurvey_arrayToHtmlTable_left1 { width:3em;  overflow:hidden;  font-style:oblique;  float:left;  background-color:#abbaab;}
  .wsurvey_arrayToHtml_main {white-space:nowrap;   overflow:auto;   border:1px dotted  gray;}
  .wsurvey_arrayToHtml_colHeaderCell {margin-right:5px;   width:5em;   min-height:1.1em;   overflow:hidden;
          float:left;  background-color:#abbaab; text-overflow:ellipsis;}
  .wsurvey_arrayToHtml_rowCell {   margin-right:5px;   width:5em;   min-height:1.1em;   overflow:hidden;
          float:left;  background-color:#abbaab; text-overflow:ellipsis;}
  .wsurvey_arrayToHtml_scrollRows {  margin-top:8px;  height:12em;  overflow-y:auto;  border:2px dotted #dbabab ;}
  .wsurvey_arrayToHtml_noScrollRows {  xborder-left:3px dashed white;}
  .wsurvey_arrayToHtml_aScrollRow {   margin-bottom:3px;}
  .wsurvey_arrayToHtml_rowHilite {   border:2px dotted blue !important ;   margin:0.5m 3px 0.5em 3px  !important;   color:#3333ff  !important ;}
  .wsurvey_arrayToHtml_hiliteRowButton {   color:#3333ff  !important ;   border-bottom:1px dotted blue !important ;}

                ------------------

V: Contact and legal

  Feb 2022
  Daniel Hellerstein
  danielh@crosslink.net


    wsurvey.arrayToHtml  is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    wsurvey.arrayToHtml  is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    If you did not receive a copy of the GNU General Public License,
    see <http://www.gnu.org/licenses/>.

