How to add autocomplete functionality to an HTML input field to aid selection and permit effortless form field pre-population on the IBM i

When presenting users with an HTML form to be filled in it can be helpful to present the user with an autocomplete list of suggested entries that gradually narrows down the potential selections until the necessary entry is visible in the select list. The list of suggested entries that is displayed in the selection list can be limited to a given number of entries gradually restricting the selections as the input field being keyed becomes more granular. Once the desired entry is presented in the drop-down list then the user can select the entry by clicking on it with the cursor which auto-populates the rest of the fields in the form.

This functionality is made possible by using jQuery and deploying the use of it’s Ajax methods to allow the transfer of data back and forward asynchronously between the web browser client and the IBM i. Below I have outlined the HTML and jQuery I used in order to call the ILE RPG program that produces the drop-down list.

//---------------------------------------------------------------------------------------------//
// All source code included in this document are provided on an "AS IS" basis without warranty //
// of any kind. NOVAGEM WILL NOT BE LIABLE FOR ANY ACTUAL, DIRECT, SPECIAL, INCIDENTAL, OR     //
// INDIRECT DAMAGES OR FOR ANY ECONOMIC CONSEQUENTIAL DAMAGES INCLUDING LOST PROFITS OR        //
// SAVINGS.                                                                                    //
//---------------------------------------------------------------------------------------------//
<!doctype html>
<html lang="en">
 <head>

  <!-- meta charset="utf-8" -->
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>Book In</title>

  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-ui-timepicker-addon/1.6.3/jquery-ui-timepicker-addon.css">
  <link href="/css/navbar-top-fixed.css" rel="stylesheet">

  <script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.0-rc.1/jquery-ui.min.js" integrity="sha256-mFypf4R+nyQVTrc8dBd0DKddGB5AedThU73sLmLWdc0=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="/js/jquery-ui-timepicker-addon.js"></script>  
  <script src="/js/jquery-ui-timepicker-addon-i18n.min.js"></script>
  <script src="/js/jquery-ui-sliderAccess.js"></script> 
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

  <script>   
   $(document).ready(function() {

    // Set entry time
    $("#TRINTIME_").val('<!--%TRINTIME_%-->').change();

    // Position cursor for initial output or field in error
    $("#<!--%focusField%-->").focus();

   });                      
  </script>                     
  <script>   

   // Process input form 
   function SbmBOOKIN() {  
    
    // Validate and process form input data
    $.post("/cgi-bin/bookin2.pgm", $("#BOOKIN").serialize(), function(data) {                            
      
     // Remove existing error from screen
     $("#errMsg").remove();    
       
     // Process error if any                      
     try { var error = JSON.parse(data); } 
     catch (e) { 
      window.location.href=data; 
     }
     if(error) {
      $("#" + error.field).after("<span id='errMsg' class='text-danger'>" + error.msg + "</span>"); // Output error text below field
      $("#" + error.field).focus(); // Position cursor on field in error                       
     }
  
    });                                                              
     
   }                                
  </script>
  <script type='text/javascript' >
   
   // ***********************************************************************************************
   // AJAX autocomplete for user name and pre-populate form fields when a drop-down entry is selected
   // ***********************************************************************************************
   $( function() {
    $( "input#USLSTNAM" ).autocomplete({
     source: function( request, response ) {
      $.ajax({
       url: "/cgi-bin/usrautocmp.pgm",
       type: 'post',
       dataType: "json",
       data: {
        search1: request.term
       },
       success: function(data){ 
        var mappedData = $.map(data, function(r) {
         return {
          label: r.USLSTNAM + " " + r.USFSTNAM + " " + r.USPSTCD,
          value: r
         }
        });
        response(mappedData);
       }, 
       error: function(err){
        alert("JSON error: " + data);
       }
      });
     },
     // Populate form fields for selected user
     select: function(event, ui) {
      $("input#USLSTNAM").val(ui.item.value.USLSTNAM); 
      $("input#USFSTNAM").val(ui.item.value.USFSTNAM); 
      $("input#USEMAIL").val(ui.item.value.USEMAIL);  
      $("input#USADR1").val(ui.item.value.USADR1);   
      $("input#USADR2").val(ui.item.value.USADR2);   
      $("input#USCITY").val(ui.item.value.USCITY);
      $("input#USPSTCD").val(ui.item.value.USPSTCD);  
      $("input#USTEL").val(ui.item.value.USTEL);  

      return false;  
     }
    });
   });
  </script>
  <script>  
  $(function() {  
    // Setup date / time picker
    $( "#TRINTIME_" ).datetimepicker({
     format:'dd/mm/yyyy hh:mm',
     dateFormat:'dd/mm/yy',
     timeFormat:'HH:mm'
    });      
  });  
  </script> 

 </head>
 <body>
<!--%navbarhtml%-->
  <main role="main" class="container">
   <div class="card-header bg-danger text-white">
    <h4 class="text-uppercase text-center">Book In</h4>
   </div>
   <div class="jumbotron">
    <form id="BOOKIN" name="BOOKIN" method='post' novalidate autocomplete="nope">
     <div class="form-group row">
      <label for="USLSTNAM" class="col-4 col-form-label">Surname</label> 
      <div class="col-8">
       <input id="USLSTNAM" name="USLSTNAM" placeholder="Surname / Christian Name" type="text" class="form-control w-75" required="required" autocomplete="nope" >
      </div>
     </div>
     <div class="form-group row">
      <label for="USFSTNAM" class="col-4 col-form-label">Christian</label> 
      <div class="col-8">
       <input id="USFSTNAM" name="USFSTNAM" placeholder="Christan Name" type="text" class="form-control w-75" required="required" autocomplete="nope" >
      </div>
     </div>
     <div class="form-group row">
      <label for="USADR1" class="col-4 col-form-label">Address Line 1</label> 
      <div class="col-8">
       <input id="USADR1" name="USADR1" placeholder="Street Address" type="text" class="form-control" required="required">
      </div>
     </div>
     <div class="form-group row">
      <label for="USADR2" class="col-4 col-form-label">Address Line 2</label> 
      <div class="col-8">
       <input id="USADR2" name="USADR2" placeholder="Address Line 2 " type="text" class="form-control" required="required">
      </div>
     </div>
     <div class="form-group row">
      <label for="USCITY" class="col-4 col-form-label">City</label> 
      <div class="col-8">
       <input id="USCITY" name="USCITY" placeholder="City" type="text" class="form-control" required="required">
      </div>
     </div>
     <div class="form-group row">
      <label for="USCOUNTY" class="col-4 col-form-label">County</label> 
      <div class="col-8">
       <select id="USCOUNTY" name="USCOUNTY" class="custom-select w-75">           
        <option></option>                                 
       </select>                                          
      </div>
     </div>
     <div class="form-group row">
      <label for="USPSTCD" class="col-4 col-form-label">Postcode</label> 
      <div class="col-8">
       <input id="USPSTCD" name="USPSTCD" placeholder="Postal / Zip Code" type="text" class="form-control w-50" required="required">
      </div>
     </div>
     <div class="form-group row">	
      <label for="USCOUNTRY" class="col-4 col-form-label">Country</label> 
      <div class="col-8">
       <select id="USCOUNTRY" name="USCOUNTRY" onchange="populateCounty('USCOUNTY','USCOUNTRY')" class="custom-select w-75">
        </select><script type="text/javascript">                                 
        //<![CDATA[                                                               
        initCountry('UK','','USCOUNTY','USCOUNTRY');                                           
        //]]>                                                                     
       </script>
      </div>
     </div>
     <div class="form-group row">
      <label for="USEMAIL" class="col-4 col-form-label">Email</label> 
      <div class="col-8">
       <input id="USEMAIL" name="USEMAIL" placeholder="Email" type="email" class="form-control" required="required">
      </div>
     </div>    
     <div class="form-group row">
      <label for="USTEL" class="col-4 col-form-label">Telephone</label> 
      <div class="col-8">
       <input id="USTEL" name="USTEL" placeholder="Telephone" type="text" class="form-control" required="required">
      </div>
     </div>
     <div class="form-group row">
      <label for="TRINTIME_" class="col-4 col-form-label">Entry date time</label> 
      <div class="col-8">
       <input id="TRINTIME_" name="TRINTIME_" type="text" class="TRINTIME_ form-control" required="required">
      </div>
     </div>
     <div class="form-group row">
      <div class="offset-4 col-8">
       <button name="btnSubmit" type="button" class="btn btn-primary" onclick="SbmBOOKIN();">Submit</button>
      </div>
     </div>
    </form>
   </div>
  </main>
 </body>
</html>

Each time the user enters a character in the input field the ajax method passes the input field data to the RPG program USRAUTOCMP which then returns a json array back to the ajax method which populates the drop down list and then pre-populates the rest of the html form fields. Below is the source for the RPG program that is called from the ajax routine. It uses SQL to fetch the table entries that match the the characters keyed in the input field. In this case it is a like statement with bracketed by percent signs which returns a fuzzy type search on the user table.

**free                                                                                                                                    
                                                                                                                                          
// Example IBM i Web App                                                                                                                  
// Demonstrate jQuery ajax user autocomplete for lastname and first name                                                                                          
// Novagem Ltd                                                                                                                            
                                                                                                                                          
ctl-opt dftactgrp(*NO) bnddir('DWABINDIR') option(*NOSHOWCPY:*NODEBUGIO);                                                                 
                                                                                                                                          
dcl-ds DWAUSR#DS extname('DWAUSR') prefix(#) end-ds;                                                                                      
dcl-ds SETTINGSDS extname('SETTINGS') end-ds;                                                                                             
                                                                                                                                          
/copy qcpysrc,prototypeb                                                                                                                  
/copy qcpysrc,usec                                                                                                                        
                                                                                                                                          
dcl-c keyVldLstNam 'DWAKEYVL';                                                                                                            
dcl-c NL X'15';                                                                                                                           
                                                                                                                                          
dcl-s #DWAUSR int(10);                                                                                                                    
dcl-s #t int(10);                                                                                                                         
dcl-s ajax char(65535);                                                                                                                   
dcl-s encryptionKey char(32);                                                                                                             
dcl-s lastTranReq char(1);                                                                                                                
dcl-s nbrVars int(10);                                                                                                                    
dcl-s savedQryStr varchar(65535);                                                                                                         
dcl-s search1 char(100);                                                                                                                  
dcl-s search2 char(100);                                                                                                                  
dcl-s TRINTIME timestamp;                                                                                                                 
dcl-s TRINTIME_ char(26);                                                                                                                 
                                                                                                                                          
dcl-ds ifsInDS;                                                                                                                           
 NoErrors ind;                                                                                                                            
 NameTooLong ind;                                                                                                                         
 NotAccessible ind;                                                                                                                       
 NoFilesUsable ind;                                                                                                                       
 DupSections ind;                                                                                                                         
 FileIsEmpty ind;                                                                                                                         
end-ds ifsInDS;                                                                                                                           
                                                                                                                                          
dcl-pr WriteWebData extproc('QtmhWrStout');                                                                                               
 writeData like(ajax) const;                                                                                                              
 length int(10) const;                                                                                                                    
 error like(qusec);                                                                                                                       
end-pr;                                                                                                                                   
                                                                                                                                          
dcl-pr GetVldLst char(100);                                                                                                               
 vldLstNam char(10) const;                                                                                                                
 vldLstLib char(10) const;                                                                                                                
 entId char(100) const;                                                                                                                   
end-pr;                                                                                                                                   
                                                                                                                                          
dcl-pr CHKUSER extpgm('CHKUSER');                                                                                                         
 returnAction char(30) const;                                                                                                             
 *N like(USBIZNAM);                                                                                                                       
 *N like(USZONE);                                                                                                                         
end-pr;                                                                                                                                   
                                                                                                                                          
dcl-ds DWAUSRDS occurs(10);                                                                                                               
 USID like(#USID);                                                                                                                        
 USBIZNAM like(#USBIZNAM);                                                                                                                
 USZONE like(#USZONE);                                                                                                                    
 USFSTNAM like(#USFSTNAM);                                                                                                                
 USLSTNAM like(#USLSTNAM);                                                                                                                
 USPSTCD like(#USPSTCD);                                                                                                                  
 USEMAIL like(#USEMAIL);                                                                                                                  
 USADR1 like(#USADR1);                                                                                                                    
 USADR2 like(#USADR2);                                                                                                                    
 USCITY like(#USCITY);                                                                                                                    
 USCOUNTY like(#USCOUNTY);                                                                                                                
 USCOUNTRY like(#USCOUNTRY);                                                                                                              
 USTEL like(#USTEL);                                                                                                                      
end-ds;                                                                                                                                   
                                                                                                                                          
exec sql SET OPTION SRTSEQ=*LANGIDSHR, DATFMT=*ISO;                                                                                       
exec sql SET TRANSACTION ISOLATION LEVEL NO COMMIT;                                                                                       
exec sql WHENEVER NOT FOUND CONTINUE;                                                                                                     
exec sql WHENEVER SQLERROR CONTINUE;                                                                                                      
                                                                                                                                          
exec sql SELECT * INTO :SETTINGSDS FROM SETTINGS WITH NC; // Get app settings                                                             
                                                                                                                                          
CHKUSER('RETURN_WITH_LOGIN_PAGE':USBIZNAM:USZONE); // Check user is logged in with valid cookie                                           
                                                                                                                                          
wrtsection('*fini'); // Output HTML buffer containg headers ready for AJAX repsonse to be sent                                            
                                                                                                                                          
// Get and set data encryption key                                                                                                        
encryptionKey=GetVldLst(keyVldLstNam:APPLIB:'DATA');                                                                                      
exec sql SET ENCRYPTION PASSWORD = :encryptionKey;                                                                                        
                                                                                                                                          
// Get StdIn                                                                                                                              
monitor;                                                                                                                                  
 nbrVars=ZhbGetInput(savedQryStr:QUSEC);                                                                                                  
on-error;                                                                                                                                 
endmon;                                                                                                                                   
if nbrVars > 0;                                                                                                                           
 search1        = ZhbGetVar('search1');                                                                                                   
 lastTranReq    = ZhbGetVar('lastTranReq');                                                                                               
endIf;                                                                                                                                    
                                                                                                                                          
// Split first and last name                                                                                                              
#t=%scan(' ':search1);                                                                                                                    
search2=%trim(%subst(search1:#t+1:100-#t));                                                                                               
search1='%'+%trim(%subst(search1:1:#t))+'%';                                                                                              
                                                                                                                                          
// Initialise fields                                                                                                                      
TRINTIME_=*BLANKS;                                                                                                                        
                                                                                                                                          
// Get users for search term, fuzzy search on user last name                                                                                                          
if search2=*BLANKS;                                                                                                                       
 exec sql                                                                                                                                 
  DECLARE C1 CURSOR FOR SELECT USID,                                                                                                      
                               USBIZNAM,                                                                                                  
                               USZONE,                                                                                                    
                DECRYPT_BINARY(USFSTNAM),                                                                                                 
                DECRYPT_BINARY(USLSTNAM),                                                                                                 
                DECRYPT_BINARY(USPSTCD),                                                                                                  
                               USEMAIL,                                                                                                   
                DECRYPT_BINARY(USADR1),                                                                                                   
                               USADR2,                                                                                                    
                               USCITY,                                                                                                    
                               USCOUNTY,                                                                                                  
                               USCOUNTRY,                                                                                                 
                DECRYPT_BINARY(USTEL)                                                                                                     
  FROM DWAUSR WHERE                                                                                                                       
  USBIZNAM = :USBIZNAM AND                                                                                                                
  USZONE = :USZONE AND                                                                                                                    
  CHAR(DECRYPT_BINARY(USLSTNAM)) LIKE TRIM(:search1)                                                                                      
  ORDER BY DECRYPT_BINARY(USLSTNAM) FOR READ ONLY WITH NC;                                                                                
 exec sql OPEN C1;                                                                                                                        
 exec sql FETCH NEXT FROM C1 FOR 10 ROWS INTO :DWAUSRDS;                                                                                  
 #DWAUSR=SQLERRD(3);                                                                                                                      
 exec sql CLOSE C1;                                                                                                                       
else;                                                                                                                                     
 search2='%'+%trim(search2)+'%';                                                                                                          
 exec sql                                                                                                                                 
  DECLARE C2 CURSOR FOR SELECT USID,                                                                                                      
                               USBIZNAM,                                                                                                  
                               USZONE,                                                                                                    
                DECRYPT_BINARY(USFSTNAM),                                                                                                 
                DECRYPT_BINARY(USLSTNAM),                                                                                                 
                DECRYPT_BINARY(USPSTCD),                                                                                                  
                               USEMAIL,                                                                                                   
                DECRYPT_BINARY(USADR1),                                                                                                   
                               USADR2,                                                                                                    
                               USCITY,                                                                                                    
                               USCOUNTY,                                                                                                  
                               USCOUNTRY,                                                                                                 
                DECRYPT_BINARY(USTEL)                                                                                                     
  FROM DWAUSR WHERE                                                                                                                       
  USBIZNAM = :USBIZNAM AND                                                                                                                
  USZONE = :USZONE AND                                                                                                                    
  CHAR(DECRYPT_BINARY(USLSTNAM)) LIKE TRIM(:search1) AND                                                                                  
  DECRYPT_BINARY(USFSTNAM) LIKE TRIM(:search2)                                                                                            
  ORDER BY DECRYPT_BINARY(USLSTNAM), DECRYPT_BINARY(USFSTNAM)                                                                             
  FOR READ ONLY WITH NC;                                                                                                                  
 exec sql OPEN C2;                                                                                                                        
 exec sql FETCH NEXT FROM C2 FOR 10 ROWS INTO :DWAUSRDS;                                                                                  
 #DWAUSR=SQLERRD(3);                                                                                                                      
 exec sql CLOSE C2;                                                                                                                       
endif;                                                                                                                                    
#DWAUSR=#DWAUSR-1;                                                                                                                        
                                                                                                                                          
// Write data back to AJAX caller                                                                                                         
if #DWAUSR>=0;                                                                                                                            
 exsr WriteJSON;                                                                                                                          
else;                                                                                                                                     
 // ajax='Content-Type: text/html'+NL+NL+'[]';                                                                                            
 ajax='[]';                                                                                                                               
 WriteWebData(%trim(ajax):%len(%trim(ajax)):QUSEC);                                                                                       
endif;                                                                                                                                    
                                                                                                                                          
// Exit program                                                                                                                           
*INLR=*ON;                                                                                                                                
return;                                                                                                                                   
                                                                                                                                          
// *************                                                                                                                          
begsr WriteJSON;                                                                                                                          
// *************                                                                                                                          
                                                                                                                                          
// Write AJAX JSON array back to caller                                                                                                              
// ajax='Content-Type: text/html'+NL+NL+'[';                                                                                              
ajax='[';                                                                                                                                 
for #t=1 to #DWAUSR;                                                                                                                      
 %occur(DWAUSRDS)=#t;                                                                                                                     
 if lastTranReq='Y';                                                                                                                      
  exsr getLastTran;                                                                                                                       
 endif;                                                                                                                                   
 ajax=%trim(ajax)+'{'+                                                                                                                    
  '"USLSTNAM":"'+%trim(USLSTNAM)+'",'+                                                                                                    
  '"USFSTNAM":"'+%trim(USFSTNAM)+'",'+                                                                                                    
  '"USEMAIL":"'+%trim(USEMAIL)+'",'+                                                                                                      
  '"USADR1":"'+%trim(USADR1)+'",'+                                                                                                        
  '"USADR2":"'+%trim(USADR2)+'",'+                                                                                                        
  '"USCITY":"'+%trim(USCITY)+'",'+                                                                                                        
  '"USCOUNTY":"'+%trim(USCOUNTY)+'",'+                                                                                                    
  '"USPSTCD":"'+%trim(USPSTCD)+'",'+                                                                                                      
  '"USCOUNTRY":"'+%trim(USCOUNTRY)+'",'+                                                                                                  
  '"USTEL":"'+%trim(USTEL)+'",'+                                                                                                          
  '"TRINTIME":"'+%trim(TRINTIME_)+'"},';                                                                                                  
endfor;                                                                                                                                   
%occur(DWAUSRDS)=#t;                                                                                                                      
if lastTranReq='Y';                                                                                                                       
 exsr getLastTran;                                                                                                                        
endif;                                                                                                                                    
ajax=%trim(ajax)+'{'+                                                                                                                     
  '"USLSTNAM":"'+%trim(USLSTNAM)+'",'+                                                                                                    
  '"USFSTNAM":"'+%trim(USFSTNAM)+'",'+                                                                                                    
  '"USEMAIL":"'+%trim(USEMAIL)+'",'+                                                                                                      
  '"USADR1":"'+%trim(USADR1)+'",'+                                                                                                        
  '"USADR2":"'+%trim(USADR2)+'",'+                                                                                                        
  '"USCITY":"'+%trim(USCITY)+'",'+                                                                                                        
  '"USCOUNTY":"'+%trim(USCOUNTY)+'",'+                                                                                                    
  '"USPSTCD":"'+%trim(USPSTCD)+'",'+                                                                                                      
  '"USCOUNTRY":"'+%trim(USCOUNTRY)+'",'+                                                                                                  
  '"USTEL":"'+%trim(USTEL)+'",'+                                                                                                          
  '"TRINTIME":"'+%trim(TRINTIME_)+'"}]';                                                                                                  
WriteWebData(%trim(ajax):%len(%trim(ajax)):QUSEC);                                                                                        
                                                                                                                                          
// ***                                                                                                                                    
endsr;                                                                                                                                    
// ***                                                                                                                                    
                                                                                                                                          
// ***************                                                                                                                        
begsr getLastTran;                                                                                                                        
// ***************                                                                                                                        
                                                                                                                                          
TRINTIME=Z'0001-01-01-00.00.00.000000';                                                                                                   
exec sql SELECT TRINTIME INTO :TRINTIME FROM DWATRN WHERE TRID = :USID ORDER BY TRADDTS DESC FETCH FIRST 1 ROW ONLY WITH NC;              
if TRINTIME>Z'0001-01-01-00.00.00.000000';                                                                                                
 TRINTIME_=%xlate('.':'/':%char(%date(TRINTIME):*EUR))+' '+ %xlate('.':':':%subst(%char(%time(TRINTIME)):1:5));                           
else;                                                                                                                                     
 TRINTIME_=*BLANKS;                                                                                                                       
endif;                                                                                                                                    
                                                                                                                                          
// ***                                                                                                                                    
endsr;                                                                                                                                    
// ***