IBM i Modernization - The User Interface
(Part 11)
This piece shows and explains a simple application that delves into the shift from the traditional display-file paradigm to the client-server paradigm, and delineates benefits for end-users.
Click on the following screen shot to try the application in a new tab or window.
This application presents a drop-down list of named entities. When you select one, the entity's address is retrieved from an IBM i database and is inserted into the table located just below the entity drop-down list.
Feel free to look up as many addresses as you want (hint: press and hold down the rightArrow and leftArrow keys to navigate the entity list and to stress test performance).
How Does It Work?
In this piece we get to see some ILE RPG code that responds to a request (for an address). But first, I should point out that the application's entry-point is a static HTML page which is simply served by the IBM i HTTP server.
You're not forced to "call" an application in order to see the initial page. This is more efficient. The initial page remains cached in the browser until users clear history. It's a good approach for applications that don't require authentication and authorization.
The body of the page is as follows:
<div class="hi-row w4 pd-5">Please Select a Name</div>
<select ht-container="locations" ht-jref="locations.json" ht-after="after_options()" id="ss" onChange="select_option(this)" class="pd-5" style="width:370"></select>
<br><br><br>
<div class="bg-1 w4 pd-5"><center>Addresses <a href="javascript:clear_list()" class="bg-1">Clear</a></center></div>
<div id="tc" class="scroll" style="height:270; width:388;">
<table id="tb" cellpadding="0" cellspacing="0" border="0"></table>
</div>
</body>
The body consists of the following parts:
- a <div> for the prompt "Please Select a Name".
- a <select> for the drop-down list.
- a <div> for the table heading "Addresses".
- a <div> for enabling scrolling the address list.
- a <table> for listing addresses returned by the IBM i server.
Populating the drop-down list:
As with examples in previous articles, I'm using a generic utility to automatically generate drop-down options from a static JSON array (locations.json) at runtime. Just specify the URL for the JSON Array using the ht-jref attribute.
Say you have a list of values that don't change often (i.e. the names of incorporated cities with a state). You shouldn't have to generate the list from a database each time the page is requested. Just generate the JSON file beforehand. The data is automatically incorporated into the app without writing server-side code.
The point is that developers may include static content on pages and not have to write (or run) code to dynamically generate it. Social media sites operate on this principle. The amount of static content may not be trivial - there are 600+ options in the drop-down list in this example. And you want it to load quickly.
Requesting an address:
The drop-down list has an event listener (onChange="select_option()") that triggers a request to the server when an option is selected. The select_option() function is defined as follows:
reqGet('/rdcaller/get_address.shtml?rwappid=IUI100&tloc100k=' + o.value);
}
reqGet() sends an a request to the server asking for "get_address" from an IBM i program named "IUI100", and passes a query-string parameter named "tloc100k" that is assigned the "value" which was selected from the combo box. That value is the key to the location (address).
What is Happening on the Server?
An IBM i Apache based HTTP server thread receives the request and invokes a plug-in that forwards the request to an IBM i program which then "calls" the program which was named in the URL (rwappid=IUI100; a program named IUI100 is called in this case).
Before we review the IUI100 program source code, I'd like to make a point that is intuitive to most web application developers, but traditional RPG programmers may struggle with. Web applications implement a request-response cycle; specifically ILE programs:
- Receive a request.
- Process it.
- Return a formatted response (if any). Repeat.
Every web application that I have written, deployed, debugged, and every one I'll illustrate in this series implements this request-response cycle.
I mention this because I've worked with a few RPG developers who have had trouble transitioning from the traditional green-screen program cycle:
- Write record(s) to a display file.
- Read record(s) from a display file.
- Perform some process based on what was returned from the read(s). Repeat.
Remember, clients initiate requests. Servers receive them and return formatted responses. That's the cycle. The only exception that I'm aware of is when servers push content to clients, which may implement web sockets.
Program Scope
Most interactive database-management programs should be scoped to handle more than one type of request (i.e. create, read, update, delete, etc.) as opposed to only handling one type of request per program. The latter leads to excessive resource utilization (open files, etc.) and code fragmentation. Most of the programs I write are scoped to handle a range of about 6-12 types of requests; some more, some less.
Programs may include sections to handle "initialization", "branching" based on requested "actions" (i.e. create, read, update, delete, etc.), and "termination" (clean-up; covered in a later article).
In the case of trivial applications (perhaps less than 5 types of requests) I prefer branching to subroutines in the same module to process requests and generate responses . Otherwise, I prefer calls to external service-program subprocedures. This keeps source members small, modular, and context-specific (more on this in later articles).
Program Content
RPG source members are less cluttered and more readable when we don't mix code belonging to other language environments. I don't mix HTML, XML, JSON, and JavaScript code with RPG, for example.
I store formatted text streams (HTML, CSS, XML, JSON, JavaScript) in external stream files as "templates" and use a few generic procedures to insert program data into templates while generating formatted output streams, which are returned to browsers.
In the next article (Part 12) I intend on discussing the idea of externalizing SQL. And sometime after that, I'd like to address the idea of externalizing record-level access. This type of ILE application consists largely of references to external procedure prototypes, external data structures, and calls to external procedures.
This program handles only one type of request. So it has minimal structure. We'll look at more complex examples in future articles.
Entity name and address pertaining to this application are defined as columns in a table named TLOC100P. This program implements an interface wherein an externally described data structure named "rw" is passed as an entry parameter, which will be delineated in a future article.
I should explain the seven (7) procedures which are called in this program.
- wtnOpen() - loads one or more "templates" into memory that may contain HTML, CSS, XML, JSON, JavaScript (any formatted text). The source of these templates resides in IFS stream files. These text streams may be peppered with "markers" which are delineated by double curly braces (i.e. {{ADDR}}. Markers are place-holders for program data that will be injected into the output-stream at runtime. A reference to the template is stored in a program variable.
- wtnSetInst() - tells the template engine which template to use for generating output streams. This may be necessary when multiple programs are called within a single IBM i JOB, wherein each program may open different templates.
- wtnQryGet() - retrieves the value of a query-string parameter by name. Query string parameters are passed on URL's. In this case the key to the DB table is passed.
- strToInt() - converts a character representation of a number to an integer data type (includes error logic).
- wtnRecSet() - tells the template engine which "record" in the template to prepare for program variable injection. Templates are divided into sections that I call "records".
- wtnFldSet() - injects program data into the template's output stream.
- wtnRecWrt() - instructs the template engine to output the record (stream).
The template used in this program contains:
ar("{{ADDR}}");
This program is injecting a JavaScript function call into the browser which inserts a table row which contains an entity name and address.
The RPG program source
Please review the complete RPG source.
//----------------------------------------------------------------- // file specifications //----------------------------------------------------------------- ftloc100p if e k disk //----------------------------------------------------------------- // procedure prototypes //----------------------------------------------------------------- /copy *libl/qrpglesrc,rdwtnapi#1 /copy *libl/qrpglesrc,rdstrapi#1 //----------------------------------------------------------------- // module level data //----------------------------------------------------------------- d rw e ds extname(rwpgmc) qualified d s1 s * inz(*null) d addr s 160a varying //----------------------------------------------------------------- // program entry //----------------------------------------------------------------- c *entry plist c parm rw //----------------------------------------------------------------- // retrieve reference to external template //----------------------------------------------------------------- /free if s1 = *null; s1 = wtnOpen('IUI100'); else; wtnSetInst(s1); endif; //----------------------------------------------------------------- // retrieve and return location address //----------------------------------------------------------------- tloc100k = strToInt(wtnQryGet('tloc100k')); chain tloc100k tloc100p; if %found(); addr = %trimr(name) + '<br>' + %trimr(gaddrstr); wtnRecSet('ADDRESS'); wtnFldSet('ADDR':addr); wtnRecWrt('ADDRESS'); endif; return; /end-free
Given the preceding explanation, I hope you can see what the program is doing. Calling this program 10 times consumes a total of 2 milliseconds of CPU time on an IBM i model 520 server, which underscores the efficiency of the interface.
Departure From the Display File Paradigm
This simple application demonstrates a departure from the display-file paradigm in a number of ways that I view as beneficial. Let's see if you agree.
- The application entry point is a static HTML page (it can be accessed without involving custom program logic or resources.
- A simple JavaScript utility merges content pulled from a JSON data object at runtime(again without involving custom program logic or resources).
- UI events can be handled on the client (i.e. clear all table entries without involving server resources; various key-press and mouse events too).
- Server requests are handled asynchronously (without locking the display).
- The size of the data stream output from the server is reduced.
- The request-response cycle is more flexible and supports a more event driven user interface than the display-file paradigm (i.e. write record(s) - read record(s)).
- The UI automatically adapts to various screen sizes and device types (desktop, laptop, tablet, & cell phones).