Monday, May 9, 2016

Example subfile program using modern RPG

rpg free subfile

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:

  1. File level keywords
  2. Record format: SFL01 the subfile record
  3. Record format: CTL01 the subfile control record
  4. 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:

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

30 comments:

  1. Actually, RPG-Free goes back to roughly 1993 when Paul Conte released it as a PC application. I have been using it from near the beginning. I would download the source code to the IFS using an RPG program, and run it through Paul's program to make the C specs free format, with indenting. After I made my changes, I ran the other half of Paul's program that would re-format it into RPG III, and upload it from the IFS to the usual source file. Free format is the best thing for programmers, IMHO. Thanks for the examples, Simon!

    ReplyDelete
  2. Just curious, why use SFLDLT rather than SFLCLR?

    ReplyDelete
    Replies
    1. There is no good reason, and you are probably right that a SFLCLR would be more efficent.

      Delete
  3. Qualified Displayfiles should be the next step

    ReplyDelete
    Replies
    1. In a more complicated program I agree, the use of qualified fields would make it easier to understand.

      Delete
  4. Finally! I meet another programmer who uses Dow 1 = 1; for a loop. People look at me funny when they see that in my code and ask why I don't use dou %eof or something like that.

    ReplyDelete
    Replies
    1. IMHO %shtdn() is more self-documenting, it returns '1' if the system operator has requested shutdown:
      dou %shtdn();
      doSomethingInNeverEndingProgram();
      enddo;

      Regards
      Jan

      Delete
  5. I like throw in an ERASE to remove the previous subfile rows from the screen if the next search returns nothing.
    A N31 ERASE(SFL01)

    Chris Ringer

    ReplyDelete
  6. Excellent simple subfile example!
    I did have a problem with MaxSfl = 9999.
    I loaded my entire product master into ITMMSTR.
    The "For Z1RRN1 = 1 to MaxSfl" statement will overflow the Z1RRN field.
    You can either set MAxSfl to 9998 or change the upper bounds on the For statement to (MaxSfl - 1).
    I changed MaxSfl to a variable (not a constant) like Z1RRN and set it to *HIVAL - 1.

    ReplyDelete
  7. One thing i see in many subfiles is that once you position the file you can't page backwards without doing a refresh. Do you have such an example?

    ReplyDelete
    Replies
    1. I would argue that once you have positioned to something you normally do not need to look before the first match. If you did you would need to have a "screen at a time" subfile, rather than a "load all" as shown in this example.

      Delete
  8. Yep, agree with you. But it's still the most common complaint I receive.

    ReplyDelete
  9. It's a really good example. Just a suggest to do a loop. You can define/declare an indicator ( e.g. wTrue ind) and then coding Dow wTrue;

    ReplyDelete
  10. Excellent post, thank you!
    In file video, what is the real difference between EXCEPT and WRITE?

    ReplyDelete
    Replies
    1. For EXCEPT you need to use Output specifications. Before the introduction of the %FIELDS built in function it was the only way to control which fields are updated.

      Delete
  11. gr8 job Simon. I am reading your article one by one.. Please keep up the good work and help us grow into gr8 as400 professionals like you !

    ReplyDelete
  12. Excellent Post and helping me a lot in sharping my AS400 Skills.

    ReplyDelete
  13. Hi all,

    How would you turn this into a subfile read when more than one file is concerned and based on a match to some reference in a table
    ie
    setll (variable) MYFILE
    reade MYFILE
    dow not %EOF

    chain (variable) ANOTHERFILE
    variable2 = something
    load subfile()
    reade MYFILE
    enddo

    ReplyDelete
  14. I would put all chains to other files within the LoadSubfile subprocedure, like this:

    dcl-proc LoadSubfile ;

    setll (Variable) FILE1 ;

    dow (1 = 1) ;

    reade (Variable) FILE1 ;
    if (%eof) ;
    leave ;
    endif ;

    chain (Variable) FILE2 ;
    if (%found) ;
    ScreenField2 = something ;
    else ;
    ScreenField2 = ' ' ;
    endif ;

    enddo ;

    end-proc ;

    (Nowadays I would build a SQL View to join the files all together, and then use a multiple row fetch. This is so much faster than RPG reads and chains)

    ReplyDelete
  15. That is brilliant Simon :)
    Yes, I do use mostly views also when returning multiple rows of data, but I'm a complete novice to subfiles as I don't usually have to use them at all due to the fact that I'm normally displaying the data in html tables.
    the example you gave me here is perfect as speed is not an issue because there will never be more than a couple of dozen rows returned. Thank you for the example!

    James

    ReplyDelete
    Replies
    1. If you are familiar with Views, etc then look at this post here as it gives an example using SQL for the file input.

      Delete
    2. Dear Simon
      Can you please kindly advice me : I am trying to work with RPG free on OS V7R3M0, switched off the syntax check but compiler does not recognizing the code as free ( I upload this program code as an example ) What Can I check / perform to overcome ?
      Thank you

      Delete
    3. Blogger changed the way links worked and the link above does not work.
      This link should work Using SEU with totally free RPG.

      Delete
  16. dear Simon just more one note:
    despite you mention in this article that (quote follows ):
    "... As I am using subprocedures I need the DFTACTGRP keyword in my control options." my V7R3M0 compiler did not created module until I removed this DFTACTGRP keyword. Can you pleaseexplain why ?

    ReplyDelete
    Replies
    1. The difference is I am creating a program, you were creating a module.

      Delete
  17. Simon, some of the links in the comments (i.e. the example of using sql for file input via a view) do not respond when I click on them.

    ReplyDelete
    Replies
    1. I know that is an issue. Blogger changed the template which affects the way links work in comments. Therefore, some links work and some do not (which is very annoying to me).

      Delete
  18. Simon, If we press Enter key in some random page, will it stay on the same page ?

    ReplyDelete
    Replies
    1. There is no way I can answer this question as it all depends on the program that displays the page.

      Delete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.