Article Index
Nathan M. Andelin   December 2016

IBM i Modernization - The User Interface

(Part 15)

This piece demonstrates a browser version of a single-page subfile and explains how it works. It was inspired by an example of a green-screen program that was delineated in this blog.

Please click on the following screen shot to try the application.


Page Navigation

The current page number is displayed, along with total number of pages. VCR-style buttons are used to implement page navigation.

You might see this type of navigation in certain web applications. It's more common in the green-screen paradigm. The use case may be applicable to database tables, or SQL views, or SQL result sets that hold many rows; You may not want to download all rows - all at once. But users may have occasion to "browse".

How Does it Work?

The RPG program, which responds to "page" requests is using my "cursor API" which "externalizes" SQL. Procedures are used to "define" a page size for an SQL result set, determine the total number of pages in the result set, navigate to pages (given a page number), and fetch the rows pertaining to that page.

In contrast to the green-screen paradigm, a single IBM i Job may be handling requests from many concurrent browsers at the same time. That won't matter. Navigation from page to page with this API doesn't rely on any end-user or cursor being in any given state in order to work.

The RPG program handles essentially only 2 requests from clients:

  1. Returns the total page count when the browser first "enters" the application (displayed on the screen).
  2. Returns the rows pertaining to any "page" that any client may request.

Clients in this case means any browser that may go to the static HTML page pertaining to the application. The page is composed of 3 tables:

  1. Page Status, and VCR Buttons.
  2. Subfile Column Headings.
  3. Subfile Rows.

Let's review the layout of each.


Page Status/VCR Buttons:

<table width="720" class="hi-row" cellpadding="5" cellspacing="0">
<tr height="50">
<td width="35"><button class="vcr" onClick="first()" title="First Page">|&lt;</button></td>
<td width="35"><button class="vcr" onClick="prior()" title="Prior Page">&lt;</button></td>
<td width="35"><button class="vcr" onClick="next()" title="Next Page">&gt;</button></td>
<td width="35"><button class="vcr" onClick="last()" title="Last Page">&gt;|</button></td>
<td>&nbsp;</td>
<td width="30" align="right">Page:</td>
<td width="25" align="right" id="cur"></td>
<td width="30" align="right">Of:</td>
<td width="25" id="pages"></td>
<td>&nbsp;</td>
<td width="120" align="center">Entities</td>
</tr>
</table>


Column Headings:

<table width="720" class="bg-1">
<tr>
<td width="40%">Name</td>
<td>Address</td>
</tr>
</table>


Subfile Rows:

<table width="720" id="tb" cellpadding="5" cellspacing="0"></table>

The "subfile" begins as an empty table. It's filled in with rows at runtime.


Java Script

A JavaScript function named goto() is invoked when a VCR button is clicked. It sends a request for a new page.

function goto(choice) {
  reqGet('/rdcaller/goto.shtml?rwappid=iui103&amp;page=' + choice);
  cur_page = choice;
  cur.innerHTML=choice.toString();
}

The response from the server invokes a JavaScript function named ar() repeatedly to add rows to the subfile.

function ar(name,addr) {
  var s = '<tr height=50><td width=40%>' + name + '</td>'
  + '<td>' + addr + '</td>';
  tb.insertRowBottom(s);
}

Click here to view the complete custom JavaScript for this application.


The RPG Program

The RPG program introduces a few new procedures pertaining to the "cursor API".

  1. csrSetPageSize(10) - Sets the page size to 10 rows.
  2. csrPages() - Returns the total number of pages in the result set.
  3. csrPage(csr_absolute:page_number) - Positions to the first row in the requested "page".

Program IUI103
      //-----------------------------------------------------------------
      // procedure prototypes
      //-----------------------------------------------------------------

      /copy *libl/qrpglesrc,rdstrapi#1
      /copy *libl/qrpglesrc,rdwtnapi#1
      /copy *libl/qrpglesrc,rdcsrapi#1

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

     d rw            e ds                  extname(rwpgmc) qualified

     d c1              s               *   inz(*null)
     d s1              s               *   inz(*null)
     d row_count       s             10i 0
     d page_number     s             10i 0
     d page_count      s             10i 0

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

     c     *entry        plist
     c                   parm                    rw

      /free

       //-----------------------------------------------------------------
       // set references to screen and cursor
       //-----------------------------------------------------------------

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

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

       select;
        when rw.action = 'INIT';
         exsr do_init;
        when rw.action = 'GOTO';
         exsr do_goto;
        when rw.action = 'PAGECOUNT';
         exsr do_page_count;
       endsl;

       return;

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

       begsr do_init;

        s1 = wtnOpen('IUI103');
        c1 = csrNew('TLOC100P':'NAME, GADDRSTR AS ADDR');

        csrSetOrder('NAME, CITY');
        csrSetPageSize(10);
        csrOpen();

        page_count = csrPages();

       endsr;

       //-----------------------------------------------------------------
       // goto the page requested
       //-----------------------------------------------------------------

       begsr do_goto;

        page_number = strToInt(wtnQryGet('page'));

        if page_number < 1 or page_number > page_count;
         page_number = 1;
        endif;

        csrPage(csr_absolute:page_number);

       //-----------------------------------------------------------------
       // fetch all rows on requested page and output to client
       //-----------------------------------------------------------------

        clear row_count;

        wtnRecWrt('CR');

        dow csrGoto(csr_next) and row_count < 10;
         wtnRecSet('AR');
         wtnFldSet('name':csrColStr('NAME'));
         wtnFldSet('addr':%trimr(csrColStr('ADDR')));
         wtnRecWrt('AR');
         row_count += 1;
        enddo;

        wtnRecWrt('FIN');

       endsr;

       //-----------------------------------------------------------------
       // return page_count to client
       //-----------------------------------------------------------------

       begsr do_page_count;

        wtnRecSet('PC');
        wtnFldSet('pages':%char(page_count));
        wtnRecWrt('PC');

       endsr;

      /end-free

Final Thoughts

As IBM i developers move to browser user interfaces, I think it helps to see how familiar design patterns such as subfile paging may be implemented. The blog referenced in the opening paragraph of this article provides a good basis for comparing a green-screen implementation vs. a browser client-server implementation. In many respects they're not dissimilar.

I hope this encourages green-screen developers to see the possibilities of browser user interfaces.

Externalizing SQL using my cursor API offers some nice features over record-level access and embedded SQL (once you become familiar with it).