Feb 2022.  wsurvey.utils1.js

I. Installing

 wsurvey.utils1.js contains an assortment of possibly useful javascript functions.
 It is used be several other 'wsurvey.' libraries and utilities.

 wsurvey.utils1 uses jQuery.

 Hence, you could use the following in html files:

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

   Notes:
     * These functions are in the 'wsurvey.' namespace. Hence they should play well with other libraries.
     * And, although using jQuery --  $() is NEVER used as a shorthand for jQuery(). Hence these functions should coexist with other frameworks.

  The wsurevy.utils_test.html (in examples/) contains demos of these functions.

II.  Alphabetical list of functions.

  wsurvey.addComma              Add commas to a number (13612.5 becomes 13,612.5)
  wsurvey.addCssRule            Add a style sheet, or add rule to an existing style sheet
  wsurvey.addEventIfNew         Add an event to an element, if event not already added
  wsurvey.argJquery             Returns jquery object (of a "this" or evt argument to an event handler)
  wsurvey_attr                  jQuery extension to find attributes (case insensitive, and more)  -- use wsurvey_, not wsurvey.
  wsurvey_attrCi                simple version of wsurvey_attr
  wsurvey.checkHtmlSyntax       check a string containing html for tag errors
  wsurvey.crc32                 crc32 checksum
  wsurvey.cssClassExists        checks for existence, in all active style sheets, for a .class definition
  wsurvey.displayInNewWindow    display contents in a new window
  wsurvey.dumpObj               Return a nicely formatted string displaying an object's values (like php's var_dump)
  wsurvey.findRelative          Find a matching dom element that is a close relative
  wsurvey.findObjectDefault     Look for an index in an existing object. If not found, return default
  wsurvey.fitToParent           Adjust height of a "target" so that fits within its "parent"
  wsurvey.getFuncFromString     Converts a string containing a function or object.method name to a function reference
  wsurvey.get_currentTime       Return time and/or date
  wsurvey.getWord :             Parse string into words
  wsurvey.htmlspecialchars      convert html special characters into html entities (similar to php htmlspecialchars)
  wsurvey.makeHtmlSafe          make an html string "safe"
  wsurvey.quickStackTrace       Produce a simple stackTrace
  wsurvey.numberK               convert a number for xxK or xxM string
  wsurvey.obsfucate             Obsfucate a string   -- a bit stronger than rot13
  wsurvey.parseAt :             Split string into two parts
  wsurvey.removeAllTags         remove all html tags (including any attributes)
  wsurvey.removeMostAttributes  remove attributes from elements in jquery object
  wsurvey.rot13                 Rot 13 obsfucator
  wsurvey.seconds_ToTime        convert a number of seconds to hh:mm:ss format
  wsurvey.strip_active_tags     strip out action tags (script, style, head) from string containing html content.
  wsurvey.toPx                  Return an integer representing the number of pixels given this size (or location) measure
  wsurvey.unescapeHtml          convert stuff converted by htmlspecialchars back to <, >, etc.
  wsurvey.unobsfucate           Unobsfucate a string
  wsurvey.updateObject          update indices  in vals with values from matching indice in newVals
  wsurvey.version               Current build number


III. Details on functions

::::::  wsurvey.addComma  :: return string containing commas at thousands

      astring=addComma(avalue)

  Example:
      z=1616355 ;
      ss=addComma(z);
      yields ss='1,616,355'


::::::  wsurvey.addCssRule      ::  add a style sheet, or add rule to an existing style sheet

    wsurvey.addCssRule(cid,rules)

   where:

     cid: id of a stylesheet to add to.

       If '', add to default style sheet.
       cid is used if you want to make multiple calls to addCssRule, and add to the same stylesheet


    rules: optional. If specified, should be an object, each field being an object.

        Each top level object's field name is used as a .class type of css rule
        The value of each top level object should be objects with fields being css attributes

        Example:   {'boldRed':{'color':'red','font-weight':600},
                    'emGreen':{'font-style':'oblique','color':'green'}
                   }
                   
        If rules is not specified, then just create a styleSheet with an id of cid.
        It could be added to in later calls to wsurvey.addCssRule

  Example:

        addCssRule('foo1',{
                           'boldRed':{'color':'red','font-weight':600},
                           'emGreen':{'font-style':'oblique','color':'green'}
                         }
                  ) ;

    would be the same as specifying:

      <style id="foo1" type="text/css">
        .boldRed {color:red, font-weight:600 }
        .emGreen {font-style:oblique; color:green}
      </style>



::::::  wsurvey.addEventIfNew :: Add an event handlers to an element, checking if handler already added

    wsurvey.addEventIfNew(doList,forEvent,afunc,dObj,noMultipleHandler)

 Add an event handler for one (or several) jQuery objects.
 And check to make sure an event handler isn't already assigned.

   doList:     jQuery "collection" of elements to add an event handler to
   forEvent:   type of event (i.e. 'click','mouseup')
   afunc:      function to use in an .on(forEvent,dobj,afunc). Should be a function reference, NOT a string
   dobj:       optional. data object to use. If false (the default), no dobj is used
  noMultipleHandler : optional. if 1 (the default)-- if an event handler of type forEvent exists, for an item in (doList,forEvent,afunc,dObj,noMultipleHandler)
        don't add afunc to to this dom element.

   This is to prevent multiple event handlers for the same event i.e.; not allow two 'click' handlers.
         THus, if =0, allow multiple event handlers for same event

  Note that if a afunc is already a forEvent event handler for a dom object, it will NOT be added -- regardless
  of the noMultipleHandler setting. 

  Thus, wsurvey.addEventIfNew is a way to avoid adding multiple instances of the same event handler (say, due to some kind
   of refreshing process). But it will permit different handlers for the same event (if you set noMultipleHandler=0).

  Returns false if afunc not afunction. Otherwise # of event handlers added (could be 0)

  Note:   wsurvey.addEventsIfNew  is a synonym for addEventIfNew

::::::  wsurvey.argJquery ::  returns jquery object (of a "this" or evt argument to an event handler)

    eJquery=wsurvey.argJquery(anarg,which)

 where
    anarg: the object to be examined -- usually the argument to an event handler
            If a string, an element with and id="anarg" is looked for (anarg can, but doesn't have to, start with a '#');

    which: optional. If specified, can be 'target', 'currentTarget', or 'delegateTarget'. If not specified, or anything else, 'target' is used

  Returns a jquery object pointing to this anarg argument... such as what element was clicked (or what parent element is the event handler
  for a child that was clicked)

  Special feature:
     If anarg is an event argument, and if a .data object exists for the event, it will be returned as a  ['argJqueryData'] element
     of the jQuery object.
     
     Example:
          <input type="button" value="View my X" id="test1">

          $('#test1').on('click','{'x':55},myHandler);
          ...
          function myHandler(evt) {
             eUse=wsurvey.argJquery(evt);
             alert(evt['argJqueryData']['x']  ;  // displays '55'
          }

     Example:
          <input type="button" value="View my X" id="test1" onClick="myHandler(this)">
          ...
          function myHandler(evt) {
             eUse=wsurvey.argJquery(evt);
             alert(evt['argJqueryData']['x']  ;  // throws an 'is undefined' error
          }

         This is a way of testing if this was an inline call: the existencs of a 'argJqueryData' index!)

     Example:
          <input type="button" value="View my X" id="test1">

          $('#test1').on('click',myHandler);
          ...
          function myHandler(evt) {
             eUse=wsurvey.argJquery(evt);
             alert(evt['argJqueryData']['x']  ;  // displays 'undefined' -- no data was specified in the .on() call
          }

      Note:
         *  Why the trouble?  wsurvey.argJquery returns the 'target' (or similar) of the dom event (that is sent to it
         by jQuery when an event happens). And there is no easy way to retrieve the original (dom) event from
         this.  In particular, eUse[0] does NOT contain the .data field!

         *  If no .data is defined, eUse['argJqueryObject']={}
         *  If an inline call, eUse['argJqueryObject'] is undefined. 
             Checking for the existense of  eUse['argJqueryObject'] is a means of determining if an inline event occurred!

  which is optional.  It selects which element to use. Possible values are (the default is 'target'):
          target: use the element that triggered the event (e.g., the user clicked on)
          delegateTarget: use the element the event listener was  attached to
          currentTarget: usually the same as delegateTarget. It can differ if .on was used, and  "selector" was specified
                   If a selector was specified, currentTarget returns the element matching this selector (a child of delegateTarget,
                   possibly parent of target).  If no selector field, currentTarget and delegateTarget are the same.

  Why use this?
   An event handler could of been assigned using:
     i) jquery.     Example:   $('#myButton').on('click',myFunction)
    ii) javascript. Example:   document.getElementById('myButton').addEventListenter('click',myFunction);
   iii) inline.     Example:   <input type="button" value="info!" onClick="myFunction(this)"  what="mainInfo">
    iv) String.     Example:   <input type="checkbox" id="whatBox" value="Guess1" onClick="myFunction('#whatbox')"  what="Correct!">

 This detects the method of assignation, and returns the appropriate jquery object.

 Note:  If a "inLine" call, or a 'string' call:  'which' is not used. For inline, the element clicked is used. For string, the element with
        a matching id.

 Example:
   function myFunction(thisEvt)  {
      var eJquery=wsurvey.argJquery(thisEvt);
      var awhat=eJquery.attr('what');

 ::::::  wsurvey_attr ::  jQuery extension to find attributes (case insensitive, and more)

   $(someSpec).wsurvey_attr(lookForAttrs,adef,notExact,doAll)

    This is called as  $(someThing).wsAttr(theAttr,adef,notExact,all) -- it is an extension to jQuery

    It returns the value of the lookForAttrs attribute(s), or a default value (adef).
    What is returned depends on the arguments:

    Special case: no arguments. Returns an object containing all attributes (as indices) and their values

  lookForAttrs: look for this attribute. Or, a csv of several attributes to look for
                Special case: 'data:attribute' will look for a match in a data object (as set in an .on() call).

  adef:  the default, if no match is found. If not specified, null is used
  notExact:     0 :  case  sensitive (exact) match
                1 :  ci  match (the default)
                2 :  look for a case insensitive abbreviation match (after exact and case insensitive)
                3 :  look for a case insensitive substring match.
     An exact match is always done first. If found, it is returned (or the first pair saved).
     A case insensitive match is done next (if exact didn't work).
     Then, either an abbreviation or a substring match may be attempted.

  doAll:
        If doAll=0 (the default), the first match found is returned.
          Thus, an exact match is returned if found, even if an abbreviation match also is possible
        If 1, an object with indices that are the attribute names of the matched attributes -- there could be several.
        Each object is an array with 3 fields:
            [0] : the value of the attribute (the index of this row of the returned object)
            [1] : the "lookForAttrs" that matched this attribute name
            [2] : the type of match: 'exact', 'ci', 'abbrev', or 'substring

    Note: if notExact=0, doAll is ignored (it is set to 0) -- if you want exact, you are getting it (or the default).

           if doAll=1, adef will be returned if no match. Adef could be specified to be an empty object. Or a null.
           It's up to the caller!

  Specifying several attributes (in lookForAttrs)

     In addition to checking for substring matches, you can specify completely different attributes to look for, by
     specifying several attribute names in a csv. wsurvey_attr will go through this list, and stop on the first success.
     If there are no matches (using notExact when searching for a match) after checking all of the attributes in the csv,
     the default is returned.

     Example:
           eTest=#('#mySpan');
           myAttr=eText.wsurvey_attr('pubYear,data-pubYear','1999',2);
      Would
        * look for a pubYear attribute, then an attribute that starts with pubYear. If none found ...
        * look for a data-pubYear attribute, then an attribute that starts with data-pubYear. If none found ...
        * return the default (1999)

   Special feature: looking for data object values
      If the an attribute  (perhaps in a csv of attributes) starts with ':', then instead of looking in the 
      element's attributes,  the data object of the event will be examined.
      Where the 'data object' is created when jQuery.on(), or a similar method, is used to assign an event handler.

      Example:

           <input type="button" value="Display pub year"  pubyear="2005" id="checkPubYear" >
           ...

           let eSpan=$('#checkPubYear');
           eSpan.on('click',{'pubDate':2010},myHandler)
           ...
           function  myHandler(evt) {
              let eTest=#('#checkPubYear');
              let myAttr=eText.wsurvey_attr(':pubDate,pubYear,data-pubYear','1999',2);
           }

         would return '2010' (since :pubDate is specified first).

       Notes:

          * data lookup is exact -- notExact is ignored.
            notExact would be used for pubYear and data-pubYear, if pubDate was not specified in the evt.data.

          * the order matters. if ':pubDate' was last in the csv, myAttr would have a value of 2005.

          * if evt is from an inline call, there is no data object. So no match can occur (but it will not be an error).

  Technical note: at this time, we can't figure out how to specify something like
         $(someSpec).wsurvey.attr(...)
     Hence the use of the oldfasiony
         $(someSpec).wsurvey_attr(..)

  Caution: if someSpec is the argument to callback from an .on('event'), we recommend using somethink like:
              function myCallback(evt) {
                  let eUse=wsurvey.argJquery(evt);
                  myAttr=euse.wsurvey_attr('myAttr','theDefault!');

           In these cases, using evt=jQuery(evt) may lead to wsurvey_attr failing.

 ::::::  wsurvey_attrCi :: simpler version of wsurvey_attr

   $(someSpec).wsurvey_attrCi(theAttr,adef)


    It returns the value of the theAttr attribute(s), or a default value (adef).
    It uses a case insensitive search. If multiple matches (say, due to case differences), the first match is found.
    Case differences are usually NOT expected in attributes in a dom element!

    Basically: this is almost the same as wsurvey_attr(oneAttribute,adef,1,0) -- but with less overhead.

    Notes: most browsers convert attribute names to lower case.
           But the .attr() function may not convert its argument to lower case!
           Using wsurvey_attr and wsurvey_attrCi avoids these nuisances.


::::::  wsurvey.cssClassExists    :: checks for existence, in all active style sheets, for a .class definition

         wsurvey.cssClassExists(aclass)

        where aclass is the name of a class.

    For example, if
      .myclass {stuff }
    is defined in a style sheet, then
        wsurvey.cssClassExists('myclass');
    returns true.

    Obviously, if no such class found, return false

::::::  wsurvey.crc32(astring,ashex)     ::  crc32 checksum.

     acrcHash=wsurvey.crc32(astring,ashex)

  where
     astring: string to create crc32 of
     ashex: optional. If 1, return as hex (otherwise as decimal)

  Examples:
      wsurvey.crc32('abc')    ==>  891568578
      wsurvey.crc32('abc',1)  ==>   352441c2

      wsurvey.rc32('hello world')    ==>  22957957
      wsurvey.crc32('hello world',1) ==>   0d4a1185


::::::  wsurvey.displayInNewWindow      -- display contents   in new window

   awin= wsurvey.displayInNewWindow(aid )
     or
   awin=wsurvey.displayInNewWindow(aid,opts)

     where :
        aid : id, or jQuery/dom object, to pull content from
        opts: list of options (

       awin: is a pointer to the new window.  Or false if an error occurred (console.log() will contain an error message)

   The two alternatives:

   The simple version, wsurvey.displayInNewWindow(aid):

      a) if aid is a string: finds an element with id=aid ('#' prepended if necessary).
         Or if aid is a jQuery or dom object, uses its contents
      b) extracts the element's contents (using .html())
      c) creates a new window
      d) and writes this content to it.

   The second version has a lot more options -- as specified in the opts object.

   Options in opts (case insensitive):

       'bars'  : default=0. If 0, don't show menubars, etc. If 1, do show
       'content': A string to use as contents. If 'content' is specified, aid is ignored
       'cssFiles': a space (or ,) delimited list of paths to css files. These are added to the <head> of
                window created to show the content.
       'header': string added to top of document.  If type='html', added at top of <body>
       'height': height of new window in px. If not specified, system determines
       'jsFiles': a space (or ,) delimited list of paths to javascript files. These are added to the <head> of
                window created to show the content.
       'name' : name (target) of the new window. If not specified, '_blank ' is used
       'title' : title to use for the new window. Otherwise, a generic title is used
       'type'   : How to open  new window.
                     'text': use 'text/plain; charset=utf-8'
                     'html' (the default): use 'text/html; charset=utf-8');

                  Note: title and cssFiles are only used if type='html'

                  This is semi-deprecated -- most browsers will always open as html.
                  Thus: type='text' will now wrap <pre> around the header and content.


       'width': width of new window in px. If not specified, system determines

  Examples:
             wsurvey.displayInNewWindow('#currentStatus');
             wsurvey.displayInNewWindow($('#currentStatus'),
                  {'title':'Your current info','cssFiles':'myProg1.css'} );
             wsurvey.displayInNewWindow(0,
                 {'cssFiles':'myStuff.css /genStyles/ourStuff.css','title':'My page','name':'myPopupWindow',
                 'content':'This is the content to <span class="myClass">display</span> .....'} );

  Notes:
    Some window names tha could be used (some are mostly deprecated)
         _blank - URL is loaded into a new window. This is default
        _parent - URL is loaded into the parent frame
        _self - URL replaces the current page
        _top - URL replaces any framesets that may be loaded

      When specifying cssFiles or jsFiles, relative use URIs are relative to the location of the current document.
 

 ::::::  wsurvey.dumpObj ::  return a nicely formatted string displaying an object's values (like php's var_dump)

    wsurvey.dumpObj=function(v, howDisplay, amess)

   where
      v: the object to display. It can be a string or number (not a very exciting display if so)
      howDisplay: if 'alert' or 'console', output is displayed in an alert, or using console.log()
                  '1' is a synonym for 'alert'
                  Anything else: output is not displayed
                  If not specified, 'alert' is used
      amess : short message to display at the top of the string (optional)
      
      Regardless of the value of howDisplay, a string is returned (that contains what alert or console.log() would display.
   

:::::: wsurvey.checkHtmlSyntax    :: check a string containing html for tag errors

       numErrs=wsurvey.checkHtmlSyntax(stringOfHtml,ignore);

  where

      stringOfHtml: string containing html markup
      ignore : string; space delimited list of "unclosed" tags to ignore  (they do not require closure)
               By default: 'br p input option hr ' are ignored (they do NOT have to be closed)

      Returns number of errors found (0 if no errors)

    Check a string containing html for tag errors (unclosed tags)
    An "unclosed" tag is something like <b> in
         <em>Hello <b>world </em>
     (there should be a </b>)

     Note that all <x /> tags are okay  -- they are considered to be closed tags

 ::::::  wsurvey.findRelative ::  Find a matching element that is a close relative

       jQobj=wsurvey.findRelative(aid,sel,doFind,maxDepth,showIter)

  Find an element that matches the jQuery "sel" selector. This a recursive combination of .parent and .find (or .children).

     aid :  id of an element. Or its jQuery (or dom) object
     sel :  jQuery selector. For example: '.gotInfo', or '[name="infoBug"]', or 'input[type="file"]'
     doFind : 0 or 1. If 1 (the default), use .find() to search for "descendants". Otherwise, use .children() to look for children only
     maxDepth : maximum depth of search. Default is 250
     showIter: if 1, return [jqObj,niters], where niters is approximate guess at how many iterations were needed to find the match

    jQobj is either a jQuery object pointing to the closest relative matching sel.
         Or if showIter=1 : a [jQobj,niters] array
         Or a  false, or a [false,niters], if no match could be found.
         Or false if a specification problem (such as aid does not match to a jQuery or dom element)

   How does this work?
      1) Does the original aid match (using .filter) ? If not--
      2) Does parent match (.parent)? If not
      3) Given doFind: if 0r look at parent's children (.children), if 1 look at all descendants (.find). If no match
      4) If the parent is <body> return false.  Or if iters>maxDept, return false.
         Otherwise, set parent= parent's parent (grandparent)
      5) Repeat 2 to 4.

 Note that for the actual parent (the first time step 3 is done) children and .find are similar.
 But in later steps, find can be much broader -- it will look for all grandchildren (not just the children of the 
 child that led to the current parent).

 See testFindRelative.html for a demonstration.


 ::::::  wsurvey.findObjectDefault ::  Look for an index in an existing object. If not found, return default

  avalue= wsurvey.findObjectDefault=function(aobj,aindex,adef,notExact,all)

  Return the value of an index (aindex) in an object (aobj).
  If no such index, return adef.

   aobj: the object (associative array) to be searched. Must exist (even if empty)!
   aindex: a string: the index (within aobj) to look for. Or a csv of several indices
   adef: value to return if no match of aindex in aobj is found  -- only used if all=0
   notExact: The matching can be exact, case insensitive, abbrevation, or substring -- notExact controls this
      0: case  sensitive (or 'e' )
      1: case insensitive (the default). Or 'c'
      2: abbrevation (case insensitive). Or 'a'
      3: substring (case insensitive).  Or 's'

  all: if 1, returns a array, each row of which is a 2 element array containing [matchingIndex,matchingIndexValue]
       if all=1, and no matches, returns an empty array
       Default is 0 (just return first match, or adef if no matches)

    IF all=0, and there are multiple matches to an aindex, the first match is returned. Which may be indeterminate.
     If all=1, all matches are returned in an array.
   If all=1, adef is ignored.

  aindex is usually a single word, naming an attribute But, it can be a csv of several attributes.
  These will be searched in order of appearance.
     If all=0, the first match is returned -- starting from the first word in aindex.
      Note: an abbrev match to first match trumps exact match of 2nd index
     If all=1, an array of object pairs is returned -- with entries for each word in a index (given a match occurs)

   Example (all =0,exact=0):
       aindex='data-color,color' would search for data-color first, and color if there was no match  to data-color

  If there is a specification error (aobj is not an object): return false.
  If all =1, also write to console.log('wsurvey.findObjectDefault error: aobj is not an object ']

:::::: wsurvey.fitToParent   :: adjust height of a "target" so that fits within its "parent"

   Adjust height of "target" container so that it fits inside of a "parent" container.
   "fits within" means that scroll bars are NOT needed on the parent -- although they may be added to
    the target.

        astat=wsurvey.fitToParent(aelem,opts)

    aelem: the "base" element. If specified this MUST be a jQuery object.
            Such as a pointer to a button that calls a function, that then calls wsurvey.fitToParent.
            This can be ignored IF the opts never use  "relative selectors"

     opts: options.  If not specified, several default options are used.

     astat: An object with properties:
             error: false if no error. Otherwise an error message
             parentHeight: original height (px) of the parent.
             targetHeight: original height (px) of the target
             newTargetHeight: height (px) of the target AFTER "fitting"
        The last 3 are not specified if an error occurs.

  The options.
    target   : the element whose height is to be adjusted
    parent   : the element that contains target -- the target height is adjusted so as to "fit" within the parent
    padding  : pixels to pad (between bottom of target and bottom of parent)
               Or, you can specify as nnEm or nn% (% is percent of parent height)
   minheight : minimum height of target pixels (or em or %)


 Specifying selectors.
     The target, and parent, properties should contain jQuery selectors, that point to a unique DOM element.
     If zero, or more than one matches, occur an error message is returned.

    In addition to the jQuery string selector, there are a few special "relative selectors"

 The relative selectors

   *  using -n to specify descendants
      Specifying a negative number (such as '-2') means to use that  ancestor --
         hence -1 is parent, -2 is grandparent.
       In all cases, if going back hits the top element (the "body")  BEFORE going back the requested n generations,
       an error occurs.

   * using a - or ~ prefix
     -  : a '-' as first character means look for an ancestor of aelem
          If aelem is NOT specified, or is not a jQuery pointer to a DOM element, this causes an error message.
          Essentially, this works using: aelem.closest(selector)  (with leading - stripped)

     ~  : a '~' as first character means look for a "relative" -- a parent, sibling, uncle, cousin, ...
         This uses wsurvey.findRelative(aelem,sel)       (with leading ~ stripped)

    For both - and ~ : if no matching element, or more than one match, an error occurs.

    Example: parent='-[name="myReportBox"]', target='~[name="myReportBox_current"];

      would find (relative to aelem)
          the "closest" ancestor with a name of myReportBox, and find
          the "relative" container with a name of myReportBox_current.

      The difference between - and ~ :
            - looks up the line of descent. 
            ~ looks up up, and then back down (in a recursive fashion).

      Hence myReportBox_current could be in a different line of descent then aelem.

      Note that one could use absolute selectors (such as '#myReportBox_current_ver1').
      The use of - and ~ makes it easy to generalize: all you need is to correctly specify the "aelem" --
      you can use the same options for each of several seperate containers (say, with its own button).


  Notes:
   *  '*' (for parent) means "document body" ... $(document).
   *  To avoid odd errors, ethis must be a jQuery object. It can not be a jQuery selector, or a dom object.
   *  The working assumption is that there is no content between the target and the bottom of the parent.
       If there is, this other content will be pushed down, and scrolling of the parent will be required.
   *  Scrolling might not be exact, hence even a good fit may require a bit of scrolling of the parent
   *  The target MUST be a descendant of the parent (as a child, or lower).
        This works best if the target is a child of parent.
           Deeper descendants don't work as well  --  since intermediate ancestors may 
           impose height restrictions that won't be accounted for.

   * wsurvey.fitToParent_find(aelem,bFind) is a helper function. It will resolve "relative selectors".
     This can be useful if you want to find other containers (say, on the same "row" as the target), and
     resize them. Say, by using the newTargetHeight property.
       belem=wsurvey.fitToParent_find(alem,aSelector)
     where belem
       [jQueryElement,true]  -- if a successful match
       [false,errorMessage'] -- if not successful
     


::::::  wsurvey.get_currentTime :: return time and/or date

       timeString=get_currentTime(timeType,dateType,myTime)

    where
        timeType:
            0 = javascript milliseconds   (and ignore dateType   argument).
            1 =  mm:hh:ss xM. This is the default (if no argument specified).
           2  = GMT offset noted
           11 or 21: same as above, but 24 hr time
           31 : hr:min as 24 hr time (no gmt)
           otherwise: same as 0

    dateType:
         1=  date Month Year
         2 = day-of-week, date Month Year
    
    myTime : optional. If specified, a javascript style unix time stamp. It will be used instead of the
           current time


    If timeType and dateType ==2 : return as: xday Month date year hh:mm:ss GMT-xxxx (tz)
    (uses javascripts .toString method)

    If both time and date specified, format is 'date-string time-string'

    Examples:
             a=wsurvey.get_currentTime() ==> a='1:51:22 AM'
             a=wsurvey.get_currentTime(0) ==> a='1588225882097'
             a=wsurvey.get_currentTime(1) ==> a='1:51:22 AM'
             a=wsurvey.get_currentTime(11) ==> a='13:24:22 '
             a=wsurvey.get_currentTime(2) ==> a='5:51:22 AM GMT'
             a=wsurvey.get_currentTime(21) ==> a='21:12:05 GMT'
             a=wsurvey.get_currentTime(31) ==> a='21:37'

             a=wsurvey.get_currentTime(1,1) ==> a= "30 Apr 2020 1:53:06 AM"
             a=wsurvey.get_currentTime(1,2) ==> a="Thu, 30 Apr 2020 1:53:06 AM"

             a=wsurvey.get_currentTime(2,1) ==> a="Apr 30 2020 5:53:06 AM GMT"
             a=wsurvey.get_currentTime(2,2) ==> a="Thu Apr 30 2020 01:53:06 GMT-0400 (Eastern Daylight Time)

    Note: wsurvey.CurrentTime and wsurvey.currentTime are synonyms for wsurvey.get_currentTime

::::::  wsurvey.getFuncFromString ::    Converts a string containing a function or object method name to a function referenc

    afunc=wsurvey.getFuncFromString=function(func) {

  Converts a string containing a function or object method name to a function reference.
  You can then the returned as a function, providing your own parameters.

  Examples, assuming:

    function sayBye(who) { alert('Goodbye to '+who)};
    myFuncs={};
    myFuncs.sayHi={};
    myFuncs.sayHi.hello=function(x) { alert('hello to '+x)};
    myFuncs.sayHi.bighello=function(x) { alert('a BIG hello to '+x)};

    useFunc=wsurvey.getFuncFromString('myFuncs.sayHi.hello');
    useFunc('Joe');  // same as using myFuncs.sayHi.hello('Joe');

    useFunc=wsurvey.getFuncFromString('myFuncs.sayHi.hello');
    useFunc('Joe');  // same as using myFuncs.sayHi.hello('Joe');

    useFunc=wsurvey.getFuncFromString('sayBye');
    useFunc('Sally');  // same as using sayBye('Sally') ;


::::::  wsurvey.getWord :: parse string into words

     wlist=wsurvey.getWord(astring,nth,adelim)

  where:
      astring : a string
      nth: if specified, retrieve just the nth word. If 0, or if not specified, return all words in an array.
           Note: to get first word, use nth=1 (not 0)
           If nth<0, get from end. So, nth=-1 means "get last word"
           If abs(nth)># words, return ''
      delim: delimiter. By default, ' ' is the delimiter.  You can specify /\s+/ (no quotes!) to signify "use any whitespace as a delimiter"
             or /[\,\s]+/ to also included commas as delimiters

       wlist: an array of words, or the nth word. Returns '' if astring is not a string, object, or if nth is > # of words

    Special case:  if nth is an array, get words from nth[0] to nth[1] INCLUSIVE.

    Note: getWords is a synonym for getWord

    Examples:
     astring="this is my string of words "
     wsurvey.getWord(astring,1) = "this"
     wsurvey.getWord(astring) = ['this','is','my','string','of','words']
     wsurvey.getWord(astring,7)=''
     foo=wsurvey.getWord(astring,,[3,4] ) = "my string"    -- note that the first word is 1, not 0.

    Note: wsurvey.getWords is a synonym for wsurvey.getWord


::::::  wsurvey.makeHtmlSafe ::  make an html string "safe"

    [stuff]=wsurvey.makeHtmlSafe(atext,careful,removeCt)

   where :

      atext  : an html string
      careful : if 1, removes less dangerous tags are also removed
      removeCt : optional. If 1, also return # of elements removed

   Returns
        [status,safeHtml]
   or if removeCt=1
        [status,safeHtml,nRemoved]

   where:
     status=0 means some kind of html problem (such as unclosed markup); 1=ok
           if 0, safeHtml contains "tags stripped" version of atext    (all html removed)
           if 1, safeHtml contains  "no potentially dangerous stuff version" of atext.

     safeHtml is a version of  atext with most attributes (such as onClick, etc) removed,
            and hazardous tags (such as <script>) removed. Or, if a problem, all html tags remove

     nRemoved : estimate of # of elements removed.

  For details of careful and removeCt, see the description of  wsurvey.strip_active_tags

  Note: wsurvey.makeHtmlSafe function  uses wsurvey.checkHtmlSyntax  and wsurvey.strip_active_tags


::::::  wsurvey.makeNumberK ::  convert number to xxxK, xxxM, or xxxG 

      numberString=wsurvey.makeNumberK(aval,maxAsIs,ndec)

   where:
       aval: numeric value to display
       maxAsIs : the maximum value to display "as is" -- with commas. Anything larger is displayed using xxx.xK. Default is 10,000
       ndec: number of decimal to display when "displaying as is". Default is -1 -- which means return no commas, no decimal

   Display number compactly, using K or M.

   Examples:
     wsurvey.makeNumberK(596.65 ) --> 597
     wsurvey.makeNumberK(596.65,1000,1 ) --> 596.6
     wsurvey.makeNumberK(59215) --> 59.2k
     wsurvey.makeNumberK(596.65 ) --> 597
     wsurvey.makeNumberK(59592.87) --> 59.6k
     wsurvey.makeNumberK(59592.87,100000) --> 59593
     wsurvey.makeNumberK(59592.87,100000,1) --> 59,592.9
     wsurvey.makeNumberK(616717.99) --> 616k
     wsurvey.makeNumberK(111222333,100000,2) --> 111m


::::::   obsfucate  :   obsfucate a string
          obsfucatedString=wsurvey.obsfucate(clearString)

       clearString is the original string.

       obscufactedString  is a unobsfucated string that can be processed by  wsurvey.unobsfucate; or by the unobsfucate
       function in wsurvey.utilP.php

       This provides a very weak encryption -- only useful for making it tougher for a visual inspection of a message to
       reveal a string.

::::::  wsurvey.quickStackTrace ::  produce a simple stackTrace (somewhat deprecated)

    wsurvey.quickStackTrace=function(asString)

    where:
        asString is optional. If specified, it should be an 's'.

    flist: an array containing the names of caller functions.
           If asString='s', return this as a csv string.

   Returns an array with names of callers, up to 10 deep.
    0th element is immediate parent, 1 is parent of caller, etc.

   If first called function is from the root: last element is ''
   If first called is an onClick="somefunc()" : last element is 'click'
   If called from an .on('click',somefunc): last element is the , 0th line is the function (somefunc)

  Limitations: 
     if  functions specified as aname.afunc() are used (i.e.; wsurvey.xxx functions), the
     name appears in the stackTrace as ''.

     More importantly, current versions of javascript (as of Feb 2022) provide undependable support for function.caller (the
     javascript function used by wsurvey.quickStackTrace).
     
     Hence:   wsurvey.quickStackTrace also call: console.trace('Quick trace ') -- 
              the console will contain an accurate and complete (with line numbers) trace!

 ::::::  wsurvey.parseAt ::  split string into two parts

   res=wsurvey.parseAt(theString,achar)

   The first part is everything before achar, the second everything after.
   If there is no achar, the first part is everything, the second is ''

   Example:
       res=wsurvey.parseAt('myPage.php?var1=1&var2=151','?');
   yields:
       res[0]='myPage.php'
       res[1]='var1=1&var2=151'

   This saves one the trouble of using .split(achar), and then using splice for indices [1]...

::::::  wsurvey.removeAllTags    :       remove all html tags

     noTagString=wsurvey.removeAllTags(htmlString);

     Removes all html tags. That means any attributes within the tag.
     Uses .dom (.html() followed by .text().

     Example: wsurvey.removeAllTags('Goodbye <b>cruel</b> world, my <input type="button" value="days"> are <span class="c1"><u>numbered</u> low</span>';
       returns: 'Goodbye cruel world, my are numbered low'

::::::  wsurvey.removeMostAttributes

       wsurvey.removeMostAttributes(q1,whitelist,blacklist)

    where:
       q1      : a jquery object
       whitelist: array of attribute names to NOT remove (case insensitive)
        blacklist: array of arrays 2nd check: if an attribute has a value in the blacklist, remove it.

   Remove attributes from elements in a jquery object

   Blacklist details:
       each array should have the syntax [attributeName,val1,val2,...];
              the attributeName should be in the whitelist (since only attributes in the whitelist are subject to blacklist testing)
              attributeName and val1,.. are case insensitive comparisons

 Example -- remove "dangerous" attributes from a body of html message

     qq=$('body').find('*');
     //  note this will remove type="submit" attributes, but retains type="checkbox"
     removeMostAttributes(qq,['value','title','style','class','name','type'],[['type','submit']]);


::::::  ws_selectText                  :: mark, or copy, contents of a container

     ares=ws_selectText( containerid,awhat)

  where
    containerid: id of a container to mark, copy, or copy to clipboard.  A string, without leading #
     awhat: optional. 'MARK' (the default), 'COPY',   'CLIP' ,or UNNMARK:
             MARK text in container.  returns true or false
             COPY marked contents.  returns contents, retaining HTML
             CLIP: copy to clipboard (case insensitive).  returns 1 on success, 0 on faiure, -1 if unsupported browser
             UNMARK if any text is selected. returns true

     NOTE: CLIP does NOT seeem to work under firefox (april 2020)

   Alternative approach, if text is in a <textarea>
       document.querySelector("textarea").select();
       document.execCommand('copy');
}


:::::: wsurvey.secondsToTime  w  ::  convert a number of seconds to hh:mm:ss format

     astring=wsurvey.seconds_ToTime(nseconds,dayHrs)

     where
          nseconds: number of seconds to convert to hh:mm:ss format.  Careful: seconds, NOT milliseconds!
          dayHrs: optional. If specified, report "days" by dividing nseconds by dayHrs hours
          astring: string containing the hh:mm:ss representation of nseconds

     If nseconds is not a number, nseconds is returned as is

     Example:
       aa=Seconds_ToTime(200) yields aa='03:20'
       aa=Seconds_ToTime(500200) yields aa='55:36:40'
       aa=Seconds_ToTime(500200,24) yields aa='2d 7:36:40'

   wsurvey.seconds_toTime and survey.seconds_ToTime are synonyms

::::::  wsurvey.strip_active_tags     :       strip out action tags (script, style, head) from string containing html content.

          newstring=wsurvey.strip_active_tags(oldstring,careful,removeCt)

    where:
        oldstring : string with html
        careful: if specified and 1, removes less dangerous tags (base, applet,frame,iframe,layer,embed,meta)
         removeCt: if specified and 1, returns [newText,# removals]. Otherwise, returns newText
                     # of removals isn't perfect -- it does not count Head, body, html and some other tags (but not
                     necessarily content) removed

     This takes as input a string that contains html markup.
     It returns a string containing html markup, with "possibly dangerous" tags removed, such as  script, style, and head.

     Thus, the resulting is safer for display -- it's formatting might not be as pretty, but it is unlikely to do anything odd.

     This also remove attributes that are suspicious, such as <input type="submit">
     And it changes css position to relative.
     
     IOW: it is meant for processing user supplied input, that should not have fancy formatting or dynamic controls.


::::::  wsurvey.toPx ::  return an integer representing the number of pixels given this size (or location) measure

   jsizeInPx= wsurvey.toPx(asizeXX,heightOrWidth,bodyFontSize)

     asizeXX : a size measure. "xx" can be "px", "%", or "em". Or can be ""
     heightOrWidth : the screen height or width (in pixels). Used in "%" measures.
                     We recommend using one of the special values: 'h' to use current screen height, 'w' to use current screen width
                     If not specified, an average of current screen height and current screen width is used

     bodyFontSize: size of a character. Used in "em" measures. If not specified, or '',  use the value of $("body").css('font-size')

     jsizeinPx is an integer -- the number of pixels asizeXX converts to.

     if asizeXX is a number, it is converted into an integer and returned as is.


::::::   wsurvey.unescapeHtml  : convert stuff converted by htmlspecialchars back to <, >, etc.

         htmlString=unescapeHtml(convertHtmlString)

         Example:
            let astring='This is my <b>bold</b> and <span style="color:blue">blue</span> string ' ;
            let cvtString=wsurvey.htmlspecialchars(astring) // This is my &lt;b&gt;bold&lt;/b&gt;  and ...
            let astringAgain=wsurvey.htmlspecialchar(cvtString) ; // astringAgain==astring

::::::   wsurvey.unobsfucate  :   Unobsfucate a string
          clearString=wsurvey.unobsfucate(obsfucatedString)

       where obscufactedString was created by wsurvey.unobsfucate; or by the obsfucate function in wsurvey.utilP.php
       and clearString is the original string.

       This provides a very weak encryption -- only useful for making it tougher for a visual inspection of a message to
       reveal a string.



::::::   wsurvey.updateObject  : update  fields in vals with values from newVals

    updatedVals= wsurvey.updateObject(vals,newVals,checkType,okFields,synonyms){


   where:

       vals : object with existing values.  This sets the "default values" of the updatable fields

       newVals: object with values that will be used to change values in vals (if an index  match occurs).

       checkType: optional. If 1, then  a match in newVals to vals only occurs if the type of the value is the same

       okFields : optional. If specified, should be an array (or csv) of indices in vals that can be updated.
                   Thus, if okFields is specified; if newVals has a field that matches a field in vals, it ALSO much match a 
                   field specified in okFields.
                   If it does not match a field specified in okFields, updating does not occur (the value in vals is unchanged)
                   
                   Thus: if okFields is specifed, than any index in vals that is NOT listed in okFields is locked -- its value will
                         NOT change (even if there is a match in newVals)

      synonyms: optional.   If specified, should be an associative array. 
                Each element in the object should be structured as :
                     'altIndex':'indexInVal'
                If an "altIndex" exists in newVals, its value will be used to update the "indexInVal" index in vals (assuming
                vals has such an index".

                Example synonynms={
                    'FieldA':'field1',
                    'fieldB':'field2',
                    'field':'field1'};
                A newValues field of 'fieldA', 'field1', or 'field' will be used to set the value of vals['field1'].
                Note that 'field1' does not have to be mentioned, synonyms extend (they do not replace) the natural match.

       When examining newVals:
            Case insensitive (and space trimmed) matching is used -- so fooBar in vals matches FOOBAR in newVals
            A field name in newVals that does NOT exist (using a case insensitive match) in vals is ignored
            If there are multiple matches (due to synonyms or multiple case-insensitive matches), the value used is indeterminate
            (it might depend on the order of specifiction of indices in newVal)

       Returns the updated vals object



::::::  wsurvey.version ::  return version number of wsurvey.utils1

   aver=wsurvey.version()

   For example, avar could be: '20220128.1';

Contact and legal

  Feb 2022
  Daniel Hellerstein
  danielh@crosslink.net
  http://www.wsurvey.org/distrib, or  https://github.com/dHellerstein 

    wsurvey.utils1  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.utils1 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/>.

   