Use a message subfile to return error information to the user.
Anyone who has been following this column for a while probably knows that I believe in using messages whenever possible. Messages are a wonderful way of keeping textual information out of my code, thereby allowing me to reword some text without having to change an application program, a command definition, and so on. One place where I find that many developers (CL and otherwise) are underutilizing messages is in the area of display file error-reporting.
Many display files are still being coded using the ERRMSG DDS keyword and the associated (hard-coded) message text for reporting an input validation error to a user. While there's nothing wrong with ERRMSG, today we will look at how you can use a message subfile to return error information to the user. Using a message subfile, you can decide tomorrow to clarify error-related textual information and simply change a message description—with no need to even recompile the display file, let alone the program, in order to start using the new text. We'll also take a quick look at how to use status messages to let the user know that the program has been running for a while and everything is OK.
In our sample application, we'll prompt the user for various inputs, validate the input provided, and then either return error-related information to the user or run the request. The inputs the user will provide are a starting and ending year for a report, the type of report, whether the report should be of a summary or detail nature, and whether the report should be run in batch or interactively. This last choice, running the report in batch or interactively, is only being done to show how to send status messages, not to suggest that I would actually allow a user to run batch-type reports in an interactive session.
The source for the display file is shown below and, assuming the source is stored in member CLRPTDSPF (CL Report Display File) of source file QDDSSRC, can be created using the CRTDSPF command CRTDSPF FILE(CLRPTDSPF) SRCFILE(QDDSSRC).
A R CRITERIA OVERLAY
A CA03(03 'Exit')
A CF10(10 'Run immediate')
A 1 2SYSNAME
A 1 70DATE EDTCDE(Y)
A 2 2USER
A 2 34'Sample Screen'
A COLOR(WHT)
A 2 70TIME
A 4 2'Start year . . . .'
A STRYEAR 4Y 0B 4 22EDTCDE(4)
A 5 2'Ending year . . .'
A ENDYEAR 4Y 0B 5 22EDTCDE(4)
A 7 2'Report type . . .'
A REPORT 1Y 0B 7 22EDTCDE(4)
A 8 5'1 - Report 1'
A 9 5'2 - Report 2'
A 10 5'3 - Report X'
A 12 2'Report detail . .'
A RPT_DTL 1A B 12 22
A 12 24'Y/N'
A 23 2'F3=Exit'
A 23 15'F10=Run immediately'
A*************************************************************
A R MSGSFL SFL
A SFLMSGRCD(24)
A MSGKEY SFLMSGKEY
A PGM SFLPGMQ
A*************************************************************
A R MSGSFLCTL SFLCTL(MSGSFL)
A SFLINZ
A SFLPAG(1)
A SFLSIZ(2)
A SFLDSP SFLDSPCTL
A N99 SFLEND
A PGM SFLPGMQ
There are three record formats defined within CLRPTDSPF. Record format CRITERIA is the format used to prompt the user for the report. Record formats MSGSFL and MSGSFLCTL are used for controlling how messages are shown to the user.
For demonstration purposes, we will be creating several messages using the Add Message Description (ADDMSGD) command. As shown below, the messages will be added to message file USERMSGF in library VINING. The error message IDs will be prefixed by ERR, status message IDs by STS, and informational message IDs by INF. Feel free to use any library, message file, and prefix you would like; there's nothing magic about any of these values. If you do not currently have a message file handy, you can use the Create Message File (CRTMSGF) command as with CRTMSGF MSGF(VINING/USERMSGF). With this introduction, create the following message descriptions:
ADDMSGD MSGID(ERR0101) MSGF(VINING/USERMSGF)
MSG('Start date must be within the last four years')
ADDMSGD MSGID(ERR0102) MSGF(VINING/USERMSGF)
MSG('End date cannot be before start date')
ADDMSGD MSGID(ERR0103) MSGF(VINING/USERMSGF)
MSG('A valid report type must be specified')
ADDMSGD MSGID(ERR0104) MSGF(VINING/USERMSGF)
MSG('Report detail must be Y or N')
ADDMSGD MSGID(ERR0105) MSGF(VINING/USERMSGF)
MSG('Report detail Y is only valid for report type 2')
ADDMSGD MSGID(STS0101) MSGF(VINING/USERMSGF)
MSG('Report is being generated, this may take a while.')
ADDMSGD MSGID(INF0101) MSGF(VINING/USERMSGF)
MSG('Report has been completed')
ADDMSGD MSGID(INF0102) MSGF(VINING/USERMSGF)
MSG('Report has been submitted')
Having created the display file CLRPTDSPF and the message descriptions shown above, you can now run the CL program below, which validates user input, reports any user input errors that are found, and (in theory) runs a report. Assuming the following CL source is in member CLRPT (CL Report) of source file QCLSRC, you can create the program into library VINING using the command CRTBNDCL PGM(VINING/CLRPT).
Pgm
DclF File(CLRptDspF)
Dcl Var(&ErrorFnd) Type(*Lgl)
Dcl Var(&MinYear) Type(*Dec) Len(4 0)
Dcl Var(&DateTime) Type(*Char) Len(20)
ChgVar Var(&Pgm) Value('CLRPT')
RtvJobA DateTime(&DateTime)
ChgVar Var(&StrYear) Value(%sst(&DateTime 1 4))
ChgVar Var(&EndYear) Value(&StrYear)
ChgVar Var(&Rpt_Dtl) Value('N')
ChgVar Var(&MinYear) Value(&StrYear - 4)
RmvMsg Clear(*All)
DoWhile Cond(&IN03 *NE '1')
SndF RcdFmt(MsgSFLCtl)
SndRcvF RcdFmt(Criteria)
ChgVar Var(&ErrorFnd) Value('0')
RmvMsg Clear(*All)
If Cond(&IN03 = '1') Then(Leave)
If Cond(&StrYear *LT &MinYear) Then(Do)
SndPgmMsg MsgID(ERR0101) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
EndDo
If Cond(&EndYear *LT &StrYear) Then(Do)
SndPgmMsg MsgID(ERR0102) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
EndDo
If Cond((&Report *NE 1) *And (&Report *NE 2) *And +
(&Report *NE 3)) Then(Do)
SndPgmMsg MsgID(ERR0103) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
EndDo
If Cond((&Rpt_Dtl *NE 'Y') *And +
(&Rpt_Dtl *NE 'N')) Then(Do)
SndPgmMsg MsgID(ERR0104) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
EndDo
If Cond((&Report *NE 2) *And +
(&Rpt_Dtl *EQ 'Y')) Then(Do)
SndPgmMsg MsgID(ERR0105) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
EndDo
Select
When Cond(&ErrorFnd) Then(Iterate)
When Cond(&IN10 = '1') Then(Do)
SndPgmMSg MsgID(STS0101) MsgF(UserMsgF) +
ToPgmQ(*Ext) MsgType(*Status)
DlyJob Dly(5)
SndPgmMsg MsgID(INF0101) MsgF(UserMsgF) +
ToPgmQ(*Same)
EndDo
Otherwise Cmd(Do)
SbmJob Cmd(Signoff)
RmvMsg Clear(*All)
SndPgmMsg MsgID(INF0102) MsgF(UserMsgF) +
ToPgmQ(*Same)
EndDo
EndSelect
EndDo
RmvMsg Clear(*All)
EndPgm
For now ignoring the message subfile support, the program declares display file CLRPTDSPF and a few CL variables related to user-input validation. The variables declared are &ErrorFnd (a logical variable which, if on ('1'), indicates that one or more validation checks failed), &MinYear (a 4-digit decimal variable with zero decimal positions representing the minimum year for which a report can be run), and &DateTime (a 20-byte character variable representing the current date and time in YYYYMMDDHHMMSSmmmmmm format).
The program then does the following:
1. Sets initial default values for various user inputs using the current year for the report start year (&StrYear), the current year for the report end year (&EndYear), and 'N' for report detail (&Rpt_Dtl)
2. Calculates the earliest year the report can be run for (&MinYear) by subtracting four years from the current year
3. Enters into a DOWHILE loop where the program:
a. Prompts the user for report criteria by sending and receiving record format CRITERIA
b. Sets the &ErrorFnd logical variable to off ('0') indicating that no user validation errors have been found
c. Checks if command key 3 was used, in which case the program returns
d. Validates the user inputs. If any errors are found, the program redisplays record format CRITERIA with all validation errors listed in a message subfile located on line 24 of the workstation. If no errors are found and command key 10 was used, the program runs the report interactively while displaying a status message on line 24 letting the user know the report is being run (and to be patient). If no errors are found and command key 10 was not used, the program submits the report to run in batch and displays a message on line 24 letting the user know the report has been submitted.
e. Re-runs the DOWHILE loop, allowing the user to run another report
Validate the User Inputs
The remainder of this month's column will focus on the preceding "Validate the user inputs" step. Next month, we'll look at the nitty-gritty details of constructing the message subfile. And note that two of the steps shown above—"Sets the &ErrorFnd logical variable to off" and "Checks if command key 3 was used"—can be reversed. It's simply my preference to clear error-related variables immediately and unconditionally.
The error validation is quite straightforward, with each validation check being done in a manner such as this:
If Cond(&StrYear *LT &MinYear) Then(Do)
SndPgmMsg MsgID(ERR0101) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
EndDo
For instance, if the requested report starting year (&StrYear) is less than the report's earliest supported year (&MinYear), then the CL code snippet shown above will…
- send an appropriate message (such as ERR0101) to the program queue of the program running the Send Program Message (SNDPGMMSG) command—in other words, itself. The type of message being sent will be, due to the defaulted message type (MSGTYPE) parameter of the SNDPGMMSG command, informational. As we will see in a future article, other message types can be used.
- set the &ErrorFnd variable to on, indicating that a validation error has been encountered.
After performing all validation checks, the program checks to see if any error was encountered (condition &ErrorFnd is true) and, if so, writes the message subfile associated with the current program (using record format MSGSFLCTL) and then writes and waits for further input using the CRITERIA record format.
As mentioned previously, next month we'll look at the DDS details of implementing the message subfile (record formats MSGSFL and MSGSFLCTL). As this article does, however, provide the DDS source for the subfile record formats, feel free to "skip ahead" and do your own study of message subfiles. Independent study certainly never hurts.
Before ending this column, though, I want to point out that the CLRPT program also sends messages in non-error situations. To mimic the running of a report interactively, CLRPT sends a status message (STS0101 – 'Report is being generated, this may take a while.') and then delays for five seconds (this delay giving you a chance to see the message). Sending a status message, and having the message appear on line 24 of the display, is as simple as using the SNDPGMMSG command, specifying a MSGTYPE of *STATUS, and sending the message to the external message queue: TOPGMQ(*EXT). This part of the sample program has no dependency on whether or not a message subfile is being used.
Message subfiles can also be used for more than just error conditions. When the report is run interactively (using command key 10) and has "completed" or when the report has been submitted for running in batch, CLRPT will also send a message (INF0101 and INF0102, respectively). These messages, unlike the STS0101 status message, are displayed to the user using message subfile support, letting the user know what has happened with the latest request, but they clearly do not represent an error situation.
And last but not least, you may notice that the fields in error are not highlighted to the user (as ERRMSG would have done). This can be easily remedied by adding appropriate DSPATR(RI) conditioning to the display file and CLRPT program. To highlight the start year being prior to &MinYear, as an example, you could add the following:
- To the CLRPTDSPF display file
A STRYEAR 4Y 0B 4 22EDTCDE(4)
A 51 DSPATR(RI) <- new line
- To the CLRPT setting off of &ErrorFnd
ChgVar Var(&ErrorFnd) Value('0')
ChgVar Var(&IN51) Value('0') <- new line
- To the CLRPT error validation
If Cond(&StrYear *LT &MinYear) Then(Do)
SndPgmMsg MsgID(ERR0101) MsgF(UserMsgF) +
ToPgmQ(*Same)
ChgVar Var(&ErrorFnd) Value('1')
ChgVar Var(&IN51) Value('1') <- new line
EndDo
With these simple changes, you get the flexibility of messages and the preciseness of highlighting the fields that are in error.
More CL Questions?
Wondering how to accomplish a function in CL? Send your CL-related questions to me at
LATEST COMMENTS
MC Press Online