Someone asked me if I could recommend an example simple subfile program written in "RPG/free". After a few minutes of Googling most of the examples I found were in RPGIII, a few were in fixed format RPGLE, and a couple had free format Calculations. This was rather disappointing as RPGLE is over 21 years old, free format calculations 14 years old, even free format definitions RPG is in its third year. All of the examples I found looked overly complicated for a beginner, so I decided to create this post showing a simple subfile program written in modern RPG.
The examples in this post are the way I would write a simple subfile program using totally free RPG. I am sure that my examples are not the way all other IBM i developers would write theirs. There are probably as many ways to write a subfile program as there are programmers who write them, each with his or her own coding peccadillos.
This example is for a "Load all" subfile (subfile size = 9999). If you want to use an "Expanding" subfile (subfile size > subfile page) or a "Page at a time" subfile (subfile size = subfile page) you can use this example as a starting point.
Let me start with display file. The example display file consists of four parts:
- File level keywords
- Record format: SFL01 the subfile record
- Record format: CTL01 the subfile control record
- Record format: REC01 the bit at the bottom to display the function keys
This is not going to be a tutorial to explain every keyword in the display file, I am just going to point out certain lines and explain why they are the way they are.
Let me start with the file level keywords:
01 A REF(ITMMST) 02 A DSPSIZ(24 80 *DS3) 03 A PRINT 04 A INDARA 05 A CA03(03 'F3=Exit')
Line 1: This subfile is going to display data from the file ITMMST, therefore, in my opinion it is best that I use the REF keyword so I can just "refer" to the field names from the file when defining fields in the rest of the display file.
Line 4: Those of you who are regular readers of this website will know that I a proponent of using the Indicator Communication Area for passing indicator values to and from display and printer files and a RPG program. I am not going to go into detail about it as I have written about it previously in the post No More Number Indicators.
The subfile record is very simple with just four fields. There is a single character field that will be used so that the user can enter a value to, for example, edit, view, delete, etc. the selected record.
06 A R SFL01 SFL 07 A Z1RRN 4S 0H 08 A Z1OPT 1A B 5 3 09 A ITMNBR R O 5 6 10 A ITMDESC R O 5 24
The subfile control record format is longer than the subfile record format, but it is still very simple.
11 A R CTL01 SFLCTL(SFL01) 12 A SFLSIZ(9999) 13 A SFLPAG(0017) 14 A OVERLAY 15 A 31 SFLDSP 16 A 30 SFLDSPCTL 17 *** A N30 SFLDLT 17 A N30 SFLCLR 18 A 30 SFLEND(*MORE) 19 A CA05(05 'F5=Refresh') 20 A 1 2USER 21 A COLOR(BLU) 22 A 1 63TIME 23 A COLOR(BLU) 24 A 1 72DATE 25 A EDTCDE(Y) 26 A COLOR(BLU) 27 A Z1SCREEN 12A O 2 2COLOR(BLU) 28 A 2 72SYSNAME 29 A COLOR(BLU) 30 A 3 2'Position to:' 31 A COLOR(BLU) 32 A Z1POSITIONR B 3 15REFFLD(ITMNBR) 33 A COLOR(BLU) 34 A 4 2'Opt Item number + 35 A Item description + 36 A ' 37 A DSPATR(UL) 38 A DSPATR(HI)
Line 12: As this is a "Load all" subfile I want my subfile to be 9,999 records.
Line 13: 17 subfile records will appear on each screen (page).
Lines 15 – 18: These are the subfile keywords that are needed to control the display of the subfile. I only have to use two indicators to condition them. Indicator 31 is used just to control the display of the subfile, if there are no records in the subfile I do not want to display it. Indicator 30 is used for all the display of the subfile control, and I want "More..." to appear at the bottom of the subfile page rather than a "+" as I think it is more aesthetically pleasing.
Amendment: Brian Rusch made suggestion in a comment below that makes good sense. Rather than use a SFLDLT on line 17 a SFLCLR is better.
Lines 20 – 26: I always display the user id, date, and time on all my display files.
Lines 27 – 29: I also display the "screen id", which is the program name with "-1" on the end. If the user has a problem with the program I tell them to give me the name of the screen and I know what program to work with. I never hard code this field. It is taken from the RPG program's data structure. This way if I copy or rename the program it will always show the current program's name.
Lines 32 – 33: I am providing a "Position to" field so that the user can position the start of the subfile to a record in the file, and then the program loads the subfile from that point.
The last record format really needs no explanation:
38 A R REC01 39 A 23 3'F3=Exit F5=Refresh' 40 A COLOR(BLU)
And onto the RPG. For those of you using a release of IBM i that does not allow for free format definitions I have the fixed format equivalents at the bottom of this post here. If you are not able to use the totally free RPG, but can use free format definitions, then you will need to start your code in the eighth column, and ignore the **FREE. I use what I call "open" subprocedures in place of subroutines. But if you prefer use subroutines you can replace the subprocedures, by replacing the call to the procedures with EXSR, DCL-PROC with BEGSR, and END-PROC with ENDSR.
Let me start with the definitions part of the my example program:
01 **free 02 ctl-opt option(*nodebugio:*srcstmt:*nounref) dftactgrp(*no) ; 03 dcl-ds PgmDs psds qualified ; 04 PgmName *proc ; 05 end-ds ; 06 dcl-f SFL_DSPF workstn indds(Dspf) sfile(SFL01:Z1RRN) ; 07 dcl-c MaxSfl 9999 ; 08 dcl-ds Dspf qualified ; 09 Exit ind pos(3) ; 10 Refresh ind pos(5) ; 11 SflDspCtl ind pos(30) ; 12 SflDsp ind pos(31) ; 13 end-ds ; 14 dcl-f ITMMST keyed ; 15 dcl-s PrvPosition like(Z1POSITION) ; 16 Z1SCREEN = %trimr(PgmDs.PgmName) + '-1' ;
Line 1: All totally free RPG has to start with a **FREE.
Line 2: As I am using subprocedures I need the DFTACTGRP keyword in my control options.
Lines 3 – 5: This is my program status data structure. I use this to get the program's name which I use for the screen name on the subfile control record format.
Line 6: This is the definition for the display file that contains the subfile. I need the INDDS to give the name of my Indicator Communication Area data structure. The SFILE keyword is needed for each subfile, I need to give the name of the subfile record format and the name of the field that is used as a "numeric key" for the subfile.
Line 7: This constant contains the maximum number of records my subfile can contain.
Line 8 – 13: This is the Indicator Communication Area data structure. What I like about these data structures is I can map an indicator from the display file to a meaningful name. For example: indicator 3 from the display file will be known as Dsp.Exit as the data structure subfields are qualified, see the QUALIFIED on line 8. This is so much better than *IN03 or *INKC.
Line 14: This is the definition for the Item Master file, which I am reading in keyed order.
Line 15: PrvPosition is defined to be like the screen field Z1POSITION. I will be using this to contain the previous value of Z1POSITION. In other programs I have coded this as a hidden field in the subfile control, but I am keeping this example simple.
Line 16: This is not a definition line. It is where I create the screen name by appending "-1" onto the end of the program's name.
To learn more about using free format definitions you ought to read the following:
- File definition in RPG all free
- Defining variables in RPG all free
- Mixing it up with RPG all free
- Defining Procedures in RPG all free
This next section is the code I need to handle the subfile. As I am using a "Load all" I do not have to code of the user pressing the Page Up or Down key the subfile does all that for me behind the scenes.
17 setll *loval ITMMSTR ; 18 LoadSubfile() ; 19 dow (1 = 1) ; 20 write REC01 ; 21 exfmt CTL01 ; 22 if (Dspf.Exit) ; 23 leave ; 24 elseif (Dspf.Refresh) ; 25 Z1POSITION = ' ' ; 26 PrvPosition = ' ' ; 27 setll *loval ITMMSTR ; 28 LoadSubfile() ; 29 iter ; 30 elseif (Z1POSITION <> PrvPosition) ; 31 PrvPosition = Z1POSITION ; 32 setll Z1POSITION ITMMSTR ; 33 LoadSubfile() ; 34 iter ; 35 endif ; 36 if (Dspf.SflDsp) ; 37 ReadSubfile() ; 38 endif ; 39 enddo ; 40 *inlr = *on ;
Line 17: I don't really need this line as the file is opened at the first record.
Line 18: This is the call to the procedure that loads the subfile.
Line 20: Record format REC01 displays the function keys at the bottom of the screen. I WRITE it here so it overlays the previous screen displayed, this is especially useful if there are no records to display in the current subfile.
Line 21: The EXFMT of the subfile control record means that the processing of the program remains "within" the subfile until Enter or one of the valid Function keys are pressed. No code necessary for Page Up or Down as the subfile handles that itself.
Lines 22 – 23: As Dsp.Exit is the equivalent of indicator 3 this code is only executed if F3 has been pressed.
Lines 24 – 29: This is the code for when the F5 key has been pressed. I use IF and ELSEIF rather than the SELECT operation code, you can learn more about ELSEIF here. All this code does is to reload the subfile from the start of the file.
Lines 30 – 34: If the user types something into the "Position to" then I use SETLL to reposition the file pointer before reloading the subfile.
Lines 36 – 38: If none of the above has happened I want to see if the user has entered a value into the option field of the record they want to do something with, which is done in the subprocedure ReadSubfile. But I only want to call that subprocedure if there are any records in the subfile. The indicator Dsp.SflDsp will only be on if there are records in the subfile.
The subprocedure LoadSubfile is where the subfile is loaded:
41 dcl-proc LoadSubfile ; 42 Dspf.SflDspCtl = *off ; 43 Dspf.SflDsp = *off ; 44 write CTL01 ; 45 Dspf.SflDspCtl = *on ; 46 Z1OPT = ' ' ; 47 for Z1RRN = 1 to MaxSfl ; 48 read ITMMSTR ; 49 if (%eof) ; 50 leave ; 51 endif ; 52 write SFL01 ; 53 endfor ; 54 if (Z1RRN > 1) ; 55 Dspf.SflDsp = *on ; 56 endif ; 57 end-proc ;
Lines 42 – 44: This is where the existing subfile is deleted, Dspf.DspSflCtl = *off, and as I am not sure if will be any records I set the indicator that displays the subfile, Dsp.DspSfl, to off. The deleting of the existing subfile does not occur until I write the subfile control record.
Line 45: I am going to want the subfile control record format to display at all times so this is where I set on the indicator that flags that I want to display it.
Line 46: I clear the option field so that all of the option fields in the subfile will be blank.
Line 47: I prefer to use the FOR operation code when I need to perform a loop a number of times. If you have not used it you ought to check out the post FOR replaces DO in RPGLE. Z1RRN is the "key" field for the subfile, in every subfile record Z1RRN must contain a unique sequential number. The statement says set Z1RRN to one, then increment it by one each time the For-loop cycles for the number of times held in the constant MaxSfl.
Lines 48 – 52: The processing within the For-loop is very simple. Read a record, write to the subfile, until either end of the file or the loop has been performed the prescribed number of times.
Line 54 – 56: I only want to display the subfile if it contains any records. If only one record has been written to the subfile Z1RRN will contain two, and if more record were written the number would be greater.
The other subprocedure handles any values entered into the option field of the subfile.
58 dcl-proc ReadSubfile ; 59 dow (1 = 1) ; 60 readc SFL01 ; 61 if (%eof) ; 62 leave ; 63 endif ; //Do something depending on value in Z1OPT 64 Z1OPT = ' ' ; 65 update SFL01 ; 66 enddo ; 67 end-proc ;
Lines 60 – 63: RPG provides us with the READC operation code for use with subfiles. It only reads changed subfile records, which will be the ones where the user has entered a value in the option field, as that is the only field in the subfile that can receive input. After all the changed records have been read, or there are no changed records, the end of file indicator is set on.
Lines 64 – 65: After whatever processing is performed I need to clear the option field of the subfile record, and then I need to update the subfile record to clear the option field on the display.
As I said this is a very simple example subfile. You can make yours far more complicated if you so desire. I hope this will be of use to people writing their first subfile program, and as another example of using free format definition RPG.
This article was written for IBM i 7.2.
Fixed format definitions
01 H option(*nodebugio:*srcstmt:*nounref) 02 FSFL_DSPF CF E WORKSTN indds(Dspf) sfile(SFL01:Z1RRN) 03 FITMMST IF E K DISK 04 D PgmDs SDS qualified 05 D PgmName *proc 06 D Dspf DS qualified 07 D Exit 3 3N 08 D Refresh 5 5N 09 D SflDspCtl 30 30N 10 D SflDsp 31 31N 11 D MaxSfl C 9999 12 D PrvPosition S like(Z1POSITION) /free
In this program I replaced the "open" subprocedures with subroutines, therefore, I do not need the DFTACTGRP(*NO) in the Header specifications.Return