Retrieve Program Functions Services

This service program example was created to externalize program functions

Externalizing Keys

Since most interactive RPG (and CL, and SQL) programs functions revolve around function keys to react to user instructions I created a service to externalize a significant portion of those operations. Based on work published by Bob Cozzi, Jr. I moved program function key actions and options into database files. This isn't a new concept. I created the first version of the process in 1988 (six years before ILE was available for RPG developers) using subprograms instead of service programs. It signaled a major change in my approach to application design.

Get Function

WIth ILE, the subprograms became service program procedures. The SoftCode service program included the modules necessary to move function keys (and program options) into database tables. Since the functions are external to the program, they may be changed independently of the program code. This creates something similar to a plug-and-play environment for HLL programs. The code below represents the module (a part of the SoftCode System Services) which performs the task of looking up the function requested and returning the instruction to the calling program.


     H DEBUG(*YES)
     H nomain 
      ****************************************************************
      * PROGRAM NAME - SC0080RM                                      *
      *                                                              *
      * FUNCTION     - This is a module designed to support the      *
      *                softcoded program functions.                  *
      *                                                              *
      * PROGRAMMER   - STEVE CROY        04/11/06                    *
      ****************************************************************
      * Prototypes
      *---
      /copy qrpglesrc,SC0000_PR
      *---
     DExecCmd          PR                  EXTPGM('QCMDEXC')
     D  Command                     256
     D  Length                       15  5
      *---
      * Global Variables
      *---
     D KEYMACRO      E DS                  EXTNAME(SCFUNCPF)
     D MACRO         E DS                  EXTNAME(SCOPTNPF)
     D sql             s          32767a   varying
     D SQLSTTOK        C                   '00000'
     D SQLNOMOREROWS   C                   '02000'
     D True            C                   '1'
     D False           C                   '0'
     D CloseLbl        C                   'Close'
     D FetchLbl        C                   'Fetch'
     D OpenLbl         C                   'Open'
     D MoreRows        S              1A   Inz('0')
     D SourceOpen      S              1A   Inz('0')
     D PgmError        S              1A   Inz('0')
     D URLfound        S               N
     D ProgramName     S             10A
     D PanelName       S             10A
     D SQLTable        S             21A
     D SQLStmnt        S             10A
     D TblName         S             10A
     D Sort2           S             10A
     D Msg             S             51A
     D W$CMD           S            256    INZ
     D W$LEN           S             15  5 INZ(256)
      **********************************************************************
      * Get function keys
      **********************************************************************

     P GetFunction     B                   export
     D GetFunction     PI
     D  pgmnam                       10
     D  pnlnam                       10
     D  keyDS                         1
     D  keyID                        10
     D  function                     45
     D  level                         3

     D FN              S             10    DIM(31)
     D K               S              3  0
     D I               S              3  0
     D  thisPgm        S             10
     D  thisPnl        S             10


     D CMDKEY        E DS                  EXTNAME(SCKEYSPF) QUALIFIED
     D   KeyDefined            1     31    DIM(31)

      /free

         cmdkey = FunctionKeys();
         programName = pgmnam;
         panelName = pnlnam;
         thisPgm = pgmnam;
         thisPnl = pnlnam;

         cmdkey = FunctionKeys();
         FN(1) = 'ENTER';
         FN(2) = 'ROLLUP';
         FN(3) = 'ROLLDN';
         FN(4) = 'HELP';
         FN(5) = 'PRINT';
         FN(6) = 'BSPACE';
         FN(7) = 'CLEAR';
         FN(8) = 'F1';
         FN(9) = 'F2';
         FN(10)= 'F3';
         FN(11)= 'F4';
         FN(12)= 'F5';
         FN(13)= 'F6';
         FN(14)= 'F7';
         FN(15)= 'F8';
         FN(16)= 'F9';
         FN(17)= 'F10';
         FN(18)= 'F11';
         FN(19)= 'F12';
         FN(20)= 'F13';
         FN(21)= 'F14';
         FN(22)= 'F15';
         FN(23)= 'F16';
         FN(24)= 'F17';
         FN(25)= 'F18';
         FN(26)= 'F19';
         FN(27)= 'F20';
         FN(28)= 'F21';
         FN(29)= 'F22';
         FN(30)= 'F23';
         FN(31)= 'F24';

         k = %lookup(KeyDS:CMDKEY.KeyDefined:1);  // Lookup key passed

         IF k > 0;
            keyID = fn(k);
         ELSE;
            keyID = *blank;
         ENDIF;

         //*-------------------------------------------------------------
         // Determine what macro is to be returned to the calling pgm
         //*-------------------------------------------------------------

         EXSR @find;

         IF sqlstt <> sqlsttok;
            thisPnl = *blank;
            EXSR @find;
         ENDIF;

         IF sqlstt <> sqlsttok;
            thisPgm = '*SYS';
            thisPnl = *blank;
            EXSR @find;
         ENDIF;

        IF sqlstt <> sqlsttok;
           function = 'Undefined';
        ELSE;
           IF fkautl <= level;
              IF %subst(fmacro:1:1) = '&';
                 w$cmd = %subst(fmacro:2);
                 w$len = %len(%trim(w$cmd));
                 function = '*NOOP';
                 Command( w$cmd : w$len);
              ELSEIF %subst(fmacro:1:5) = '*HELP';
                 URLfound = ShowHelp( programName );
                 IF URLfound;
                    function = '*NOOP';
                 ELSE;
                    HelpText(programName:fpnlid);
                    function = '*NOOP';
                 ENDIF;
              ELSE;
                 function = fmacro;
              ENDIF;
           ELSE;
              function = 'Unauthorized';
           ENDIF;
        ENDIF;

         RETURN;

      /end-free
      *=============================================================
      * Subroutine to find function
      *=============================================================
     CSR   @FIND         BEGSR
     c/Exec SQL
     c+          Set Option Commit = *None
     c/End-Exec
     c/exec sql
     c+ select *
     c+   into :KEYMACRO
     c+   from SCFUNCPF
     c+  where fpgmid=:thisPgm and fpnlid=:thisPnl and fkeyid=:keyID
     c/end-exec
     CSR                 ENDSR

     P GetFunction     E
      **********************************************************************
      * Get function key text
      **********************************************************************
     P GetKeyText      B                   export
     D GetKeyText      PI
     D  pgmnam                       10
     D  pnlnam                       10
     D  keytxt                      720
     D  level                         3
     D KEYS            S             10    DIM(80)                              KEY LIST

     D TEXT            DS           720
     D  INFO                   1    720    DIM(36)

     D SORT            DS           720
     D  ARR                    1    720    DIM(36) ASCEND

     D F               S              3  0
     D I               S              3  0
     D K               S              3  0
     D X               S              3  0
     D R               S              3  0

      /free
         EXSR SetCursor;
         f = 0;
         k = 0;
         x = 0;
         i = 1;
         sort = *blanks;
         text = *blanks;
         EXSR OpnCursor;

         IF SQLStt = SQLSttOK;
            SourceOpen = True;
            MoreRows   = True;
         ELSE;
            SourceOpen = False;
            MoreRows   = False;
            SQLStmnt = OpenLbl;
            SQLTable = 'KeySource';
            SQLError();
         ENDIF;

         DOU MoreRows = false;
            EXSR FetchNext;
            IF MoreRows = True;
               IF (fpnlid = pnlnam or fpnlid = *blank)
                           AND fkautl <= level;
                  r = %lookup(fkeyid:keys:1);
                  IF r = 0;
                     F = F + 1;
                     keys(f) = fkeyid;
                     info(i) = %trim(fkeyid) + '=' + %trim(fktext);
                     i = i + 1;
                  ENDIF;
               ENDIF;
            ENDIF;
         ENDDO;

         sort = text;

         FOR x = 1 to 36;
            IF %subst(arr(x):3:1) = '=';
               arr(x) = 'A' + %subst(arr(x):2);
            ENDIF;
         ENDFOR;

         SORTA ARR;
         CLEAR text;
         i = 1;

         FOR x = 1 to 36;
            IF %subst(arr(x):1:1) = 'A';
               arr(x) = 'F' + %subst(arr(x):2);
            ENDIF;
            IF arr(x)<> *blank;
               info(i) = arr(x);
               i = i+ 1;
            ENDIF;
         ENDFOR;

         IF SourceOpen = True;
            SourceOpen = False;
            EXSR ClsCursor;
            IF SQLStt <> SQLSttOK;
               SQLStmnt = CloseLbl;
               SQLTable = 'File' + ':Source';
               SQLError();
            ENDIF;
         ENDIF;

         keytxt = text;

         RETURN;

         //*=============================================================
         //* Read the  Cursor
         //*=============================================================
        BEGSR FetchNext;
         EXSR GetNext;
         SELECT;
            WHEN SQLStt = SQLSttOK;
                MoreRows = *ON;
            WHEN SQLStt = SQLNoMoreRows;
                MoreRows = *Off;
            OTHER;
                SQLStmnt = FetchLbl;
                SQLTable = 'File' + ':KeySource';
                SQLError();
            ENDSL;
         ENDSR;
      /end-free
      *---------------------------------------------------------------------
      * Declare Cursor
      *---------------------------------------------------------------------
     CSR   SetCursor     BEGSR
     C/Exec SQL
     C+   Declare KeySource scroll Cursor For
     C+      Select *
     C+      From  SCFUNCPF
     C+      Where FPGMID=:PGMNAM
     C+      Order By FPGMID, FPNLID
     C+      For Read Only
     C+      Optimize for 1 Rows
     C/End-Exec
     CSR                 ENDSR
     CSR   ClsCursor     BEGSR
     C/Exec SQL
     C+                  Close KeySource
     C/End-Exec
     CSR                 ENDSR
     CSR   OpnCursor     BEGSR
     C/Exec SQL
     C+                  Open KeySource
     C/End-Exec
     CSR                 ENDSR
     CSR   GetNext       BEGSR
     C/Exec SQL
     C+ FETCH NEXT FROM KeySource INTO :KeyMacro
     C/End-Exec
     CSR                 ENDSR

     P GetKeyText      E

      **********************************************************************
      * Get program option
      **********************************************************************
     P GetOption       B                   export
     D GetOption       PI
     D  pgmnam                       10
     D  pnlnam                       10
     D  optID                        10
     D  option                       45
     D  level                         3

     D  thisPgm        S             10
     D  thisPnl        S             10
      /free
         thisPgm = pgmnam;
         thisPnl = pnlnam;

         EXSR @find;

         IF sqlstt <> sqlsttok;
            thisPnl = *blank;
            EXSR @find;
         ENDIF;

         IF sqlstt <> sqlsttok;
            thisPgm = '*SYS';
            thisPnl = *blank;
            EXSR @find;
         ENDIF;

        IF sqlstt <> sqlsttok;
           option = 'Undefined';
        ELSE;
           IF OPautl <= level;
              IF %subst(omacro:1:1) = '&';
                 w$cmd = %subst(omacro:2);
                 w$len = %len(%trim(w$cmd));
                 option = '*NOOP';
                 Command(w$cmd:w$len);
              ELSEIF %subst(omacro:1:5) = '*HELP';
                 ShowHelp( pgmnam );
                 option = '*NOOP';
              ELSE;
                 option = omacro;
              ENDIF;
           ELSE;
              option = 'Unauthorized';
           ENDIF;
        ENDIF;

        RETURN;

      /end-free
      *=============================================================
      * Subroutine to find program option
      *=============================================================
---> CSR   @FIND         BEGSR
     C/exec sql
     C+ select *
     C+   into :macro
     C+   from SCOPTNPF
     C+  where opgmid=:thisPgm and opnlid=:thisPnl and optnid=:optID
     C/end-exec
<--- CSR                 ENDSR

     P GetOption       E

      **********************************************************************
      * Get program option text
      **********************************************************************
     P GetOptText      B                   export
     D GetOptText      PI
     D  pgmnam                       10
     D  pnlnam                       10
     D  Opttxt                      720
     D  level                         3

     D FA              S             01    DIM(80)                              ACTION
     D OPTS            S             10    DIM(80)                              OPTION LIST

     D TEXT            DS           720
     D  INFO                   1    720    DIM(36)

     D SORT            DS           720
     D  ARR                    1    720    DIM(36) ASCEND

     D F               S              3  0
     D I               S              3  0
     D K               S              3  0
     D X               S              3  0
     D R               S              3  0

      /free
         EXSR SetCursor;
         f = 0;
         k = 0;
         x = 0;
         i = 1;
         sort = *blanks;
         text = *blanks;

         EXSR OpnCursor;

         IF SQLStt = SQLSttOK;
            SourceOpen = True;
            MoreRows   = True;
         ELSE;
            SourceOpen = False;
            MoreRows   = False;
            SQLStmnt = OpenLbl;
            SQLTable = 'File' + ':OptSource';
            SQLError();
         ENDIF;

         DOU MoreRows = false;
            EXSR FetchNext;
            IF MoreRows = True;
               IF (opnlid = pnlnam or opnlid = *blank)
                           AND opautl <= level;
                  r = %lookup(optnid:opts:1);
                  IF r = 0;
                     F = F + 1;
                     opts(f) = optnid;
                     info(i) = %trim(optnid) + '=' + %trim(OPtext);
                     i = i + 1 ;
                  ENDIF;
               ENDIF;
            ENDIF;
         ENDDO;

         sort = text;
         SORTA ARR;
         CLEAR text;
         i = 1;

         FOR x = 1 to 36;
            IF arr(x)<> *blank;
               info(i) = arr(x);
               i = i+ 1;
            ENDIF;
         ENDFOR;

         IF SourceOpen = True;
            SourceOpen = False;
            EXSR ClsCursor;
            IF SQLStt <> SQLSttOK;
               SQLStmnt = CloseLbl;
               SQLTable = 'File' + ':OptSource';
               SQLError();
            ENDIF;
         ENDIF;

         OptTxt = text;

         RETURN;

         //*=============================================================
         //* Read the  Cursor
         //*=============================================================
        BEGSR FetchNext;
        EXSR GetNext;
         SELECT;
            WHEN SQLStt = SQLSttOK;
                MoreRows = *ON;
            WHEN SQLStt = SQLNoMoreRows;
                MoreRows = *Off;
            OTHER;
                SQLStmnt = FetchLbl;
                SQLTable = 'File' + ':OptSource';
                SQLError();
            ENDSL;
         ENDSR;
      /end-free
      *---------------------------------------------------------------------
      * Declare Cursor
      *---------------------------------------------------------------------
     CSR   SetCursor     BEGSR
     C/Exec SQL
     C+          Declare OptSource Cursor For
     C+            Select *
     C+            From  SCOPTNPF
     C+            Where OPGMID=:PGMNAM
     C+            Order By OPGMID, OPNLID
     C+            For Read Only
     C+            Optimize for 1 Rows
     C/End-Exec
     CSR                 ENDSR

     CSR   ClsCursor     BEGSR
     C/Exec SQL
     C+                  Close OptSource
     C/End-Exec
     CSR                 ENDSR

     CSR   OpnCursor     BEGSR
     C/Exec SQL
     C+                  Open OptSource
     C/End-Exec
     CSR                 ENDSR

     CSR   GetNext       BEGSR
     C/Exec SQL
     C+    Fetch Next
     C+       From OptSource
     C+       Into :MACRO
     C/End-Exec
     CSR                 ENDSR

     P GetOptText      E
      **********************************************************************
      * SQL error
      **********************************************************************
     P SQLError        B                   export
     D SQLError        PI
      /free
           Msg = 'SQL error ' + SQLStt  + ' on '
                 + %TrimR(SQLStmnt) + ' for '
                 + %TrimR(SQLTable) + ' table.';
           DSPLY(E) Msg;
           PgmError = True;
        RETURN ;
      /end-free
     P SQLError        E
      **********************************************************************
      * Execute command
      **********************************************************************
     P Command         B
     D Command         PI
     D   Cmd                        256
     D   Len                         15  5
      /free
         MONITOR;
            CALLP ExecCmd( cmd: len );
         ON-ERROR;
            PgmError = True;
         ENDMON;
         RETURN;
      /end-free
     P Command         E
      **********************************************************************
      * Display Function keys
      **********************************************************************
     P DisplayKeys     B                   EXPORT
     D DisplayKeys     PI
     D   KeysIn                     720
     D   LinOut1                     60
     D   LinOut2                     60
     D   KeyIdx                       3S 0
     D   pos           S              3S 0
     D   MoreKeys      S             20a   INZ('F24=More Keys      ')
      /free

         IF KeyIdx > 601                                   ;
            KeyIdx = 0                                     ;
         ENDIF                                             ;

            LinOut1 = %subst(KeysIn:KeyIdx +1 :60)         ;
            LinOut2 = %subst(KeysIn:KeyIdx + 61:60)        ;
            KeyIdx = KeyIdx + 60                           ;

         IF LinOut1 = *blanks                              ;
            LinOut1 = %subst(KeysIn:1:60)                  ;
            LinOut2 = %subst(KeysIn:61:60)                 ;
            KeyIdx = 60                                    ;
         ENDIF                                             ;

         pos = %scan('=':KeysIn:120)                       ;

         IF pos > 120                                      ;
            %subst(LinOut2:41:20) = MoreKeys               ;
         ENDIF                                             ;

         RETURN;
      /end-free
     P DisplayKeys     E

      **********************************************************************
      * Display Program Options
      **********************************************************************
     P DisplayOptions  B                   EXPORT
     D DisplayOptions  PI
     D   OptionsIn                  720
     D   LinOut1                     60
     D   LinOut2                     60
     D   OptIdx                       3S 0
     D   pos           S              3S 0
      /free

         IF OptIdx > 601                                   ;
            OptIdx = 0                                     ;
         ENDIF                                             ;

            LinOut1 = %subst(OptionsIn:OptIdx +1 :60)      ;
            LinOut2 = %subst(OptionsIn:OptIdx + 61:60)     ;
            OptIdx = OptIdx + 120                          ;

         IF LinOut1 = *blanks                              ;
            LinOut1 = %subst(OptionsIn:1:60)               ;
            LinOut2 = %subst(OptionsIn:61:60)              ;
            OptIdx = 120                                   ;
         ENDIF                                             ;

         pos = %scan('=':OptionsIn:120)                    ;

         IF pos > 120                                      ;
            %subst(LinOut2:57:3) = '...'                   ;
         ENDIF                                             ;

         RETURN;
      /end-free
     P DisplayOptions  E


Function Table

The program functions are contained in the table (SCFUNCPF) a keyed physical file. The keys to the table are the program name, record format name, and the function key ID. The field named FMACRO is a free-format text field that, optionally, may be subdivided into multiple fields. This allows a great degree of flexibility in coding HLL applications. The macro instruction can be used to identify different key lists to use, or whether the function action is related to an update, delete, insert, or simply view only.

     A****************************************************************
     A* PHYSICAL FILE- SCFUNCPF                                      *
     A*                                                              *
     A* FUNCTION     - This file provides a set command function     *
     A*                macro instructions to be executed from a      *
     A*                HLL, or the function editor program.          *
     A*                                                              *
     A* PROGRAMMER   - Steve Croy        04/11/06                    *
     A****************************************************************
     A****************************************************************
     A*               FILE ATTRIBUTE SECTION                         *
     A*                                                              *
     A*  KEY FIELDS: FPGMID, FPNLID, FKEYID                          *
     A*  MAXMBRS   : 1                                               *
     A*                                                              *
     A****************************************************************
     A          R RSCFUNC                   TEXT('FUNCTION KEY ACTIONS')
      *
     A            FPGMID        10          TEXT('PROGRAM ID')
     A                                      COLHDG('Program ID')
     A            FPNLID        10          TEXT('PANEL ID')
     A                                      COLHDG('Panel' 'ID')
     A            FKEYID        10          TEXT('FUNCTION KEY ID')
     A                                      COLHDG('Function' 'Key')
     A            FMACRO        45          TEXT('FN KEY ACTION')
     A                                      COLHDG('Macro' 'Action')
     A            FKTEXT        20          TEXT('TEXT DESCRIPTION')
     A                                      COLHDG('Description')
     A            FKAUTL         3          TEXT('AUTHORITY LEVEL')
     A                                      COLHDG('Authority' 'Level')
     A          K FPGMID
     A          K FPNLID
     A          K FKEYID


Macro Flexibility

Subdividing the macro text field introduces a greater range of flexibility into the HLL application. The macro can determine the name of the program to call, as well as the parameter list to use when invoking the subprogram. If a called program is updated to use a different argument, then the program using the macro may have to be changed. But by using the macro, new programs can be added if they use an existing parameter list. This allows a staged implementation in modular development. For example: The interactive session with list presentation may be placed into production without the suite of services being complete. Based on a pre-defined argument, print, update, insert, and delete functions can be added as development on those modules are complete.