Article Index
Nathan M. Andelin   December 2016

IBM i Modernization - The User Interface

(Part 19)

This article discusses the idea of filling in web forms and generating PDF documents based on the input. Click on the following screen shot to try it.


Background

A question arose on Midrange Lists in December 2016 asking about generating PDF documents from ILE RPG programs that could be filled in using Adobe Reader.

Rather than using Adobe Reader to fill in forms, I suggested generating PDFs from web forms. This article shows what that might entail.

Web forms are a good option for collecting input from people and at the same time producing PDF documents that cross-reference back to it - which is often better than extracting input from PDF documents (i.e. OCR or re-keyed by employees) that may have been filled in by customers.

What the Program Does

  1. Displays a web form and tab panel.
  2. Prompts users to fill in form values.
  3. Stores form values in a database keyed by session (document) ID.
  4. Generates and displays a PDF by merging a form template and input data.

The tab panel is paired with an inline frame that alternatively shows a web form or a PDF, depending on which tab is selected. Let's review the layout of the web form.

Form Template

The web form is based on an HTML template. The layout is as follows:


Form Template

<html>
<head>
<style>@import url("/rdweb/info3/iui19/fillin.css");</style>
</head>
<body>
<form action="post.shtml" method="post">
<img src="/rdweb/info3/iui19/australia.png" style="float:left; margin-right:20px;">
<center><h1>Official Certificate of Marriage</h1></center>
<center><h5>paragraph 50(1)(b) Marriage Act 1961</h5></center>
<p>Marriage was solemnised between the parties, details of whom are given below, on the <input value="{{day}}" type="text" autofocus size="5" maxlength="5" name="day" placeholder="1st"> day of <input value="{{month}}" type="text" size="10" maxlength="10" name="month" placeholder="January">, 20 <input value="{{year}}" type="text" size="2" maxlength="2" name="year" placeholder="17">, at <input value="{{location}}" type="text" size="82" maxlength="90" name="location" placeholder="Location">.</p>

<table cellpadding="2px" cellspacing="0px">
<tr>
<td align="center" height="50px" class="hi-row">Detail</td>
<td align="center" class="hi-row">Bridegroom</td>
<td align="center" class="hi-row">Bride</td>
</tr>
<tr>
<td class="even">Surname</td>
<td><input value="{{surname_g}}" type="text" size="50" maxlength="50" name="surname_g" placeholder="Surname"></td>
<td><input value="{{surname_b}}" type="text" size="50" maxlength="50" name="surname_b" placeholder="Surname"></td>
</tr>
<tr>
<td class="even">Other Names</td>
<td><input value="{{other_g}}" type="text" size="50" maxlength="50" name="other_g" placeholder="Other Names"></td>
<td><input value="{{other_b}}" type="text" size="50" maxlength="50" name="other_b" placeholder="Other Names"></td>
</tr>
<tr>
<td class="even">Usual occupation</td>
<td><input value="{{occupa_g}}" type="text" size="50" maxlength="50" name="occupa_g" placeholder="Usual occupation"></td>
<td><input value="{{occupa_b}}" type="text" size="50" maxlength="50" name="occupa_b" placeholder="Usual occupation"></td>
</tr>
<tr>
<td class="even">Usual residence</td>
<td><input value="{{reside_g}}" type="text" size="50" maxlength="50" name="reside_g" placeholder="Usual residence"></td>
<td><input value="{{reside_b}}" type="text" size="50" maxlength="50" name="reside_b" placeholder="Usual residence"></td>
</tr>
<tr>
<td class="even">Conjugal status</td>
<td><input value="{{conjugal_g}}" type="text" size="50" maxlength="50" name="conjugal_g" placeholder="conjugal status"></td>
<td><input value="{{conjugal_b}}" type="text" size="50" maxlength="50" name="conjugal_b" placeholder="conjugal status"></td>
</tr>
<tr>
<td class="even">Birthplace</td>
<td><input value="{{birthpl_g}}" type="text" size="50" maxlength="50" name="birthpl_g" placeholder="Birthplace"></td>
<td><input value="{{birthpl_b}}" type="text" size="50" maxlength="50" name="birthpl_b" placeholder="Birthplace"></td>
</tr>
<tr>
<td class="even">Father's full name</td>
<td><input value="{{father_g}}" type="text" size="50" maxlength="50" name="father_g" placeholder="Father's full name"></td>
<td><input value="{{father_b}}" type="text" size="50" maxlength="50" name="father_b" placeholder="Father's full name"></td>
</tr>
<tr>
<td class="even">Mother's maiden</td>
<td><input value="{{mother_g}}" type="text" size="50" maxlength="50" name="mother_g" placeholder="Mother's maiden name"></td>
<td><input value="{{mother_b}}" type="text" size="50" maxlength="50" name="mother_b" placeholder="Mother's maiden name"></td>
</tr>
<tr>
<td class="even" height="50px" style="font-weight:bold;">Signatures</td>
<td class="bb">&nbsp;</td>
<td class="bb">&nbsp;</td>
</tr>
</table>

<table cellpadding="2px" cellspacing="0px">
<tr>
<td colspan="3" class="hi-row">Witnesses to the Marriage:</td>
</tr>
<tr>
<td class="even">Full Names</td>
<td><input value="{{witness_1}}" type="text" size="50" maxlength="50" name="witness_1" placeholder="Witness full name"></td>
<td><input value="{{witness_2}}" type="text" size="50" maxlength="50" name="witness_2" placeholder="Witness full name"></td>
</tr>
<tr>
<td class="even" style="font-weight:bold;" height="50px">Signatures</td>
<td class="bb">&nbsp;</td>
<td class="bb">&nbsp;</td>
</tr>
</table>

<p>I, <input value="{{performer}}" type="text" size="50" maxlength="50" name="performer" placeholder="Performer full name"> certify that, on the date and at the place specified above, I duly solemnised marriage in accordance with the provisions of the Marriage Act 1961 between the parties specified above.</p>
<table cellpadding="2px" cellspacing="0px">
<tr>
<td>Signature</td>
<td width="60%" class="bb">&nbsp;</td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;<input value="{{date_perf}}" type="text" size="30px" maxlength="50px" name="date_perf" placeholder="Date"></td>
</tr>
</table>
<p></p>
<table cellpadding="2px" cellspacing="0px" width="100%">
<tr>
<td width="50%">Performer number <input value="{{author_no}}" type="text" size="20" maxlength="20" name="author_no" placeholder="Authorization Number">.</td>
<td width="50%" align="center">Marriage licence <input value="{{license}}" type="text" size="20" maxlength="20" name="license" placeholder="License Number">.</td> </tr>
</table></form>
</body>
</html>


The layout consists of:

  • An Image.
  • A web form.
  • Headers.
  • Tables.
  • Input elements.
  • Paragraphs.
  • Doubly curly brace markers (placeholders for data values).

Program IUI107

An RPG program named IUI107 handles the user interface and back-end processing. The code is as follows:


Program IUI107
      //-----------------------------------------------------------------
      // Marriage Certificates file
      //-----------------------------------------------------------------

     ffmar100   uf a e           k disk

      //-----------------------------------------------------------------
      // procedure prototypes
      //-----------------------------------------------------------------

      /copy *libl/qrpglesrc,rdstmapi#1
      /copy *libl/qrpglesrc,rdsesapi#1
      /copy *libl/qrpglesrc,rdwtnapi#1
      /copy *libl/qrpglesrc,rdrptapi#1

      //-----------------------------------------------------------------
      // module level data
      //-----------------------------------------------------------------

     d rw            e ds                  extname(rwpgmc) qualified

     d s1              s               *   inz(*null)
     d stm1            s               *   inz(*null)
     d fmt1            s               *   inz(*null)
     d htm_file        s            128a   varying
     d pdf_file        s            128a   varying

      //-----------------------------------------------------------------
      // program entry
      //-----------------------------------------------------------------

     c     *entry        plist
     c                   parm                    rw

      /free

       //-----------------------------------------------------------------
       // set reference to UI template
       //-----------------------------------------------------------------

       if s1 <> *null;
        wtnSetInst(s1);
       endif;

       //-----------------------------------------------------------------
       // branch to subroutines based on requested actions
       //-----------------------------------------------------------------

       select;
        when rw.action = 'INIT';
         exsr do_init;
        when rw.action = 'FILL';
         exsr do_fill;
        when rw.action = 'POST';
         exsr do_post;
       endsl;

       return;

       //-----------------------------------------------------------------
       // initialization
       //-----------------------------------------------------------------

       begsr do_init;

        stmInit();

        s1 = wtnOpen('IUI107');

        fmt1 = stmFmtLoad('IUI107A');

       endsr;

       //-----------------------------------------------------------------
       // update database record
       //-----------------------------------------------------------------

       begsr do_post;

        key = sesId();

        chain key fmar100r;

        if %found();
         day = %trimr(wtnFldGet('day'));
         month = %trimr(wtnFldGet('month'));
         year = %trimr(wtnFldGet('year'));
         location = %trimr(wtnFldGet('location'));
         surname_g = %trimr(wtnFldGet('surname_g'));
         surname_b = %trimr(wtnFldGet('surname_b'));
         other_g = %trimr(wtnFldGet('other_g'));
         other_b = %trimr(wtnFldGet('other_b'));
         occupa_g = %trimr(wtnFldGet('occupa_g'));
         occupa_b = %trimr(wtnFldGet('occupa_b'));
         reside_g = %trimr(wtnFldGet('reside_g'));
         reside_b = %trimr(wtnFldGet('reside_b'));
         conjugal_g = %trimr(wtnFldGet('conjugal_g'));
         conjugal_b = %trimr(wtnFldGet('conjugal_b'));
         birthpl_g = %trimr(wtnFldGet('birthpl_g'));
         birthpl_b = %trimr(wtnFldGet('birthpl_b'));
         father_g = %trimr(wtnFldGet('father_g'));
         father_b = %trimr(wtnFldGet('father_b'));
         mother_g = %trimr(wtnFldGet('mother_g'));
         mother_b = %trimr(wtnFldGet('mother_b'));
         witness_1 = %trimr(wtnFldGet('witness_1'));
         witness_2 = %trimr(wtnFldGet('witness_2'));
         performer = %trimr(wtnFldGet('performer'));
         date_perf = %trimr(wtnFldGet('date_perf'));
         author_no = %trimr(wtnFldGet('author_no'));
         license = %trimr(wtnFldGet('license'));
         update fmar100r;
        endif;

       //-----------------------------------------------------------------
       // generate names for HTML and PDF stream files based on session ID
       //-----------------------------------------------------------------

        htm_file = '/rdweb/info3/iui19/' + sesId() + '.html';
        pdf_file = '/rdweb/info3/iui19/' + sesId() + '.pdf';

       //-----------------------------------------------------------------
       // generate HTML stream file
       //-----------------------------------------------------------------

        stmFmtInst(fmt1);

        stm1 = stmFileOpen(htm_file);

        stmRecSet('FILLIN');
        stmVarSet('day':day);
        stmVarSet('month':month);
        stmVarSet('year':%trimr(year));
        stmVarSet('location':location);
        stmVarSet('surname_g':surname_g);
        stmVarSet('surname_b':surname_b);
        stmVarSet('other_g':other_g);
        stmVarSet('other_b':other_b);
        stmVarSet('occupa_g':occupa_g);
        stmVarSet('occupa_b':occupa_b);
        stmVarSet('reside_g':reside_g);
        stmVarSet('reside_b':reside_b);
        stmVarSet('conjugal_g':conjugal_g);
        stmVarSet('conjugal_b':conjugal_b);
        stmVarSet('birthpl_g':birthpl_g);
        stmVarSet('birthpl_b':birthpl_b);
        stmVarSet('father_g':father_g);
        stmVarSet('father_b':father_b);
        stmVarSet('mother_g':mother_g);
        stmVarSet('mother_b':mother_b);
        stmVarSet('witness_1':witness_1);
        stmVarSet('witness_2':witness_2);
        stmVarSet('performer':performer);
        stmVarSet('date_perf':date_perf);
        stmVarSet('author_no':author_no);
        stmVarSet('license':license);
        stmVarSet('doc_ref':sesId());
        stmRecWrt('FILLIN');

       //-----------------------------------------------------------------
       // transform HTML file to PDF - stream PDF to browser
       //-----------------------------------------------------------------

        stmToPDF(60);

        wtnHdrSet('content-type':'application/pdf');

        if wtnQryGet('attach') = 'y';
         wtnHdrSet('content-disposition':
                   'attachment; filename=marriage.pdf');
        endif;

        rptToWeb(pdf_file:*off);

       //-----------------------------------------------------------------
       // close and delete temporary stream files
       //-----------------------------------------------------------------

        rptRmvFile(pdf_file);

        stmFileClose(*off);

       endsr;

       //-----------------------------------------------------------------
       // show the fill-in form
       //-----------------------------------------------------------------

       begsr do_fill;

        clear fmar100r;

        key = sesId();

        chain(n) key fmar100r;

        if not %found();
         write fmar100r;
        endif;

       //-----------------------------------------------------------------
       // fill in the form from the DB record
       //-----------------------------------------------------------------

        wtnRecSet('FILLIN');
        wtnFldSet('day':day);
        wtnFldSet('month':month);
        wtnFldSet('year':%trimr(year));
        wtnFldSet('location':location);
        wtnFldSet('surname_g':surname_g);
        wtnFldSet('surname_b':surname_b);
        wtnFldSet('other_g':other_g);
        wtnFldSet('other_b':other_b);
        wtnFldSet('occupa_g':occupa_g);
        wtnFldSet('occupa_b':occupa_b);
        wtnFldSet('reside_g':reside_g);
        wtnFldSet('reside_b':reside_b);
        wtnFldSet('conjugal_g':conjugal_g);
        wtnFldSet('conjugal_b':conjugal_b);
        wtnFldSet('birthpl_g':birthpl_g);
        wtnFldSet('birthpl_b':birthpl_b);
        wtnFldSet('father_g':father_g);
        wtnFldSet('father_b':father_b);
        wtnFldSet('mother_g':mother_g);
        wtnFldSet('mother_b':mother_b);
        wtnFldSet('witness_1':witness_1);
        wtnFldSet('witness_2':witness_2);
        wtnFldSet('performer':performer);

        wtnFldSet('date_perf':date_perf);
        wtnFldSet('author_no':author_no);
        wtnFldSet('license':license);
        wtnRecWrt('FILLIN');

       endsr;

      /end-free

High-level explanation of program IUI107:

  • Web form values are stored in a DB table named FMAR100, which is keyed by the user's session ID.
  • At program initialization, two versions of the web form are read into memory (the version used to prompt for input contains <input> elements, while the one used to generate the PDF doesn't.
  • A subroutine named do_fill is used to generate the web form and stream it to the browser.
  • A subroutine named do_post stores input values in the FMAR100 table, generates a PDF, and streams it to the browser.
  • PDF generation entails output to a couple of temporary stream files that are named based on the user's session ID.
  • Procedure names beginning with "wtn" are used to generate browser output.
  • Procedure names beginning with "stm" are used to generate IFS stream-file output.
  • Procedure stmToPDF() converts the HTML stream file to PDF.

Wrapping Up

With some basic skills in HTML markup and ILE RPG you can create web forms and generate PDF documents based on input received from users.

Programs like this could be adapted to present partially filled in web forms, prompt only for input required to complete the forms, notify people who are parties to the document and provide links for them, etc.

Follow Up

I posted a reference to this article in a discussion on Midrange Lists, which led to a perplexing exchange with Richard Schoen of Help Systems. It began at the end of December 2016 and carried into the first week in January 2017. He initially labeled my content as conceptual, synthetic, incomplete, and hardly useful. He repeatedly cajoled me to publish the black-box code behind the PDF generation - to put it in the public domain.

His sideswipes seemed even more incongruous after I took an opportunity to view and read numerous "resources" available at the Help Systems web site in regards to document management. The content there IS abstract, embellished, riddled with promises and assurances, and leaves viewer/readers hanging with the intent of baiting them to discuss their interests with sales staff.

On a positive note, Mr. Schoen's criticisms did make me reflect more deeply about the content I had published, including pros and cons.

On the pro side, I think there is a valid message for organizations that design interactive PDF forms using Adobe Acrobat DC and publish them for people (i.e. customers) to fill out by using Adobe Reader. Although ubiquitous, that paradigm does not support placeholder prompts in input elements, validation, or appropriate feedback. The data keyed into Adobe Reader is typically re-keyed by employees in order to collect it into line-of-business systems. And there's typically no way to pre-fill form elements with data that may exist in a database.

On the con side, filling in web forms using hand-held devices (i.e. smart phones and tablets) is tedious when forms are designed for printing on 8.5 X 11.0 inch paper. The virtual keyboards on the devices overlay too much of the viewing area. This problem applies to web forms as well as Adobe Reader forms. On the whole, I would recommend designing web forms that adapt appropriately to hand-held devices (see my articles about "responsive design"), and use a separate HTML template for generating the PDF version.