Back in my early days of the IBM midrange platforms, RPG was known as primarily being a Report Program Generator (although the "Generator" part was a little over-optimistic!). This was back when writing interactive programs with RPG was a black art of shoehorning the RPG cycle into something in which it clearly did not fit.

But with enough effort, some slamming your head into the wall, and a little time with a Shelly Cashman textbook or two, it was possible back in the day to write serviceable interactive RPG programs. That code was never going to win a Most Beautiful Code Contest, but it did the job. After spreading my wings and learning other languages, I realized the value of indented, expressive, and obvious code -- code written as much for eyeballs as for a compiler. Alas, even with the advent of the mid-90s era of ILE RPG, RPG persisted as a compile-only language very unfriendly to both eyeballs and quick comprehension. I’ll go out on a huge limb here and flatly state that our old RPG, regardless of how good a coder you are, was simply impervious to being expressive and obvious.

I am late to the party, but the other day I started noodling around with the 7.1 Technical Refresh of ILE RPG, the one that provides full "free-form" RPG capabilities. In very short order, I was outrageously impressed. Nits remain to be picked with several syntactical aspects of the language, but for the first time ever, I am writing RPG that is expressive and comprehensible. If you write RPG for anything serious, you owe it to yourself to dig into the 7.1 Technical Refresh RPG. It will forever change how you write RPG. While the refresh doesn't add any great new functional changes, RPG programming power previously straight-jacketed by column reliance quickly comes shining through.

This article features an example RPG program I wrote with the TR 7.1 RPG free-format syntax. I'm a rusty RPG coder to be sure, so if you see anything glaringly stupid (for which I have a knack!), please let me know. I cut a few corners to keep the code short, and at 191 lines I'm probably pressing my luck. But it's 2015, and articles like this are no longer constrained by the printed page, so bear with me. I won't cover every line of the RPG in detail in this article, but all of the project's source, including the RPG, is available here for easy download for further inspection (you don't need a Git client to download the code—look for the "Download Zip" button).

Making it mobile

I thought ILE RPG's spanking new syntax was worthy of a spanking new user interface, so the program I wrote is an ASNA Mobile RPG (MR) app. MR apps are HTML5 apps primarily intended for smartphones and tablets, but they also run on desktop browsers. They feature performant and secure IBM i database connectivity. The app presented here is a simple little customer CRUD app with, for a little more sizzle, a map of the customer's address.

The intent of this article is two fold:

  1. To show TR 7.1 ILE RPG in action. A full RPG program shows more context and capabilities than just a few snippets of RPG here and there.
  2. To show an RPG model that provides a (use your imagination here) work-with panel-like UI for a mobile app. Mobile idioms are vastly different than what we used to use in the green-screen, but as you'll see, MR abstracts away mobile uniqueness and empowers RPG coders to create IBM i mobile apps with nothing but RPG.

The ASNA Mobile RPG mobile display file

Before digging into the ILE RPG for this mobile app, let's take a quick tour of MR's mobile display file. This isn't really an article about ASNA Mobile RPG (MR) as much as it is about ILE RPG (I wrote an article a while back that goes into more Mobile RPG detail that can be read here). While there are a few things to understand about Mobile RPG, thanks to IBM's Open Access API, there is less than you think. Mobile RPG provides a Windows-based mobile UI designer. It lets you create mobile display files with record formats, just like old-school display files. MR enforces the traditional indicator-driven "contract" between an RPG program and its display file.

The three steps to creating and running an ASNA Mobile RPG app are shown in the figure below. 

Figure 1: Creating and running an MR mobile app requires three steps. 

Figure 1 above summarizes the three steps for creating and running an MR mobile app.

A little detail for each step follows:

  1. Create the mobile UI. MR provides a Windows-based designer for creating the mobile UI. It includes all of the user interface elements you'd expect for a mobile user interface (including text boxes, buttons, navigational bars, map, data charts, data list, signature capture, images, and many others). Once you've created the mobile display file, you export it through MR as a traditional display file object on the IBM i. This display file will never be seen by eyeballs; rather, it exists purely to compile an associated RPG program. During this export step you can optionally save the exported display file source, which is mostly useful for learning purposes to see how all of MR's user interface elements map to RPG idioms.

  2. Write an RPG program. This program provides the logic and file IO for your mobile app. It is compiled against the display file object created by the export process in Step 1.

  3. Run the mobile app. The MR mobile app is an HTML5 browser-based mobile app that runs in mobile (and desktop) browsers. At runtime, IBM's Open Access API intercepts this RPG program's display file data and routes it to and from the MR mobile user interface. That the traditional display file is swapped out for the mobile display file at runtime is completely transparent to the RPG program.

Let's take a closer look at the example app's three display panels (which are actually surfaced as display file record formats).

The Mobile List Panel

The initial screen for this example app is the list panel shown in Figure 2A.

 

Figure 2A: The mobile list display looks like this.

The initial screen for the list panel's format name is CUSTLIST. It is scrollable, and a user can select a row one of two ways: the user can either tap on the customer's name or tap on the chevron on the right end of the row. This list effectively presents the work-with panel model for mobile devices.

Because this is an example of an IBM i app, it assumes it might be using an input file with a great deal of records, thus the "Next" button to get the next page of rows. For this example, six rows are shown and the display isn't scrollable. However, the number of rows displayed is controlled by a constant in the underlying RPG program. Unlike a tethered green-screen, apps like this need to be written with performance in mind. The user doesn't want to wait for hundreds of rows to load (but it might also be reasonable to load more than I am loading here).

The Mobile RPG designer is used to map function keys to button (and other UI element) taps. In this panel, the F3 key is mapped to the "End" button and the F5 key is mapped to the "Next" button. The record format name for this list panel is CUSTINFO.

This customer list is populated in the ILE RPG as a very simple subfile. This subfile has the following configuration assignments:

Description Value
Subfile name CSTSBF
Subfile controller name CSTCTRL
Clear subfile indicator 99
Main text field name CSTXT
Main text field length 70
Secondary text field name CSTDTL
Secondary text field length      50
Selection field name CSTSEL
Value field CSTVAL
Value field length 30

 

The CSTSEL field is a single-character field that is populated implicitly by MR with a '1' when a user taps the row. RPG's READC operation is then used to identify the row selected. The CSTVAL field is a "hidden" field used to stash hidden data for a row. In this case, the customer number is stored in this field (so that when a row is selected, via READC, the customer number is available).

The Update Panel

The mobile update panel is shown in Figure 2B.

 

Figure 2B: The mobile update panel shows a customer's information.

The update panel, which is format CUSTINFO, is shown by tapping on a customer name from the list panel. The user can change any of the fields presented and tap OK or tap Cancel. Tapping either button returns the user to the list panel.

In this panel, the F2 key is mapped to the "Back" button and to the “Cancel” button, the F8 key is mapped to the "OK" button.

The Map Panel

The map panel, shown below, provides geo-location functionality.

Figure 2C: The map panel shows a customer's location.

The map panel, which is format CUSTMAP, is displayed for a customer when the chevron is tapped on the list panel. With an ILE RPG program providing this mobile app's logic, Mobile RPG needed an idiomatic RPG way of providing the addresses for a map. In this case, a single address is mapped, but the Mobile RPG map control can map many addresses.

The map control is fed addresses through a simple RPG subfile. This subfile has the following configuration assignments:

Description Value
Subfile name MAPSBF
Subfile controller name       MAPCTRL
Clear subfile indicator 99
Address field name Location
Main text field length 60

 

Clicking back returns the user to the list. After exporting the display file these three record formats comprise, the residual DDS display file source member is shown (Figure 3 below). Take a look at it and notice that its fields and subfiles resolve to those explained above.

0001  A                                      DSPSIZ(27 132 *DS4)
0002  A                                      INDARA
0003   *--------------------------------------------------------
0004  A          R CUSTLIST
0005  A                                      OVERLAY
0006  A            MOREROWS      10A  O  1  2
0007   *--------------------------------------------------------
0008  A          R CUSTINFO
0009  A                                      OVERLAY
0010  A            CUSTKEY       50A  O  1  2
0011  A            CMNAME        40A  B  2  1
0012  A            CMADDR1       35A  B  2 43
0013  A            CMCITY        30A  B  3  1
0014  A            CMSTATE        2A  B  3 33
0015  A            CMPOSTCODE    10A  B  3 37
0016   *--------------------------------------------------------
0017  A          R CUSTMAP
0018  A                                      OVERLAY
0019  A            CMNAME        40A  O  1  2
0020  A            CSZ           60A  O  2  1
0021   *--------------------------------------------------------
0022  A          R CSTSBF                    SFL
0023  A            CSTSEL         1A  H
0024  A            CSTTXT        70A  O  1  5
0025  A            CSTVAL        30A  H
0026  A            CSTDTL        50A  O  3  1
0027   *--------------------------------------------------------
0028  A          R CSTCTRL                   SFLCTL(CSTSBF)
0029  A                                      SFLSIZ(2)
0030  A                                      SFLPAG(1)
0031  A N99                                  SFLDSP
0032  A N99                                  SFLDSPCTL
0033  A  99                                  SFLCLR
0034  A                                      OVERLAY
0035   *--------------------------------------------------------
0036  A          R MAPSBF                    SFL
0037  A            LOCATION      60A  O  1  2
0038   *--------------------------------------------------------
0039  A          R MAPCTRL                   SFLCTL(MAPSBF) 
0040  A                                      SFLSIZ(2)
0041  A                                      SFLPAG(1)
0042  A N99                                  SFLDSP
0043  A N99                                  SFLDSPCTL
0044  A  99                                  SFLCLR
0045  A                                      OVERLAY

Figure 3: This DDS display file source is generated when MR mobile UI is exported as a display file object.

Think of MR's exported display file object as a proxy for the mobile UI. At compile time, the RPG program will reference this proxy display file, but at runtime, through Open Access, the MR mobile display file is used. Once again, the proxy IBM i display file is never seen by human eyeballs.

With the MR mobile display file and its IBM i proxy display file created, let's turn our attention to some of the interesting parts of the ILE RPG source code. Because MR so well abstracts away the notion that we're really creating a mobile app, the RPG is written (and can be examined) without regard for it really being a mobile app. Just think of it as powering three simple traditional display file formats (which, by the way, is exactly what it's doing).

The ILE RPG source

The full RPG source is shown below in Figure 4A and the single /COPY member it includes is shown in Figure 4B. A brief code narrative follows these two listings.

0001  Ctl-Opt Option(*srcstmt) Dftactgrp(*No) ActGrp('rpmobile');
0002 
0003  Dcl-F smplst WORKSTN Infds(infds)
0004                       Handler('MOBILERPG')
0005                       SFile(CSTSBF:CSTRRN)
0006                       SFile(MAPSBF:CSTRRN);
0007 
0008  Dcl-F CustomerL2 Disk(*ext) Usage(*Input) Keyed
0009                   Rename(RCMMASTER:RCUSTL2);
0010 
0011  Dcl-F CustomerL1 Disk(*ext) Usage(*Update) Keyed
0012                   Rename(RCMMASTER:RCUSTL1);
0013 
0014  Dcl-DS CustL2Key LikeRec(RCUSTL2:*Key);
0015  Dcl-DS TopRow    LikeRec(RCUSTL2:*Key);
0016 
0017  Dcl-S CstRRN   Zoned(4:0);
0018  Dcl-C MAXROWS  Const(6);
0019 
0020  /copy RPMRDATA/QRPGLESRC,KEYMAPF
0021 
0022  Dcl-DS Action Qualified;
0023      Exit           Char(1) Inz(F03);
0024      Cancel         Char(1) Inz(F02);
0025      Back           Char(1) Inz(F02);
0026      ItemTapped     Char(1) Inz(F01);
0027      ChevronTapped  Char(1) Inz(F04);
0028      More           Char(1) Inz(F05);
0029      Backward       Char(1) Inz(F06);
0030      Find           Char(1) Inz(F07);
0031      OK             Char(1) Inz(F08);
0032      Display        Char(1) Inz(F09);
0033      Remove         Char(1) Inz(F10);
0034  End-DS;
0035 
0036  SetLL *LoVal CustomerL2;
0037  LoadCstLst();
0038  ExFmt CUSTLIST;
0039 
0040  Dow ActionRequest <> Action.Exit;
0041      Select;
0042          When CurrentFormat = 'CUSTLIST';
0043               Select;
0044                   When ActionRequest = Action.ItemTapped;
0045                       ReadC CSTSBF;
0046                       If ReadCustById(%INT(CSTVAL));
0047                          // Customer not found.
0048                       EndIf;
0049                       ExFmt CUSTINFO;
0050 
0051                   When ActionRequest = Action.ChevronTapped;
0052                       ReadC CSTSBF;
0053                       If ReadCustById(%INT(CSTVAL));
0054                          // Customer not found.
0055                       EndIf;
0056                       CSZ = %TRIM(CMADDR1) + ', ' +
0057                             %TRIM(CMCITY) + ', ' +
0058                             CMState;
0059                       ShowMap(CSZ);
0060                       ExFmt CUSTMAP;
0061 
0062                    When ActionRequest = Action.More;
0063                       LoadCstLst();
0064                       ExFmt CUSTLIST;
0065 
0066                    Other;
0067                       ExFmt CUSTLIST;
0068               EndSl;
0069 
0070         When CurrentFormat = 'CUSTINFO';
0071              Select;
0072                  When ActionRequest = Action.OK;
0073                      UpdateCust();
0074                      RefreshCustList(CMName:
0075                                      CMCustNo);
0076                      ExFmt CUSTLIST;
0077 
0078                  When ActionRequest = Action.Cancel;
0079                      RefreshCustList(TopRow.CMName:
0080                                      TopRow.CMCustNo);
0081                      ExFmt CUSTLIST;
0082 
0083                  When ActionRequest = Action.Back;
0084                      SetLL *LoVal CustomerL2;
0085                      LoadCstLst();
0086                      ExFmt CUSTLIST;
0087 
0088                  Other;
0089                      SetLL *LoVal CustomerL2;
0090                      LoadCstLst();
0091                      ExFmt CUSTLIST;
0092              EndSl;
0093 
0094         When CurrentFormat = 'CUSTMAP';
0095              Select;
0096                  When ActionRequest = Action.OK OR
0097                       ActionRequest = Action.Back;
0098                      RefreshCustList(CMName:
0099                                      CMCustNo);
0100                      ExFmt CUSTLIST;
0101              EndSl;
0102 
0103      EndSl;
0104  EndDo;
0105 
0106  *InLR = *On;
0107  Return;
0108 
0109  Dcl-Proc UpdateCust;
0110      Update RCustL1;
0111  End-Proc;
0112 
0113  Dcl-Proc LoadCstLst;
0114      Dcl-S RowCount Packed(12:0);
0115 
0116      *IN99 = *On;
0117      Write CSTCTRL;
0118      CstRRN = 0;
0119      RowCount = 0;
0120 
0121      DoW (RowCount < MAXROWS);
0122          Read CustomerL2;
0123          If NOT %EOF;
0124              If RowCount = 0;
0125                  TopRow.CMName = CMName;
0126                  TopRow.CMCustNo = CMCustNo;
0127              EndIf;
0128              RowCount = RowCount + 1;
0129              CstRRN = CstRRN + 1;
0130              CSTSEL = '0';
0131              CSTTXT = CMName;
0132              CSTDTL = %Trim(CMCITY) + ', ' + CMSTATE;
0133              CSTVAL = %CHAR(CMCustNO);
0134              Write CSTSBF;
0135          Else;
0136              Leave;
0137          EndIf;
0138      EndDo;
0139 
0140      *In99 = *Off;
0141      Write CSTCTRL;
0142      PeekForEOF();
0143  End-Proc;
0144 
0145  Dcl-Proc PeekForEOF;
0146      Read CustomerL2;
0147      If NOT %EOF;
0148         Eval MOREROWS = 'More...';
0149         ReadP CustomerL2;
0150      Else;
0151         Eval MOREROWS = 'No More';
0152         SETLL *LOVAL CustomerL2;
0153      EndIf;
0154  End-Proc;
0155 
0156  Dcl-Proc ReadCustById;
0157      Dcl-Pi *N Ind;
0158          CustNo Packed(9:0) CONST;
0159      End-Pi;
0160 
0161      Chain CustNo CustomerL1;
0162      Return %EOF();
0163  End-Proc;
0164 
0165  Dcl-Proc RefreshCustList;
0166      Dcl-Pi *N;
0167          Name Like(CMName);
0168          CustNo Like(CMCustNo);
0169      End-Pi;
0170 
0171      CustL2Key.CMName = Name;
0172      CustL2Key.CMCustNo = CustNo;
0173      SetLL %KDS(CustL2Key) CustomerL2;
0174      LoadCstLst();
0175  End-Proc;
0176 
0177  Dcl-Proc ShowMap;
0178      Dcl-Pi *N;
0179          Address Char(60);
0180      End-Pi;
0181 
0182      *IN99 = *On;
0183      Write MAPCTRL;
0184 
0185      CstRRN = 1;
0186      Location = Address;
0187      Write MAPSBF;
0188 
0189      *In99 = *Off;
0190      Write MAPCTRL;
0191  End-Proc;

Figure 4A. The full ILE RPG source listing.

Narrative for several chunks of Figure 4's RPG follows

Line 1

Define compile time options for the program.

Lines 2 – 12

Declare the workstation file and two disk files (CustomerL2 is keyed by customer name and number, and CustomerL1 is keyed by customer number only). The workstation file continuation on line 4 registers this RPG program with the Open Access API. This is the RPG program’s only nod to it not using a traditional display file.

Lines 14 – 15

A very cool RPG feature, these lines provide fully qualified key definition data structures for the two disk files. These one-line shortcuts serve as KLIST alternatives (albeit vastly more programmer-friendly).

Lines 17 – 18

These are two global variables for the program (using local variables in procedures substantially reduces global variable dependence).

Line 20

Include the INFDS copy member (from Figure 4B below) and its constants. This enables the very old-school trick of interrogating the hex value of INFDS’s position 369 to determine what function (or special) key was pressed. Positions 260-270 also define and interrogate the last-written display file format as “CurrentFormat”. This field is instrumental in guiding this program’s logic. (As an aside, I was amazed to realize that, with Mobile RPG and IBM’s Open Access API, this INFDS data structure feature works just fine! The INFDS data flows naturally out of, and back into, the RPG program through Open Access.)

Lines 22 – 34

Declare a fully qualified data structure named “Action” that maps the function key constants (declared in Figure 4B’s copy member) to semantic actions. This lets the programmer think not about function key presses, but about user actions selected (such as a list’s chevron having been tapped). My longer-term plan for this data structure is to map reusable and generic “actions” to function keys and then move that definition to the Figure 4B copy member. Note that a couple of these actions are defined multiple times. This is for those times when a “Back” action is different from a “Cancel” action (for example) semantically but both are implemented with an F2 key press. I was surprised to find what appeared to be keyword-type conflicts with some field names in RPG data structures. For example, the ILE RPG does not at all tolerate an action being named “Next,” to my great disappointment. This is why there is a “More” action; I’d sure rather have a “Next” action.

Lines 36 – 38

Here's the mainline code to bootstrap the program. These three lines populate the initial customer list. There aren’t any subroutines in this program; those have been dispatched and replaced with procedures. Thanks to TR 7.1’s free-format procedure declaration, you no longer need to bleed to death to remember how to declare a procedure and its interface.

Lines 40 – 104

This is the main loop of this program. This loop is wayyyy too long to be considered good code in anyone’s book. I sacrificed code quality here for slightly shorter source listing and less eyeball hopping required to comprehend the code.

Lines 42 – 49

This is the point in the narrative, were I explaining this to my father, where he’d throw up his hands and say, “Enough about the labor pains! Show me the baby!” This is where the fully qualified data structures, mapping function keys to semantic actions, and RPG automatically tracking the most recent record format really pay off. While it’s really a stretch, this RPG code almost presents itself as three controllers for three routes (the three record formats), each having at least one action. You can easily see that, for the CUSTLIST format, the program allows three actions: ItemTapped, ChevronTapped, and More. These three actions (and the Exit action) are the actions available on the CUSTLIST panel.

Lines 46 – 46

You didn’t get to write RPG like line 46 when Saturday Night Fever was number 1 at the box office! This line calls a procedure that accepts a single integer argument and returns a Boolean value which indicates if a record was read. I’ve left the error-handling as an exercise to the reader (That’s usually what writers say when they don’t know how to do something! In this case, I promise, it’s to minimize the code sample).

Lines 74 – 75

When the CUSTINFO panel is displayed (Figure 4B) and the OK button is tapped, the program needs to return to the customer list (Figure 4A), but reposition it to the customer just updated. RefreshCustList is a procedure that does just that. It is passed the customer name and number for which to position the list.

Lines 113 – 143

The LoadCstLst procedure loads up to the constant MAXROWS value of rows into the subfile displayed in Figure 4A. Note the variable RowCount is declared locally (so therefore it isn’t available to the rest of the program) and don’t forget that disk files can also be declared locally to procedures. Otherwise there isn’t much RPG here of any major interest, but it is interesting to note that this journeyman RPG is actually populating a list to be displayed on a smartphone.

Lines 156 – 163

The ReadCustById reads a customer record from the CustomerL1 file by customer number. Line 157 declares the procedure’s interface (where *N is a much-needed shorthand for the procedure name). If an RPG data type is declared after the *N, that is the type that the procedure returns. There must be a Return opcode with a variable of that type in the procedure to return the value to the caller. Any variable declarations between the Dcl-Pi and the End-Pi define required arguments that must be passed to the procedure. For simple procedures (those that effectively take the place of subroutines) the Dcl-Pi/End-Pi declarations can be omitted. There is more to explore in the RPG presented than what’s covered in the narrative, but it all follows the patterns and explanations provided in the code narrative.

 

0001  Dcl-Ds Infds;
0002      ActionRequest Char(1)  Pos(369);
0003      CurrentFormat Char(10) Pos(261);
0004  End-Ds;
0005
0006  Dcl-C F01 const(x'31');
0007  Dcl-C F02 const(x'32');
0008  Dcl-C F03 const(x'33');
...
0028  Dcl-C F23 const(x'bb');
0029  Dcl-C F24 const(x'bc');
0030
0031  // Other attention keys
0032  Dcl-C Clear_Key       const(x'bd');
0033  Dcl-C Enter           const(x'f1');
0034  Dcl-C Help            const(x'f3');
0035  Dcl-C PageUp          const(x'f4');
0036  Dcl-C PageDown        const(x'f5');
0037  Dcl-C Print_Key       const(x'f6');
0038  Dcl-C Auto_Enter      const(x'50');

Figure 4B. External source member to define the INFDS data structure and its constants.

Give free a chance!

This article intended to show TR 7.1 free-format ILE RPG in action and also that, with ASNA Mobile RPG, you can put that ILE RPG to work building a vastly non-traditional but very powerful new application type for your IBM i. Reading code is always much harder than writing code, but IBM’s latest RPG refresh substantially raises the bar for being able to write readable RPG. IBM deserves a big tip of the hat for finally coming ‘round and providing us with this powerful, expressive RPG syntax. The IBM i is much better for it and you’ll be a much better RPG programmer using it. Please, please, investigate this new syntax. It is very much worth breaking out of your old school RPG comfort zone!

 

 

Resources for learning TR7.1 ILE RPG

Four Reasons RPG Geezers Should Care About The New Free-Form RPG, Jon Paris in IT Jungle (or anything else that Jon Paris or Susan Ganter have written about RPG—get your Google on!)

Simon Hutchinson’s RPGPGM.COM blog